From cff9dab650b8bc0e453be6b312a96b778fdcbc93 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Tue, 14 Dec 2021 03:11:04 +0000 Subject: [PATCH 001/924] Remove combo scaling and change miss penalty --- .../Difficulty/OsuPerformanceCalculator.cs | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 8d45c7a8cc..6de6572b93 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -101,13 +101,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty aimValue *= lengthBonus; - // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (effectiveMissCount > 0) - aimValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), effectiveMissCount); - - // Combo scaling. - if (Attributes.MaxCombo > 0) - aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0); + aimValue *= Math.Pow(0.97, effectiveMissCount); double approachRateFactor = 0.0; if (Attributes.ApproachRate > 10.33) @@ -151,13 +146,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); speedValue *= lengthBonus; - // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (effectiveMissCount > 0) - speedValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875)); - - // Combo scaling. - if (Attributes.MaxCombo > 0) - speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0); + speedValue *= Math.Pow(0.97, effectiveMissCount); double approachRateFactor = 0.0; if (Attributes.ApproachRate > 10.33) @@ -238,9 +228,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty 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)); + flashlightValue *= Math.Pow(0.97, effectiveMissCount); // Combo scaling. if (Attributes.MaxCombo > 0) From 86ad42a7440ec0a1478e2030209c2675985e3220 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Tue, 14 Dec 2021 17:47:41 +0000 Subject: [PATCH 002/924] Nerf length bonus --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 6de6572b93..6ade8c1732 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double aimValue = Math.Pow(5.0 * Math.Max(1.0, rawAim / 0.0675) - 4.0, 3.0) / 100000.0; // Longer maps are worth more. - double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + + double lengthBonus = 0.95 + 0.25 * Math.Min(1.0, totalHits / 2000.0) + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); aimValue *= lengthBonus; @@ -142,7 +142,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double speedValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.SpeedStrain / 0.0675) - 4.0, 3.0) / 100000.0; // Longer maps are worth more. - double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + + double lengthBonus = 0.95 + 0.25 * Math.Min(1.0, totalHits / 2000.0) + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); speedValue *= lengthBonus; From 489aa43b1bab6efeb0ed05e97ec66181dfe3b4f4 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Tue, 14 Dec 2021 19:57:36 +0000 Subject: [PATCH 003/924] Make miss penalty harsher --- .../Difficulty/OsuPerformanceCalculator.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 6ade8c1732..0459c64c0e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -96,13 +96,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty double aimValue = Math.Pow(5.0 * Math.Max(1.0, rawAim / 0.0675) - 4.0, 3.0) / 100000.0; // Longer maps are worth more. - double lengthBonus = 0.95 + 0.25 * Math.Min(1.0, totalHits / 2000.0) + + double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); aimValue *= lengthBonus; if (effectiveMissCount > 0) - aimValue *= Math.Pow(0.97, effectiveMissCount); + aimValue *= Math.Pow(0.96, effectiveMissCount); double approachRateFactor = 0.0; if (Attributes.ApproachRate > 10.33) @@ -142,12 +142,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty double speedValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.SpeedStrain / 0.0675) - 4.0, 3.0) / 100000.0; // Longer maps are worth more. - double lengthBonus = 0.95 + 0.25 * Math.Min(1.0, totalHits / 2000.0) + + double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); speedValue *= lengthBonus; if (effectiveMissCount > 0) - speedValue *= Math.Pow(0.97, effectiveMissCount); + speedValue *= Math.Pow(0.96, effectiveMissCount); double approachRateFactor = 0.0; if (Attributes.ApproachRate > 10.33) @@ -229,7 +229,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty flashlightValue *= 1.3; if (effectiveMissCount > 0) - flashlightValue *= Math.Pow(0.97, effectiveMissCount); + flashlightValue *= Math.Pow(0.96, effectiveMissCount); // Combo scaling. if (Attributes.MaxCombo > 0) From bac4cfed50be4e3d1e0264ce0cd9099c3132d4b5 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Thu, 16 Dec 2021 18:37:57 +0000 Subject: [PATCH 004/924] Use frost's miss count penalty --- .../Difficulty/OsuPerformanceCalculator.cs | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 0459c64c0e..bba53c92fc 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty aimValue *= lengthBonus; if (effectiveMissCount > 0) - aimValue *= Math.Pow(0.96, effectiveMissCount); + aimValue *= calculateMissPenalty(effectiveMissCount); double approachRateFactor = 0.0; if (Attributes.ApproachRate > 10.33) @@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty speedValue *= lengthBonus; if (effectiveMissCount > 0) - speedValue *= Math.Pow(0.96, effectiveMissCount); + speedValue *= calculateMissPenalty(effectiveMissCount); double approachRateFactor = 0.0; if (Attributes.ApproachRate > 10.33) @@ -229,7 +229,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty flashlightValue *= 1.3; if (effectiveMissCount > 0) - flashlightValue *= Math.Pow(0.96, effectiveMissCount); + flashlightValue *= calculateMissPenalty(effectiveMissCount); // Combo scaling. if (Attributes.MaxCombo > 0) @@ -265,6 +265,21 @@ namespace osu.Game.Rulesets.Osu.Difficulty return Math.Max(countMiss, (int)Math.Floor(comboBasedMissCount)); } + private double calculateMissPenalty(double missCount) + { + double leniency = 2.0; + + if (missCount > totalHits - leniency) + return 0; + + double missApprox = erfInvApprox((totalHits - leniency - missCount) / totalHits); + double fcApprox = erfInvApprox((totalHits - leniency) / totalHits); + + return Math.Pow(missApprox / fcApprox, 1.5); + } + + private double logit(double x) => Math.Log(x / (1 - x)); + private double erfInvApprox(double x) => (Math.Sqrt(Math.PI) / 4) * logit((x + 1) / 2); private int totalHits => countGreat + countOk + countMeh + countMiss; private int totalSuccessfulHits => countGreat + countOk + countMeh; } From 60e2a8ed4b2f0f38dafe0cc9bc6ab8c55128b189 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Tue, 21 Dec 2021 17:54:26 +0000 Subject: [PATCH 005/924] Use MathNet for miss penalty calculation, and use old penalty formula for Flashlight --- .../Difficulty/OsuPerformanceCalculator.cs | 14 +++++++------- osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj | 4 ++++ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index bba53c92fc..90caa64512 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -9,6 +9,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using MathNet.Numerics; namespace osu.Game.Rulesets.Osu.Difficulty { @@ -228,8 +229,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty 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 *= calculateMissPenalty(effectiveMissCount); + flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875)); // Combo scaling. if (Attributes.MaxCombo > 0) @@ -267,19 +269,17 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double calculateMissPenalty(double missCount) { - double leniency = 2.0; + double leniency = 4.3; if (missCount > totalHits - leniency) return 0; - double missApprox = erfInvApprox((totalHits - leniency - missCount) / totalHits); - double fcApprox = erfInvApprox((totalHits - leniency) / totalHits); + double missApprox = SpecialFunctions.ErfInv((totalHits - leniency - missCount) / totalHits); + double fcApprox = SpecialFunctions.ErfInv((totalHits - leniency) / totalHits); - return Math.Pow(missApprox / fcApprox, 1.5); + return Math.Pow(missApprox / fcApprox, 3.5); } - private double logit(double x) => Math.Log(x / (1 - x)); - private double erfInvApprox(double x) => (Math.Sqrt(Math.PI) / 4) * logit((x + 1) / 2); private int totalHits => countGreat + countOk + countMeh + countMiss; private int totalSuccessfulHits => countGreat + countOk + countMeh; } diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj index 98f1e69bd1..018bdb93df 100644 --- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj +++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj @@ -15,4 +15,8 @@ + + + + \ No newline at end of file From 5640918c8c7c8af2e29feec4c7ad6d501619dafc Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Sun, 26 Dec 2021 23:51:49 +0000 Subject: [PATCH 006/924] New miss penalty formula, using relevant difficult notes in each skill (targets diffspikes) --- .../Difficulty/OsuDifficultyAttributes.cs | 6 ++++++ .../Difficulty/OsuDifficultyCalculator.cs | 9 +++++++++ .../Difficulty/OsuPerformanceCalculator.cs | 16 ++++------------ .../Difficulty/Skills/OsuStrainSkill.cs | 7 +++++++ 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index 4b2e54da17..6102f4a8b2 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -24,6 +24,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty [JsonProperty("slider_factor")] public double SliderFactor { get; set; } + [JsonProperty("aim_difficult_strain_count")] + public int AimDifficultStrainCount { get; set; } + + [JsonProperty("speed_difficult_strain_count")] + public int SpeedDifficultStrainCount { get; set; } + [JsonProperty("approach_rate")] public double ApproachRate { get; set; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index ed42f333c0..ac228b0a32 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -40,6 +40,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; + int aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).RelevantDifficultStrains(); + int speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).RelevantDifficultStrains(); + + // Total number of strains in a map can vary by clockrate, and this needs to be corrected for. + aimDifficultyStrainCount = (int)(aimDifficultyStrainCount * clockRate); + speedDifficultyStrainCount = (int)(aimDifficultyStrainCount * clockRate); + if (mods.Any(h => h is OsuModRelax)) speedRating = 0.0; @@ -78,6 +85,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty SpeedStrain = speedRating, FlashlightRating = flashlightRating, SliderFactor = sliderFactor, + AimDifficultStrainCount = aimDifficultyStrainCount, + SpeedDifficultStrainCount = speedDifficultyStrainCount, ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, OverallDifficulty = (80 - hitWindowGreat) / 6, DrainRate = drainRate, diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 90caa64512..3efb8951b3 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty aimValue *= lengthBonus; if (effectiveMissCount > 0) - aimValue *= calculateMissPenalty(effectiveMissCount); + aimValue *= calculateMissPenalty(effectiveMissCount, Attributes.AimDifficultStrainCount); double approachRateFactor = 0.0; if (Attributes.ApproachRate > 10.33) @@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty speedValue *= lengthBonus; if (effectiveMissCount > 0) - speedValue *= calculateMissPenalty(effectiveMissCount); + speedValue *= calculateMissPenalty(effectiveMissCount, Attributes.SpeedDifficultStrainCount); double approachRateFactor = 0.0; if (Attributes.ApproachRate > 10.33) @@ -267,17 +267,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty return Math.Max(countMiss, (int)Math.Floor(comboBasedMissCount)); } - private double calculateMissPenalty(double missCount) + private double calculateMissPenalty(double missCount, double strainCount) { - double leniency = 4.3; - - if (missCount > totalHits - leniency) - return 0; - - double missApprox = SpecialFunctions.ErfInv((totalHits - leniency - missCount) / totalHits); - double fcApprox = SpecialFunctions.ErfInv((totalHits - leniency) / totalHits); - - return Math.Pow(missApprox / fcApprox, 3.5); + return 0.95 / ((missCount / (3 * Math.Sqrt(strainCount))) + 1); } private int totalHits => countGreat + countOk + countMeh + countMiss; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index e47edc37cc..43e39482a7 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -57,5 +57,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills return difficulty * DifficultyMultiplier; } + + public int RelevantDifficultStrains() + { + List strains = GetCurrentStrainPeaks().OrderByDescending(d => d).ToList(); + + return strains.Count(s => s > strains[0] * 0.66); + } } } From e9589e57a662bc47297fec2429d7bfce1256db68 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Mon, 27 Dec 2021 02:23:03 +0000 Subject: [PATCH 007/924] Fix logical error --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index ac228b0a32..87ab12248f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Total number of strains in a map can vary by clockrate, and this needs to be corrected for. aimDifficultyStrainCount = (int)(aimDifficultyStrainCount * clockRate); - speedDifficultyStrainCount = (int)(aimDifficultyStrainCount * clockRate); + speedDifficultyStrainCount = (int)(speedDifficultyStrainCount * clockRate); if (mods.Any(h => h is OsuModRelax)) speedRating = 0.0; From 8ce6e3c573104d55956c9a3c4cd3c8beffd41b09 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 29 Dec 2021 18:49:13 +0000 Subject: [PATCH 008/924] Remove mathnet --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 4 ---- osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj | 4 ---- 2 files changed, 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index d7d294df47..f6e5481feb 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -99,8 +99,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (effectiveMissCount > 0) aimValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), effectiveMissCount); - aimValue *= getComboScalingFactor(); - double approachRateFactor = 0.0; if (Attributes.ApproachRate > 10.33) approachRateFactor = 0.3 * (Attributes.ApproachRate - 10.33); @@ -146,8 +144,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (effectiveMissCount > 0) speedValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875)); - speedValue *= getComboScalingFactor(); - double approachRateFactor = 0.0; if (Attributes.ApproachRate > 10.33) approachRateFactor = 0.3 * (Attributes.ApproachRate - 10.33); diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj index 018bdb93df..98f1e69bd1 100644 --- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj +++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj @@ -15,8 +15,4 @@ - - - - \ No newline at end of file From 4f257d6987b1144912f561d5a3360593598be834 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 29 Dec 2021 18:59:17 +0000 Subject: [PATCH 009/924] Clean up unsuccessful merge --- .../Difficulty/OsuPerformanceCalculator.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index f6e5481feb..793f9e790f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -95,9 +95,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); aimValue *= lengthBonus; - // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (effectiveMissCount > 0) - aimValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), effectiveMissCount); + aimValue *= calculateMissPenalty(effectiveMissCount, Attributes.AimDifficultStrainCount); double approachRateFactor = 0.0; if (Attributes.ApproachRate > 10.33) @@ -140,9 +139,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); speedValue *= lengthBonus; - // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (effectiveMissCount > 0) - speedValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875)); + speedValue *= calculateMissPenalty(effectiveMissCount, Attributes.SpeedDifficultStrainCount); double approachRateFactor = 0.0; if (Attributes.ApproachRate > 10.33) @@ -259,6 +257,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty } private double getComboScalingFactor() => Attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0); + + private double calculateMissPenalty(double missCount, double strainCount) + { + return 0.95 / ((missCount / (3 * Math.Sqrt(strainCount))) + 1); + } + private int totalHits => countGreat + countOk + countMeh + countMiss; private int totalSuccessfulHits => countGreat + countOk + countMeh; } From fd1028f3bb5c0f19ac2154974fafa528ea6e52e9 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 29 Dec 2021 23:49:07 +0000 Subject: [PATCH 010/924] Use clockrate in the difficult strain count method --- .../Difficulty/OsuDifficultyCalculator.cs | 8 ++------ .../Difficulty/Skills/OsuStrainSkill.cs | 9 +++++++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 44ba6c6a58..27d97e9d75 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -40,12 +40,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; - int aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).RelevantDifficultStrains(); - int speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).RelevantDifficultStrains(); - - // Total number of strains in a map can vary by clockrate, and this needs to be corrected for. - aimDifficultyStrainCount = (int)(aimDifficultyStrainCount * clockRate); - speedDifficultyStrainCount = (int)(speedDifficultyStrainCount * clockRate); + int aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountDifficultStrains(clockRate); + int speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountDifficultStrains(clockRate); if (mods.Any(h => h is OsuModRelax)) speedRating = 0.0; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 43e39482a7..fd751727a9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -58,11 +58,16 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills return difficulty * DifficultyMultiplier; } - public int RelevantDifficultStrains() + /// + /// Returns the number of difficult strains. + /// A strain is considered difficult if it's higher than 66% of the highest strain. + /// + public int CountDifficultStrains(double clockRate) { List strains = GetCurrentStrainPeaks().OrderByDescending(d => d).ToList(); - return strains.Count(s => s > strains[0] * 0.66); + // Total number of strains in a map can vary by clockrate, and this needs to be corrected for. + return (int)(strains.Count(s => s > strains[0] * 0.66) * clockRate); } } } From d2b815b745b1cfaef241488c436b26fede000571 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Sun, 2 Jan 2022 19:20:20 +0000 Subject: [PATCH 011/924] Add miss penalty comment --- .../Difficulty/OsuPerformanceCalculator.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 793f9e790f..d8fe85d645 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -256,13 +256,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty return Math.Max(countMiss, (int)Math.Floor(comboBasedMissCount)); } + // Miss penalty assumes that a player will miss on the relatively hard parts of a map, not the easy parts, hence the strain count. + private double calculateMissPenalty(double missCount, double strainCount) => 0.95 / ((missCount / (3 * Math.Sqrt(strainCount))) + 1); + private double getComboScalingFactor() => Attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0); - - private double calculateMissPenalty(double missCount, double strainCount) - { - return 0.95 / ((missCount / (3 * Math.Sqrt(strainCount))) + 1); - } - private int totalHits => countGreat + countOk + countMeh + countMiss; private int totalSuccessfulHits => countGreat + countOk + countMeh; } From 75be4e83d6a44adc7016c150b5cbd1c74d1caf34 Mon Sep 17 00:00:00 2001 From: Luminiscental Date: Mon, 3 Jan 2022 22:22:32 +0000 Subject: [PATCH 012/924] Remove abusable 0.66 threshold by averaging --- .../Difficulty/Skills/OsuStrainSkill.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index fd751727a9..1513befad5 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -59,15 +59,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills } /// - /// Returns the number of difficult strains. - /// A strain is considered difficult if it's higher than 66% of the highest strain. + /// Returns the number of strains above a threshold averaged as the threshold varies. + /// The result is scaled by clock rate as it affects the total number of strains. /// public int CountDifficultStrains(double clockRate) { - List strains = GetCurrentStrainPeaks().OrderByDescending(d => d).ToList(); - - // Total number of strains in a map can vary by clockrate, and this needs to be corrected for. - return (int)(strains.Count(s => s > strains[0] * 0.66) * clockRate); + List strains = GetCurrentStrainPeaks().ToList(); + // This is the average value of strains.Count(s => s > p * strains.Max()) for p between 0 and 1. + double realtimeCount = strains.Sum() / strains.Max(); + return (int)(clockRate * realtimeCount); } } } From 132079004ca5e6df33a2bc646a52f3036edff85e Mon Sep 17 00:00:00 2001 From: Luminiscental Date: Tue, 4 Jan 2022 12:30:05 +0000 Subject: [PATCH 013/924] Remove unnecessary truncation --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs | 4 ++-- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 4 ++-- osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index 51842f65d5..7ab4232a19 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -25,10 +25,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty public double SliderFactor { get; set; } [JsonProperty("aim_difficult_strain_count")] - public int AimDifficultStrainCount { get; set; } + public double AimDifficultStrainCount { get; set; } [JsonProperty("speed_difficult_strain_count")] - public int SpeedDifficultStrainCount { get; set; } + public double SpeedDifficultStrainCount { get; set; } [JsonProperty("approach_rate")] public double ApproachRate { get; set; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 27d97e9d75..3c039c9b7e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -40,8 +40,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; - int aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountDifficultStrains(clockRate); - int speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountDifficultStrains(clockRate); + double aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountDifficultStrains(clockRate); + double speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountDifficultStrains(clockRate); if (mods.Any(h => h is OsuModRelax)) speedRating = 0.0; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 1513befad5..46dc9c683b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -62,12 +62,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// Returns the number of strains above a threshold averaged as the threshold varies. /// The result is scaled by clock rate as it affects the total number of strains. /// - public int CountDifficultStrains(double clockRate) + public double CountDifficultStrains(double clockRate) { List strains = GetCurrentStrainPeaks().ToList(); // This is the average value of strains.Count(s => s > p * strains.Max()) for p between 0 and 1. double realtimeCount = strains.Sum() / strains.Max(); - return (int)(clockRate * realtimeCount); + return clockRate * realtimeCount; } } } From 443640a48c8bed53510947240e8337c9350d8d6d Mon Sep 17 00:00:00 2001 From: apollo <83023433+apollo-dw@users.noreply.github.com> Date: Tue, 4 Jan 2022 16:39:30 +0000 Subject: [PATCH 014/924] Revert "Remove abusable 0.66 threshold by averaging" --- .../Difficulty/OsuDifficultyAttributes.cs | 4 ++-- .../Difficulty/OsuDifficultyCalculator.cs | 4 ++-- .../Difficulty/Skills/OsuStrainSkill.cs | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index 7ab4232a19..51842f65d5 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -25,10 +25,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty public double SliderFactor { get; set; } [JsonProperty("aim_difficult_strain_count")] - public double AimDifficultStrainCount { get; set; } + public int AimDifficultStrainCount { get; set; } [JsonProperty("speed_difficult_strain_count")] - public double SpeedDifficultStrainCount { get; set; } + public int SpeedDifficultStrainCount { get; set; } [JsonProperty("approach_rate")] public double ApproachRate { get; set; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 3c039c9b7e..27d97e9d75 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -40,8 +40,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; - double aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountDifficultStrains(clockRate); - double speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountDifficultStrains(clockRate); + int aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountDifficultStrains(clockRate); + int speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountDifficultStrains(clockRate); if (mods.Any(h => h is OsuModRelax)) speedRating = 0.0; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 46dc9c683b..fd751727a9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -59,15 +59,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills } /// - /// Returns the number of strains above a threshold averaged as the threshold varies. - /// The result is scaled by clock rate as it affects the total number of strains. + /// Returns the number of difficult strains. + /// A strain is considered difficult if it's higher than 66% of the highest strain. /// - public double CountDifficultStrains(double clockRate) + public int CountDifficultStrains(double clockRate) { - List strains = GetCurrentStrainPeaks().ToList(); - // This is the average value of strains.Count(s => s > p * strains.Max()) for p between 0 and 1. - double realtimeCount = strains.Sum() / strains.Max(); - return clockRate * realtimeCount; + List strains = GetCurrentStrainPeaks().OrderByDescending(d => d).ToList(); + + // Total number of strains in a map can vary by clockrate, and this needs to be corrected for. + return (int)(strains.Count(s => s > strains[0] * 0.66) * clockRate); } } } From dcb969316dd5a7e2bd1b06ebd1450a0ab56ef831 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Tue, 4 Jan 2022 17:33:23 +0000 Subject: [PATCH 015/924] Weight difficult strain count against the top strain --- osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 46dc9c683b..3e0fccb6c4 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -59,14 +59,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills } /// - /// Returns the number of strains above a threshold averaged as the threshold varies. + /// Returns the number of strains weighted against the top strain. /// The result is scaled by clock rate as it affects the total number of strains. /// public double CountDifficultStrains(double clockRate) { List strains = GetCurrentStrainPeaks().ToList(); - // This is the average value of strains.Count(s => s > p * strains.Max()) for p between 0 and 1. - double realtimeCount = strains.Sum() / strains.Max(); + double topStrain = strains.Max(); + + double realtimeCount = strains.Sum(s => Math.Pow(s / topStrain, 4)); return clockRate * realtimeCount; } } From 400abc147b1f3742061125d91e80a62f5e1333f7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 6 Jan 2022 16:28:04 +0900 Subject: [PATCH 016/924] Add attribute ids to mapping functions --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs | 4 ++++ osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs | 2 ++ 2 files changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index 7ab4232a19..7041acc16c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -60,6 +60,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty yield return (ATTRIB_ID_FLASHLIGHT, FlashlightDifficulty); yield return (ATTRIB_ID_SLIDER_FACTOR, SliderFactor); + yield return (ATTRIB_ID_AIM_DIFFICULT_STRAIN_COUNT, AimDifficultStrainCount); + yield return (ATTRIB_ID_SPEED_DIFFICULT_STRAIN_COUNT, SpeedDifficultStrainCount); } public override void FromDatabaseAttributes(IReadOnlyDictionary values) @@ -74,6 +76,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty StarRating = values[ATTRIB_ID_DIFFICULTY]; FlashlightDifficulty = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT); SliderFactor = values[ATTRIB_ID_SLIDER_FACTOR]; + AimDifficultStrainCount = values[ATTRIB_ID_AIM_DIFFICULT_STRAIN_COUNT]; + SpeedDifficultStrainCount = values[ATTRIB_ID_SPEED_DIFFICULT_STRAIN_COUNT]; } #region Newtonsoft.Json implicit ShouldSerialize() methods diff --git a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs index 991b567f57..803a5bdac7 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs @@ -24,6 +24,8 @@ namespace osu.Game.Rulesets.Difficulty protected const int ATTRIB_ID_SCORE_MULTIPLIER = 15; protected const int ATTRIB_ID_FLASHLIGHT = 17; protected const int ATTRIB_ID_SLIDER_FACTOR = 19; + protected const int ATTRIB_ID_AIM_DIFFICULT_STRAIN_COUNT = 21; + protected const int ATTRIB_ID_SPEED_DIFFICULT_STRAIN_COUNT = 23; /// /// The mods which were applied to the beatmap. From 598946737f07c0f2a3ac7523666a868722906db1 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 12 Jan 2022 14:38:53 +0000 Subject: [PATCH 017/924] Reword comment and rename argument --- .../Difficulty/OsuPerformanceCalculator.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index d8fe85d645..52658dfe38 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -256,9 +256,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty return Math.Max(countMiss, (int)Math.Floor(comboBasedMissCount)); } - // Miss penalty assumes that a player will miss on the relatively hard parts of a map, not the easy parts, hence the strain count. - private double calculateMissPenalty(double missCount, double strainCount) => 0.95 / ((missCount / (3 * Math.Sqrt(strainCount))) + 1); - + // Miss penalty assumes that a player will miss on the hardest parts of a map, + // so we use the amount of relatively difficult sections to adjust miss penalty + // to make it more punishing on maps with lower amount of hard sections. + private double calculateMissPenalty(double missCount, double difficultStrainCount) => 0.95 / ((missCount / (3 * Math.Sqrt(difficultStrainCount))) + 1); private double getComboScalingFactor() => Attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0); private int totalHits => countGreat + countOk + countMeh + countMiss; private int totalSuccessfulHits => countGreat + countOk + countMeh; From da31ca17e7c12a2922ab83657df4d8d9b6826bbd Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Mon, 14 Feb 2022 01:53:03 +0000 Subject: [PATCH 018/924] Use note strains instead of sectional strains --- osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 2 ++ osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 7 ++++--- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 6 +++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index a6301aed6d..3486db04af 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -159,6 +159,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills currentStrain *= strainDecay(current.DeltaTime); currentStrain += strainValueOf(current) * skillMultiplier; + objectStrains.Add(currentStrain); + return currentStrain; } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 3e0fccb6c4..94e2c9d774 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -28,6 +28,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// protected virtual double DifficultyMultiplier => 1.06; + protected List objectStrains = new List(); + protected OsuStrainSkill(Mod[] mods) : base(mods) { @@ -64,10 +66,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// public double CountDifficultStrains(double clockRate) { - List strains = GetCurrentStrainPeaks().ToList(); - double topStrain = strains.Max(); + double topStrain = objectStrains.Max(); - double realtimeCount = strains.Sum(s => Math.Pow(s / topStrain, 4)); + double realtimeCount = objectStrains.Sum(s => Math.Pow(s / topStrain, 4)); return clockRate * realtimeCount; } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 06d1ef7346..108edc6f2d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -173,7 +173,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills currentRhythm = calculateRhythmBonus(current); - return currentStrain * currentRhythm; + double totalStrain = currentStrain * currentRhythm; + + objectStrains.Add(totalStrain); + + return totalStrain; } } } From 94a46ab640b36c3ca58f03eaf2644ab51c3abd51 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Mon, 14 Feb 2022 02:02:46 +0000 Subject: [PATCH 019/924] Rescale miss penalty for note strains --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index cf2116cc5d..cf1af18d39 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -259,7 +259,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Miss penalty assumes that a player will miss on the hardest parts of a map, // so we use the amount of relatively difficult sections to adjust miss penalty // to make it more punishing on maps with lower amount of hard sections. - private double calculateMissPenalty(double missCount, double difficultStrainCount) => 0.95 / ((missCount / (3 * Math.Sqrt(difficultStrainCount))) + 1); + private double calculateMissPenalty(double missCount, double difficultStrainCount) => 0.94 / ((missCount / (2 * Math.Sqrt(difficultStrainCount))) + 1); private double getComboScalingFactor() => Attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0); private int totalHits => countGreat + countOk + countMeh + countMiss; private int totalSuccessfulHits => countGreat + countOk + countMeh; From c18df86720244a7d5bba22bc502afd4f959c9082 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Sat, 19 Feb 2022 15:33:28 +0000 Subject: [PATCH 020/924] Remove clockrate factor --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 4 ++-- osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 3c039c9b7e..788b515d7f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -40,8 +40,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; - double aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountDifficultStrains(clockRate); - double speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountDifficultStrains(clockRate); + double aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountDifficultStrains(); + double speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountDifficultStrains(); if (mods.Any(h => h is OsuModRelax)) speedRating = 0.0; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 94e2c9d774..1124c4466f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -64,12 +64,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// Returns the number of strains weighted against the top strain. /// The result is scaled by clock rate as it affects the total number of strains. /// - public double CountDifficultStrains(double clockRate) + public double CountDifficultStrains() { double topStrain = objectStrains.Max(); - double realtimeCount = objectStrains.Sum(s => Math.Pow(s / topStrain, 4)); - return clockRate * realtimeCount; + return objectStrains.Sum(s => Math.Pow(s / topStrain, 4)); } } } From 2f335a76dca77b0304f5ff21ed7ca2c8e1130757 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Thu, 17 Mar 2022 22:08:56 +0000 Subject: [PATCH 021/924] Switch to using osuAttributes --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 48b4f53a3e..964bd73e81 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty aimValue *= lengthBonus; if (effectiveMissCount > 0) - aimValue *= calculateMissPenalty(effectiveMissCount, Attributes.AimDifficultStrainCount); + aimValue *= calculateMissPenalty(effectiveMissCount, attributes.AimDifficultStrainCount); double approachRateFactor = 0.0; if (attributes.ApproachRate > 10.33) @@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty speedValue *= lengthBonus; if (effectiveMissCount > 0) - speedValue *= calculateMissPenalty(effectiveMissCount, Attributes.SpeedDifficultStrainCount); + speedValue *= calculateMissPenalty(effectiveMissCount, attributes.SpeedDifficultStrainCount); double approachRateFactor = 0.0; if (attributes.ApproachRate > 10.33) From 442e68ac1a496529869e7cbb75ad40bc4881aef4 Mon Sep 17 00:00:00 2001 From: Natelytle Date: Tue, 25 Oct 2022 17:41:20 -0400 Subject: [PATCH 022/924] Implement taiko deviation estimation --- .../Difficulty/TaikoPerformanceAttributes.cs | 3 +++ .../Difficulty/TaikoPerformanceCalculator.cs | 27 +++++++++++++++---- .../osu.Game.Rulesets.Taiko.csproj | 4 +++ 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs index b61c13a2df..fa8c3fd27a 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs @@ -19,6 +19,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty [JsonProperty("effective_miss_count")] public double EffectiveMissCount { get; set; } + + [JsonProperty("estimated_ur")] + public double EstimatedUR { get; set; } public override IEnumerable GetAttributesForDisplay() { diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index dc7bad2f75..7d5cf3accd 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -11,6 +11,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Scoring; +using MathNet.Numerics; namespace osu.Game.Rulesets.Taiko.Difficulty { @@ -21,6 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private int countMeh; private int countMiss; private double accuracy; + private double estimatedUR; private double effectiveMissCount; @@ -37,7 +39,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty countOk = score.Statistics.GetValueOrDefault(HitResult.Ok); countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); - accuracy = customAccuracy; + estimatedUR = 10 * computeEstimatedUR(score, taikoAttributes); // The effectiveMissCount is calculated by gaining a ratio for totalSuccessfulHits and increasing the miss penalty for shorter object counts lower than 1000. if (totalSuccessfulHits > 0) @@ -64,6 +66,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty Difficulty = difficultyValue, Accuracy = accuracyValue, EffectiveMissCount = effectiveMissCount, + EstimatedUR = 10 * estimatedUR, Total = totalValue }; } @@ -97,10 +100,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (attributes.GreatHitWindow <= 0) return 0; - double accuracyValue = Math.Pow(60.0 / attributes.GreatHitWindow, 1.1) * Math.Pow(accuracy, 8.0) * Math.Pow(attributes.StarRating, 0.4) * 27.0; + double accuracyValue = Math.Pow(75 / estimatedUR, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 70.0; double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3)); - accuracyValue *= lengthBonus; // Slight HDFL Bonus for accuracy. A clamp is used to prevent against negative values if (score.Mods.Any(m => m is ModFlashlight) && score.Mods.Any(m => m is ModHidden)) @@ -109,10 +111,25 @@ namespace osu.Game.Rulesets.Taiko.Difficulty return accuracyValue; } + private double computeEstimatedUR(ScoreInfo score, TaikoDifficultyAttributes attributes) + { + if (totalHits == 0) + return double.PositiveInfinity; + + double greatProbability = 1 - (countOk + countMiss + 1.0) / (totalHits + 1.0); + + if (greatProbability <= 0) + { + return double.PositiveInfinity; + } + + double deviation = attributes.GreatHitWindow / (Math.Sqrt(2) * SpecialFunctions.ErfInv(greatProbability)); + + return deviation; + } + private int totalHits => countGreat + countOk + countMeh + countMiss; private int totalSuccessfulHits => countGreat + countOk + countMeh; - - private double customAccuracy => totalHits > 0 ? (countGreat * 300 + countOk * 150) / (totalHits * 300.0) : 0; } } diff --git a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj index b752c13d18..b2953106d8 100644 --- a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj +++ b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj @@ -15,4 +15,8 @@ + + + + From d5b06ae9454885e3eeec8de652a8cd51c86d8b7a Mon Sep 17 00:00:00 2001 From: Natelytle Date: Tue, 25 Oct 2022 17:52:34 -0400 Subject: [PATCH 023/924] Fix difficultyvalue acc scaling --- .../Difficulty/TaikoPerformanceCalculator.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 7d5cf3accd..ee2105d859 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -21,7 +21,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private int countOk; private int countMeh; private int countMiss; - private double accuracy; private double estimatedUR; private double effectiveMissCount; @@ -92,7 +91,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (score.Mods.Any(m => m is ModFlashlight)) difficultyValue *= 1.050 * lengthBonus; - return difficultyValue * Math.Pow(accuracy, 2.0); + return difficultyValue * Math.Pow(100 * SpecialFunctions.Erf(400 / (Math.Sqrt(2) * estimatedUR)), 2.0); } private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes) From 607a006c4f5b11be1f4e8c223c8cfea2b96a9c19 Mon Sep 17 00:00:00 2001 From: Natelytle Date: Tue, 25 Oct 2022 17:55:16 -0400 Subject: [PATCH 024/924] oops --- .../Difficulty/TaikoPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index ee2105d859..ba96e0b95e 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (score.Mods.Any(m => m is ModFlashlight)) difficultyValue *= 1.050 * lengthBonus; - return difficultyValue * Math.Pow(100 * SpecialFunctions.Erf(400 / (Math.Sqrt(2) * estimatedUR)), 2.0); + return difficultyValue * Math.Pow(SpecialFunctions.Erf(400 / (Math.Sqrt(2) * estimatedUR)), 2.0); } private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes) From 87cba2d828455b88c26e6eb813667c916ec07c60 Mon Sep 17 00:00:00 2001 From: Natelytle Date: Tue, 25 Oct 2022 19:15:58 -0400 Subject: [PATCH 025/924] Slight adjustments --- .../Difficulty/TaikoPerformanceCalculator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index ba96e0b95e..ec5c150ea4 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty Difficulty = difficultyValue, Accuracy = accuracyValue, EffectiveMissCount = effectiveMissCount, - EstimatedUR = 10 * estimatedUR, + EstimatedUR = estimatedUR, Total = totalValue }; } @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (attributes.GreatHitWindow <= 0) return 0; - double accuracyValue = Math.Pow(75 / estimatedUR, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 70.0; + double accuracyValue = Math.Pow(75 / estimatedUR, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 100.0; double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3)); From 7d3338a0eacb1d4ecbcdb82ee98f8f34d2ac4a3d Mon Sep 17 00:00:00 2001 From: Natelytle Date: Wed, 26 Oct 2022 15:58:20 -0400 Subject: [PATCH 026/924] LTCA Balancing pass --- .../Difficulty/TaikoPerformanceCalculator.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index ec5c150ea4..2a7cb6c6b5 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private int countOk; private int countMeh; private int countMiss; - private double estimatedUR; + private double estimatedDeviation; private double effectiveMissCount; @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty countOk = score.Statistics.GetValueOrDefault(HitResult.Ok); countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); - estimatedUR = 10 * computeEstimatedUR(score, taikoAttributes); + estimatedDeviation = computeEstimatedDeviation(score, taikoAttributes); // The effectiveMissCount is calculated by gaining a ratio for totalSuccessfulHits and increasing the miss penalty for shorter object counts lower than 1000. if (totalSuccessfulHits > 0) @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty Difficulty = difficultyValue, Accuracy = accuracyValue, EffectiveMissCount = effectiveMissCount, - EstimatedUR = estimatedUR, + EstimatedUR = estimatedDeviation * 10, Total = totalValue }; } @@ -86,12 +86,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty difficultyValue *= 1.025; if (score.Mods.Any(m => m is ModHardRock)) - difficultyValue *= 1.050; + difficultyValue *= 1.10; if (score.Mods.Any(m => m is ModFlashlight)) difficultyValue *= 1.050 * lengthBonus; - return difficultyValue * Math.Pow(SpecialFunctions.Erf(400 / (Math.Sqrt(2) * estimatedUR)), 2.0); + return difficultyValue * Math.Pow(SpecialFunctions.Erf(400 / (Math.Sqrt(2) * estimatedDeviation)), 2.0); } private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes) @@ -99,18 +99,18 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (attributes.GreatHitWindow <= 0) return 0; - double accuracyValue = Math.Pow(75 / estimatedUR, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 100.0; + double accuracyValue = Math.Pow(7.5 / estimatedDeviation, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 100.0; double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3)); // Slight HDFL Bonus for accuracy. A clamp is used to prevent against negative values if (score.Mods.Any(m => m is ModFlashlight) && score.Mods.Any(m => m is ModHidden)) - accuracyValue *= Math.Max(1.050, 1.075 * lengthBonus); + accuracyValue *= Math.Max(1.0, 1.05 * lengthBonus); return accuracyValue; } - private double computeEstimatedUR(ScoreInfo score, TaikoDifficultyAttributes attributes) + private double computeEstimatedDeviation(ScoreInfo score, TaikoDifficultyAttributes attributes) { if (totalHits == 0) return double.PositiveInfinity; From af919a6550f58da73784931883e59e4b042f910b Mon Sep 17 00:00:00 2001 From: Natelytle Date: Wed, 26 Oct 2022 16:10:36 -0400 Subject: [PATCH 027/924] harshen deviation scaling --- .../Difficulty/TaikoPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 2a7cb6c6b5..565d166c32 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (score.Mods.Any(m => m is ModFlashlight)) difficultyValue *= 1.050 * lengthBonus; - return difficultyValue * Math.Pow(SpecialFunctions.Erf(400 / (Math.Sqrt(2) * estimatedDeviation)), 2.0); + return difficultyValue * Math.Pow(SpecialFunctions.Erf(40 / (Math.Sqrt(2) * estimatedDeviation)), 2.0); } private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes) From 2940d18d3393c1e006c715ec47d5d2fa88b1bf61 Mon Sep 17 00:00:00 2001 From: Natelytle Date: Thu, 27 Oct 2022 00:07:32 -0400 Subject: [PATCH 028/924] Fix formatting --- .../Difficulty/TaikoPerformanceAttributes.cs | 2 +- .../Difficulty/TaikoPerformanceCalculator.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs index fa8c3fd27a..6893f24d20 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty [JsonProperty("effective_miss_count")] public double EffectiveMissCount { get; set; } - + [JsonProperty("estimated_ur")] public double EstimatedUR { get; set; } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 565d166c32..1e57d77d91 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { if (totalHits == 0) return double.PositiveInfinity; - + double greatProbability = 1 - (countOk + countMiss + 1.0) / (totalHits + 1.0); if (greatProbability <= 0) From 883790c7a78a7a69b08a3d5299557d2e245c16f5 Mon Sep 17 00:00:00 2001 From: Natelytle Date: Fri, 28 Oct 2022 16:18:17 -0400 Subject: [PATCH 029/924] Return null instead of infinity --- .../Difficulty/TaikoPerformanceAttributes.cs | 2 +- .../Difficulty/TaikoPerformanceCalculator.cs | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs index 6893f24d20..3786009a1f 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty public double EffectiveMissCount { get; set; } [JsonProperty("estimated_ur")] - public double EstimatedUR { get; set; } + public double? EstimatedUR { get; set; } public override IEnumerable GetAttributesForDisplay() { diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 1e57d77d91..c27efb518d 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private int countOk; private int countMeh; private int countMiss; - private double estimatedDeviation; + private double? estimatedDeviation; private double effectiveMissCount; @@ -91,15 +91,18 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (score.Mods.Any(m => m is ModFlashlight)) difficultyValue *= 1.050 * lengthBonus; - return difficultyValue * Math.Pow(SpecialFunctions.Erf(40 / (Math.Sqrt(2) * estimatedDeviation)), 2.0); + if (estimatedDeviation == null) + return 0; + + return difficultyValue * Math.Pow(SpecialFunctions.Erf(40 / (Math.Sqrt(2) * (double)estimatedDeviation)), 2.0); } private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes) { - if (attributes.GreatHitWindow <= 0) + if (attributes.GreatHitWindow <= 0 || estimatedDeviation == null) return 0; - double accuracyValue = Math.Pow(7.5 / estimatedDeviation, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 100.0; + double accuracyValue = Math.Pow(7.5 / (double)estimatedDeviation, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 100.0; double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3)); @@ -110,10 +113,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty return accuracyValue; } - private double computeEstimatedDeviation(ScoreInfo score, TaikoDifficultyAttributes attributes) + private double? computeEstimatedDeviation(ScoreInfo score, TaikoDifficultyAttributes attributes) { if (totalHits == 0) - return double.PositiveInfinity; + return null; double greatProbability = 1 - (countOk + countMiss + 1.0) / (totalHits + 1.0); From 01c79d8ef29abf4871335db06326eeef56d3fed6 Mon Sep 17 00:00:00 2001 From: Natelytle Date: Fri, 28 Oct 2022 16:20:21 -0400 Subject: [PATCH 030/924] remove other infinity reference --- .../Difficulty/TaikoPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index c27efb518d..4fba926205 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -122,7 +122,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (greatProbability <= 0) { - return double.PositiveInfinity; + return null; } double deviation = attributes.GreatHitWindow / (Math.Sqrt(2) * SpecialFunctions.ErfInv(greatProbability)); From 7403c1cc862f6ebbd9e6a2bc791857b53a973da6 Mon Sep 17 00:00:00 2001 From: Natelytle Date: Fri, 28 Oct 2022 23:23:50 -0400 Subject: [PATCH 031/924] Return null for greatprobability >= 1 --- .../Difficulty/TaikoPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 4fba926205..003e46d77b 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty double greatProbability = 1 - (countOk + countMiss + 1.0) / (totalHits + 1.0); - if (greatProbability <= 0) + if (greatProbability <= 0 || greatProbability >= 1) { return null; } From 16301f052e8d64164e6f480a594e28868f940614 Mon Sep 17 00:00:00 2001 From: Natelytle Date: Sun, 30 Oct 2022 15:01:25 -0400 Subject: [PATCH 032/924] Fix low end accuracy, buff high end --- .../Difficulty/TaikoPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 003e46d77b..c57efd3c4d 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (attributes.GreatHitWindow <= 0 || estimatedDeviation == null) return 0; - double accuracyValue = Math.Pow(7.5 / (double)estimatedDeviation, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 100.0; + double accuracyValue = Math.Pow(7.5 / (double)estimatedDeviation, 1.1) * Math.Pow(attributes.StarRating / 2.7, 0.8) * 100.0; double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3)); From 37c21cdd7cbe4e00ae564dfce66d7a55ab1aa68e Mon Sep 17 00:00:00 2001 From: Natelytle Date: Tue, 1 Nov 2022 15:14:55 -0400 Subject: [PATCH 033/924] fix formatting --- .../Difficulty/TaikoPerformanceAttributes.cs | 2 +- .../Difficulty/TaikoPerformanceCalculator.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs index 3786009a1f..74770eaa79 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty public double EffectiveMissCount { get; set; } [JsonProperty("estimated_ur")] - public double? EstimatedUR { get; set; } + public double? EstimatedUr { get; set; } public override IEnumerable GetAttributesForDisplay() { diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index c57efd3c4d..0a24e33419 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty Difficulty = difficultyValue, Accuracy = accuracyValue, EffectiveMissCount = effectiveMissCount, - EstimatedUR = estimatedDeviation * 10, + EstimatedUr = estimatedDeviation * 10, Total = totalValue }; } @@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (estimatedDeviation == null) return 0; - return difficultyValue * Math.Pow(SpecialFunctions.Erf(40 / (Math.Sqrt(2) * (double)estimatedDeviation)), 2.0); + return difficultyValue * Math.Pow(SpecialFunctions.Erf(40 / (Math.Sqrt(2) * estimatedDeviation.Value)), 2.0); } private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes) @@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (attributes.GreatHitWindow <= 0 || estimatedDeviation == null) return 0; - double accuracyValue = Math.Pow(7.5 / (double)estimatedDeviation, 1.1) * Math.Pow(attributes.StarRating / 2.7, 0.8) * 100.0; + double accuracyValue = Math.Pow(7.5 / estimatedDeviation.Value, 1.1) * Math.Pow(attributes.StarRating / 2.7, 0.8) * 100.0; double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3)); @@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty double greatProbability = 1 - (countOk + countMiss + 1.0) / (totalHits + 1.0); - if (greatProbability <= 0 || greatProbability >= 1) + if (greatProbability is <= 0 or >= 1) { return null; } From 2ba163440aae865d656cf954bed3157d39b60d7f Mon Sep 17 00:00:00 2001 From: Natelytle Date: Thu, 24 Nov 2022 19:09:30 -0500 Subject: [PATCH 034/924] account for low acc FC deviation --- .../Difficulty/TaikoPerformanceCalculator.cs | 51 ++++++++++++++++--- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 0a24e33419..281dc19837 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -12,6 +12,9 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Scoring; using MathNet.Numerics; +using MathNet.Numerics.RootFinding; +using osu.Framework.Audio.Track; +using osu.Framework.Extensions.IEnumerableExtensions; namespace osu.Game.Rulesets.Taiko.Difficulty { @@ -66,7 +69,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty Accuracy = accuracyValue, EffectiveMissCount = effectiveMissCount, EstimatedUr = estimatedDeviation * 10, - Total = totalValue + Total = (double)estimatedDeviation * 10 }; } @@ -118,20 +121,56 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (totalHits == 0) return null; - double greatProbability = 1 - (countOk + countMiss + 1.0) / (totalHits + 1.0); + // Create a new track to properly calculate the hit windows of 100s and 50s. + var track = new TrackVirtual(10000); + score.Mods.OfType().ForEach(m => m.ApplyToTrack(track)); + double clockRate = track.Rate; + double overallDifficulty = (50 - attributes.GreatHitWindow) / 3 * clockRate; + double goodHitWindow = 0; + if (overallDifficulty <= 5) + goodHitWindow = (120 - 8 * overallDifficulty) / clockRate; + if (overallDifficulty > 5) + goodHitWindow = 80 - 6 * (overallDifficulty - 5); + + double root2 = Math.Sqrt(2); - if (greatProbability is <= 0 or >= 1) + double logLikelihoodGradient(double d) { - return null; + if (d <= 0) + return 1; + + double p300 = SpecialFunctions.Erf(attributes.GreatHitWindow / root2 * d); + double lnP300 = lnErfcApprox(attributes.GreatHitWindow / root2 * d); + double lnP100 = lnErfcApprox(goodHitWindow / root2 * d); + + double t1 = countGreat * ((attributes.GreatHitWindow * Math.Exp(-Math.Pow(attributes.GreatHitWindow / root2 * d, 2))) / p300); + + double t2a = Math.Exp(Math.Log(goodHitWindow * Math.Exp(-Math.Pow(goodHitWindow / root2 * d, 2))) - logDiff(lnP300, lnP100)); + double t2b = Math.Exp(Math.Log(attributes.GreatHitWindow * Math.Exp(-Math.Pow(attributes.GreatHitWindow / root2 * d, 2))) - logDiff(lnP300, lnP100)); + double t2 = (countOk + 1) * (t2a - t2b); + + double t3 = countMiss * Math.Exp(Math.Log(goodHitWindow * Math.Exp(-Math.Pow(goodHitWindow / root2 * d, 2))) - lnP100); + + return t1 + t2 - t3; } - double deviation = attributes.GreatHitWindow / (Math.Sqrt(2) * SpecialFunctions.ErfInv(greatProbability)); + double root = Brent.FindRootExpand(logLikelihoodGradient, 0.02, 0.3, expandFactor: 2); - return deviation; + return 1 / root; } private int totalHits => countGreat + countOk + countMeh + countMiss; private int totalSuccessfulHits => countGreat + countOk + countMeh; + + double lnErfcApprox(double x) + { + if (x <= 5) + return Math.Log(SpecialFunctions.Erfc(x)); + + return -Math.Pow(x, 2) - Math.Log(x) - Math.Log(Math.Sqrt(Math.PI)); + } + + double logDiff(double l1, double l2) => l1 + SpecialFunctions.Log1p(-Math.Exp(-(l1 - l2))); } } From 0e4e92b344b6bce55ccf6ceb776cc8bfb450338f Mon Sep 17 00:00:00 2001 From: Natelytle Date: Thu, 24 Nov 2022 19:28:47 -0500 Subject: [PATCH 035/924] totalvalue --- .../Difficulty/TaikoPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 281dc19837..332f6f8267 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty Accuracy = accuracyValue, EffectiveMissCount = effectiveMissCount, EstimatedUr = estimatedDeviation * 10, - Total = (double)estimatedDeviation * 10 + Total = totalValue }; } From b579af674e128b94294ef9d379bc9a89d4dc94df Mon Sep 17 00:00:00 2001 From: Natelytle Date: Thu, 24 Nov 2022 19:46:55 -0500 Subject: [PATCH 036/924] fix dt --- .../Difficulty/TaikoPerformanceCalculator.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 332f6f8267..f184a7cdce 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -125,7 +125,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty var track = new TrackVirtual(10000); score.Mods.OfType().ForEach(m => m.ApplyToTrack(track)); double clockRate = track.Rate; - double overallDifficulty = (50 - attributes.GreatHitWindow) / 3 * clockRate; + + double overallDifficulty = (50 - attributes.GreatHitWindow * clockRate) / 3; double goodHitWindow = 0; if (overallDifficulty <= 5) goodHitWindow = (120 - 8 * overallDifficulty) / clockRate; From e3ef180c4669b3197c7a527daaad76435fa2fa7f Mon Sep 17 00:00:00 2001 From: Natelytle Date: Fri, 25 Nov 2022 15:18:39 -0500 Subject: [PATCH 037/924] fixes --- .../Difficulty/TaikoPerformanceCalculator.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index f184a7cdce..d1e305fe9b 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private double? computeEstimatedDeviation(ScoreInfo score, TaikoDifficultyAttributes attributes) { - if (totalHits == 0) + if (totalSuccessfulHits == 0) return null; // Create a new track to properly calculate the hit windows of 100s and 50s. @@ -155,7 +155,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty return t1 + t2 - t3; } - double root = Brent.FindRootExpand(logLikelihoodGradient, 0.02, 0.3, expandFactor: 2); + double root = Brent.FindRootExpand(logLikelihoodGradient, 0.02, 0.3); return 1 / root; } @@ -164,7 +164,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private int totalSuccessfulHits => countGreat + countOk + countMeh; - double lnErfcApprox(double x) + private double lnErfcApprox(double x) { if (x <= 5) return Math.Log(SpecialFunctions.Erfc(x)); @@ -172,6 +172,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty return -Math.Pow(x, 2) - Math.Log(x) - Math.Log(Math.Sqrt(Math.PI)); } - double logDiff(double l1, double l2) => l1 + SpecialFunctions.Log1p(-Math.Exp(-(l1 - l2))); + private double logDiff(double l1, double l2) => l1 + SpecialFunctions.Log1p(-Math.Exp(-(l1 - l2))); } } From 7b5373ac5a8f70c332840a5b1fe374f96ac51c19 Mon Sep 17 00:00:00 2001 From: Natelytle Date: Sun, 27 Nov 2022 15:24:54 -0500 Subject: [PATCH 038/924] add comments --- .../Difficulty/TaikoPerformanceCalculator.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index d1e305fe9b..1d7af4e167 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -116,12 +116,17 @@ namespace osu.Game.Rulesets.Taiko.Difficulty return accuracyValue; } + /// + /// Estimates the player's tap deviation based on the OD, number of objects, and number of 300s, 100s, and misses, + /// assuming the player's mean hit error is 0. The estimation is consistent in that two SS scores on the same map with the same settings + /// will always return the same deviation. See: https://www.desmos.com/calculator/qlr946netu + /// private double? computeEstimatedDeviation(ScoreInfo score, TaikoDifficultyAttributes attributes) { if (totalSuccessfulHits == 0) return null; - // Create a new track to properly calculate the hit windows of 100s and 50s. + // Create a new track to properly calculate the hit window of 100s. var track = new TrackVirtual(10000); score.Mods.OfType().ForEach(m => m.ApplyToTrack(track)); double clockRate = track.Rate; @@ -135,6 +140,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty double root2 = Math.Sqrt(2); + // Log of the most likely deviation resulting in the score's hit judgements, differentiated such that 1 over the most likely deviation returns 0. double logLikelihoodGradient(double d) { if (d <= 0) From 6a27206abdffa6e18bca1c3f27df6614b9abf869 Mon Sep 17 00:00:00 2001 From: Natelytle Date: Fri, 2 Dec 2022 17:01:15 -0500 Subject: [PATCH 039/924] bugfix + tests --- .../Difficulty/TaikoPerformanceCalculator.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 19b43d0422..ca4e5fe53a 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -135,11 +135,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty double clockRate = track.Rate; double overallDifficulty = (50 - attributes.GreatHitWindow * clockRate) / 3; - double goodHitWindow = 0; + double goodHitWindow; if (overallDifficulty <= 5) goodHitWindow = (120 - 8 * overallDifficulty) / clockRate; - if (overallDifficulty > 5) - goodHitWindow = 80 - 6 * (overallDifficulty - 5); + else + goodHitWindow = (80 - 6 * (overallDifficulty - 5)) / clockRate; double root2 = Math.Sqrt(2); @@ -155,9 +155,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty double t1 = countGreat * ((attributes.GreatHitWindow * Math.Exp(-Math.Pow(attributes.GreatHitWindow / root2 * d, 2))) / p300); - double t2a = Math.Exp(Math.Log(goodHitWindow * Math.Exp(-Math.Pow(goodHitWindow / root2 * d, 2))) - logDiff(lnP300, lnP100)); - double t2b = Math.Exp(Math.Log(attributes.GreatHitWindow * Math.Exp(-Math.Pow(attributes.GreatHitWindow / root2 * d, 2))) - logDiff(lnP300, lnP100)); - double t2 = (countOk + 1) * (t2a - t2b); + double t2A = Math.Exp(Math.Log(goodHitWindow * Math.Exp(-Math.Pow(goodHitWindow / root2 * d, 2))) - logDiff(lnP300, lnP100)); + double t2B = Math.Exp(Math.Log(attributes.GreatHitWindow * Math.Exp(-Math.Pow(attributes.GreatHitWindow / root2 * d, 2))) - logDiff(lnP300, lnP100)); + double t2 = (countOk + 1) * (t2A - t2B); double t3 = countMiss * Math.Exp(Math.Log(goodHitWindow * Math.Exp(-Math.Pow(goodHitWindow / root2 * d, 2))) - lnP100); From 2b74c4ef8cd60e9d3d6f7907962759573a8896d8 Mon Sep 17 00:00:00 2001 From: Natelytle Date: Sat, 3 Dec 2022 15:39:38 -0500 Subject: [PATCH 040/924] tests return a greathitwindow of 0, add check --- .../Difficulty/TaikoPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index ca4e5fe53a..fd2dd47d30 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty /// private double? computeEstimatedDeviation(ScoreInfo score, TaikoDifficultyAttributes attributes) { - if (totalSuccessfulHits == 0) + if (totalSuccessfulHits == 0 || attributes.GreatHitWindow == 0) return null; // Create a new track to properly calculate the hit window of 100s. From 45e8d18b1b9618d8c068731a389285dc270dd2b4 Mon Sep 17 00:00:00 2001 From: Natelytle Date: Mon, 5 Dec 2022 22:33:13 -0500 Subject: [PATCH 041/924] fix extremely low OD breaking deviation calc --- .../Difficulty/TaikoPerformanceCalculator.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index fd2dd47d30..807e9359fc 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -153,13 +153,13 @@ namespace osu.Game.Rulesets.Taiko.Difficulty double lnP300 = lnErfcApprox(attributes.GreatHitWindow / root2 * d); double lnP100 = lnErfcApprox(goodHitWindow / root2 * d); - double t1 = countGreat * ((attributes.GreatHitWindow * Math.Exp(-Math.Pow(attributes.GreatHitWindow / root2 * d, 2))) / p300); + double t1 = countGreat * (Math.Exp(Math.Log(attributes.GreatHitWindow) + -Math.Pow(attributes.GreatHitWindow / root2 * d, 2)) / p300); - double t2A = Math.Exp(Math.Log(goodHitWindow * Math.Exp(-Math.Pow(goodHitWindow / root2 * d, 2))) - logDiff(lnP300, lnP100)); - double t2B = Math.Exp(Math.Log(attributes.GreatHitWindow * Math.Exp(-Math.Pow(attributes.GreatHitWindow / root2 * d, 2))) - logDiff(lnP300, lnP100)); + double t2A = Math.Exp(Math.Log(goodHitWindow) + -Math.Pow(goodHitWindow / root2 * d, 2) - logDiff(lnP300, lnP100)); + double t2B = Math.Exp(Math.Log(attributes.GreatHitWindow) + -Math.Pow(attributes.GreatHitWindow / root2 * d, 2) - logDiff(lnP300, lnP100)); double t2 = (countOk + 1) * (t2A - t2B); - double t3 = countMiss * Math.Exp(Math.Log(goodHitWindow * Math.Exp(-Math.Pow(goodHitWindow / root2 * d, 2))) - lnP100); + double t3 = countMiss * Math.Exp(Math.Log(goodHitWindow) + -Math.Pow(goodHitWindow / root2 * d, 2) - lnP100); return t1 + t2 - t3; } From 334f60f528b130cb47634e960c8fe47ff01581cc Mon Sep 17 00:00:00 2001 From: Natelytle Date: Wed, 22 Feb 2023 14:55:48 -0500 Subject: [PATCH 042/924] Reformat everything to be simpler --- .../Difficulty/TaikoPerformanceCalculator.cs | 78 ++++++++++--------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 807e9359fc..84b71a98d4 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -12,7 +12,6 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Scoring; using MathNet.Numerics; -using MathNet.Numerics.RootFinding; using osu.Framework.Audio.Track; using osu.Framework.Extensions.IEnumerableExtensions; @@ -24,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private int countOk; private int countMeh; private int countMiss; - private double? estimatedDeviation; + private double? estimatedUr; private double effectiveMissCount; @@ -41,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty countOk = score.Statistics.GetValueOrDefault(HitResult.Ok); countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); - estimatedDeviation = computeEstimatedDeviation(score, taikoAttributes); + estimatedUr = computeEstimatedUr(score, taikoAttributes); // The effectiveMissCount is calculated by gaining a ratio for totalSuccessfulHits and increasing the miss penalty for shorter object counts lower than 1000. if (totalSuccessfulHits > 0) @@ -71,7 +70,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty Difficulty = difficultyValue, Accuracy = accuracyValue, EffectiveMissCount = effectiveMissCount, - EstimatedUr = estimatedDeviation * 10, + EstimatedUr = estimatedUr, Total = totalValue }; } @@ -97,18 +96,18 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (score.Mods.Any(m => m is ModFlashlight)) difficultyValue *= 1.050 * lengthBonus; - if (estimatedDeviation == null) + if (estimatedUr == null) return 0; - return difficultyValue * Math.Pow(SpecialFunctions.Erf(40 / (Math.Sqrt(2) * estimatedDeviation.Value)), 2.0); + return difficultyValue * Math.Pow(SpecialFunctions.Erf(400 / (Math.Sqrt(2) * estimatedUr.Value)), 2.0); } private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes, bool isConvert) { - if (attributes.GreatHitWindow <= 0 || estimatedDeviation == null) + if (attributes.GreatHitWindow <= 0 || estimatedUr == null) return 0; - double accuracyValue = Math.Pow(7.5 / estimatedDeviation.Value, 1.1) * Math.Pow(attributes.StarRating / 2.7, 0.8) * 100.0; + double accuracyValue = Math.Pow(75 / estimatedUr.Value, 1.1) * Math.Pow(attributes.StarRating / 2.7, 0.8) * 100.0; double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3)); @@ -124,7 +123,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty /// assuming the player's mean hit error is 0. The estimation is consistent in that two SS scores on the same map with the same settings /// will always return the same deviation. See: https://www.desmos.com/calculator/qlr946netu /// - private double? computeEstimatedDeviation(ScoreInfo score, TaikoDifficultyAttributes attributes) + private double? computeEstimatedUr(ScoreInfo score, TaikoDifficultyAttributes attributes) { if (totalSuccessfulHits == 0 || attributes.GreatHitWindow == 0) return null; @@ -135,52 +134,55 @@ namespace osu.Game.Rulesets.Taiko.Difficulty double clockRate = track.Rate; double overallDifficulty = (50 - attributes.GreatHitWindow * clockRate) / 3; - double goodHitWindow; - if (overallDifficulty <= 5) - goodHitWindow = (120 - 8 * overallDifficulty) / clockRate; - else - goodHitWindow = (80 - 6 * (overallDifficulty - 5)) / clockRate; + double h300 = attributes.GreatHitWindow; + double h100 = overallDifficulty <= 5 ? (120 - 8 * overallDifficulty) / clockRate : (80 - 6 * (overallDifficulty - 5)) / clockRate; - double root2 = Math.Sqrt(2); - - // Log of the most likely deviation resulting in the score's hit judgements, differentiated such that 1 over the most likely deviation returns 0. - double logLikelihoodGradient(double d) + // Returns the likelihood of a deviation resulting in the score's hit judgements. The peak of the curve is the most likely deviation. + double likelihoodGradient(double d) { if (d <= 0) - return 1; + return 0; - double p300 = SpecialFunctions.Erf(attributes.GreatHitWindow / root2 * d); - double lnP300 = lnErfcApprox(attributes.GreatHitWindow / root2 * d); - double lnP100 = lnErfcApprox(goodHitWindow / root2 * d); + double p300 = logDiff(0, logPcHit(h300, d)); + double p100 = logDiff(logPcHit(h300, d), logPcHit(h100, d)); + double p0 = logPcHit(h100, d); - double t1 = countGreat * (Math.Exp(Math.Log(attributes.GreatHitWindow) + -Math.Pow(attributes.GreatHitWindow / root2 * d, 2)) / p300); + double gradient = Math.Exp( + (countGreat * p300 + + (countOk + 0.5) * p100 + + countMiss * p0) / totalHits + ); - double t2A = Math.Exp(Math.Log(goodHitWindow) + -Math.Pow(goodHitWindow / root2 * d, 2) - logDiff(lnP300, lnP100)); - double t2B = Math.Exp(Math.Log(attributes.GreatHitWindow) + -Math.Pow(attributes.GreatHitWindow / root2 * d, 2) - logDiff(lnP300, lnP100)); - double t2 = (countOk + 1) * (t2A - t2B); - - double t3 = countMiss * Math.Exp(Math.Log(goodHitWindow) + -Math.Pow(goodHitWindow / root2 * d, 2) - lnP100); - - return t1 + t2 - t3; + return -gradient; } - double root = Brent.FindRootExpand(logLikelihoodGradient, 0.02, 0.3); + double deviation = FindMinimum.OfScalarFunction(likelihoodGradient, 30); - return 1 / root; + return deviation * 10; } private int totalHits => countGreat + countOk + countMeh + countMiss; private int totalSuccessfulHits => countGreat + countOk + countMeh; - private double lnErfcApprox(double x) + private double logPcHit(double x, double deviation) => logErfcApprox(x / (deviation * Math.Sqrt(2))); + + // There is a numerical approximation to increase how far you can calculate Erfc(x). + private double logErfcApprox(double x) => x <= 5 ? Math.Log(SpecialFunctions.Erfc(x)) : -Math.Pow(x, 2) - Math.Log(x) - Math.Log(Math.Sqrt(Math.PI)); + + // Log rules make subtraction of the non-log value non-trivial, this method simply subtracts the base value of 2 logs. + private double logDiff(double firstLog, double secondLog) { - if (x <= 5) - return Math.Log(SpecialFunctions.Erfc(x)); + double maxVal = Math.Max(firstLog, secondLog); - return -Math.Pow(x, 2) - Math.Log(x) - Math.Log(Math.Sqrt(Math.PI)); + // Avoid negative infinity - negative infinity (NaN) by checking if the higher value is negative infinity. + // Shouldn't ever happen, but good for redundancy purposes. + if (double.IsNegativeInfinity(maxVal)) + { + return maxVal; + } + + return firstLog + SpecialFunctions.Log1p(-Math.Exp(-(firstLog - secondLog))); } - - private double logDiff(double l1, double l2) => l1 + SpecialFunctions.Log1p(-Math.Exp(-(l1 - l2))); } } From adf16187b1d0e1ceb2520f5a3eccb7551ce7047a Mon Sep 17 00:00:00 2001 From: Natelytle Date: Wed, 22 Feb 2023 21:03:02 -0500 Subject: [PATCH 043/924] Change accuracy scaling --- .../Difficulty/TaikoPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 84b71a98d4..88c26e9e6a 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (attributes.GreatHitWindow <= 0 || estimatedUr == null) return 0; - double accuracyValue = Math.Pow(75 / estimatedUr.Value, 1.1) * Math.Pow(attributes.StarRating / 2.7, 0.8) * 100.0; + double accuracyValue = Math.Pow(60 / estimatedUr.Value, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 100.0; double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3)); From 858afcd0b3ecdd10869cf540bcd7d79a9e8b1b22 Mon Sep 17 00:00:00 2001 From: Natelytle Date: Mon, 20 Mar 2023 22:00:33 -0400 Subject: [PATCH 044/924] Pass OK hit window as a separate difficulty attribute, fix erfc approximation --- .../Difficulty/TaikoDifficultyAttributes.cs | 9 +++++++++ .../Difficulty/TaikoDifficultyCalculator.cs | 1 + .../Difficulty/TaikoPerformanceCalculator.cs | 18 ++++++------------ 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs index 72452e27b3..f5c9280590 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs @@ -43,6 +43,15 @@ namespace osu.Game.Rulesets.Taiko.Difficulty [JsonProperty("great_hit_window")] public double GreatHitWindow { get; set; } + /// + /// The perceived hit window for an OK hit inclusive of rate-adjusting mods (DT/HT/etc). + /// + /// + /// Rate-adjusting mods don't directly affect the hit window, but have a perceived effect as a result of adjusting audio timing. + /// + [JsonProperty("ok_hit_window")] + public double OkHitWindow { get; set; } + public override IEnumerable<(int attributeId, object value)> ToDatabaseAttributes() { foreach (var v in base.ToDatabaseAttributes()) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 24b5f5939a..90bcb74533 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -95,6 +95,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty ColourDifficulty = colourRating, PeakDifficulty = combinedRating, GreatHitWindow = hitWindows.WindowFor(HitResult.Great) / clockRate, + OkHitWindow = hitWindows.WindowFor(HitResult.Ok) / clockRate, MaxCombo = beatmap.HitObjects.Count(h => h is Hit), }; } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 88c26e9e6a..e430616d54 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -12,8 +12,6 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Scoring; using MathNet.Numerics; -using osu.Framework.Audio.Track; -using osu.Framework.Extensions.IEnumerableExtensions; namespace osu.Game.Rulesets.Taiko.Difficulty { @@ -125,17 +123,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty /// private double? computeEstimatedUr(ScoreInfo score, TaikoDifficultyAttributes attributes) { - if (totalSuccessfulHits == 0 || attributes.GreatHitWindow == 0) + if (totalSuccessfulHits == 0 || attributes.GreatHitWindow <= 0) return null; - // Create a new track to properly calculate the hit window of 100s. - var track = new TrackVirtual(10000); - score.Mods.OfType().ForEach(m => m.ApplyToTrack(track)); - double clockRate = track.Rate; - - double overallDifficulty = (50 - attributes.GreatHitWindow * clockRate) / 3; double h300 = attributes.GreatHitWindow; - double h100 = overallDifficulty <= 5 ? (120 - 8 * overallDifficulty) / clockRate : (80 - 6 * (overallDifficulty - 5)) / clockRate; + double h100 = attributes.OkHitWindow; // Returns the likelihood of a deviation resulting in the score's hit judgements. The peak of the curve is the most likely deviation. double likelihoodGradient(double d) @@ -167,8 +159,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private double logPcHit(double x, double deviation) => logErfcApprox(x / (deviation * Math.Sqrt(2))); - // There is a numerical approximation to increase how far you can calculate Erfc(x). - private double logErfcApprox(double x) => x <= 5 ? Math.Log(SpecialFunctions.Erfc(x)) : -Math.Pow(x, 2) - Math.Log(x) - Math.Log(Math.Sqrt(Math.PI)); + // There's a numerical approximation to increase how far you can calculate ln(erfc(x)). + private double logErfcApprox(double x) => x <= 5 + ? Math.Log(SpecialFunctions.Erfc(x)) + : -Math.Pow(x, 2) - Math.Log(x * Math.Sqrt(Math.PI)); // https://www.desmos.com/calculator/kdbxwxgf01 // Log rules make subtraction of the non-log value non-trivial, this method simply subtracts the base value of 2 logs. private double logDiff(double firstLog, double secondLog) From 9aa11e00905452f9181755f3997d517c84c3365b Mon Sep 17 00:00:00 2001 From: Natelytle Date: Mon, 20 Mar 2023 23:13:33 -0400 Subject: [PATCH 045/924] update desmos --- .../Difficulty/TaikoPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index e430616d54..be250f3b80 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty /// /// Estimates the player's tap deviation based on the OD, number of objects, and number of 300s, 100s, and misses, /// assuming the player's mean hit error is 0. The estimation is consistent in that two SS scores on the same map with the same settings - /// will always return the same deviation. See: https://www.desmos.com/calculator/qlr946netu + /// will always return the same deviation. See: https://www.desmos.com/calculator/x3mvtir093 /// private double? computeEstimatedUr(ScoreInfo score, TaikoDifficultyAttributes attributes) { From 31c8cf09332410866c994586f38483d55010c3a2 Mon Sep 17 00:00:00 2001 From: Natelytle Date: Thu, 27 Jul 2023 22:44:56 -0400 Subject: [PATCH 046/924] Buff accuracy scaling --- .../Difficulty/TaikoPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index a38eaf6a31..39246f962c 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (attributes.GreatHitWindow <= 0 || estimatedUr == null) return 0; - double accuracyValue = Math.Pow(60 / estimatedUr.Value, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 100.0; + double accuracyValue = Math.Pow(70 / estimatedUr.Value, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 110.0; double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3)); From 4de024675c1738d788238d66e88605ff5652159e Mon Sep 17 00:00:00 2001 From: Natelytle Date: Thu, 27 Jul 2023 23:02:07 -0400 Subject: [PATCH 047/924] Make comments more professional --- .../Difficulty/TaikoPerformanceCalculator.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 39246f962c..f5cdb02c82 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -115,9 +115,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty } /// - /// Estimates the player's tap deviation based on the OD, number of objects, and number of 300s, 100s, and misses, - /// assuming the player's mean hit error is 0. The estimation is consistent in that two SS scores on the same map with the same settings - /// will always return the same deviation. See: https://www.desmos.com/calculator/x3mvtir093 + /// Calculates the tap deviation for a player using the OD, object count, and scores of 300s, 100s, and misses, with an assumed mean hit error of 0. + /// Consistency is ensured as identical SS scores on the same map and settings yield the same deviation. /// private double? computeEstimatedUr(ScoreInfo score, TaikoDifficultyAttributes attributes) { @@ -127,7 +126,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty double h300 = attributes.GreatHitWindow; double h100 = attributes.OkHitWindow; - // Returns the likelihood of a deviation resulting in the score's hit judgements. The peak of the curve is the most likely deviation. + // Determines the probability of a deviation leading to the score's hit evaluations. The curve's apex represents the most probable deviation. double likelihoodGradient(double d) { if (d <= 0) @@ -157,18 +156,17 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private double logPcHit(double x, double deviation) => logErfcApprox(x / (deviation * Math.Sqrt(2))); - // There's a numerical approximation to increase how far you can calculate ln(erfc(x)). + // Utilises a numerical approximation to extend the computation range of ln(erfc(x)). private double logErfcApprox(double x) => x <= 5 ? Math.Log(SpecialFunctions.Erfc(x)) : -Math.Pow(x, 2) - Math.Log(x * Math.Sqrt(Math.PI)); // https://www.desmos.com/calculator/kdbxwxgf01 - // Log rules make subtraction of the non-log value non-trivial, this method simply subtracts the base value of 2 logs. + // Subtracts the base value of two logs, circumventing log rules that typically complicate subtraction of non-logarithmic values. private double logDiff(double firstLog, double secondLog) { double maxVal = Math.Max(firstLog, secondLog); - // Avoid negative infinity - negative infinity (NaN) by checking if the higher value is negative infinity. - // Shouldn't ever happen, but good for redundancy purposes. + // To avoid a NaN result, a check is performed to prevent subtraction of two negative infinity values. if (double.IsNegativeInfinity(maxVal)) { return maxVal; From e56942012cf24981d517ec3d50128412feb7e76b Mon Sep 17 00:00:00 2001 From: Natelytle Date: Fri, 28 Jul 2023 11:38:30 -0400 Subject: [PATCH 048/924] Serialize ok hit window attribute to db --- osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs | 2 ++ osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs index 9d498d705a..451aed183d 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs @@ -59,6 +59,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty yield return (ATTRIB_ID_DIFFICULTY, StarRating); yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow); + yield return (ATTRIB_ID_OK_HIT_WINDOW, OkHitWindow); } public override void FromDatabaseAttributes(IReadOnlyDictionary values, IBeatmapOnlineInfo onlineInfo) @@ -67,6 +68,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty StarRating = values[ATTRIB_ID_DIFFICULTY]; GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW]; + OkHitWindow = values[ATTRIB_ID_OK_HIT_WINDOW]; } } } diff --git a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs index 5a01faa417..b21b2cf567 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs @@ -30,6 +30,7 @@ namespace osu.Game.Rulesets.Difficulty protected const int ATTRIB_ID_LEGACY_ACCURACY_SCORE = 23; protected const int ATTRIB_ID_LEGACY_COMBO_SCORE = 25; protected const int ATTRIB_ID_LEGACY_BONUS_SCORE_RATIO = 27; + protected const int ATTRIB_ID_OK_HIT_WINDOW = 29; /// /// The mods which were applied to the beatmap. From 5f0020b0ca05aceeb6102d076dd7c3dfa8d59dd2 Mon Sep 17 00:00:00 2001 From: Natelytle Date: Sun, 30 Jul 2023 20:14:15 -0400 Subject: [PATCH 049/924] Reduce accuracy scaling --- .../Difficulty/TaikoPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index af988ef2d1..723a6612bc 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (attributes.GreatHitWindow <= 0 || estimatedUr == null) return 0; - double accuracyValue = Math.Pow(70 / estimatedUr.Value, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 110.0; + double accuracyValue = Math.Pow(65 / estimatedUr.Value, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 100.0; double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3)); From e803b0215f146e449f12a77fae1f09db4fdae30f Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 30 Dec 2023 01:38:08 +0100 Subject: [PATCH 050/924] flip along grid axis --- .../Edit/OsuSelectionHandler.cs | 19 ++++++++++--------- osu.Game/Utils/GeometryUtils.cs | 11 +++++++++++ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index cea2adc6e2..021c735ebd 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -15,7 +16,6 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Utils; using osuTK; @@ -28,6 +28,9 @@ namespace osu.Game.Rulesets.Osu.Edit [Resolved(CanBeNull = true)] private IDistanceSnapProvider? snapProvider { get; set; } + [Resolved] + private OsuGridToolboxGroup gridToolbox { get; set; } = null!; + /// /// During a transform, the initial path types of a single selected slider are stored so they /// can be maintained throughout the operation. @@ -104,13 +107,16 @@ namespace osu.Game.Rulesets.Osu.Edit { var hitObjects = selectedMovableObjects; - var flipQuad = flipOverOrigin ? new Quad(0, 0, OsuPlayfield.BASE_SIZE.X, OsuPlayfield.BASE_SIZE.Y) : GeometryUtils.GetSurroundingQuad(hitObjects); + var flipQuad = flipOverOrigin ? new Quad(gridToolbox.StartPositionX.Value, gridToolbox.StartPositionY.Value, 0, 0) : GeometryUtils.GetSurroundingQuad(hitObjects); + var flipAxis = flipOverOrigin ? new Vector2(MathF.Cos(MathHelper.DegreesToRadians(gridToolbox.GridLinesRotation.Value)), MathF.Sin(MathHelper.DegreesToRadians(gridToolbox.GridLinesRotation.Value))) : Vector2.UnitX; + flipAxis = direction == Direction.Vertical ? flipAxis.PerpendicularLeft : flipAxis; + var controlPointFlipQuad = new Quad(); bool didFlip = false; foreach (var h in hitObjects) { - var flippedPosition = GeometryUtils.GetFlippedPosition(direction, flipQuad, h.Position); + var flippedPosition = GeometryUtils.GetFlippedPosition(flipAxis, flipQuad, h.Position); if (!Precision.AlmostEquals(flippedPosition, h.Position)) { @@ -123,12 +129,7 @@ namespace osu.Game.Rulesets.Osu.Edit didFlip = true; foreach (var cp in slider.Path.ControlPoints) - { - cp.Position = new Vector2( - (direction == Direction.Horizontal ? -1 : 1) * cp.Position.X, - (direction == Direction.Vertical ? -1 : 1) * cp.Position.Y - ); - } + cp.Position = GeometryUtils.GetFlippedPosition(flipAxis, controlPointFlipQuad, cp.Position); } } diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index e0d217dd48..aacf9b91f9 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -70,6 +70,17 @@ namespace osu.Game.Utils return position; } + /// + /// Given a flip axis vector, a surrounding quad for all selected objects, and a position, + /// will return the flipped position in screen space coordinates. + /// + public static Vector2 GetFlippedPosition(Vector2 axis, Quad quad, Vector2 position) + { + var centre = quad.Centre; + + return position - 2 * Vector2.Dot(position - centre, axis) * axis; + } + /// /// Given a scale vector, a surrounding quad for all selected objects, and a position, /// will return the scaled position in screen space coordinates. From 078fe5a78ca5b0744d12c6f4fa8f242a0730986c Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 30 Dec 2023 01:53:19 +0100 Subject: [PATCH 051/924] Rotate popover rotates around grid center --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 2 +- osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs | 8 +++++--- osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs | 4 +++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 84d5adbc52..02e98d75a7 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Edit RightToolbox.AddRange(new EditorToolboxGroup[] { OsuGridToolboxGroup, - new TransformToolboxGroup { RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, }, + new TransformToolboxGroup { RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, GridToolbox = OsuGridToolboxGroup }, FreehandlSliderToolboxGroup } ); diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs index f09d6b78e6..02a8ff5872 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.UserInterfaceV2; -using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit.Components.RadioButtons; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -19,14 +18,17 @@ namespace osu.Game.Rulesets.Osu.Edit { private readonly SelectionRotationHandler rotationHandler; + private readonly OsuGridToolboxGroup gridToolbox; + private readonly Bindable rotationInfo = new Bindable(new PreciseRotationInfo(0, RotationOrigin.PlayfieldCentre)); private SliderWithTextBoxInput angleInput = null!; private EditorRadioButtonCollection rotationOrigin = null!; - public PreciseRotationPopover(SelectionRotationHandler rotationHandler) + public PreciseRotationPopover(SelectionRotationHandler rotationHandler, OsuGridToolboxGroup gridToolbox) { this.rotationHandler = rotationHandler; + this.gridToolbox = gridToolbox; AllowableAnchors = new[] { Anchor.CentreLeft, Anchor.CentreRight }; } @@ -78,7 +80,7 @@ namespace osu.Game.Rulesets.Osu.Edit rotationInfo.BindValueChanged(rotation => { - rotationHandler.Update(rotation.NewValue.Degrees, rotation.NewValue.Origin == RotationOrigin.PlayfieldCentre ? OsuPlayfield.BASE_SIZE / 2 : null); + rotationHandler.Update(rotation.NewValue.Degrees, rotation.NewValue.Origin == RotationOrigin.PlayfieldCentre ? gridToolbox.StartPosition.Value : null); }); } diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs index 3da9f5b69b..f8df45f545 100644 --- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs @@ -24,6 +24,8 @@ namespace osu.Game.Rulesets.Osu.Edit public SelectionRotationHandler RotationHandler { get; init; } = null!; + public OsuGridToolboxGroup GridToolbox { get; init; } = null!; + public TransformToolboxGroup() : base("transform") { @@ -41,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Edit { rotateButton = new EditorToolButton("Rotate", () => new SpriteIcon { Icon = FontAwesome.Solid.Undo }, - () => new PreciseRotationPopover(RotationHandler)), + () => new PreciseRotationPopover(RotationHandler, GridToolbox)), // TODO: scale } }; From 09852bc46b6b0b7a78914e19ba3414efab60afdd Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 21:23:13 +0100 Subject: [PATCH 052/924] fix horizontal vs vertical flips being mixed up when rotation angle is too big --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 021c735ebd..1cb206c2f8 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -107,9 +106,13 @@ namespace osu.Game.Rulesets.Osu.Edit { var hitObjects = selectedMovableObjects; + // If we're flipping over the origin, we take the grid origin position from the grid toolbox. var flipQuad = flipOverOrigin ? new Quad(gridToolbox.StartPositionX.Value, gridToolbox.StartPositionY.Value, 0, 0) : GeometryUtils.GetSurroundingQuad(hitObjects); - var flipAxis = flipOverOrigin ? new Vector2(MathF.Cos(MathHelper.DegreesToRadians(gridToolbox.GridLinesRotation.Value)), MathF.Sin(MathHelper.DegreesToRadians(gridToolbox.GridLinesRotation.Value))) : Vector2.UnitX; + // If we're flipping over the origin, we take the grid rotation from the grid toolbox. + // We want to normalize the rotation angle to -45 to 45 degrees, so horizontal vs vertical flip is not mixed up by the rotation and it stays intuitive to use. + var flipAxis = flipOverOrigin ? GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 405) % 90 - 45)) : Vector2.UnitX; flipAxis = direction == Direction.Vertical ? flipAxis.PerpendicularLeft : flipAxis; + var controlPointFlipQuad = new Quad(); bool didFlip = false; From 3495510c7b651efee091bad375591b2aa7875102 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 30 Dec 2023 19:16:05 +0100 Subject: [PATCH 053/924] kinda working grid from points --- .../Edit/OsuBlueprintContainer.cs | 42 +++++++++++++++++++ .../Edit/OsuGridToolboxGroup.cs | 21 ++++++++++ 2 files changed, 63 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs index 54c54fca17..8b391cd06e 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.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. +using osu.Framework.Allocation; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; @@ -8,16 +10,26 @@ using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; +using osuTK; namespace osu.Game.Rulesets.Osu.Edit { public partial class OsuBlueprintContainer : ComposeBlueprintContainer { + private OsuGridToolboxGroup gridToolbox = null!; + public OsuBlueprintContainer(HitObjectComposer composer) : base(composer) { } + [BackgroundDependencyLoader] + private void load(OsuGridToolboxGroup gridToolbox) + { + this.gridToolbox = gridToolbox; + gridToolbox.GridFromPointsClicked += OnGridFromPointsClicked; + } + protected override SelectionHandler CreateSelectionHandler() => new OsuSelectionHandler(); public override HitObjectSelectionBlueprint? CreateHitObjectBlueprintFor(HitObject hitObject) @@ -36,5 +48,35 @@ namespace osu.Game.Rulesets.Osu.Edit return base.CreateHitObjectBlueprintFor(hitObject); } + + private bool isPlacingGridFromPoints; + private Vector2? gridFromPointsStart; + + private void OnGridFromPointsClicked() + { + isPlacingGridFromPoints = true; + gridFromPointsStart = null; + } + + protected override bool OnClick(ClickEvent e) + { + if (!isPlacingGridFromPoints) + return base.OnClick(e); + + var pos = ToLocalSpace(Composer.FindSnappedPositionAndTime(e.ScreenSpaceMousePosition).ScreenSpacePosition); + + if (!gridFromPointsStart.HasValue) + { + gridFromPointsStart = pos; + } + else + { + gridToolbox.SetGridFromPoints(gridFromPointsStart.Value, pos); + isPlacingGridFromPoints = false; + gridFromPointsStart = null; + } + + return true; + } } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 76e735449a..05b90696a0 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -12,6 +13,7 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.UI; @@ -90,6 +92,8 @@ namespace osu.Game.Rulesets.Osu.Edit private ExpandableSlider gridLinesRotationSlider = null!; private EditorRadioButtonCollection gridTypeButtons = null!; + public event Action? GridFromPointsClicked; + public OsuGridToolboxGroup() : base("grid") { @@ -97,6 +101,17 @@ namespace osu.Game.Rulesets.Osu.Edit private const float max_automatic_spacing = 64; + public void SetGridFromPoints(Vector2 point1, Vector2 point2) + { + StartPositionX.Value = point1.X; + StartPositionY.Value = point1.Y; + GridLinesRotation.Value = (MathHelper.RadiansToDegrees(MathF.Atan2(point2.Y - point1.Y, point2.X - point1.X)) + 405) % 90 - 45; + float dist = Vector2.Distance(point1, point2); + while (dist > Spacing.MaxValue) + dist /= 2; + Spacing.Value = dist; + } + [BackgroundDependencyLoader] private void load() { @@ -129,6 +144,12 @@ namespace osu.Game.Rulesets.Osu.Edit Spacing = new Vector2(0f, 10f), Children = new Drawable[] { + new RoundedButton + { + Action = () => GridFromPointsClicked?.Invoke(), + RelativeSizeAxes = Axes.X, + Text = "Grid from points", + }, gridTypeButtons = new EditorRadioButtonCollection { RelativeSizeAxes = Axes.X, From fea0ceb49899643371e5f81bf32ccf74fac560df Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 30 Dec 2023 20:22:14 +0100 Subject: [PATCH 054/924] improve the grid from points --- .../Edit/OsuBlueprintContainer.cs | 26 ++++++++++++++- .../Edit/OsuGridToolboxGroup.cs | 32 ++++++++++++------- 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs index 8b391cd06e..79b6fafcab 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs @@ -58,12 +58,17 @@ namespace osu.Game.Rulesets.Osu.Edit gridFromPointsStart = null; } + protected override bool OnMouseDown(MouseDownEvent e) + { + return isPlacingGridFromPoints || base.OnMouseDown(e); + } + protected override bool OnClick(ClickEvent e) { if (!isPlacingGridFromPoints) return base.OnClick(e); - var pos = ToLocalSpace(Composer.FindSnappedPositionAndTime(e.ScreenSpaceMousePosition).ScreenSpacePosition); + var pos = ToLocalSpace(Composer.FindSnappedPositionAndTime(e.ScreenSpaceMousePosition, ~SnapType.GlobalGrids).ScreenSpacePosition); if (!gridFromPointsStart.HasValue) { @@ -78,5 +83,24 @@ namespace osu.Game.Rulesets.Osu.Edit return true; } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + if (!isPlacingGridFromPoints) + return base.OnMouseMove(e); + + var pos = ToLocalSpace(Composer.FindSnappedPositionAndTime(e.ScreenSpaceMousePosition, ~SnapType.GlobalGrids).ScreenSpacePosition); + + if (!gridFromPointsStart.HasValue) + { + gridToolbox.StartPosition.Value = pos; + } + else + { + gridToolbox.SetGridFromPoints(gridFromPointsStart.Value, pos); + } + + return true; + } } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 05b90696a0..b07d8dcd56 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -39,7 +39,6 @@ namespace osu.Game.Rulesets.Osu.Edit { MinValue = 0f, MaxValue = OsuPlayfield.BASE_SIZE.X, - Precision = 1f }; /// @@ -49,7 +48,6 @@ namespace osu.Game.Rulesets.Osu.Edit { MinValue = 0f, MaxValue = OsuPlayfield.BASE_SIZE.Y, - Precision = 1f }; /// @@ -59,7 +57,6 @@ namespace osu.Game.Rulesets.Osu.Edit { MinValue = 4f, MaxValue = 128f, - Precision = 1f }; /// @@ -69,7 +66,6 @@ namespace osu.Game.Rulesets.Osu.Edit { MinValue = -45f, MaxValue = 45f, - Precision = 1f }; /// @@ -105,9 +101,15 @@ namespace osu.Game.Rulesets.Osu.Edit { StartPositionX.Value = point1.X; StartPositionY.Value = point1.Y; - GridLinesRotation.Value = (MathHelper.RadiansToDegrees(MathF.Atan2(point2.Y - point1.Y, point2.X - point1.X)) + 405) % 90 - 45; + + // Get the angle between the two points and normalize to the valid range. + float period = GridType.Value == PositionSnapGridType.Triangle ? 60 : 90; + GridLinesRotation.Value = (MathHelper.RadiansToDegrees(MathF.Atan2(point2.Y - point1.Y, point2.X - point1.X)) + + 360 + period / 2) % period - period / 2; + + // Divide the distance so that there is a good density of grid lines. float dist = Vector2.Distance(point1, point2); - while (dist > Spacing.MaxValue) + while (dist > 32) dist /= 2; Spacing.Value = dist; } @@ -181,22 +183,28 @@ namespace osu.Game.Rulesets.Osu.Edit StartPositionX.BindValueChanged(x => { - startPositionXSlider.ContractedLabelText = $"X: {x.NewValue:N0}"; - startPositionXSlider.ExpandedLabelText = $"X Offset: {x.NewValue:N0}"; + startPositionXSlider.ContractedLabelText = $"X: {x.NewValue:#,0.##}"; + startPositionXSlider.ExpandedLabelText = $"X Offset: {x.NewValue:#,0.##}"; StartPosition.Value = new Vector2(x.NewValue, StartPosition.Value.Y); }, true); StartPositionY.BindValueChanged(y => { - startPositionYSlider.ContractedLabelText = $"Y: {y.NewValue:N0}"; - startPositionYSlider.ExpandedLabelText = $"Y Offset: {y.NewValue:N0}"; + startPositionYSlider.ContractedLabelText = $"Y: {y.NewValue:#,0.##}"; + startPositionYSlider.ExpandedLabelText = $"Y Offset: {y.NewValue:#,0.##}"; StartPosition.Value = new Vector2(StartPosition.Value.X, y.NewValue); }, true); + StartPosition.BindValueChanged(pos => + { + StartPositionX.Value = pos.NewValue.X; + StartPositionY.Value = pos.NewValue.Y; + }); + Spacing.BindValueChanged(spacing => { - spacingSlider.ContractedLabelText = $"S: {spacing.NewValue:N0}"; - spacingSlider.ExpandedLabelText = $"Spacing: {spacing.NewValue:N0}"; + spacingSlider.ContractedLabelText = $"S: {spacing.NewValue:#,0.##}"; + spacingSlider.ExpandedLabelText = $"Spacing: {spacing.NewValue:#,0.##}"; SpacingVector.Value = new Vector2(spacing.NewValue); editorBeatmap.BeatmapInfo.GridSize = (int)spacing.NewValue; }, true); From c2ea1848ca96c580f36cef4393c2dd5c145f03c1 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 30 Dec 2023 20:58:21 +0100 Subject: [PATCH 055/924] fix period --- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index b07d8dcd56..31148f10a2 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -64,8 +64,8 @@ namespace osu.Game.Rulesets.Osu.Edit /// public BindableFloat GridLinesRotation { get; } = new BindableFloat(0f) { - MinValue = -45f, - MaxValue = 45f, + MinValue = -90f, + MaxValue = 90f, }; /// @@ -103,9 +103,9 @@ namespace osu.Game.Rulesets.Osu.Edit StartPositionY.Value = point1.Y; // Get the angle between the two points and normalize to the valid range. - float period = GridType.Value == PositionSnapGridType.Triangle ? 60 : 90; + const float period = 180; GridLinesRotation.Value = (MathHelper.RadiansToDegrees(MathF.Atan2(point2.Y - point1.Y, point2.X - point1.X)) - + 360 + period / 2) % period - period / 2; + + period * 1.5f) % period - period * 0.5f; // Divide the distance so that there is a good density of grid lines. float dist = Vector2.Distance(point1, point2); From c952e2f6209ff0cffa7b7db01266baf2cdab73d3 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 02:12:50 +0100 Subject: [PATCH 056/924] deselect stuff stuff when grid from points --- osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs index 79b6fafcab..a5f7185a7d 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs @@ -56,6 +56,9 @@ namespace osu.Game.Rulesets.Osu.Edit { isPlacingGridFromPoints = true; gridFromPointsStart = null; + + // Deselect all objects because we cant snap to objects which are selected. + DeselectAll(); } protected override bool OnMouseDown(MouseDownEvent e) From 7a0535a2ebe1e612691f16ced09137feb1aa9da3 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 02:26:14 +0100 Subject: [PATCH 057/924] add right click for abort --- .../Edit/OsuBlueprintContainer.cs | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs index a5f7185a7d..45cf754c57 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs @@ -11,6 +11,7 @@ using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; using osuTK; +using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit { @@ -62,28 +63,23 @@ namespace osu.Game.Rulesets.Osu.Edit } protected override bool OnMouseDown(MouseDownEvent e) - { - return isPlacingGridFromPoints || base.OnMouseDown(e); - } - - protected override bool OnClick(ClickEvent e) { if (!isPlacingGridFromPoints) - return base.OnClick(e); + return base.OnMouseDown(e); - var pos = ToLocalSpace(Composer.FindSnappedPositionAndTime(e.ScreenSpaceMousePosition, ~SnapType.GlobalGrids).ScreenSpacePosition); + var pos = snappedLocalPosition(e); if (!gridFromPointsStart.HasValue) - { gridFromPointsStart = pos; - } else { gridToolbox.SetGridFromPoints(gridFromPointsStart.Value, pos); isPlacingGridFromPoints = false; - gridFromPointsStart = null; } + if (e.Button == MouseButton.Right) + isPlacingGridFromPoints = false; + return true; } @@ -92,18 +88,16 @@ namespace osu.Game.Rulesets.Osu.Edit if (!isPlacingGridFromPoints) return base.OnMouseMove(e); - var pos = ToLocalSpace(Composer.FindSnappedPositionAndTime(e.ScreenSpaceMousePosition, ~SnapType.GlobalGrids).ScreenSpacePosition); + var pos = snappedLocalPosition(e); if (!gridFromPointsStart.HasValue) - { gridToolbox.StartPosition.Value = pos; - } else - { gridToolbox.SetGridFromPoints(gridFromPointsStart.Value, pos); - } return true; } + + private Vector2 snappedLocalPosition(UIEvent e) => ToLocalSpace(Composer.FindSnappedPositionAndTime(e.ScreenSpaceMousePosition, ~SnapType.GlobalGrids).ScreenSpacePosition); } } From 85bfd6137a5f286341a14615a9ac0ea1fee8137f Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 03:53:42 +0100 Subject: [PATCH 058/924] improve UI From 4e3fe5112d9d02171fb19e23bd26520f34590c87 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 1 Jan 2024 16:09:18 +0100 Subject: [PATCH 059/924] merge conflict fix --- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 31148f10a2..c318bac64d 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -86,6 +86,7 @@ namespace osu.Game.Rulesets.Osu.Edit private ExpandableSlider startPositionYSlider = null!; private ExpandableSlider spacingSlider = null!; private ExpandableSlider gridLinesRotationSlider = null!; + private RoundedButton gridFromPointsButton = null!; private EditorRadioButtonCollection gridTypeButtons = null!; public event Action? GridFromPointsClicked; @@ -146,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.Edit Spacing = new Vector2(0f, 10f), Children = new Drawable[] { - new RoundedButton + gridFromPointsButton = new RoundedButton { Action = () => GridFromPointsClicked?.Invoke(), RelativeSizeAxes = Axes.X, @@ -217,6 +218,8 @@ namespace osu.Game.Rulesets.Osu.Edit expandingContainer?.Expanded.BindValueChanged(v => { + gridFromPointsButton.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint); + gridFromPointsButton.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None; gridTypeButtons.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint); gridTypeButtons.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None; }, true); From 98505d0bba18fe4b08c06bf44d77298040c1c5e8 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 18:58:29 +0100 Subject: [PATCH 060/924] improve grid from points tool code --- .../Edit/GridFromPointsTool.cs | 72 +++++++++++++++++++ .../Edit/OsuBlueprintContainer.cs | 63 ---------------- .../Edit/OsuHitObjectComposer.cs | 18 +++-- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 5 +- 4 files changed, 90 insertions(+), 68 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs diff --git a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs new file mode 100644 index 0000000000..722d9d1303 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs @@ -0,0 +1,72 @@ +// 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.Input.Events; +using osu.Game.Rulesets.Edit; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public partial class GridFromPointsTool : Drawable + { + [Resolved] + private OsuGridToolboxGroup gridToolboxGroup { get; set; } = null!; + + [Resolved(canBeNull: true)] + private IPositionSnapProvider? snapProvider { get; set; } + + public bool IsPlacing { get; private set; } + + private Vector2? startPosition; + + public void BeginPlacement() + { + IsPlacing = true; + startPosition = null; + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (!IsPlacing) + return base.OnMouseDown(e); + + var pos = snappedLocalPosition(e); + + if (!startPosition.HasValue) + startPosition = pos; + else + { + gridToolboxGroup.SetGridFromPoints(startPosition.Value, pos); + IsPlacing = false; + } + + if (e.Button == MouseButton.Right) + IsPlacing = false; + + return true; + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + if (!IsPlacing) + return base.OnMouseMove(e); + + var pos = snappedLocalPosition(e); + + if (!startPosition.HasValue) + gridToolboxGroup.StartPosition.Value = pos; + else + gridToolboxGroup.SetGridFromPoints(startPosition.Value, pos); + + return true; + } + + private Vector2 snappedLocalPosition(UIEvent e) + { + return ToLocalSpace(snapProvider?.FindSnappedPositionAndTime(e.ScreenSpaceMousePosition, ~SnapType.GlobalGrids).ScreenSpacePosition ?? e.ScreenSpaceMousePosition); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs index 45cf754c57..54c54fca17 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs @@ -1,8 +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 osu.Framework.Allocation; -using osu.Framework.Input.Events; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; @@ -10,27 +8,16 @@ using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; -using osuTK; -using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit { public partial class OsuBlueprintContainer : ComposeBlueprintContainer { - private OsuGridToolboxGroup gridToolbox = null!; - public OsuBlueprintContainer(HitObjectComposer composer) : base(composer) { } - [BackgroundDependencyLoader] - private void load(OsuGridToolboxGroup gridToolbox) - { - this.gridToolbox = gridToolbox; - gridToolbox.GridFromPointsClicked += OnGridFromPointsClicked; - } - protected override SelectionHandler CreateSelectionHandler() => new OsuSelectionHandler(); public override HitObjectSelectionBlueprint? CreateHitObjectBlueprintFor(HitObject hitObject) @@ -49,55 +36,5 @@ namespace osu.Game.Rulesets.Osu.Edit return base.CreateHitObjectBlueprintFor(hitObject); } - - private bool isPlacingGridFromPoints; - private Vector2? gridFromPointsStart; - - private void OnGridFromPointsClicked() - { - isPlacingGridFromPoints = true; - gridFromPointsStart = null; - - // Deselect all objects because we cant snap to objects which are selected. - DeselectAll(); - } - - protected override bool OnMouseDown(MouseDownEvent e) - { - if (!isPlacingGridFromPoints) - return base.OnMouseDown(e); - - var pos = snappedLocalPosition(e); - - if (!gridFromPointsStart.HasValue) - gridFromPointsStart = pos; - else - { - gridToolbox.SetGridFromPoints(gridFromPointsStart.Value, pos); - isPlacingGridFromPoints = false; - } - - if (e.Button == MouseButton.Right) - isPlacingGridFromPoints = false; - - return true; - } - - protected override bool OnMouseMove(MouseMoveEvent e) - { - if (!isPlacingGridFromPoints) - return base.OnMouseMove(e); - - var pos = snappedLocalPosition(e); - - if (!gridFromPointsStart.HasValue) - gridToolbox.StartPosition.Value = pos; - else - gridToolbox.SetGridFromPoints(gridFromPointsStart.Value, pos); - - return true; - } - - private Vector2 snappedLocalPosition(UIEvent e) => ToLocalSpace(Composer.FindSnappedPositionAndTime(e.ScreenSpaceMousePosition, ~SnapType.GlobalGrids).ScreenSpacePosition); } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 02e98d75a7..e035317194 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -81,13 +81,12 @@ namespace osu.Game.Rulesets.Osu.Edit // Give a bit of breathing room around the playfield content. PlayfieldContentContainer.Padding = new MarginPadding(10); - LayerBelowRuleset.AddRange(new Drawable[] - { + LayerBelowRuleset.Add( distanceSnapGridContainer = new Container { RelativeSizeAxes = Axes.Both } - }); + ); selectedHitObjects = EditorBeatmap.SelectedHitObjects.GetBoundCopy(); selectedHitObjects.CollectionChanged += (_, _) => updateDistanceSnapGrid(); @@ -100,6 +99,15 @@ namespace osu.Game.Rulesets.Osu.Edit updateDistanceSnapGrid(); OsuGridToolboxGroup.GridType.BindValueChanged(updatePositionSnapGrid, true); + OsuGridToolboxGroup.GridFromPointsClicked += () => gridFromPointsTool.BeginPlacement(); + + LayerAboveRuleset.Add( + // Place it above the playfield and blueprints, so it takes priority when handling input. + gridFromPointsTool = new GridFromPointsTool + { + RelativeSizeAxes = Axes.Both, + } + ); RightToolbox.AddRange(new EditorToolboxGroup[] { @@ -110,6 +118,8 @@ namespace osu.Game.Rulesets.Osu.Edit ); } + private GridFromPointsTool gridFromPointsTool; + private void updatePositionSnapGrid(ValueChangedEvent obj) { if (positionSnapGrid != null) @@ -278,7 +288,7 @@ namespace osu.Game.Rulesets.Osu.Edit foreach (var b in blueprints) { - if (b.IsSelected) + if (b.IsSelected && !gridFromPointsTool.IsPlacing) continue; var snapPositions = b.ScreenSpaceSnapPoints; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 50e6393895..4f8329cc82 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -76,6 +76,8 @@ namespace osu.Game.Rulesets.Edit protected readonly Container LayerBelowRuleset = new Container { RelativeSizeAxes = Axes.Both }; + protected readonly Container LayerAboveRuleset = new Container { RelativeSizeAxes = Axes.Both }; + protected InputManager InputManager { get; private set; } private EditorRadioButtonCollection toolboxCollection; @@ -137,7 +139,8 @@ namespace osu.Game.Rulesets.Edit drawableRulesetWrapper, // layers above playfield drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer() - .WithChild(BlueprintContainer = CreateBlueprintContainer()) + .WithChild(BlueprintContainer = CreateBlueprintContainer()), + drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer().WithChild(LayerAboveRuleset), } }, new Container From b54c9a36fea93e5bcf2ec3b05ac931eba5090c96 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 19:08:27 +0100 Subject: [PATCH 061/924] move GridFromPointsClicked handler creation code --- osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs | 7 +++++++ osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 5 ++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs index 722d9d1303..9bbeeaafc3 100644 --- a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs +++ b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs @@ -22,6 +22,13 @@ namespace osu.Game.Rulesets.Osu.Edit private Vector2? startPosition; + protected override void LoadComplete() + { + base.LoadComplete(); + + gridToolboxGroup.GridFromPointsClicked += BeginPlacement; + } + public void BeginPlacement() { IsPlacing = true; diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index e035317194..cd5c4f4ba9 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -63,6 +63,8 @@ namespace osu.Game.Rulesets.Osu.Edit private Bindable placementObject; + private GridFromPointsTool gridFromPointsTool; + [Cached(typeof(IDistanceSnapProvider))] protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider(); @@ -99,7 +101,6 @@ namespace osu.Game.Rulesets.Osu.Edit updateDistanceSnapGrid(); OsuGridToolboxGroup.GridType.BindValueChanged(updatePositionSnapGrid, true); - OsuGridToolboxGroup.GridFromPointsClicked += () => gridFromPointsTool.BeginPlacement(); LayerAboveRuleset.Add( // Place it above the playfield and blueprints, so it takes priority when handling input. @@ -118,8 +119,6 @@ namespace osu.Game.Rulesets.Osu.Edit ); } - private GridFromPointsTool gridFromPointsTool; - private void updatePositionSnapGrid(ValueChangedEvent obj) { if (positionSnapGrid != null) From f8979cff3aaf2c184f08d66fd6cc6f4aeb397f41 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 1 Jan 2024 16:14:20 +0100 Subject: [PATCH 062/924] fix distance --- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index c318bac64d..b73eaecc66 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Osu.Edit // Divide the distance so that there is a good density of grid lines. float dist = Vector2.Distance(point1, point2); - while (dist > 32) + while (dist >= max_automatic_spacing) dist /= 2; Spacing.Value = dist; } From 236f029dad22722ebf73c02d40dc19b312b16642 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 1 Feb 2024 16:56:57 +0100 Subject: [PATCH 063/924] Remove Masking from PositionSnapGrid This caused issues in rendering the outline of the grid because the outline was getting masked at some resolutions. --- .../Components/LinedPositionSnapGrid.cs | 128 +++++++++++++++--- .../Compose/Components/PositionSnapGrid.cs | 2 - 2 files changed, 106 insertions(+), 24 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs index ebdd76a4e2..8a7f6b5344 100644 --- a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs @@ -15,18 +15,29 @@ namespace osu.Game.Screens.Edit.Compose.Components { protected void GenerateGridLines(Vector2 step, Vector2 drawSize) { + if (Precision.AlmostEquals(step, Vector2.Zero)) + return; + int index = 0; - var currentPosition = StartPosition.Value; // Make lines the same width independent of display resolution. float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; - float lineLength = drawSize.Length * 2; + float rotation = MathHelper.RadiansToDegrees(MathF.Atan2(step.Y, step.X)); List generatedLines = new List(); - while (lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize) || - isMovingTowardsBox(currentPosition, step, drawSize)) + while (true) { + Vector2 currentPosition = StartPosition.Value + index++ * step; + + if (!lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize, out var p1, out var p2)) + { + if (!isMovingTowardsBox(currentPosition, step, drawSize)) + break; + + continue; + } + var gridLine = new Box { Colour = Colour4.White, @@ -34,15 +45,12 @@ namespace osu.Game.Screens.Edit.Compose.Components Origin = Anchor.Centre, RelativeSizeAxes = Axes.None, Width = lineWidth, - Height = lineLength, - Position = currentPosition, - Rotation = MathHelper.RadiansToDegrees(MathF.Atan2(step.Y, step.X)), + Height = Vector2.Distance(p1, p2), + Position = (p1 + p2) / 2, + Rotation = rotation, }; generatedLines.Add(gridLine); - - index += 1; - currentPosition = StartPosition.Value + index * step; } if (generatedLines.Count == 0) @@ -59,23 +67,99 @@ namespace osu.Game.Screens.Edit.Compose.Components (currentPosition + step - box).LengthSquared < (currentPosition - box).LengthSquared; } - private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box) + /// + /// Determines if the line starting at and going in the direction of + /// definitely intersects the box on (0, 0) with the given width and height and returns the intersection points if it does. + /// + /// The start point of the line. + /// The direction of the line. + /// The width and height of the box. + /// The first intersection point. + /// The second intersection point. + /// Whether the line definitely intersects the box. + private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box, out Vector2 p1, out Vector2 p2) { - var p2 = lineStart + lineDir; + p1 = Vector2.Zero; + p2 = Vector2.Zero; - double d1 = det(Vector2.Zero); - double d2 = det(new Vector2(box.X, 0)); - double d3 = det(new Vector2(0, box.Y)); - double d4 = det(box); + if (Precision.AlmostEquals(lineDir.X, 0)) + { + // If the line is vertical, we only need to check if the X coordinate of the line is within the box. + if (!Precision.DefinitelyBigger(lineStart.X, 0) || !Precision.DefinitelyBigger(box.X, lineStart.X)) + return false; - return definitelyDifferentSign(d1, d2) || definitelyDifferentSign(d3, d4) || - definitelyDifferentSign(d1, d3) || definitelyDifferentSign(d2, d4); + p1 = new Vector2(lineStart.X, 0); + p2 = new Vector2(lineStart.X, box.Y); + return true; + } - double det(Vector2 p) => (p.X - lineStart.X) * (p2.Y - lineStart.Y) - (p.Y - lineStart.Y) * (p2.X - lineStart.X); + if (Precision.AlmostEquals(lineDir.Y, 0)) + { + // If the line is horizontal, we only need to check if the Y coordinate of the line is within the box. + if (!Precision.DefinitelyBigger(lineStart.Y, 0) || !Precision.DefinitelyBigger(box.Y, lineStart.Y)) + return false; - bool definitelyDifferentSign(double a, double b) => !Precision.AlmostEquals(a, 0) && - !Precision.AlmostEquals(b, 0) && - Math.Sign(a) != Math.Sign(b); + p1 = new Vector2(0, lineStart.Y); + p2 = new Vector2(box.X, lineStart.Y); + return true; + } + + float m = lineDir.Y / lineDir.X; + float mInv = lineDir.X / lineDir.Y; // Use this to improve numerical stability if X is close to zero. + float b = lineStart.Y - m * lineStart.X; + + // Calculate intersection points with the sides of the box. + var p = new List(4); + + if (0 <= b && b <= box.Y) + p.Add(new Vector2(0, b)); + if (0 <= (box.Y - b) * mInv && (box.Y - b) * mInv <= box.X) + p.Add(new Vector2((box.Y - b) * mInv, box.Y)); + if (0 <= m * box.X + b && m * box.X + b <= box.Y) + p.Add(new Vector2(box.X, m * box.X + b)); + if (0 <= -b * mInv && -b * mInv <= box.X) + p.Add(new Vector2(-b * mInv, 0)); + + switch (p.Count) + { + case 4: + // If there are 4 intersection points, the line is a diagonal of the box. + if (m > 0) + { + p1 = Vector2.Zero; + p2 = box; + } + else + { + p1 = new Vector2(0, box.Y); + p2 = new Vector2(box.X, 0); + } + + break; + + case 3: + // If there are 3 intersection points, the line goes through a corner of the box. + if (p[0] == p[1]) + { + p1 = p[0]; + p2 = p[2]; + } + else + { + p1 = p[0]; + p2 = p[1]; + } + + break; + + case 2: + p1 = p[0]; + p2 = p[1]; + + break; + } + + return !Precision.AlmostEquals(p1, p2); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs index 36687ef73a..e576ac1e49 100644 --- a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs @@ -21,8 +21,6 @@ namespace osu.Game.Screens.Edit.Compose.Components protected PositionSnapGrid() { - Masking = true; - StartPosition.BindValueChanged(_ => GridCache.Invalidate()); AddLayout(GridCache); From 2918ecf46c3516039a28f58d2dd71042d505f574 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 1 Feb 2024 16:56:57 +0100 Subject: [PATCH 064/924] Remove Masking from PositionSnapGrid This caused issues in rendering the outline of the grid because the outline was getting masked at some resolutions. --- .../Components/LinedPositionSnapGrid.cs | 128 +++++++++++++++--- .../Compose/Components/PositionSnapGrid.cs | 2 - 2 files changed, 106 insertions(+), 24 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs index ebdd76a4e2..8a7f6b5344 100644 --- a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs @@ -15,18 +15,29 @@ namespace osu.Game.Screens.Edit.Compose.Components { protected void GenerateGridLines(Vector2 step, Vector2 drawSize) { + if (Precision.AlmostEquals(step, Vector2.Zero)) + return; + int index = 0; - var currentPosition = StartPosition.Value; // Make lines the same width independent of display resolution. float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; - float lineLength = drawSize.Length * 2; + float rotation = MathHelper.RadiansToDegrees(MathF.Atan2(step.Y, step.X)); List generatedLines = new List(); - while (lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize) || - isMovingTowardsBox(currentPosition, step, drawSize)) + while (true) { + Vector2 currentPosition = StartPosition.Value + index++ * step; + + if (!lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize, out var p1, out var p2)) + { + if (!isMovingTowardsBox(currentPosition, step, drawSize)) + break; + + continue; + } + var gridLine = new Box { Colour = Colour4.White, @@ -34,15 +45,12 @@ namespace osu.Game.Screens.Edit.Compose.Components Origin = Anchor.Centre, RelativeSizeAxes = Axes.None, Width = lineWidth, - Height = lineLength, - Position = currentPosition, - Rotation = MathHelper.RadiansToDegrees(MathF.Atan2(step.Y, step.X)), + Height = Vector2.Distance(p1, p2), + Position = (p1 + p2) / 2, + Rotation = rotation, }; generatedLines.Add(gridLine); - - index += 1; - currentPosition = StartPosition.Value + index * step; } if (generatedLines.Count == 0) @@ -59,23 +67,99 @@ namespace osu.Game.Screens.Edit.Compose.Components (currentPosition + step - box).LengthSquared < (currentPosition - box).LengthSquared; } - private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box) + /// + /// Determines if the line starting at and going in the direction of + /// definitely intersects the box on (0, 0) with the given width and height and returns the intersection points if it does. + /// + /// The start point of the line. + /// The direction of the line. + /// The width and height of the box. + /// The first intersection point. + /// The second intersection point. + /// Whether the line definitely intersects the box. + private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box, out Vector2 p1, out Vector2 p2) { - var p2 = lineStart + lineDir; + p1 = Vector2.Zero; + p2 = Vector2.Zero; - double d1 = det(Vector2.Zero); - double d2 = det(new Vector2(box.X, 0)); - double d3 = det(new Vector2(0, box.Y)); - double d4 = det(box); + if (Precision.AlmostEquals(lineDir.X, 0)) + { + // If the line is vertical, we only need to check if the X coordinate of the line is within the box. + if (!Precision.DefinitelyBigger(lineStart.X, 0) || !Precision.DefinitelyBigger(box.X, lineStart.X)) + return false; - return definitelyDifferentSign(d1, d2) || definitelyDifferentSign(d3, d4) || - definitelyDifferentSign(d1, d3) || definitelyDifferentSign(d2, d4); + p1 = new Vector2(lineStart.X, 0); + p2 = new Vector2(lineStart.X, box.Y); + return true; + } - double det(Vector2 p) => (p.X - lineStart.X) * (p2.Y - lineStart.Y) - (p.Y - lineStart.Y) * (p2.X - lineStart.X); + if (Precision.AlmostEquals(lineDir.Y, 0)) + { + // If the line is horizontal, we only need to check if the Y coordinate of the line is within the box. + if (!Precision.DefinitelyBigger(lineStart.Y, 0) || !Precision.DefinitelyBigger(box.Y, lineStart.Y)) + return false; - bool definitelyDifferentSign(double a, double b) => !Precision.AlmostEquals(a, 0) && - !Precision.AlmostEquals(b, 0) && - Math.Sign(a) != Math.Sign(b); + p1 = new Vector2(0, lineStart.Y); + p2 = new Vector2(box.X, lineStart.Y); + return true; + } + + float m = lineDir.Y / lineDir.X; + float mInv = lineDir.X / lineDir.Y; // Use this to improve numerical stability if X is close to zero. + float b = lineStart.Y - m * lineStart.X; + + // Calculate intersection points with the sides of the box. + var p = new List(4); + + if (0 <= b && b <= box.Y) + p.Add(new Vector2(0, b)); + if (0 <= (box.Y - b) * mInv && (box.Y - b) * mInv <= box.X) + p.Add(new Vector2((box.Y - b) * mInv, box.Y)); + if (0 <= m * box.X + b && m * box.X + b <= box.Y) + p.Add(new Vector2(box.X, m * box.X + b)); + if (0 <= -b * mInv && -b * mInv <= box.X) + p.Add(new Vector2(-b * mInv, 0)); + + switch (p.Count) + { + case 4: + // If there are 4 intersection points, the line is a diagonal of the box. + if (m > 0) + { + p1 = Vector2.Zero; + p2 = box; + } + else + { + p1 = new Vector2(0, box.Y); + p2 = new Vector2(box.X, 0); + } + + break; + + case 3: + // If there are 3 intersection points, the line goes through a corner of the box. + if (p[0] == p[1]) + { + p1 = p[0]; + p2 = p[2]; + } + else + { + p1 = p[0]; + p2 = p[1]; + } + + break; + + case 2: + p1 = p[0]; + p2 = p[1]; + + break; + } + + return !Precision.AlmostEquals(p1, p2); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs index 36687ef73a..e576ac1e49 100644 --- a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs @@ -21,8 +21,6 @@ namespace osu.Game.Screens.Edit.Compose.Components protected PositionSnapGrid() { - Masking = true; - StartPosition.BindValueChanged(_ => GridCache.Invalidate()); AddLayout(GridCache); From b4b5cdfcf2d84527ec4d5cb9e504152be59b7e19 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 1 Feb 2024 17:07:03 +0100 Subject: [PATCH 065/924] Fix masking in circular snap grid --- .../Edit/Compose/Components/CircularPositionSnapGrid.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs index 403a270359..791cb33439 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs @@ -82,7 +82,12 @@ namespace osu.Game.Screens.Edit.Compose.Components generatedCircles.First().Alpha = 0.8f; - AddRangeInternal(generatedCircles); + AddInternal(new Container + { + Masking = true, + RelativeSizeAxes = Axes.Both, + Children = generatedCircles, + }); } public override Vector2 GetSnappedPosition(Vector2 original) From 93dbd7507fd61086a8c9d3422e6a02560254f4d4 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 1 Feb 2024 17:07:03 +0100 Subject: [PATCH 066/924] Fix masking in circular snap grid --- .../Edit/Compose/Components/CircularPositionSnapGrid.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs index 403a270359..791cb33439 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs @@ -82,7 +82,12 @@ namespace osu.Game.Screens.Edit.Compose.Components generatedCircles.First().Alpha = 0.8f; - AddRangeInternal(generatedCircles); + AddInternal(new Container + { + Masking = true, + RelativeSizeAxes = Axes.Both, + Children = generatedCircles, + }); } public override Vector2 GetSnappedPosition(Vector2 original) From 8ccb14f19f1363c42eaf80718ed50c1a68338185 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Tue, 6 Feb 2024 13:08:17 +0000 Subject: [PATCH 067/924] include slider count in accuracy pp if slider head accuracy is in use --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index b31f4ff519..7f4c5f6c46 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -192,6 +192,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty // This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window. double betterAccuracyPercentage; int amountHitObjectsWithAccuracy = attributes.HitCircleCount; + if (!score.Mods.Any(h => h is OsuModClassic cl && cl.NoSliderHeadAccuracy.Value)) + amountHitObjectsWithAccuracy += attributes.SliderCount; if (amountHitObjectsWithAccuracy > 0) betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countOk * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6); From 7d34542c12a8e1db707d101fc7988e32e9da74d8 Mon Sep 17 00:00:00 2001 From: TextAdventurer12 Date: Thu, 22 Feb 2024 15:14:56 +1300 Subject: [PATCH 068/924] use difficulty instead of topstrain --- osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 918e702b45..13d89cf2b8 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -37,6 +37,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills protected virtual double DifficultyMultiplier => DEFAULT_DIFFICULTY_MULTIPLIER; protected List objectStrains = new List(); + protected double difficulty; protected OsuStrainSkill(Mod[] mods) : base(mods) @@ -45,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public override double DifficultyValue() { - double difficulty = 0; + difficulty = 0; double weight = 1; // Sections with 0 strain are excluded to avoid worst-case time complexity of the following sort (e.g. /b/2351871). @@ -78,9 +79,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// public double CountDifficultStrains() { - double topStrain = objectStrains.Max(); + double consistentTopStrain = difficulty / 10; // What would the top strain be if all strain values were identical - return objectStrains.Sum(s => Math.Pow(s / topStrain, 4)); + return objectStrains.Sum(s => Math.Pow(1, s / consistentTopStrain, 5)); } } } From 0db910deb90b792f41203ee9410ef6dc05231308 Mon Sep 17 00:00:00 2001 From: TextAdventurer12 Date: Thu, 22 Feb 2024 15:20:32 +1300 Subject: [PATCH 069/924] cap each note at adding 1 difficult strain count --- osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 13d89cf2b8..84bf8e3bf6 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills { double consistentTopStrain = difficulty / 10; // What would the top strain be if all strain values were identical - return objectStrains.Sum(s => Math.Pow(1, s / consistentTopStrain, 5)); + return objectStrains.Sum(s => Math.Pow(Math.Min(1, s / consistentTopStrain), 5)); } } } From caba0510db86b66f9e238c640a7a09e272d67fdb Mon Sep 17 00:00:00 2001 From: nathen Date: Sat, 9 Mar 2024 23:10:53 -0500 Subject: [PATCH 070/924] Compute the upper bound on deviation with a 99% confidence interval --- .../Difficulty/TaikoPerformanceCalculator.cs | 81 +++++++++---------- 1 file changed, 40 insertions(+), 41 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 723a6612bc..322e3874ba 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty countOk = score.Statistics.GetValueOrDefault(HitResult.Ok); countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); - estimatedUr = computeEstimatedUr(score, taikoAttributes); + estimatedUr = computeDeviationUpperBound(taikoAttributes) * 10; // The effectiveMissCount is calculated by gaining a ratio for totalSuccessfulHits and increasing the miss penalty for shorter object counts lower than 1000. if (totalSuccessfulHits > 0) @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (attributes.GreatHitWindow <= 0 || estimatedUr == null) return 0; - double accuracyValue = Math.Pow(65 / estimatedUr.Value, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 100.0; + double accuracyValue = Math.Pow(70 / estimatedUr.Value, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 100.0; double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3)); @@ -115,10 +115,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty } /// - /// Calculates the tap deviation for a player using the OD, object count, and scores of 300s, 100s, and misses, with an assumed mean hit error of 0. - /// Consistency is ensured as identical SS scores on the same map and settings yield the same deviation. + /// Computes an upper bound on the player's tap deviation based on the OD, number of circles and sliders, + /// and the hit judgements, assuming the player's mean hit error is 0. The estimation is consistent in that + /// two SS scores on the same map with the same settings will always return the same deviation. /// - private double? computeEstimatedUr(ScoreInfo score, TaikoDifficultyAttributes attributes) + private double? computeDeviationUpperBound(TaikoDifficultyAttributes attributes) { if (totalSuccessfulHits == 0 || attributes.GreatHitWindow <= 0) return null; @@ -126,53 +127,51 @@ namespace osu.Game.Rulesets.Taiko.Difficulty double h300 = attributes.GreatHitWindow; double h100 = attributes.OkHitWindow; - // Determines the probability of a deviation leading to the score's hit evaluations. The curve's apex represents the most probable deviation. - double likelihoodGradient(double d) + const double z = 2.32634787404; // 99% critical value for the normal distribution (one-tailed). + + // The upper bound on deviation, calculated with the ratio of 300s to 100s, and the great hit window. + double? calcDeviationGreatWindow() { - if (d <= 0) - return 0; + if (countGreat == 0) return null; - double p300 = logDiff(0, logPcHit(h300, d)); - double p100 = logDiff(logPcHit(h300, d), logPcHit(h100, d)); - double p0 = logPcHit(h100, d); + double n = totalSuccessfulHits; - double gradient = Math.Exp( - (countGreat * p300 - + (countOk + 0.5) * p100 - + countMiss * p0) / totalHits - ); + // Proportion of greats hit, ignoring misses. + double p = countGreat / n; - return -gradient; + // We can be 99% confident that p is at least this value. + double pLowerBound = (n * p + z * z / 2) / (n + z * z) - z / (n + z * z) * Math.Sqrt(n * p * (1 - p) + z * z / 4); + + // We can be 99% confident that the deviation is not higher than: + return h300 / (Math.Sqrt(2) * SpecialFunctions.ErfInv(pLowerBound)); } - double deviation = FindMinimum.OfScalarFunction(likelihoodGradient, 30); + // The upper bound on deviation, calculated with the ratio of 300s + 100s to misses, and the good hit window. + // This will return a lower value than the first method when the number of 100s is high, but the miss count is low. + double? calcDeviationGoodWindow() + { + if (totalSuccessfulHits == 0) return null; - return deviation * 10; + double n = totalHits; + + // Proportion of greats + goods hit. + double p = totalSuccessfulHits / n; + + // We can be 99% confident that p is at least this value. + double pLowerBound = (n * p + z * z / 2) / (n + z * z) - z / (n + z * z) * Math.Sqrt(n * p * (1 - p) + z * z / 4); + + // We can be 99% confident that the deviation is not higher than: + return h100 / (Math.Sqrt(2) * SpecialFunctions.ErfInv(pLowerBound)); + } + + if (calcDeviationGreatWindow() is null) + return calcDeviationGoodWindow(); + + return Math.Min(calcDeviationGreatWindow()!.Value, calcDeviationGoodWindow()!.Value); } private int totalHits => countGreat + countOk + countMeh + countMiss; private int totalSuccessfulHits => countGreat + countOk + countMeh; - - private double logPcHit(double x, double deviation) => logErfcApprox(x / (deviation * Math.Sqrt(2))); - - // Utilises a numerical approximation to extend the computation range of ln(erfc(x)). - private double logErfcApprox(double x) => x <= 5 - ? Math.Log(SpecialFunctions.Erfc(x)) - : -Math.Pow(x, 2) - Math.Log(x * Math.Sqrt(Math.PI)); // https://www.desmos.com/calculator/kdbxwxgf01 - - // Subtracts the base value of two logs, circumventing log rules that typically complicate subtraction of non-logarithmic values. - private double logDiff(double firstLog, double secondLog) - { - double maxVal = Math.Max(firstLog, secondLog); - - // To avoid a NaN result, a check is performed to prevent subtraction of two negative infinity values. - if (double.IsNegativeInfinity(maxVal)) - { - return maxVal; - } - - return firstLog + SpecialFunctions.Log1p(-Math.Exp(-(firstLog - secondLog))); - } } } From 6ddb2b7f8b1a40f3fc86892b00c67486884ac025 Mon Sep 17 00:00:00 2001 From: nathen Date: Sun, 10 Mar 2024 00:19:04 -0500 Subject: [PATCH 071/924] Include misses in the great window deviation calc --- .../Difficulty/TaikoPerformanceCalculator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 322e3874ba..8998b5cfa3 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -129,12 +129,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty const double z = 2.32634787404; // 99% critical value for the normal distribution (one-tailed). - // The upper bound on deviation, calculated with the ratio of 300s to 100s, and the great hit window. + // The upper bound on deviation, calculated with the ratio of 300s to objects, and the great hit window. double? calcDeviationGreatWindow() { if (countGreat == 0) return null; - double n = totalSuccessfulHits; + double n = totalHits; // Proportion of greats hit, ignoring misses. double p = countGreat / n; @@ -146,7 +146,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty return h300 / (Math.Sqrt(2) * SpecialFunctions.ErfInv(pLowerBound)); } - // The upper bound on deviation, calculated with the ratio of 300s + 100s to misses, and the good hit window. + // The upper bound on deviation, calculated with the ratio of 300s + 100s to objects, and the good hit window. // This will return a lower value than the first method when the number of 100s is high, but the miss count is low. double? calcDeviationGoodWindow() { From 537059504aa12c4dbc4a790c8dc24603ccdd94f6 Mon Sep 17 00:00:00 2001 From: nathen Date: Sun, 10 Mar 2024 00:20:06 -0500 Subject: [PATCH 072/924] Fix comment --- .../Difficulty/TaikoPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 8998b5cfa3..c532d24f4d 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty double n = totalHits; - // Proportion of greats hit, ignoring misses. + // Proportion of greats hit. double p = countGreat / n; // We can be 99% confident that p is at least this value. From a9b3416a3fe2e77f66c249c85636ca476ac3027c Mon Sep 17 00:00:00 2001 From: nathen Date: Sun, 10 Mar 2024 00:37:28 -0500 Subject: [PATCH 073/924] Remove MathNet.Numerics dependency --- .../Difficulty/TaikoPerformanceCalculator.cs | 2 +- .../osu.Game.Rulesets.Taiko.csproj | 4 - osu.Game/Utils/SpecialFunctions.cs | 694 ++++++++++++++++++ 3 files changed, 695 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Utils/SpecialFunctions.cs diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index c532d24f4d..ca11397801 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Scoring; -using MathNet.Numerics; +using osu.Game.Utils; namespace osu.Game.Rulesets.Taiko.Difficulty { diff --git a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj index 6ad5425d5b..cacba55c2a 100644 --- a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj +++ b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj @@ -15,8 +15,4 @@ - - - - diff --git a/osu.Game/Utils/SpecialFunctions.cs b/osu.Game/Utils/SpecialFunctions.cs new file mode 100644 index 0000000000..0b0f0598bb --- /dev/null +++ b/osu.Game/Utils/SpecialFunctions.cs @@ -0,0 +1,694 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +// All code is referenced from the following: +// https://github.com/mathnet/mathnet-numerics/blob/master/src/Numerics/SpecialFunctions/Erf.cs +// https://github.com/mathnet/mathnet-numerics/blob/master/src/Numerics/Optimization/NelderMeadSimplex.cs + +/* + Copyright (c) 2002-2022 Math.NET +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +using System; + +namespace osu.Game.Utils +{ + public class SpecialFunctions + { + private const double sqrt2_pi = 2.5066282746310005024157652848110452530069867406099d; + + /// + /// ************************************** + /// COEFFICIENTS FOR METHOD ErfImp * + /// ************************************** + /// + /// Polynomial coefficients for a numerator of ErfImp + /// calculation for Erf(x) in the interval [1e-10, 0.5]. + /// + private static readonly double[] erf_imp_an = { 0.00337916709551257388990745, -0.00073695653048167948530905, -0.374732337392919607868241, 0.0817442448733587196071743, -0.0421089319936548595203468, 0.0070165709512095756344528, -0.00495091255982435110337458, 0.000871646599037922480317225 }; + + /// Polynomial coefficients for a denominator of ErfImp + /// calculation for Erf(x) in the interval [1e-10, 0.5]. + /// + private static readonly double[] erf_imp_ad = { 1, -0.218088218087924645390535, 0.412542972725442099083918, -0.0841891147873106755410271, 0.0655338856400241519690695, -0.0120019604454941768171266, 0.00408165558926174048329689, -0.000615900721557769691924509 }; + + /// Polynomial coefficients for a numerator in ErfImp + /// calculation for Erfc(x) in the interval [0.5, 0.75]. + /// + private static readonly double[] erf_imp_bn = { -0.0361790390718262471360258, 0.292251883444882683221149, 0.281447041797604512774415, 0.125610208862766947294894, 0.0274135028268930549240776, 0.00250839672168065762786937 }; + + /// Polynomial coefficients for a denominator in ErfImp + /// calculation for Erfc(x) in the interval [0.5, 0.75]. + /// + private static readonly double[] erf_imp_bd = { 1, 1.8545005897903486499845, 1.43575803037831418074962, 0.582827658753036572454135, 0.124810476932949746447682, 0.0113724176546353285778481 }; + + /// Polynomial coefficients for a numerator in ErfImp + /// calculation for Erfc(x) in the interval [0.75, 1.25]. + /// + private static readonly double[] erf_imp_cn = { -0.0397876892611136856954425, 0.153165212467878293257683, 0.191260295600936245503129, 0.10276327061989304213645, 0.029637090615738836726027, 0.0046093486780275489468812, 0.000307607820348680180548455 }; + + /// Polynomial coefficients for a denominator in ErfImp + /// calculation for Erfc(x) in the interval [0.75, 1.25]. + /// + private static readonly double[] erf_imp_cd = { 1, 1.95520072987627704987886, 1.64762317199384860109595, 0.768238607022126250082483, 0.209793185936509782784315, 0.0319569316899913392596356, 0.00213363160895785378615014 }; + + /// Polynomial coefficients for a numerator in ErfImp + /// calculation for Erfc(x) in the interval [1.25, 2.25]. + /// + private static readonly double[] erf_imp_dn = { -0.0300838560557949717328341, 0.0538578829844454508530552, 0.0726211541651914182692959, 0.0367628469888049348429018, 0.00964629015572527529605267, 0.00133453480075291076745275, 0.778087599782504251917881e-4 }; + + /// Polynomial coefficients for a denominator in ErfImp + /// calculation for Erfc(x) in the interval [1.25, 2.25]. + /// + private static readonly double[] erf_imp_dd = { 1, 1.75967098147167528287343, 1.32883571437961120556307, 0.552528596508757581287907, 0.133793056941332861912279, 0.0179509645176280768640766, 0.00104712440019937356634038, -0.106640381820357337177643e-7 }; + + /// Polynomial coefficients for a numerator in ErfImp + /// calculation for Erfc(x) in the interval [2.25, 3.5]. + /// + private static readonly double[] erf_imp_en = { -0.0117907570137227847827732, 0.014262132090538809896674, 0.0202234435902960820020765, 0.00930668299990432009042239, 0.00213357802422065994322516, 0.00025022987386460102395382, 0.120534912219588189822126e-4 }; + + /// Polynomial coefficients for a denominator in ErfImp + /// calculation for Erfc(x) in the interval [2.25, 3.5]. + /// + private static readonly double[] erf_imp_ed = { 1, 1.50376225203620482047419, 0.965397786204462896346934, 0.339265230476796681555511, 0.0689740649541569716897427, 0.00771060262491768307365526, 0.000371421101531069302990367 }; + + /// Polynomial coefficients for a numerator in ErfImp + /// calculation for Erfc(x) in the interval [3.5, 5.25]. + /// + private static readonly double[] erf_imp_fn = { -0.00546954795538729307482955, 0.00404190278731707110245394, 0.0054963369553161170521356, 0.00212616472603945399437862, 0.000394984014495083900689956, 0.365565477064442377259271e-4, 0.135485897109932323253786e-5 }; + + /// Polynomial coefficients for a denominator in ErfImp + /// calculation for Erfc(x) in the interval [3.5, 5.25]. + /// + private static readonly double[] erf_imp_fd = { 1, 1.21019697773630784832251, 0.620914668221143886601045, 0.173038430661142762569515, 0.0276550813773432047594539, 0.00240625974424309709745382, 0.891811817251336577241006e-4, -0.465528836283382684461025e-11 }; + + /// Polynomial coefficients for a numerator in ErfImp + /// calculation for Erfc(x) in the interval [5.25, 8]. + /// + private static readonly double[] erf_imp_gn = { -0.00270722535905778347999196, 0.0013187563425029400461378, 0.00119925933261002333923989, 0.00027849619811344664248235, 0.267822988218331849989363e-4, 0.923043672315028197865066e-6 }; + + /// Polynomial coefficients for a denominator in ErfImp + /// calculation for Erfc(x) in the interval [5.25, 8]. + /// + private static readonly double[] erf_imp_gd = { 1, 0.814632808543141591118279, 0.268901665856299542168425, 0.0449877216103041118694989, 0.00381759663320248459168994, 0.000131571897888596914350697, 0.404815359675764138445257e-11 }; + + /// Polynomial coefficients for a numerator in ErfImp + /// calculation for Erfc(x) in the interval [8, 11.5]. + /// + private static readonly double[] erf_imp_hn = { -0.00109946720691742196814323, 0.000406425442750422675169153, 0.000274499489416900707787024, 0.465293770646659383436343e-4, 0.320955425395767463401993e-5, 0.778286018145020892261936e-7 }; + + /// Polynomial coefficients for a denominator in ErfImp + /// calculation for Erfc(x) in the interval [8, 11.5]. + /// + private static readonly double[] erf_imp_hd = { 1, 0.588173710611846046373373, 0.139363331289409746077541, 0.0166329340417083678763028, 0.00100023921310234908642639, 0.24254837521587225125068e-4 }; + + /// Polynomial coefficients for a numerator in ErfImp + /// calculation for Erfc(x) in the interval [11.5, 17]. + /// + private static readonly double[] erf_imp_in = { -0.00056907993601094962855594, 0.000169498540373762264416984, 0.518472354581100890120501e-4, 0.382819312231928859704678e-5, 0.824989931281894431781794e-7 }; + + /// Polynomial coefficients for a denominator in ErfImp + /// calculation for Erfc(x) in the interval [11.5, 17]. + /// + private static readonly double[] erf_imp_id = { 1, 0.339637250051139347430323, 0.043472647870310663055044, 0.00248549335224637114641629, 0.535633305337152900549536e-4, -0.117490944405459578783846e-12 }; + + /// Polynomial coefficients for a numerator in ErfImp + /// calculation for Erfc(x) in the interval [17, 24]. + /// + private static readonly double[] erf_imp_jn = { -0.000241313599483991337479091, 0.574224975202501512365975e-4, 0.115998962927383778460557e-4, 0.581762134402593739370875e-6, 0.853971555085673614607418e-8 }; + + /// Polynomial coefficients for a denominator in ErfImp + /// calculation for Erfc(x) in the interval [17, 24]. + /// + private static readonly double[] erf_imp_jd = { 1, 0.233044138299687841018015, 0.0204186940546440312625597, 0.000797185647564398289151125, 0.117019281670172327758019e-4 }; + + /// Polynomial coefficients for a numerator in ErfImp + /// calculation for Erfc(x) in the interval [24, 38]. + /// + private static readonly double[] erf_imp_kn = { -0.000146674699277760365803642, 0.162666552112280519955647e-4, 0.269116248509165239294897e-5, 0.979584479468091935086972e-7, 0.101994647625723465722285e-8 }; + + /// Polynomial coefficients for a denominator in ErfImp + /// calculation for Erfc(x) in the interval [24, 38]. + /// + private static readonly double[] erf_imp_kd = { 1, 0.165907812944847226546036, 0.0103361716191505884359634, 0.000286593026373868366935721, 0.298401570840900340874568e-5 }; + + /// Polynomial coefficients for a numerator in ErfImp + /// calculation for Erfc(x) in the interval [38, 60]. + /// + private static readonly double[] erf_imp_ln = { -0.583905797629771786720406e-4, 0.412510325105496173512992e-5, 0.431790922420250949096906e-6, 0.993365155590013193345569e-8, 0.653480510020104699270084e-10 }; + + /// Polynomial coefficients for a denominator in ErfImp + /// calculation for Erfc(x) in the interval [38, 60]. + /// + private static readonly double[] erf_imp_ld = { 1, 0.105077086072039915406159, 0.00414278428675475620830226, 0.726338754644523769144108e-4, 0.477818471047398785369849e-6 }; + + /// Polynomial coefficients for a numerator in ErfImp + /// calculation for Erfc(x) in the interval [60, 85]. + /// + private static readonly double[] erf_imp_mn = { -0.196457797609229579459841e-4, 0.157243887666800692441195e-5, 0.543902511192700878690335e-7, 0.317472492369117710852685e-9 }; + + /// Polynomial coefficients for a denominator in ErfImp + /// calculation for Erfc(x) in the interval [60, 85]. + /// + private static readonly double[] erf_imp_md = { 1, 0.052803989240957632204885, 0.000926876069151753290378112, 0.541011723226630257077328e-5, 0.535093845803642394908747e-15 }; + + /// Polynomial coefficients for a numerator in ErfImp + /// calculation for Erfc(x) in the interval [85, 110]. + /// + private static readonly double[] erf_imp_nn = { -0.789224703978722689089794e-5, 0.622088451660986955124162e-6, 0.145728445676882396797184e-7, 0.603715505542715364529243e-10 }; + + /// Polynomial coefficients for a denominator in ErfImp + /// calculation for Erfc(x) in the interval [85, 110]. + /// + private static readonly double[] erf_imp_nd = { 1, 0.0375328846356293715248719, 0.000467919535974625308126054, 0.193847039275845656900547e-5 }; + + /// + /// ************************************** + /// COEFFICIENTS FOR METHOD ErfInvImp * + /// ************************************** + /// + /// Polynomial coefficients for a numerator of ErfInvImp + /// calculation for Erf^-1(z) in the interval [0, 0.5]. + /// + private static readonly double[] erv_inv_imp_an = { -0.000508781949658280665617, -0.00836874819741736770379, 0.0334806625409744615033, -0.0126926147662974029034, -0.0365637971411762664006, 0.0219878681111168899165, 0.00822687874676915743155, -0.00538772965071242932965 }; + + /// Polynomial coefficients for a denominator of ErfInvImp + /// calculation for Erf^-1(z) in the interval [0, 0.5]. + /// + private static readonly double[] erv_inv_imp_ad = { 1, -0.970005043303290640362, -1.56574558234175846809, 1.56221558398423026363, 0.662328840472002992063, -0.71228902341542847553, -0.0527396382340099713954, 0.0795283687341571680018, -0.00233393759374190016776, 0.000886216390456424707504 }; + + /// Polynomial coefficients for a numerator of ErfInvImp + /// calculation for Erf^-1(z) in the interval [0.5, 0.75]. + /// + private static readonly double[] erv_inv_imp_bn = { -0.202433508355938759655, 0.105264680699391713268, 8.37050328343119927838, 17.6447298408374015486, -18.8510648058714251895, -44.6382324441786960818, 17.445385985570866523, 21.1294655448340526258, -3.67192254707729348546 }; + + /// Polynomial coefficients for a denominator of ErfInvImp + /// calculation for Erf^-1(z) in the interval [0.5, 0.75]. + /// + private static readonly double[] erv_inv_imp_bd = { 1, 6.24264124854247537712, 3.9713437953343869095, -28.6608180499800029974, -20.1432634680485188801, 48.5609213108739935468, 10.8268667355460159008, -22.6436933413139721736, 1.72114765761200282724 }; + + /// Polynomial coefficients for a numerator of ErfInvImp + /// calculation for Erf^-1(z) in the interval [0.75, 1] with x less than 3. + /// + private static readonly double[] erv_inv_imp_cn = { -0.131102781679951906451, -0.163794047193317060787, 0.117030156341995252019, 0.387079738972604337464, 0.337785538912035898924, 0.142869534408157156766, 0.0290157910005329060432, 0.00214558995388805277169, -0.679465575181126350155e-6, 0.285225331782217055858e-7, -0.681149956853776992068e-9 }; + + /// Polynomial coefficients for a denominator of ErfInvImp + /// calculation for Erf^-1(z) in the interval [0.75, 1] with x less than 3. + /// + private static readonly double[] erv_inv_imp_cd = { 1, 3.46625407242567245975, 5.38168345707006855425, 4.77846592945843778382, 2.59301921623620271374, 0.848854343457902036425, 0.152264338295331783612, 0.01105924229346489121 }; + + /// Polynomial coefficients for a numerator of ErfInvImp + /// calculation for Erf^-1(z) in the interval [0.75, 1] with x between 3 and 6. + /// + private static readonly double[] erv_inv_imp_dn = { -0.0350353787183177984712, -0.00222426529213447927281, 0.0185573306514231072324, 0.00950804701325919603619, 0.00187123492819559223345, 0.000157544617424960554631, 0.460469890584317994083e-5, -0.230404776911882601748e-9, 0.266339227425782031962e-11 }; + + /// Polynomial coefficients for a denominator of ErfInvImp + /// calculation for Erf^-1(z) in the interval [0.75, 1] with x between 3 and 6. + /// + private static readonly double[] erv_inv_imp_dd = { 1, 1.3653349817554063097, 0.762059164553623404043, 0.220091105764131249824, 0.0341589143670947727934, 0.00263861676657015992959, 0.764675292302794483503e-4 }; + + /// Polynomial coefficients for a numerator of ErfInvImp + /// calculation for Erf^-1(z) in the interval [0.75, 1] with x between 6 and 18. + /// + private static readonly double[] erv_inv_imp_en = { -0.0167431005076633737133, -0.00112951438745580278863, 0.00105628862152492910091, 0.000209386317487588078668, 0.149624783758342370182e-4, 0.449696789927706453732e-6, 0.462596163522878599135e-8, -0.281128735628831791805e-13, 0.99055709973310326855e-16 }; + + /// Polynomial coefficients for a denominator of ErfInvImp + /// calculation for Erf^-1(z) in the interval [0.75, 1] with x between 6 and 18. + /// + private static readonly double[] erv_inv_imp_ed = { 1, 0.591429344886417493481, 0.138151865749083321638, 0.0160746087093676504695, 0.000964011807005165528527, 0.275335474764726041141e-4, 0.282243172016108031869e-6 }; + + /// Polynomial coefficients for a numerator of ErfInvImp + /// calculation for Erf^-1(z) in the interval [0.75, 1] with x between 18 and 44. + /// + private static readonly double[] erv_inv_imp_fn = { -0.0024978212791898131227, -0.779190719229053954292e-5, 0.254723037413027451751e-4, 0.162397777342510920873e-5, 0.396341011304801168516e-7, 0.411632831190944208473e-9, 0.145596286718675035587e-11, -0.116765012397184275695e-17 }; + + /// Polynomial coefficients for a denominator of ErfInvImp + /// calculation for Erf^-1(z) in the interval [0.75, 1] with x between 18 and 44. + /// + private static readonly double[] erv_inv_imp_fd = { 1, 0.207123112214422517181, 0.0169410838120975906478, 0.000690538265622684595676, 0.145007359818232637924e-4, 0.144437756628144157666e-6, 0.509761276599778486139e-9 }; + + /// Polynomial coefficients for a numerator of ErfInvImp + /// calculation for Erf^-1(z) in the interval [0.75, 1] with x greater than 44. + /// + private static readonly double[] erv_inv_imp_gn = { -0.000539042911019078575891, -0.28398759004727721098e-6, 0.899465114892291446442e-6, 0.229345859265920864296e-7, 0.225561444863500149219e-9, 0.947846627503022684216e-12, 0.135880130108924861008e-14, -0.348890393399948882918e-21 }; + + /// Polynomial coefficients for a denominator of ErfInvImp + /// calculation for Erf^-1(z) in the interval [0.75, 1] with x greater than 44. + /// + private static readonly double[] erv_inv_imp_gd = { 1, 0.0845746234001899436914, 0.00282092984726264681981, 0.468292921940894236786e-4, 0.399968812193862100054e-6, 0.161809290887904476097e-8, 0.231558608310259605225e-11 }; + + /// Calculates the error function. + /// The value to evaluate. + /// the error function evaluated at given value. + /// + /// + /// returns 1 if x == double.PositiveInfinity. + /// returns -1 if x == double.NegativeInfinity. + /// + /// + public static double Erf(double x) + { + if (x == 0) + { + return 0; + } + + if (double.IsPositiveInfinity(x)) + { + return 1; + } + + if (double.IsNegativeInfinity(x)) + { + return -1; + } + + if (double.IsNaN(x)) + { + return double.NaN; + } + + return erfImp(x, false); + } + + /// Calculates the complementary error function. + /// The value to evaluate. + /// the complementary error function evaluated at given value. + /// + /// + /// returns 0 if x == double.PositiveInfinity. + /// returns 2 if x == double.NegativeInfinity. + /// + /// + public static double Erfc(double x) + { + if (x == 0) + { + return 1; + } + + if (double.IsPositiveInfinity(x)) + { + return 0; + } + + if (double.IsNegativeInfinity(x)) + { + return 2; + } + + if (double.IsNaN(x)) + { + return double.NaN; + } + + return erfImp(x, true); + } + + /// Calculates the inverse error function evaluated at z. + /// The inverse error function evaluated at given value. + /// + /// + /// returns double.PositiveInfinity if z >= 1.0. + /// returns double.NegativeInfinity if z <= -1.0. + /// + /// + /// Calculates the inverse error function evaluated at z. + /// value to evaluate. + /// the inverse error function evaluated at Z. + public static double ErfInv(double z) + { + if (z == 0.0) + { + return 0.0; + } + + if (z >= 1.0) + { + return double.PositiveInfinity; + } + + if (z <= -1.0) + { + return double.NegativeInfinity; + } + + double p, q, s; + + if (z < 0) + { + p = -z; + q = 1 - p; + s = -1; + } + else + { + p = z; + q = 1 - z; + s = 1; + } + + return erfInvImpl(p, q, s); + } + + /// + /// Implementation of the error function. + /// + /// Where to evaluate the error function. + /// Whether to compute 1 - the error function. + /// the error function. + private static double erfImp(double z, bool invert) + { + if (z < 0) + { + if (!invert) + { + return -erfImp(-z, false); + } + + if (z < -0.5) + { + return 2 - erfImp(-z, true); + } + + return 1 + erfImp(-z, false); + } + + double result; + + // Big bunch of selection statements now to pick which + // implementation to use, try to put most likely options + // first: + if (z < 0.5) + { + // We're going to calculate erf: + if (z < 1e-10) + { + result = (z * 1.125) + (z * 0.003379167095512573896158903121545171688); + } + else + { + // Worst case absolute error found: 6.688618532e-21 + result = (z * 1.125) + (z * evaluatePolynomial(z, erf_imp_an) / evaluatePolynomial(z, erf_imp_ad)); + } + } + else if (z < 110) + { + // We'll be calculating erfc: + invert = !invert; + double r, b; + + if (z < 0.75) + { + // Worst case absolute error found: 5.582813374e-21 + r = evaluatePolynomial(z - 0.5, erf_imp_bn) / evaluatePolynomial(z - 0.5, erf_imp_bd); + b = 0.3440242112F; + } + else if (z < 1.25) + { + // Worst case absolute error found: 4.01854729e-21 + r = evaluatePolynomial(z - 0.75, erf_imp_cn) / evaluatePolynomial(z - 0.75, erf_imp_cd); + b = 0.419990927F; + } + else if (z < 2.25) + { + // Worst case absolute error found: 2.866005373e-21 + r = evaluatePolynomial(z - 1.25, erf_imp_dn) / evaluatePolynomial(z - 1.25, erf_imp_dd); + b = 0.4898625016F; + } + else if (z < 3.5) + { + // Worst case absolute error found: 1.045355789e-21 + r = evaluatePolynomial(z - 2.25, erf_imp_en) / evaluatePolynomial(z - 2.25, erf_imp_ed); + b = 0.5317370892F; + } + else if (z < 5.25) + { + // Worst case absolute error found: 8.300028706e-22 + r = evaluatePolynomial(z - 3.5, erf_imp_fn) / evaluatePolynomial(z - 3.5, erf_imp_fd); + b = 0.5489973426F; + } + else if (z < 8) + { + // Worst case absolute error found: 1.700157534e-21 + r = evaluatePolynomial(z - 5.25, erf_imp_gn) / evaluatePolynomial(z - 5.25, erf_imp_gd); + b = 0.5571740866F; + } + else if (z < 11.5) + { + // Worst case absolute error found: 3.002278011e-22 + r = evaluatePolynomial(z - 8, erf_imp_hn) / evaluatePolynomial(z - 8, erf_imp_hd); + b = 0.5609807968F; + } + else if (z < 17) + { + // Worst case absolute error found: 6.741114695e-21 + r = evaluatePolynomial(z - 11.5, erf_imp_in) / evaluatePolynomial(z - 11.5, erf_imp_id); + b = 0.5626493692F; + } + else if (z < 24) + { + // Worst case absolute error found: 7.802346984e-22 + r = evaluatePolynomial(z - 17, erf_imp_jn) / evaluatePolynomial(z - 17, erf_imp_jd); + b = 0.5634598136F; + } + else if (z < 38) + { + // Worst case absolute error found: 2.414228989e-22 + r = evaluatePolynomial(z - 24, erf_imp_kn) / evaluatePolynomial(z - 24, erf_imp_kd); + b = 0.5638477802F; + } + else if (z < 60) + { + // Worst case absolute error found: 5.896543869e-24 + r = evaluatePolynomial(z - 38, erf_imp_ln) / evaluatePolynomial(z - 38, erf_imp_ld); + b = 0.5640528202F; + } + else if (z < 85) + { + // Worst case absolute error found: 3.080612264e-21 + r = evaluatePolynomial(z - 60, erf_imp_mn) / evaluatePolynomial(z - 60, erf_imp_md); + b = 0.5641309023F; + } + else + { + // Worst case absolute error found: 8.094633491e-22 + r = evaluatePolynomial(z - 85, erf_imp_nn) / evaluatePolynomial(z - 85, erf_imp_nd); + b = 0.5641584396F; + } + + double g = Math.Exp(-z * z) / z; + result = (g * b) + (g * r); + } + else + { + // Any value of z larger than 28 will underflow to zero: + result = 0; + invert = !invert; + } + + if (invert) + { + result = 1 - result; + } + + return result; + } + + /// Calculates the complementary inverse error function evaluated at z. + /// The complementary inverse error function evaluated at given value. + /// We have tested this implementation against the arbitrary precision mpmath library + /// and found cases where we can only guarantee 9 significant figures correct. + /// + /// returns double.PositiveInfinity if z <= 0.0. + /// returns double.NegativeInfinity if z >= 2.0. + /// + /// + /// calculates the complementary inverse error function evaluated at z. + /// value to evaluate. + /// the complementary inverse error function evaluated at Z. + public static double ErfcInv(double z) + { + if (z <= 0.0) + { + return double.PositiveInfinity; + } + + if (z >= 2.0) + { + return double.NegativeInfinity; + } + + double p, q, s; + + if (z > 1) + { + q = 2 - z; + p = 1 - q; + s = -1; + } + else + { + p = 1 - z; + q = z; + s = 1; + } + + return erfInvImpl(p, q, s); + } + + /// + /// The implementation of the inverse error function. + /// + /// First intermediate parameter. + /// Second intermediate parameter. + /// Third intermediate parameter. + /// the inverse error function. + private static double erfInvImpl(double p, double q, double s) + { + double result; + + if (p <= 0.5) + { + // Evaluate inverse erf using the rational approximation: + // + // x = p(p+10)(Y+R(p)) + // + // Where Y is a constant, and R(p) is optimized for a low + // absolute error compared to |Y|. + // + // double: Max error found: 2.001849e-18 + // long double: Max error found: 1.017064e-20 + // Maximum Deviation Found (actual error term at infinite precision) 8.030e-21 + const float y = 0.0891314744949340820313f; + double g = p * (p + 10); + double r = evaluatePolynomial(p, erv_inv_imp_an) / evaluatePolynomial(p, erv_inv_imp_ad); + result = (g * y) + (g * r); + } + else if (q >= 0.25) + { + // Rational approximation for 0.5 > q >= 0.25 + // + // x = sqrt(-2*log(q)) / (Y + R(q)) + // + // Where Y is a constant, and R(q) is optimized for a low + // absolute error compared to Y. + // + // double : Max error found: 7.403372e-17 + // long double : Max error found: 6.084616e-20 + // Maximum Deviation Found (error term) 4.811e-20 + const float y = 2.249481201171875f; + double g = Math.Sqrt(-2 * Math.Log(q)); + double xs = q - 0.25; + double r = evaluatePolynomial(xs, erv_inv_imp_bn) / evaluatePolynomial(xs, erv_inv_imp_bd); + result = g / (y + r); + } + else + { + // For q < 0.25 we have a series of rational approximations all + // of the general form: + // + // let: x = sqrt(-log(q)) + // + // Then the result is given by: + // + // x(Y+R(x-B)) + // + // where Y is a constant, B is the lowest value of x for which + // the approximation is valid, and R(x-B) is optimized for a low + // absolute error compared to Y. + // + // Note that almost all code will really go through the first + // or maybe second approximation. After than we're dealing with very + // small input values indeed: 80 and 128 bit long double's go all the + // way down to ~ 1e-5000 so the "tail" is rather long... + double x = Math.Sqrt(-Math.Log(q)); + + if (x < 3) + { + // Max error found: 1.089051e-20 + const float y = 0.807220458984375f; + double xs = x - 1.125; + double r = evaluatePolynomial(xs, erv_inv_imp_cn) / evaluatePolynomial(xs, erv_inv_imp_cd); + result = (y * x) + (r * x); + } + else if (x < 6) + { + // Max error found: 8.389174e-21 + const float y = 0.93995571136474609375f; + double xs = x - 3; + double r = evaluatePolynomial(xs, erv_inv_imp_dn) / evaluatePolynomial(xs, erv_inv_imp_dd); + result = (y * x) + (r * x); + } + else if (x < 18) + { + // Max error found: 1.481312e-19 + const float y = 0.98362827301025390625f; + double xs = x - 6; + double r = evaluatePolynomial(xs, erv_inv_imp_en) / evaluatePolynomial(xs, erv_inv_imp_ed); + result = (y * x) + (r * x); + } + else if (x < 44) + { + // Max error found: 5.697761e-20 + const float y = 0.99714565277099609375f; + double xs = x - 18; + double r = evaluatePolynomial(xs, erv_inv_imp_fn) / evaluatePolynomial(xs, erv_inv_imp_fd); + result = (y * x) + (r * x); + } + else + { + // Max error found: 1.279746e-20 + const float y = 0.99941349029541015625f; + double xs = x - 44; + double r = evaluatePolynomial(xs, erv_inv_imp_gn) / evaluatePolynomial(xs, erv_inv_imp_gd); + result = (y * x) + (r * x); + } + } + + return s * result; + } + + /// + /// Evaluate a polynomial at point x. + /// Coefficients are ordered ascending by power with power k at index k. + /// Example: coefficients [3,-1,2] represent y=2x^2-x+3. + /// + /// The location where to evaluate the polynomial at. + /// The coefficients of the polynomial, coefficient for power k at index k. + /// + /// is a null reference. + /// + private static double evaluatePolynomial(double z, params double[] coefficients) + { + // 2020-10-07 jbialogrodzki #730 Since this is public API we should probably + // handle null arguments? It doesn't seem to have been done consistently in this class though. + if (coefficients == null) + { + throw new ArgumentNullException(nameof(coefficients)); + } + + // 2020-10-07 jbialogrodzki #730 Zero polynomials need explicit handling. + // Without this check, we attempted to peek coefficients at negative indices! + int n = coefficients.Length; + + if (n == 0) + { + return 0; + } + + double sum = coefficients[n - 1]; + + for (int i = n - 2; i >= 0; --i) + { + sum *= z; + sum += coefficients[i]; + } + + return sum; + } + } +} From 941c0487a454fad1447812f2cd12fad26f1cd28c Mon Sep 17 00:00:00 2001 From: Fina Date: Thu, 21 Mar 2024 19:02:36 -0700 Subject: [PATCH 074/924] Make length bonus account for sliders, use proper misscount for classic --- .../Difficulty/OsuPerformanceCalculator.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index b31f4ff519..fa5bd8e094 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -23,6 +23,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty private int countMiss; private double effectiveMissCount; + private bool SLIDERS_ACC; public OsuPerformanceCalculator() : base(new OsuRuleset()) @@ -33,13 +34,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty { var osuAttributes = (OsuDifficultyAttributes)attributes; + SLIDERS_ACC = !score.Mods.Any(m => m is OsuModClassic); + accuracy = score.Accuracy; scoreMaxCombo = score.MaxCombo; countGreat = score.Statistics.GetValueOrDefault(HitResult.Great); countOk = score.Statistics.GetValueOrDefault(HitResult.Ok); countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); - effectiveMissCount = calculateEffectiveMissCount(osuAttributes); + effectiveMissCount = SLIDERS_ACC ? countMiss : calculateEffectiveMissCount(osuAttributes); double multiplier = PERFORMANCE_BASE_MULTIPLIER; @@ -191,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty // This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window. double betterAccuracyPercentage; - int amountHitObjectsWithAccuracy = attributes.HitCircleCount; + int amountHitObjectsWithAccuracy = SLIDERS_ACC ? attributes.HitCircleCount + attributes.SliderCount : attributes.HitCircleCount; if (amountHitObjectsWithAccuracy > 0) betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countOk * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6); From 4db6f288d3cad1d848d76e11a9f31941169b800d Mon Sep 17 00:00:00 2001 From: Fina Date: Thu, 21 Mar 2024 23:15:36 -0700 Subject: [PATCH 075/924] Use actual sliderends dropped instead of estimating Score data for non-CL scores includes sliderends dropped, meaning no need to estimate. CL scores are still estimated. --- .../Difficulty/OsuPerformanceCalculator.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index b31f4ff519..1e90df3081 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -21,6 +21,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty private int countOk; private int countMeh; private int countMiss; + private int countSliderEndsDropped; private double effectiveMissCount; @@ -39,6 +40,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty countOk = score.Statistics.GetValueOrDefault(HitResult.Ok); countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); + countSliderEndsDropped = score.Statistics.GetValueOrDefault(HitResult.SmallTickMiss); effectiveMissCount = calculateEffectiveMissCount(osuAttributes); double multiplier = PERFORMANCE_BASE_MULTIPLIER; @@ -123,7 +125,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty 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; + double sliderNerfFactor = 0; + if (!score.Mods.Any(h => h is OsuModClassic cl && cl.NoSliderHeadAccuracy.Value)) + sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - countSliderEndsDropped / estimateDifficultSliders, 3) + attributes.SliderFactor; + else + sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + attributes.SliderFactor; aimValue *= sliderNerfFactor; } From 3dafdc01bb006b36f1ecea460efa2dd8587f1333 Mon Sep 17 00:00:00 2001 From: Fina Date: Thu, 21 Mar 2024 23:17:10 -0700 Subject: [PATCH 076/924] Revert "Make length bonus account for sliders, use proper misscount for classic" This reverts commit 941c0487a454fad1447812f2cd12fad26f1cd28c. --- .../Difficulty/OsuPerformanceCalculator.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index fa5bd8e094..b31f4ff519 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -23,7 +23,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty private int countMiss; private double effectiveMissCount; - private bool SLIDERS_ACC; public OsuPerformanceCalculator() : base(new OsuRuleset()) @@ -34,15 +33,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty { var osuAttributes = (OsuDifficultyAttributes)attributes; - SLIDERS_ACC = !score.Mods.Any(m => m is OsuModClassic); - accuracy = score.Accuracy; scoreMaxCombo = score.MaxCombo; countGreat = score.Statistics.GetValueOrDefault(HitResult.Great); countOk = score.Statistics.GetValueOrDefault(HitResult.Ok); countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); - effectiveMissCount = SLIDERS_ACC ? countMiss : calculateEffectiveMissCount(osuAttributes); + effectiveMissCount = calculateEffectiveMissCount(osuAttributes); double multiplier = PERFORMANCE_BASE_MULTIPLIER; @@ -194,7 +191,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty // This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window. double betterAccuracyPercentage; - int amountHitObjectsWithAccuracy = SLIDERS_ACC ? attributes.HitCircleCount + attributes.SliderCount : attributes.HitCircleCount; + int amountHitObjectsWithAccuracy = attributes.HitCircleCount; if (amountHitObjectsWithAccuracy > 0) betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countOk * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6); From 840845527f68adab6fe0a2f3e28d0ad3af8d863d Mon Sep 17 00:00:00 2001 From: Fina Date: Thu, 21 Mar 2024 23:24:37 -0700 Subject: [PATCH 077/924] Use miss count for effective miss count No need to estimate misses for non-CL scores. --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index b31f4ff519..e000438e5f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -39,7 +39,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty countOk = score.Statistics.GetValueOrDefault(HitResult.Ok); countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); - effectiveMissCount = calculateEffectiveMissCount(osuAttributes); + if (!score.Mods.Any(h => h is OsuModClassic cl && cl.NoSliderHeadAccuracy.Value)) + effectiveMissCount = countMiss; + else + effectiveMissCount = calculateEffectiveMissCount(osuAttributes); double multiplier = PERFORMANCE_BASE_MULTIPLIER; From b0d20e68ae303076d4dc644f32af8e98cb62cf98 Mon Sep 17 00:00:00 2001 From: Fina Date: Thu, 21 Mar 2024 23:31:45 -0700 Subject: [PATCH 078/924] Update OsuPerformanceCalculator.cs --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 08a09f3929..8fbee5931d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); countSliderEndsDropped = score.Statistics.GetValueOrDefault(HitResult.SmallTickMiss); - + if (!score.Mods.Any(h => h is OsuModClassic cl && cl.NoSliderHeadAccuracy.Value)) effectiveMissCount = countMiss; else From 6fe478c8655a2da8979f04e3278b65ea24f58f02 Mon Sep 17 00:00:00 2001 From: Fina Date: Thu, 21 Mar 2024 23:49:54 -0700 Subject: [PATCH 079/924] Add slider ticks and reverse arrows to effective misscount Very much open to discussion on if these should be weighed differently --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 8fbee5931d..94548e4985 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -21,6 +21,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty private int countOk; private int countMeh; private int countMiss; + private int countLargeTickMiss; private int countSliderEndsDropped; private double effectiveMissCount; @@ -40,10 +41,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty countOk = score.Statistics.GetValueOrDefault(HitResult.Ok); countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); + countLargeTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss); countSliderEndsDropped = score.Statistics.GetValueOrDefault(HitResult.SmallTickMiss); - if (!score.Mods.Any(h => h is OsuModClassic cl && cl.NoSliderHeadAccuracy.Value)) - effectiveMissCount = countMiss; + effectiveMissCount = countMiss + countLargeTickMiss; else effectiveMissCount = calculateEffectiveMissCount(osuAttributes); From 58bc184e0a266ddef4269e89030d96e2e42f38a2 Mon Sep 17 00:00:00 2001 From: Fina Date: Sat, 23 Mar 2024 14:43:26 -0700 Subject: [PATCH 080/924] Use sliderend data for all non-legacy scores As per suggestion by givikap, I was not aware that non-legacy cl scores stored this data --- .../Difficulty/OsuPerformanceCalculator.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 94548e4985..d6b7a17678 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -129,12 +129,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (attributes.SliderCount > 0) { - double estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders); - double sliderNerfFactor = 0; - if (!score.Mods.Any(h => h is OsuModClassic cl && cl.NoSliderHeadAccuracy.Value)) - sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - countSliderEndsDropped / estimateDifficultSliders, 3) + attributes.SliderFactor; + double estimateSliderEndsDropped; + if (score.IsLegacyScore) + estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders); else - sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + attributes.SliderFactor; + estimateSliderEndsDropped = countSliderEndsDropped; + + double sliderNerfFactor = 0; + sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + attributes.SliderFactor; aimValue *= sliderNerfFactor; } From 9f5f6b5d37405787a4308db875331060f54572b4 Mon Sep 17 00:00:00 2001 From: TextAdventurer12 Date: Sat, 6 Apr 2024 21:39:27 +1300 Subject: [PATCH 081/924] stop capping difficult strains per note --- osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 84bf8e3bf6..b585a30855 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -81,7 +81,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills { double consistentTopStrain = difficulty / 10; // What would the top strain be if all strain values were identical - return objectStrains.Sum(s => Math.Pow(Math.Min(1, s / consistentTopStrain), 5)); + // Apply a power to nerf diffspikes, but only apply that power if s / adjustedDifficulty is less than 1, to prevent buffing certain spiky maps + return objectStrains.Sum(s => s >= adjustedDifficulty ? s / adjustedDifficulty : Math.Pow(s / adjustedDifficulty, 8)); } } -} +} \ No newline at end of file From 2dd49036edaec714641d55475849fcf3f852c452 Mon Sep 17 00:00:00 2001 From: Fina <75299710+Finadoggie@users.noreply.github.com> Date: Wed, 10 Apr 2024 20:31:52 -0700 Subject: [PATCH 082/924] Cap Buzz Slider Related Misses After letting the comments @Flamiii left brew for a while, I realized they were very much right about the buzz slider thing. As such, I've implemented a quick and dirty untested fix that will hopefully have zero unintended side-effects :) I don't see this as a permanent or final solution yet. There's definitely some potential issues/inaccuracies that could arise with maps like Notch Hell or IOException's Black Rover, but afaik this implementation would not cause any issues that stable doesn't already have. --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index d6b7a17678..0dd2adaec8 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -44,10 +44,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty countLargeTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss); countSliderEndsDropped = score.Statistics.GetValueOrDefault(HitResult.SmallTickMiss); if (!score.Mods.Any(h => h is OsuModClassic cl && cl.NoSliderHeadAccuracy.Value)) - effectiveMissCount = countMiss + countLargeTickMiss; + effectiveMissCount = countMiss + Math.Min(countLargeTickMiss, calculateEffectiveMissCount(osuAttributes) - countMiss); // Cap stuff like buzz-slider related drops to a sane value else effectiveMissCount = calculateEffectiveMissCount(osuAttributes); + double multiplier = PERFORMANCE_BASE_MULTIPLIER; if (score.Mods.Any(m => m is OsuModNoFail)) From dd17c898b3037fb5be4996acf4e6a31639674812 Mon Sep 17 00:00:00 2001 From: Fina Date: Thu, 11 Apr 2024 19:07:48 -0700 Subject: [PATCH 083/924] removed large tick misses from effectivemisscount --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 0dd2adaec8..830bc47731 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty countLargeTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss); countSliderEndsDropped = score.Statistics.GetValueOrDefault(HitResult.SmallTickMiss); if (!score.Mods.Any(h => h is OsuModClassic cl && cl.NoSliderHeadAccuracy.Value)) - effectiveMissCount = countMiss + Math.Min(countLargeTickMiss, calculateEffectiveMissCount(osuAttributes) - countMiss); // Cap stuff like buzz-slider related drops to a sane value + effectiveMissCount = countMiss; else effectiveMissCount = calculateEffectiveMissCount(osuAttributes); From b32d73ec9b7be94b3f55a7007e236cffd9228ca1 Mon Sep 17 00:00:00 2001 From: TextAdventurer12 Date: Sat, 13 Apr 2024 02:43:33 +1200 Subject: [PATCH 084/924] adjust weighting function --- osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index b585a30855..c20ea732ec 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -80,9 +80,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public double CountDifficultStrains() { double consistentTopStrain = difficulty / 10; // What would the top strain be if all strain values were identical - - // Apply a power to nerf diffspikes, but only apply that power if s / adjustedDifficulty is less than 1, to prevent buffing certain spiky maps - return objectStrains.Sum(s => s >= adjustedDifficulty ? s / adjustedDifficulty : Math.Pow(s / adjustedDifficulty, 8)); + // Use a weighted sum of all strains. Constants are arbitrary and give nice values + return objectStrains.Sum(s => 1.3 / (1 + Math.Exp(-14.15 * (Math.Pow(s / consistentTopStrain, 2) - 0.945)))); } } } \ No newline at end of file From e2a5d1904b000965f009ee746a3175396011e3fc Mon Sep 17 00:00:00 2001 From: TextAdventurer12 Date: Wed, 17 Apr 2024 01:21:06 +1200 Subject: [PATCH 085/924] adjust count difficult strains formula --- osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index c20ea732ec..beaf1d1288 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills { double consistentTopStrain = difficulty / 10; // What would the top strain be if all strain values were identical // Use a weighted sum of all strains. Constants are arbitrary and give nice values - return objectStrains.Sum(s => 1.3 / (1 + Math.Exp(-14.15 * (Math.Pow(s / consistentTopStrain, 2) - 0.945)))); + return objectStrains.Sum(s => 1.1 / (1 + Math.Exp(-10 * (s / consistentTopStrain - 0.88)))); } } } \ No newline at end of file From ca246015d5c5e46ddbb6eaa003ae1d87a818aa7c Mon Sep 17 00:00:00 2001 From: Fina <75299710+Finadoggie@users.noreply.github.com> Date: Fri, 19 Apr 2024 16:00:31 -0700 Subject: [PATCH 086/924] Add bool useSliderHead --- .../Difficulty/OsuPerformanceCalculator.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 830bc47731..c78fae1c79 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty { public const double PERFORMANCE_BASE_MULTIPLIER = 1.14; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things. + private bool useSliderHead; + private double accuracy; private int scoreMaxCombo; private int countGreat; @@ -35,6 +37,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty { var osuAttributes = (OsuDifficultyAttributes)attributes; + useSliderHead = !score.Mods.Any(h => h is OsuModClassic cl && cl.NoSliderHeadAccuracy.Value); + accuracy = score.Accuracy; scoreMaxCombo = score.MaxCombo; countGreat = score.Statistics.GetValueOrDefault(HitResult.Great); @@ -42,13 +46,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); countLargeTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss); - countSliderEndsDropped = score.Statistics.GetValueOrDefault(HitResult.SmallTickMiss); - if (!score.Mods.Any(h => h is OsuModClassic cl && cl.NoSliderHeadAccuracy.Value)) - effectiveMissCount = countMiss; + + if (useSliderHead) + countSliderEndsDropped = osuAttributes.SliderCount - score.Statistics.GetValueOrDefault(HitResult.SliderTailHit); + + if (useSliderHead) + effectiveMissCount = countMiss; else effectiveMissCount = calculateEffectiveMissCount(osuAttributes); - double multiplier = PERFORMANCE_BASE_MULTIPLIER; if (score.Mods.Any(m => m is OsuModNoFail)) @@ -131,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (attributes.SliderCount > 0) { double estimateSliderEndsDropped; - if (score.IsLegacyScore) + if (useSliderHead) estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders); else estimateSliderEndsDropped = countSliderEndsDropped; From 77814ec69fa3eabc286bd0e77db3dff7dd7b7ec8 Mon Sep 17 00:00:00 2001 From: Fina <75299710+Finadoggie@users.noreply.github.com> Date: Fri, 19 Apr 2024 16:04:17 -0700 Subject: [PATCH 087/924] Fix getting slider head drops --- .../Difficulty/OsuPerformanceCalculator.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index c78fae1c79..e3dab135dd 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -15,8 +15,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty { public const double PERFORMANCE_BASE_MULTIPLIER = 1.14; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things. - private bool useSliderHead; - private double accuracy; private int scoreMaxCombo; private int countGreat; @@ -37,8 +35,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty { var osuAttributes = (OsuDifficultyAttributes)attributes; - useSliderHead = !score.Mods.Any(h => h is OsuModClassic cl && cl.NoSliderHeadAccuracy.Value); - accuracy = score.Accuracy; scoreMaxCombo = score.MaxCombo; countGreat = score.Statistics.GetValueOrDefault(HitResult.Great); @@ -46,12 +42,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); countLargeTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss); - - if (useSliderHead) + if (!score.Mods.Any(h => h is OsuModClassic cl && cl.NoSliderHeadAccuracy.Value)) + { countSliderEndsDropped = osuAttributes.SliderCount - score.Statistics.GetValueOrDefault(HitResult.SliderTailHit); - - if (useSliderHead) - effectiveMissCount = countMiss; + } + if (!score.Mods.Any(h => h is OsuModClassic cl && cl.NoSliderHeadAccuracy.Value)) + effectiveMissCount = countMiss; else effectiveMissCount = calculateEffectiveMissCount(osuAttributes); @@ -137,7 +133,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (attributes.SliderCount > 0) { double estimateSliderEndsDropped; - if (useSliderHead) + if (score.IsLegacyScore) estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders); else estimateSliderEndsDropped = countSliderEndsDropped; From 759a82655cf3e741b5c27097a8d6ecddb9a71259 Mon Sep 17 00:00:00 2001 From: Fina <75299710+Finadoggie@users.noreply.github.com> Date: Fri, 19 Apr 2024 16:04:49 -0700 Subject: [PATCH 088/924] Clamp estimatedSliderEndsDrop --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index e3dab135dd..a2542d83e5 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (score.IsLegacyScore) estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders); else - estimateSliderEndsDropped = countSliderEndsDropped; + estimateSliderEndsDropped = Math.Min(countSliderEndsDropped + countLargeTickMiss, estimateDifficultSliders); ; double sliderNerfFactor = 0; sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + attributes.SliderFactor; From 4a7b8138aeb4cf463a83fd8c51ca31ea22d8fe8f Mon Sep 17 00:00:00 2001 From: Fina <75299710+Finadoggie@users.noreply.github.com> Date: Fri, 19 Apr 2024 16:07:54 -0700 Subject: [PATCH 089/924] Re-add bool useSliderHead oops --- .../Difficulty/OsuPerformanceCalculator.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index a2542d83e5..86f427159a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty { public const double PERFORMANCE_BASE_MULTIPLIER = 1.14; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things. + private bool useSliderHead; + private double accuracy; private int scoreMaxCombo; private int countGreat; @@ -42,12 +44,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); countLargeTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss); - if (!score.Mods.Any(h => h is OsuModClassic cl && cl.NoSliderHeadAccuracy.Value)) + if (useSliderHead) { countSliderEndsDropped = osuAttributes.SliderCount - score.Statistics.GetValueOrDefault(HitResult.SliderTailHit); } - if (!score.Mods.Any(h => h is OsuModClassic cl && cl.NoSliderHeadAccuracy.Value)) - effectiveMissCount = countMiss; + if (useSliderHead) + effectiveMissCount = countMiss; else effectiveMissCount = calculateEffectiveMissCount(osuAttributes); @@ -133,7 +135,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (attributes.SliderCount > 0) { double estimateSliderEndsDropped; - if (score.IsLegacyScore) + if (useSliderHead) estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders); else estimateSliderEndsDropped = Math.Min(countSliderEndsDropped + countLargeTickMiss, estimateDifficultSliders); ; From d1dcac08c6cf05518288fab49b72b2aa093c5c10 Mon Sep 17 00:00:00 2001 From: Fina <75299710+Finadoggie@users.noreply.github.com> Date: Fri, 19 Apr 2024 16:11:26 -0700 Subject: [PATCH 090/924] fix code formatting --- .../Difficulty/OsuPerformanceCalculator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 86f427159a..0a92b724d2 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -44,10 +44,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); countLargeTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss); + if (useSliderHead) - { countSliderEndsDropped = osuAttributes.SliderCount - score.Statistics.GetValueOrDefault(HitResult.SliderTailHit); - } + if (useSliderHead) effectiveMissCount = countMiss; else @@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (useSliderHead) estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders); else - estimateSliderEndsDropped = Math.Min(countSliderEndsDropped + countLargeTickMiss, estimateDifficultSliders); ; + estimateSliderEndsDropped = Math.Min(countSliderEndsDropped + countLargeTickMiss, estimateDifficultSliders); double sliderNerfFactor = 0; sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + attributes.SliderFactor; From 4fe55d437a906817e275217e28687f9512e090a8 Mon Sep 17 00:00:00 2001 From: Fina <75299710+Finadoggie@users.noreply.github.com> Date: Sat, 20 Apr 2024 14:14:57 -0700 Subject: [PATCH 091/924] Renamed useSliderHead to useClassicSlider (and refactored code accordingly) --- .../Difficulty/OsuPerformanceCalculator.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 0a92b724d2..23842f4e67 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty { public const double PERFORMANCE_BASE_MULTIPLIER = 1.14; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things. - private bool useSliderHead; + private bool useClassicSlider; private double accuracy; private int scoreMaxCombo; @@ -36,6 +36,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, DifficultyAttributes attributes) { var osuAttributes = (OsuDifficultyAttributes)attributes; + useClassicSlider = score.Mods.Any(h => h is OsuModClassic cl && cl.NoSliderHeadAccuracy.Value); accuracy = score.Accuracy; scoreMaxCombo = score.MaxCombo; @@ -45,13 +46,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); countLargeTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss); - if (useSliderHead) + if (!useClassicSlider) countSliderEndsDropped = osuAttributes.SliderCount - score.Statistics.GetValueOrDefault(HitResult.SliderTailHit); - if (useSliderHead) - effectiveMissCount = countMiss; - else + if (useClassicSlider) effectiveMissCount = calculateEffectiveMissCount(osuAttributes); + else + effectiveMissCount = countMiss; double multiplier = PERFORMANCE_BASE_MULTIPLIER; @@ -135,7 +136,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (attributes.SliderCount > 0) { double estimateSliderEndsDropped; - if (useSliderHead) + if (useClassicSlider) estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders); else estimateSliderEndsDropped = Math.Min(countSliderEndsDropped + countLargeTickMiss, estimateDifficultSliders); From c1efcc054cf1594d3d883fcf66ab67b811d4de81 Mon Sep 17 00:00:00 2001 From: danielthirtle Date: Tue, 21 May 2024 21:03:53 +1200 Subject: [PATCH 092/924] Change miss penalty (nerf longer maps) --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 47f6770ce5..36768967ff 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -261,7 +261,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Miss penalty assumes that a player will miss on the hardest parts of a map, // so we use the amount of relatively difficult sections to adjust miss penalty // to make it more punishing on maps with lower amount of hard sections. - private double calculateMissPenalty(double missCount, double difficultStrainCount) => 0.94 / ((missCount / (2 * Math.Sqrt(difficultStrainCount))) + 1); + private double calculateMissPenalty(double missCount, double difficultStrainCount) => 0.96 / ((missCount / (4 * Math.Pow(Math.Log(difficultStrainCount), 0.94))) + 1); private double getComboScalingFactor(OsuDifficultyAttributes attributes) => attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(attributes.MaxCombo, 0.8), 1.0); private int totalHits => countGreat + countOk + countMeh + countMiss; } From 20c54ab697eec2bb183a2f7f9ea5022a7b9be66a Mon Sep 17 00:00:00 2001 From: js1086 Date: Thu, 23 May 2024 19:08:32 +0100 Subject: [PATCH 093/924] Apply code quality changes --- osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 3 +-- .../Difficulty/Skills/OsuStrainSkill.cs | 16 ++++++++-------- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 10 ++++------ 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index ad0e3fd107..6c17c84c19 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -34,8 +34,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills { currentStrain *= strainDecay(current.DeltaTime); currentStrain += AimEvaluator.EvaluateDifficultyOf(current, withSliders) * skillMultiplier; - - objectStrains.Add(currentStrain); + ObjectStrains.Add(currentStrain); return currentStrain; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 39175d55e0..c2e9357e3a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -34,8 +34,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// protected virtual double DifficultyMultiplier => DEFAULT_DIFFICULTY_MULTIPLIER; - protected List objectStrains = new List(); - protected double difficulty; + protected List ObjectStrains = new List(); + protected double Difficulty; protected OsuStrainSkill(Mod[] mods) : base(mods) @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public override double DifficultyValue() { - difficulty = 0; + Difficulty = 0; double weight = 1; // Sections with 0 strain are excluded to avoid worst-case time complexity of the following sort (e.g. /b/2351871). @@ -64,11 +64,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills // We're sorting from highest to lowest strain. foreach (double strain in strains.OrderDescending()) { - difficulty += strain * weight; + Difficulty += strain * weight; weight *= DecayWeight; } - return difficulty * DifficultyMultiplier; + return Difficulty * DifficultyMultiplier; } /// @@ -77,9 +77,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// public double CountDifficultStrains() { - double consistentTopStrain = difficulty / 10; // What would the top strain be if all strain values were identical + double consistentTopStrain = Difficulty / 10; // What would the top strain be if all strain values were identical // Use a weighted sum of all strains. Constants are arbitrary and give nice values - return objectStrains.Sum(s => 1.1 / (1 + Math.Exp(-10 * (s / consistentTopStrain - 0.88)))); + return ObjectStrains.Sum(s => 1.1 / (1 + Math.Exp(-10 * (s / consistentTopStrain - 0.88)))); } } -} \ No newline at end of file +} diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index b1cd0b21d1..bf8e09bd53 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -41,23 +41,21 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current); double totalStrain = currentStrain * currentRhythm; - - objectStrains.Add(totalStrain); + ObjectStrains.Add(totalStrain); return totalStrain; } public double RelevantNoteCount() { - if (objectStrains.Count == 0) + if (ObjectStrains.Count == 0) return 0; - double maxStrain = objectStrains.Max(); - + double maxStrain = ObjectStrains.Max(); if (maxStrain == 0) return 0; - return objectStrains.Sum(strain => 1.0 / (1.0 + Math.Exp(-(strain / maxStrain * 12.0 - 6.0)))); + return ObjectStrains.Sum(strain => 1.0 / (1.0 + Math.Exp(-(strain / maxStrain * 12.0 - 6.0)))); } } } From 1f55c1413bc5fddbae2d9fc932376e189c9c9023 Mon Sep 17 00:00:00 2001 From: Fina <75299710+Finadoggie@users.noreply.github.com> Date: Fri, 24 May 2024 13:50:26 -0700 Subject: [PATCH 094/924] merged givi's accuracy changes stat acc save me --- .../Difficulty/OsuPerformanceCalculator.cs | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 23842f4e67..fe9d1f1afc 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -46,6 +46,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); countLargeTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss); + if (totalHits > 0) accuracy = calculateEffectiveAccuracy(countGreat, countOk, countMeh, countMiss, osuAttributes); + if (!useClassicSlider) countSliderEndsDropped = osuAttributes.SliderCount - score.Statistics.GetValueOrDefault(HitResult.SliderTailHit); @@ -192,7 +194,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty double relevantCountGreat = Math.Max(0, countGreat - relevantTotalDiff); double relevantCountOk = Math.Max(0, countOk - Math.Max(0, relevantTotalDiff - countGreat)); double relevantCountMeh = Math.Max(0, countMeh - Math.Max(0, relevantTotalDiff - countGreat - countOk)); - double relevantAccuracy = attributes.SpeedNoteCount == 0 ? 0 : (relevantCountGreat * 6.0 + relevantCountOk * 2.0 + relevantCountMeh) / (attributes.SpeedNoteCount * 6.0); + double relevantCountMiss = Math.Max(0, countMiss - Math.Max(0, relevantTotalDiff - countGreat - countOk - countMeh)); + double relevantAccuracy = attributes.SpeedNoteCount == 0 ? 0 : calculateEffectiveAccuracy(relevantCountGreat, relevantCountOk, relevantCountMeh, relevantCountMiss, attributes); // Scale the speed value with accuracy and OD. speedValue *= (0.95 + Math.Pow(attributes.OverallDifficulty, 2) / 750) * Math.Pow((accuracy + relevantAccuracy) / 2.0, (14.5 - Math.Max(attributes.OverallDifficulty, 8)) / 2); @@ -283,6 +286,25 @@ namespace osu.Game.Rulesets.Osu.Difficulty return Math.Max(countMiss, comboBasedMissCount); } + // This function is calculating accuracy trying to remove sliders from mistap if slideracc is present + // This is wrong way to do this, but doing it right way overnerfs already set scores + // This is used to no until the better way (statistical accuracy) to do this is present + private double calculateEffectiveAccuracy(double c300, double c100, double c50, double cMiss, OsuDifficultyAttributes attributes) + { + double accuracy = (c300 * 6 + c100 * 2 + c50) / (c300 + c100 + c50 + cMiss) / 6; + if (useClassicSlider) return accuracy; + + // Try to remove sliders from mistakes + double mistakesPortion = 1 - accuracy; + + double hitcircleRatio = (double)attributes.HitCircleCount / (attributes.HitCircleCount + attributes.SliderCount); + mistakesPortion *= hitcircleRatio; + + accuracy = Math.Max(accuracy, 1 - mistakesPortion); + + return accuracy; + } + private double getComboScalingFactor(OsuDifficultyAttributes attributes) => attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(attributes.MaxCombo, 0.8), 1.0); private int totalHits => countGreat + countOk + countMeh + countMiss; } From 6c9e906b2d3c89008be590347f2f9e84b3cf9fd2 Mon Sep 17 00:00:00 2001 From: Fina <75299710+Finadoggie@users.noreply.github.com> Date: Fri, 24 May 2024 14:00:42 -0700 Subject: [PATCH 095/924] Revert "merged givi's accuracy changes" This reverts commit 1f55c1413bc5fddbae2d9fc932376e189c9c9023. --- .../Difficulty/OsuPerformanceCalculator.cs | 24 +------------------ 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index fe9d1f1afc..23842f4e67 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -46,8 +46,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); countLargeTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss); - if (totalHits > 0) accuracy = calculateEffectiveAccuracy(countGreat, countOk, countMeh, countMiss, osuAttributes); - if (!useClassicSlider) countSliderEndsDropped = osuAttributes.SliderCount - score.Statistics.GetValueOrDefault(HitResult.SliderTailHit); @@ -194,8 +192,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double relevantCountGreat = Math.Max(0, countGreat - relevantTotalDiff); double relevantCountOk = Math.Max(0, countOk - Math.Max(0, relevantTotalDiff - countGreat)); double relevantCountMeh = Math.Max(0, countMeh - Math.Max(0, relevantTotalDiff - countGreat - countOk)); - double relevantCountMiss = Math.Max(0, countMiss - Math.Max(0, relevantTotalDiff - countGreat - countOk - countMeh)); - double relevantAccuracy = attributes.SpeedNoteCount == 0 ? 0 : calculateEffectiveAccuracy(relevantCountGreat, relevantCountOk, relevantCountMeh, relevantCountMiss, attributes); + double relevantAccuracy = attributes.SpeedNoteCount == 0 ? 0 : (relevantCountGreat * 6.0 + relevantCountOk * 2.0 + relevantCountMeh) / (attributes.SpeedNoteCount * 6.0); // Scale the speed value with accuracy and OD. speedValue *= (0.95 + Math.Pow(attributes.OverallDifficulty, 2) / 750) * Math.Pow((accuracy + relevantAccuracy) / 2.0, (14.5 - Math.Max(attributes.OverallDifficulty, 8)) / 2); @@ -286,25 +283,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty return Math.Max(countMiss, comboBasedMissCount); } - // This function is calculating accuracy trying to remove sliders from mistap if slideracc is present - // This is wrong way to do this, but doing it right way overnerfs already set scores - // This is used to no until the better way (statistical accuracy) to do this is present - private double calculateEffectiveAccuracy(double c300, double c100, double c50, double cMiss, OsuDifficultyAttributes attributes) - { - double accuracy = (c300 * 6 + c100 * 2 + c50) / (c300 + c100 + c50 + cMiss) / 6; - if (useClassicSlider) return accuracy; - - // Try to remove sliders from mistakes - double mistakesPortion = 1 - accuracy; - - double hitcircleRatio = (double)attributes.HitCircleCount / (attributes.HitCircleCount + attributes.SliderCount); - mistakesPortion *= hitcircleRatio; - - accuracy = Math.Max(accuracy, 1 - mistakesPortion); - - return accuracy; - } - private double getComboScalingFactor(OsuDifficultyAttributes attributes) => attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(attributes.MaxCombo, 0.8), 1.0); private int totalHits => countGreat + countOk + countMeh + countMiss; } From 61afda1089eeee4d42f59ab6185023d699acb788 Mon Sep 17 00:00:00 2001 From: js1086 Date: Sun, 26 May 2024 11:24:06 +0100 Subject: [PATCH 096/924] Fix NaN case when difficulty is 0 --- osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index c2e9357e3a..af97d90e53 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -77,6 +77,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// public double CountDifficultStrains() { + if (double.IsNaN(Difficulty)) + return 0.0; + double consistentTopStrain = Difficulty / 10; // What would the top strain be if all strain values were identical // Use a weighted sum of all strains. Constants are arbitrary and give nice values return ObjectStrains.Sum(s => 1.1 / (1 + Math.Exp(-10 * (s / consistentTopStrain - 0.88)))); From c25e1bdeb586db8a2def47232632be61b4d4242e Mon Sep 17 00:00:00 2001 From: js1086 Date: Sun, 26 May 2024 14:21:47 +0100 Subject: [PATCH 097/924] Use correct operation for 0 difficulty case --- osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index af97d90e53..ec132237db 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// public double CountDifficultStrains() { - if (double.IsNaN(Difficulty)) + if (Difficulty == 0) return 0.0; double consistentTopStrain = Difficulty / 10; // What would the top strain be if all strain values were identical From 17145673421118baee2d6971277771625512a78a Mon Sep 17 00:00:00 2001 From: Nathen Date: Wed, 29 May 2024 09:40:39 -0400 Subject: [PATCH 098/924] Save deviation calculations to variables --- .../Difficulty/TaikoPerformanceCalculator.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index ca11397801..a8ccaedfd0 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -129,6 +129,14 @@ namespace osu.Game.Rulesets.Taiko.Difficulty const double z = 2.32634787404; // 99% critical value for the normal distribution (one-tailed). + double? deviationGreatWindow = calcDeviationGreatWindow(); + double? deviationGoodWindow = calcDeviationGoodWindow(); + + if (deviationGreatWindow is null) + return deviationGoodWindow; + + return Math.Min(deviationGreatWindow.Value, deviationGoodWindow!.Value); + // The upper bound on deviation, calculated with the ratio of 300s to objects, and the great hit window. double? calcDeviationGreatWindow() { @@ -163,11 +171,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty // We can be 99% confident that the deviation is not higher than: return h100 / (Math.Sqrt(2) * SpecialFunctions.ErfInv(pLowerBound)); } - - if (calcDeviationGreatWindow() is null) - return calcDeviationGoodWindow(); - - return Math.Min(calcDeviationGreatWindow()!.Value, calcDeviationGoodWindow()!.Value); } private int totalHits => countGreat + countOk + countMeh + countMiss; From f8f18b6cbd49750916c4fb75b85f0ad93e2f98c4 Mon Sep 17 00:00:00 2001 From: Nathen Date: Wed, 29 May 2024 09:40:59 -0400 Subject: [PATCH 099/924] Fix naming convention --- .../Difficulty/TaikoPerformanceAttributes.cs | 4 ++-- .../Difficulty/TaikoPerformanceCalculator.cs | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs index 6d85aa8f3d..7c74e43db1 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs @@ -18,8 +18,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty [JsonProperty("effective_miss_count")] public double EffectiveMissCount { get; set; } - [JsonProperty("estimated_ur")] - public double? EstimatedUr { get; set; } + [JsonProperty("estimated_unstable_rate")] + public double? EstimatedUnstableRate { get; set; } public override IEnumerable GetAttributesForDisplay() { diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index a8ccaedfd0..ab809a0ade 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private int countOk; private int countMeh; private int countMiss; - private double? estimatedUr; + private double? estimatedUnstableRate; private double effectiveMissCount; @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty countOk = score.Statistics.GetValueOrDefault(HitResult.Ok); countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); - estimatedUr = computeDeviationUpperBound(taikoAttributes) * 10; + estimatedUnstableRate = computeDeviationUpperBound(taikoAttributes) * 10; // The effectiveMissCount is calculated by gaining a ratio for totalSuccessfulHits and increasing the miss penalty for shorter object counts lower than 1000. if (totalSuccessfulHits > 0) @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty Difficulty = difficultyValue, Accuracy = accuracyValue, EffectiveMissCount = effectiveMissCount, - EstimatedUr = estimatedUr, + EstimatedUnstableRate = estimatedUnstableRate, Total = totalValue }; } @@ -92,18 +92,18 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (score.Mods.Any(m => m is ModFlashlight)) difficultyValue *= 1.050 * lengthBonus; - if (estimatedUr == null) + if (estimatedUnstableRate == null) return 0; - return difficultyValue * Math.Pow(SpecialFunctions.Erf(400 / (Math.Sqrt(2) * estimatedUr.Value)), 2.0); + return difficultyValue * Math.Pow(SpecialFunctions.Erf(400 / (Math.Sqrt(2) * estimatedUnstableRate.Value)), 2.0); } private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes, bool isConvert) { - if (attributes.GreatHitWindow <= 0 || estimatedUr == null) + if (attributes.GreatHitWindow <= 0 || estimatedUnstableRate == null) return 0; - double accuracyValue = Math.Pow(70 / estimatedUr.Value, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 100.0; + double accuracyValue = Math.Pow(70 / estimatedUnstableRate.Value, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 100.0; double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3)); From e85869e0b48e7d683933ed821f6ec4baa5ea57a3 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Sun, 2 Jun 2024 16:38:58 +0200 Subject: [PATCH 100/924] Move already placed objects when adjusting offset/BPM --- .../Screens/Edit/Timing/TapTimingControl.cs | 56 ++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 8cdbd97ecb..cc0d195626 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.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. +using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -9,11 +11,13 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; +using osu.Game.Rulesets.Objects; using osuTK; namespace osu.Game.Screens.Edit.Timing @@ -33,6 +37,8 @@ namespace osu.Game.Screens.Edit.Timing private MetronomeDisplay metronome = null!; + private LabelledSwitchButton adjustPlacedNotes = null!; + [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, OsuColour colours) { @@ -59,6 +65,7 @@ namespace osu.Game.Screens.Edit.Timing { new Dimension(GridSizeMode.Absolute, 200), new Dimension(GridSizeMode.Absolute, 50), + new Dimension(GridSizeMode.Absolute, 50), new Dimension(GridSizeMode.Absolute, TapButton.SIZE + padding), }, Content = new[] @@ -116,6 +123,18 @@ namespace osu.Game.Screens.Edit.Timing }, }, new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Bottom = padding, Horizontal = padding }, + Children = new Drawable[] + { + adjustPlacedNotes = new LabelledSwitchButton { Label = "Move already placed notes\nwhen changing the offset/BPM" }, + } + }, + }, + new Drawable[] { new Container { @@ -192,6 +211,17 @@ namespace osu.Game.Screens.Edit.Timing editorClock.Seek(selectedGroup.Value.Time); } + private List hitObjectsInTimingRange(EditorBeatmap beatmap, ControlPointGroup selectedGroup) + { + // If the first group, we grab all hitobjects prior to the next, if the last group, we grab all remaining hitobjects + double firstGroupTime = beatmap.ControlPointInfo.Groups.Any(x => x.ControlPoints.Any(y => y is TimingControlPoint) && x.Time < selectedGroup.Time) ? selectedGroup.Time : double.MinValue; + double nextGroupTime = beatmap.ControlPointInfo.Groups.FirstOrDefault(x => x.ControlPoints.Any(y => y is TimingControlPoint) && x.Time > selectedGroup.Time)?.Time ?? double.MaxValue; + + var result = beatmap.HitObjects.Where(x => Precision.AlmostBigger(x.StartTime, firstGroupTime) && Precision.DefinitelyBigger(nextGroupTime, x.StartTime)).ToList(); + Console.WriteLine(firstGroupTime + ", " + nextGroupTime + ", " + result.Count); + return result; + } + private void adjustOffset(double adjust) { if (selectedGroup.Value == null) @@ -199,6 +229,8 @@ namespace osu.Game.Screens.Edit.Timing bool wasAtStart = editorClock.CurrentTimeAccurate == selectedGroup.Value.Time; + List hitObjectsInRange = hitObjectsInTimingRange(beatmap, selectedGroup.Value); + // VERY TEMPORARY var currentGroupItems = selectedGroup.Value.ControlPoints.ToArray(); @@ -212,6 +244,14 @@ namespace osu.Game.Screens.Edit.Timing // the control point might not necessarily exist yet, if currentGroupItems was empty. selectedGroup.Value = beatmap.ControlPointInfo.GroupAt(newOffset, true); + if (adjustPlacedNotes.Current.Value) + { + foreach (HitObject hitObject in hitObjectsInRange) + { + hitObject.StartTime += adjust; + } + } + if (!editorClock.IsRunning && wasAtStart) editorClock.Seek(newOffset); } @@ -223,7 +263,21 @@ namespace osu.Game.Screens.Edit.Timing if (timing == null) return; - timing.BeatLength = 60000 / (timing.BPM + adjust); + double newBeatLength = 60000 / (timing.BPM + adjust); + + List hitObjectsInRange = hitObjectsInTimingRange(beatmap, selectedGroup.Value!); + + if (adjustPlacedNotes.Current.Value) + { + foreach (HitObject hitObject in hitObjectsInRange) + { + double beat = (hitObject.StartTime - selectedGroup.Value!.Time) / timing.BeatLength; + + hitObject.StartTime = beat * newBeatLength + selectedGroup.Value.Time; + } + } + + timing.BeatLength = newBeatLength; } private partial class InlineButton : OsuButton From d02c291168af670e66612cda33d35842ec5f05cf Mon Sep 17 00:00:00 2001 From: Aurelian Date: Sun, 2 Jun 2024 16:42:06 +0200 Subject: [PATCH 101/924] Removed debugging information --- osu.Game/Screens/Edit/Timing/TapTimingControl.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index cc0d195626..c4d67eaf1a 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -217,9 +216,7 @@ namespace osu.Game.Screens.Edit.Timing double firstGroupTime = beatmap.ControlPointInfo.Groups.Any(x => x.ControlPoints.Any(y => y is TimingControlPoint) && x.Time < selectedGroup.Time) ? selectedGroup.Time : double.MinValue; double nextGroupTime = beatmap.ControlPointInfo.Groups.FirstOrDefault(x => x.ControlPoints.Any(y => y is TimingControlPoint) && x.Time > selectedGroup.Time)?.Time ?? double.MaxValue; - var result = beatmap.HitObjects.Where(x => Precision.AlmostBigger(x.StartTime, firstGroupTime) && Precision.DefinitelyBigger(nextGroupTime, x.StartTime)).ToList(); - Console.WriteLine(firstGroupTime + ", " + nextGroupTime + ", " + result.Count); - return result; + return beatmap.HitObjects.Where(x => Precision.AlmostBigger(x.StartTime, firstGroupTime) && Precision.DefinitelyBigger(nextGroupTime, x.StartTime)).ToList(); } private void adjustOffset(double adjust) From 649cfb11fc76b70573fe865f2a4669b1e5a804e9 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Sun, 2 Jun 2024 16:59:16 +0200 Subject: [PATCH 102/924] To satisfy CodeFactor --- osu.Game/Screens/Edit/Timing/TapTimingControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index c4d67eaf1a..952bdf0ccc 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -270,7 +270,7 @@ namespace osu.Game.Screens.Edit.Timing { double beat = (hitObject.StartTime - selectedGroup.Value!.Time) / timing.BeatLength; - hitObject.StartTime = beat * newBeatLength + selectedGroup.Value.Time; + hitObject.StartTime = (beat * newBeatLength) + selectedGroup.Value.Time; } } From a940809fbf8694486f9b1f0c3fb0f6ce243026e7 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Mon, 3 Jun 2024 14:36:53 +0200 Subject: [PATCH 103/924] Addressed some more code maintainability issues --- osu.Game/Screens/Edit/Timing/TapTimingControl.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 952bdf0ccc..6ef7bd1a40 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -210,13 +211,13 @@ namespace osu.Game.Screens.Edit.Timing editorClock.Seek(selectedGroup.Value.Time); } - private List hitObjectsInTimingRange(EditorBeatmap beatmap, ControlPointGroup selectedGroup) + private static List hitObjectsInTimingRange(EditorBeatmap beatmap, ControlPointGroup selectedGroup) { // If the first group, we grab all hitobjects prior to the next, if the last group, we grab all remaining hitobjects - double firstGroupTime = beatmap.ControlPointInfo.Groups.Any(x => x.ControlPoints.Any(y => y is TimingControlPoint) && x.Time < selectedGroup.Time) ? selectedGroup.Time : double.MinValue; - double nextGroupTime = beatmap.ControlPointInfo.Groups.FirstOrDefault(x => x.ControlPoints.Any(y => y is TimingControlPoint) && x.Time > selectedGroup.Time)?.Time ?? double.MaxValue; + double startTime = beatmap.ControlPointInfo.TimingPoints.Any(x => x.Time < selectedGroup.Time) ? selectedGroup.Time : double.MinValue; + double endTime = beatmap.ControlPointInfo.TimingPoints.FirstOrDefault(x => x.Time > selectedGroup.Time)?.Time ?? double.MaxValue; - return beatmap.HitObjects.Where(x => Precision.AlmostBigger(x.StartTime, firstGroupTime) && Precision.DefinitelyBigger(nextGroupTime, x.StartTime)).ToList(); + return beatmap.HitObjects.Where(x => Precision.AlmostBigger(x.StartTime, startTime) && Precision.DefinitelyBigger(endTime, x.StartTime)).ToList(); } private void adjustOffset(double adjust) From 09f3fb9eeea540197032c187688db03e036863bf Mon Sep 17 00:00:00 2001 From: Aurelian Date: Mon, 3 Jun 2024 23:02:57 +0200 Subject: [PATCH 104/924] Compatible IHasDuration hitobjects now scale with BPM changes Does not apply to hitobjects with IHasRepeats --- osu.Game/Screens/Edit/Timing/TapTimingControl.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 6ef7bd1a40..cffa31d117 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -18,6 +17,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osuTK; namespace osu.Game.Screens.Edit.Timing @@ -272,6 +272,9 @@ namespace osu.Game.Screens.Edit.Timing double beat = (hitObject.StartTime - selectedGroup.Value!.Time) / timing.BeatLength; hitObject.StartTime = (beat * newBeatLength) + selectedGroup.Value.Time; + + if (hitObject is not IHasRepeats && hitObject is IHasDuration hitObjectWithDuration) + hitObjectWithDuration.Duration *= newBeatLength / timing.BeatLength; } } From 3d5a04ac99d4b9f2002a24206106472c53da5c7b Mon Sep 17 00:00:00 2001 From: Aurelian Date: Wed, 5 Jun 2024 17:57:44 +0200 Subject: [PATCH 105/924] HitObject has defaults applied on bpm/offset adjustment --- osu.Game/Screens/Edit/Timing/TapTimingControl.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index cffa31d117..739b9894ce 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -252,6 +252,9 @@ namespace osu.Game.Screens.Edit.Timing if (!editorClock.IsRunning && wasAtStart) editorClock.Seek(newOffset); + + foreach (HitObject hitObject in hitObjectsInRange) + hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); } private void adjustBpm(double adjust) @@ -279,6 +282,9 @@ namespace osu.Game.Screens.Edit.Timing } timing.BeatLength = newBeatLength; + + foreach (HitObject hitObject in hitObjectsInRange) + hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); } private partial class InlineButton : OsuButton From 2db55f9379d6171ac9b583a883301d07dc03446a Mon Sep 17 00:00:00 2001 From: Aurelian Date: Fri, 7 Jun 2024 08:21:09 +0200 Subject: [PATCH 106/924] Change HitObject update call to use established patterns --- osu.Game/Screens/Edit/Timing/TapTimingControl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 739b9894ce..1c7f89c80d 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -254,7 +254,7 @@ namespace osu.Game.Screens.Edit.Timing editorClock.Seek(newOffset); foreach (HitObject hitObject in hitObjectsInRange) - hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); + beatmap.Update(hitObject); } private void adjustBpm(double adjust) @@ -284,7 +284,7 @@ namespace osu.Game.Screens.Edit.Timing timing.BeatLength = newBeatLength; foreach (HitObject hitObject in hitObjectsInRange) - hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); + beatmap.Update(hitObject); } private partial class InlineButton : OsuButton From 559f94aa02a138154a136fbb1708ca01d83b3b5b Mon Sep 17 00:00:00 2001 From: Aurelian Date: Fri, 7 Jun 2024 21:57:12 +0200 Subject: [PATCH 107/924] Moved HitObject adjustments to TimingControlPoint --- .../ControlPoints/TimingControlPoint.cs | 35 +++++++++++++++ .../Screens/Edit/Timing/TapTimingControl.cs | 44 ++++--------------- 2 files changed, 43 insertions(+), 36 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index 4e69486e2d..a3224a6ab9 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -2,9 +2,14 @@ // 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.Utils; using osu.Game.Beatmaps.Timing; using osu.Game.Graphics; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osuTK.Graphics; namespace osu.Game.Beatmaps.ControlPoints @@ -105,5 +110,35 @@ namespace osu.Game.Beatmaps.ControlPoints && BeatLength.Equals(other.BeatLength); public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), TimeSignature, BeatLength, OmitFirstBarLine); + + public List HitObjectsInTimingRange(IBeatmap beatmap) + { + // If the first group, we grab all hitobjects prior to the next, if the last group, we grab all remaining hitobjects + double startTime = beatmap.ControlPointInfo.TimingPoints.Any(x => x.Time < Time) ? Time : double.MinValue; + double endTime = beatmap.ControlPointInfo.TimingPoints.FirstOrDefault(x => x.Time > Time)?.Time ?? double.MaxValue; + + return beatmap.HitObjects.Where(x => Precision.AlmostBigger(x.StartTime, startTime) && Precision.DefinitelyBigger(endTime, x.StartTime)).ToList(); + } + + public void AdjustHitObjectOffset(IBeatmap beatmap, double adjust) + { + foreach (HitObject hitObject in HitObjectsInTimingRange(beatmap)) + { + hitObject.StartTime += adjust; + } + } + + public void SetHitObjectBPM(IBeatmap beatmap, double newBeatLength) + { + foreach (HitObject hitObject in HitObjectsInTimingRange(beatmap)) + { + double beat = (hitObject.StartTime - Time) / BeatLength; + + hitObject.StartTime = (beat * newBeatLength) + Time; + + if (hitObject is not IHasRepeats && hitObject is IHasDuration hitObjectWithDuration) + hitObjectWithDuration.Duration *= newBeatLength / BeatLength; + } + } } } diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 1c7f89c80d..49d3df4aef 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -211,15 +211,6 @@ namespace osu.Game.Screens.Edit.Timing editorClock.Seek(selectedGroup.Value.Time); } - private static List hitObjectsInTimingRange(EditorBeatmap beatmap, ControlPointGroup selectedGroup) - { - // If the first group, we grab all hitobjects prior to the next, if the last group, we grab all remaining hitobjects - double startTime = beatmap.ControlPointInfo.TimingPoints.Any(x => x.Time < selectedGroup.Time) ? selectedGroup.Time : double.MinValue; - double endTime = beatmap.ControlPointInfo.TimingPoints.FirstOrDefault(x => x.Time > selectedGroup.Time)?.Time ?? double.MaxValue; - - return beatmap.HitObjects.Where(x => Precision.AlmostBigger(x.StartTime, startTime) && Precision.DefinitelyBigger(endTime, x.StartTime)).ToList(); - } - private void adjustOffset(double adjust) { if (selectedGroup.Value == null) @@ -227,8 +218,6 @@ namespace osu.Game.Screens.Edit.Timing bool wasAtStart = editorClock.CurrentTimeAccurate == selectedGroup.Value.Time; - List hitObjectsInRange = hitObjectsInTimingRange(beatmap, selectedGroup.Value); - // VERY TEMPORARY var currentGroupItems = selectedGroup.Value.ControlPoints.ToArray(); @@ -237,26 +226,22 @@ namespace osu.Game.Screens.Edit.Timing double newOffset = selectedGroup.Value.Time + adjust; foreach (var cp in currentGroupItems) + { + if (adjustPlacedNotes.Current.Value && cp is TimingControlPoint tp) + tp.AdjustHitObjectOffset(beatmap, adjust); beatmap.ControlPointInfo.Add(newOffset, cp); + } // the control point might not necessarily exist yet, if currentGroupItems was empty. selectedGroup.Value = beatmap.ControlPointInfo.GroupAt(newOffset, true); - if (adjustPlacedNotes.Current.Value) - { - foreach (HitObject hitObject in hitObjectsInRange) - { - hitObject.StartTime += adjust; - } - } - if (!editorClock.IsRunning && wasAtStart) editorClock.Seek(newOffset); - foreach (HitObject hitObject in hitObjectsInRange) - beatmap.Update(hitObject); + beatmap.UpdateAllHitObjects(); } + private void adjustBpm(double adjust) { var timing = selectedGroup.Value?.ControlPoints.OfType().FirstOrDefault(); @@ -266,25 +251,12 @@ namespace osu.Game.Screens.Edit.Timing double newBeatLength = 60000 / (timing.BPM + adjust); - List hitObjectsInRange = hitObjectsInTimingRange(beatmap, selectedGroup.Value!); - if (adjustPlacedNotes.Current.Value) - { - foreach (HitObject hitObject in hitObjectsInRange) - { - double beat = (hitObject.StartTime - selectedGroup.Value!.Time) / timing.BeatLength; - - hitObject.StartTime = (beat * newBeatLength) + selectedGroup.Value.Time; - - if (hitObject is not IHasRepeats && hitObject is IHasDuration hitObjectWithDuration) - hitObjectWithDuration.Duration *= newBeatLength / timing.BeatLength; - } - } + timing.SetHitObjectBPM(beatmap, newBeatLength); timing.BeatLength = newBeatLength; - foreach (HitObject hitObject in hitObjectsInRange) - beatmap.Update(hitObject); + beatmap.UpdateAllHitObjects(); } private partial class InlineButton : OsuButton From ac0c425e2921a2b84467b4703eacff35d380dba7 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Sun, 9 Jun 2024 11:27:53 +0200 Subject: [PATCH 108/924] Moved setting to the menu bar --- .../ControlPoints/TimingControlPoint.cs | 8 +++--- osu.Game/Localisation/EditorStrings.cs | 5 ++++ osu.Game/Screens/Edit/Editor.cs | 6 ++++- osu.Game/Screens/Edit/EditorBeatmap.cs | 2 ++ .../Screens/Edit/Timing/TapTimingControl.cs | 25 +++---------------- 5 files changed, 20 insertions(+), 26 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index a3224a6ab9..234a34dc87 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -128,16 +128,16 @@ namespace osu.Game.Beatmaps.ControlPoints } } - public void SetHitObjectBPM(IBeatmap beatmap, double newBeatLength) + public void SetHitObjectBPM(IBeatmap beatmap, double oldBeatLength) { foreach (HitObject hitObject in HitObjectsInTimingRange(beatmap)) { - double beat = (hitObject.StartTime - Time) / BeatLength; + double beat = (hitObject.StartTime - Time) / oldBeatLength; - hitObject.StartTime = (beat * newBeatLength) + Time; + hitObject.StartTime = (beat * BeatLength) + Time; if (hitObject is not IHasRepeats && hitObject is IHasDuration hitObjectWithDuration) - hitObjectWithDuration.Duration *= newBeatLength / BeatLength; + hitObjectWithDuration.Duration *= BeatLength / oldBeatLength; } } } diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs index 6ad12f54df..b604fc3889 100644 --- a/osu.Game/Localisation/EditorStrings.cs +++ b/osu.Game/Localisation/EditorStrings.cs @@ -39,6 +39,11 @@ namespace osu.Game.Localisation /// public static LocalisableString SetPreviewPointToCurrent => new TranslatableString(getKey(@"set_preview_point_to_current"), @"Set preview point to current time"); + /// + /// "Move already placed notes when changing the offset / BPM" + /// + public static LocalisableString AdjustNotesOnOffsetBPMChange => new TranslatableString(getKey(@"adjust_notes_on_offset_bpm_change"), @"Move already placed notes when changing the offset / BPM"); + /// /// "For editing (.olz)" /// diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 07c32983f5..af099413fd 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -367,7 +367,11 @@ namespace osu.Game.Screens.Edit { Items = new MenuItem[] { - new EditorMenuItem(EditorStrings.SetPreviewPointToCurrent, MenuItemType.Standard, SetPreviewPointToCurrentTime) + new EditorMenuItem(EditorStrings.SetPreviewPointToCurrent, MenuItemType.Standard, SetPreviewPointToCurrentTime), + new ToggleMenuItem(EditorStrings.AdjustNotesOnOffsetBPMChange) + { + State = { BindTarget = editorBeatmap.AdjustNotesOnOffsetBPMChange }, + } } } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 7a3ea474fb..42a3a6d3b2 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -88,6 +88,8 @@ namespace osu.Game.Screens.Edit public BindableInt PreviewTime { get; } + public Bindable AdjustNotesOnOffsetBPMChange { get; } = new Bindable(false); + private readonly IBeatmapProcessor beatmapProcessor; private readonly Dictionary> startTimeBindables = new Dictionary>(); diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 49d3df4aef..30e5919411 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -37,8 +37,6 @@ namespace osu.Game.Screens.Edit.Timing private MetronomeDisplay metronome = null!; - private LabelledSwitchButton adjustPlacedNotes = null!; - [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, OsuColour colours) { @@ -65,7 +63,6 @@ namespace osu.Game.Screens.Edit.Timing { new Dimension(GridSizeMode.Absolute, 200), new Dimension(GridSizeMode.Absolute, 50), - new Dimension(GridSizeMode.Absolute, 50), new Dimension(GridSizeMode.Absolute, TapButton.SIZE + padding), }, Content = new[] @@ -123,18 +120,6 @@ namespace osu.Game.Screens.Edit.Timing }, }, new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Bottom = padding, Horizontal = padding }, - Children = new Drawable[] - { - adjustPlacedNotes = new LabelledSwitchButton { Label = "Move already placed notes\nwhen changing the offset/BPM" }, - } - }, - }, - new Drawable[] { new Container { @@ -227,7 +212,7 @@ namespace osu.Game.Screens.Edit.Timing foreach (var cp in currentGroupItems) { - if (adjustPlacedNotes.Current.Value && cp is TimingControlPoint tp) + if (beatmap.AdjustNotesOnOffsetBPMChange.Value && cp is TimingControlPoint tp) tp.AdjustHitObjectOffset(beatmap, adjust); beatmap.ControlPointInfo.Add(newOffset, cp); } @@ -249,12 +234,10 @@ namespace osu.Game.Screens.Edit.Timing if (timing == null) return; - double newBeatLength = 60000 / (timing.BPM + adjust); + timing.BeatLength = 60000 / (timing.BPM + adjust); - if (adjustPlacedNotes.Current.Value) - timing.SetHitObjectBPM(beatmap, newBeatLength); - - timing.BeatLength = newBeatLength; + if (beatmap.AdjustNotesOnOffsetBPMChange.Value) + timing.SetHitObjectBPM(beatmap, 60000 / (timing.BPM - adjust)); beatmap.UpdateAllHitObjects(); } From 33d0d4c8f22b284c784a55ab8e143781a580ed16 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Sun, 9 Jun 2024 11:29:25 +0200 Subject: [PATCH 109/924] Fixed certain UI elements not working for HitObject BPM/Offset adjustment --- osu.Game/Screens/Edit/Timing/GroupSection.cs | 7 +++++++ osu.Game/Screens/Edit/Timing/TapTimingControl.cs | 5 ----- osu.Game/Screens/Edit/Timing/TimingSection.cs | 11 +++++++++++ 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/GroupSection.cs b/osu.Game/Screens/Edit/Timing/GroupSection.cs index 487a871881..7d0eab1f7f 100644 --- a/osu.Game/Screens/Edit/Timing/GroupSection.cs +++ b/osu.Game/Screens/Edit/Timing/GroupSection.cs @@ -109,11 +109,18 @@ namespace osu.Game.Screens.Edit.Timing Beatmap.ControlPointInfo.RemoveGroup(SelectedGroup.Value); foreach (var cp in currentGroupItems) + { + // Only adjust hit object offsets if the group contains a timing control point + if (Beatmap.AdjustNotesOnOffsetBPMChange.Value && cp is TimingControlPoint tp) + tp.AdjustHitObjectOffset(Beatmap, time - SelectedGroup.Value.Time); Beatmap.ControlPointInfo.Add(time, cp); + } // the control point might not necessarily exist yet, if currentGroupItems was empty. SelectedGroup.Value = Beatmap.ControlPointInfo.GroupAt(time, true); + Beatmap.UpdateAllHitObjects(); + changeHandler?.EndChange(); } } diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 30e5919411..937235c7bc 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -10,14 +9,11 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; -using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; using osuTK; namespace osu.Game.Screens.Edit.Timing @@ -226,7 +222,6 @@ namespace osu.Game.Screens.Edit.Timing beatmap.UpdateAllHitObjects(); } - private void adjustBpm(double adjust) { var timing = selectedGroup.Value?.ControlPoints.OfType().FirstOrDefault(); diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 2757753b07..24cf865ce3 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -42,6 +42,17 @@ namespace osu.Game.Screens.Edit.Timing { if (!isRebinding) ChangeHandler?.SaveState(); } + + bpmTextEntry.Bindable.BindValueChanged(val => + { + if (ControlPoint.Value == null) + return; + + ChangeHandler?.BeginChange(); + ControlPoint.Value.SetHitObjectBPM(Beatmap, val.OldValue); + Beatmap.UpdateAllHitObjects(); + ChangeHandler?.EndChange(); + }); } private bool isRebinding; From 101887d3154306e53021352f47c609f90362c913 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Sun, 9 Jun 2024 18:04:27 +0200 Subject: [PATCH 110/924] Notes aren't adjusted if setting is off --- osu.Game/Screens/Edit/Timing/TimingSection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 24cf865ce3..1ae85e6c9e 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Edit.Timing bpmTextEntry.Bindable.BindValueChanged(val => { - if (ControlPoint.Value == null) + if (!Beatmap.AdjustNotesOnOffsetBPMChange.Value || ControlPoint.Value == null) return; ChangeHandler?.BeginChange(); From 9906ab3449719c0d01b3f847edbabe73359a3d95 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Wed, 12 Jun 2024 19:25:48 +0200 Subject: [PATCH 111/924] Fixed double adjustment of hitobject beatlength --- osu.Game/Screens/Edit/Timing/TapTimingControl.cs | 3 --- osu.Game/Screens/Edit/Timing/TimingSection.cs | 2 -- 2 files changed, 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 937235c7bc..55404702ac 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -231,9 +231,6 @@ namespace osu.Game.Screens.Edit.Timing timing.BeatLength = 60000 / (timing.BPM + adjust); - if (beatmap.AdjustNotesOnOffsetBPMChange.Value) - timing.SetHitObjectBPM(beatmap, 60000 / (timing.BPM - adjust)); - beatmap.UpdateAllHitObjects(); } diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 1ae85e6c9e..5be1de467d 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -48,10 +48,8 @@ namespace osu.Game.Screens.Edit.Timing if (!Beatmap.AdjustNotesOnOffsetBPMChange.Value || ControlPoint.Value == null) return; - ChangeHandler?.BeginChange(); ControlPoint.Value.SetHitObjectBPM(Beatmap, val.OldValue); Beatmap.UpdateAllHitObjects(); - ChangeHandler?.EndChange(); }); } From 9b076a8b03f7f7258e2c20b237ab94490c245e42 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Wed, 12 Jun 2024 19:57:17 +0200 Subject: [PATCH 112/924] Moved HitObject adjustment methods to a static helper class --- .../ControlPoints/TimingControlPoint.cs | 35 ---------------- osu.Game/Screens/Edit/Timing/GroupSection.cs | 2 +- .../Screens/Edit/Timing/TapTimingControl.cs | 2 +- osu.Game/Screens/Edit/Timing/TimingSection.cs | 41 ++++++++++++++++++- 4 files changed, 42 insertions(+), 38 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index 234a34dc87..4e69486e2d 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -2,14 +2,9 @@ // 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.Utils; using osu.Game.Beatmaps.Timing; using osu.Game.Graphics; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; using osuTK.Graphics; namespace osu.Game.Beatmaps.ControlPoints @@ -110,35 +105,5 @@ namespace osu.Game.Beatmaps.ControlPoints && BeatLength.Equals(other.BeatLength); public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), TimeSignature, BeatLength, OmitFirstBarLine); - - public List HitObjectsInTimingRange(IBeatmap beatmap) - { - // If the first group, we grab all hitobjects prior to the next, if the last group, we grab all remaining hitobjects - double startTime = beatmap.ControlPointInfo.TimingPoints.Any(x => x.Time < Time) ? Time : double.MinValue; - double endTime = beatmap.ControlPointInfo.TimingPoints.FirstOrDefault(x => x.Time > Time)?.Time ?? double.MaxValue; - - return beatmap.HitObjects.Where(x => Precision.AlmostBigger(x.StartTime, startTime) && Precision.DefinitelyBigger(endTime, x.StartTime)).ToList(); - } - - public void AdjustHitObjectOffset(IBeatmap beatmap, double adjust) - { - foreach (HitObject hitObject in HitObjectsInTimingRange(beatmap)) - { - hitObject.StartTime += adjust; - } - } - - public void SetHitObjectBPM(IBeatmap beatmap, double oldBeatLength) - { - foreach (HitObject hitObject in HitObjectsInTimingRange(beatmap)) - { - double beat = (hitObject.StartTime - Time) / oldBeatLength; - - hitObject.StartTime = (beat * BeatLength) + Time; - - if (hitObject is not IHasRepeats && hitObject is IHasDuration hitObjectWithDuration) - hitObjectWithDuration.Duration *= BeatLength / oldBeatLength; - } - } } } diff --git a/osu.Game/Screens/Edit/Timing/GroupSection.cs b/osu.Game/Screens/Edit/Timing/GroupSection.cs index 7d0eab1f7f..c5a4422192 100644 --- a/osu.Game/Screens/Edit/Timing/GroupSection.cs +++ b/osu.Game/Screens/Edit/Timing/GroupSection.cs @@ -112,7 +112,7 @@ namespace osu.Game.Screens.Edit.Timing { // Only adjust hit object offsets if the group contains a timing control point if (Beatmap.AdjustNotesOnOffsetBPMChange.Value && cp is TimingControlPoint tp) - tp.AdjustHitObjectOffset(Beatmap, time - SelectedGroup.Value.Time); + TimingSectionAdjustments.AdjustHitObjectOffset(Beatmap, tp, time - SelectedGroup.Value.Time); Beatmap.ControlPointInfo.Add(time, cp); } diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 55404702ac..f2e37369d1 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -209,7 +209,7 @@ namespace osu.Game.Screens.Edit.Timing foreach (var cp in currentGroupItems) { if (beatmap.AdjustNotesOnOffsetBPMChange.Value && cp is TimingControlPoint tp) - tp.AdjustHitObjectOffset(beatmap, adjust); + TimingSectionAdjustments.AdjustHitObjectOffset(beatmap, tp, adjust); beatmap.ControlPointInfo.Add(newOffset, cp); } diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 5be1de467d..ba335d55eb 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -1,11 +1,17 @@ // 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.Utils; +using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Screens.Edit.Timing { @@ -48,7 +54,7 @@ namespace osu.Game.Screens.Edit.Timing if (!Beatmap.AdjustNotesOnOffsetBPMChange.Value || ControlPoint.Value == null) return; - ControlPoint.Value.SetHitObjectBPM(Beatmap, val.OldValue); + TimingSectionAdjustments.SetHitObjectBPM(Beatmap, ControlPoint.Value, val.OldValue); Beatmap.UpdateAllHitObjects(); }); } @@ -128,4 +134,37 @@ namespace osu.Game.Screens.Edit.Timing private static double beatLengthToBpm(double beatLength) => 60000 / beatLength; } + + public static class TimingSectionAdjustments + { + public static List HitObjectsInTimingRange(IBeatmap beatmap, double time) + { + // If the first group, we grab all hitobjects prior to the next, if the last group, we grab all remaining hitobjects + double startTime = beatmap.ControlPointInfo.TimingPoints.Any(x => x.Time < time) ? time : double.MinValue; + double endTime = beatmap.ControlPointInfo.TimingPoints.FirstOrDefault(x => x.Time > time)?.Time ?? double.MaxValue; + + return beatmap.HitObjects.Where(x => Precision.AlmostBigger(x.StartTime, startTime) && Precision.DefinitelyBigger(endTime, x.StartTime)).ToList(); + } + + public static void AdjustHitObjectOffset(IBeatmap beatmap, TimingControlPoint timingControlPoint, double adjust) + { + foreach (HitObject hitObject in HitObjectsInTimingRange(beatmap, timingControlPoint.Time)) + { + hitObject.StartTime += adjust; + } + } + + public static void SetHitObjectBPM(IBeatmap beatmap, TimingControlPoint timingControlPoint, double oldBeatLength) + { + foreach (HitObject hitObject in HitObjectsInTimingRange(beatmap, timingControlPoint.Time)) + { + double beat = (hitObject.StartTime - timingControlPoint.Time) / oldBeatLength; + + hitObject.StartTime = (beat * timingControlPoint.BeatLength) + timingControlPoint.Time; + + if (hitObject is not IHasRepeats && hitObject is IHasDuration hitObjectWithDuration) + hitObjectWithDuration.Duration *= timingControlPoint.BeatLength / oldBeatLength; + } + } + } } From 2fb22f1febef5ad5ab120c24875e69b798ed984e Mon Sep 17 00:00:00 2001 From: Nathen Date: Sun, 23 Jun 2024 19:17:19 -0400 Subject: [PATCH 113/924] Move the return value for deviation below the local functions --- .../Difficulty/TaikoPerformanceCalculator.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index ab809a0ade..e42b015176 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -129,14 +129,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty const double z = 2.32634787404; // 99% critical value for the normal distribution (one-tailed). - double? deviationGreatWindow = calcDeviationGreatWindow(); - double? deviationGoodWindow = calcDeviationGoodWindow(); - - if (deviationGreatWindow is null) - return deviationGoodWindow; - - return Math.Min(deviationGreatWindow.Value, deviationGoodWindow!.Value); - // The upper bound on deviation, calculated with the ratio of 300s to objects, and the great hit window. double? calcDeviationGreatWindow() { @@ -171,6 +163,14 @@ namespace osu.Game.Rulesets.Taiko.Difficulty // We can be 99% confident that the deviation is not higher than: return h100 / (Math.Sqrt(2) * SpecialFunctions.ErfInv(pLowerBound)); } + + double? deviationGreatWindow = calcDeviationGreatWindow(); + double? deviationGoodWindow = calcDeviationGoodWindow(); + + if (deviationGreatWindow is null) + return deviationGoodWindow; + + return Math.Min(deviationGreatWindow.Value, deviationGoodWindow!.Value); } private int totalHits => countGreat + countOk + countMeh + countMiss; From b15028a918ececff597d694c3315d732cf784cdc Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 3 Jul 2024 12:36:12 +0200 Subject: [PATCH 114/924] fixes --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 8d80ed651e..7720afe60a 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; @@ -23,18 +24,9 @@ namespace osu.Game.Rulesets.Osu.Edit { public partial class OsuSelectionHandler : EditorSelectionHandler { - [Resolved(CanBeNull = true)] - private IDistanceSnapProvider? snapProvider { get; set; } - [Resolved] private OsuGridToolboxGroup gridToolbox { get; set; } = null!; - /// - /// During a transform, the initial path types of a single selected slider are stored so they - /// can be maintained throughout the operation. - /// - private List? referencePathTypes; - protected override void OnSelectionChanged() { base.OnSelectionChanged(); From 5f8512896e0eee0eda8ec0c9410704422d475182 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 3 Jul 2024 12:40:22 +0200 Subject: [PATCH 115/924] use grid origin in scale tool --- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 8 +++++--- osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index a299eebbce..65a07e2e2f 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; -using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit.Components.RadioButtons; using osuTK; @@ -20,6 +19,8 @@ namespace osu.Game.Rulesets.Osu.Edit { private readonly OsuSelectionScaleHandler scaleHandler; + private readonly OsuGridToolboxGroup gridToolbox; + private readonly Bindable scaleInfo = new Bindable(new PreciseScaleInfo(1, ScaleOrigin.PlayfieldCentre, true, true)); private SliderWithTextBoxInput scaleInput = null!; @@ -32,9 +33,10 @@ namespace osu.Game.Rulesets.Osu.Edit private OsuCheckbox xCheckBox = null!; private OsuCheckbox yCheckBox = null!; - public PreciseScalePopover(OsuSelectionScaleHandler scaleHandler) + public PreciseScalePopover(OsuSelectionScaleHandler scaleHandler, OsuGridToolboxGroup gridToolbox) { this.scaleHandler = scaleHandler; + this.gridToolbox = gridToolbox; AllowableAnchors = new[] { Anchor.CentreLeft, Anchor.CentreRight }; } @@ -179,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Edit updateAxisCheckBoxesEnabled(); } - private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? OsuPlayfield.BASE_SIZE / 2 : null; + private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? gridToolbox.StartPosition.Value : null; private void setAxis(bool x, bool y) { diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs index 2e4d7e8b91..a41412cbe3 100644 --- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Edit () => new PreciseRotationPopover(RotationHandler, GridToolbox)), scaleButton = new EditorToolButton("Scale", () => new SpriteIcon { Icon = FontAwesome.Solid.ArrowsAlt }, - () => new PreciseScalePopover(ScaleHandler)) + () => new PreciseScalePopover(ScaleHandler, GridToolbox)) } }; } From d0715c5f12a98e9a31465600d1cc69bb0efe1df2 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 3 Jul 2024 16:23:19 +0200 Subject: [PATCH 116/924] scale along rotated axis --- .../Edit/OsuSelectionScaleHandler.cs | 104 ++++++++++++++---- .../Edit/PreciseScalePopover.cs | 8 +- .../Editing/TestSceneComposeSelectBox.cs | 2 +- .../SkinEditor/SkinSelectionScaleHandler.cs | 2 +- .../Components/SelectionScaleHandler.cs | 8 +- osu.Game/Utils/GeometryUtils.cs | 4 +- 6 files changed, 97 insertions(+), 31 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index f4fd48f183..cfcf90e5f5 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.Edit defaultOrigin = OriginalSurroundingQuad.Value.Centre; } - public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both) + public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0) { if (!OperationInProgress.Value) throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!"); @@ -94,6 +94,7 @@ namespace osu.Game.Rulesets.Osu.Edit Debug.Assert(objectsInScale != null && defaultOrigin != null && OriginalSurroundingQuad != null); Vector2 actualOrigin = origin ?? defaultOrigin.Value; + scale = clampScaleToAdjustAxis(scale, adjustAxis); // for the time being, allow resizing of slider paths only if the slider is // the only hit object selected. with a group selection, it's likely the user @@ -102,15 +103,15 @@ namespace osu.Game.Rulesets.Osu.Edit { var originalInfo = objectsInScale[slider]; Debug.Assert(originalInfo.PathControlPointPositions != null && originalInfo.PathControlPointTypes != null); - scaleSlider(slider, scale, originalInfo.PathControlPointPositions, originalInfo.PathControlPointTypes); + scaleSlider(slider, scale, originalInfo.PathControlPointPositions, originalInfo.PathControlPointTypes, axisRotation); } else { - scale = ClampScaleToPlayfieldBounds(scale, actualOrigin); + scale = ClampScaleToPlayfieldBounds(scale, actualOrigin, adjustAxis, axisRotation); foreach (var (ho, originalState) in objectsInScale) { - ho.Position = GeometryUtils.GetScaledPosition(scale, actualOrigin, originalState.Position); + ho.Position = GeometryUtils.GetScaledPosition(scale, actualOrigin, originalState.Position, axisRotation); } } @@ -134,14 +135,34 @@ namespace osu.Game.Rulesets.Osu.Edit private IEnumerable selectedMovableObjects => selectedItems.Cast() .Where(h => h is not Spinner); - private void scaleSlider(Slider slider, Vector2 scale, Vector2[] originalPathPositions, PathType?[] originalPathTypes) + private Vector2 clampScaleToAdjustAxis(Vector2 scale, Axes adjustAxis) + { + switch (adjustAxis) + { + case Axes.Y: + scale.X = 1; + break; + + case Axes.X: + scale.Y = 1; + break; + + case Axes.None: + scale = Vector2.One; + break; + } + + return scale; + } + + private void scaleSlider(Slider slider, Vector2 scale, Vector2[] originalPathPositions, PathType?[] originalPathTypes, float axisRotation = 0) { scale = Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON)); // Maintain the path types in case they were defaulted to bezier at some point during scaling for (int i = 0; i < slider.Path.ControlPoints.Count; i++) { - slider.Path.ControlPoints[i].Position = originalPathPositions[i] * scale; + slider.Path.ControlPoints[i].Position = GeometryUtils.GetScaledPosition(scale, Vector2.Zero, originalPathPositions[i], axisRotation); slider.Path.ControlPoints[i].Type = originalPathTypes[i]; } @@ -176,11 +197,13 @@ namespace osu.Game.Rulesets.Osu.Edit /// /// The origin from which the scale operation is performed /// The scale to be clamped + /// The axes to adjust the scale in. + /// The rotation of the axes in degrees /// The clamped scale vector - public Vector2 ClampScaleToPlayfieldBounds(Vector2 scale, Vector2? origin = null) + public Vector2 ClampScaleToPlayfieldBounds(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0) { //todo: this is not always correct for selections involving sliders. This approximation assumes each point is scaled independently, but sliderends move with the sliderhead. - if (objectsInScale == null) + if (objectsInScale == null || adjustAxis == Axes.None) return scale; Debug.Assert(defaultOrigin != null && OriginalSurroundingQuad != null); @@ -188,24 +211,63 @@ namespace osu.Game.Rulesets.Osu.Edit if (objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider) origin = slider.Position; + scale = clampScaleToAdjustAxis(scale, adjustAxis); Vector2 actualOrigin = origin ?? defaultOrigin.Value; var selectionQuad = OriginalSurroundingQuad.Value; - var tl1 = Vector2.Divide(-actualOrigin, selectionQuad.TopLeft - actualOrigin); - var tl2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - actualOrigin, selectionQuad.TopLeft - actualOrigin); - var br1 = Vector2.Divide(-actualOrigin, selectionQuad.BottomRight - actualOrigin); - var br2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - actualOrigin, selectionQuad.BottomRight - actualOrigin); - - if (!Precision.AlmostEquals(selectionQuad.TopLeft.X - actualOrigin.X, 0)) - scale.X = selectionQuad.TopLeft.X - actualOrigin.X < 0 ? MathHelper.Clamp(scale.X, tl2.X, tl1.X) : MathHelper.Clamp(scale.X, tl1.X, tl2.X); - if (!Precision.AlmostEquals(selectionQuad.TopLeft.Y - actualOrigin.Y, 0)) - scale.Y = selectionQuad.TopLeft.Y - actualOrigin.Y < 0 ? MathHelper.Clamp(scale.Y, tl2.Y, tl1.Y) : MathHelper.Clamp(scale.Y, tl1.Y, tl2.Y); - if (!Precision.AlmostEquals(selectionQuad.BottomRight.X - actualOrigin.X, 0)) - scale.X = selectionQuad.BottomRight.X - actualOrigin.X < 0 ? MathHelper.Clamp(scale.X, br2.X, br1.X) : MathHelper.Clamp(scale.X, br1.X, br2.X); - if (!Precision.AlmostEquals(selectionQuad.BottomRight.Y - actualOrigin.Y, 0)) - scale.Y = selectionQuad.BottomRight.Y - actualOrigin.Y < 0 ? MathHelper.Clamp(scale.Y, br2.Y, br1.Y) : MathHelper.Clamp(scale.Y, br1.Y, br2.Y); + scale = clampToBound(scale, selectionQuad.BottomRight, OsuPlayfield.BASE_SIZE.X, Axes.X); + scale = clampToBound(scale, selectionQuad.BottomRight, OsuPlayfield.BASE_SIZE.Y, Axes.Y); + scale = clampToBound(scale, selectionQuad.TopLeft, 0, Axes.X); + scale = clampToBound(scale, selectionQuad.TopLeft, 0, Axes.Y); return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON)); + + Vector2 clampToBound(Vector2 s, Vector2 p, float bound, Axes axis) + { + float px = p.X - actualOrigin.X; + float py = p.Y - actualOrigin.Y; + float c = axis == Axes.X ? bound - actualOrigin.X : bound - actualOrigin.Y; + float cos = MathF.Cos(float.DegreesToRadians(-axisRotation)); + float sin = MathF.Sin(float.DegreesToRadians(-axisRotation)); + float a, b; + + if (axis == Axes.X) + { + a = cos * cos * px - sin * cos * py; + b = sin * sin * px + sin * cos * py; + } + else + { + a = -sin * cos * px + sin * sin * py; + b = sin * cos * px + cos * cos * py; + } + + switch (adjustAxis) + { + case Axes.X: + if (Precision.AlmostEquals(a, 0) || (c - b) / a < 0) + break; + + s.X = MathF.Min(scale.X, (c - b) / a); + break; + + case Axes.Y: + if (Precision.AlmostEquals(b, 0) || (c - a) / b < 0) + break; + + s.Y = MathF.Min(scale.Y, (c - a) / b); + break; + + case Axes.Both: + if (Precision.AlmostEquals(a + b, 0) || c / (a * s.X + b * s.Y) < 0) + break; + + s = Vector2.ComponentMin(s, s * c / (a * s.X + b * s.Y)); + break; + } + + return s; + } } private void moveSelectionInBounds() diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index 65a07e2e2f..a1907a2fd5 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -130,8 +130,8 @@ namespace osu.Game.Rulesets.Osu.Edit scaleInfo.BindValueChanged(scale => { - var newScale = new Vector2(scale.NewValue.XAxis ? scale.NewValue.Scale : 1, scale.NewValue.YAxis ? scale.NewValue.Scale : 1); - scaleHandler.Update(newScale, getOriginPosition(scale.NewValue)); + var newScale = new Vector2(scale.NewValue.Scale, scale.NewValue.Scale); + scaleHandler.Update(newScale, getOriginPosition(scale.NewValue), getAdjustAxis(scale.NewValue), gridToolbox.GridLinesRotation.Value); }); } @@ -164,7 +164,7 @@ namespace osu.Game.Rulesets.Osu.Edit return; const float max_scale = 10; - var scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(max_scale), getOriginPosition(scaleInfo.Value)); + var scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(max_scale), getOriginPosition(scaleInfo.Value), getAdjustAxis(scaleInfo.Value), gridToolbox.GridLinesRotation.Value); if (!scaleInfo.Value.XAxis) scale.X = max_scale; @@ -183,6 +183,8 @@ namespace osu.Game.Rulesets.Osu.Edit private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? gridToolbox.StartPosition.Value : null; + private Axes getAdjustAxis(PreciseScaleInfo scale) => scale.XAxis ? scale.YAxis ? Axes.Both : Axes.X : Axes.Y; + private void setAxis(bool x, bool y) { scaleInfo.Value = scaleInfo.Value with { XAxis = x, YAxis = y }; diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs index 28763051e3..30f397f518 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs @@ -134,7 +134,7 @@ namespace osu.Game.Tests.Visual.Editing OriginalSurroundingQuad = new Quad(targetContainer!.X, targetContainer.Y, targetContainer.Width, targetContainer.Height); } - public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both) + public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0) { if (targetContainer == null) throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!"); diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs index 4bfa7fba81..977aaade99 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs @@ -73,7 +73,7 @@ namespace osu.Game.Overlays.SkinEditor isFlippedY = false; } - public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both) + public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0) { if (objectsInScale == null) throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!"); diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs index c91362219c..177de9df33 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs @@ -52,10 +52,11 @@ namespace osu.Game.Screens.Edit.Compose.Components /// If the default value is supplied, a sane implementation-defined default will be used. /// /// The axes to adjust the scale in. - public void ScaleSelection(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both) + /// The rotation of the axes in degrees. + public void ScaleSelection(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0) { Begin(); - Update(scale, origin, adjustAxis); + Update(scale, origin, adjustAxis, axisRotation); Commit(); } @@ -91,7 +92,8 @@ namespace osu.Game.Screens.Edit.Compose.Components /// If the default value is supplied, a sane implementation-defined default will be used. /// /// The axes to adjust the scale in. - public virtual void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both) + /// The rotation of the axes in degrees. + public virtual void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0) { } diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index bf1addf6c8..f6e7e81007 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -104,9 +104,9 @@ namespace osu.Game.Utils /// Given a scale multiplier, an origin, and a position, /// will return the scaled position in screen space coordinates. /// - public static Vector2 GetScaledPosition(Vector2 scale, Vector2 origin, Vector2 position) + public static Vector2 GetScaledPosition(Vector2 scale, Vector2 origin, Vector2 position, float axisRotation = 0) { - return origin + (position - origin) * scale; + return origin + RotateVector(RotateVector(position - origin, axisRotation) * scale, -axisRotation); } /// From 979a5e9f3e5de341bcfe785ca7978f5449e0fd78 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 3 Jul 2024 16:41:41 +0200 Subject: [PATCH 117/924] simplify code --- .../Edit/OsuSelectionScaleHandler.cs | 46 ++++++------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index cfcf90e5f5..2cf5a604ed 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -194,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Edit /// /// Clamp scale for multi-object-scaling where selection does not exceed playfield bounds or flip. - /// + /// The origin from which the scale operation is performed /// The scale to be clamped /// The axes to adjust the scale in. @@ -215,54 +215,34 @@ namespace osu.Game.Rulesets.Osu.Edit Vector2 actualOrigin = origin ?? defaultOrigin.Value; var selectionQuad = OriginalSurroundingQuad.Value; - scale = clampToBound(scale, selectionQuad.BottomRight, OsuPlayfield.BASE_SIZE.X, Axes.X); - scale = clampToBound(scale, selectionQuad.BottomRight, OsuPlayfield.BASE_SIZE.Y, Axes.Y); - scale = clampToBound(scale, selectionQuad.TopLeft, 0, Axes.X); - scale = clampToBound(scale, selectionQuad.TopLeft, 0, Axes.Y); + scale = clampToBound(scale, selectionQuad.BottomRight, OsuPlayfield.BASE_SIZE); + scale = clampToBound(scale, selectionQuad.TopLeft, Vector2.Zero); return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON)); - Vector2 clampToBound(Vector2 s, Vector2 p, float bound, Axes axis) + float minPositiveComponent(Vector2 v) => MathF.Min(v.X < 0 ? float.PositiveInfinity : v.X, v.Y < 0 ? float.PositiveInfinity : v.Y); + + Vector2 clampToBound(Vector2 s, Vector2 p, Vector2 bound) { - float px = p.X - actualOrigin.X; - float py = p.Y - actualOrigin.Y; - float c = axis == Axes.X ? bound - actualOrigin.X : bound - actualOrigin.Y; + p -= actualOrigin; + bound -= actualOrigin; float cos = MathF.Cos(float.DegreesToRadians(-axisRotation)); float sin = MathF.Sin(float.DegreesToRadians(-axisRotation)); - float a, b; - - if (axis == Axes.X) - { - a = cos * cos * px - sin * cos * py; - b = sin * sin * px + sin * cos * py; - } - else - { - a = -sin * cos * px + sin * sin * py; - b = sin * cos * px + cos * cos * py; - } + var a = new Vector2(cos * cos * p.X - sin * cos * p.Y, -sin * cos * p.X + sin * sin * p.Y); + var b = new Vector2(sin * sin * p.X + sin * cos * p.Y, sin * cos * p.X + cos * cos * p.Y); switch (adjustAxis) { case Axes.X: - if (Precision.AlmostEquals(a, 0) || (c - b) / a < 0) - break; - - s.X = MathF.Min(scale.X, (c - b) / a); + s.X = MathF.Min(scale.X, minPositiveComponent(Vector2.Divide(bound - b, a))); break; case Axes.Y: - if (Precision.AlmostEquals(b, 0) || (c - a) / b < 0) - break; - - s.Y = MathF.Min(scale.Y, (c - a) / b); + s.Y = MathF.Min(scale.Y, minPositiveComponent(Vector2.Divide(bound - a, b))); break; case Axes.Both: - if (Precision.AlmostEquals(a + b, 0) || c / (a * s.X + b * s.Y) < 0) - break; - - s = Vector2.ComponentMin(s, s * c / (a * s.X + b * s.Y)); + s = Vector2.ComponentMin(s, s * minPositiveComponent(Vector2.Divide(bound, a * s.X + b * s.Y))); break; } From 0797d942aecde4e267fca11fbec5bf73611fc9b8 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 3 Jul 2024 16:41:57 +0200 Subject: [PATCH 118/924] fix warning --- osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 2cf5a604ed..8b87246456 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -194,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Edit /// /// Clamp scale for multi-object-scaling where selection does not exceed playfield bounds or flip. - /// /// The origin from which the scale operation is performed /// The scale to be clamped /// The axes to adjust the scale in. From 4165ded8134d05f4d6b934255a5678a6a7d74bca Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 3 Jul 2024 19:03:15 +0200 Subject: [PATCH 119/924] fix incorrect rotated bound checking --- .../Edit/OsuSelectionScaleHandler.cs | 53 +++++++++++++++---- osu.Game/Utils/GeometryUtils.cs | 20 ++++--- 2 files changed, 56 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 8b87246456..d336261499 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -80,12 +80,32 @@ namespace osu.Game.Rulesets.Osu.Edit changeHandler?.BeginChange(); objectsInScale = selectedMovableObjects.ToDictionary(ho => ho, ho => new OriginalHitObjectState(ho)); - OriginalSurroundingQuad = objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider - ? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => slider.Position + p.Position)) - : GeometryUtils.GetSurroundingQuad(objectsInScale.Keys); + OriginalSurroundingQuad = getOriginalSurroundingQuad()!; defaultOrigin = OriginalSurroundingQuad.Value.Centre; } + private Quad? getOriginalSurroundingQuad(float axisRotation = 0) + { + if (objectsInScale == null) + return null; + + return objectsInScale.Count == 1 && objectsInScale.First().Value.PathControlPointPositions != null + ? GeometryUtils.GetSurroundingQuad(objectsInScale.First().Value.PathControlPointPositions!.Select(p => objectsInScale.First().Value.Position + p), axisRotation) + : GeometryUtils.GetSurroundingQuad(objectsInScale.Values.SelectMany(s => + { + if (s.EndPosition.HasValue) + { + return new[] + { + s.Position, + s.Position + s.EndPosition.Value + }; + } + + return new[] { s.Position }; + }), axisRotation); + } + public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0) { if (!OperationInProgress.Value) @@ -213,10 +233,23 @@ namespace osu.Game.Rulesets.Osu.Edit scale = clampScaleToAdjustAxis(scale, adjustAxis); Vector2 actualOrigin = origin ?? defaultOrigin.Value; - var selectionQuad = OriginalSurroundingQuad.Value; + var selectionQuad = axisRotation == 0 ? OriginalSurroundingQuad.Value : getOriginalSurroundingQuad(axisRotation)!.Value; + var points = new[] + { + selectionQuad.TopLeft, + selectionQuad.TopRight, + selectionQuad.BottomLeft, + selectionQuad.BottomRight + }; - scale = clampToBound(scale, selectionQuad.BottomRight, OsuPlayfield.BASE_SIZE); - scale = clampToBound(scale, selectionQuad.TopLeft, Vector2.Zero); + float cos = MathF.Cos(float.DegreesToRadians(-axisRotation)); + float sin = MathF.Sin(float.DegreesToRadians(-axisRotation)); + + foreach (var point in points) + { + scale = clampToBound(scale, point, Vector2.Zero); + scale = clampToBound(scale, point, OsuPlayfield.BASE_SIZE); + } return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON)); @@ -226,19 +259,17 @@ namespace osu.Game.Rulesets.Osu.Edit { p -= actualOrigin; bound -= actualOrigin; - float cos = MathF.Cos(float.DegreesToRadians(-axisRotation)); - float sin = MathF.Sin(float.DegreesToRadians(-axisRotation)); var a = new Vector2(cos * cos * p.X - sin * cos * p.Y, -sin * cos * p.X + sin * sin * p.Y); var b = new Vector2(sin * sin * p.X + sin * cos * p.Y, sin * cos * p.X + cos * cos * p.Y); switch (adjustAxis) { case Axes.X: - s.X = MathF.Min(scale.X, minPositiveComponent(Vector2.Divide(bound - b, a))); + s.X = MathF.Min(s.X, minPositiveComponent(Vector2.Divide(bound - b, a))); break; case Axes.Y: - s.Y = MathF.Min(scale.Y, minPositiveComponent(Vector2.Divide(bound - a, b))); + s.Y = MathF.Min(s.Y, minPositiveComponent(Vector2.Divide(bound - a, b))); break; case Axes.Both: @@ -275,12 +306,14 @@ namespace osu.Game.Rulesets.Osu.Edit public Vector2 Position { get; } public Vector2[]? PathControlPointPositions { get; } public PathType?[]? PathControlPointTypes { get; } + public Vector2? EndPosition { get; } public OriginalHitObjectState(OsuHitObject hitObject) { Position = hitObject.Position; PathControlPointPositions = (hitObject as IHasPath)?.Path.ControlPoints.Select(p => p.Position).ToArray(); PathControlPointTypes = (hitObject as IHasPath)?.Path.ControlPoints.Select(p => p.Type).ToArray(); + EndPosition = (hitObject as IHasPath)?.Path.PositionAt(1); } } } diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index f6e7e81007..23c25cfffa 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -113,7 +113,8 @@ namespace osu.Game.Utils /// Returns a quad surrounding the provided points. /// /// The points to calculate a quad for. - public static Quad GetSurroundingQuad(IEnumerable points) + /// The rotation in degrees of the axis to align the quad to. + public static Quad GetSurroundingQuad(IEnumerable points, float axisRotation = 0) { if (!points.Any()) return new Quad(); @@ -124,20 +125,25 @@ namespace osu.Game.Utils // Go through all hitobjects to make sure they would remain in the bounds of the editor after movement, before any movement is attempted foreach (var p in points) { - minPosition = Vector2.ComponentMin(minPosition, p); - maxPosition = Vector2.ComponentMax(maxPosition, p); + var pr = RotateVector(p, axisRotation); + minPosition = Vector2.ComponentMin(minPosition, pr); + maxPosition = Vector2.ComponentMax(maxPosition, pr); } - Vector2 size = maxPosition - minPosition; + var p1 = RotateVector(minPosition, -axisRotation); + var p2 = RotateVector(new Vector2(minPosition.X, maxPosition.Y), -axisRotation); + var p3 = RotateVector(maxPosition, -axisRotation); + var p4 = RotateVector(new Vector2(maxPosition.X, minPosition.Y), -axisRotation); - return new Quad(minPosition.X, minPosition.Y, size.X, size.Y); + return new Quad(p1, p2, p3, p4); } /// /// Returns a gamefield-space quad surrounding the provided hit objects. /// /// The hit objects to calculate a quad for. - public static Quad GetSurroundingQuad(IEnumerable hitObjects) => + /// The rotation in degrees of the axis to align the quad to. + public static Quad GetSurroundingQuad(IEnumerable hitObjects, float axisRotation = 0) => GetSurroundingQuad(hitObjects.SelectMany(h => { if (h is IHasPath path) @@ -151,6 +157,6 @@ namespace osu.Game.Utils } return new[] { h.Position }; - })); + }), axisRotation); } } From dfe6c70996b06fa0e597fffbf1aec75e5b1d508c Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 3 Jul 2024 19:08:31 +0200 Subject: [PATCH 120/924] prevent flipping objects far offscreen --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 7720afe60a..7d6ef66909 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -15,6 +15,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Utils; using osuTK; @@ -119,6 +120,9 @@ namespace osu.Game.Rulesets.Osu.Edit { var flippedPosition = GeometryUtils.GetFlippedPosition(flipAxis, flipQuad, h.Position); + // Clamp the flipped position inside the playfield bounds, because the flipped position might be outside the playfield bounds if the origin is not centered. + flippedPosition = Vector2.Clamp(flippedPosition, Vector2.Zero, OsuPlayfield.BASE_SIZE); + if (!Precision.AlmostEquals(flippedPosition, h.Position)) { h.Position = flippedPosition; From ae380027772867b45baf80acc910c98cb39fac46 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 14 Jul 2024 15:46:40 +0200 Subject: [PATCH 121/924] Revert "fix incorrect rotated bound checking" This reverts commit 4165ded8134d05f4d6b934255a5678a6a7d74bca. --- .../Edit/OsuSelectionScaleHandler.cs | 53 ++++--------------- osu.Game/Utils/GeometryUtils.cs | 20 +++---- 2 files changed, 17 insertions(+), 56 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index d336261499..8b87246456 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -80,32 +80,12 @@ namespace osu.Game.Rulesets.Osu.Edit changeHandler?.BeginChange(); objectsInScale = selectedMovableObjects.ToDictionary(ho => ho, ho => new OriginalHitObjectState(ho)); - OriginalSurroundingQuad = getOriginalSurroundingQuad()!; + OriginalSurroundingQuad = objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider + ? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => slider.Position + p.Position)) + : GeometryUtils.GetSurroundingQuad(objectsInScale.Keys); defaultOrigin = OriginalSurroundingQuad.Value.Centre; } - private Quad? getOriginalSurroundingQuad(float axisRotation = 0) - { - if (objectsInScale == null) - return null; - - return objectsInScale.Count == 1 && objectsInScale.First().Value.PathControlPointPositions != null - ? GeometryUtils.GetSurroundingQuad(objectsInScale.First().Value.PathControlPointPositions!.Select(p => objectsInScale.First().Value.Position + p), axisRotation) - : GeometryUtils.GetSurroundingQuad(objectsInScale.Values.SelectMany(s => - { - if (s.EndPosition.HasValue) - { - return new[] - { - s.Position, - s.Position + s.EndPosition.Value - }; - } - - return new[] { s.Position }; - }), axisRotation); - } - public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0) { if (!OperationInProgress.Value) @@ -233,23 +213,10 @@ namespace osu.Game.Rulesets.Osu.Edit scale = clampScaleToAdjustAxis(scale, adjustAxis); Vector2 actualOrigin = origin ?? defaultOrigin.Value; - var selectionQuad = axisRotation == 0 ? OriginalSurroundingQuad.Value : getOriginalSurroundingQuad(axisRotation)!.Value; - var points = new[] - { - selectionQuad.TopLeft, - selectionQuad.TopRight, - selectionQuad.BottomLeft, - selectionQuad.BottomRight - }; + var selectionQuad = OriginalSurroundingQuad.Value; - float cos = MathF.Cos(float.DegreesToRadians(-axisRotation)); - float sin = MathF.Sin(float.DegreesToRadians(-axisRotation)); - - foreach (var point in points) - { - scale = clampToBound(scale, point, Vector2.Zero); - scale = clampToBound(scale, point, OsuPlayfield.BASE_SIZE); - } + scale = clampToBound(scale, selectionQuad.BottomRight, OsuPlayfield.BASE_SIZE); + scale = clampToBound(scale, selectionQuad.TopLeft, Vector2.Zero); return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON)); @@ -259,17 +226,19 @@ namespace osu.Game.Rulesets.Osu.Edit { p -= actualOrigin; bound -= actualOrigin; + float cos = MathF.Cos(float.DegreesToRadians(-axisRotation)); + float sin = MathF.Sin(float.DegreesToRadians(-axisRotation)); var a = new Vector2(cos * cos * p.X - sin * cos * p.Y, -sin * cos * p.X + sin * sin * p.Y); var b = new Vector2(sin * sin * p.X + sin * cos * p.Y, sin * cos * p.X + cos * cos * p.Y); switch (adjustAxis) { case Axes.X: - s.X = MathF.Min(s.X, minPositiveComponent(Vector2.Divide(bound - b, a))); + s.X = MathF.Min(scale.X, minPositiveComponent(Vector2.Divide(bound - b, a))); break; case Axes.Y: - s.Y = MathF.Min(s.Y, minPositiveComponent(Vector2.Divide(bound - a, b))); + s.Y = MathF.Min(scale.Y, minPositiveComponent(Vector2.Divide(bound - a, b))); break; case Axes.Both: @@ -306,14 +275,12 @@ namespace osu.Game.Rulesets.Osu.Edit public Vector2 Position { get; } public Vector2[]? PathControlPointPositions { get; } public PathType?[]? PathControlPointTypes { get; } - public Vector2? EndPosition { get; } public OriginalHitObjectState(OsuHitObject hitObject) { Position = hitObject.Position; PathControlPointPositions = (hitObject as IHasPath)?.Path.ControlPoints.Select(p => p.Position).ToArray(); PathControlPointTypes = (hitObject as IHasPath)?.Path.ControlPoints.Select(p => p.Type).ToArray(); - EndPosition = (hitObject as IHasPath)?.Path.PositionAt(1); } } } diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index 23c25cfffa..f6e7e81007 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -113,8 +113,7 @@ namespace osu.Game.Utils /// Returns a quad surrounding the provided points. /// /// The points to calculate a quad for. - /// The rotation in degrees of the axis to align the quad to. - public static Quad GetSurroundingQuad(IEnumerable points, float axisRotation = 0) + public static Quad GetSurroundingQuad(IEnumerable points) { if (!points.Any()) return new Quad(); @@ -125,25 +124,20 @@ namespace osu.Game.Utils // Go through all hitobjects to make sure they would remain in the bounds of the editor after movement, before any movement is attempted foreach (var p in points) { - var pr = RotateVector(p, axisRotation); - minPosition = Vector2.ComponentMin(minPosition, pr); - maxPosition = Vector2.ComponentMax(maxPosition, pr); + minPosition = Vector2.ComponentMin(minPosition, p); + maxPosition = Vector2.ComponentMax(maxPosition, p); } - var p1 = RotateVector(minPosition, -axisRotation); - var p2 = RotateVector(new Vector2(minPosition.X, maxPosition.Y), -axisRotation); - var p3 = RotateVector(maxPosition, -axisRotation); - var p4 = RotateVector(new Vector2(maxPosition.X, minPosition.Y), -axisRotation); + Vector2 size = maxPosition - minPosition; - return new Quad(p1, p2, p3, p4); + return new Quad(minPosition.X, minPosition.Y, size.X, size.Y); } /// /// Returns a gamefield-space quad surrounding the provided hit objects. /// /// The hit objects to calculate a quad for. - /// The rotation in degrees of the axis to align the quad to. - public static Quad GetSurroundingQuad(IEnumerable hitObjects, float axisRotation = 0) => + public static Quad GetSurroundingQuad(IEnumerable hitObjects) => GetSurroundingQuad(hitObjects.SelectMany(h => { if (h is IHasPath path) @@ -157,6 +151,6 @@ namespace osu.Game.Utils } return new[] { h.Position }; - }), axisRotation); + })); } } From 58eb7f6fe174ba72f06304d0334b0668dde14c74 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 14 Jul 2024 16:58:05 +0200 Subject: [PATCH 122/924] fix rotated scale bounds again --- .../Edit/OsuSelectionScaleHandler.cs | 31 ++++++++++-- osu.Game/Utils/GeometryUtils.cs | 49 ++++++++++++++++++- 2 files changed, 73 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 8b87246456..56c3ba9315 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -69,6 +69,7 @@ namespace osu.Game.Rulesets.Osu.Edit private Dictionary? objectsInScale; private Vector2? defaultOrigin; + private List? originalConvexHull; public override void Begin() { @@ -84,6 +85,9 @@ namespace osu.Game.Rulesets.Osu.Edit ? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => slider.Position + p.Position)) : GeometryUtils.GetSurroundingQuad(objectsInScale.Keys); defaultOrigin = OriginalSurroundingQuad.Value.Centre; + originalConvexHull = objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider2 + ? GeometryUtils.GetConvexHull(slider2.Path.ControlPoints.Select(p => slider2.Position + p.Position)) + : GeometryUtils.GetConvexHull(objectsInScale.Keys); } public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0) @@ -211,12 +215,31 @@ namespace osu.Game.Rulesets.Osu.Edit if (objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider) origin = slider.Position; + float cos = MathF.Cos(float.DegreesToRadians(-axisRotation)); + float sin = MathF.Sin(float.DegreesToRadians(-axisRotation)); scale = clampScaleToAdjustAxis(scale, adjustAxis); Vector2 actualOrigin = origin ?? defaultOrigin.Value; - var selectionQuad = OriginalSurroundingQuad.Value; + IEnumerable points; - scale = clampToBound(scale, selectionQuad.BottomRight, OsuPlayfield.BASE_SIZE); - scale = clampToBound(scale, selectionQuad.TopLeft, Vector2.Zero); + if (axisRotation == 0) + { + var selectionQuad = OriginalSurroundingQuad.Value; + points = new[] + { + selectionQuad.TopLeft, + selectionQuad.TopRight, + selectionQuad.BottomLeft, + selectionQuad.BottomRight + }; + } + else + points = originalConvexHull!; + + foreach (var point in points) + { + scale = clampToBound(scale, point, Vector2.Zero); + scale = clampToBound(scale, point, OsuPlayfield.BASE_SIZE); + } return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON)); @@ -226,8 +249,6 @@ namespace osu.Game.Rulesets.Osu.Edit { p -= actualOrigin; bound -= actualOrigin; - float cos = MathF.Cos(float.DegreesToRadians(-axisRotation)); - float sin = MathF.Sin(float.DegreesToRadians(-axisRotation)); var a = new Vector2(cos * cos * p.X - sin * cos * p.Y, -sin * cos * p.X + sin * sin * p.Y); var b = new Vector2(sin * sin * p.X + sin * cos * p.Y, sin * cos * p.X + cos * cos * p.Y); diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index f6e7e81007..5a8ca9722e 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -138,7 +138,52 @@ namespace osu.Game.Utils /// /// The hit objects to calculate a quad for. public static Quad GetSurroundingQuad(IEnumerable hitObjects) => - GetSurroundingQuad(hitObjects.SelectMany(h => + GetSurroundingQuad(enumerateStartAndEndPositions(hitObjects)); + + /// + /// Returns the points that make up the convex hull of the provided points. + /// + /// The points to calculate a convex hull. + public static List GetConvexHull(IEnumerable points) + { + List p = points.ToList(); + + if (p.Count <= 1) + return p; + + int n = p.Count, k = 0; + List hull = new List(new Vector2[2 * n]); + + p.Sort((a, b) => a.X == b.X ? a.Y.CompareTo(b.Y) : a.X.CompareTo(b.X)); + + // Build lower hull + for (int i = 0; i < n; ++i) + { + while (k >= 2 && cross(hull[k - 2], hull[k - 1], p[i]) <= 0) + k--; + hull[k] = p[i]; + k++; + } + + // Build upper hull + for (int i = n - 2, t = k + 1; i >= 0; i--) + { + while (k >= t && cross(hull[k - 2], hull[k - 1], p[i]) <= 0) + k--; + hull[k] = p[i]; + k++; + } + + return hull.Take(k - 1).ToList(); + + float cross(Vector2 o, Vector2 a, Vector2 b) => (a.X - o.X) * (b.Y - o.Y) - (a.Y - o.Y) * (b.X - o.X); + } + + public static List GetConvexHull(IEnumerable hitObjects) => + GetConvexHull(enumerateStartAndEndPositions(hitObjects)); + + private static IEnumerable enumerateStartAndEndPositions(IEnumerable hitObjects) => + hitObjects.SelectMany(h => { if (h is IHasPath path) { @@ -151,6 +196,6 @@ namespace osu.Game.Utils } return new[] { h.Position }; - })); + }); } } From 7a319a6d74ee29fbf3e7b5dbc2b7c6c9ca8e4990 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 14 Jul 2024 17:03:17 +0200 Subject: [PATCH 123/924] dont rotate scale when in selection origin mode --- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index a1907a2fd5..0f04efcfa5 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -131,7 +131,7 @@ namespace osu.Game.Rulesets.Osu.Edit scaleInfo.BindValueChanged(scale => { var newScale = new Vector2(scale.NewValue.Scale, scale.NewValue.Scale); - scaleHandler.Update(newScale, getOriginPosition(scale.NewValue), getAdjustAxis(scale.NewValue), gridToolbox.GridLinesRotation.Value); + scaleHandler.Update(newScale, getOriginPosition(scale.NewValue), getAdjustAxis(scale.NewValue), getRotation(scale.NewValue)); }); } @@ -164,7 +164,7 @@ namespace osu.Game.Rulesets.Osu.Edit return; const float max_scale = 10; - var scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(max_scale), getOriginPosition(scaleInfo.Value), getAdjustAxis(scaleInfo.Value), gridToolbox.GridLinesRotation.Value); + var scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(max_scale), getOriginPosition(scaleInfo.Value), getAdjustAxis(scaleInfo.Value), getRotation(scaleInfo.Value)); if (!scaleInfo.Value.XAxis) scale.X = max_scale; @@ -185,6 +185,8 @@ namespace osu.Game.Rulesets.Osu.Edit private Axes getAdjustAxis(PreciseScaleInfo scale) => scale.XAxis ? scale.YAxis ? Axes.Both : Axes.X : Axes.Y; + private float getRotation(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? gridToolbox.GridLinesRotation.Value : 0; + private void setAxis(bool x, bool y) { scaleInfo.Value = scaleInfo.Value with { XAxis = x, YAxis = y }; From 9e5d099b1b1113304294c17155f42ab4a8ea76cd Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 14 Jul 2024 17:13:22 +0200 Subject: [PATCH 124/924] rename playfield centre origin to grid centre --- .../Edit/PreciseRotationPopover.cs | 12 ++++++------ .../Edit/PreciseScalePopover.cs | 16 ++++++++-------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs index 6a3e326c2b..4a1ccc4b61 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Edit private readonly OsuGridToolboxGroup gridToolbox; - private readonly Bindable rotationInfo = new Bindable(new PreciseRotationInfo(0, RotationOrigin.PlayfieldCentre)); + private readonly Bindable rotationInfo = new Bindable(new PreciseRotationInfo(0, RotationOrigin.GridCentre)); private SliderWithTextBoxInput angleInput = null!; private EditorRadioButtonCollection rotationOrigin = null!; @@ -60,9 +60,9 @@ namespace osu.Game.Rulesets.Osu.Edit RelativeSizeAxes = Axes.X, Items = new[] { - new RadioButton("Playfield centre", - () => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.PlayfieldCentre }, - () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), + new RadioButton("Grid centre", + () => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.GridCentre }, + () => new SpriteIcon { Icon = FontAwesome.Regular.PlusSquare }), selectionCentreButton = new RadioButton("Selection centre", () => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.SelectionCentre }, () => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare }) @@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Osu.Edit rotationInfo.BindValueChanged(rotation => { - rotationHandler.Update(rotation.NewValue.Degrees, rotation.NewValue.Origin == RotationOrigin.PlayfieldCentre ? gridToolbox.StartPosition.Value : null); + rotationHandler.Update(rotation.NewValue.Degrees, rotation.NewValue.Origin == RotationOrigin.GridCentre ? gridToolbox.StartPosition.Value : null); }); } @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Osu.Edit public enum RotationOrigin { - PlayfieldCentre, + GridCentre, SelectionCentre } diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index 0f04efcfa5..15ed4c59c3 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Edit private readonly OsuGridToolboxGroup gridToolbox; - private readonly Bindable scaleInfo = new Bindable(new PreciseScaleInfo(1, ScaleOrigin.PlayfieldCentre, true, true)); + private readonly Bindable scaleInfo = new Bindable(new PreciseScaleInfo(1, ScaleOrigin.GridCentre, true, true)); private SliderWithTextBoxInput scaleInput = null!; private BindableNumber scaleInputBindable = null!; @@ -68,9 +68,9 @@ namespace osu.Game.Rulesets.Osu.Edit RelativeSizeAxes = Axes.X, Items = new[] { - playfieldCentreButton = new RadioButton("Playfield centre", - () => setOrigin(ScaleOrigin.PlayfieldCentre), - () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), + playfieldCentreButton = new RadioButton("Grid centre", + () => setOrigin(ScaleOrigin.GridCentre), + () => new SpriteIcon { Icon = FontAwesome.Regular.PlusSquare }), selectionCentreButton = new RadioButton("Selection centre", () => setOrigin(ScaleOrigin.SelectionCentre), () => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare }) @@ -137,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Edit private void updateAxisCheckBoxesEnabled() { - if (scaleInfo.Value.Origin == ScaleOrigin.PlayfieldCentre) + if (scaleInfo.Value.Origin == ScaleOrigin.GridCentre) { toggleAxisAvailable(xCheckBox.Current, true); toggleAxisAvailable(yCheckBox.Current, true); @@ -181,11 +181,11 @@ namespace osu.Game.Rulesets.Osu.Edit updateAxisCheckBoxesEnabled(); } - private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? gridToolbox.StartPosition.Value : null; + private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.GridCentre ? gridToolbox.StartPosition.Value : null; private Axes getAdjustAxis(PreciseScaleInfo scale) => scale.XAxis ? scale.YAxis ? Axes.Both : Axes.X : Axes.Y; - private float getRotation(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? gridToolbox.GridLinesRotation.Value : 0; + private float getRotation(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.GridCentre ? gridToolbox.GridLinesRotation.Value : 0; private void setAxis(bool x, bool y) { @@ -210,7 +210,7 @@ namespace osu.Game.Rulesets.Osu.Edit public enum ScaleOrigin { - PlayfieldCentre, + GridCentre, SelectionCentre } From a80e3337860773790c88010a7592219984a31570 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 14 Jul 2024 17:27:04 +0200 Subject: [PATCH 125/924] add playfield origin as third origin option --- .../Edit/PreciseRotationPopover.cs | 17 ++++++++++++- .../Edit/PreciseScalePopover.cs | 24 ++++++++++++++++--- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs index 4a1ccc4b61..352debf500 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -8,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit.Components.RadioButtons; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -63,6 +65,9 @@ namespace osu.Game.Rulesets.Osu.Edit new RadioButton("Grid centre", () => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.GridCentre }, () => new SpriteIcon { Icon = FontAwesome.Regular.PlusSquare }), + new RadioButton("Playfield centre", + () => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.PlayfieldCentre }, + () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), selectionCentreButton = new RadioButton("Selection centre", () => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.SelectionCentre }, () => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare }) @@ -95,10 +100,19 @@ namespace osu.Game.Rulesets.Osu.Edit rotationInfo.BindValueChanged(rotation => { - rotationHandler.Update(rotation.NewValue.Degrees, rotation.NewValue.Origin == RotationOrigin.GridCentre ? gridToolbox.StartPosition.Value : null); + rotationHandler.Update(rotation.NewValue.Degrees, getOriginPosition(rotation.NewValue)); }); } + private Vector2? getOriginPosition(PreciseRotationInfo rotation) => + rotation.Origin switch + { + RotationOrigin.GridCentre => gridToolbox.StartPosition.Value, + RotationOrigin.PlayfieldCentre => OsuPlayfield.BASE_SIZE / 2, + RotationOrigin.SelectionCentre => null, + _ => throw new ArgumentOutOfRangeException(nameof(rotation)) + }; + protected override void PopIn() { base.PopIn(); @@ -117,6 +131,7 @@ namespace osu.Game.Rulesets.Osu.Edit public enum RotationOrigin { GridCentre, + PlayfieldCentre, SelectionCentre } diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index 15ed4c59c3..dff370d259 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit.Components.RadioButtons; using osuTK; @@ -27,6 +28,7 @@ namespace osu.Game.Rulesets.Osu.Edit private BindableNumber scaleInputBindable = null!; private EditorRadioButtonCollection scaleOrigin = null!; + private RadioButton gridCentreButton = null!; private RadioButton playfieldCentreButton = null!; private RadioButton selectionCentreButton = null!; @@ -68,9 +70,12 @@ namespace osu.Game.Rulesets.Osu.Edit RelativeSizeAxes = Axes.X, Items = new[] { - playfieldCentreButton = new RadioButton("Grid centre", + gridCentreButton = new RadioButton("Grid centre", () => setOrigin(ScaleOrigin.GridCentre), () => new SpriteIcon { Icon = FontAwesome.Regular.PlusSquare }), + playfieldCentreButton = new RadioButton("Playfield centre", + () => setOrigin(ScaleOrigin.PlayfieldCentre), + () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), selectionCentreButton = new RadioButton("Selection centre", () => setOrigin(ScaleOrigin.SelectionCentre), () => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare }) @@ -99,6 +104,10 @@ namespace osu.Game.Rulesets.Osu.Edit }, } }; + gridCentreButton.Selected.DisabledChanged += isDisabled => + { + gridCentreButton.TooltipText = isDisabled ? "The current selection cannot be scaled relative to grid centre." : string.Empty; + }; playfieldCentreButton.Selected.DisabledChanged += isDisabled => { playfieldCentreButton.TooltipText = isDisabled ? "The current selection cannot be scaled relative to playfield centre." : string.Empty; @@ -125,6 +134,7 @@ namespace osu.Game.Rulesets.Osu.Edit selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleX.Value || scaleHandler.CanScaleY.Value); playfieldCentreButton.Selected.Disabled = scaleHandler.IsScalingSlider.Value && !selectionCentreButton.Selected.Disabled; + gridCentreButton.Selected.Disabled = playfieldCentreButton.Selected.Disabled; scaleOrigin.Items.First(b => !b.Selected.Disabled).Select(); @@ -137,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.Edit private void updateAxisCheckBoxesEnabled() { - if (scaleInfo.Value.Origin == ScaleOrigin.GridCentre) + if (scaleInfo.Value.Origin != ScaleOrigin.SelectionCentre) { toggleAxisAvailable(xCheckBox.Current, true); toggleAxisAvailable(yCheckBox.Current, true); @@ -181,7 +191,14 @@ namespace osu.Game.Rulesets.Osu.Edit updateAxisCheckBoxesEnabled(); } - private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.GridCentre ? gridToolbox.StartPosition.Value : null; + private Vector2? getOriginPosition(PreciseScaleInfo scale) => + scale.Origin switch + { + ScaleOrigin.GridCentre => gridToolbox.StartPosition.Value, + ScaleOrigin.PlayfieldCentre => OsuPlayfield.BASE_SIZE / 2, + ScaleOrigin.SelectionCentre => null, + _ => throw new ArgumentOutOfRangeException(nameof(scale)) + }; private Axes getAdjustAxis(PreciseScaleInfo scale) => scale.XAxis ? scale.YAxis ? Axes.Both : Axes.X : Axes.Y; @@ -211,6 +228,7 @@ namespace osu.Game.Rulesets.Osu.Edit public enum ScaleOrigin { GridCentre, + PlayfieldCentre, SelectionCentre } From 2bbaa8e43ccce0a1bf42f2c5790ad469a81a195e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 14 Jul 2024 18:12:55 +0200 Subject: [PATCH 126/924] make flips grid-type aware --- .../Edit/OsuSelectionHandler.cs | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 7d6ef66909..1334dbdbec 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -107,10 +107,28 @@ namespace osu.Game.Rulesets.Osu.Edit // If we're flipping over the origin, we take the grid origin position from the grid toolbox. var flipQuad = flipOverOrigin ? new Quad(gridToolbox.StartPositionX.Value, gridToolbox.StartPositionY.Value, 0, 0) : GeometryUtils.GetSurroundingQuad(hitObjects); - // If we're flipping over the origin, we take the grid rotation from the grid toolbox. - // We want to normalize the rotation angle to -45 to 45 degrees, so horizontal vs vertical flip is not mixed up by the rotation and it stays intuitive to use. - var flipAxis = flipOverOrigin ? GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 405) % 90 - 45)) : Vector2.UnitX; - flipAxis = direction == Direction.Vertical ? flipAxis.PerpendicularLeft : flipAxis; + Vector2 flipAxis = direction == Direction.Vertical ? Vector2.UnitY : Vector2.UnitX; + + if (flipOverOrigin) + { + // If we're flipping over the origin, we take one of the axes of the grid. + // Take the axis closest to the direction we want to flip over. + switch (gridToolbox.GridType.Value) + { + case PositionSnapGridType.Square: + flipAxis = GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 405) % 90 - 45)); + flipAxis = direction == Direction.Vertical ? flipAxis.PerpendicularLeft : flipAxis; + break; + + case PositionSnapGridType.Triangle: + // Hex grid has 3 axes, so you can not directly flip over one of the axes, + // however it's still possible to achieve that flip by combining multiple flips over the other axes. + flipAxis = direction == Direction.Vertical + ? GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 390) % 60 + 60)) + : GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 390) % 60 - 60)); + break; + } + } var controlPointFlipQuad = new Quad(); From 952540024eeb41c039d5a1072d7b1d520ff2cab3 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 14 Jul 2024 20:43:16 +0200 Subject: [PATCH 127/924] addition bank toggles in compose --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 24 +++- .../Components/ComposeBlueprintContainer.cs | 9 +- .../Components/EditorSelectionHandler.cs | 131 +++++++++++++++++- 3 files changed, 149 insertions(+), 15 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 3c38a7258e..ac9c830f03 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -85,6 +85,7 @@ namespace osu.Game.Rulesets.Edit private EditorRadioButtonCollection toolboxCollection; private FillFlowContainer togglesCollection; private FillFlowContainer sampleBankTogglesCollection; + private FillFlowContainer sampleAdditionBankTogglesCollection; private IBindable hasTiming; private Bindable autoSeekOnPlacement; @@ -184,7 +185,17 @@ namespace osu.Game.Rulesets.Edit Direction = FillDirection.Vertical, Spacing = new Vector2(0, 5), }, - } + }, + new EditorToolboxGroup("additions (Alt-Q~R)") + { + Child = sampleAdditionBankTogglesCollection = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + }, + }, } }, } @@ -230,6 +241,7 @@ namespace osu.Game.Rulesets.Edit togglesCollection.AddRange(TernaryStates.Select(b => new DrawableTernaryButton(b))); sampleBankTogglesCollection.AddRange(BlueprintContainer.SampleBankTernaryStates.Select(b => new DrawableTernaryButton(b))); + sampleAdditionBankTogglesCollection.AddRange(BlueprintContainer.SampleAdditionBankTernaryStates.Select(b => new DrawableTernaryButton(b))); setSelectTool(); @@ -358,7 +370,7 @@ namespace osu.Game.Rulesets.Edit protected override bool OnKeyDown(KeyDownEvent e) { - if (e.ControlPressed || e.AltPressed || e.SuperPressed) + if (e.ControlPressed || e.SuperPressed) return false; if (checkLeftToggleFromKey(e.Key, out int leftIndex)) @@ -375,9 +387,11 @@ namespace osu.Game.Rulesets.Edit if (checkRightToggleFromKey(e.Key, out int rightIndex)) { - var item = e.ShiftPressed - ? sampleBankTogglesCollection.ElementAtOrDefault(rightIndex) - : togglesCollection.ElementAtOrDefault(rightIndex); + var item = e.AltPressed + ? sampleAdditionBankTogglesCollection.ElementAtOrDefault(rightIndex) + : e.ShiftPressed + ? sampleBankTogglesCollection.ElementAtOrDefault(rightIndex) + : togglesCollection.ElementAtOrDefault(rightIndex); if (item is DrawableTernaryButton button) { diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index fc8bce4c96..671180348d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -62,7 +62,8 @@ namespace osu.Game.Screens.Edit.Compose.Components private void load() { MainTernaryStates = CreateTernaryButtons().ToArray(); - SampleBankTernaryStates = createSampleBankTernaryButtons().ToArray(); + SampleBankTernaryStates = createSampleBankTernaryButtons(SelectionHandler.SelectionBankStates).ToArray(); + SampleAdditionBankTernaryStates = createSampleBankTernaryButtons(SelectionHandler.SelectionAdditionBankStates).ToArray(); AddInternal(new DrawableRulesetDependenciesProvidingContainer(Composer.Ruleset) { @@ -219,6 +220,8 @@ namespace osu.Game.Screens.Edit.Compose.Components public TernaryButton[] SampleBankTernaryStates { get; private set; } + public TernaryButton[] SampleAdditionBankTernaryStates { get; private set; } + /// /// Create all ternary states required to be displayed to the user. /// @@ -231,9 +234,9 @@ namespace osu.Game.Screens.Edit.Compose.Components yield return new TernaryButton(kvp.Value, kvp.Key.Replace("hit", string.Empty).Titleize(), () => GetIconForSample(kvp.Key)); } - private IEnumerable createSampleBankTernaryButtons() + private IEnumerable createSampleBankTernaryButtons(Dictionary> sampleBankStates) { - foreach (var kvp in SelectionHandler.SelectionBankStates) + foreach (var kvp in sampleBankStates) yield return new TernaryButton(kvp.Value, kvp.Key.Titleize(), () => getIconForBank(kvp.Key)); } diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index a4efe66bf8..04baaa6134 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -58,6 +58,11 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public readonly Dictionary> SelectionBankStates = new Dictionary>(); + /// + /// The state of each sample addition bank type for all selected hitobjects. + /// + public readonly Dictionary> SelectionAdditionBankStates = new Dictionary>(); + /// /// Set up ternary state bindables and bind them to selection/hitobject changes (in both directions) /// @@ -90,7 +95,7 @@ namespace osu.Game.Screens.Edit.Compose.Components // Never remove a sample bank. // These are basically radio buttons, not toggles. - if (SelectedItems.All(h => h.Samples.All(s => s.Bank == bankName))) + if (SelectedItems.All(h => h.Samples.Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName))) bindable.Value = TernaryState.True; } @@ -127,8 +132,70 @@ namespace osu.Game.Screens.Edit.Compose.Components SelectionBankStates[bankName] = bindable; } + foreach (string bankName in HitSampleInfo.AllBanks.Prepend(HIT_BANK_AUTO)) + { + var bindable = new Bindable + { + Description = bankName.Titleize() + }; + + bindable.ValueChanged += state => + { + switch (state.NewValue) + { + case TernaryState.False: + if (SelectedItems.Count == 0) + { + // Ensure that if this is the last selected bank, it should remain selected. + if (SelectionAdditionBankStates.Values.All(b => b.Value == TernaryState.False)) + bindable.Value = TernaryState.True; + } + + // Auto should never apply when there is a selection made. + // Completely empty selections should be allowed in the case that none of the selected objects have any addition samples. + break; + + case TernaryState.True: + if (SelectedItems.Count == 0) + { + // Ensure the user can't stack multiple bank selections when there's no hitobject selection. + // Note that in normal scenarios this is sorted out by the feedback from applying the bank to the selected objects. + foreach (var other in SelectionAdditionBankStates.Values) + { + if (other != bindable) + other.Value = TernaryState.False; + } + } + else + { + // Auto should just not apply if there's a selection already made. + // Maybe we could make it a disabled button in the future, but right now the editor buttons don't support disabled state. + if (bankName == HIT_BANK_AUTO) + { + bindable.Value = TernaryState.False; + break; + } + + // If none of the selected objects have any addition samples, we should not apply the bank. + if (SelectedItems.All(h => h.Samples.All(o => o.Name == HitSampleInfo.HIT_NORMAL))) + { + bindable.Value = TernaryState.False; + break; + } + + SetSampleAdditionBank(bankName); + } + + break; + } + }; + + SelectionAdditionBankStates[bankName] = bindable; + } + // start with normal selected. SelectionBankStates[SampleControlPoint.DEFAULT_BANK].Value = TernaryState.True; + SelectionAdditionBankStates[SampleControlPoint.DEFAULT_BANK].Value = TernaryState.True; foreach (string sampleName in HitSampleInfo.AllAdditions) { @@ -186,7 +253,12 @@ namespace osu.Game.Screens.Edit.Compose.Components foreach ((string bankName, var bindable) in SelectionBankStates) { - bindable.Value = GetStateFromSelection(samplesInSelection.SelectMany(s => s), h => h.Bank == bankName); + bindable.Value = GetStateFromSelection(samplesInSelection.SelectMany(s => s).Where(o => o.Name == HitSampleInfo.HIT_NORMAL), h => h.Bank == bankName); + } + + foreach ((string bankName, var bindable) in SelectionAdditionBankStates) + { + bindable.Value = GetStateFromSelection(samplesInSelection.SelectMany(s => s).Where(o => o.Name != HitSampleInfo.HIT_NORMAL), h => h.Bank == bankName); } IEnumerable> enumerateAllSamples(HitObject hitObject) @@ -213,12 +285,12 @@ namespace osu.Game.Screens.Edit.Compose.Components { bool hasRelevantBank(HitObject hitObject) { - bool result = hitObject.Samples.All(s => s.Bank == bankName); + bool result = hitObject.Samples.Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName); if (hitObject is IHasRepeats hasRepeats) { foreach (var node in hasRepeats.NodeSamples) - result &= node.All(s => s.Bank == bankName); + result &= node.Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName); } return result; @@ -229,15 +301,54 @@ namespace osu.Game.Screens.Edit.Compose.Components EditorBeatmap.PerformOnSelection(h => { - if (h.Samples.All(s => s.Bank == bankName)) + if (h.Samples.Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName)) return; - h.Samples = h.Samples.Select(s => s.With(newBank: bankName)).ToList(); + h.Samples = h.Samples.Select(s => s.Name == HitSampleInfo.HIT_NORMAL ? s.With(newBank: bankName) : s).ToList(); if (h is IHasRepeats hasRepeats) { for (int i = 0; i < hasRepeats.NodeSamples.Count; ++i) - hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(s => s.With(newBank: bankName)).ToList(); + hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(s => s.Name == HitSampleInfo.HIT_NORMAL ? s.With(newBank: bankName) : s).ToList(); + } + + EditorBeatmap.Update(h); + }); + } + + /// + /// Sets the sample addition bank for all selected s. + /// + /// The name of the sample bank. + public void SetSampleAdditionBank(string bankName) + { + bool hasRelevantBank(HitObject hitObject) + { + bool result = hitObject.Samples.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName); + + if (hitObject is IHasRepeats hasRepeats) + { + foreach (var node in hasRepeats.NodeSamples) + result &= node.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName); + } + + return result; + } + + if (SelectedItems.All(hasRelevantBank)) + return; + + EditorBeatmap.PerformOnSelection(h => + { + if (h.Samples.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName)) + return; + + h.Samples = h.Samples.Select(s => s.Name != HitSampleInfo.HIT_NORMAL ? s.With(newBank: bankName) : s).ToList(); + + if (h is IHasRepeats hasRepeats) + { + for (int i = 0; i < hasRepeats.NodeSamples.Count; ++i) + hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(s => s.Name != HitSampleInfo.HIT_NORMAL ? s.With(newBank: bankName) : s).ToList(); } EditorBeatmap.Update(h); @@ -366,6 +477,12 @@ namespace osu.Game.Screens.Edit.Compose.Components Items = SelectionBankStates.Select(kvp => new TernaryStateToggleMenuItem(kvp.Value.Description) { State = { BindTarget = kvp.Value } }).ToArray() }; + + yield return new OsuMenuItem("Addition bank") + { + Items = SelectionAdditionBankStates.Select(kvp => + new TernaryStateToggleMenuItem(kvp.Value.Description) { State = { BindTarget = kvp.Value } }).ToArray() + }; } #endregion From 5e86a01e5e3e91404d5021572cd8170661d59618 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 14 Jul 2024 20:59:46 +0200 Subject: [PATCH 128/924] allow hotkeys in sample point piece --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 28 +++++++++++++------ .../Components/Timeline/SamplePointPiece.cs | 19 +++++++++---- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index ac9c830f03..f2713739b9 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -373,6 +373,8 @@ namespace osu.Game.Rulesets.Edit if (e.ControlPressed || e.SuperPressed) return false; + bool handled = false; + if (checkLeftToggleFromKey(e.Key, out int leftIndex)) { var item = toolboxCollection.Items.ElementAtOrDefault(leftIndex); @@ -387,20 +389,30 @@ namespace osu.Game.Rulesets.Edit if (checkRightToggleFromKey(e.Key, out int rightIndex)) { - var item = e.AltPressed - ? sampleAdditionBankTogglesCollection.ElementAtOrDefault(rightIndex) - : e.ShiftPressed - ? sampleBankTogglesCollection.ElementAtOrDefault(rightIndex) - : togglesCollection.ElementAtOrDefault(rightIndex); + if (e.ShiftPressed || e.AltPressed) + { + if (e.ShiftPressed) + attemptToggle(rightIndex, sampleBankTogglesCollection); + + if (e.AltPressed) + attemptToggle(rightIndex, sampleAdditionBankTogglesCollection); + } + else + attemptToggle(rightIndex, togglesCollection); + } + + return handled || base.OnKeyDown(e); + + void attemptToggle(int index, FillFlowContainer collection) + { + var item = collection.ElementAtOrDefault(index); if (item is DrawableTernaryButton button) { button.Button.Toggle(); - return true; + handled = true; } } - - return base.OnKeyDown(e); } private bool checkLeftToggleFromKey(Key key, out int index) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 8c7603021a..07c6cab7a1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -381,20 +381,27 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override bool OnKeyDown(KeyDownEvent e) { - if (e.ControlPressed || e.AltPressed || e.SuperPressed || !checkRightToggleFromKey(e.Key, out int rightIndex)) + if (e.ControlPressed || e.SuperPressed || !checkRightToggleFromKey(e.Key, out int rightIndex)) return base.OnKeyDown(e); - if (e.ShiftPressed) + if (e.ShiftPressed || e.AltPressed) { string? newBank = banks.ElementAtOrDefault(rightIndex); if (string.IsNullOrEmpty(newBank)) return true; - setBank(newBank); - updatePrimaryBankState(); - setAdditionBank(newBank); - updateAdditionBankState(); + if (e.ShiftPressed) + { + setBank(newBank); + updatePrimaryBankState(); + } + + if (e.AltPressed) + { + setAdditionBank(newBank); + updateAdditionBankState(); + } } else { From df7a42a00e95ceae944114fe162f266a5fc7708d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 14 Jul 2024 22:33:23 +0200 Subject: [PATCH 129/924] fix placement samples --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 37 +++++++++++-------- .../Components/ComposeBlueprintContainer.cs | 18 ++++++++- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 2817e26abd..26b18828d3 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -37,6 +37,11 @@ namespace osu.Game.Rulesets.Edit /// public bool AutomaticBankAssignment { get; set; } + /// + /// Whether the sample addition bank should be taken from the previous hit objects. + /// + public bool AutomaticAdditionBankAssignment { get; set; } + /// /// The that is being placed. /// @@ -157,26 +162,26 @@ namespace osu.Game.Rulesets.Edit } var lastHitObject = getPreviousHitObject(); + var lastHitNormal = lastHitObject?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL); - if (AutomaticBankAssignment) + if (AutomaticAdditionBankAssignment) { - // Create samples based on the sample settings of the previous hit object - if (lastHitObject != null) - { - for (int i = 0; i < HitObject.Samples.Count; i++) - HitObject.Samples[i] = lastHitObject.CreateHitSampleInfo(HitObject.Samples[i].Name); - } + // Inherit the addition bank from the previous hit object + // If there is no previous addition, inherit from the normal sample + var lastAddition = lastHitObject?.Samples?.FirstOrDefault(o => o.Name != HitSampleInfo.HIT_NORMAL) ?? lastHitNormal; + + if (lastAddition != null) + HitObject.Samples = HitObject.Samples.Select(s => s.Name != HitSampleInfo.HIT_NORMAL ? s.With(newBank: lastAddition.Bank) : s).ToList(); } - else - { - var lastHitNormal = lastHitObject?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL); - if (lastHitNormal != null) - { - // Only inherit the volume from the previous hit object - for (int i = 0; i < HitObject.Samples.Count; i++) - HitObject.Samples[i] = HitObject.Samples[i].With(newVolume: lastHitNormal.Volume); - } + if (lastHitNormal != null) + { + if (AutomaticBankAssignment) + // Inherit the bank from the previous hit object + HitObject.Samples = HitObject.Samples.Select(s => s.Name == HitSampleInfo.HIT_NORMAL ? s.With(newBank: lastHitNormal.Bank) : s).ToList(); + + // Inherit the volume from the previous hit object + HitObject.Samples = HitObject.Samples.Select(s => s.With(newVolume: lastHitNormal.Volume)).ToList(); } if (HitObject is IHasRepeats hasRepeats) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 671180348d..fc81bc5706 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -89,6 +89,9 @@ namespace osu.Game.Screens.Edit.Compose.Components foreach (var kvp in SelectionHandler.SelectionBankStates) kvp.Value.BindValueChanged(_ => updatePlacementSamples()); + + foreach (var kvp in SelectionHandler.SelectionAdditionBankStates) + kvp.Value.BindValueChanged(_ => updatePlacementSamples()); } protected override void TransferBlueprintFor(HitObject hitObject, DrawableHitObject drawableObject) @@ -177,6 +180,9 @@ namespace osu.Game.Screens.Edit.Compose.Components foreach (var kvp in SelectionHandler.SelectionBankStates) bankChanged(kvp.Key, kvp.Value.Value); + + foreach (var kvp in SelectionHandler.SelectionAdditionBankStates) + additionBankChanged(kvp.Key, kvp.Value.Value); } private void sampleChanged(string sampleName, TernaryState state) @@ -208,7 +214,17 @@ namespace osu.Game.Screens.Edit.Compose.Components if (bankName == EditorSelectionHandler.HIT_BANK_AUTO) CurrentPlacement.AutomaticBankAssignment = state == TernaryState.True; else if (state == TernaryState.True) - CurrentPlacement.HitObject.Samples = CurrentPlacement.HitObject.Samples.Select(s => s.With(newBank: bankName)).ToList(); + CurrentPlacement.HitObject.Samples = CurrentPlacement.HitObject.Samples.Select(s => s.Name == HitSampleInfo.HIT_NORMAL ? s.With(newBank: bankName) : s).ToList(); + } + + private void additionBankChanged(string bankName, TernaryState state) + { + if (CurrentPlacement == null) return; + + if (bankName == EditorSelectionHandler.HIT_BANK_AUTO) + CurrentPlacement.AutomaticAdditionBankAssignment = state == TernaryState.True; + else if (state == TernaryState.True) + CurrentPlacement.HitObject.Samples = CurrentPlacement.HitObject.Samples.Select(s => s.Name != HitSampleInfo.HIT_NORMAL ? s.With(newBank: bankName) : s).ToList(); } public readonly Bindable NewCombo = new Bindable { Description = "New Combo" }; From 9f02a1f1bf248a3ea395f63e6da6eb4ce19ef639 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 14 Jul 2024 22:50:15 +0200 Subject: [PATCH 130/924] fix addition on node sample not working --- .../Components/EditorSelectionHandler.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index 04baaa6134..3a0428e7fc 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -176,8 +176,8 @@ namespace osu.Game.Screens.Edit.Compose.Components break; } - // If none of the selected objects have any addition samples, we should not apply the bank. - if (SelectedItems.All(h => h.Samples.All(o => o.Name == HitSampleInfo.HIT_NORMAL))) + // If none of the selected objects have any addition samples, we should not apply the addition bank. + if (SelectedItems.SelectMany(enumerateAllSamples).All(h => h.All(o => o.Name == HitSampleInfo.HIT_NORMAL))) { bindable.Value = TernaryState.False; break; @@ -260,16 +260,16 @@ namespace osu.Game.Screens.Edit.Compose.Components { bindable.Value = GetStateFromSelection(samplesInSelection.SelectMany(s => s).Where(o => o.Name != HitSampleInfo.HIT_NORMAL), h => h.Bank == bankName); } + } - IEnumerable> enumerateAllSamples(HitObject hitObject) + private IEnumerable> enumerateAllSamples(HitObject hitObject) + { + yield return hitObject.Samples; + + if (hitObject is IHasRepeats withRepeats) { - yield return hitObject.Samples; - - if (hitObject is IHasRepeats withRepeats) - { - foreach (var node in withRepeats.NodeSamples) - yield return node; - } + foreach (var node in withRepeats.NodeSamples) + yield return node; } } @@ -340,7 +340,7 @@ namespace osu.Game.Screens.Edit.Compose.Components EditorBeatmap.PerformOnSelection(h => { - if (h.Samples.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName)) + if (enumerateAllSamples(h).SelectMany(o => o).Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName)) return; h.Samples = h.Samples.Select(s => s.Name != HitSampleInfo.HIT_NORMAL ? s.With(newBank: bankName) : s).ToList(); From 29c2e821ea3e66f9b7846bae4a19a419cdf38d95 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 14 Jul 2024 22:50:25 +0200 Subject: [PATCH 131/924] extend tests --- .../TestSceneHitObjectSampleAdjustments.cs | 136 ++++++++++++++++-- 1 file changed, 123 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs index 558d8dce94..e962d3975c 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs @@ -321,6 +321,12 @@ namespace osu.Game.Tests.Visual.Editing } }); + AddStep("add whistle addition", () => + { + foreach (var h in EditorBeatmap.HitObjects) + h.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, HitSampleInfo.BANK_SOFT)); + }); + AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT); @@ -333,8 +339,10 @@ namespace osu.Game.Tests.Visual.Editing InputManager.ReleaseKey(Key.ShiftLeft); }); - hitObjectHasSampleBank(0, HitSampleInfo.BANK_NORMAL); - hitObjectHasSampleBank(1, HitSampleInfo.BANK_NORMAL); + hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL); + hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_NORMAL); + hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT); + hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_SOFT); AddStep("Press drum bank shortcut", () => { @@ -343,8 +351,10 @@ namespace osu.Game.Tests.Visual.Editing InputManager.ReleaseKey(Key.ShiftLeft); }); - hitObjectHasSampleBank(0, HitSampleInfo.BANK_DRUM); - hitObjectHasSampleBank(1, HitSampleInfo.BANK_DRUM); + hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM); + hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_DRUM); + hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT); + hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_SOFT); AddStep("Press auto bank shortcut", () => { @@ -354,8 +364,47 @@ namespace osu.Game.Tests.Visual.Editing }); // Should be a noop. - hitObjectHasSampleBank(0, HitSampleInfo.BANK_DRUM); - hitObjectHasSampleBank(1, HitSampleInfo.BANK_DRUM); + hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM); + hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_DRUM); + hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT); + hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_SOFT); + + AddStep("Press addition normal bank shortcut", () => + { + InputManager.PressKey(Key.AltLeft); + InputManager.Key(Key.W); + InputManager.ReleaseKey(Key.AltLeft); + }); + + hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM); + hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_DRUM); + hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_NORMAL); + hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_NORMAL); + + AddStep("Press addition drum bank shortcut", () => + { + InputManager.PressKey(Key.AltLeft); + InputManager.Key(Key.R); + InputManager.ReleaseKey(Key.AltLeft); + }); + + hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM); + hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_DRUM); + hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM); + hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_DRUM); + + AddStep("Press auto bank shortcut", () => + { + InputManager.PressKey(Key.AltLeft); + InputManager.Key(Key.Q); + InputManager.ReleaseKey(Key.AltLeft); + }); + + // Should be a noop. + hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM); + hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_DRUM); + hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM); + hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_DRUM); } [Test] @@ -373,7 +422,21 @@ namespace osu.Game.Tests.Visual.Editing InputManager.ReleaseKey(Key.ShiftLeft); }); - checkPlacementSample(HitSampleInfo.BANK_NORMAL); + AddStep("Press soft addition bank shortcut", () => + { + InputManager.PressKey(Key.AltLeft); + InputManager.Key(Key.E); + InputManager.ReleaseKey(Key.AltLeft); + }); + + checkPlacementSampleBank(HitSampleInfo.BANK_NORMAL); + + AddStep("Press finish sample shortcut", () => + { + InputManager.Key(Key.E); + }); + + checkPlacementSampleAdditionBank(HitSampleInfo.BANK_SOFT); AddStep("Press drum bank shortcut", () => { @@ -382,7 +445,18 @@ namespace osu.Game.Tests.Visual.Editing InputManager.ReleaseKey(Key.ShiftLeft); }); - checkPlacementSample(HitSampleInfo.BANK_DRUM); + checkPlacementSampleBank(HitSampleInfo.BANK_DRUM); + checkPlacementSampleAdditionBank(HitSampleInfo.BANK_SOFT); + + AddStep("Press drum addition bank shortcut", () => + { + InputManager.PressKey(Key.AltLeft); + InputManager.Key(Key.R); + InputManager.ReleaseKey(Key.AltLeft); + }); + + checkPlacementSampleBank(HitSampleInfo.BANK_DRUM); + checkPlacementSampleAdditionBank(HitSampleInfo.BANK_DRUM); AddStep("Press auto bank shortcut", () => { @@ -391,15 +465,29 @@ namespace osu.Game.Tests.Visual.Editing InputManager.ReleaseKey(Key.ShiftLeft); }); - checkPlacementSample(HitSampleInfo.BANK_NORMAL); + checkPlacementSampleBank(HitSampleInfo.BANK_NORMAL); + checkPlacementSampleAdditionBank(HitSampleInfo.BANK_DRUM); + + AddStep("Press auto addition bank shortcut", () => + { + InputManager.PressKey(Key.AltLeft); + InputManager.Key(Key.Q); + InputManager.ReleaseKey(Key.AltLeft); + }); + + checkPlacementSampleBank(HitSampleInfo.BANK_NORMAL); + checkPlacementSampleAdditionBank(HitSampleInfo.BANK_NORMAL); AddStep("Move after second object", () => EditorClock.Seek(750)); - checkPlacementSample(HitSampleInfo.BANK_SOFT); + checkPlacementSampleBank(HitSampleInfo.BANK_SOFT); + checkPlacementSampleAdditionBank(HitSampleInfo.BANK_SOFT); AddStep("Move to first object", () => EditorClock.Seek(0)); - checkPlacementSample(HitSampleInfo.BANK_NORMAL); + checkPlacementSampleBank(HitSampleInfo.BANK_NORMAL); + checkPlacementSampleAdditionBank(HitSampleInfo.BANK_NORMAL); - void checkPlacementSample(string expected) => AddAssert($"Placement sample is {expected}", () => EditorBeatmap.PlacementObject.Value.Samples.First().Bank, () => Is.EqualTo(expected)); + void checkPlacementSampleBank(string expected) => AddAssert($"Placement sample is {expected}", () => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name == HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(expected)); + void checkPlacementSampleAdditionBank(string expected) => AddAssert($"Placement sample addition is {expected}", () => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name != HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(expected)); } [Test] @@ -480,7 +568,29 @@ namespace osu.Game.Tests.Visual.Editing hitObjectHasSamples(2, HitSampleInfo.HIT_NORMAL); hitObjectNodeHasSampleBank(2, 0, HitSampleInfo.BANK_DRUM); hitObjectNodeHasSamples(2, 0, HitSampleInfo.HIT_NORMAL); - hitObjectNodeHasSampleBank(2, 1, HitSampleInfo.BANK_DRUM); + hitObjectNodeHasSampleNormalBank(2, 1, HitSampleInfo.BANK_DRUM); + hitObjectNodeHasSampleAdditionBank(2, 1, HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSamples(2, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); + + AddStep("set normal addition bank", () => + { + InputManager.PressKey(Key.LAlt); + InputManager.Key(Key.W); + InputManager.ReleaseKey(Key.LAlt); + }); + + hitObjectHasSampleBank(0, HitSampleInfo.BANK_DRUM); + hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL); + + hitObjectHasSampleBank(1, HitSampleInfo.BANK_DRUM); + hitObjectHasSamples(1, HitSampleInfo.HIT_NORMAL); + + hitObjectHasSampleBank(2, HitSampleInfo.BANK_DRUM); + hitObjectHasSamples(2, HitSampleInfo.HIT_NORMAL); + hitObjectNodeHasSampleBank(2, 0, HitSampleInfo.BANK_DRUM); + hitObjectNodeHasSamples(2, 0, HitSampleInfo.HIT_NORMAL); + hitObjectNodeHasSampleNormalBank(2, 1, HitSampleInfo.BANK_DRUM); + hitObjectNodeHasSampleAdditionBank(2, 1, HitSampleInfo.BANK_NORMAL); hitObjectNodeHasSamples(2, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); } From e5bc359b8811fcd0281dd6052031e05a950644b5 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 14 Jul 2024 23:39:19 +0200 Subject: [PATCH 132/924] fix test --- osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs index e9b442f8dd..fba84108d0 100644 --- a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs @@ -123,7 +123,9 @@ namespace osu.Game.Tests.Visual.Editing AddStep("enable automatic bank assignment", () => { InputManager.PressKey(Key.LShift); + InputManager.PressKey(Key.LAlt); InputManager.Key(Key.Q); + InputManager.ReleaseKey(Key.LAlt); InputManager.ReleaseKey(Key.LShift); }); AddStep("select circle placement tool", () => InputManager.Key(Key.Number2)); @@ -186,7 +188,9 @@ namespace osu.Game.Tests.Visual.Editing AddStep("select drum bank", () => { InputManager.PressKey(Key.LShift); + InputManager.PressKey(Key.LAlt); InputManager.Key(Key.R); + InputManager.ReleaseKey(Key.LAlt); InputManager.ReleaseKey(Key.LShift); }); AddStep("enable clap addition", () => InputManager.Key(Key.R)); From e25642b48472789aaffa5254106d763a4cee9cad Mon Sep 17 00:00:00 2001 From: StanR Date: Mon, 15 Jul 2024 14:45:31 +0500 Subject: [PATCH 133/924] Implement a bunch of rhythm difficulty calculation fixes --- .../Difficulty/Evaluators/RhythmEvaluator.cs | 103 ++++++++++++++---- .../Preprocessing/OsuDifficultyHitObject.cs | 11 +- 2 files changed, 90 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index f2218a89a7..39c7572c85 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Linq; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Objects; @@ -10,21 +12,65 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators { public static class RhythmEvaluator { - private const int history_time_max = 5000; // 5 seconds of calculatingRhythmBonus max. - private const double rhythm_multiplier = 0.75; + private readonly struct Island : IEquatable + { + public Island() + { + } + + public Island(int firstDelta, double epsilon) + { + AddDelta(firstDelta, epsilon); + } + + public List Deltas { get; } = new List(); + + public void AddDelta(int delta, double epsilon) + { + int existingDelta = Deltas.FirstOrDefault(x => Math.Abs(x - delta) >= epsilon); + + Deltas.Add(existingDelta == default ? delta : existingDelta); + } + + public double AverageDelta() => Math.Max(Deltas.Average(), OsuDifficultyHitObject.MIN_DELTA_TIME); + + public override int GetHashCode() + { + // we need to compare all deltas and they must be in the exact same order we added them + string joinedDeltas = string.Join(string.Empty, Deltas); + return joinedDeltas.GetHashCode(); + } + + public bool Equals(Island other) + { + return other.GetHashCode() == GetHashCode(); + } + + public override bool Equals(object? obj) + { + return obj?.GetHashCode() == GetHashCode(); + } + } + + private const int history_time_max = 5 * 1000; // 5 seconds of calculatingRhythmBonus max. + private const double rhythm_multiplier = 1.14; + private const int max_island_size = 7; /// /// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current . /// public static double EvaluateDifficultyOf(DifficultyHitObject current) { + Dictionary islandCounts = new Dictionary(); + if (current.BaseObject is Spinner) return 0; - int previousIslandSize = 0; - double rhythmComplexitySum = 0; - int islandSize = 1; + + var island = new Island(); + var previousIsland = new Island(); + double startRatio = 0; // store the ratio of the current start of an island to buff for tighter rhythms bool firstDeltaSwitch = false; @@ -50,6 +96,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators double currDelta = currObj.StrainTime; double prevDelta = prevObj.StrainTime; double lastDelta = lastObj.StrainTime; + double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses. double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - currObj.HitWindowGreat * 0.3) / (currObj.HitWindowGreat * 0.3)); @@ -58,12 +105,17 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators double effectiveRatio = windowPenalty * currRatio; + double deltaDifferenceEpsilon = currObj.HitWindowGreat * 0.3; + if (firstDeltaSwitch) { - if (!(prevDelta > 1.25 * currDelta || prevDelta * 1.25 < currDelta)) + if (!(Math.Abs(prevDelta - currDelta) > deltaDifferenceEpsilon)) { - if (islandSize < 7) - islandSize++; // island is still progressing, count size. + if (island.Deltas.Count < max_island_size) + { + // island is still progressing + island.AddDelta((int)currDelta, deltaDifferenceEpsilon); + } } else { @@ -73,33 +125,44 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators if (prevObj.BaseObject is Slider) // bpm change was from a slider, this is easier typically than circle -> circle effectiveRatio *= 0.25; - if (previousIslandSize == islandSize) // repeated island size (ex: triplet -> triplet) - effectiveRatio *= 0.25; - - if (previousIslandSize % 2 == islandSize % 2) // repeated island polartiy (2 -> 4, 3 -> 5) + if (previousIsland.Deltas.Count % 2 == island.Deltas.Count % 2) // repeated island polartiy (2 -> 4, 3 -> 5) effectiveRatio *= 0.50; - if (lastDelta > prevDelta + 10 && prevDelta > currDelta + 10) // previous increase happened a note ago, 1/1->1/2-1/4, dont want to buff this. + if (lastDelta > prevDelta + deltaDifferenceEpsilon && prevDelta > currDelta + deltaDifferenceEpsilon) // previous increase happened a note ago, 1/1->1/2-1/4, dont want to buff this. effectiveRatio *= 0.125; - rhythmComplexitySum += Math.Sqrt(effectiveRatio * startRatio) * currHistoricalDecay * Math.Sqrt(4 + islandSize) / 2 * Math.Sqrt(4 + previousIslandSize) / 2; + if (islandCounts.ContainsKey(island)) + { + islandCounts[island]++; + + // repeated island (ex: triplet -> triplet) + double power = Math.Max(0.75, logistic(island.AverageDelta(), 3, 0.15, 9)); + effectiveRatio *= Math.Pow(1.0 / islandCounts[island], power); + } + else + { + islandCounts.Add(island, 1); + } + + rhythmComplexitySum += Math.Sqrt(effectiveRatio * startRatio) * currHistoricalDecay; startRatio = effectiveRatio; - previousIslandSize = islandSize; // log the last island size. + previousIsland = island; - if (prevDelta * 1.25 < currDelta) // we're slowing down, stop counting + if (prevDelta + deltaDifferenceEpsilon < currDelta) // we're slowing down, stop counting firstDeltaSwitch = false; // if we're speeding up, this stays true and we keep counting island size. - islandSize = 1; + island = new Island((int)currDelta, deltaDifferenceEpsilon); } } - else if (prevDelta > 1.25 * currDelta) // we want to be speeding up. + else if (prevDelta > currDelta + deltaDifferenceEpsilon) // we want to be speeding up. { // Begin counting island until we change speed again. firstDeltaSwitch = true; startRatio = effectiveRatio; - islandSize = 1; + + island = new Island((int)currDelta, deltaDifferenceEpsilon); } lastObj = prevObj; @@ -108,5 +171,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators return Math.Sqrt(4 + rhythmComplexitySum * rhythm_multiplier) / 2; //produces multiplier that can be applied to strain. range [1, infinity) (not really though) } + + private static double logistic(double x, double maxValue, double multiplier, double offset) => (maxValue / (1 + Math.Pow(Math.E, offset - multiplier * x))); } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 0e537632b1..95535274c1 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -20,7 +20,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing /// public const int NORMALISED_RADIUS = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths. - private const int min_delta_time = 25; + public const int MIN_DELTA_TIME = 25; + private const float maximum_slider_radius = NORMALISED_RADIUS * 2.4f; private const float assumed_slider_radius = NORMALISED_RADIUS * 1.8f; @@ -93,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing this.lastObject = (OsuHitObject)lastObject; // Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects. - StrainTime = Math.Max(DeltaTime, min_delta_time); + StrainTime = Math.Max(DeltaTime, MIN_DELTA_TIME); if (BaseObject is Slider sliderObject) { @@ -143,7 +144,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing computeSliderCursorPosition(currentSlider); // Bonus for repeat sliders until a better per nested object strain system can be achieved. TravelDistance = currentSlider.LazyTravelDistance * (float)Math.Pow(1 + currentSlider.RepeatCount / 2.5, 1.0 / 2.5); - TravelTime = Math.Max(currentSlider.LazyTravelTime / clockRate, min_delta_time); + TravelTime = Math.Max(currentSlider.LazyTravelTime / clockRate, MIN_DELTA_TIME); } // We don't need to calculate either angle or distance when one of the last->curr objects is a spinner @@ -167,8 +168,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing if (lastObject is Slider lastSlider) { - double lastTravelTime = Math.Max(lastSlider.LazyTravelTime / clockRate, min_delta_time); - MinimumJumpTime = Math.Max(StrainTime - lastTravelTime, min_delta_time); + double lastTravelTime = Math.Max(lastSlider.LazyTravelTime / clockRate, MIN_DELTA_TIME); + MinimumJumpTime = Math.Max(StrainTime - lastTravelTime, MIN_DELTA_TIME); // // There are two types of slider-to-object patterns to consider in order to better approximate the real movement a player will take to jump between the hitobjects. From 67cb4a2d02e47a2c8bfd493c4def3c19538057e8 Mon Sep 17 00:00:00 2001 From: StanR Date: Mon, 15 Jul 2024 22:54:25 +0500 Subject: [PATCH 134/924] InspectCode --- osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index 39c7572c85..60bc53e7a1 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -172,6 +172,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators return Math.Sqrt(4 + rhythmComplexitySum * rhythm_multiplier) / 2; //produces multiplier that can be applied to strain. range [1, infinity) (not really though) } - private static double logistic(double x, double maxValue, double multiplier, double offset) => (maxValue / (1 + Math.Pow(Math.E, offset - multiplier * x))); + private static double logistic(double x, double maxValue, double multiplier, double offset) => (maxValue / (1 + Math.Pow(Math.E, offset - (multiplier * x)))); } } From fcc8e7be8a711c9356f0f6df725a1aad6aa86162 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 16 Jul 2024 12:09:09 +0900 Subject: [PATCH 135/924] Invert condition to reduce number of brain flips required --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 7b54d0ae54..0aec0a9d4f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty // This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window. double betterAccuracyPercentage; int amountHitObjectsWithAccuracy = attributes.HitCircleCount; - if (!score.Mods.Any(h => h is OsuModClassic cl && cl.NoSliderHeadAccuracy.Value)) + if (score.Mods.All(h => h is not OsuModClassic cl || !cl.NoSliderHeadAccuracy.Value)) amountHitObjectsWithAccuracy += attributes.SliderCount; if (amountHitObjectsWithAccuracy > 0) From ced11e69496eb4cdd6790d8bcf4689420a24c7e1 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 16 Jul 2024 12:23:46 +0900 Subject: [PATCH 136/924] Even better readability --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 0aec0a9d4f..a4ebcd15a4 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty // This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window. double betterAccuracyPercentage; int amountHitObjectsWithAccuracy = attributes.HitCircleCount; - if (score.Mods.All(h => h is not OsuModClassic cl || !cl.NoSliderHeadAccuracy.Value)) + if (score.Mods.OfType().All(m => !m.NoSliderHeadAccuracy.Value)) amountHitObjectsWithAccuracy += attributes.SliderCount; if (amountHitObjectsWithAccuracy > 0) From c18814817b7d5b1f907574b6cca89f4fdc0efa53 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 16 Jul 2024 11:17:54 +0200 Subject: [PATCH 137/924] fix test --- osu.Game.Rulesets.Osu.Tests/Editor/TestScenePreciseRotation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePreciseRotation.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePreciseRotation.cs index 30e0dbbf2e..d14bd1fc87 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePreciseRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePreciseRotation.cs @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor () => EditorBeatmap.HitObjects.OfType().ElementAt(1).Position, () => Is.EqualTo(OsuPlayfield.BASE_SIZE - new Vector2(200))); - AddStep("change rotation origin", () => getPopover().ChildrenOfType().ElementAt(1).TriggerClick()); + AddStep("change rotation origin", () => getPopover().ChildrenOfType().ElementAt(2).TriggerClick()); AddAssert("first object rotated 90deg around selection centre", () => EditorBeatmap.HitObjects.OfType().ElementAt(0).Position, () => Is.EqualTo(new Vector2(200, 200))); AddAssert("second object rotated 90deg around selection centre", From ba501a8eb80cddea92a77cbedea8cfc4c8a540e0 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 16 Jul 2024 11:30:52 +0200 Subject: [PATCH 138/924] fix addition bank toggle can be switched off with selection --- .../Components/EditorSelectionHandler.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index 3a0428e7fc..df8c0176e1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -150,9 +150,23 @@ namespace osu.Game.Screens.Edit.Compose.Components if (SelectionAdditionBankStates.Values.All(b => b.Value == TernaryState.False)) bindable.Value = TernaryState.True; } + else + { + // Auto should never apply when there is a selection made. + if (bankName == HIT_BANK_AUTO) + break; + + // Completely empty selections should be allowed in the case that none of the selected objects have any addition samples. + // This is also required to stop a bindable feedback loop when a HitObject has zero addition samples (and LINQ `All` below becomes true). + if (SelectedItems.SelectMany(enumerateAllSamples).All(h => h.All(o => o.Name == HitSampleInfo.HIT_NORMAL))) + break; + + // Never remove a sample bank. + // These are basically radio buttons, not toggles. + if (SelectedItems.SelectMany(enumerateAllSamples).All(h => h.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName))) + bindable.Value = TernaryState.True; + } - // Auto should never apply when there is a selection made. - // Completely empty selections should be allowed in the case that none of the selected objects have any addition samples. break; case TernaryState.True: From 7dc006f9bab4f22e326c5692a40a2afc5bfdc566 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 16 Jul 2024 13:19:01 +0200 Subject: [PATCH 139/924] fix horizontal flip rotation --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 1334dbdbec..2dc43deee1 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.Osu.Edit // however it's still possible to achieve that flip by combining multiple flips over the other axes. flipAxis = direction == Direction.Vertical ? GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 390) % 60 + 60)) - : GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 390) % 60 - 60)); + : GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 360) % 60 - 30)); break; } } From bae9625b0b4785b83124bfd38ccd8ff85d74947b Mon Sep 17 00:00:00 2001 From: StanR Date: Fri, 19 Jul 2024 10:13:50 +0500 Subject: [PATCH 140/924] Make repetition nerf harsher, buff initial rhythm ratio, small refactoring --- .../Difficulty/Evaluators/RhythmEvaluator.cs | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index 60bc53e7a1..9c48af80a7 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -32,7 +32,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators Deltas.Add(existingDelta == default ? delta : existingDelta); } - public double AverageDelta() => Math.Max(Deltas.Average(), OsuDifficultyHitObject.MIN_DELTA_TIME); + public double AverageDelta() => Deltas.Count > 0 ? Math.Max(Deltas.Average(), OsuDifficultyHitObject.MIN_DELTA_TIME) : 0; + + public bool IsSimilarPolarity(Island other, double epsilon) + { + // consider islands to be of similar polarity only if they're having the same average delta (we don't want to consider 3 singletaps similar to a triple) + return Math.Abs(AverageDelta() - other.AverageDelta()) < epsilon && + Deltas.Count % 2 == other.Deltas.Count % 2; + } public override int GetHashCode() { @@ -53,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators } private const int history_time_max = 5 * 1000; // 5 seconds of calculatingRhythmBonus max. - private const double rhythm_multiplier = 1.14; + private const double rhythm_multiplier = 1.05; private const int max_island_size = 7; /// @@ -61,8 +68,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators /// public static double EvaluateDifficultyOf(DifficultyHitObject current) { - Dictionary islandCounts = new Dictionary(); - if (current.BaseObject is Spinner) return 0; @@ -70,6 +75,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators var island = new Island(); var previousIsland = new Island(); + Dictionary islandCounts = new Dictionary(); double startRatio = 0; // store the ratio of the current start of an island to buff for tighter rhythms @@ -97,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators double prevDelta = prevObj.StrainTime; double lastDelta = lastObj.StrainTime; - double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses. + double currRatio = 1.0 + 10.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses. double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - currObj.HitWindowGreat * 0.3) / (currObj.HitWindowGreat * 0.3)); @@ -125,23 +131,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators if (prevObj.BaseObject is Slider) // bpm change was from a slider, this is easier typically than circle -> circle effectiveRatio *= 0.25; - if (previousIsland.Deltas.Count % 2 == island.Deltas.Count % 2) // repeated island polartiy (2 -> 4, 3 -> 5) + if (island.IsSimilarPolarity(previousIsland, deltaDifferenceEpsilon)) // repeated island polartiy (2 -> 4, 3 -> 5) effectiveRatio *= 0.50; if (lastDelta > prevDelta + deltaDifferenceEpsilon && prevDelta > currDelta + deltaDifferenceEpsilon) // previous increase happened a note ago, 1/1->1/2-1/4, dont want to buff this. effectiveRatio *= 0.125; - if (islandCounts.ContainsKey(island)) + if (!islandCounts.TryAdd(island, 1)) { islandCounts[island]++; // repeated island (ex: triplet -> triplet) - double power = Math.Max(0.75, logistic(island.AverageDelta(), 3, 0.15, 9)); - effectiveRatio *= Math.Pow(1.0 / islandCounts[island], power); - } - else - { - islandCounts.Add(island, 1); + double power = logistic(island.AverageDelta(), 4, 0.165, 10); + effectiveRatio *= Math.Min(1.0 / islandCounts[island], Math.Pow(1.0 / islandCounts[island], power)); } rhythmComplexitySum += Math.Sqrt(effectiveRatio * startRatio) * currHistoricalDecay; From c1532bcb570ae36d7a73d5b42e451b8c7325b111 Mon Sep 17 00:00:00 2001 From: StanR Date: Fri, 19 Jul 2024 11:01:42 +0500 Subject: [PATCH 141/924] Reduce base ratio a bit --- osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index 9c48af80a7..ba77bc6a61 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators double prevDelta = prevObj.StrainTime; double lastDelta = lastObj.StrainTime; - double currRatio = 1.0 + 10.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses. + double currRatio = 1.0 + 8.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses. double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - currObj.HitWindowGreat * 0.3) / (currObj.HitWindowGreat * 0.3)); From 3acd00b9b703a0a5a7445192eabac94bfe385594 Mon Sep 17 00:00:00 2001 From: StanR Date: Sat, 10 Aug 2024 22:30:24 +0500 Subject: [PATCH 142/924] Tweak some values --- .../Difficulty/Evaluators/RhythmEvaluator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index ba77bc6a61..d2f58925cd 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators } private const int history_time_max = 5 * 1000; // 5 seconds of calculatingRhythmBonus max. - private const double rhythm_multiplier = 1.05; + private const double rhythm_multiplier = 1.1; private const int max_island_size = 7; /// @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators double prevDelta = prevObj.StrainTime; double lastDelta = lastObj.StrainTime; - double currRatio = 1.0 + 8.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses. + double currRatio = 1.0 + 8.4 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses. double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - currObj.HitWindowGreat * 0.3) / (currObj.HitWindowGreat * 0.3)); @@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators effectiveRatio *= 0.25; if (island.IsSimilarPolarity(previousIsland, deltaDifferenceEpsilon)) // repeated island polartiy (2 -> 4, 3 -> 5) - effectiveRatio *= 0.50; + effectiveRatio *= 0.35; if (lastDelta > prevDelta + deltaDifferenceEpsilon && prevDelta > currDelta + deltaDifferenceEpsilon) // previous increase happened a note ago, 1/1->1/2-1/4, dont want to buff this. effectiveRatio *= 0.125; From b18706274784430b26526fc4b3eecb75c8ef058e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 13 Aug 2024 12:58:52 +0200 Subject: [PATCH 143/924] clarify meaning of flip axis vector --- osu.Game/Utils/GeometryUtils.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index 5a8ca9722e..810eda9950 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -51,6 +51,9 @@ namespace osu.Game.Utils /// Given a flip direction, a surrounding quad for all selected objects, and a position, /// will return the flipped position in screen space coordinates. /// + /// The direction to flip towards. + /// The quad surrounding all selected objects. The center of this determines the position of the axis. + /// The position to flip. public static Vector2 GetFlippedPosition(Direction direction, Quad quad, Vector2 position) { var centre = quad.Centre; @@ -73,6 +76,9 @@ namespace osu.Game.Utils /// Given a flip axis vector, a surrounding quad for all selected objects, and a position, /// will return the flipped position in screen space coordinates. /// + /// The vector indicating the direction to flip towards. This is perpendicular to the mirroring axis. + /// The quad surrounding all selected objects. The center of this determines the position of the axis. + /// The position to flip. public static Vector2 GetFlippedPosition(Vector2 axis, Quad quad, Vector2 position) { var centre = quad.Centre; From ae47671d17e6322a688f24a090020f6188539c1e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 13 Aug 2024 14:21:42 +0200 Subject: [PATCH 144/924] clarify angle ranges in HandleFlip --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index da98da5238..bac0a5e273 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -138,15 +138,17 @@ namespace osu.Game.Rulesets.Osu.Edit switch (gridToolbox.GridType.Value) { case PositionSnapGridType.Square: - flipAxis = GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 405) % 90 - 45)); + flipAxis = GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 360 + 45) % 90 - 45)); flipAxis = direction == Direction.Vertical ? flipAxis.PerpendicularLeft : flipAxis; break; case PositionSnapGridType.Triangle: // Hex grid has 3 axes, so you can not directly flip over one of the axes, // however it's still possible to achieve that flip by combining multiple flips over the other axes. + // Angle degree range for vertical = (-120, -60] + // Angle degree range for horizontal = [-30, 30) flipAxis = direction == Direction.Vertical - ? GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 390) % 60 + 60)) + ? GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 360 + 30) % 60 + 60)) : GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 360) % 60 - 30)); break; } From 2abcdd006466fdb291d9b727fb85b34a505155f7 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 13 Aug 2024 22:05:01 +0200 Subject: [PATCH 145/924] Redesign sample bank toggles --- .../Rulesets/Edit/ExpandableSpriteText.cs | 34 ++++++++ osu.Game/Rulesets/Edit/HitObjectComposer.cs | 83 ++++++++++++++----- .../DoubleDrawableTernaryButton.cs | 78 +++++++++++++++++ .../TernaryButtons/DrawableTernaryButton.cs | 10 +-- .../Components/ComposeBlueprintContainer.cs | 27 ++---- 5 files changed, 185 insertions(+), 47 deletions(-) create mode 100644 osu.Game/Rulesets/Edit/ExpandableSpriteText.cs create mode 100644 osu.Game/Screens/Edit/Components/TernaryButtons/DoubleDrawableTernaryButton.cs diff --git a/osu.Game/Rulesets/Edit/ExpandableSpriteText.cs b/osu.Game/Rulesets/Edit/ExpandableSpriteText.cs new file mode 100644 index 0000000000..b45fce1d22 --- /dev/null +++ b/osu.Game/Rulesets/Edit/ExpandableSpriteText.cs @@ -0,0 +1,34 @@ +// 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.Bindables; +using osu.Framework.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Rulesets.Edit +{ + internal partial class ExpandableSpriteText : OsuSpriteText, IExpandable + { + public BindableBool Expanded { get; } = new BindableBool(); + + [Resolved(canBeNull: true)] + private IExpandingContainer? expandingContainer { get; set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + expandingContainer?.Expanded.BindValueChanged(containerExpanded => + { + Expanded.Value = containerExpanded.NewValue; + }, true); + + Expanded.BindValueChanged(expanded => + { + this.FadeTo(expanded.NewValue ? 1 : 0, 150, Easing.OutQuint); + }, true); + } + } +} diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 1319fb3352..e5b118004c 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -18,6 +18,7 @@ using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Graphics; using osu.Game.Overlays; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Edit.Tools; @@ -85,7 +86,6 @@ namespace osu.Game.Rulesets.Edit private EditorRadioButtonCollection toolboxCollection; private FillFlowContainer togglesCollection; private FillFlowContainer sampleBankTogglesCollection; - private FillFlowContainer sampleAdditionBankTogglesCollection; private IBindable hasTiming; private Bindable autoSeekOnPlacement; @@ -176,25 +176,55 @@ namespace osu.Game.Rulesets.Edit Spacing = new Vector2(0, 5), }, }, - new EditorToolboxGroup("bank (Shift-Q~R)") + new EditorToolboxGroup("bank (Shift/Alt-Q~R)") { - Child = sampleBankTogglesCollection = new FillFlowContainer + Child = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, Spacing = new Vector2(0, 5), - }, - }, - new EditorToolboxGroup("additions (Alt-Q~R)") - { - Child = sampleAdditionBankTogglesCollection = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), - }, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new ExpandableSpriteText + { + Text = "Normal", + AlwaysPresent = true, + AllowMultiline = false, + RelativePositionAxes = Axes.X, + X = 0.25f, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopLeft, + Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 17), + }, + new ExpandableSpriteText + { + Text = "Addition", + AlwaysPresent = true, + AllowMultiline = false, + RelativePositionAxes = Axes.X, + X = 0.75f, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopLeft, + Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 17), + }, + } + }, + sampleBankTogglesCollection = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + }, + } + } }, } }, @@ -240,8 +270,7 @@ namespace osu.Game.Rulesets.Edit TernaryStates = CreateTernaryButtons().ToArray(); togglesCollection.AddRange(TernaryStates.Select(b => new DrawableTernaryButton(b))); - sampleBankTogglesCollection.AddRange(BlueprintContainer.SampleBankTernaryStates.Select(b => new DrawableTernaryButton(b))); - sampleAdditionBankTogglesCollection.AddRange(BlueprintContainer.SampleAdditionBankTernaryStates.Select(b => new DrawableTernaryButton(b))); + sampleBankTogglesCollection.AddRange(BlueprintContainer.SampleBankTernaryStates.Zip(BlueprintContainer.SampleAdditionBankTernaryStates).Select(b => new DoubleDrawableTernaryButton(b.First, b.Second))); setSelectTool(); @@ -397,7 +426,7 @@ namespace osu.Game.Rulesets.Edit attemptToggle(rightIndex, sampleBankTogglesCollection); if (e.AltPressed) - attemptToggle(rightIndex, sampleAdditionBankTogglesCollection); + attemptToggle(rightIndex, sampleBankTogglesCollection, true); } else attemptToggle(rightIndex, togglesCollection); @@ -405,14 +434,26 @@ namespace osu.Game.Rulesets.Edit return handled || base.OnKeyDown(e); - void attemptToggle(int index, FillFlowContainer collection) + void attemptToggle(int index, FillFlowContainer collection, bool second = false) { var item = collection.ElementAtOrDefault(index); - if (item is DrawableTernaryButton button) + switch (item) { - button.Button.Toggle(); - handled = true; + case DrawableTernaryButton button: + button.Button.Toggle(); + handled = true; + break; + + case DoubleDrawableTernaryButton doubleButton: + { + if (second) + doubleButton.Button2.Toggle(); + else + doubleButton.Button1.Toggle(); + handled = true; + break; + } } } } diff --git a/osu.Game/Screens/Edit/Components/TernaryButtons/DoubleDrawableTernaryButton.cs b/osu.Game/Screens/Edit/Components/TernaryButtons/DoubleDrawableTernaryButton.cs new file mode 100644 index 0000000000..44b5d10b9e --- /dev/null +++ b/osu.Game/Screens/Edit/Components/TernaryButtons/DoubleDrawableTernaryButton.cs @@ -0,0 +1,78 @@ +// 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.Sprites; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Edit.Components.TernaryButtons +{ + public partial class DoubleDrawableTernaryButton : CompositeDrawable + { + public readonly TernaryButton Button1; + public readonly TernaryButton Button2; + + public DoubleDrawableTernaryButton(TernaryButton button1, TernaryButton button2) + { + Button1 = button1; + Button2 = button2; + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Masking = true; + CornerRadius = 5; + + InternalChildren = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = 0.5f, + Padding = new MarginPadding { Right = 1 }, + Child = new InlineDrawableTernaryButton(Button1), + }, + new Container + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = 0.5f, + Padding = new MarginPadding { Left = 1 }, + Child = new InlineDrawableTernaryButton(Button2), + }, + }; + } + } + + public partial class InlineDrawableTernaryButton : DrawableTernaryButton + { + public InlineDrawableTernaryButton(TernaryButton button) + : base(button) + { + } + + [BackgroundDependencyLoader] + private void load() + { + Content.Masking = false; + Content.CornerRadius = 0; + Icon.X = 4.5f; + } + + protected override SpriteText CreateText() => new OsuSpriteText + { + Depth = -1, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + X = 31f + }; + } +} diff --git a/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs b/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs index 95d5dd36d8..d3fd701406 100644 --- a/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs +++ b/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons private Color4 selectedBackgroundColour; private Color4 selectedIconColour; - private Drawable icon = null!; + protected Drawable Icon = null!; public readonly TernaryButton Button; @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons defaultIconColour = defaultBackgroundColour.Darken(0.5f); selectedIconColour = selectedBackgroundColour.Lighten(0.5f); - Add(icon = (Button.CreateIcon?.Invoke() ?? new Circle()).With(b => + Add(Icon = (Button.CreateIcon?.Invoke() ?? new Circle()).With(b => { b.Blending = BlendingParameters.Additive; b.Anchor = Anchor.CentreLeft; @@ -75,17 +75,17 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons switch (Button.Bindable.Value) { case TernaryState.Indeterminate: - icon.Colour = selectedIconColour.Darken(0.5f); + Icon.Colour = selectedIconColour.Darken(0.5f); BackgroundColour = selectedBackgroundColour.Darken(0.5f); break; case TernaryState.False: - icon.Colour = defaultIconColour; + Icon.Colour = defaultIconColour; BackgroundColour = defaultBackgroundColour; break; case TernaryState.True: - icon.Colour = selectedIconColour; + Icon.Colour = selectedIconColour; BackgroundColour = selectedBackgroundColour; break; } diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 4c1610902e..9f2fff9ca3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -259,28 +259,13 @@ namespace osu.Game.Screens.Edit.Compose.Components private Drawable getIconForBank(string sampleName) { - return new Container + return new OsuSpriteText { - Size = new Vector2(30, 20), - Children = new Drawable[] - { - new SpriteIcon - { - Size = new Vector2(8), - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Icon = FontAwesome.Solid.VolumeOff - }, - new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - X = 10, - Y = -1, - Font = OsuFont.Default.With(weight: FontWeight.Bold, size: 20), - Text = $"{char.ToUpperInvariant(sampleName.First())}" - } - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Y = -1, + Font = OsuFont.Default.With(weight: FontWeight.Bold, size: 20), + Text = $"{char.ToUpperInvariant(sampleName.First())}" }; } From 10f2ac64905784c22db6d36d8a9f7ae571e8164d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 13 Aug 2024 22:13:17 +0200 Subject: [PATCH 146/924] fix anti-aliased white leaking out of the button --- .../Components/TernaryButtons/DoubleDrawableTernaryButton.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/TernaryButtons/DoubleDrawableTernaryButton.cs b/osu.Game/Screens/Edit/Components/TernaryButtons/DoubleDrawableTernaryButton.cs index 44b5d10b9e..2cf3b760f7 100644 --- a/osu.Game/Screens/Edit/Components/TernaryButtons/DoubleDrawableTernaryButton.cs +++ b/osu.Game/Screens/Edit/Components/TernaryButtons/DoubleDrawableTernaryButton.cs @@ -5,7 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Edit; namespace osu.Game.Screens.Edit.Components.TernaryButtons { @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons Icon.X = 4.5f; } - protected override SpriteText CreateText() => new OsuSpriteText + protected override SpriteText CreateText() => new ExpandableSpriteText { Depth = -1, Origin = Anchor.CentreLeft, From 55e9bb6a5da9ccbfed949890b492f2620e5dcfab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Aug 2024 10:59:47 +0200 Subject: [PATCH 147/924] Privatise setter --- .../Edit/Components/TernaryButtons/DrawableTernaryButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs b/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs index d3fd701406..9e528aa21b 100644 --- a/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs +++ b/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons private Color4 selectedBackgroundColour; private Color4 selectedIconColour; - protected Drawable Icon = null!; + protected Drawable Icon { get; private set; } = null!; public readonly TernaryButton Button; From 32821be0462a56f7b64b037f8e6ab291e584205e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Aug 2024 11:07:34 +0200 Subject: [PATCH 148/924] Make "double ternary button" specific to samples We can generalise *when* there is the need to generalise. So far the generalisation only looked like *obfuscation*. --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 42 +++++--------- ...ryButton.cs => SampleBankTernaryButton.cs} | 58 +++++++++---------- 2 files changed, 44 insertions(+), 56 deletions(-) rename osu.Game/Screens/Edit/Components/TernaryButtons/{DoubleDrawableTernaryButton.cs => SampleBankTernaryButton.cs} (52%) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index e5b118004c..f9b218a429 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -270,7 +270,7 @@ namespace osu.Game.Rulesets.Edit TernaryStates = CreateTernaryButtons().ToArray(); togglesCollection.AddRange(TernaryStates.Select(b => new DrawableTernaryButton(b))); - sampleBankTogglesCollection.AddRange(BlueprintContainer.SampleBankTernaryStates.Zip(BlueprintContainer.SampleAdditionBankTernaryStates).Select(b => new DoubleDrawableTernaryButton(b.First, b.Second))); + sampleBankTogglesCollection.AddRange(BlueprintContainer.SampleBankTernaryStates.Zip(BlueprintContainer.SampleAdditionBankTernaryStates).Select(b => new SampleBankTernaryButton(b.First, b.Second))); setSelectTool(); @@ -422,40 +422,28 @@ namespace osu.Game.Rulesets.Edit { if (e.ShiftPressed || e.AltPressed) { - if (e.ShiftPressed) - attemptToggle(rightIndex, sampleBankTogglesCollection); + if (sampleBankTogglesCollection.ElementAtOrDefault(rightIndex) is SampleBankTernaryButton sampleBankTernaryButton) + { + if (e.ShiftPressed) + sampleBankTernaryButton.NormalButton.Toggle(); - if (e.AltPressed) - attemptToggle(rightIndex, sampleBankTogglesCollection, true); + if (e.AltPressed) + sampleBankTernaryButton.AdditionsButton.Toggle(); + + return true; + } } else - attemptToggle(rightIndex, togglesCollection); - } - - return handled || base.OnKeyDown(e); - - void attemptToggle(int index, FillFlowContainer collection, bool second = false) - { - var item = collection.ElementAtOrDefault(index); - - switch (item) { - case DrawableTernaryButton button: - button.Button.Toggle(); - handled = true; - break; - - case DoubleDrawableTernaryButton doubleButton: + if (togglesCollection.ElementAtOrDefault(rightIndex) is DrawableTernaryButton button) { - if (second) - doubleButton.Button2.Toggle(); - else - doubleButton.Button1.Toggle(); - handled = true; - break; + button.Button.Toggle(); + return true; } } } + + return base.OnKeyDown(e); } private bool checkLeftToggleFromKey(Key key, out int index) diff --git a/osu.Game/Screens/Edit/Components/TernaryButtons/DoubleDrawableTernaryButton.cs b/osu.Game/Screens/Edit/Components/TernaryButtons/SampleBankTernaryButton.cs similarity index 52% rename from osu.Game/Screens/Edit/Components/TernaryButtons/DoubleDrawableTernaryButton.cs rename to osu.Game/Screens/Edit/Components/TernaryButtons/SampleBankTernaryButton.cs index 2cf3b760f7..33eb2ac0b4 100644 --- a/osu.Game/Screens/Edit/Components/TernaryButtons/DoubleDrawableTernaryButton.cs +++ b/osu.Game/Screens/Edit/Components/TernaryButtons/SampleBankTernaryButton.cs @@ -9,15 +9,15 @@ using osu.Game.Rulesets.Edit; namespace osu.Game.Screens.Edit.Components.TernaryButtons { - public partial class DoubleDrawableTernaryButton : CompositeDrawable + public partial class SampleBankTernaryButton : CompositeDrawable { - public readonly TernaryButton Button1; - public readonly TernaryButton Button2; + public readonly TernaryButton NormalButton; + public readonly TernaryButton AdditionsButton; - public DoubleDrawableTernaryButton(TernaryButton button1, TernaryButton button2) + public SampleBankTernaryButton(TernaryButton normalButton, TernaryButton additionsButton) { - Button1 = button1; - Button2 = button2; + NormalButton = normalButton; + AdditionsButton = additionsButton; } [BackgroundDependencyLoader] @@ -36,7 +36,7 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons AutoSizeAxes = Axes.Y, Width = 0.5f, Padding = new MarginPadding { Right = 1 }, - Child = new InlineDrawableTernaryButton(Button1), + Child = new InlineDrawableTernaryButton(NormalButton), }, new Container { @@ -46,33 +46,33 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons AutoSizeAxes = Axes.Y, Width = 0.5f, Padding = new MarginPadding { Left = 1 }, - Child = new InlineDrawableTernaryButton(Button2), + Child = new InlineDrawableTernaryButton(AdditionsButton), }, }; } - } - public partial class InlineDrawableTernaryButton : DrawableTernaryButton - { - public InlineDrawableTernaryButton(TernaryButton button) - : base(button) + private partial class InlineDrawableTernaryButton : DrawableTernaryButton { + public InlineDrawableTernaryButton(TernaryButton button) + : base(button) + { + } + + [BackgroundDependencyLoader] + private void load() + { + Content.Masking = false; + Content.CornerRadius = 0; + Icon.X = 4.5f; + } + + protected override SpriteText CreateText() => new ExpandableSpriteText + { + Depth = -1, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + X = 31f + }; } - - [BackgroundDependencyLoader] - private void load() - { - Content.Masking = false; - Content.CornerRadius = 0; - Icon.X = 4.5f; - } - - protected override SpriteText CreateText() => new ExpandableSpriteText - { - Depth = -1, - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - X = 31f - }; } } From 588a36cba39293497ec46c1106cd33525a87999c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Aug 2024 14:58:12 +0200 Subject: [PATCH 149/924] Remove unused variable --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index f9b218a429..085740d3eb 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -404,8 +404,6 @@ namespace osu.Game.Rulesets.Edit if (e.ControlPressed || e.SuperPressed) return false; - bool handled = false; - if (checkLeftToggleFromKey(e.Key, out int leftIndex)) { var item = toolboxCollection.Items.ElementAtOrDefault(leftIndex); From 09ca190b8ddb44d27b16763f6a6c19d71332e001 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 21 Aug 2024 00:10:15 +0200 Subject: [PATCH 150/924] re-implement ConvexHull 100% original --- osu.Game/Utils/GeometryUtils.cs | 39 +++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index 810eda9950..d4c1dc2db7 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -154,33 +154,38 @@ namespace osu.Game.Utils { List p = points.ToList(); - if (p.Count <= 1) + if (p.Count < 3) return p; - int n = p.Count, k = 0; - List hull = new List(new Vector2[2 * n]); - p.Sort((a, b) => a.X == b.X ? a.Y.CompareTo(b.Y) : a.X.CompareTo(b.X)); - // Build lower hull - for (int i = 0; i < n; ++i) + List upper = new List(); + List lower = new List(); + + // Build the lower hull + for (int i = 0; i < p.Count; i++) { - while (k >= 2 && cross(hull[k - 2], hull[k - 1], p[i]) <= 0) - k--; - hull[k] = p[i]; - k++; + while (lower.Count >= 2 && cross(lower[^2], lower[^1], p[i]) <= 0) + lower.RemoveAt(lower.Count - 1); + + lower.Add(p[i]); } - // Build upper hull - for (int i = n - 2, t = k + 1; i >= 0; i--) + // Build the upper hull + for (int i = p.Count - 1; i >= 0; i--) { - while (k >= t && cross(hull[k - 2], hull[k - 1], p[i]) <= 0) - k--; - hull[k] = p[i]; - k++; + while (upper.Count >= 2 && cross(upper[^2], upper[^1], p[i]) <= 0) + upper.RemoveAt(upper.Count - 1); + + upper.Add(p[i]); } - return hull.Take(k - 1).ToList(); + // Remove the last point of each half because it's a duplicate of the first point of the other half + lower.RemoveAt(lower.Count - 1); + upper.RemoveAt(upper.Count - 1); + + lower.AddRange(upper); + return lower; float cross(Vector2 o, Vector2 a, Vector2 b) => (a.X - o.X) * (b.Y - o.Y) - (a.Y - o.Y) * (b.X - o.X); } From ff3bffc7d9e0e955afe3ad3ad498c2d3259e6877 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 21 Aug 2024 00:30:57 +0200 Subject: [PATCH 151/924] add test --- osu.Game.Tests/Utils/GeometryUtilsTest.cs | 33 +++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 osu.Game.Tests/Utils/GeometryUtilsTest.cs diff --git a/osu.Game.Tests/Utils/GeometryUtilsTest.cs b/osu.Game.Tests/Utils/GeometryUtilsTest.cs new file mode 100644 index 0000000000..ded4656ac1 --- /dev/null +++ b/osu.Game.Tests/Utils/GeometryUtilsTest.cs @@ -0,0 +1,33 @@ +// 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.Utils; +using osuTK; + +namespace osu.Game.Tests.Utils +{ + [TestFixture] + public class GeometryUtilsTest + { + [TestCase(new int[] { }, new int[] { })] + [TestCase(new[] { 0, 0 }, new[] { 0, 0 })] + [TestCase(new[] { 0, 0, 1, 1, 1, -1, 2, 0 }, new[] { 0, 0, 1, 1, 2, 0, 1, -1 })] + [TestCase(new[] { 0, 0, 1, 1, 1, -1, 2, 0, 1, 0 }, new[] { 0, 0, 1, 1, 2, 0, 1, -1 })] + [TestCase(new[] { 0, 0, 1, 1, 2, -1, 2, 0, 1, 0, 4, 10 }, new[] { 0, 0, 4, 10, 2, -1 })] + public void TestConvexHull(int[] values, int[] expected) + { + var points = new Vector2[values.Length / 2]; + for (int i = 0; i < values.Length; i += 2) + points[i / 2] = new Vector2(values[i], values[i + 1]); + + var expectedPoints = new Vector2[expected.Length / 2]; + for (int i = 0; i < expected.Length; i += 2) + expectedPoints[i / 2] = new Vector2(expected[i], expected[i + 1]); + + var hull = GeometryUtils.GetConvexHull(points); + + Assert.That(hull, Is.EquivalentTo(expectedPoints)); + } + } +} From f1adc6f98cc48024a0bb35811955fb5653c3fdf6 Mon Sep 17 00:00:00 2001 From: StanR Date: Thu, 22 Aug 2024 15:59:13 +0500 Subject: [PATCH 152/924] Don't cap max island size, make repetition nerf more lenient on high bpm, adjust balancing --- .../Difficulty/Evaluators/RhythmEvaluator.cs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index d2f58925cd..9ffb4fc3d7 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -60,8 +60,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators } private const int history_time_max = 5 * 1000; // 5 seconds of calculatingRhythmBonus max. - private const double rhythm_multiplier = 1.1; - private const int max_island_size = 7; + private const double rhythm_multiplier = 1.05; /// /// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current . @@ -103,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators double prevDelta = prevObj.StrainTime; double lastDelta = lastObj.StrainTime; - double currRatio = 1.0 + 8.4 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses. + double currRatio = 1.0 + 8.8 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses. double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - currObj.HitWindowGreat * 0.3) / (currObj.HitWindowGreat * 0.3)); @@ -117,11 +116,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators { if (!(Math.Abs(prevDelta - currDelta) > deltaDifferenceEpsilon)) { - if (island.Deltas.Count < max_island_size) - { - // island is still progressing - island.AddDelta((int)currDelta, deltaDifferenceEpsilon); - } + // island is still progressing + island.AddDelta((int)currDelta, deltaDifferenceEpsilon); } else { @@ -143,7 +139,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators // repeated island (ex: triplet -> triplet) double power = logistic(island.AverageDelta(), 4, 0.165, 10); - effectiveRatio *= Math.Min(1.0 / islandCounts[island], Math.Pow(1.0 / islandCounts[island], power)); + effectiveRatio *= Math.Min(2.0 / islandCounts[island], Math.Pow(1.0 / islandCounts[island], power)); } rhythmComplexitySum += Math.Sqrt(effectiveRatio * startRatio) * currHistoricalDecay; From ce8286d299f11a9e7c96baf67ca38d5a143239d5 Mon Sep 17 00:00:00 2001 From: StanR Date: Sat, 24 Aug 2024 04:37:58 +0500 Subject: [PATCH 153/924] Scale difficulty with doubletapness, make kicksliders not reduce the difficulty of the next object, adjust balancing --- .../Difficulty/Evaluators/RhythmEvaluator.cs | 28 +++++++++++++------ .../Difficulty/Evaluators/SpeedEvaluator.cs | 14 +--------- .../Preprocessing/OsuDifficultyHitObject.cs | 18 ++++++++++++ 3 files changed, 38 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index 9ffb4fc3d7..22b330bcac 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators } private const int history_time_max = 5 * 1000; // 5 seconds of calculatingRhythmBonus max. - private const double rhythm_multiplier = 1.05; + private const double rhythm_multiplier = 0.95; /// /// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current . @@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators double prevDelta = prevObj.StrainTime; double lastDelta = lastObj.StrainTime; - double currRatio = 1.0 + 8.8 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses. + double currRatio = 1.0 + 9.8 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses. double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - currObj.HitWindowGreat * 0.3) / (currObj.HitWindowGreat * 0.3)); @@ -121,16 +121,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators } else { - if (currObj.BaseObject is Slider) // bpm change is into slider, this is easy acc window + // bpm change is into slider, this is easy acc window + if (currObj.BaseObject is Slider) effectiveRatio *= 0.125; - if (prevObj.BaseObject is Slider) // bpm change was from a slider, this is easier typically than circle -> circle - effectiveRatio *= 0.25; + // bpm change was from a slider, this is easier typically than circle -> circle + // unintentional side effect is that bursts with kicksliders at the ends might have lower difficulty than bursts without sliders + // therefore we're checking for quick sliders and don't lower the difficulty for them since they don't really make tapping easier (no time to adjust) + if (prevObj.BaseObject is Slider && prevObj.TravelTime > prevDelta * 1.5) + effectiveRatio *= 0.15; - if (island.IsSimilarPolarity(previousIsland, deltaDifferenceEpsilon)) // repeated island polartiy (2 -> 4, 3 -> 5) - effectiveRatio *= 0.35; + // repeated island polartiy (2 -> 4, 3 -> 5) + if (island.IsSimilarPolarity(previousIsland, deltaDifferenceEpsilon)) + effectiveRatio *= 0.3; - if (lastDelta > prevDelta + deltaDifferenceEpsilon && prevDelta > currDelta + deltaDifferenceEpsilon) // previous increase happened a note ago, 1/1->1/2-1/4, dont want to buff this. + // previous increase happened a note ago, 1/1->1/2-1/4, dont want to buff this. + if (lastDelta > prevDelta + deltaDifferenceEpsilon && prevDelta > currDelta + deltaDifferenceEpsilon) effectiveRatio *= 0.125; if (!islandCounts.TryAdd(island, 1)) @@ -142,6 +148,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators effectiveRatio *= Math.Min(2.0 / islandCounts[island], Math.Pow(1.0 / islandCounts[island], power)); } + // scale down the difficulty if the object is doubletappable + double doubletapness = prevObj.GetDoubletapness((OsuDifficultyHitObject?)prevObj.Next(0)); + effectiveRatio *= 1 - doubletapness * 0.75; + rhythmComplexitySum += Math.Sqrt(effectiveRatio * startRatio) * currHistoricalDecay; startRatio = effectiveRatio; @@ -167,7 +177,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators prevObj = currObj; } - return Math.Sqrt(4 + rhythmComplexitySum * rhythm_multiplier) / 2; //produces multiplier that can be applied to strain. range [1, infinity) (not really though) + return Math.Sqrt(4 + rhythmComplexitySum * rhythm_multiplier) / 2.0; //produces multiplier that can be applied to strain. range [1, infinity) (not really though) } private static double logistic(double x, double maxValue, double multiplier, double offset) => (maxValue / (1 + Math.Pow(Math.E, offset - (multiplier * x)))); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs index 2df383aaa8..3a264188f6 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs @@ -30,21 +30,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators // derive strainTime for calculation var osuCurrObj = (OsuDifficultyHitObject)current; var osuPrevObj = current.Index > 0 ? (OsuDifficultyHitObject)current.Previous(0) : null; - var osuNextObj = (OsuDifficultyHitObject?)current.Next(0); double strainTime = osuCurrObj.StrainTime; - double doubletapness = 1; - - // Nerf doubletappable doubles. - if (osuNextObj != null) - { - double currDeltaTime = Math.Max(1, osuCurrObj.DeltaTime); - double nextDeltaTime = Math.Max(1, osuNextObj.DeltaTime); - double deltaDifference = Math.Abs(nextDeltaTime - currDeltaTime); - double speedRatio = currDeltaTime / Math.Max(currDeltaTime, deltaDifference); - double windowRatio = Math.Pow(Math.Min(1, currDeltaTime / osuCurrObj.HitWindowGreat), 2); - doubletapness = Math.Pow(speedRatio, 1 - windowRatio); - } + double doubletapness = 1.0 - osuCurrObj.GetDoubletapness((OsuDifficultyHitObject?)osuCurrObj.Next(0)); // Cap deltatime to the OD 300 hitwindow. // 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly, whilst 0.92 limits the effect of the cap. diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 95535274c1..3eaf500ad7 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -137,6 +137,24 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing return Math.Clamp((time - fadeInStartTime) / fadeInDuration, 0.0, 1.0); } + /// + /// Returns how possible is it to doubletap this object together with the next one and get perfect judgement in range from 0 to 1 + /// + public double GetDoubletapness(OsuDifficultyHitObject? osuNextObj) + { + if (osuNextObj != null) + { + double currDeltaTime = Math.Max(1, DeltaTime); + double nextDeltaTime = Math.Max(1, osuNextObj.DeltaTime); + double deltaDifference = Math.Abs(nextDeltaTime - currDeltaTime); + double speedRatio = currDeltaTime / Math.Max(currDeltaTime, deltaDifference); + double windowRatio = Math.Pow(Math.Min(1, currDeltaTime / HitWindowGreat), 2); + return 1.0 - Math.Pow(speedRatio, 1 - windowRatio); + } + + return 0; + } + private void setDistances(double clockRate) { if (BaseObject is Slider currentSlider) From ed45c947fca29fd07439e1004ce17ec3069f9021 Mon Sep 17 00:00:00 2001 From: StanR Date: Tue, 27 Aug 2024 15:50:08 +0500 Subject: [PATCH 154/924] Adjust max history by clockrate to make rhythm calculation more consistent between rates --- .../Difficulty/Evaluators/RhythmEvaluator.cs | 28 ++++++++++--------- .../Difficulty/OsuDifficultyCalculator.cs | 2 +- .../Difficulty/Skills/Speed.cs | 6 ++-- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index 22b330bcac..26f50efc72 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -60,12 +60,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators } private const int history_time_max = 5 * 1000; // 5 seconds of calculatingRhythmBonus max. - private const double rhythm_multiplier = 0.95; + private const int history_objects_max = 32; + private const double rhythm_multiplier = 1.25; /// /// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current . /// - public static double EvaluateDifficultyOf(DifficultyHitObject current) + public static double EvaluateDifficultyOf(DifficultyHitObject current, double clockRate) { if (current.BaseObject is Spinner) return 0; @@ -76,15 +77,18 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators var previousIsland = new Island(); Dictionary islandCounts = new Dictionary(); + int historyTimeMaxAdjusted = (int)Math.Ceiling(history_time_max / clockRate); + int historyObjectsMaxAdjusted = (int)Math.Ceiling(history_objects_max / clockRate); + double startRatio = 0; // store the ratio of the current start of an island to buff for tighter rhythms bool firstDeltaSwitch = false; - int historicalNoteCount = Math.Min(current.Index, 32); + int historicalNoteCount = Math.Min(current.Index, historyObjectsMaxAdjusted); int rhythmStart = 0; - while (rhythmStart < historicalNoteCount - 2 && current.StartTime - current.Previous(rhythmStart).StartTime < history_time_max) + while (rhythmStart < historicalNoteCount - 2 && current.StartTime - current.Previous(rhythmStart).StartTime < historyTimeMaxAdjusted) rhythmStart++; OsuDifficultyHitObject prevObj = (OsuDifficultyHitObject)current.Previous(rhythmStart); @@ -94,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators { OsuDifficultyHitObject currObj = (OsuDifficultyHitObject)current.Previous(i - 1); - double currHistoricalDecay = (history_time_max - (current.StartTime - currObj.StartTime)) / history_time_max; // scales note 0 to 1 from history to now + double currHistoricalDecay = (historyTimeMaxAdjusted - (current.StartTime - currObj.StartTime)) / historyTimeMaxAdjusted; // scales note 0 to 1 from history to now currHistoricalDecay = Math.Min((double)(historicalNoteCount - i) / historicalNoteCount, currHistoricalDecay); // either we're limited by time or limited by object count. @@ -102,16 +106,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators double prevDelta = prevObj.StrainTime; double lastDelta = lastObj.StrainTime; - double currRatio = 1.0 + 9.8 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses. - - double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - currObj.HitWindowGreat * 0.3) / (currObj.HitWindowGreat * 0.3)); - - windowPenalty = Math.Min(1, windowPenalty); - - double effectiveRatio = windowPenalty * currRatio; + double currRatio = 1.0 + 7.27 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses. double deltaDifferenceEpsilon = currObj.HitWindowGreat * 0.3; + double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - deltaDifferenceEpsilon) / deltaDifferenceEpsilon); + + double effectiveRatio = windowPenalty * currRatio; + if (firstDeltaSwitch) { if (!(Math.Abs(prevDelta - currDelta) > deltaDifferenceEpsilon)) @@ -145,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators // repeated island (ex: triplet -> triplet) double power = logistic(island.AverageDelta(), 4, 0.165, 10); - effectiveRatio *= Math.Min(2.0 / islandCounts[island], Math.Pow(1.0 / islandCounts[island], power)); + effectiveRatio *= Math.Min(3.0 / islandCounts[island], Math.Pow(1.0 / islandCounts[island], power)); } // scale down the difficulty if the object is doubletappable diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 007cd977e5..a5ef0764bb 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty { new Aim(mods, true), new Aim(mods, false), - new Speed(mods) + new Speed(mods, clockRate) }; if (mods.Any(h => h is OsuModFlashlight)) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 40aac013ab..ae85980815 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -16,6 +16,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// public class Speed : OsuStrainSkill { + private readonly double clockRate; private double skillMultiplier => 1375; private double strainDecayBase => 0.3; @@ -27,9 +28,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private readonly List objectStrains = new List(); - public Speed(Mod[] mods) + public Speed(Mod[] mods, double clockRate) : base(mods) { + this.clockRate = clockRate; } private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); @@ -41,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills currentStrain *= strainDecay(((OsuDifficultyHitObject)current).StrainTime); currentStrain += SpeedEvaluator.EvaluateDifficultyOf(current) * skillMultiplier; - currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current); + currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current, clockRate); double totalStrain = currentStrain * currentRhythm; From 7fda8bc95b8092124820306cab26561e66cda8fe Mon Sep 17 00:00:00 2001 From: StanR Date: Tue, 27 Aug 2024 23:48:15 +0500 Subject: [PATCH 155/924] Reduce repetition nerf for non-consecutive islands --- .../Difficulty/Evaluators/RhythmEvaluator.cs | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index 26f50efc72..9e851f11a6 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -14,19 +14,24 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators { private readonly struct Island : IEquatable { - public Island() + private readonly double deltaDifferenceEpsilon; + + public Island(double epsilon) { + deltaDifferenceEpsilon = epsilon; } public Island(int firstDelta, double epsilon) { - AddDelta(firstDelta, epsilon); + deltaDifferenceEpsilon = epsilon; + AddDelta(firstDelta); } public List Deltas { get; } = new List(); - public void AddDelta(int delta, double epsilon) + public void AddDelta(int delta) { + double epsilon = deltaDifferenceEpsilon; int existingDelta = Deltas.FirstOrDefault(x => Math.Abs(x - delta) >= epsilon); Deltas.Add(existingDelta == default ? delta : existingDelta); @@ -34,10 +39,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators public double AverageDelta() => Deltas.Count > 0 ? Math.Max(Deltas.Average(), OsuDifficultyHitObject.MIN_DELTA_TIME) : 0; - public bool IsSimilarPolarity(Island other, double epsilon) + public bool IsSimilarPolarity(Island other) { // consider islands to be of similar polarity only if they're having the same average delta (we don't want to consider 3 singletaps similar to a triple) - return Math.Abs(AverageDelta() - other.AverageDelta()) < epsilon && + return Math.Abs(AverageDelta() - other.AverageDelta()) < deltaDifferenceEpsilon && Deltas.Count % 2 == other.Deltas.Count % 2; } @@ -61,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators private const int history_time_max = 5 * 1000; // 5 seconds of calculatingRhythmBonus max. private const int history_objects_max = 32; - private const double rhythm_multiplier = 1.25; + private const double rhythm_multiplier = 1.2; /// /// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current . @@ -73,8 +78,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators double rhythmComplexitySum = 0; - var island = new Island(); - var previousIsland = new Island(); + double deltaDifferenceEpsilon = ((OsuDifficultyHitObject)current).HitWindowGreat * 0.3; + + var island = new Island(deltaDifferenceEpsilon); + var previousIsland = new Island(deltaDifferenceEpsilon); Dictionary islandCounts = new Dictionary(); int historyTimeMaxAdjusted = (int)Math.Ceiling(history_time_max / clockRate); @@ -106,9 +113,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators double prevDelta = prevObj.StrainTime; double lastDelta = lastObj.StrainTime; - double currRatio = 1.0 + 7.27 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses. - - double deltaDifferenceEpsilon = currObj.HitWindowGreat * 0.3; + double currRatio = 1.0 + 5.8 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses. double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - deltaDifferenceEpsilon) / deltaDifferenceEpsilon); @@ -119,7 +124,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators if (!(Math.Abs(prevDelta - currDelta) > deltaDifferenceEpsilon)) { // island is still progressing - island.AddDelta((int)currDelta, deltaDifferenceEpsilon); + island.AddDelta((int)currDelta); } else { @@ -131,10 +136,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators // unintentional side effect is that bursts with kicksliders at the ends might have lower difficulty than bursts without sliders // therefore we're checking for quick sliders and don't lower the difficulty for them since they don't really make tapping easier (no time to adjust) if (prevObj.BaseObject is Slider && prevObj.TravelTime > prevDelta * 1.5) - effectiveRatio *= 0.15; + effectiveRatio *= 0.2; // repeated island polartiy (2 -> 4, 3 -> 5) - if (island.IsSimilarPolarity(previousIsland, deltaDifferenceEpsilon)) + if (island.IsSimilarPolarity(previousIsland)) effectiveRatio *= 0.3; // previous increase happened a note ago, 1/1->1/2-1/4, dont want to buff this. @@ -143,7 +148,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators if (!islandCounts.TryAdd(island, 1)) { - islandCounts[island]++; + // only add island to island counts if they're going one after another + if (previousIsland.Equals(island)) + islandCounts[island]++; // repeated island (ex: triplet -> triplet) double power = logistic(island.AverageDelta(), 4, 0.165, 10); From d4bfbe59174a7669d6027d704092ff829cb990d5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 29 Aug 2024 20:21:32 +0900 Subject: [PATCH 156/924] Add test --- .../ManiaScoreProcessorTest.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 osu.Game.Rulesets.Mania.Tests/ManiaScoreProcessorTest.cs diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaScoreProcessorTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaScoreProcessorTest.cs new file mode 100644 index 0000000000..1516aaf058 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/ManiaScoreProcessorTest.cs @@ -0,0 +1,39 @@ +// 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 NUnit.Framework; +using osu.Game.Rulesets.Mania.Scoring; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; + +namespace osu.Game.Rulesets.Mania.Tests +{ + [TestFixture] + public class ManiaScoreProcessorTest + { + [TestCase(ScoreRank.X, 1, HitResult.Perfect)] + [TestCase(ScoreRank.X, 0.99, HitResult.Great)] + [TestCase(ScoreRank.D, 0.1, HitResult.Great)] + [TestCase(ScoreRank.X, 0.99, HitResult.Perfect, HitResult.Great)] + [TestCase(ScoreRank.X, 0.99, HitResult.Great, HitResult.Great)] + [TestCase(ScoreRank.S, 0.99, HitResult.Perfect, HitResult.Good)] + [TestCase(ScoreRank.S, 0.99, HitResult.Perfect, HitResult.Ok)] + [TestCase(ScoreRank.S, 0.99, HitResult.Perfect, HitResult.Meh)] + [TestCase(ScoreRank.S, 0.99, HitResult.Perfect, HitResult.Miss)] + [TestCase(ScoreRank.S, 0.99, HitResult.Great, HitResult.Good)] + [TestCase(ScoreRank.S, 0.99, HitResult.Great, HitResult.Ok)] + [TestCase(ScoreRank.S, 0.99, HitResult.Great, HitResult.Meh)] + [TestCase(ScoreRank.S, 0.99, HitResult.Great, HitResult.Miss)] + public void TestRanks(ScoreRank expected, double accuracy, params HitResult[] results) + { + var scoreProcessor = new ManiaScoreProcessor(); + + Dictionary resultsDict = new Dictionary(); + foreach (var result in results) + resultsDict[result] = resultsDict.GetValueOrDefault(result) + 1; + + Assert.That(scoreProcessor.RankFromScore(accuracy, resultsDict), Is.EqualTo(expected)); + } + } +} From d59d5685d08ef99f15beba32e60f2ebe0c2b9c18 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 29 Aug 2024 20:21:59 +0900 Subject: [PATCH 157/924] Make mania award SS even if there are GREAT judgements --- .../Scoring/ManiaScoreProcessor.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 0444394d87..dfd6ed6dd2 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -9,6 +9,7 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Mania.Scoring { @@ -58,6 +59,24 @@ namespace osu.Game.Rulesets.Mania.Scoring return GetBaseScoreForResult(result); } + public override ScoreRank RankFromScore(double accuracy, IReadOnlyDictionary results) + { + ScoreRank rank = base.RankFromScore(accuracy, results); + + if (rank != ScoreRank.S) + return rank; + + // SS is expected as long as all hitobjects have been hit with either a GREAT or PERFECT result. + + bool anyImperfect = + results.GetValueOrDefault(HitResult.Good) > 0 + || results.GetValueOrDefault(HitResult.Ok) > 0 + || results.GetValueOrDefault(HitResult.Meh) > 0 + || results.GetValueOrDefault(HitResult.Miss) > 0; + + return anyImperfect ? rank : ScoreRank.X; + } + private class JudgementOrderComparer : IComparer { public static readonly JudgementOrderComparer DEFAULT = new JudgementOrderComparer(); From d97a94aadd68aada70a8d808fdb7622e96267e44 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 29 Aug 2024 20:22:54 +0900 Subject: [PATCH 158/924] Change accuracy circle to show badges based on score rank --- osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index cebc54f490..319a87fdfc 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -298,7 +298,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { foreach (var badge in badges) { - if (badge.Accuracy > score.Accuracy) + if (badge.Rank > score.Rank) continue; using (BeginDelayedSequence( From ca2dc702e612986ccb40d7dcfe9fdb5acdb2d79b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 2 Sep 2024 09:52:00 +0200 Subject: [PATCH 159/924] Move helper class out to separate file --- osu.Game/Screens/Edit/Timing/TimingSection.cs | 39 ---------------- .../Edit/Timing/TimingSectionAdjustments.cs | 46 +++++++++++++++++++ 2 files changed, 46 insertions(+), 39 deletions(-) create mode 100644 osu.Game/Screens/Edit/Timing/TimingSectionAdjustments.cs diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 4b47ecaab6..139f53a961 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -1,17 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Utils; -using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.UserInterfaceV2; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Screens.Edit.Timing { @@ -135,37 +129,4 @@ namespace osu.Game.Screens.Edit.Timing private static double beatLengthToBpm(double beatLength) => 60000 / beatLength; } - - public static class TimingSectionAdjustments - { - public static List HitObjectsInTimingRange(IBeatmap beatmap, double time) - { - // If the first group, we grab all hitobjects prior to the next, if the last group, we grab all remaining hitobjects - double startTime = beatmap.ControlPointInfo.TimingPoints.Any(x => x.Time < time) ? time : double.MinValue; - double endTime = beatmap.ControlPointInfo.TimingPoints.FirstOrDefault(x => x.Time > time)?.Time ?? double.MaxValue; - - return beatmap.HitObjects.Where(x => Precision.AlmostBigger(x.StartTime, startTime) && Precision.DefinitelyBigger(endTime, x.StartTime)).ToList(); - } - - public static void AdjustHitObjectOffset(IBeatmap beatmap, TimingControlPoint timingControlPoint, double adjust) - { - foreach (HitObject hitObject in HitObjectsInTimingRange(beatmap, timingControlPoint.Time)) - { - hitObject.StartTime += adjust; - } - } - - public static void SetHitObjectBPM(IBeatmap beatmap, TimingControlPoint timingControlPoint, double oldBeatLength) - { - foreach (HitObject hitObject in HitObjectsInTimingRange(beatmap, timingControlPoint.Time)) - { - double beat = (hitObject.StartTime - timingControlPoint.Time) / oldBeatLength; - - hitObject.StartTime = (beat * timingControlPoint.BeatLength) + timingControlPoint.Time; - - if (hitObject is not IHasRepeats && hitObject is IHasDuration hitObjectWithDuration) - hitObjectWithDuration.Duration *= timingControlPoint.BeatLength / oldBeatLength; - } - } - } } diff --git a/osu.Game/Screens/Edit/Timing/TimingSectionAdjustments.cs b/osu.Game/Screens/Edit/Timing/TimingSectionAdjustments.cs new file mode 100644 index 0000000000..11c4be4790 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/TimingSectionAdjustments.cs @@ -0,0 +1,46 @@ +// 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.Utils; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Screens.Edit.Timing +{ + public static class TimingSectionAdjustments + { + public static List HitObjectsInTimingRange(IBeatmap beatmap, double time) + { + // If the first group, we grab all hitobjects prior to the next, if the last group, we grab all remaining hitobjects + double startTime = beatmap.ControlPointInfo.TimingPoints.Any(x => x.Time < time) ? time : double.MinValue; + double endTime = beatmap.ControlPointInfo.TimingPoints.FirstOrDefault(x => x.Time > time)?.Time ?? double.MaxValue; + + return beatmap.HitObjects.Where(x => Precision.AlmostBigger(x.StartTime, startTime) && Precision.DefinitelyBigger(endTime, x.StartTime)).ToList(); + } + + public static void AdjustHitObjectOffset(IBeatmap beatmap, TimingControlPoint timingControlPoint, double adjust) + { + foreach (HitObject hitObject in HitObjectsInTimingRange(beatmap, timingControlPoint.Time)) + { + hitObject.StartTime += adjust; + } + } + + public static void SetHitObjectBPM(IBeatmap beatmap, TimingControlPoint timingControlPoint, double oldBeatLength) + { + foreach (HitObject hitObject in HitObjectsInTimingRange(beatmap, timingControlPoint.Time)) + { + double beat = (hitObject.StartTime - timingControlPoint.Time) / oldBeatLength; + + hitObject.StartTime = (beat * timingControlPoint.BeatLength) + timingControlPoint.Time; + + if (hitObject is not IHasRepeats && hitObject is IHasDuration hitObjectWithDuration) + hitObjectWithDuration.Duration *= timingControlPoint.BeatLength / oldBeatLength; + } + } + } +} From e61fd080c14602c489d3ef5b4937bc441b2bbc86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 2 Sep 2024 09:59:19 +0200 Subject: [PATCH 160/924] Retouch & document helper methods --- .../Editing/TimingSectionAdjustmentsTest.cs | 10 ++++++++ .../Edit/Timing/TimingSectionAdjustments.cs | 23 +++++++++++++------ 2 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 osu.Game.Tests/Editing/TimingSectionAdjustmentsTest.cs diff --git a/osu.Game.Tests/Editing/TimingSectionAdjustmentsTest.cs b/osu.Game.Tests/Editing/TimingSectionAdjustmentsTest.cs new file mode 100644 index 0000000000..2cb9e72c1f --- /dev/null +++ b/osu.Game.Tests/Editing/TimingSectionAdjustmentsTest.cs @@ -0,0 +1,10 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Tests.Editing +{ + public class TimingSectionAdjustmentsTest + { + + } +} diff --git a/osu.Game/Screens/Edit/Timing/TimingSectionAdjustments.cs b/osu.Game/Screens/Edit/Timing/TimingSectionAdjustments.cs index 11c4be4790..65edc47ff5 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSectionAdjustments.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSectionAdjustments.cs @@ -13,26 +13,35 @@ namespace osu.Game.Screens.Edit.Timing { public static class TimingSectionAdjustments { - public static List HitObjectsInTimingRange(IBeatmap beatmap, double time) + /// + /// Returns all objects from which are affected by the supplied . + /// + public static List HitObjectsInTimingRange(IBeatmap beatmap, TimingControlPoint timingControlPoint) { // If the first group, we grab all hitobjects prior to the next, if the last group, we grab all remaining hitobjects - double startTime = beatmap.ControlPointInfo.TimingPoints.Any(x => x.Time < time) ? time : double.MinValue; - double endTime = beatmap.ControlPointInfo.TimingPoints.FirstOrDefault(x => x.Time > time)?.Time ?? double.MaxValue; + double startTime = beatmap.ControlPointInfo.TimingPoints.Any(x => x.Time < timingControlPoint.Time) ? timingControlPoint.Time : double.MinValue; + double endTime = beatmap.ControlPointInfo.TimingPoints.FirstOrDefault(x => x.Time > timingControlPoint.Time)?.Time ?? double.MaxValue; return beatmap.HitObjects.Where(x => Precision.AlmostBigger(x.StartTime, startTime) && Precision.DefinitelyBigger(endTime, x.StartTime)).ToList(); } - public static void AdjustHitObjectOffset(IBeatmap beatmap, TimingControlPoint timingControlPoint, double adjust) + /// + /// Moves all relevant objects after 's offset has been changed by . + /// + public static void AdjustHitObjectOffset(IBeatmap beatmap, TimingControlPoint timingControlPoint, double adjustment) { - foreach (HitObject hitObject in HitObjectsInTimingRange(beatmap, timingControlPoint.Time)) + foreach (HitObject hitObject in HitObjectsInTimingRange(beatmap, timingControlPoint)) { - hitObject.StartTime += adjust; + hitObject.StartTime += adjustment; } } + /// + /// Ensures all relevant objects are still snapped to the same beats after 's beat length / BPM has been changed. + /// public static void SetHitObjectBPM(IBeatmap beatmap, TimingControlPoint timingControlPoint, double oldBeatLength) { - foreach (HitObject hitObject in HitObjectsInTimingRange(beatmap, timingControlPoint.Time)) + foreach (HitObject hitObject in HitObjectsInTimingRange(beatmap, timingControlPoint)) { double beat = (hitObject.StartTime - timingControlPoint.Time) / oldBeatLength; From db608159cf6a6b1dcbcd56a668765d94a9593c77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 2 Sep 2024 10:27:40 +0200 Subject: [PATCH 161/924] Add test coverage --- .../Editing/TimingSectionAdjustmentsTest.cs | 153 +++++++++++++++++- 1 file changed, 152 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Editing/TimingSectionAdjustmentsTest.cs b/osu.Game.Tests/Editing/TimingSectionAdjustmentsTest.cs index 2cb9e72c1f..5f5a1760ea 100644 --- a/osu.Game.Tests/Editing/TimingSectionAdjustmentsTest.cs +++ b/osu.Game.Tests/Editing/TimingSectionAdjustmentsTest.cs @@ -1,10 +1,161 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit.Timing; + namespace osu.Game.Tests.Editing { + [TestFixture] public class TimingSectionAdjustmentsTest { - + [Test] + public void TestOffsetAdjustment() + { + var controlPoints = new ControlPointInfo(); + + controlPoints.Add(100, new TimingControlPoint { BeatLength = 100 }); + controlPoints.Add(50_000, new TimingControlPoint { BeatLength = 200 }); + controlPoints.Add(100_000, new TimingControlPoint { BeatLength = 50 }); + + var beatmap = new Beatmap + { + ControlPointInfo = controlPoints, + HitObjects = new List + { + new HitCircle { StartTime = 0 }, + new HitCircle { StartTime = 200 }, + new HitCircle { StartTime = 49_900 }, + new HitCircle { StartTime = 50_000 }, + new HitCircle { StartTime = 50_200 }, + new HitCircle { StartTime = 99_800 }, + new HitCircle { StartTime = 100_000 }, + new HitCircle { StartTime = 100_050 }, + new HitCircle { StartTime = 100_550 }, + } + }; + + moveTimingPoint(beatmap, 100, -50); + + Assert.Multiple(() => + { + Assert.That(beatmap.HitObjects[0].StartTime, Is.EqualTo(-50)); + Assert.That(beatmap.HitObjects[1].StartTime, Is.EqualTo(150)); + Assert.That(beatmap.HitObjects[2].StartTime, Is.EqualTo(49_850)); + Assert.That(beatmap.HitObjects[3].StartTime, Is.EqualTo(50_000)); + }); + + moveTimingPoint(beatmap, 50_000, 1_000); + + Assert.Multiple(() => + { + Assert.That(beatmap.HitObjects[2].StartTime, Is.EqualTo(49_850)); + Assert.That(beatmap.HitObjects[3].StartTime, Is.EqualTo(51_000)); + Assert.That(beatmap.HitObjects[4].StartTime, Is.EqualTo(51_200)); + Assert.That(beatmap.HitObjects[5].StartTime, Is.EqualTo(100_800)); + Assert.That(beatmap.HitObjects[6].StartTime, Is.EqualTo(100_000)); + }); + + moveTimingPoint(beatmap, 100_000, 10_000); + + Assert.Multiple(() => + { + Assert.That(beatmap.HitObjects[4].StartTime, Is.EqualTo(51_200)); + Assert.That(beatmap.HitObjects[5].StartTime, Is.EqualTo(110_800)); + Assert.That(beatmap.HitObjects[6].StartTime, Is.EqualTo(110_000)); + Assert.That(beatmap.HitObjects[7].StartTime, Is.EqualTo(110_050)); + Assert.That(beatmap.HitObjects[8].StartTime, Is.EqualTo(110_550)); + }); + } + + [Test] + public void TestBPMAdjustment() + { + var controlPoints = new ControlPointInfo(); + + controlPoints.Add(100, new TimingControlPoint { BeatLength = 100 }); + controlPoints.Add(50_000, new TimingControlPoint { BeatLength = 200 }); + controlPoints.Add(100_000, new TimingControlPoint { BeatLength = 50 }); + + var beatmap = new Beatmap + { + ControlPointInfo = controlPoints, + HitObjects = new List + { + new HitCircle { StartTime = 0 }, + new HitCircle { StartTime = 200 }, + new Spinner { StartTime = 500, EndTime = 1000 }, + new HitCircle { StartTime = 49_900 }, + new HitCircle { StartTime = 50_000 }, + new HitCircle { StartTime = 50_200 }, + new HitCircle { StartTime = 99_800 }, + new HitCircle { StartTime = 100_000 }, + new HitCircle { StartTime = 100_050 }, + new HitCircle { StartTime = 100_550 }, + } + }; + + adjustBeatLength(beatmap, 100, 50); + + Assert.Multiple(() => + { + Assert.That(beatmap.HitObjects[0].StartTime, Is.EqualTo(50)); + Assert.That(beatmap.HitObjects[1].StartTime, Is.EqualTo(150)); + Assert.That(beatmap.HitObjects[2].StartTime, Is.EqualTo(300)); + Assert.That(beatmap.HitObjects[2].GetEndTime(), Is.EqualTo(550)); + Assert.That(beatmap.HitObjects[3].StartTime, Is.EqualTo(25_000)); + Assert.That(beatmap.HitObjects[4].StartTime, Is.EqualTo(50_000)); + }); + + adjustBeatLength(beatmap, 50_000, 400); + + Assert.Multiple(() => + { + Assert.That(beatmap.HitObjects[2].StartTime, Is.EqualTo(300)); + Assert.That(beatmap.HitObjects[2].GetEndTime(), Is.EqualTo(550)); + Assert.That(beatmap.HitObjects[3].StartTime, Is.EqualTo(25_000)); + Assert.That(beatmap.HitObjects[4].StartTime, Is.EqualTo(50_000)); + Assert.That(beatmap.HitObjects[5].StartTime, Is.EqualTo(50_400)); + Assert.That(beatmap.HitObjects[6].StartTime, Is.EqualTo(149_600)); + Assert.That(beatmap.HitObjects[7].StartTime, Is.EqualTo(100_000)); + }); + + adjustBeatLength(beatmap, 100_000, 100); + + Assert.Multiple(() => + { + Assert.That(beatmap.HitObjects[5].StartTime, Is.EqualTo(50_400)); + Assert.That(beatmap.HitObjects[6].StartTime, Is.EqualTo(199_200)); + Assert.That(beatmap.HitObjects[7].StartTime, Is.EqualTo(100_000)); + Assert.That(beatmap.HitObjects[8].StartTime, Is.EqualTo(100_100)); + Assert.That(beatmap.HitObjects[9].StartTime, Is.EqualTo(101_100)); + }); + } + + private static void moveTimingPoint(IBeatmap beatmap, double originalTime, double adjustment) + { + var controlPoints = beatmap.ControlPointInfo; + var controlPointGroup = controlPoints.GroupAt(originalTime); + var timingPoint = controlPointGroup.ControlPoints.OfType().Single(); + controlPoints.RemoveGroup(controlPointGroup); + TimingSectionAdjustments.AdjustHitObjectOffset(beatmap, timingPoint, adjustment); + controlPoints.Add(originalTime - adjustment, timingPoint); + } + + private static void adjustBeatLength(IBeatmap beatmap, double groupTime, double newBeatLength) + { + var controlPoints = beatmap.ControlPointInfo; + var controlPointGroup = controlPoints.GroupAt(groupTime); + var timingPoint = controlPointGroup.ControlPoints.OfType().Single(); + double oldBeatLength = timingPoint.BeatLength; + timingPoint.BeatLength = newBeatLength; + TimingSectionAdjustments.SetHitObjectBPM(beatmap, timingPoint, oldBeatLength); + } } } From 3eaffbb70a0ca053fb3c3443b8a846d817968a70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 2 Sep 2024 10:40:55 +0200 Subject: [PATCH 162/924] Make application of offset/BPM object adjustments more sane --- osu.Game/Screens/Edit/Timing/GroupSection.cs | 6 ++++-- .../Screens/Edit/Timing/TapTimingControl.cs | 17 ++++++++++++++--- osu.Game/Screens/Edit/Timing/TimingSection.cs | 2 ++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/GroupSection.cs b/osu.Game/Screens/Edit/Timing/GroupSection.cs index 18a222f414..abcdf7e4ff 100644 --- a/osu.Game/Screens/Edit/Timing/GroupSection.cs +++ b/osu.Game/Screens/Edit/Timing/GroupSection.cs @@ -113,15 +113,17 @@ namespace osu.Game.Screens.Edit.Timing { // Only adjust hit object offsets if the group contains a timing control point if (Beatmap.AdjustNotesOnOffsetBPMChange.Value && cp is TimingControlPoint tp) + { TimingSectionAdjustments.AdjustHitObjectOffset(Beatmap, tp, time - SelectedGroup.Value.Time); + Beatmap.UpdateAllHitObjects(); + } + Beatmap.ControlPointInfo.Add(time, cp); } // the control point might not necessarily exist yet, if currentGroupItems was empty. SelectedGroup.Value = Beatmap.ControlPointInfo.GroupAt(time, true); - Beatmap.UpdateAllHitObjects(); - changeHandler?.EndChange(); } } diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index f2e37369d1..91a0a43d62 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -202,6 +202,7 @@ namespace osu.Game.Screens.Edit.Timing // VERY TEMPORARY var currentGroupItems = selectedGroup.Value.ControlPoints.ToArray(); + beatmap.BeginChange(); beatmap.ControlPointInfo.RemoveGroup(selectedGroup.Value); double newOffset = selectedGroup.Value.Time + adjust; @@ -209,17 +210,20 @@ namespace osu.Game.Screens.Edit.Timing foreach (var cp in currentGroupItems) { if (beatmap.AdjustNotesOnOffsetBPMChange.Value && cp is TimingControlPoint tp) + { TimingSectionAdjustments.AdjustHitObjectOffset(beatmap, tp, adjust); + beatmap.UpdateAllHitObjects(); + } + beatmap.ControlPointInfo.Add(newOffset, cp); } // the control point might not necessarily exist yet, if currentGroupItems was empty. selectedGroup.Value = beatmap.ControlPointInfo.GroupAt(newOffset, true); + beatmap.EndChange(); if (!editorClock.IsRunning && wasAtStart) editorClock.Seek(newOffset); - - beatmap.UpdateAllHitObjects(); } private void adjustBpm(double adjust) @@ -229,9 +233,16 @@ namespace osu.Game.Screens.Edit.Timing if (timing == null) return; + double oldBeatLength = timing.BeatLength; timing.BeatLength = 60000 / (timing.BPM + adjust); - beatmap.UpdateAllHitObjects(); + if (beatmap.AdjustNotesOnOffsetBPMChange.Value) + { + beatmap.BeginChange(); + TimingSectionAdjustments.SetHitObjectBPM(beatmap, timing, oldBeatLength); + beatmap.UpdateAllHitObjects(); + beatmap.EndChange(); + } } private partial class InlineButton : OsuButton diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 139f53a961..e1567d65aa 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -48,8 +48,10 @@ namespace osu.Game.Screens.Edit.Timing if (!Beatmap.AdjustNotesOnOffsetBPMChange.Value || ControlPoint.Value == null) return; + Beatmap.BeginChange(); TimingSectionAdjustments.SetHitObjectBPM(Beatmap, ControlPoint.Value, val.OldValue); Beatmap.UpdateAllHitObjects(); + Beatmap.EndChange(); }); } From 57f1259a336163aac5439e34c88f5c7a1d35b8ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 2 Sep 2024 10:49:31 +0200 Subject: [PATCH 163/924] Fix weirdness around spurious adjustments firing due to overloaded bindable --- osu.Game/Screens/Edit/Timing/TimingSection.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index e1567d65aa..e668120d0d 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -43,16 +44,16 @@ namespace osu.Game.Screens.Edit.Timing if (!isRebinding) ChangeHandler?.SaveState(); } - bpmTextEntry.Bindable.BindValueChanged(val => + bpmTextEntry.OnCommit = (oldBeatLength, _) => { if (!Beatmap.AdjustNotesOnOffsetBPMChange.Value || ControlPoint.Value == null) return; Beatmap.BeginChange(); - TimingSectionAdjustments.SetHitObjectBPM(Beatmap, ControlPoint.Value, val.OldValue); + TimingSectionAdjustments.SetHitObjectBPM(Beatmap, ControlPoint.Value, oldBeatLength); Beatmap.UpdateAllHitObjects(); Beatmap.EndChange(); - }); + }; } private bool isRebinding; @@ -85,6 +86,8 @@ namespace osu.Game.Screens.Edit.Timing private partial class BPMTextBox : LabelledTextBox { + public new Action? OnCommit { get; set; } + private readonly BindableNumber beatLengthBindable = new TimingControlPoint().BeatLengthBindable; public BPMTextBox() @@ -92,10 +95,12 @@ namespace osu.Game.Screens.Edit.Timing Label = "BPM"; SelectAllOnFocus = true; - OnCommit += (_, isNew) => + base.OnCommit += (_, isNew) => { if (!isNew) return; + double oldBeatLength = beatLengthBindable.Value; + try { if (double.TryParse(Current.Value, out double doubleVal) && doubleVal > 0) @@ -109,6 +114,7 @@ namespace osu.Game.Screens.Edit.Timing // This is run regardless of parsing success as the parsed number may not actually trigger a change // due to bindable clamping. Even in such a case we want to update the textbox to a sane visual state. beatLengthBindable.TriggerChange(); + OnCommit?.Invoke(oldBeatLength, beatLengthBindable.Value); }; beatLengthBindable.BindValueChanged(val => From 3531f646f232eb21ed14b27f31a43884e425d547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 18 Jul 2024 09:46:06 +0200 Subject: [PATCH 164/924] Refactor `DrawableOsuMenuItem` to remove a hack --- .../UserInterface/DrawableOsuMenuItem.cs | 41 +++++++++++-------- .../UserInterface/DrawableStatefulMenuItem.cs | 10 +---- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs index 06ef75cf58..703dcbf3b7 100644 --- a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs @@ -124,7 +124,7 @@ namespace osu.Game.Graphics.UserInterface protected sealed override Drawable CreateContent() => text = CreateTextContainer(); protected virtual TextContainer CreateTextContainer() => new TextContainer(); - protected partial class TextContainer : Container, IHasText + protected partial class TextContainer : FillFlowContainer, IHasText { public LocalisableString Text { @@ -145,25 +145,32 @@ namespace osu.Game.Graphics.UserInterface Origin = Anchor.CentreLeft; AutoSizeAxes = Axes.Both; + Spacing = new Vector2(10); + Direction = FillDirection.Horizontal; - Children = new Drawable[] + Child = new Container { - NormalText = new OsuSpriteText + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Horizontal = MARGIN_HORIZONTAL, Vertical = MARGIN_VERTICAL }, + Children = new Drawable[] { - AlwaysPresent = true, // ensures that the menu item does not change width when switching between normal and bold text. - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: text_size), - Margin = new MarginPadding { Horizontal = MARGIN_HORIZONTAL, Vertical = MARGIN_VERTICAL }, - }, - BoldText = new OsuSpriteText - { - AlwaysPresent = true, // ensures that the menu item does not change width when switching between normal and bold text. - Alpha = 0, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), - Margin = new MarginPadding { Horizontal = MARGIN_HORIZONTAL, Vertical = MARGIN_VERTICAL }, + NormalText = new OsuSpriteText + { + AlwaysPresent = true, // ensures that the menu item does not change width when switching between normal and bold text. + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: text_size), + }, + BoldText = new OsuSpriteText + { + AlwaysPresent = true, // ensures that the menu item does not change width when switching between normal and bold text. + Alpha = 0, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), + } } }; } diff --git a/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs index b9e81e1bf2..6888c2c71b 100644 --- a/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs @@ -51,7 +51,7 @@ namespace osu.Game.Graphics.UserInterface Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Size = new Vector2(10), - Margin = new MarginPadding { Horizontal = MARGIN_HORIZONTAL }, + Margin = new MarginPadding { Left = -MARGIN_HORIZONTAL, Right = MARGIN_HORIZONTAL }, AlwaysPresent = true, }); } @@ -62,14 +62,6 @@ namespace osu.Game.Graphics.UserInterface state.BindValueChanged(updateState, true); } - protected override void Update() - { - base.Update(); - - // Todo: This is bad. This can maybe be done better with a refactor of DrawableOsuMenuItem. - stateIcon.X = BoldText.DrawWidth + 10; - } - private void updateState(ValueChangedEvent state) { var icon = menuItem.GetIconForState(state.NewValue); From 3c6c49187a30dbba31bd1f705d5e6c9aeab0a1c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 18 Jul 2024 10:21:04 +0200 Subject: [PATCH 165/924] Implement component for displaying hotkeys --- .../UserInterface/TestSceneHotkeyDisplay.cs | 28 +++++ osu.Game/Graphics/UserInterface/Hotkey.cs | 53 +++++++++ .../Graphics/UserInterface/HotkeyDisplay.cs | 110 ++++++++++++++++++ osu.Game/OsuGameBase.cs | 1 + 4 files changed, 192 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneHotkeyDisplay.cs create mode 100644 osu.Game/Graphics/UserInterface/Hotkey.cs create mode 100644 osu.Game/Graphics/UserInterface/HotkeyDisplay.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneHotkeyDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneHotkeyDisplay.cs new file mode 100644 index 0000000000..1c2c94dbf1 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneHotkeyDisplay.cs @@ -0,0 +1,28 @@ +// 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.Containers; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Game.Graphics.UserInterface; +using osu.Game.Input.Bindings; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public partial class TestSceneHotkeyDisplay : ThemeComparisonTestScene + { + protected override Drawable CreateContent() => new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Vertical, + Children = new[] + { + new HotkeyDisplay { Hotkey = new Hotkey(new KeyCombination(InputKey.MouseLeft)) }, + new HotkeyDisplay { Hotkey = new Hotkey(GlobalAction.EditorDecreaseDistanceSpacing) }, + new HotkeyDisplay { Hotkey = new Hotkey(PlatformAction.Save) }, + } + }; + } +} diff --git a/osu.Game/Graphics/UserInterface/Hotkey.cs b/osu.Game/Graphics/UserInterface/Hotkey.cs new file mode 100644 index 0000000000..811d385466 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/Hotkey.cs @@ -0,0 +1,53 @@ +// 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.Input; +using osu.Framework.Input.Bindings; +using osu.Framework.Platform; +using osu.Game.Input; +using osu.Game.Input.Bindings; + +namespace osu.Game.Graphics.UserInterface +{ + public struct Hotkey + { + public KeyCombination[]? KeyCombinations { get; } + public GlobalAction? GlobalAction { get; } + public PlatformAction? PlatformAction { get; } + + public Hotkey(params KeyCombination[] keyCombinations) + { + KeyCombinations = keyCombinations; + } + + public Hotkey(GlobalAction globalAction) + { + GlobalAction = globalAction; + } + + public Hotkey(PlatformAction platformAction) + { + PlatformAction = platformAction; + } + + public IEnumerable ResolveKeyCombination(ReadableKeyCombinationProvider keyCombinationProvider, RealmKeyBindingStore keyBindingStore, GameHost gameHost) + { + if (KeyCombinations != null) + return KeyCombinations.Select(keyCombinationProvider.GetReadableString); + + if (GlobalAction != null) + return keyBindingStore.GetReadableKeyCombinationsFor(GlobalAction.Value); + + if (PlatformAction != null) + { + var action = PlatformAction.Value; + var bindings = gameHost.PlatformKeyBindings.Where(kb => (PlatformAction)kb.Action == action); + return bindings.Select(b => keyCombinationProvider.GetReadableString(b.KeyCombination)); + } + + return Enumerable.Empty(); + } + } +} diff --git a/osu.Game/Graphics/UserInterface/HotkeyDisplay.cs b/osu.Game/Graphics/UserInterface/HotkeyDisplay.cs new file mode 100644 index 0000000000..63970249d1 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/HotkeyDisplay.cs @@ -0,0 +1,110 @@ +// 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.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Framework.Platform; +using osu.Game.Graphics.Sprites; +using osu.Game.Input; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Graphics.UserInterface +{ + public partial class HotkeyDisplay : CompositeDrawable + { + private Hotkey hotkey; + + public Hotkey Hotkey + { + get => hotkey; + set + { + if (EqualityComparer.Default.Equals(hotkey, value)) + return; + + hotkey = value; + + if (IsLoaded) + updateState(); + } + } + + private FillFlowContainer flow = null!; + + [Resolved] + private ReadableKeyCombinationProvider readableKeyCombinationProvider { get; set; } = null!; + + [Resolved] + private RealmKeyBindingStore realmKeyBindingStore { get; set; } = null!; + + [Resolved] + private GameHost gameHost { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load() + { + AutoSizeAxes = Axes.Both; + + InternalChild = flow = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5) + }; + + updateState(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + updateState(); + } + + private void updateState() + { + flow.Clear(); + foreach (string h in hotkey.ResolveKeyCombination(readableKeyCombinationProvider, realmKeyBindingStore, gameHost)) + flow.Add(new HotkeyBox(h)); + } + + private partial class HotkeyBox : CompositeDrawable + { + private readonly string hotkey; + + public HotkeyBox(string hotkey) + { + this.hotkey = hotkey; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider? colourProvider, OsuColour colours) + { + AutoSizeAxes = Axes.Both; + Masking = true; + CornerRadius = 3; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider?.Background6 ?? Colour4.Black.Opacity(0.7f), + }, + new OsuSpriteText + { + Margin = new MarginPadding { Horizontal = 5, Bottom = 1, }, + Text = hotkey.ToUpperInvariant(), + Font = OsuFont.Default.With(size: 12, weight: FontWeight.Bold), + Colour = colourProvider?.Light1 ?? colours.GrayA, + } + }; + } + } + } +} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index ce0c288934..e317fac25d 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -409,6 +409,7 @@ namespace osu.Game KeyBindingStore = new RealmKeyBindingStore(realm, keyCombinationProvider); KeyBindingStore.Register(globalBindings, RulesetStore.AvailableRulesets); + dependencies.Cache(KeyBindingStore); dependencies.Cache(globalBindings); From 3acc5fe5a0f10d24228579f393202dae42a6c1e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 18 Jul 2024 11:20:22 +0200 Subject: [PATCH 166/924] Integrate hotkey display into drawable menu items --- .../UserInterface/DrawableOsuMenuItem.cs | 117 +++++++++++------- .../UserInterface/DrawableStatefulMenuItem.cs | 7 +- osu.Game/Graphics/UserInterface/OsuMenu.cs | 2 +- .../Graphics/UserInterface/OsuMenuItem.cs | 2 + .../Edit/Components/Menus/EditorMenuBar.cs | 1 + 5 files changed, 82 insertions(+), 47 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs index 703dcbf3b7..3ecda50537 100644 --- a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs @@ -3,6 +3,7 @@ #nullable disable +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -20,12 +21,13 @@ namespace osu.Game.Graphics.UserInterface { public partial class DrawableOsuMenuItem : Menu.DrawableMenuItem { - public const int MARGIN_HORIZONTAL = 17; + public const int MARGIN_HORIZONTAL = 10; public const int MARGIN_VERTICAL = 4; private const int text_size = 17; private const int transition_length = 80; - private TextContainer text; + protected TextContainer Text { get; private set; } + private HotkeyDisplay hotkey; private HoverClickSounds hoverClickSounds; public DrawableOsuMenuItem(MenuItem item) @@ -39,32 +41,33 @@ namespace osu.Game.Graphics.UserInterface BackgroundColour = Color4.Transparent; BackgroundColourHover = Color4Extensions.FromHex(@"172023"); + AddInternal(hotkey = new HotkeyDisplay + { + Alpha = 0, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Margin = new MarginPadding { Right = 10, Top = 1 }, + }); AddInternal(hoverClickSounds = new HoverClickSounds()); - updateTextColour(); + updateText(); - bool hasSubmenu = Item.Items.Any(); - - // Only add right chevron if direction of menu items is vertical (i.e. width is relative size, see `DrawableMenuItem.SetFlowDirection()`). - if (hasSubmenu && RelativeSizeAxes == Axes.X) + if (showChevron) { AddInternal(new SpriteIcon { - Margin = new MarginPadding(6), + Margin = new MarginPadding { Horizontal = 10, }, Size = new Vector2(8), Icon = FontAwesome.Solid.ChevronRight, Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, }); - - text.Padding = new MarginPadding - { - // Add some padding for the chevron above. - Right = 5, - }; } } + // Only add right chevron if direction of menu items is vertical (i.e. width is relative size, see `DrawableMenuItem.SetFlowDirection()`). + private bool showChevron => Item.Items.Any() && RelativeSizeAxes == Axes.X; + protected override void LoadComplete() { base.LoadComplete(); @@ -73,23 +76,39 @@ namespace osu.Game.Graphics.UserInterface FinishTransforms(); } - private void updateTextColour() + private void updateText() { - switch ((Item as OsuMenuItem)?.Type) + var osuMenuItem = Item as OsuMenuItem; + + switch (osuMenuItem?.Type) { default: case MenuItemType.Standard: - text.Colour = Color4.White; + Text.Colour = Color4.White; break; case MenuItemType.Destructive: - text.Colour = Color4.Red; + Text.Colour = Color4.Red; break; case MenuItemType.Highlighted: - text.Colour = Color4Extensions.FromHex(@"ffcc22"); + Text.Colour = Color4Extensions.FromHex(@"ffcc22"); break; } + + hotkey.Hotkey = osuMenuItem?.Hotkey ?? default; + hotkey.Alpha = EqualityComparer.Default.Equals(hotkey.Hotkey, default) ? 0 : 1; + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + // this hack ensures that the menu can auto-size while leaving enough space for the hotkey display. + // the gist of it is that while the hotkey display is not in the text / "content" that determines sizing + // (because it cannot be, because we want the hotkey display to align to the *right* and not the left), + // enough padding to fit the hotkey with _its_ spacing is added as padding of the text to compensate. + Text.Padding = new MarginPadding { Right = hotkey.Alpha > 0 || showChevron ? hotkey.DrawWidth + 15 : 0 }; } protected override bool OnHover(HoverEvent e) @@ -111,20 +130,20 @@ namespace osu.Game.Graphics.UserInterface if (IsHovered && IsActionable) { - text.BoldText.FadeIn(transition_length, Easing.OutQuint); - text.NormalText.FadeOut(transition_length, Easing.OutQuint); + Text.BoldText.FadeIn(transition_length, Easing.OutQuint); + Text.NormalText.FadeOut(transition_length, Easing.OutQuint); } else { - text.BoldText.FadeOut(transition_length, Easing.OutQuint); - text.NormalText.FadeIn(transition_length, Easing.OutQuint); + Text.BoldText.FadeOut(transition_length, Easing.OutQuint); + Text.NormalText.FadeIn(transition_length, Easing.OutQuint); } } - protected sealed override Drawable CreateContent() => text = CreateTextContainer(); + protected sealed override Drawable CreateContent() => Text = CreateTextContainer(); protected virtual TextContainer CreateTextContainer() => new TextContainer(); - protected partial class TextContainer : FillFlowContainer, IHasText + protected partial class TextContainer : Container, IHasText { public LocalisableString Text { @@ -138,39 +157,53 @@ namespace osu.Game.Graphics.UserInterface public readonly SpriteText NormalText; public readonly SpriteText BoldText; + public readonly Container CheckboxContainer; public TextContainer() { - Anchor = Anchor.CentreLeft; - Origin = Anchor.CentreLeft; - AutoSizeAxes = Axes.Both; - Spacing = new Vector2(10); - Direction = FillDirection.Horizontal; - Child = new Container + Child = new FillFlowContainer { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Horizontal = MARGIN_HORIZONTAL, Vertical = MARGIN_VERTICAL }, + Spacing = new Vector2(10), + Direction = FillDirection.Horizontal, + Padding = new MarginPadding { Horizontal = MARGIN_HORIZONTAL, Vertical = MARGIN_VERTICAL, }, + Children = new Drawable[] { - NormalText = new OsuSpriteText + CheckboxContainer = new Container { - AlwaysPresent = true, // ensures that the menu item does not change width when switching between normal and bold text. - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: text_size), + RelativeSizeAxes = Axes.Y, + Width = MARGIN_HORIZONTAL, }, - BoldText = new OsuSpriteText + new Container { - AlwaysPresent = true, // ensures that the menu item does not change width when switching between normal and bold text. - Alpha = 0, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), - } + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + NormalText = new OsuSpriteText + { + AlwaysPresent = true, // ensures that the menu item does not change width when switching between normal and bold text. + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: text_size), + }, + BoldText = new OsuSpriteText + { + AlwaysPresent = true, // ensures that the menu item does not change width when switching between normal and bold text. + Alpha = 0, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), + } + } + }, } }; } diff --git a/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs index 6888c2c71b..4206f77c98 100644 --- a/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs @@ -46,12 +46,11 @@ namespace osu.Game.Graphics.UserInterface state = menuItem.State.GetBoundCopy(); - Add(stateIcon = new SpriteIcon + CheckboxContainer.Add(stateIcon = new SpriteIcon { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Size = new Vector2(10), - Margin = new MarginPadding { Left = -MARGIN_HORIZONTAL, Right = MARGIN_HORIZONTAL }, AlwaysPresent = true, }); } diff --git a/osu.Game/Graphics/UserInterface/OsuMenu.cs b/osu.Game/Graphics/UserInterface/OsuMenu.cs index e2aac297e3..2b9a26166f 100644 --- a/osu.Game/Graphics/UserInterface/OsuMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuMenu.cs @@ -109,7 +109,7 @@ namespace osu.Game.Graphics.UserInterface Colour = BackgroundColourHover, RelativeSizeAxes = Axes.X, Height = 2f, - Width = 0.8f, + Width = 0.9f, }); } diff --git a/osu.Game/Graphics/UserInterface/OsuMenuItem.cs b/osu.Game/Graphics/UserInterface/OsuMenuItem.cs index 20461de08f..f122990a0f 100644 --- a/osu.Game/Graphics/UserInterface/OsuMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/OsuMenuItem.cs @@ -11,6 +11,8 @@ namespace osu.Game.Graphics.UserInterface { public readonly MenuItemType Type; + public Hotkey Hotkey { get; init; } + public OsuMenuItem(LocalisableString text, MenuItemType type = MenuItemType.Standard) : this(text, type, null) { diff --git a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs index ee954a7ea0..101e5e31b0 100644 --- a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs @@ -92,6 +92,7 @@ namespace osu.Game.Screens.Edit.Components.Menus BackgroundColour = colourProvider.Background2; ForegroundColourHover = colourProvider.Content1; BackgroundColourHover = colourProvider.Background1; + Text.CheckboxContainer.Alpha = 0; } protected override void LoadComplete() From 0c4f5bcdaad0fbffe62a37a1351819482fe93923 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 4 Sep 2024 11:45:29 +0200 Subject: [PATCH 167/924] Decouple editor main menu items from `DrawableOsuMenuItem` It didn't ever really make sense for it to be sharing the implementation details of that (e.g. colouring of primary/dangerous actions), and with the hotkey display things got outright hacky, so I'm decoupling it entirely. --- .../Editing/TestSceneDifficultyDelete.cs | 5 +- .../UserInterface/DrawableOsuMenuItem.cs | 28 +++--- .../Edit/Components/Menus/EditorMenuBar.cs | 91 +++++++++++++++++-- 3 files changed, 101 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultyDelete.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultyDelete.cs index d4bd77642c..62ff59c6b3 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultyDelete.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultyDelete.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Storyboards; using osu.Game.Tests.Beatmaps.IO; using osuTK.Input; @@ -60,7 +61,7 @@ namespace osu.Game.Tests.Visual.Editing beatmapSetHashBefore = Beatmap.Value.BeatmapSetInfo.Hash; }); - AddStep("click File", () => this.ChildrenOfType().First().TriggerClick()); + AddStep("click File", () => this.ChildrenOfType().First().TriggerClick()); if (i == 11) { @@ -107,7 +108,7 @@ namespace osu.Game.Tests.Visual.Editing EditorBeatmap.EndChange(); }); - AddStep("click File", () => this.ChildrenOfType().First().TriggerClick()); + AddStep("click File", () => this.ChildrenOfType().First().TriggerClick()); AddStep("click delete", () => getDeleteMenuItem().TriggerClick()); AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog != null); diff --git a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs index 3ecda50537..20de8e3c9f 100644 --- a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs @@ -23,10 +23,10 @@ namespace osu.Game.Graphics.UserInterface { public const int MARGIN_HORIZONTAL = 10; public const int MARGIN_VERTICAL = 4; - private const int text_size = 17; - private const int transition_length = 80; + public const int TEXT_SIZE = 17; + public const int TRANSITION_LENGTH = 80; - protected TextContainer Text { get; private set; } + private TextContainer text; private HotkeyDisplay hotkey; private HoverClickSounds hoverClickSounds; @@ -84,15 +84,15 @@ namespace osu.Game.Graphics.UserInterface { default: case MenuItemType.Standard: - Text.Colour = Color4.White; + text.Colour = Color4.White; break; case MenuItemType.Destructive: - Text.Colour = Color4.Red; + text.Colour = Color4.Red; break; case MenuItemType.Highlighted: - Text.Colour = Color4Extensions.FromHex(@"ffcc22"); + text.Colour = Color4Extensions.FromHex(@"ffcc22"); break; } @@ -108,7 +108,7 @@ namespace osu.Game.Graphics.UserInterface // the gist of it is that while the hotkey display is not in the text / "content" that determines sizing // (because it cannot be, because we want the hotkey display to align to the *right* and not the left), // enough padding to fit the hotkey with _its_ spacing is added as padding of the text to compensate. - Text.Padding = new MarginPadding { Right = hotkey.Alpha > 0 || showChevron ? hotkey.DrawWidth + 15 : 0 }; + text.Padding = new MarginPadding { Right = hotkey.Alpha > 0 || showChevron ? hotkey.DrawWidth + 15 : 0 }; } protected override bool OnHover(HoverEvent e) @@ -130,17 +130,17 @@ namespace osu.Game.Graphics.UserInterface if (IsHovered && IsActionable) { - Text.BoldText.FadeIn(transition_length, Easing.OutQuint); - Text.NormalText.FadeOut(transition_length, Easing.OutQuint); + text.BoldText.FadeIn(TRANSITION_LENGTH, Easing.OutQuint); + text.NormalText.FadeOut(TRANSITION_LENGTH, Easing.OutQuint); } else { - Text.BoldText.FadeOut(transition_length, Easing.OutQuint); - Text.NormalText.FadeIn(transition_length, Easing.OutQuint); + text.BoldText.FadeOut(TRANSITION_LENGTH, Easing.OutQuint); + text.NormalText.FadeIn(TRANSITION_LENGTH, Easing.OutQuint); } } - protected sealed override Drawable CreateContent() => Text = CreateTextContainer(); + protected sealed override Drawable CreateContent() => text = CreateTextContainer(); protected virtual TextContainer CreateTextContainer() => new TextContainer(); protected partial class TextContainer : Container, IHasText @@ -192,7 +192,7 @@ namespace osu.Game.Graphics.UserInterface AlwaysPresent = true, // ensures that the menu item does not change width when switching between normal and bold text. Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: text_size), + Font = OsuFont.GetFont(size: TEXT_SIZE), }, BoldText = new OsuSpriteText { @@ -200,7 +200,7 @@ namespace osu.Game.Graphics.UserInterface Alpha = 0, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), + Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold), } } }, diff --git a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs index 101e5e31b0..47a13dcfba 100644 --- a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs @@ -7,7 +7,10 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osuTK; @@ -78,8 +81,11 @@ namespace osu.Game.Screens.Edit.Components.Menus protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableEditorBarMenuItem(item); - private partial class DrawableEditorBarMenuItem : DrawableOsuMenuItem + internal partial class DrawableEditorBarMenuItem : DrawableMenuItem { + private HoverClickSounds hoverClickSounds = null!; + private TextContainer text = null!; + public DrawableEditorBarMenuItem(MenuItem item) : base(item) { @@ -92,7 +98,8 @@ namespace osu.Game.Screens.Edit.Components.Menus BackgroundColour = colourProvider.Background2; ForegroundColourHover = colourProvider.Content1; BackgroundColourHover = colourProvider.Background1; - Text.CheckboxContainer.Alpha = 0; + + AddInternal(hoverClickSounds = new HoverClickSounds()); } protected override void LoadComplete() @@ -101,6 +108,36 @@ namespace osu.Game.Screens.Edit.Components.Menus Foreground.Anchor = Anchor.CentreLeft; Foreground.Origin = Anchor.CentreLeft; + Item.Action.BindDisabledChanged(_ => updateState(), true); + } + + protected override bool OnHover(HoverEvent e) + { + updateState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateState(); + base.OnHoverLost(e); + } + + private void updateState() + { + hoverClickSounds.Enabled.Value = IsActionable; + Alpha = IsActionable ? 1 : 0.2f; + + if (IsHovered && IsActionable) + { + text.BoldText.FadeIn(DrawableOsuMenuItem.TRANSITION_LENGTH, Easing.OutQuint); + text.NormalText.FadeOut(DrawableOsuMenuItem.TRANSITION_LENGTH, Easing.OutQuint); + } + else + { + text.BoldText.FadeOut(DrawableOsuMenuItem.TRANSITION_LENGTH, Easing.OutQuint); + text.NormalText.FadeIn(DrawableOsuMenuItem.TRANSITION_LENGTH, Easing.OutQuint); + } } protected override void UpdateBackgroundColour() @@ -119,16 +156,56 @@ namespace osu.Game.Screens.Edit.Components.Menus base.UpdateForegroundColour(); } - protected override DrawableOsuMenuItem.TextContainer CreateTextContainer() => new TextContainer(); + protected sealed override Drawable CreateContent() => text = new TextContainer(); + } - private new partial class TextContainer : DrawableOsuMenuItem.TextContainer + private partial class TextContainer : Container, IHasText + { + public LocalisableString Text { - public TextContainer() + get => NormalText.Text; + set { - NormalText.Font = OsuFont.TorusAlternate; - BoldText.Font = OsuFont.TorusAlternate.With(weight: FontWeight.Bold); + NormalText.Text = value; + BoldText.Text = value; } } + + public readonly SpriteText NormalText; + public readonly SpriteText BoldText; + + public TextContainer() + { + AutoSizeAxes = Axes.Both; + + Child = new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = 17, Vertical = DrawableOsuMenuItem.MARGIN_VERTICAL, }, + + Children = new Drawable[] + { + NormalText = new OsuSpriteText + { + AlwaysPresent = true, // ensures that the menu item does not change width when switching between normal and bold text. + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: DrawableOsuMenuItem.TEXT_SIZE), + }, + BoldText = new OsuSpriteText + { + AlwaysPresent = true, // ensures that the menu item does not change width when switching between normal and bold text. + Alpha = 0, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: DrawableOsuMenuItem.TEXT_SIZE, weight: FontWeight.Bold), + } + } + }; + } } private partial class SubMenu : OsuMenu From 130802e48048c134c6c8f19c77e3e032834acf72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 18 Jul 2024 11:20:31 +0200 Subject: [PATCH 168/924] Add hotkey hints to editor menus --- .../JuiceStreamSelectionBlueprint.cs | 6 +- .../Components/PathControlPointVisualiser.cs | 16 ++++- .../Sliders/SliderSelectionBlueprint.cs | 11 +++- .../Edit/TaikoSelectionHandler.cs | 17 ++++- osu.Game/Graphics/UserInterface/Hotkey.cs | 20 ++++-- .../Components/EditorSelectionHandler.cs | 65 +++++++++++++++++-- .../Compose/Components/SelectionHandler.cs | 5 +- osu.Game/Screens/Edit/Editor.cs | 14 ++-- 8 files changed, 124 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs index a492920d3a..3eb8d6c018 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs @@ -8,6 +8,7 @@ using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Catch.Edit.Blueprints.Components; @@ -172,7 +173,10 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints yield return new OsuMenuItem("Add vertex", MenuItemType.Standard, () => { editablePath.AddVertex(rightMouseDownPosition); - }); + }) + { + Hotkey = new Hotkey(new KeyCombination(InputKey.Control, InputKey.MouseLeft)) + }; } protected override void Dispose(bool isDisposing) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index df369dcef5..21d63a0ea3 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -488,8 +488,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components curveTypeItems = new List(); - foreach (PathType? type in path_types) + for (int i = 0; i < path_types.Length; ++i) { + var type = path_types[i]; + // special inherit case if (type == null) { @@ -499,7 +501,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components curveTypeItems.Add(new OsuMenuItemSpacer()); } - curveTypeItems.Add(createMenuItemForPathType(type)); + curveTypeItems.Add(createMenuItemForPathType(type, InputKey.Number1 + i)); } if (selectedPieces.Any(piece => piece.ControlPoint.Type?.Type == SplineType.Catmull)) @@ -533,7 +535,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components return menuItems.ToArray(); - CurveTypeMenuItem createMenuItemForPathType(PathType? type) => new CurveTypeMenuItem(type, _ => updatePathTypeOfSelectedPieces(type)); + CurveTypeMenuItem createMenuItemForPathType(PathType? type, InputKey? key = null) + { + Hotkey hotkey = default; + + if (key != null) + hotkey = new Hotkey(new KeyCombination(InputKey.Alt, key.Value)); + + return new CurveTypeMenuItem(type, _ => updatePathTypeOfSelectedPieces(type)) { Hotkey = hotkey }; + } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 1debb09099..25b3012d8f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -11,6 +11,7 @@ using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Audio; @@ -593,8 +594,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders changeHandler?.BeginChange(); addControlPoint(lastRightClickPosition); changeHandler?.EndChange(); - }), - new OsuMenuItem("Convert to stream", MenuItemType.Destructive, convertToStream), + }) + { + Hotkey = new Hotkey(new KeyCombination(InputKey.Control, InputKey.MouseLeft)) + }, + new OsuMenuItem("Convert to stream", MenuItemType.Destructive, convertToStream) + { + Hotkey = new Hotkey(new KeyCombination(InputKey.Control, InputKey.Shift, InputKey.F)) + }, }; // Always refer to the drawable object's slider body so subsequent movement deltas are calculated with updated positions. diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs index ae6dced9aa..b706e96bdb 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Bindings; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -86,10 +87,22 @@ namespace osu.Game.Rulesets.Taiko.Edit protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) { if (selection.All(s => s.Item is Hit)) - yield return new TernaryStateToggleMenuItem("Rim") { State = { BindTarget = selectionRimState } }; + { + yield return new TernaryStateToggleMenuItem("Rim") + { + State = { BindTarget = selectionRimState }, + Hotkey = new Hotkey(new KeyCombination(InputKey.W), new KeyCombination(InputKey.R)), + }; + } if (selection.All(s => s.Item is TaikoHitObject)) - yield return new TernaryStateToggleMenuItem("Strong") { State = { BindTarget = selectionStrongState } }; + { + yield return new TernaryStateToggleMenuItem("Strong") + { + State = { BindTarget = selectionStrongState }, + Hotkey = new Hotkey(new KeyCombination(InputKey.E)), + }; + } foreach (var item in base.GetContextMenuItemsForSelection(selection)) yield return item; diff --git a/osu.Game/Graphics/UserInterface/Hotkey.cs b/osu.Game/Graphics/UserInterface/Hotkey.cs index 811d385466..c4c0eb63c1 100644 --- a/osu.Game/Graphics/UserInterface/Hotkey.cs +++ b/osu.Game/Graphics/UserInterface/Hotkey.cs @@ -13,9 +13,9 @@ namespace osu.Game.Graphics.UserInterface { public struct Hotkey { - public KeyCombination[]? KeyCombinations { get; } - public GlobalAction? GlobalAction { get; } - public PlatformAction? PlatformAction { get; } + public KeyCombination[]? KeyCombinations { get; init; } + public GlobalAction? GlobalAction { get; init; } + public PlatformAction? PlatformAction { get; init; } public Hotkey(params KeyCombination[] keyCombinations) { @@ -34,20 +34,26 @@ namespace osu.Game.Graphics.UserInterface public IEnumerable ResolveKeyCombination(ReadableKeyCombinationProvider keyCombinationProvider, RealmKeyBindingStore keyBindingStore, GameHost gameHost) { + var result = new List(); + if (KeyCombinations != null) - return KeyCombinations.Select(keyCombinationProvider.GetReadableString); + { + result.AddRange(KeyCombinations.Select(keyCombinationProvider.GetReadableString)); + } if (GlobalAction != null) - return keyBindingStore.GetReadableKeyCombinationsFor(GlobalAction.Value); + { + result.AddRange(keyBindingStore.GetReadableKeyCombinationsFor(GlobalAction.Value)); + } if (PlatformAction != null) { var action = PlatformAction.Value; var bindings = gameHost.PlatformKeyBindings.Where(kb => (PlatformAction)kb.Action == action); - return bindings.Select(b => keyCombinationProvider.GetReadableString(b.KeyCombination)); + result.AddRange(bindings.Select(b => keyCombinationProvider.GetReadableString(b.KeyCombination))); } - return Enumerable.Empty(); + return result; } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index 472b48425f..dbbf767a7d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -8,6 +8,7 @@ using Humanizer; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Bindings; using osu.Game.Audio; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.UserInterface; @@ -350,19 +351,69 @@ namespace osu.Game.Screens.Edit.Compose.Components { if (SelectedBlueprints.All(b => b.Item is IHasComboInformation)) { - yield return new TernaryStateToggleMenuItem("New combo") { State = { BindTarget = SelectionNewComboState } }; + yield return new TernaryStateToggleMenuItem("New combo") + { + State = { BindTarget = SelectionNewComboState }, + Hotkey = new Hotkey(new KeyCombination(InputKey.Q)) + }; } - yield return new OsuMenuItem("Sample") + yield return new OsuMenuItem("Sample") { Items = getSampleSubmenuItems().ToArray(), }; + yield return new OsuMenuItem("Bank") { Items = getBankSubmenuItems().ToArray(), }; + } + + private IEnumerable getSampleSubmenuItems() + { + var whistle = SelectionSampleStates[HitSampleInfo.HIT_WHISTLE]; + yield return new TernaryStateToggleMenuItem(whistle.Description) { - Items = SelectionSampleStates.Select(kvp => - new TernaryStateToggleMenuItem(kvp.Value.Description) { State = { BindTarget = kvp.Value } }).ToArray() + State = { BindTarget = whistle }, + Hotkey = new Hotkey(new KeyCombination(InputKey.W)) }; - yield return new OsuMenuItem("Bank") + var finish = SelectionSampleStates[HitSampleInfo.HIT_FINISH]; + yield return new TernaryStateToggleMenuItem(finish.Description) { - Items = SelectionBankStates.Select(kvp => - new TernaryStateToggleMenuItem(kvp.Value.Description) { State = { BindTarget = kvp.Value } }).ToArray() + State = { BindTarget = finish }, + Hotkey = new Hotkey(new KeyCombination(InputKey.E)) + }; + + var clap = SelectionSampleStates[HitSampleInfo.HIT_CLAP]; + yield return new TernaryStateToggleMenuItem(clap.Description) + { + State = { BindTarget = clap }, + Hotkey = new Hotkey(new KeyCombination(InputKey.R)) + }; + } + + private IEnumerable getBankSubmenuItems() + { + var auto = SelectionBankStates[HIT_BANK_AUTO]; + yield return new TernaryStateToggleMenuItem(auto.Description) + { + State = { BindTarget = auto }, + Hotkey = new Hotkey(new KeyCombination(InputKey.Shift, InputKey.Q)) + }; + + var normal = SelectionBankStates[HitSampleInfo.BANK_NORMAL]; + yield return new TernaryStateToggleMenuItem(normal.Description) + { + State = { BindTarget = normal }, + Hotkey = new Hotkey(new KeyCombination(InputKey.Shift, InputKey.W)) + }; + + var soft = SelectionBankStates[HitSampleInfo.BANK_SOFT]; + yield return new TernaryStateToggleMenuItem(soft.Description) + { + State = { BindTarget = soft }, + Hotkey = new Hotkey(new KeyCombination(InputKey.Shift, InputKey.E)) + }; + + var drum = SelectionBankStates[HitSampleInfo.BANK_DRUM]; + yield return new TernaryStateToggleMenuItem(drum.Description) + { + State = { BindTarget = drum }, + Hotkey = new Hotkey(new KeyCombination(InputKey.Shift, InputKey.R)) }; } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 98807ad85d..39fff169b7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -415,7 +415,10 @@ namespace osu.Game.Screens.Edit.Compose.Components if (SelectedBlueprints.Count == 1) items.AddRange(SelectedBlueprints[0].ContextMenuItems); - items.Add(new OsuMenuItem(CommonStrings.ButtonsDelete, MenuItemType.Destructive, DeleteSelected)); + items.Add(new OsuMenuItem(CommonStrings.ButtonsDelete, MenuItemType.Destructive, DeleteSelected) + { + Hotkey = new Hotkey { PlatformAction = PlatformAction.Delete, KeyCombinations = [new KeyCombination(InputKey.Shift, InputKey.MouseRight), new KeyCombination(InputKey.MouseMiddle)] } + }); return items.ToArray(); } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 9bb91af806..de74fd87cc 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -362,13 +362,13 @@ namespace osu.Game.Screens.Edit { Items = new[] { - undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo), - redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo), + undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo) { Hotkey = new Hotkey(PlatformAction.Undo) }, + redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo) { Hotkey = new Hotkey(PlatformAction.Redo) }, new OsuMenuItemSpacer(), - cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut), - copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy), - pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste), - cloneMenuItem = new EditorMenuItem(CommonStrings.Clone, MenuItemType.Standard, Clone), + cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut) { Hotkey = new Hotkey(PlatformAction.Cut) }, + copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy) { Hotkey = new Hotkey(PlatformAction.Copy) }, + pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste) { Hotkey = new Hotkey(PlatformAction.Paste) }, + cloneMenuItem = new EditorMenuItem(CommonStrings.Clone, MenuItemType.Standard, Clone) { Hotkey = new Hotkey(GlobalAction.EditorCloneSelection) }, } }, new MenuItem(CommonStrings.MenuBarView) @@ -1194,7 +1194,7 @@ namespace osu.Game.Screens.Edit yield return new EditorMenuItem(EditorStrings.DeleteDifficulty, MenuItemType.Standard, deleteDifficulty) { Action = { Disabled = Beatmap.Value.BeatmapSetInfo.Beatmaps.Count < 2 } }; yield return new OsuMenuItemSpacer(); - var save = new EditorMenuItem(WebCommonStrings.ButtonsSave, MenuItemType.Standard, () => attemptMutationOperation(Save)); + var save = new EditorMenuItem(WebCommonStrings.ButtonsSave, MenuItemType.Standard, () => attemptMutationOperation(Save)) { Hotkey = new Hotkey(PlatformAction.Save) }; saveRelatedMenuItems.Add(save); yield return save; From 6f99d839b05602285a9ede38d00666a9db31e9d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 4 Sep 2024 12:41:02 +0200 Subject: [PATCH 169/924] Make struct readonly --- osu.Game/Graphics/UserInterface/Hotkey.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/Hotkey.cs b/osu.Game/Graphics/UserInterface/Hotkey.cs index c4c0eb63c1..0b5176a02e 100644 --- a/osu.Game/Graphics/UserInterface/Hotkey.cs +++ b/osu.Game/Graphics/UserInterface/Hotkey.cs @@ -11,7 +11,7 @@ using osu.Game.Input.Bindings; namespace osu.Game.Graphics.UserInterface { - public struct Hotkey + public readonly struct Hotkey { public KeyCombination[]? KeyCombinations { get; init; } public GlobalAction? GlobalAction { get; init; } From 17760afa6023a9eca18051b4a1e2f22ed0f131aa Mon Sep 17 00:00:00 2001 From: Fabep Date: Wed, 4 Sep 2024 15:29:48 +0200 Subject: [PATCH 170/924] Changed ModCustomisationHeader to inherit from OsuClickableContainer. ModCustomisationHeader changes color depending on state. --- .../Overlays/Mods/ModCustomisationHeader.cs | 43 +++++++++++++++---- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs index 32fd5a37aa..1d40fb3f5c 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -20,17 +19,16 @@ using static osu.Game.Overlays.Mods.ModCustomisationPanel; namespace osu.Game.Overlays.Mods { - public partial class ModCustomisationHeader : OsuHoverContainer + public partial class ModCustomisationHeader : OsuClickableContainer { private Box background = null!; + private Box hoverBackground = null!; private Box backgroundFlash = null!; private SpriteIcon icon = null!; [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; - protected override IEnumerable EffectTargets => new[] { background }; - public readonly Bindable ExpandedState = new Bindable(ModCustomisationPanelState.Collapsed); private readonly ModCustomisationPanel panel; @@ -53,6 +51,13 @@ namespace osu.Game.Overlays.Mods { RelativeSizeAxes = Axes.Both, }, + hoverBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(80).Opacity(180), + Blending = BlendingParameters.Additive, + Alpha = 0, + }, backgroundFlash = new Box { RelativeSizeAxes = Axes.Both, @@ -84,9 +89,6 @@ namespace osu.Game.Overlays.Mods } } }; - - IdleColour = colourProvider.Dark3; - HoverColour = colourProvider.Light4; } protected override void LoadComplete() @@ -109,15 +111,40 @@ namespace osu.Game.Overlays.Mods ExpandedState.BindValueChanged(v => { icon.ScaleTo(v.NewValue > ModCustomisationPanelState.Collapsed ? new Vector2(1, -1) : Vector2.One, 300, Easing.OutQuint); + + switch (v.NewValue) + { + case ModCustomisationPanelState.Collapsed: + background.FadeColour(colourProvider.Dark3, 500, Easing.OutQuint); + break; + + case ModCustomisationPanelState.Expanded: + case ModCustomisationPanelState.ExpandedByMod: + background.FadeColour(colourProvider.Light4, 500, Easing.OutQuint); + break; + } }, true); } protected override bool OnHover(HoverEvent e) { - if (Enabled.Value && panel.ExpandedState.Value == ModCustomisationPanelState.Collapsed) + if (!Enabled.Value) + return base.OnHover(e); + + if (panel.ExpandedState.Value == ModCustomisationPanelState.Collapsed) panel.ExpandedState.Value = ModCustomisationPanelState.Expanded; + hoverBackground.FadeIn(200); + return base.OnHover(e); } + + protected override void OnHoverLost(HoverLostEvent e) + { + if (Enabled.Value) + hoverBackground.FadeOut(200); + + base.OnHoverLost(e); + } } } From a7e1d35f648c449e749fa1346d3de704b87fe4ce Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 11 Sep 2024 12:29:17 +0100 Subject: [PATCH 171/924] Update osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs Co-authored-by: James Wilson --- osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs index 8dfaaca7b2..a0f2a04f40 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs @@ -25,9 +25,9 @@ namespace osu.Game.Rulesets.Difficulty protected const int ATTRIB_ID_SCORE_MULTIPLIER = 15; protected const int ATTRIB_ID_FLASHLIGHT = 17; protected const int ATTRIB_ID_SLIDER_FACTOR = 19; - protected const int ATTRIB_ID_AIM_DIFFICULT_STRAIN_COUNT = 21; - protected const int ATTRIB_ID_SPEED_DIFFICULT_STRAIN_COUNT = 23; protected const int ATTRIB_ID_SPEED_NOTE_COUNT = 21; + protected const int ATTRIB_ID_SPEED_DIFFICULT_STRAIN_COUNT = 23; + protected const int ATTRIB_ID_AIM_DIFFICULT_STRAIN_COUNT = 25; /// /// The mods which were applied to the beatmap. From 3398497d4b7ef41db750ba2b9db048528836ba18 Mon Sep 17 00:00:00 2001 From: StanR Date: Thu, 12 Sep 2024 01:04:07 +0500 Subject: [PATCH 172/924] Remove kickslider changes --- osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index 9e851f11a6..ac38bc14e1 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -134,8 +134,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators // bpm change was from a slider, this is easier typically than circle -> circle // unintentional side effect is that bursts with kicksliders at the ends might have lower difficulty than bursts without sliders - // therefore we're checking for quick sliders and don't lower the difficulty for them since they don't really make tapping easier (no time to adjust) - if (prevObj.BaseObject is Slider && prevObj.TravelTime > prevDelta * 1.5) + if (prevObj.BaseObject is Slider) effectiveRatio *= 0.2; // repeated island polartiy (2 -> 4, 3 -> 5) From 1292a34b9d9ab0d932c6d56976a1e3b38f789b8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 13 Sep 2024 10:53:43 +0200 Subject: [PATCH 173/924] Add failing test coverage for object nudging --- .../Editing/TestSceneComposerSelection.cs | 48 ++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs index 3884a3108f..ff93a8d83f 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; @@ -78,7 +79,7 @@ namespace osu.Game.Tests.Visual.Editing } [Test] - public void TestNudgeSelection() + public void TestNudgeSelectionTime() { HitCircle[] addedObjects = null!; @@ -99,6 +100,51 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("objects reverted to original position", () => addedObjects[0].StartTime == 100); } + [Test] + public void TestNudgeSelectionPosition() + { + HitCircle addedObject = null!; + + AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[] + { + addedObject = new HitCircle { StartTime = 200, Position = new Vector2(100) }, + })); + + AddStep("select object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject)); + + AddStep("nudge up", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.Up); + InputManager.ReleaseKey(Key.ControlLeft); + }); + AddAssert("object position moved up", () => addedObject.Position.Y, () => Is.EqualTo(99).Within(Precision.FLOAT_EPSILON)); + + AddStep("nudge down", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.Down); + InputManager.ReleaseKey(Key.ControlLeft); + }); + AddAssert("object position moved down", () => addedObject.Position.Y, () => Is.EqualTo(100).Within(Precision.FLOAT_EPSILON)); + + AddStep("nudge left", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.Left); + InputManager.ReleaseKey(Key.ControlLeft); + }); + AddAssert("object position moved left", () => addedObject.Position.X, () => Is.EqualTo(99).Within(Precision.FLOAT_EPSILON)); + + AddStep("nudge right", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.Right); + InputManager.ReleaseKey(Key.ControlLeft); + }); + AddAssert("object position moved right", () => addedObject.Position.X, () => Is.EqualTo(100).Within(Precision.FLOAT_EPSILON)); + } + [Test] public void TestRotateHotkeys() { From 2ccb4a48eb68600559e0fbdd58712ab726211927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 13 Sep 2024 11:18:50 +0200 Subject: [PATCH 174/924] Add test coverage for seeking between objects in editor --- .../Visual/Editing/TestSceneEditorSeeking.cs | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSeeking.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSeeking.cs index da4f159cae..06facc546d 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSeeking.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSeeking.cs @@ -1,11 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; using osuTK.Input; namespace osu.Game.Tests.Visual.Editing @@ -135,9 +137,42 @@ namespace osu.Game.Tests.Visual.Editing pressAndCheckTime(Key.Up, 0); } - private void pressAndCheckTime(Key key, double expectedTime) + [Test] + public void TestSeekBetweenObjects() { - AddStep($"press {key}", () => InputManager.Key(key)); + AddStep("add objects", () => + { + EditorBeatmap.Clear(); + EditorBeatmap.AddRange(new[] + { + new HitCircle { StartTime = 1000, }, + new HitCircle { StartTime = 2250, }, + new HitCircle { StartTime = 3600, }, + }); + }); + AddStep("seek to 0", () => EditorClock.Seek(0)); + + pressAndCheckTime(Key.Right, 1000, Key.ControlLeft); + pressAndCheckTime(Key.Right, 2250, Key.ControlLeft); + pressAndCheckTime(Key.Right, 3600, Key.ControlLeft); + pressAndCheckTime(Key.Right, 3600, Key.ControlLeft); + pressAndCheckTime(Key.Left, 2250, Key.ControlLeft); + pressAndCheckTime(Key.Left, 1000, Key.ControlLeft); + pressAndCheckTime(Key.Left, 1000, Key.ControlLeft); + } + + private void pressAndCheckTime(Key key, double expectedTime, params Key[] modifiers) + { + AddStep($"press {key} with {(modifiers.Any() ? string.Join(',', modifiers) : "no modifiers")}", () => + { + foreach (var modifier in modifiers) + InputManager.PressKey(modifier); + + InputManager.Key(key); + + foreach (var modifier in modifiers) + InputManager.ReleaseKey(modifier); + }); AddUntilStep($"time is {expectedTime}", () => EditorClock.CurrentTime, () => Is.EqualTo(expectedTime).Within(1)); } } From 7f71ef4547981d9d3d432264087fa6a5813cbc46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 13 Sep 2024 15:14:09 +0200 Subject: [PATCH 175/924] Only allow seek to next/previous object via keybinding if there is no selection --- osu.Game/Screens/Edit/Editor.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 9bb91af806..a3549fdec5 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -721,10 +721,16 @@ namespace osu.Game.Screens.Edit switch (e.Action) { case GlobalAction.EditorSeekToPreviousHitObject: + if (editorBeatmap.SelectedHitObjects.Any()) + return false; + seekHitObject(-1); return true; case GlobalAction.EditorSeekToNextHitObject: + if (editorBeatmap.SelectedHitObjects.Any()) + return false; + seekHitObject(1); return true; From 385eb5eed5a4842e0eeb4e4b4dc450d77ec7c407 Mon Sep 17 00:00:00 2001 From: kongehund <63306696+kongehund@users.noreply.github.com> Date: Sat, 14 Sep 2024 16:32:51 +0200 Subject: [PATCH 176/924] Rewrite GetConvexHull --- osu.Game/Utils/GeometryUtils.cs | 76 +++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 23 deletions(-) diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index d4c1dc2db7..4c90421aca 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -152,42 +152,72 @@ namespace osu.Game.Utils /// The points to calculate a convex hull. public static List GetConvexHull(IEnumerable points) { - List p = points.ToList(); + // Naming convention implies positive y upwards. - if (p.Count < 3) - return p; + bool isCCW(Vector2 a, Vector2 b, Vector2 c) => crossProduct(b - a, c - a) > 0; - p.Sort((a, b) => a.X == b.X ? a.Y.CompareTo(b.Y) : a.X.CompareTo(b.X)); + float crossProduct(Vector2 v1, Vector2 v2) => v1.X * v2.Y - v1.Y * v2.X; - List upper = new List(); - List lower = new List(); + var pointsList = points.ToList(); - // Build the lower hull - for (int i = 0; i < p.Count; i++) + pointsList.Sort(delegate (Vector2 point1, Vector2 point2) { - while (lower.Count >= 2 && cross(lower[^2], lower[^1], p[i]) <= 0) - lower.RemoveAt(lower.Count - 1); + if (point1.X == point2.X) + return point1.Y.CompareTo(point2.Y); + return point1.X.CompareTo(point2.X); + }); - lower.Add(p[i]); + if (pointsList.Count < 3) + return pointsList; + + var convexHullUpper = new List + { + pointsList[0], + pointsList[1] + }; + var convexHullLower = new List + { + pointsList[pointsList.Count - 1], + pointsList[pointsList.Count - 2] + }; + + for (int i_points = 2; i_points < pointsList.Count; i_points++) + { + Vector2 c = pointsList[i_points]; + for (int i_hull = convexHullUpper.Count - 1; i_hull > 0; i_hull--) + { + Vector2 a = convexHullUpper[^2]; + Vector2 b = convexHullUpper[^1]; + if (isCCW(a, b, c)) + convexHullUpper.Remove(b); + else + break; + } + convexHullUpper.Add(c); } - // Build the upper hull - for (int i = p.Count - 1; i >= 0; i--) + for (int i_points = pointsList.Count - 3; i_points >= 0; i_points--) { - while (upper.Count >= 2 && cross(upper[^2], upper[^1], p[i]) <= 0) - upper.RemoveAt(upper.Count - 1); - - upper.Add(p[i]); + Vector2 c = pointsList[i_points]; + for (int i_hull = convexHullLower.Count - 1; i_hull > 0; i_hull--) + { + Vector2 a = convexHullLower[^2]; + Vector2 b = convexHullLower[^1]; + if (isCCW(a, b, c)) + convexHullLower.Remove(b); + else + break; + } + convexHullLower.Add(c); } - // Remove the last point of each half because it's a duplicate of the first point of the other half - lower.RemoveAt(lower.Count - 1); - upper.RemoveAt(upper.Count - 1); + convexHullUpper.RemoveAt(convexHullUpper.Count - 1); + convexHullLower.RemoveAt(convexHullLower.Count - 1); - lower.AddRange(upper); - return lower; + convexHullUpper.AddRange(convexHullLower); + var convexHull = convexHullUpper; - float cross(Vector2 o, Vector2 a, Vector2 b) => (a.X - o.X) * (b.Y - o.Y) - (a.Y - o.Y) * (b.X - o.X); + return convexHull; } public static List GetConvexHull(IEnumerable hitObjects) => From 30096c1c7198168c667f85873a60461410df296a Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 14 Sep 2024 17:26:04 +0200 Subject: [PATCH 177/924] clean up code --- osu.Game/Utils/GeometryUtils.cs | 74 ++++++++++++--------------------- 1 file changed, 27 insertions(+), 47 deletions(-) diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index 4c90421aca..8572ac6609 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -152,72 +152,52 @@ namespace osu.Game.Utils /// The points to calculate a convex hull. public static List GetConvexHull(IEnumerable points) { - // Naming convention implies positive y upwards. - - bool isCCW(Vector2 a, Vector2 b, Vector2 c) => crossProduct(b - a, c - a) > 0; - - float crossProduct(Vector2 v1, Vector2 v2) => v1.X * v2.Y - v1.Y * v2.X; - - var pointsList = points.ToList(); - - pointsList.Sort(delegate (Vector2 point1, Vector2 point2) - { - if (point1.X == point2.X) - return point1.Y.CompareTo(point2.Y); - return point1.X.CompareTo(point2.X); - }); + var pointsList = points.OrderBy(p => p.X).ThenBy(p => p.Y).ToList(); if (pointsList.Count < 3) return pointsList; - var convexHullUpper = new List + var convexHullLower = new List { pointsList[0], pointsList[1] }; - var convexHullLower = new List + var convexHullUpper = new List { - pointsList[pointsList.Count - 1], - pointsList[pointsList.Count - 2] + pointsList[^1], + pointsList[^2] }; - for (int i_points = 2; i_points < pointsList.Count; i_points++) + // Build the lower hull. + for (int i = 2; i < pointsList.Count; i++) { - Vector2 c = pointsList[i_points]; - for (int i_hull = convexHullUpper.Count - 1; i_hull > 0; i_hull--) - { - Vector2 a = convexHullUpper[^2]; - Vector2 b = convexHullUpper[^1]; - if (isCCW(a, b, c)) - convexHullUpper.Remove(b); - else - break; - } - convexHullUpper.Add(c); - } + Vector2 c = pointsList[i]; + while (convexHullLower.Count > 1 && isClockwise(convexHullLower[^2], convexHullLower[^1], c)) + convexHullLower.RemoveAt(convexHullLower.Count - 1); - for (int i_points = pointsList.Count - 3; i_points >= 0; i_points--) - { - Vector2 c = pointsList[i_points]; - for (int i_hull = convexHullLower.Count - 1; i_hull > 0; i_hull--) - { - Vector2 a = convexHullLower[^2]; - Vector2 b = convexHullLower[^1]; - if (isCCW(a, b, c)) - convexHullLower.Remove(b); - else - break; - } convexHullLower.Add(c); } - convexHullUpper.RemoveAt(convexHullUpper.Count - 1); + // Build the upper hull. + for (int i = pointsList.Count - 3; i >= 0; i--) + { + Vector2 c = pointsList[i]; + while (convexHullUpper.Count > 1 && isClockwise(convexHullUpper[^2], convexHullUpper[^1], c)) + convexHullUpper.RemoveAt(convexHullUpper.Count - 1); + + convexHullUpper.Add(c); + } + convexHullLower.RemoveAt(convexHullLower.Count - 1); + convexHullUpper.RemoveAt(convexHullUpper.Count - 1); - convexHullUpper.AddRange(convexHullLower); - var convexHull = convexHullUpper; + convexHullLower.AddRange(convexHullUpper); - return convexHull; + return convexHullLower; + + float crossProduct(Vector2 v1, Vector2 v2) => v1.X * v2.Y - v1.Y * v2.X; + + bool isClockwise(Vector2 a, Vector2 b, Vector2 c) => crossProduct(b - a, c - a) >= 0; } public static List GetConvexHull(IEnumerable hitObjects) => From 5e8174faa881d5a54398ddd4c0226f9a18e40f92 Mon Sep 17 00:00:00 2001 From: StanR Date: Sat, 14 Sep 2024 22:03:01 +0500 Subject: [PATCH 178/924] Reduce ratio for island size 1 --- .../Difficulty/Evaluators/RhythmEvaluator.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index ac38bc14e1..bd99799ee0 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators private const int history_time_max = 5 * 1000; // 5 seconds of calculatingRhythmBonus max. private const int history_objects_max = 32; - private const double rhythm_multiplier = 1.2; + private const double rhythm_multiplier = 1.3; /// /// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current . @@ -113,7 +113,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators double prevDelta = prevObj.StrainTime; double lastDelta = lastObj.StrainTime; - double currRatio = 1.0 + 5.8 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses. + double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses. double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - deltaDifferenceEpsilon) / deltaDifferenceEpsilon); @@ -137,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators if (prevObj.BaseObject is Slider) effectiveRatio *= 0.2; - // repeated island polartiy (2 -> 4, 3 -> 5) + // repeated island polarity (2 -> 4, 3 -> 5) if (island.IsSimilarPolarity(previousIsland)) effectiveRatio *= 0.3; @@ -145,6 +145,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators if (lastDelta > prevDelta + deltaDifferenceEpsilon && prevDelta > currDelta + deltaDifferenceEpsilon) effectiveRatio *= 0.125; + // singletaps are easier to control + if (island.Deltas.Count == 1) + effectiveRatio *= 0.7; + if (!islandCounts.TryAdd(island, 1)) { // only add island to island counts if they're going one after another From db626f168ad294b90ea0fbb989b0fc681557b35c Mon Sep 17 00:00:00 2001 From: StanR Date: Sun, 15 Sep 2024 01:12:41 +0500 Subject: [PATCH 179/924] Refactor --- .../Difficulty/Evaluators/RhythmEvaluator.cs | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index bd99799ee0..fb1374eccf 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Objects; @@ -12,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators { public static class RhythmEvaluator { - private readonly struct Island : IEquatable + private struct Island : IEquatable { private readonly double deltaDifferenceEpsilon; @@ -21,41 +20,39 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators deltaDifferenceEpsilon = epsilon; } - public Island(int firstDelta, double epsilon) + public Island(int delta, double epsilon) { deltaDifferenceEpsilon = epsilon; - AddDelta(firstDelta); + Delta = Math.Max(delta, OsuDifficultyHitObject.MIN_DELTA_TIME); } - public List Deltas { get; } = new List(); + public int Delta { get; private set; } + public int DeltaCount { get; private set; } public void AddDelta(int delta) { - double epsilon = deltaDifferenceEpsilon; - int existingDelta = Deltas.FirstOrDefault(x => Math.Abs(x - delta) >= epsilon); + if (Delta == default) + Delta = Math.Max(delta, OsuDifficultyHitObject.MIN_DELTA_TIME); - Deltas.Add(existingDelta == default ? delta : existingDelta); + DeltaCount++; } - public double AverageDelta() => Deltas.Count > 0 ? Math.Max(Deltas.Average(), OsuDifficultyHitObject.MIN_DELTA_TIME) : 0; - public bool IsSimilarPolarity(Island other) { // consider islands to be of similar polarity only if they're having the same average delta (we don't want to consider 3 singletaps similar to a triple) - return Math.Abs(AverageDelta() - other.AverageDelta()) < deltaDifferenceEpsilon && - Deltas.Count % 2 == other.Deltas.Count % 2; + return DeltaCount % 2 == other.DeltaCount % 2 && + Math.Abs(Delta - other.Delta) < deltaDifferenceEpsilon; } public override int GetHashCode() { - // we need to compare all deltas and they must be in the exact same order we added them - string joinedDeltas = string.Join(string.Empty, Deltas); - return joinedDeltas.GetHashCode(); + return HashCode.Combine(Delta, DeltaCount); } public bool Equals(Island other) { - return other.GetHashCode() == GetHashCode(); + return Math.Abs(Delta - other.Delta) < deltaDifferenceEpsilon && + DeltaCount == other.DeltaCount; } public override bool Equals(object? obj) @@ -113,7 +110,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators double prevDelta = prevObj.StrainTime; double lastDelta = lastObj.StrainTime; - double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses. + // calculate how much current delta difference deserves a rhythm bonus + // this function is meant to reduce rhythm bonus for deltas that are multiples of each other (i.e 100 and 200) + double deltaDifferenceRatio = Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta); + double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / deltaDifferenceRatio), 2)); double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - deltaDifferenceEpsilon) / deltaDifferenceEpsilon); @@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators if (firstDeltaSwitch) { - if (!(Math.Abs(prevDelta - currDelta) > deltaDifferenceEpsilon)) + if (Math.Abs(prevDelta - currDelta) < deltaDifferenceEpsilon) { // island is still progressing island.AddDelta((int)currDelta); @@ -146,7 +146,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators effectiveRatio *= 0.125; // singletaps are easier to control - if (island.Deltas.Count == 1) + if (island.DeltaCount == 1) effectiveRatio *= 0.7; if (!islandCounts.TryAdd(island, 1)) @@ -156,7 +156,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators islandCounts[island]++; // repeated island (ex: triplet -> triplet) - double power = logistic(island.AverageDelta(), 4, 0.165, 10); + double power = logistic(island.Delta, 4, 0.165, 10); effectiveRatio *= Math.Min(3.0 / islandCounts[island], Math.Pow(1.0 / islandCounts[island], power)); } From 738d4bcb804e2641a73a774cdd6d329cae91a054 Mon Sep 17 00:00:00 2001 From: StanR Date: Sun, 15 Sep 2024 01:49:52 +0500 Subject: [PATCH 180/924] Reduce ratio if we're starting island counting after a slider --- .../Difficulty/Evaluators/RhythmEvaluator.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index fb1374eccf..5732d924f9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -98,6 +98,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators OsuDifficultyHitObject prevObj = (OsuDifficultyHitObject)current.Previous(rhythmStart); OsuDifficultyHitObject lastObj = (OsuDifficultyHitObject)current.Previous(rhythmStart + 1); + // we go from the furthest object back to the current one for (int i = rhythmStart; i > 0; i--) { OsuDifficultyHitObject currObj = (OsuDifficultyHitObject)current.Previous(i - 1); @@ -171,15 +172,20 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators previousIsland = island; if (prevDelta + deltaDifferenceEpsilon < currDelta) // we're slowing down, stop counting - firstDeltaSwitch = false; // if we're speeding up, this stays true and we keep counting island size. + firstDeltaSwitch = false; // if we're speeding up, this stays true and we keep counting island size. island = new Island((int)currDelta, deltaDifferenceEpsilon); } } - else if (prevDelta > currDelta + deltaDifferenceEpsilon) // we want to be speeding up. + else if (prevDelta > currDelta + deltaDifferenceEpsilon) // we're speeding up { // Begin counting island until we change speed again. firstDeltaSwitch = true; + + // reduce ratio if we're starting after a slider + if (prevObj.BaseObject is Slider) + effectiveRatio *= 0.3; + startRatio = effectiveRatio; island = new Island((int)currDelta, deltaDifferenceEpsilon); From 863a74f9803f787872ddfec69c903d7e81be7d27 Mon Sep 17 00:00:00 2001 From: StanR Date: Sun, 15 Sep 2024 02:08:37 +0500 Subject: [PATCH 181/924] Balancing --- .../Difficulty/Evaluators/RhythmEvaluator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index 5732d924f9..c01530194b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators private const int history_time_max = 5 * 1000; // 5 seconds of calculatingRhythmBonus max. private const int history_objects_max = 32; - private const double rhythm_multiplier = 1.3; + private const double rhythm_multiplier = 1.4; /// /// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current . @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators // calculate how much current delta difference deserves a rhythm bonus // this function is meant to reduce rhythm bonus for deltas that are multiples of each other (i.e 100 and 200) double deltaDifferenceRatio = Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta); - double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / deltaDifferenceRatio), 2)); + double currRatio = 1.0 + 5.5 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / deltaDifferenceRatio), 2)); double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - deltaDifferenceEpsilon) / deltaDifferenceEpsilon); From 145731bdefe9b35fc89a685bbd1eca76c537d850 Mon Sep 17 00:00:00 2001 From: StanR Date: Sun, 15 Sep 2024 02:48:41 +0500 Subject: [PATCH 182/924] More balancing --- .../Difficulty/Evaluators/RhythmEvaluator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index c01530194b..c4d78c7904 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators private const int history_time_max = 5 * 1000; // 5 seconds of calculatingRhythmBonus max. private const int history_objects_max = 32; - private const double rhythm_multiplier = 1.4; + private const double rhythm_multiplier = 1.32; /// /// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current . @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators // calculate how much current delta difference deserves a rhythm bonus // this function is meant to reduce rhythm bonus for deltas that are multiples of each other (i.e 100 and 200) double deltaDifferenceRatio = Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta); - double currRatio = 1.0 + 5.5 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / deltaDifferenceRatio), 2)); + double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / deltaDifferenceRatio), 2)); double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - deltaDifferenceEpsilon) / deltaDifferenceEpsilon); From bee18b03e73ade94c43c8657ae9fa7d81887a06f Mon Sep 17 00:00:00 2001 From: StanR Date: Mon, 16 Sep 2024 00:49:36 +0500 Subject: [PATCH 183/924] Reduce history max overall instead of using clock rate --- .../Difficulty/Evaluators/RhythmEvaluator.cs | 15 ++++++--------- .../Difficulty/OsuDifficultyCalculator.cs | 2 +- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 6 ++---- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index c4d78c7904..09e9011596 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -61,14 +61,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators } } - private const int history_time_max = 5 * 1000; // 5 seconds of calculatingRhythmBonus max. - private const int history_objects_max = 32; + private const int history_time_max = 4 * 1000; // 5 seconds of calculatingRhythmBonus max. + private const int history_objects_max = 24; private const double rhythm_multiplier = 1.32; /// /// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current . /// - public static double EvaluateDifficultyOf(DifficultyHitObject current, double clockRate) + public static double EvaluateDifficultyOf(DifficultyHitObject current) { if (current.BaseObject is Spinner) return 0; @@ -81,18 +81,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators var previousIsland = new Island(deltaDifferenceEpsilon); Dictionary islandCounts = new Dictionary(); - int historyTimeMaxAdjusted = (int)Math.Ceiling(history_time_max / clockRate); - int historyObjectsMaxAdjusted = (int)Math.Ceiling(history_objects_max / clockRate); - double startRatio = 0; // store the ratio of the current start of an island to buff for tighter rhythms bool firstDeltaSwitch = false; - int historicalNoteCount = Math.Min(current.Index, historyObjectsMaxAdjusted); + int historicalNoteCount = Math.Min(current.Index, history_objects_max); int rhythmStart = 0; - while (rhythmStart < historicalNoteCount - 2 && current.StartTime - current.Previous(rhythmStart).StartTime < historyTimeMaxAdjusted) + while (rhythmStart < historicalNoteCount - 2 && current.StartTime - current.Previous(rhythmStart).StartTime < history_time_max) rhythmStart++; OsuDifficultyHitObject prevObj = (OsuDifficultyHitObject)current.Previous(rhythmStart); @@ -103,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators { OsuDifficultyHitObject currObj = (OsuDifficultyHitObject)current.Previous(i - 1); - double currHistoricalDecay = (historyTimeMaxAdjusted - (current.StartTime - currObj.StartTime)) / historyTimeMaxAdjusted; // scales note 0 to 1 from history to now + double currHistoricalDecay = (history_time_max - (current.StartTime - currObj.StartTime)) / history_time_max; // scales note 0 to 1 from history to now currHistoricalDecay = Math.Min((double)(historicalNoteCount - i) / historicalNoteCount, currHistoricalDecay); // either we're limited by time or limited by object count. diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 835f4ee196..e93475ecff 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty { new Aim(mods, true), new Aim(mods, false), - new Speed(mods, clockRate) + new Speed(mods) }; if (mods.Any(h => h is OsuModFlashlight)) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index d0586662ff..f7f081b7ea 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -17,7 +17,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public class Speed : OsuStrainSkill { private double skillMultiplier => 1.375; - private readonly double clockRate; private double strainDecayBase => 0.3; private double currentStrain; @@ -28,10 +27,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private readonly List objectStrains = new List(); - public Speed(Mod[] mods, double clockRate) + public Speed(Mod[] mods) : base(mods) { - this.clockRate = clockRate; } private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); @@ -43,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills currentStrain *= strainDecay(((OsuDifficultyHitObject)current).StrainTime); currentStrain += SpeedEvaluator.EvaluateDifficultyOf(current) * skillMultiplier; - currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current, clockRate); + currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current); double totalStrain = currentStrain * currentRhythm; From c9ce7d29e66f8b25d4d7fad371cb78f6b88121bc Mon Sep 17 00:00:00 2001 From: StanR Date: Mon, 16 Sep 2024 01:04:46 +0500 Subject: [PATCH 184/924] Adjust multipliers --- .../Difficulty/Evaluators/RhythmEvaluator.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index 09e9011596..9eae3c7a10 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -63,7 +63,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators private const int history_time_max = 4 * 1000; // 5 seconds of calculatingRhythmBonus max. private const int history_objects_max = 24; - private const double rhythm_multiplier = 1.32; + private const double rhythm_overall_multiplier = 1.2; + private const double rhythm_ratio_multiplier = 10.0; /// /// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current . @@ -111,7 +112,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators // calculate how much current delta difference deserves a rhythm bonus // this function is meant to reduce rhythm bonus for deltas that are multiples of each other (i.e 100 and 200) double deltaDifferenceRatio = Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta); - double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / deltaDifferenceRatio), 2)); + double currRatio = 1.0 + rhythm_ratio_multiplier * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / deltaDifferenceRatio), 2)); double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - deltaDifferenceEpsilon) / deltaDifferenceEpsilon); @@ -192,7 +193,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators prevObj = currObj; } - return Math.Sqrt(4 + rhythmComplexitySum * rhythm_multiplier) / 2.0; //produces multiplier that can be applied to strain. range [1, infinity) (not really though) + return Math.Sqrt(4 + rhythmComplexitySum * rhythm_overall_multiplier) / 2.0; // produces multiplier that can be applied to strain. range [1, infinity) (not really though) } private static double logistic(double x, double maxValue, double multiplier, double offset) => (maxValue / (1 + Math.Pow(Math.E, offset - (multiplier * x)))); From d34e8ea69e78f80940b2cade87b9c0dba6f066b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Sep 2024 16:15:09 +0900 Subject: [PATCH 185/924] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index d5bdfd91b5..c7ce707562 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index da1cec395f..bb20125282 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + From 785a7255074bb374ccf92b3d6ec68d5c7a3b6117 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Sep 2024 15:12:02 +0900 Subject: [PATCH 186/924] Fix osu!catch fruit rotation being applied too late --- .../Objects/Drawables/DrawableFruit.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs index 52c53523e6..7bac6b588e 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Graphics; using osu.Game.Rulesets.Catch.Skinning.Default; using osu.Game.Skinning; @@ -28,11 +27,10 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables _ => new FruitPiece()); } - protected override void UpdateInitialTransforms() + protected override void OnApply() { - base.UpdateInitialTransforms(); - - ScalingContainer.RotateTo((RandomSingle(1) - 0.5f) * 40); + base.OnApply(); + ScalingContainer.Rotation = (RandomSingle(1) - 0.5f) * 40; } } } From a99dbfa768e56bcad9e6e8ebff295d42f482a4f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 17 Sep 2024 08:21:58 +0200 Subject: [PATCH 187/924] Add failing test step demonstrating incorrect end drag marker position --- .../Editor/TestSceneSliderSelectionBlueprint.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs index c2589f11ef..fa8db51e09 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs @@ -218,6 +218,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("tail positioned correctly", () => Precision.AlmostEquals(blueprint.TailOverlay.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre)); + + AddAssert("end drag marker positioned correctly", + () => Precision.AlmostEquals(blueprint.TailOverlay.EndDragMarker!.ToScreenSpace(blueprint.TailOverlay.EndDragMarker.OriginPosition), drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre, 2)); } private void moveMouseToControlPoint(int index) From 3e63fe399f75f31d35803564e40559f59d4a213a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 17 Sep 2024 08:22:37 +0200 Subject: [PATCH 188/924] Enable NRT in test scene --- .../Editor/TestSceneSliderSelectionBlueprint.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs index fa8db51e09..f0f969b15b 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Framework.Utils; using osu.Game.Beatmaps; @@ -22,9 +20,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { public partial class TestSceneSliderSelectionBlueprint : SelectionBlueprintTestScene { - private Slider slider; - private DrawableSlider drawableObject; - private TestSliderBlueprint blueprint; + private Slider slider = null!; + private DrawableSlider drawableObject = null!; + private TestSliderBlueprint blueprint = null!; [SetUp] public void Setup() => Schedule(() => @@ -233,14 +231,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor } private void checkControlPointSelected(int index, bool selected) - => AddAssert($"control point {index} {(selected ? "selected" : "not selected")}", () => blueprint.ControlPointVisualiser.Pieces[index].IsSelected.Value == selected); + => AddAssert($"control point {index} {(selected ? "selected" : "not selected")}", () => blueprint.ControlPointVisualiser!.Pieces[index].IsSelected.Value == selected); private partial class TestSliderBlueprint : SliderSelectionBlueprint { public new SliderBodyPiece BodyPiece => base.BodyPiece; public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay; public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay; - public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser; + public new PathControlPointVisualiser? ControlPointVisualiser => base.ControlPointVisualiser; public TestSliderBlueprint(Slider slider) : base(slider) From 67a7f608f155aa7c1e5abcd4f17c31ef23028f9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 17 Sep 2024 08:23:46 +0200 Subject: [PATCH 189/924] Fix slider end drag marker being in incorrect position for stacked sliders Closes https://github.com/ppy/osu/issues/29884. --- .../Edit/Blueprints/Sliders/SliderCircleOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs index 247ceb4078..9c2998466a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders if (endDragMarkerContainer != null) { - endDragMarkerContainer.Position = circle.Position; + endDragMarkerContainer.Position = circle.Position + slider.StackOffset; endDragMarkerContainer.Scale = CirclePiece.Scale * 1.2f; var diff = slider.Path.PositionAt(1) - slider.Path.PositionAt(0.99f); endDragMarkerContainer.Rotation = float.RadiansToDegrees(MathF.Atan2(diff.Y, diff.X)); From 2ccdad41e793d9bb982eabeedf6f5be4a08367b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Sep 2024 15:27:16 +0900 Subject: [PATCH 190/924] Also fix banana showers --- .../Objects/Drawables/DrawableBanana.cs | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs index 26e304cf3f..9a4bc45bda 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs @@ -1,10 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; -using osu.Framework.Graphics; +using osu.Framework.Utils; using osu.Game.Rulesets.Catch.Skinning.Default; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Catch.Objects.Drawables { @@ -36,23 +38,37 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables StartTimeBindable.BindValueChanged(_ => UpdateComboColour()); } - protected override void UpdateInitialTransforms() + private float startScale; + private float endScale; + + private float startAngle; + private float endAngle; + + protected override void OnApply() { - base.UpdateInitialTransforms(); + base.OnApply(); const float end_scale = 0.6f; const float random_scale_range = 1.6f; - ScalingContainer.ScaleTo(HitObject.Scale * (end_scale + random_scale_range * RandomSingle(3))) - .Then().ScaleTo(HitObject.Scale * end_scale, HitObject.TimePreempt); + startScale = end_scale + random_scale_range * RandomSingle(3); + endScale = end_scale; - ScalingContainer.RotateTo(getRandomAngle(1)) - .Then() - .RotateTo(getRandomAngle(2), HitObject.TimePreempt); + startAngle = getRandomAngle(1); + endAngle = getRandomAngle(2); float getRandomAngle(int series) => 180 * (RandomSingle(series) * 2 - 1); } + protected override void Update() + { + base.Update(); + + double preemptProgress = Math.Min(1, (Time.Current - (HitObject.StartTime - InitialLifetimeOffset)) / HitObject.TimePreempt); + ScalingContainer.Scale = new Vector2(HitObject.Scale * (float)Interpolation.Lerp(startScale, endScale, preemptProgress)); + ScalingContainer.Rotation = (float)Interpolation.Lerp(startAngle, endAngle, preemptProgress); + } + public override void PlaySamples() { base.PlaySamples(); From 3f4422429da16292cfc8a8b48797be1197507393 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Sep 2024 15:37:54 +0900 Subject: [PATCH 191/924] *Also* fix droplets --- .../Objects/Drawables/DrawableDroplet.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs index 8f32cdcc31..c92fd7cbba 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Graphics; +using osu.Framework.Utils; using osu.Game.Rulesets.Catch.Skinning.Default; using osu.Game.Skinning; @@ -28,15 +28,22 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables _ => new DropletPiece()); } - protected override void UpdateInitialTransforms() + private float startRotation; + + protected override void OnApply() { - base.UpdateInitialTransforms(); + base.OnApply(); // roughly matches osu-stable - float startRotation = RandomSingle(1) * 20; - double duration = HitObject.TimePreempt + 2000; + startRotation = RandomSingle(1) * 20; + } - ScalingContainer.RotateTo(startRotation).RotateTo(startRotation + 720, duration); + protected override void Update() + { + base.Update(); + + double preemptProgress = (Time.Current - (HitObject.StartTime - InitialLifetimeOffset)) / (HitObject.TimePreempt + 2000); + ScalingContainer.Rotation = (float)Interpolation.Lerp(startRotation, startRotation + 720, preemptProgress); } } } From c1c0d49bfeecba12db92f48d8f3c586ab820c3e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Sep 2024 16:15:45 +0900 Subject: [PATCH 192/924] Add comments and fix bananas stopping still if not caught --- .../Objects/Drawables/DrawableBanana.cs | 7 ++++++- .../Objects/Drawables/DrawableDroplet.cs | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs index 9a4bc45bda..f6ecdce616 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs @@ -64,7 +64,12 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables { base.Update(); - double preemptProgress = Math.Min(1, (Time.Current - (HitObject.StartTime - InitialLifetimeOffset)) / HitObject.TimePreempt); + double preemptProgress = (Time.Current - (HitObject.StartTime - InitialLifetimeOffset)) / HitObject.TimePreempt; + + // Clamp scale and rotation at the point of bananas being caught, else let them freely extrapolate. + if (Result.IsHit) + preemptProgress = Math.Min(1, preemptProgress); + ScalingContainer.Scale = new Vector2(HitObject.Scale * (float)Interpolation.Lerp(startScale, endScale, preemptProgress)); ScalingContainer.Rotation = (float)Interpolation.Lerp(startAngle, endAngle, preemptProgress); } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs index c92fd7cbba..73442a502b 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs @@ -42,6 +42,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables { base.Update(); + // No clamping for droplets. They should be considered indefinitely spinning regardless of time. + // They also never end up on the plate, so they shouldn't stop spinning when caught. double preemptProgress = (Time.Current - (HitObject.StartTime - InitialLifetimeOffset)) / (HitObject.TimePreempt + 2000); ScalingContainer.Rotation = (float)Interpolation.Lerp(startRotation, startRotation + 720, preemptProgress); } From f8fff4074ddecafbd79076662a11df71b7cc7610 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Sep 2024 16:18:29 +0900 Subject: [PATCH 193/924] Fix rotation not being updated correctly on start time change --- osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs | 5 +++-- .../Objects/Drawables/DrawableDroplet.cs | 6 +++--- osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs | 6 ++++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs index f6ecdce616..10e483b577 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs @@ -44,10 +44,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables private float startAngle; private float endAngle; - protected override void OnApply() + protected override void UpdateInitialTransforms() { - base.OnApply(); + base.UpdateInitialTransforms(); + // Important to have this in UpdateInitialTransforms() to it is re-triggered by RefreshStateTransforms(). const float end_scale = 0.6f; const float random_scale_range = 1.6f; diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs index 73442a502b..fadd630116 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs @@ -30,11 +30,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables private float startRotation; - protected override void OnApply() + protected override void UpdateInitialTransforms() { - base.OnApply(); + base.UpdateInitialTransforms(); - // roughly matches osu-stable + // Important to have this in UpdateInitialTransforms() to it is re-triggered by RefreshStateTransforms(). startRotation = RandomSingle(1) * 20; } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs index 7bac6b588e..877fae9d67 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs @@ -27,9 +27,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables _ => new FruitPiece()); } - protected override void OnApply() + protected override void UpdateInitialTransforms() { - base.OnApply(); + base.UpdateInitialTransforms(); + + // Important to have this in UpdateInitialTransforms() to it is re-triggered by RefreshStateTransforms(). ScalingContainer.Rotation = (RandomSingle(1) - 0.5f) * 40; } } From e4cfa7c86f793042c311e706ec1c70fcf86a1a9f Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 17 Sep 2024 11:27:23 +0200 Subject: [PATCH 194/924] Add view menu toggle for sample points --- osu.Game/Configuration/OsuConfigManager.cs | 4 +++- osu.Game/Localisation/EditorStrings.cs | 5 +++++ .../Components/Timeline/SamplePointPiece.cs | 15 ++++++++++++++- osu.Game/Screens/Edit/Editor.cs | 6 ++++++ 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 8d6c244b35..ae2025fb50 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -205,6 +205,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.EditorTimelineShowTimingChanges, true); SetDefault(OsuSetting.EditorTimelineShowTicks, true); + SetDefault(OsuSetting.EditorTimelineShowSamples, true); SetDefault(OsuSetting.AlwaysShowHoldForMenuButton, false); } @@ -431,6 +432,7 @@ namespace osu.Game.Configuration HideCountryFlags, EditorTimelineShowTimingChanges, EditorTimelineShowTicks, - AlwaysShowHoldForMenuButton + AlwaysShowHoldForMenuButton, + EditorTimelineShowSamples } } diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs index bcffc18d4d..f72a0f4a26 100644 --- a/osu.Game/Localisation/EditorStrings.cs +++ b/osu.Game/Localisation/EditorStrings.cs @@ -139,6 +139,11 @@ namespace osu.Game.Localisation /// public static LocalisableString TimelineShowTicks => new TranslatableString(getKey(@"timeline_show_ticks"), @"Show ticks"); + /// + /// "Show samples" + /// + public static LocalisableString TimelineShowSamples => new TranslatableString(getKey(@"timeline_show_samples"), @"Show samples"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index a8cf8723f2..3843d56d06 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -16,6 +16,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Audio; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; @@ -40,6 +41,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private Editor? editor { get; set; } + private Bindable samplesVisible = null!; + public SamplePointPiece(HitObject hitObject) { HitObject = hitObject; @@ -53,13 +56,23 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected virtual double GetTime() => HitObject is IHasRepeats r ? HitObject.StartTime + r.Duration / r.SpanCount() / 2 : HitObject.StartTime; [BackgroundDependencyLoader] - private void load() + private void load(OsuConfigManager config) { HitObject.DefaultsApplied += _ => updateText(); updateText(); if (editor != null) editor.ShowSampleEditPopoverRequested += onShowSampleEditPopoverRequested; + + samplesVisible = config.GetBindable(OsuSetting.EditorTimelineShowSamples); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + samplesVisible.BindValueChanged(visible => this.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint)); + this.FadeTo(samplesVisible.Value ? 1 : 0); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 9bb91af806..b059557b28 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -215,6 +215,7 @@ namespace osu.Game.Screens.Edit private Bindable editorLimitedDistanceSnap; private Bindable editorTimelineShowTimingChanges; private Bindable editorTimelineShowTicks; + private Bindable editorTimelineShowSamples; /// /// This controls the opacity of components like the timelines, sidebars, etc. @@ -323,6 +324,7 @@ namespace osu.Game.Screens.Edit editorLimitedDistanceSnap = config.GetBindable(OsuSetting.EditorLimitedDistanceSnap); editorTimelineShowTimingChanges = config.GetBindable(OsuSetting.EditorTimelineShowTimingChanges); editorTimelineShowTicks = config.GetBindable(OsuSetting.EditorTimelineShowTicks); + editorTimelineShowSamples = config.GetBindable(OsuSetting.EditorTimelineShowSamples); AddInternal(new OsuContextMenuContainer { @@ -388,6 +390,10 @@ namespace osu.Game.Screens.Edit { State = { BindTarget = editorTimelineShowTicks } }, + new ToggleMenuItem(EditorStrings.TimelineShowSamples) + { + State = { BindTarget = editorTimelineShowSamples } + } ] }, new BackgroundDimMenuItem(editorBackgroundDim), From 1b17231da47daba7743d868e7ba8f6bb281a3b72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 17 Sep 2024 11:35:16 +0200 Subject: [PATCH 195/924] Implement "form" slider bar control --- .../UserInterface/TestSceneFormControls.cs | 14 + .../Graphics/UserInterface/OsuSliderBar.cs | 6 +- .../Graphics/UserInterfaceV2/FormSliderBar.cs | 330 ++++++++++++++++++ .../Graphics/UserInterfaceV2/FormTextBox.cs | 2 +- 4 files changed, 348 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs index eb8a8b3fe9..6dd7275abf 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs @@ -1,12 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Localisation; +using osu.Game.Resources.Localisation.Web; using osuTK; namespace osu.Game.Tests.Visual.UserInterface @@ -68,6 +70,18 @@ namespace osu.Game.Tests.Visual.UserInterface HintText = EditorSetupStrings.LetterboxDuringBreaksDescription, Current = { Disabled = true }, }, + new FormSliderBar + { + Caption = BeatmapsetsStrings.ShowStatsDrain, + HintText = EditorSetupStrings.DrainRateDescription, + Current = new BindableFloat + { + MinValue = 0, + MaxValue = 10, + Value = 5, + Precision = 0.1f, + } + }, }, }, } diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 9cb6356cab..334fe343ae 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -46,7 +46,7 @@ namespace osu.Game.Graphics.UserInterface protected override void LoadComplete() { base.LoadComplete(); - CurrentNumber.BindValueChanged(current => TooltipText = getTooltipText(current.NewValue), true); + CurrentNumber.BindValueChanged(current => TooltipText = GetDisplayableValue(current.NewValue), true); } protected override void OnUserChange(T value) @@ -55,7 +55,7 @@ namespace osu.Game.Graphics.UserInterface playSample(value); - TooltipText = getTooltipText(value); + TooltipText = GetDisplayableValue(value); } private void playSample(T value) @@ -83,7 +83,7 @@ namespace osu.Game.Graphics.UserInterface channel.Play(); } - private LocalisableString getTooltipText(T value) + public LocalisableString GetDisplayableValue(T value) { if (CurrentNumber.IsInteger) return int.CreateTruncating(value).ToString("N0"); diff --git a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs new file mode 100644 index 0000000000..91ce9da2d2 --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs @@ -0,0 +1,330 @@ +// 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.Globalization; +using System.Numerics; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Framework.Input.Events; +using osu.Framework.Localisation; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public partial class FormSliderBar : CompositeDrawable, IHasCurrentValue + where T : struct, INumber, IMinMaxValue + { + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + private bool instantaneous; + + /// + /// Whether changes to the slider should instantaneously transfer to the text box (and vice versa). + /// If , the transfer will happen on text box commit (explicit, or implicit via focus loss), or on slider drag end. + /// + public bool Instantaneous + { + get => instantaneous; + set + { + instantaneous = value; + slider.TransferValueOnCommit = !instantaneous; + } + } + + private readonly BindableNumberWithCurrent current = new BindableNumberWithCurrent(); + + public LocalisableString Caption { get; init; } + public LocalisableString HintText { get; init; } + + private Box background = null!; + private Box flashLayer = null!; + private FormTextBox.InnerTextBox textBox = null!; + private Slider slider = null!; + private FormFieldCaption caption = null!; + private IFocusManager focusManager = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativeSizeAxes = Axes.X; + Height = 50; + + Masking = true; + CornerRadius = 5; + + InternalChildren = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background5, + }, + flashLayer = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.Transparent, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(9), + Children = new Drawable[] + { + caption = new FormFieldCaption + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + Caption = Caption, + TooltipText = HintText, + }, + textBox = new FormNumberBox.InnerNumberBox + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Width = 0.5f, + CommitOnFocusLost = true, + SelectAllOnFocus = true, + AllowDecimals = true, + OnInputError = () => + { + flashLayer.Colour = ColourInfo.GradientVertical(colours.Red3.Opacity(0), colours.Red3); + flashLayer.FadeOutFromOne(200, Easing.OutQuint); + } + }, + slider = new Slider + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.X, + Width = 0.5f, + Current = Current, + } + }, + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + focusManager = GetContainingFocusManager()!; + + textBox.Focused.BindValueChanged(_ => updateState()); + textBox.OnCommit += textCommitted; + textBox.Current.BindValueChanged(textChanged); + + current.BindValueChanged(_ => + { + updateState(); + updateTextBoxFromSlider(); + }, true); + } + + private bool updatingFromTextBox; + + private void textChanged(ValueChangedEvent change) + { + if (!instantaneous) return; + + tryUpdateSliderFromTextBox(); + } + + private void textCommitted(TextBox t, bool isNew) + { + tryUpdateSliderFromTextBox(); + + // If the attempted update above failed, restore text box to match the slider. + Current.TriggerChange(); + + flashLayer.Colour = ColourInfo.GradientVertical(colourProvider.Dark2.Opacity(0), colourProvider.Dark2); + flashLayer.FadeOutFromOne(800, Easing.OutQuint); + } + + private void tryUpdateSliderFromTextBox() + { + updatingFromTextBox = true; + + try + { + switch (Current) + { + case Bindable bindableInt: + bindableInt.Value = int.Parse(textBox.Current.Value); + break; + + case Bindable bindableDouble: + bindableDouble.Value = double.Parse(textBox.Current.Value); + break; + + default: + Current.Parse(textBox.Current.Value, CultureInfo.CurrentCulture); + break; + } + } + catch + { + // ignore parsing failures. + // sane state will eventually be restored by a commit (either explicit, or implicit via focus loss). + } + + updatingFromTextBox = false; + } + + protected override bool OnHover(HoverEvent e) + { + updateState(); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + updateState(); + } + + protected override bool OnClick(ClickEvent e) + { + focusManager.ChangeFocus(textBox); + return true; + } + + private void updateState() + { + textBox.Alpha = 1; + + background.Colour = Current.Disabled ? colourProvider.Background4 : colourProvider.Background5; + caption.Colour = Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content2; + textBox.Colour = Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content1; + + BorderThickness = IsHovered || textBox.Focused.Value ? 2 : 0; + BorderColour = textBox.Focused.Value ? colourProvider.Highlight1 : colourProvider.Light4; + + if (textBox.Focused.Value) + background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark3); + else if (IsHovered) + background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark4); + else + background.Colour = colourProvider.Background5; + } + + private void updateTextBoxFromSlider() + { + if (updatingFromTextBox) return; + + textBox.Text = slider.GetDisplayableValue(Current.Value).ToString(); + } + + private partial class Slider : OsuSliderBar + { + private Box leftBox = null!; + private Box rightBox = null!; + private Circle nub = null!; + private const float nub_width = 10; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load() + { + Height = 40; + RelativeSizeAxes = Axes.X; + RangePadding = nub_width / 2; + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 5, + Children = new Drawable[] + { + leftBox = new Box + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + rightBox = new Box + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + }, + }, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = RangePadding, }, + Child = nub = new Circle + { + Width = nub_width, + RelativeSizeAxes = Axes.Y, + RelativePositionAxes = Axes.X, + Origin = Anchor.TopCentre, + } + }, + new HoverClickSounds() + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + updateState(); + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + leftBox.Width = Math.Clamp(RangePadding + nub.DrawPosition.X, 0, Math.Max(0, DrawWidth)) / DrawWidth; + rightBox.Width = Math.Clamp(DrawWidth - nub.DrawPosition.X - RangePadding, 0, Math.Max(0, DrawWidth)) / DrawWidth; + } + + protected override bool OnHover(HoverEvent e) + { + updateState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateState(); + base.OnHoverLost(e); + } + + private void updateState() + { + rightBox.Colour = colourProvider.Background6; + leftBox.Colour = IsHovered ? colourProvider.Highlight1.Opacity(0.5f) : colourProvider.Dark2; + nub.Colour = IsHovered ? colourProvider.Highlight1 : colourProvider.Light4; + } + + protected override void UpdateValue(float value) + { + nub.MoveToX(value, 250, Easing.OutQuint); + } + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs index 044576c635..741bff6db6 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs @@ -122,7 +122,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 if (!current.Disabled && !ReadOnly) { - flashLayer.Colour = ColourInfo.GradientVertical(colourProvider.Dark1.Opacity(0), colourProvider.Dark2); + flashLayer.Colour = ColourInfo.GradientVertical(colourProvider.Dark2.Opacity(0), colourProvider.Dark2); flashLayer.FadeOutFromOne(800, Easing.OutQuint); } }; From e0f92bab6a7d981642eca24c3ee2e8d73b19446c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 17 Sep 2024 13:07:52 +0200 Subject: [PATCH 196/924] Add test case covering failure --- .../Editor/TestSceneManiaSelectionHandler.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaSelectionHandler.cs index b48f579ec0..4285ef2029 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaSelectionHandler.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.UI; using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Visual; @@ -92,5 +93,30 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor AddAssert("second object flipped", () => second.StartTime, () => Is.EqualTo(250)); AddAssert("third object flipped", () => third.StartTime, () => Is.EqualTo(1250)); } + + [Test] + public void TestOffScreenObjectsRemainSelectedOnColumnChange() + { + AddStep("create objects", () => + { + for (int i = 0; i < 20; ++i) + EditorBeatmap.Add(new Note { StartTime = 1000 * i, Column = 0 }); + }); + + AddStep("select everything", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); + AddStep("start drag", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().First()); + InputManager.PressButton(MouseButton.Left); + }); + AddStep("end drag", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Last()); + InputManager.ReleaseButton(MouseButton.Left); + }); + + AddAssert("all objects in last column", () => EditorBeatmap.HitObjects.All(ho => ((ManiaHitObject)ho).Column == 3)); + AddAssert("all objects remain selected", () => EditorBeatmap.SelectedHitObjects.SequenceEqual(EditorBeatmap.HitObjects)); + } } } From 20b1d762699ac7cb0bebf705ed7708f4ebc20ef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 17 Sep 2024 13:07:57 +0200 Subject: [PATCH 197/924] Ensure selection is preserved when moving selection between columns Closes https://github.com/ppy/osu/issues/29793. I believe that the sequence of events that makes this happens is as follows: - User selects a range of objects. Some of those objects are off-screen, and thus would be presumed to be not alive - except the blueprint container forces them to remain alive, because they're part of the selection. - User moves the selection to another column, which is implemented by temporarily removing the objects from the playfield, changing their column, and re-adding them. This sort of pattern is supposed to kick off the `HitObjectUsageTransferred` flow in `HitObjectUsageEventBuffer` - and it does... for objects that are *currently visible on screen* and thus would be alive regardless of `SetKeepAlive()`. However, this does not hold for objects that are off-screen - nothing ensures they are kept alive again after re-adding, and thus they inadvertently become dead. - Thus, this doesn't kick off the `BlueprintContainer` flows associated with transferring objects to another column, and instead fires the removal flows, which ensure that the off-screen objects that were being moved are instead deselected. I tried a few other options but found no better resolution than this - calling `SetKeepAlive()` directly would require making it public, which seems like a bad idea. There's really no good way to generically handle this either, because it is the ruleset that decides that its way of implementing this operation will be a removal and re-add of objects, so... --- osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 9ae2112b30..7e0991a4d4 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -104,8 +104,10 @@ namespace osu.Game.Rulesets.Mania.Edit int minColumn = int.MaxValue; int maxColumn = int.MinValue; + var selectedObjects = EditorBeatmap.SelectedHitObjects.OfType().ToArray(); + // find min/max in an initial pass before actually performing the movement. - foreach (var obj in EditorBeatmap.SelectedHitObjects.OfType()) + foreach (var obj in selectedObjects) { if (obj.Column < minColumn) minColumn = obj.Column; @@ -121,6 +123,13 @@ namespace osu.Game.Rulesets.Mania.Edit ((ManiaHitObject)h).Column += columnDelta; maniaPlayfield.Add(h); }); + + // `HitObjectUsageEventBuffer`'s usage transferal flows and the playfield's `SetKeepAlive()` functionality do not combine well with this operation's usage pattern, + // leading to selections being sometimes partially dropped if some of the objects being moved are off screen + // (check blame for detailed explanation). + // thus, ensure that selection is preserved manually. + EditorBeatmap.SelectedHitObjects.Clear(); + EditorBeatmap.SelectedHitObjects.AddRange(selectedObjects); } } } From 76c5e743d7ce834701c07fc15df3a19339a3db32 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Sep 2024 13:49:27 +0900 Subject: [PATCH 198/924] Remove opacity from old style dropdown menus These aren't used in many places, but we've since moved away from opacity in UI elements like this, so let's just nuke it here for legibility. Addresses https://github.com/ppy/osu/discussions/29906. --- osu.Game/Graphics/UserInterface/OsuDropdown.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index 71ae149cf6..dc42216c55 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs @@ -75,7 +75,7 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader(true)] private void load(OverlayColourProvider? colourProvider, OsuColour colours, AudioManager audio) { - BackgroundColour = colourProvider?.Background5 ?? Color4.Black.Opacity(0.5f); + BackgroundColour = colourProvider?.Background5 ?? Color4.Black; HoverColour = colourProvider?.Light4 ?? colours.PinkDarker; SelectionColour = colourProvider?.Background3 ?? colours.PinkDarker.Opacity(0.5f); @@ -397,7 +397,7 @@ namespace osu.Game.Graphics.UserInterface { bool hovered = Enabled.Value && IsHovered; var hoveredColour = colourProvider?.Light4 ?? colours.PinkDarker; - var unhoveredColour = colourProvider?.Background5 ?? Color4.Black.Opacity(0.5f); + var unhoveredColour = colourProvider?.Background5 ?? Color4.Black; Colour = Color4.White; Alpha = Enabled.Value ? 1 : 0.3f; From fd6b3b6b36bd7d88e4aa7e16393f2cbf6f8343d8 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 17 Sep 2024 22:25:18 -0700 Subject: [PATCH 199/924] Fix searching by clicking title/artist in beatmap overlay not following original language setting --- osu.Game/Online/Chat/MessageFormatter.cs | 2 ++ osu.Game/OsuGame.cs | 15 +++++++++++---- .../BeatmapSet/BeatmapSetHeaderContent.cs | 4 ++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 77454c4775..0f444ccde9 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -340,6 +340,8 @@ namespace osu.Game.Online.Chat Spectate, OpenUserProfile, SearchBeatmapSet, + SearchBeatmapTitle, + SearchBeatmapArtist, OpenWiki, Custom, OpenChangelog, diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 0ef6a94679..ffb145d7de 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -445,10 +445,17 @@ namespace osu.Game break; case LinkAction.SearchBeatmapSet: - if (link.Argument is RomanisableString romanisable) - SearchBeatmapSet(romanisable.GetPreferred(Localisation.CurrentParameters.Value.PreferOriginalScript)); - else - SearchBeatmapSet(argString); + SearchBeatmapSet(argString); + break; + + case LinkAction.SearchBeatmapTitle: + string title = ((RomanisableString)link.Argument).GetPreferred(Localisation.CurrentParameters.Value.PreferOriginalScript); + SearchBeatmapSet($@"title=""""{title}"""""); + break; + + case LinkAction.SearchBeatmapArtist: + string artist = ((RomanisableString)link.Argument).GetPreferred(Localisation.CurrentParameters.Value.PreferOriginalScript); + SearchBeatmapSet($@"artist=""""{artist}"""""); break; case LinkAction.FilterBeatmapSetGenre: diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index f9e0c6c380..6ea16a9997 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -242,7 +242,7 @@ namespace osu.Game.Overlays.BeatmapSet title.Clear(); artist.Clear(); - title.AddLink(titleText, LinkAction.SearchBeatmapSet, $@"title=""""{titleText}"""""); + title.AddLink(titleText, LinkAction.SearchBeatmapTitle, titleText); title.AddArbitraryDrawable(Empty().With(d => d.Width = 5)); title.AddArbitraryDrawable(externalLink = new ExternalLinkButton()); @@ -259,7 +259,7 @@ namespace osu.Game.Overlays.BeatmapSet title.AddArbitraryDrawable(new SpotlightBeatmapBadge()); } - artist.AddLink(artistText, LinkAction.SearchBeatmapSet, $@"artist=""""{artistText}"""""); + artist.AddLink(artistText, LinkAction.SearchBeatmapArtist, artistText); if (setInfo.NewValue.TrackId != null) { From 2d993645af3c2cd7f0ee8d7f2dcdb065a0dfb0c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Sep 2024 15:03:55 +0900 Subject: [PATCH 200/924] Add test coverage of judgements not being synced when resuming a replay --- .../Visual/Gameplay/TestSceneSpectator.cs | 11 +++++--- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 ++ .../Visual/Spectator/TestSpectatorClient.cs | 26 +++++++++++++++++-- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 0de2b6a980..d8817e563c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -19,6 +19,7 @@ using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens; using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD.JudgementCounter; using osu.Game.Tests.Beatmaps.IO; using osu.Game.Tests.Gameplay; using osu.Game.Tests.Visual.Multiplayer; @@ -167,14 +168,16 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestSpectatingDuringGameplay() { start(); - sendFrames(300); + sendFrames(300, initialResultCount: 100); loadSpectatingScreen(); waitForPlayerCurrent(); - sendFrames(300); + sendFrames(300, initialResultCount: 100); AddUntilStep("playing from correct point in time", () => player.ChildrenOfType().First().FrameStableClock.CurrentTime, () => Is.GreaterThan(30000)); + AddAssert("check judgement counts are correct", () => player.ChildrenOfType().Single().Counters.Sum(c => c.ResultCount.Value), + () => Is.GreaterThanOrEqualTo(100)); } [Test] @@ -405,9 +408,9 @@ namespace osu.Game.Tests.Visual.Gameplay private void checkPaused(bool state) => AddUntilStep($"game is {(state ? "paused" : "playing")}", () => player.ChildrenOfType().First().IsPaused.Value == state); - private void sendFrames(int count = 10, double startTime = 0) + private void sendFrames(int count = 10, double startTime = 0, int initialResultCount = 0) { - AddStep("send frames", () => spectatorClient.SendFramesFromUser(streamingUser.Id, count, startTime)); + AddStep("send frames", () => spectatorClient.SendFramesFromUser(streamingUser.Id, count, startTime, initialResultCount)); } private void loadSpectatingScreen() diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 44ddb8c187..9752918dfb 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -181,6 +181,8 @@ namespace osu.Game.Rulesets.Scoring } } + public IReadOnlyDictionary Statistics => ScoreResultCounts; + private bool beatmapApplied; protected readonly Dictionary ScoreResultCounts = new Dictionary(); diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index 5aef85fa13..c27e7f15ca 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -13,6 +13,8 @@ using osu.Game.Online.API; using osu.Game.Online.Spectator; using osu.Game.Replays.Legacy; using osu.Game.Rulesets; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -99,13 +101,24 @@ namespace osu.Game.Tests.Visual.Spectator /// The user to send frames for. /// The total number of frames to send. /// The time to start gameplay frames from. - public void SendFramesFromUser(int userId, int count, double startTime = 0) + /// Add a number of misses to frame header data for testing purposes. + public void SendFramesFromUser(int userId, int count, double startTime = 0, int initialResultCount = 0) { var frames = new List(); int currentFrameIndex = userNextFrameDictionary[userId]; int lastFrameIndex = currentFrameIndex + count - 1; + var scoreProcessor = new ScoreProcessor(rulesetStore.GetRuleset(0)!.CreateInstance()); + + for (int i = 0; i < initialResultCount; i++) + { + scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) + { + Type = HitResult.Miss, + }); + } + for (; currentFrameIndex <= lastFrameIndex; currentFrameIndex++) { // This is done in the next frame so that currentFrameIndex is updated to the correct value. @@ -130,7 +143,16 @@ namespace osu.Game.Tests.Visual.Spectator Combo = currentFrameIndex, TotalScore = (long)(currentFrameIndex * 123478 * RNG.NextDouble(0.99, 1.01)), Accuracy = RNG.NextDouble(0.98, 1), - }, new ScoreProcessor(rulesetStore.GetRuleset(0)!.CreateInstance()), frames.ToArray()); + Statistics = scoreProcessor.Statistics.ToDictionary(), + }, scoreProcessor, frames.ToArray()); + + if (initialResultCount > 0) + { + foreach (var f in frames) + f.Header = bundle.Header; + } + + scoreProcessor.ResetFromReplayFrame(frames.Last()); ((ISpectatorClient)this).UserSentFrames(userId, bundle); frames.Clear(); From c46e9cbce3d098dafe1cf5331d533fc00cf2aa1e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Sep 2024 14:35:18 +0900 Subject: [PATCH 201/924] Tidy up `JudgementCounter` classes --- .../HUD/JudgementCounter/JudgementCount.cs | 18 ++++++++++++++++++ .../JudgementCountController.cs | 9 --------- .../HUD/JudgementCounter/JudgementCounter.cs | 7 +++---- .../JudgementCounterDisplay.cs | 2 +- 4 files changed, 22 insertions(+), 14 deletions(-) create mode 100644 osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCount.cs diff --git a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCount.cs b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCount.cs new file mode 100644 index 0000000000..ad70e519a2 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCount.cs @@ -0,0 +1,18 @@ +// 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.Framework.Localisation; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Screens.Play.HUD.JudgementCounter +{ + public struct JudgementCount + { + public LocalisableString DisplayName { get; set; } + + public HitResult[] Types { get; set; } + + public BindableInt ResultCount { get; set; } + } +} diff --git a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCountController.cs b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCountController.cs index 8134c97bac..5a53a9edd3 100644 --- a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCountController.cs +++ b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCountController.cs @@ -67,14 +67,5 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter else count.ResultCount.Value++; } - - public struct JudgementCount - { - public LocalisableString DisplayName { get; set; } - - public HitResult[] Types { get; set; } - - public BindableInt ResultCount { get; set; } - } } } diff --git a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounter.cs b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounter.cs index 45ed8d749b..d69416f34a 100644 --- a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounter.cs +++ b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounter.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets; using osu.Game.Rulesets.Scoring; namespace osu.Game.Screens.Play.HUD.JudgementCounter @@ -19,16 +18,16 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter public BindableBool ShowName = new BindableBool(); public Bindable Direction = new Bindable(); - public readonly JudgementCountController.JudgementCount Result; + public readonly JudgementCount Result; - public JudgementCounter(JudgementCountController.JudgementCount result) => Result = result; + public JudgementCounter(JudgementCount result) => Result = result; public OsuSpriteText ResultName = null!; private FillFlowContainer flowContainer = null!; private JudgementRollingCounter counter = null!; [BackgroundDependencyLoader] - private void load(OsuColour colours, IBindable ruleset) + private void load(OsuColour colours) { AutoSizeAxes = Axes.Both; diff --git a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounterDisplay.cs b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounterDisplay.cs index 25e5464205..bc953435b7 100644 --- a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounterDisplay.cs +++ b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCounterDisplay.cs @@ -126,7 +126,7 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter } } - private JudgementCounter createCounter(JudgementCountController.JudgementCount info) => + private JudgementCounter createCounter(JudgementCount info) => new JudgementCounter(info) { State = { Value = Visibility.Hidden }, From 8f49876fe7458924801338d04545fbf39a34755d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Sep 2024 14:35:44 +0900 Subject: [PATCH 202/924] Re-sync judgement counter display after replay frame reset --- .../JudgementCountController.cs | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCountController.cs b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCountController.cs index 5a53a9edd3..7e9f3cba08 100644 --- a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCountController.cs +++ b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCountController.cs @@ -6,7 +6,6 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Localisation; using osu.Game.Rulesets; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; @@ -53,8 +52,40 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter { base.LoadComplete(); + scoreProcessor.OnResetFromReplayFrame += updateAllCounts; scoreProcessor.NewJudgement += judgement => updateCount(judgement, false); scoreProcessor.JudgementReverted += judgement => updateCount(judgement, true); + + updateAllCounts(); + } + + private void updateAllCounts() + { + // This flow is made to handle cases of watching from the middle of a replay / spectating session. + // + // Once we get an initial state, we can rely on `NewJudgement` and `JudgementReverted`, so + // as a preemptive optimisation, only do a full re-sync if we have all-zero counts. + bool hasCounts = false; + + foreach (var r in results) + { + if (r.Value.ResultCount.Value > 0) + { + hasCounts = true; + break; + } + } + + if (hasCounts) + return; + + foreach (var kvp in scoreProcessor.Statistics) + { + if (!results.TryGetValue(kvp.Key, out var count)) + continue; + + count.ResultCount.Value = kvp.Value; + } } private void updateCount(JudgementResult judgement, bool revert) From aae98e6906450bbd4518e4ec33af8049a0508df4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Sep 2024 16:11:47 +0900 Subject: [PATCH 203/924] Add failing test showing crash at song select on selection edge case --- .../Navigation/TestSceneScreenNavigation.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index f02c2fd4f0..6cd89dcd0c 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -144,6 +144,22 @@ namespace osu.Game.Tests.Visual.Navigation exitViaEscapeAndConfirm(); } + [Test] + public void TestEnterGameplayWhileFilteringToNoSelection() + { + TestPlaySongSelect songSelect = null; + + PushAndConfirm(() => songSelect = new TestPlaySongSelect()); + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + + AddStep("force selection", () => + { + songSelect.FinaliseSelection(); + songSelect.FilterControl.CurrentTextSearch.Value = "test"; + }); + } + [Test] public void TestSongSelectBackActionHandling() { From c192a6a1d54b69936c6e741e49c7b0524c0a2c8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Sep 2024 15:50:55 +0900 Subject: [PATCH 204/924] Fix song select crashes due to attempting to clear selection after load has already begun --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 525884c413..57978b7bbd 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -1036,7 +1036,7 @@ namespace osu.Game.Screens.Select itemsCache.Validate(); // update and let external consumers know about selection loss. - if (BeatmapSetsLoaded) + if (BeatmapSetsLoaded && AllowSelection) { bool selectionLost = selectedBeatmapSet != null && selectedBeatmapSet.State.Value != CarouselItemState.Selected; From 743d50924105a557a0d08424e7c274ee1996d580 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Sep 2024 16:40:27 +0900 Subject: [PATCH 205/924] Also ensure filter is applied when returning to song select --- .../Navigation/TestSceneScreenNavigation.cs | 6 +++++ osu.Game/Screens/Select/SongSelect.cs | 23 ++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 6cd89dcd0c..eda7ce925a 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -150,6 +150,7 @@ namespace osu.Game.Tests.Visual.Navigation TestPlaySongSelect songSelect = null; PushAndConfirm(() => songSelect = new TestPlaySongSelect()); + AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); @@ -158,6 +159,11 @@ namespace osu.Game.Tests.Visual.Navigation songSelect.FinaliseSelection(); songSelect.FilterControl.CurrentTextSearch.Value = "test"; }); + + AddUntilStep("wait for player", () => !songSelect.IsCurrentScreen()); + AddStep("return to song select", () => songSelect.MakeCurrent()); + + AddUntilStep("wait for selection lost", () => songSelect.Beatmap.IsDefault); } [Test] diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 6da72ee660..18608d61e9 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -127,6 +127,8 @@ namespace osu.Game.Screens.Select private Sample sampleChangeDifficulty = null!; private Sample sampleChangeBeatmap = null!; + private bool pendingFilterApplication; + private Container carouselContainer = null!; protected BeatmapDetailArea BeatmapDetails { get; private set; } = null!; @@ -328,7 +330,20 @@ namespace osu.Game.Screens.Select GetRecommendedBeatmap = s => recommender?.GetRecommendedBeatmap(s), }, c => carouselContainer.Child = c); - FilterControl.FilterChanged = Carousel.Filter; + FilterControl.FilterChanged = criteria => + { + // If a filter operation is applied when we're in a state that doesn't allow selection, + // we might end up in an unexpected state. This is because currently carousel panels are in charge + // of updating the global selection (which is very hard to deal with). + // + // For now let's just avoid filtering when selection isn't allowed locally. + // This should be nuked from existence when we get around to fixing the complexity of song select <-> beatmap carousel. + // The debounce part of BeatmapCarousel's filtering should probably also be removed and handled locally. + if (Carousel.AllowSelection) + Carousel.Filter(criteria); + else + pendingFilterApplication = true; + }; if (ShowSongSelectFooter) { @@ -701,6 +716,12 @@ namespace osu.Game.Screens.Select Carousel.AllowSelection = true; + if (pendingFilterApplication) + { + Carousel.Filter(FilterControl.CreateCriteria()); + pendingFilterApplication = false; + } + BeatmapDetails.Refresh(); beginLooping(); From ac507a3ba568e40396a642d13032dbc1e8d6c314 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Sep 2024 17:21:43 +0900 Subject: [PATCH 206/924] Remove unused parameter in `applyActiveCriteria` --- osu.Game/Screens/Select/BeatmapCarousel.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 57978b7bbd..d9359cfec3 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -706,7 +706,7 @@ namespace osu.Game.Screens.Select private bool beatmapsSplitOut; - private void applyActiveCriteria(bool debounce, bool alwaysResetScrollPosition = true) + private void applyActiveCriteria(bool debounce) { PendingFilter?.Cancel(); PendingFilter = null; @@ -735,8 +735,7 @@ namespace osu.Game.Screens.Select root.Filter(activeCriteria); itemsCache.Invalidate(); - if (alwaysResetScrollPosition || !Scroll.UserScrolling) - ScrollToSelected(true); + ScrollToSelected(true); FilterApplied?.Invoke(); } From 95e26e6fd8a548d1d0443e52e838ca68d9bc7319 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 18 Sep 2024 11:23:00 +0200 Subject: [PATCH 207/924] Make slider bar instantaneous by default (and fix broken implementation) --- .../UserInterface/TestSceneFormControls.cs | 16 +++++++++++++--- .../Graphics/UserInterfaceV2/FormSliderBar.cs | 8 ++++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs index 6dd7275abf..369fe1a40c 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Cursor; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Localisation; -using osu.Game.Resources.Localisation.Web; using osuTK; namespace osu.Game.Tests.Visual.UserInterface @@ -72,8 +71,7 @@ namespace osu.Game.Tests.Visual.UserInterface }, new FormSliderBar { - Caption = BeatmapsetsStrings.ShowStatsDrain, - HintText = EditorSetupStrings.DrainRateDescription, + Caption = "Instantaneous slider", Current = new BindableFloat { MinValue = 0, @@ -82,6 +80,18 @@ namespace osu.Game.Tests.Visual.UserInterface Precision = 0.1f, } }, + new FormSliderBar + { + Caption = "Non-instantaneous slider", + Current = new BindableFloat + { + MinValue = 0, + MaxValue = 10, + Value = 5, + Precision = 0.1f, + }, + Instantaneous = false, + }, }, }, } diff --git a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs index 91ce9da2d2..e4c814e71d 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs @@ -7,6 +7,7 @@ using System.Numerics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -29,7 +30,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 set => current.Current = value; } - private bool instantaneous; + private bool instantaneous = true; /// /// Whether changes to the slider should instantaneously transfer to the text box (and vice versa). @@ -41,7 +42,9 @@ namespace osu.Game.Graphics.UserInterfaceV2 set { instantaneous = value; - slider.TransferValueOnCommit = !instantaneous; + + if (slider.IsNotNull()) + slider.TransferValueOnCommit = !instantaneous; } } @@ -116,6 +119,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 RelativeSizeAxes = Axes.X, Width = 0.5f, Current = Current, + TransferValueOnCommit = !instantaneous, } }, }, From 0bab755be316d46ef82700f7cd9e0f916202db46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 18 Sep 2024 11:25:23 +0200 Subject: [PATCH 208/924] Add missing xmldoc --- osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs | 7 +++++++ osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs | 7 +++++++ osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs | 11 +++++++++++ 3 files changed, 25 insertions(+) diff --git a/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs b/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs index 797ff09800..d4cd86010f 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs @@ -29,7 +29,14 @@ namespace osu.Game.Graphics.UserInterfaceV2 private readonly BindableWithCurrent current = new BindableWithCurrent(); + /// + /// Caption describing this slider bar, displayed on top of the controls. + /// public LocalisableString Caption { get; init; } + + /// + /// Hint text containing an extended description of this slider bar, displayed in a tooltip when hovering the caption. + /// public LocalisableString HintText { get; init; } private Box background = null!; diff --git a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs index e4c814e71d..1d44c5d810 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs @@ -50,7 +50,14 @@ namespace osu.Game.Graphics.UserInterfaceV2 private readonly BindableNumberWithCurrent current = new BindableNumberWithCurrent(); + /// + /// Caption describing this slider bar, displayed on top of the controls. + /// public LocalisableString Caption { get; init; } + + /// + /// Hint text containing an extended description of this slider bar, displayed in a tooltip when hovering the caption. + /// public LocalisableString HintText { get; init; } private Box background = null!; diff --git a/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs index 741bff6db6..9bbb5cba99 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs @@ -59,8 +59,19 @@ namespace osu.Game.Graphics.UserInterfaceV2 private readonly BindableWithCurrent current = new BindableWithCurrent(); + /// + /// Caption describing this slider bar, displayed on top of the controls. + /// public LocalisableString Caption { get; init; } + + /// + /// Hint text containing an extended description of this slider bar, displayed in a tooltip when hovering the caption. + /// public LocalisableString HintText { get; init; } + + /// + /// Text displayed in the text box when its contents are empty. + /// public LocalisableString PlaceholderText { get; init; } private Box background = null!; From 093d9ab076129cf732e21849f5ee49a0185a451d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 18 Sep 2024 11:30:52 +0200 Subject: [PATCH 209/924] Keep slider bar looking active when dragging outside of its bounds --- .../Graphics/UserInterfaceV2/FormSliderBar.cs | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs index 1d44c5d810..fa6d44d4c5 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs @@ -143,6 +143,8 @@ namespace osu.Game.Graphics.UserInterfaceV2 textBox.OnCommit += textCommitted; textBox.Current.BindValueChanged(textChanged); + slider.IsDragging.BindValueChanged(_ => updateState()); + current.BindValueChanged(_ => { updateState(); @@ -226,12 +228,12 @@ namespace osu.Game.Graphics.UserInterfaceV2 caption.Colour = Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content2; textBox.Colour = Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content1; - BorderThickness = IsHovered || textBox.Focused.Value ? 2 : 0; + BorderThickness = IsHovered || textBox.Focused.Value || slider.IsDragging.Value ? 2 : 0; BorderColour = textBox.Focused.Value ? colourProvider.Highlight1 : colourProvider.Light4; if (textBox.Focused.Value) background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark3); - else if (IsHovered) + else if (IsHovered || slider.IsDragging.Value) background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark4); else background.Colour = colourProvider.Background5; @@ -246,6 +248,8 @@ namespace osu.Game.Graphics.UserInterfaceV2 private partial class Slider : OsuSliderBar { + public BindableBool IsDragging { get; set; } = new BindableBool(); + private Box leftBox = null!; private Box rightBox = null!; private Circle nub = null!; @@ -313,6 +317,21 @@ namespace osu.Game.Graphics.UserInterfaceV2 rightBox.Width = Math.Clamp(DrawWidth - nub.DrawPosition.X - RangePadding, 0, Math.Max(0, DrawWidth)) / DrawWidth; } + protected override bool OnDragStart(DragStartEvent e) + { + bool dragging = base.OnDragStart(e); + IsDragging.Value = dragging; + updateState(); + return dragging; + } + + protected override void OnDragEnd(DragEndEvent e) + { + base.OnDragEnd(e); + IsDragging.Value = false; + updateState(); + } + protected override bool OnHover(HoverEvent e) { updateState(); @@ -328,8 +347,8 @@ namespace osu.Game.Graphics.UserInterfaceV2 private void updateState() { rightBox.Colour = colourProvider.Background6; - leftBox.Colour = IsHovered ? colourProvider.Highlight1.Opacity(0.5f) : colourProvider.Dark2; - nub.Colour = IsHovered ? colourProvider.Highlight1 : colourProvider.Light4; + leftBox.Colour = IsHovered || IsDragged ? colourProvider.Highlight1.Opacity(0.5f) : colourProvider.Dark2; + nub.Colour = IsHovered || IsDragged ? colourProvider.Highlight1 : colourProvider.Light4; } protected override void UpdateValue(float value) From d506d8a1500f73a3b93473f155541073957b7a59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 18 Sep 2024 11:32:55 +0200 Subject: [PATCH 210/924] Implement `TabbableContentContainer` for slider control --- .../UserInterface/TestSceneFormControls.cs | 4 +++- .../Graphics/UserInterfaceV2/FormSliderBar.cs | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs index 369fe1a40c..b456da0f26 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs @@ -78,7 +78,8 @@ namespace osu.Game.Tests.Visual.UserInterface MaxValue = 10, Value = 5, Precision = 0.1f, - } + }, + TabbableContentContainer = this, }, new FormSliderBar { @@ -91,6 +92,7 @@ namespace osu.Game.Tests.Visual.UserInterface Precision = 0.1f, }, Instantaneous = false, + TabbableContentContainer = this, }, }, }, diff --git a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs index fa6d44d4c5..84becb72c9 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs @@ -48,6 +48,19 @@ namespace osu.Game.Graphics.UserInterfaceV2 } } + private CompositeDrawable? tabbableContentContainer; + + public CompositeDrawable? TabbableContentContainer + { + set + { + tabbableContentContainer = value; + + if (textBox.IsNotNull()) + textBox.TabbableContentContainer = tabbableContentContainer; + } + } + private readonly BindableNumberWithCurrent current = new BindableNumberWithCurrent(); /// @@ -117,7 +130,8 @@ namespace osu.Game.Graphics.UserInterfaceV2 { flashLayer.Colour = ColourInfo.GradientVertical(colours.Red3.Opacity(0), colours.Red3); flashLayer.FadeOutFromOne(200, Easing.OutQuint); - } + }, + TabbableContentContainer = tabbableContentContainer, }, slider = new Slider { From 12bd516a570191cbb31d271082530eafca1206de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 18 Sep 2024 13:51:45 +0200 Subject: [PATCH 211/924] Shuffle playback order in global playlist by default RFC. Closes https://github.com/ppy/osu/issues/18169. Implements the given proposal of keeping the current stable order but adding a shuffle facility to the now playing overlay, and enabling it by default. There are more changes I want to make here but I'd like this to get discussion first, because I am likely to continue putting this sort of selection logic into `MusicController` and I just want to confirm nobody is going to have a problem with that. In particular this is not sharing the randomisation implementation with beatmap carousel because it doesn't generalise nicely (song select cares about the particular *beatmap difficulties* selected to rewind properly, while the music controller only cares about picking a *beatmap set*). --- .../Menus/TestSceneMusicActionHandling.cs | 2 + osu.Game/Overlays/MusicController.cs | 83 ++++++++++++++++++- osu.Game/Overlays/NowPlayingOverlay.cs | 12 +++ 3 files changed, 94 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs index 03b3b94bd8..4454501a96 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs @@ -34,6 +34,8 @@ namespace osu.Game.Tests.Visual.Menus { Queue<(IWorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null!; + AddStep("disable shuffle", () => Game.MusicController.Shuffle.Value = false); + // ensure we have at least two beatmaps available to identify the direction the music controller navigated to. AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo()), 5); diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 63efdd5381..a7bca536df 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -13,8 +14,10 @@ using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; using osu.Framework.Threading; +using osu.Framework.Utils; using osu.Game.Audio.Effects; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Rulesets.Mods; @@ -43,6 +46,8 @@ namespace osu.Game.Overlays /// public readonly BindableBool AllowTrackControl = new BindableBool(true); + public readonly BindableBool Shuffle = new BindableBool(true); + /// /// Fired when the global has changed. /// Includes direction information for display purposes. @@ -66,12 +71,18 @@ namespace osu.Game.Overlays private AudioFilter audioDuckFilter = null!; + private readonly Bindable randomSelectAlgorithm = new Bindable(); + private readonly List previousRandomSets = new List(); + private int randomHistoryDirection; + [BackgroundDependencyLoader] - private void load(AudioManager audio) + private void load(AudioManager audio, OsuConfigManager configManager) { AddInternal(audioDuckFilter = new AudioFilter(audio.TrackMixer)); audio.Tracks.AddAdjustment(AdjustableProperty.Volume, audioDuckVolume); sampleVolume = audio.VolumeSample.GetBoundCopy(); + + configManager.BindWith(OsuSetting.RandomSelectAlgorithm, randomSelectAlgorithm); } protected override void LoadComplete() @@ -238,8 +249,15 @@ namespace osu.Game.Overlays queuedDirection = TrackChangeDirection.Prev; - var playableSet = getBeatmapSets().AsEnumerable().TakeWhile(i => !i.Equals(current?.BeatmapSetInfo)).LastOrDefault(s => !s.Protected || allowProtectedTracks) + BeatmapSetInfo? playableSet; + + if (Shuffle.Value) + playableSet = getNextRandom(-1, allowProtectedTracks); + else + { + playableSet = getBeatmapSets().AsEnumerable().TakeWhile(i => !i.Equals(current?.BeatmapSetInfo)).LastOrDefault(s => !s.Protected || allowProtectedTracks) ?? getBeatmapSets().AsEnumerable().LastOrDefault(s => !s.Protected || allowProtectedTracks); + } if (playableSet != null) { @@ -327,8 +345,15 @@ namespace osu.Game.Overlays queuedDirection = TrackChangeDirection.Next; - var playableSet = getBeatmapSets().AsEnumerable().SkipWhile(i => !i.Equals(current?.BeatmapSetInfo) || (i.Protected && !allowProtectedTracks)).ElementAtOrDefault(1) + BeatmapSetInfo? playableSet; + + if (Shuffle.Value) + playableSet = getNextRandom(1, allowProtectedTracks); + else + { + playableSet = getBeatmapSets().AsEnumerable().SkipWhile(i => !i.Equals(current?.BeatmapSetInfo) || (i.Protected && !allowProtectedTracks)).ElementAtOrDefault(1) ?? getBeatmapSets().AsEnumerable().FirstOrDefault(i => !i.Protected || allowProtectedTracks); + } var playableBeatmap = playableSet?.Beatmaps.FirstOrDefault(); @@ -342,6 +367,58 @@ namespace osu.Game.Overlays return false; } + private BeatmapSetInfo? getNextRandom(int direction, bool allowProtectedTracks) + { + BeatmapSetInfo result; + + var possibleSets = getBeatmapSets().AsEnumerable().Where(s => !s.Protected || allowProtectedTracks).ToArray(); + + if (possibleSets.Length == 0) + return null; + + // condition below checks if the signs of `randomHistoryDirection` and `direction` are opposite and not zero. + // if that is the case, it means that the user had previously chosen next track `randomHistoryDirection` times and wants to go back, + // or that the user had previously chosen previous track `randomHistoryDirection` times and wants to go forward. + // in both cases, it means that we have a history of previous random selections that we can rewind. + if (randomHistoryDirection * direction < 0) + { + Debug.Assert(Math.Abs(randomHistoryDirection) == previousRandomSets.Count); + result = previousRandomSets[^1]; + previousRandomSets.RemoveAt(previousRandomSets.Count - 1); + randomHistoryDirection += direction; + return result; + } + + // if the early-return above didn't cover it, it means that we have no history to fall back on + // and need to actually choose something random. + switch (randomSelectAlgorithm.Value) + { + case RandomSelectAlgorithm.Random: + result = possibleSets[RNG.Next(possibleSets.Length)]; + break; + + case RandomSelectAlgorithm.RandomPermutation: + var notYetPlayedSets = possibleSets.Except(previousRandomSets).ToArray(); + + if (notYetPlayedSets.Length == 0) + { + notYetPlayedSets = possibleSets; + previousRandomSets.Clear(); + randomHistoryDirection = 0; + } + + result = notYetPlayedSets[RNG.Next(notYetPlayedSets.Length)]; + break; + + default: + throw new ArgumentOutOfRangeException(nameof(randomSelectAlgorithm), randomSelectAlgorithm.Value, "Unsupported random select algorithm"); + } + + previousRandomSets.Add(result); + randomHistoryDirection += direction; + return result; + } + private void restartTrack() { // if not scheduled, the previously track will be stopped one frame later (see ScheduleAfterChildren logic in GameBase). diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 76c8c237d5..bcd15a2b7e 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -47,6 +47,7 @@ namespace osu.Game.Overlays private IconButton prevButton = null!; private IconButton playButton = null!; private IconButton nextButton = null!; + private MusicIconButton shuffleButton = null!; private IconButton playlistButton = null!; private ScrollingTextContainer title = null!, artist = null!; @@ -69,6 +70,7 @@ namespace osu.Game.Overlays private OsuColour colours { get; set; } = null!; private Bindable allowTrackControl = null!; + private BindableBool shuffle = new BindableBool(true); public NowPlayingOverlay() { @@ -162,6 +164,13 @@ namespace osu.Game.Overlays Action = () => musicController.NextTrack(), Icon = FontAwesome.Solid.StepForward, }, + shuffleButton = new MusicIconButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Action = shuffle.Toggle, + Icon = FontAwesome.Solid.Random, + } } }, playlistButton = new MusicIconButton @@ -227,6 +236,9 @@ namespace osu.Game.Overlays allowTrackControl = musicController.AllowTrackControl.GetBoundCopy(); allowTrackControl.BindValueChanged(_ => Scheduler.AddOnce(updateEnabledStates), true); + shuffle.BindTo(musicController.Shuffle); + shuffle.BindValueChanged(s => shuffleButton.FadeColour(s.NewValue ? colours.Yellow : Color4.White, 200, Easing.OutQuint), true); + musicController.TrackChanged += trackChanged; trackChanged(beatmap.Value); } From 2d3b027f85d7c30fe269ac8fa99bfca0dcd1555e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 18 Sep 2024 15:18:11 +0200 Subject: [PATCH 212/924] Add test case covering desired behaviour --- .../Editing/TestSceneComposerSelection.cs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs index 3884a3108f..765d7ee21e 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs @@ -215,6 +215,54 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("2 hitobjects selected", () => EditorBeatmap.SelectedHitObjects.Count == 2 && !EditorBeatmap.SelectedHitObjects.Contains(addedObjects[1])); } + [Test] + public void TestMultiSelectWithDragBox() + { + var addedObjects = new[] + { + new HitCircle { StartTime = 100 }, + new HitCircle { StartTime = 200, Position = new Vector2(100) }, + new HitCircle { StartTime = 300, Position = new Vector2(512, 0) }, + new HitCircle { StartTime = 400, Position = new Vector2(412, 100) }, + }; + AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects)); + + AddStep("start dragging", () => + { + InputManager.MoveMouseTo(blueprintContainer.ScreenSpaceDrawQuad.Centre); + InputManager.PressButton(MouseButton.Left); + }); + AddStep("drag to left corner", () => InputManager.MoveMouseTo(blueprintContainer.ScreenSpaceDrawQuad.TopLeft - new Vector2(5))); + AddStep("end dragging", () => InputManager.ReleaseButton(MouseButton.Left)); + + AddAssert("2 hitobjects selected", () => EditorBeatmap.SelectedHitObjects, () => Has.Count.EqualTo(2)); + + AddStep("start dragging with control", () => + { + InputManager.MoveMouseTo(blueprintContainer.ScreenSpaceDrawQuad.Centre); + InputManager.PressButton(MouseButton.Left); + InputManager.PressKey(Key.ControlLeft); + }); + AddStep("drag to left corner", () => InputManager.MoveMouseTo(blueprintContainer.ScreenSpaceDrawQuad.TopRight + new Vector2(5, -5))); + AddStep("end dragging", () => + { + InputManager.ReleaseButton(MouseButton.Left); + InputManager.ReleaseKey(Key.ControlLeft); + }); + + AddAssert("4 hitobjects selected", () => EditorBeatmap.SelectedHitObjects, () => Has.Count.EqualTo(4)); + + AddStep("start dragging without control", () => + { + InputManager.MoveMouseTo(blueprintContainer.ScreenSpaceDrawQuad.Centre); + InputManager.PressButton(MouseButton.Left); + }); + AddStep("drag to left corner", () => InputManager.MoveMouseTo(blueprintContainer.ScreenSpaceDrawQuad.TopRight + new Vector2(5, -5))); + AddStep("end dragging", () => InputManager.ReleaseButton(MouseButton.Left)); + + AddAssert("2 hitobjects selected", () => EditorBeatmap.SelectedHitObjects, () => Has.Count.EqualTo(2)); + } + [Test] public void TestNearestSelection() { From f6195c551547e3b801563b86f9df17e4f4b81182 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 18 Sep 2024 15:02:04 +0200 Subject: [PATCH 213/924] Add to existing selection when dragging with control pressed Closes https://github.com/ppy/osu/issues/29023. --- .../Edit/Compose/Components/BlueprintContainer.cs | 14 +++++++++++--- .../Timeline/TimelineBlueprintContainer.cs | 5 ++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index c66be90605..9776e64855 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -196,6 +196,11 @@ namespace osu.Game.Screens.Edit.Compose.Components DragBox.HandleDrag(e); DragBox.Show(); + + selectionBeforeDrag.Clear(); + if (e.ControlPressed) + selectionBeforeDrag.UnionWith(SelectedItems); + return true; } @@ -217,6 +222,7 @@ namespace osu.Game.Screens.Edit.Compose.Components } DragBox.Hide(); + selectionBeforeDrag.Clear(); } protected override void Update() @@ -227,7 +233,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { lastDragEvent.Target = this; DragBox.HandleDrag(lastDragEvent); - UpdateSelectionFromDragBox(); + UpdateSelectionFromDragBox(selectionBeforeDrag); } } @@ -472,7 +478,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Select all blueprints in a selection area specified by . /// - protected virtual void UpdateSelectionFromDragBox() + protected virtual void UpdateSelectionFromDragBox(HashSet selectionBeforeDrag) { var quad = DragBox.Box.ScreenSpaceDrawQuad; @@ -482,7 +488,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { case SelectionState.Selected: // Selection is preserved even after blueprint becomes dead. - if (!quad.Contains(blueprint.ScreenSpaceSelectionPoint)) + if (!quad.Contains(blueprint.ScreenSpaceSelectionPoint) && !selectionBeforeDrag.Contains(blueprint.Item)) blueprint.Deselect(); break; @@ -535,6 +541,8 @@ namespace osu.Game.Screens.Edit.Compose.Components /// private bool wasDragStarted; + private readonly HashSet selectionBeforeDrag = new HashSet(); + /// /// Attempts to begin the movement of any selected blueprints. /// diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 740f0b6aac..a6af83d268 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -173,7 +173,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected sealed override DragBox CreateDragBox() => new TimelineDragBox(); - protected override void UpdateSelectionFromDragBox() + protected override void UpdateSelectionFromDragBox(HashSet selectionBeforeDrag) { Composer.BlueprintContainer.CommitIfPlacementActive(); @@ -191,6 +191,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline bool shouldBeSelected(HitObject hitObject) { + if (selectionBeforeDrag.Contains(hitObject)) + return true; + double midTime = (hitObject.StartTime + hitObject.GetEndTime()) / 2; return minTime <= midTime && midTime <= maxTime; } From 7f52ae883723a684f84f162b6a71b1f8e6dcdcea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 18 Sep 2024 15:30:56 +0200 Subject: [PATCH 214/924] Fix code quality inspection --- osu.Game/Overlays/NowPlayingOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index bcd15a2b7e..e1e5aa9426 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -70,7 +70,7 @@ namespace osu.Game.Overlays private OsuColour colours { get; set; } = null!; private Bindable allowTrackControl = null!; - private BindableBool shuffle = new BindableBool(true); + private readonly BindableBool shuffle = new BindableBool(true); public NowPlayingOverlay() { From c185acdbae367902f84e803b12d62ebd95c0566d Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 18 Sep 2024 11:16:25 -0700 Subject: [PATCH 215/924] Use `GetLocalisedBindableString()` instead --- osu.Game/Online/Chat/MessageFormatter.cs | 2 -- osu.Game/OsuGame.cs | 18 ++++++++---------- .../BeatmapSet/BeatmapSetHeaderContent.cs | 4 ++-- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 0f444ccde9..77454c4775 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -340,8 +340,6 @@ namespace osu.Game.Online.Chat Spectate, OpenUserProfile, SearchBeatmapSet, - SearchBeatmapTitle, - SearchBeatmapArtist, OpenWiki, Custom, OpenChangelog, diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ffb145d7de..1af86b2d83 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -445,17 +445,15 @@ namespace osu.Game break; case LinkAction.SearchBeatmapSet: - SearchBeatmapSet(argString); - break; + if (link.Argument is LocalisableString localisable) + { + var localised = Localisation.GetLocalisedBindableString(localisable); + SearchBeatmapSet(localised.Value); + localised.UnbindAll(); + } + else + SearchBeatmapSet(argString); - case LinkAction.SearchBeatmapTitle: - string title = ((RomanisableString)link.Argument).GetPreferred(Localisation.CurrentParameters.Value.PreferOriginalScript); - SearchBeatmapSet($@"title=""""{title}"""""); - break; - - case LinkAction.SearchBeatmapArtist: - string artist = ((RomanisableString)link.Argument).GetPreferred(Localisation.CurrentParameters.Value.PreferOriginalScript); - SearchBeatmapSet($@"artist=""""{artist}"""""); break; case LinkAction.FilterBeatmapSetGenre: diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index 6ea16a9997..a50043f0f0 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -242,7 +242,7 @@ namespace osu.Game.Overlays.BeatmapSet title.Clear(); artist.Clear(); - title.AddLink(titleText, LinkAction.SearchBeatmapTitle, titleText); + title.AddLink(titleText, LinkAction.SearchBeatmapSet, LocalisableString.Interpolate($@"title=""""{titleText}""""")); title.AddArbitraryDrawable(Empty().With(d => d.Width = 5)); title.AddArbitraryDrawable(externalLink = new ExternalLinkButton()); @@ -259,7 +259,7 @@ namespace osu.Game.Overlays.BeatmapSet title.AddArbitraryDrawable(new SpotlightBeatmapBadge()); } - artist.AddLink(artistText, LinkAction.SearchBeatmapArtist, artistText); + artist.AddLink(artistText, LinkAction.SearchBeatmapSet, LocalisableString.Interpolate($@"artist=""""{artistText}""""")); if (setInfo.NewValue.TrackId != null) { From 0bad5e468455ef2667015a0c0fea148143459d6d Mon Sep 17 00:00:00 2001 From: StanR Date: Thu, 19 Sep 2024 04:38:01 +0500 Subject: [PATCH 216/924] Move slider-related ratio multiplier out of the delta switch block, add nerf for ratios with delta difference fractions that are too big, adjust consts --- .../Difficulty/Evaluators/RhythmEvaluator.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index 9eae3c7a10..80d75d5e67 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -63,8 +63,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators private const int history_time_max = 4 * 1000; // 5 seconds of calculatingRhythmBonus max. private const int history_objects_max = 24; - private const double rhythm_overall_multiplier = 1.2; - private const double rhythm_ratio_multiplier = 10.0; + private const double rhythm_overall_multiplier = 1.25; + private const double rhythm_ratio_multiplier = 11.0; /// /// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current . @@ -114,9 +114,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators double deltaDifferenceRatio = Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta); double currRatio = 1.0 + rhythm_ratio_multiplier * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / deltaDifferenceRatio), 2)); + // reduce ratio bonus if delta difference is too big + double fraction = Math.Max(prevDelta / currDelta, currDelta / prevDelta); + double fractionMultiplier = Math.Clamp(2.0 - fraction / 8.0, 0.0, 1.0); + double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - deltaDifferenceEpsilon) / deltaDifferenceEpsilon); - double effectiveRatio = windowPenalty * currRatio; + double effectiveRatio = windowPenalty * currRatio * fractionMultiplier; + + // bpm change is into slider, this is easy acc window + if (currObj.BaseObject is Slider) + effectiveRatio *= 0.125; + + // bpm change was from a slider, this is easier typically than circle -> circle + // unintentional side effect is that bursts with kicksliders at the ends might have lower difficulty than bursts without sliders + if (prevObj.BaseObject is Slider) + effectiveRatio *= 0.2; if (firstDeltaSwitch) { @@ -127,15 +140,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators } else { - // bpm change is into slider, this is easy acc window - if (currObj.BaseObject is Slider) - effectiveRatio *= 0.125; - - // bpm change was from a slider, this is easier typically than circle -> circle - // unintentional side effect is that bursts with kicksliders at the ends might have lower difficulty than bursts without sliders - if (prevObj.BaseObject is Slider) - effectiveRatio *= 0.2; - // repeated island polarity (2 -> 4, 3 -> 5) if (island.IsSimilarPolarity(previousIsland)) effectiveRatio *= 0.3; @@ -180,10 +184,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators // Begin counting island until we change speed again. firstDeltaSwitch = true; - // reduce ratio if we're starting after a slider - if (prevObj.BaseObject is Slider) - effectiveRatio *= 0.3; - startRatio = effectiveRatio; island = new Island((int)currDelta, deltaDifferenceEpsilon); From d0519238a3a74f507da725035ce9cba5ad757cb0 Mon Sep 17 00:00:00 2001 From: Neku Date: Wed, 18 Sep 2024 22:57:37 +0200 Subject: [PATCH 217/924] Implement beat-synchronized animation in skip overlay --- osu.Game/Screens/Play/SkipOverlay.cs | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index 29b2e5229b..c88724c8db 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -9,6 +9,7 @@ using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Audio.Track; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -16,6 +17,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Utils; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -26,7 +28,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.Play { - public partial class SkipOverlay : CompositeDrawable, IKeyBindingHandler + public partial class SkipOverlay : BeatSyncedContainer, IKeyBindingHandler { /// /// The total number of successful skips performed by this overlay. @@ -36,10 +38,9 @@ namespace osu.Game.Screens.Play private readonly double startTime; public Action RequestSkip; - private Button button; private ButtonContainer buttonContainer; - private Box remainingTimeBox; + private Circle remainingTimeBox; private FadeContainer fadeContainer; private double displayTime; @@ -51,7 +52,6 @@ namespace osu.Game.Screens.Play private IGameplayClock gameplayClock { get; set; } internal bool IsButtonVisible => fadeContainer.State == Visibility.Visible && buttonContainer.State.Value == Visibility.Visible; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; /// @@ -87,13 +87,13 @@ namespace osu.Game.Screens.Play Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - remainingTimeBox = new Box + remainingTimeBox = new Circle { Height = 5, - RelativeSizeAxes = Axes.X, - Colour = colours.Yellow, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, + Colour = colours.Yellow, + RelativeSizeAxes = Axes.X } } } @@ -210,6 +210,18 @@ namespace osu.Game.Screens.Play { } + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) + { + base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); + + if (fadeOutBeginTime <= gameplayClock.CurrentTime) + return; + + float progress = (float)(gameplayClock.CurrentTime - displayTime) / (float)(fadeOutBeginTime - displayTime); + float newWidth = Math.Max(0, 1 - Math.Clamp(progress, 0, 1)); + remainingTimeBox.ResizeWidthTo(newWidth, timingPoint.BeatLength * 2, Easing.OutQuint); + } + public partial class FadeContainer : Container, IStateful { [CanBeNull] From fdd94aa8452ac5d70bd139d22f700d6e456947f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 19 Sep 2024 09:41:54 +0200 Subject: [PATCH 218/924] Remove pointless max The clamp should already ensure this. --- osu.Game/Screens/Play/SkipOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index c88724c8db..362677ca5c 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -218,7 +218,7 @@ namespace osu.Game.Screens.Play return; float progress = (float)(gameplayClock.CurrentTime - displayTime) / (float)(fadeOutBeginTime - displayTime); - float newWidth = Math.Max(0, 1 - Math.Clamp(progress, 0, 1)); + float newWidth = 1 - Math.Clamp(progress, 0, 1); remainingTimeBox.ResizeWidthTo(newWidth, timingPoint.BeatLength * 2, Easing.OutQuint); } From fd45644d0f58f6eb90916aa820628b26918b60e2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2024 16:54:00 +0900 Subject: [PATCH 219/924] Fix skin layout editor `PlayerAvatar` applying corner radius weirdly after scale Closes #29919. I've also made this handle resizing better, so now you can have non-square avatar displays. --- osu.Game/Screens/Play/HUD/PlayerAvatar.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/PlayerAvatar.cs b/osu.Game/Screens/Play/HUD/PlayerAvatar.cs index 06d0f7bc9a..8e4406c2c1 100644 --- a/osu.Game/Screens/Play/HUD/PlayerAvatar.cs +++ b/osu.Game/Screens/Play/HUD/PlayerAvatar.cs @@ -39,14 +39,23 @@ namespace osu.Game.Screens.Play.HUD private IBindable? apiUser; + private readonly Container cornerContainer; + public PlayerAvatar() { Size = new Vector2(default_size); - InternalChild = avatar = new UpdateableAvatar(isInteractive: false) + InternalChild = cornerContainer = new Container { + Masking = true, RelativeSizeAxes = Axes.Both, - Masking = true + Child = avatar = new UpdateableAvatar(isInteractive: false) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fill, + } }; } @@ -66,7 +75,7 @@ namespace osu.Game.Screens.Play.HUD { base.LoadComplete(); - CornerRadius.BindValueChanged(e => avatar.CornerRadius = e.NewValue * default_size, true); + CornerRadius.BindValueChanged(e => cornerContainer.CornerRadius = e.NewValue * default_size, true); } public bool UsesFixedAnchor { get; set; } From ca8402d98021b6a83d5df5ec119c661bca38152c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2024 19:06:46 +0900 Subject: [PATCH 220/924] Make animation slightly more snappy --- osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs index 84becb72c9..ac3730598f 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs @@ -367,7 +367,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 protected override void UpdateValue(float value) { - nub.MoveToX(value, 250, Easing.OutQuint); + nub.MoveToX(value, 200, Easing.OutPow10); } } } From d5c2484109ccf55ae7169061696797d1b17e12bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2024 19:23:14 +0900 Subject: [PATCH 221/924] Always transfer updated counts once --- .../JudgementCountController.cs | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCountController.cs b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCountController.cs index 7e9f3cba08..2562e26127 100644 --- a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCountController.cs +++ b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCountController.cs @@ -55,28 +55,13 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter scoreProcessor.OnResetFromReplayFrame += updateAllCounts; scoreProcessor.NewJudgement += judgement => updateCount(judgement, false); scoreProcessor.JudgementReverted += judgement => updateCount(judgement, true); - - updateAllCounts(); } + private bool hasUpdatedCounts; + private void updateAllCounts() { - // This flow is made to handle cases of watching from the middle of a replay / spectating session. - // - // Once we get an initial state, we can rely on `NewJudgement` and `JudgementReverted`, so - // as a preemptive optimisation, only do a full re-sync if we have all-zero counts. - bool hasCounts = false; - - foreach (var r in results) - { - if (r.Value.ResultCount.Value > 0) - { - hasCounts = true; - break; - } - } - - if (hasCounts) + if (hasUpdatedCounts) return; foreach (var kvp in scoreProcessor.Statistics) @@ -86,6 +71,8 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter count.ResultCount.Value = kvp.Value; } + + hasUpdatedCounts = true; } private void updateCount(JudgementResult judgement, bool revert) From 732a114b9594d2a861709591cd74d3438afdc375 Mon Sep 17 00:00:00 2001 From: StanR Date: Thu, 19 Sep 2024 15:53:18 +0500 Subject: [PATCH 222/924] Slight refactoring --- .../Difficulty/Evaluators/RhythmEvaluator.cs | 108 +++++++++--------- 1 file changed, 55 insertions(+), 53 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index 80d75d5e67..87fc35adcd 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -11,57 +11,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators { public static class RhythmEvaluator { - private struct Island : IEquatable - { - private readonly double deltaDifferenceEpsilon; - - public Island(double epsilon) - { - deltaDifferenceEpsilon = epsilon; - } - - public Island(int delta, double epsilon) - { - deltaDifferenceEpsilon = epsilon; - Delta = Math.Max(delta, OsuDifficultyHitObject.MIN_DELTA_TIME); - } - - public int Delta { get; private set; } - public int DeltaCount { get; private set; } - - public void AddDelta(int delta) - { - if (Delta == default) - Delta = Math.Max(delta, OsuDifficultyHitObject.MIN_DELTA_TIME); - - DeltaCount++; - } - - public bool IsSimilarPolarity(Island other) - { - // consider islands to be of similar polarity only if they're having the same average delta (we don't want to consider 3 singletaps similar to a triple) - return DeltaCount % 2 == other.DeltaCount % 2 && - Math.Abs(Delta - other.Delta) < deltaDifferenceEpsilon; - } - - public override int GetHashCode() - { - return HashCode.Combine(Delta, DeltaCount); - } - - public bool Equals(Island other) - { - return Math.Abs(Delta - other.Delta) < deltaDifferenceEpsilon && - DeltaCount == other.DeltaCount; - } - - public override bool Equals(object? obj) - { - return obj?.GetHashCode() == GetHashCode(); - } - } - - private const int history_time_max = 4 * 1000; // 5 seconds of calculatingRhythmBonus max. + private const int history_time_max = 4 * 1000; // 4 seconds private const int history_objects_max = 24; private const double rhythm_overall_multiplier = 1.25; private const double rhythm_ratio_multiplier = 11.0; @@ -101,9 +51,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators { OsuDifficultyHitObject currObj = (OsuDifficultyHitObject)current.Previous(i - 1); - double currHistoricalDecay = (history_time_max - (current.StartTime - currObj.StartTime)) / history_time_max; // scales note 0 to 1 from history to now + // scales note 0 to 1 from history to now + double timeDecay = (history_time_max - (current.StartTime - currObj.StartTime)) / history_time_max; + double noteDecay = (double)(historicalNoteCount - i) / historicalNoteCount; - currHistoricalDecay = Math.Min((double)(historicalNoteCount - i) / historicalNoteCount, currHistoricalDecay); // either we're limited by time or limited by object count. + double currHistoricalDecay = Math.Min(noteDecay, timeDecay); // either we're limited by time or limited by object count. double currDelta = currObj.StrainTime; double prevDelta = prevObj.StrainTime; @@ -197,5 +149,55 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators } private static double logistic(double x, double maxValue, double multiplier, double offset) => (maxValue / (1 + Math.Pow(Math.E, offset - (multiplier * x)))); + + private struct Island : IEquatable + { + private readonly double deltaDifferenceEpsilon; + + public Island(double epsilon) + { + deltaDifferenceEpsilon = epsilon; + } + + public Island(int delta, double epsilon) + { + deltaDifferenceEpsilon = epsilon; + Delta = Math.Max(delta, OsuDifficultyHitObject.MIN_DELTA_TIME); + } + + public int Delta { get; private set; } + public int DeltaCount { get; private set; } + + public void AddDelta(int delta) + { + if (Delta == default) + Delta = Math.Max(delta, OsuDifficultyHitObject.MIN_DELTA_TIME); + + DeltaCount++; + } + + public bool IsSimilarPolarity(Island other) + { + // consider islands to be of similar polarity only if they're having the same average delta (we don't want to consider 3 singletaps similar to a triple) + return DeltaCount % 2 == other.DeltaCount % 2 && + Math.Abs(Delta - other.Delta) < deltaDifferenceEpsilon; + } + + public override int GetHashCode() + { + return HashCode.Combine(Delta, DeltaCount); + } + + public bool Equals(Island other) + { + return Math.Abs(Delta - other.Delta) < deltaDifferenceEpsilon && + DeltaCount == other.DeltaCount; + } + + public override bool Equals(object? obj) + { + return obj?.GetHashCode() == GetHashCode(); + } + } } } From 89509ea49ed608c2c587816c35833f1822490eb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 19 Sep 2024 14:03:49 +0200 Subject: [PATCH 223/924] Fix `DrawableOsuHitObject` not properly cleaning up dim application callbacks Should fix https://github.com/ppy/osu/issues/28629. First of all, to support the claim that this does fix the issue - reproduction is rather difficult, but I believe I found a way to maximise the chances of it reproducing by performing the following steps: 1. Apply the following diff: diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index eacd2b3e75..4c00da031a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -95,6 +96,8 @@ public DrawableSlider([CanBeNull] Slider s = null) [BackgroundDependencyLoader] private void load() { + Thread.Sleep(100); + tailContainer = new Container { RelativeSizeAxes = Axes.Both }; AddRangeInternal(new Drawable[] 2. Download https://osu.ppy.sh/beatmapsets/1470790#osu/3023028 and open it in the editor. 3. Select all objects using Ctrl-A. Yes, it'll take a while, especially so with the patch above. 4. Rotate the selection by any amount using the right toolbox. 5. Press undo. The game should crash. If it doesn't spam redo and undo until it does. Now to explain what the fix is. In the issue thread I spent a considerable time hemming and hawing about which of the dimmable pieces was null, which was a complete miss and a failure at reading. Let's see the stack trace again: 2024-06-27 02:15:20 [error]: at osu.Game.Rulesets.Osu.Objects.Drawables.DrawableOsuHitObject.g__applyDim|15_0(Drawable piece) in /home/runner/work/osu-auth-client/osu-auth-client/osu/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs:line 101 Line 101, you say? What could be null here? https://github.com/ppy/osu/blob/bd8addfb5f71568479d2c037d1b6e811de6e7fe6/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs#L101 Okay, what's `InitialLifetimeOffset`, then? https://github.com/ppy/osu/blob/bd8addfb5f71568479d2c037d1b6e811de6e7fe6/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs#L108 Yes, that's right. It's `HitObject` that is null here. Now why does *that* happen? First, let's note that all stacks where this died went through `UpdateState()`, which means that the problematic `applyDim()` calls had to be `ApplyCustomUpdateState` event callbacks. Meaning that the pieces where `HitObject` was null were DHOs themselves. Recall that parent DHOs and child DHOs are pooled separately. Therefore, there is no guarantee that any parent and child DHOs will remain associated with each other for the entire duration of a gameplay session; it is quite the contrary, and nobody should rely on that. Unfortunately for us, adding a `applyDimToDrawableHitObject` callback to a child object's `ApplyCustomUpdateState` *implicitly creates* such an association, because it ends up allocating a closure that captures `this` (meaning the parent in this context). Therefore, this now creates a situation where a child DHO can attempt to read state from a former parent DHO which can be in an indeterminate state, and in fact, when this crashes, the former parent DHO is most likely not even in use - hence the null `HitObject`. Thus, the fix is to clear the association by unsubscribing from the event when nested objects are cleared. My hypothesis why the reproduction scenario is like it is, is that both the sleep and the increased pressure on the pool (by way of selecting all objects and therefore forcing the DHOs to be materialised beyond pool capacity) increases the likelihood of getting a crosslink. When pool pressure is low, it is much more likely that a parent DHO *will* get the same child DHO again on re-application, even though that is not guaranteed. Just as an additional detail, note that the sentry issue for this lists the "first seen" version as 2024.312.0, which is the release that included https://github.com/ppy/osu/pull/27401 which would be directly responsible for this mess. --- .../Objects/Drawables/DrawableOsuHitObject.cs | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 5f5deca1ba..b3a68ec92d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -91,20 +91,35 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables drawableObjectPiece.ApplyCustomUpdateState -= applyDimToDrawableHitObject; drawableObjectPiece.ApplyCustomUpdateState += applyDimToDrawableHitObject; } - else - applyDim(piece); - } - void applyDim(Drawable piece) - { - piece.FadeColour(new Color4(195, 195, 195, 255)); - using (piece.BeginDelayedSequence(InitialLifetimeOffset - OsuHitWindows.MISS_WINDOW)) - piece.FadeColour(Color4.White, 100); + // but at the end apply the transforms now regardless of whether this is a DHO or not. + // the above is just to ensure they don't get overwritten later. + applyDim(piece); } - - void applyDimToDrawableHitObject(DrawableHitObject dho, ArmedState _) => applyDim(dho); } + protected override void ClearNestedHitObjects() + { + base.ClearNestedHitObjects(); + + // any dimmable pieces that are DHOs will be pooled separately. + // `applyDimToDrawableHitObject` is a closure that implicitly captures `this`, + // and because of separate pooling of parent and child objects, there is no guarantee that the pieces will be associated with `this` again on re-use. + // therefore, clean up the subscription here to avoid crosstalk. + // not doing so can result in the callback attempting to read things from `this` when it is in a completely bogus state (not in use or similar). + foreach (var piece in DimmablePieces.OfType()) + piece.ApplyCustomUpdateState -= applyDimToDrawableHitObject; + } + + private void applyDim(Drawable piece) + { + piece.FadeColour(new Color4(195, 195, 195, 255)); + using (piece.BeginDelayedSequence(InitialLifetimeOffset - OsuHitWindows.MISS_WINDOW)) + piece.FadeColour(Color4.White, 100); + } + + private void applyDimToDrawableHitObject(DrawableHitObject dho, ArmedState _) => applyDim(dho); + protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt; private OsuInputManager osuActionInputManager; From 202364be5edc8f5126f12894f614a33533bc454f Mon Sep 17 00:00:00 2001 From: StanR Date: Thu, 19 Sep 2024 17:52:55 +0500 Subject: [PATCH 224/924] Use `List` instead of `Dictionary` for island counting, minor adjustments --- .../Difficulty/Evaluators/RhythmEvaluator.cs | 35 +++++++++++++------ .../Difficulty/Skills/Speed.cs | 2 +- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index 87fc35adcd..d6ed60cc77 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; +using System.Linq; +using osu.Framework.Lists; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Objects; @@ -13,8 +15,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators { private const int history_time_max = 4 * 1000; // 4 seconds private const int history_objects_max = 24; - private const double rhythm_overall_multiplier = 1.25; - private const double rhythm_ratio_multiplier = 11.0; + private const double rhythm_overall_multiplier = 1.0; + private const double rhythm_ratio_multiplier = 14.0; /// /// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current . @@ -30,7 +32,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators var island = new Island(deltaDifferenceEpsilon); var previousIsland = new Island(deltaDifferenceEpsilon); - Dictionary islandCounts = new Dictionary(); + + // we can't use dictionary here because we need to compare island with a tolerance + // which is impossible to pass into the hash comparer + var islandCounts = new List<(Island Island, int Count)>(); double startRatio = 0; // store the ratio of the current start of an island to buff for tighter rhythms @@ -104,15 +109,21 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators if (island.DeltaCount == 1) effectiveRatio *= 0.7; - if (!islandCounts.TryAdd(island, 1)) + var islandCount = islandCounts.FirstOrDefault(x => x.Island.Equals(island)); + + if (islandCount != default) { // only add island to island counts if they're going one after another if (previousIsland.Equals(island)) - islandCounts[island]++; + islandCount.Count++; // repeated island (ex: triplet -> triplet) double power = logistic(island.Delta, 4, 0.165, 10); - effectiveRatio *= Math.Min(3.0 / islandCounts[island], Math.Pow(1.0 / islandCounts[island], power)); + effectiveRatio *= Math.Min(3.0 / islandCount.Count, Math.Pow(1.0 / islandCount.Count, power)); + } + else + { + islandCounts.Add((island, 1)); } // scale down the difficulty if the object is doubletappable @@ -150,7 +161,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators private static double logistic(double x, double maxValue, double multiplier, double offset) => (maxValue / (1 + Math.Pow(Math.E, offset - (multiplier * x)))); - private struct Island : IEquatable + private class Island : IEquatable { private readonly double deltaDifferenceEpsilon; @@ -163,6 +174,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators { deltaDifferenceEpsilon = epsilon; Delta = Math.Max(delta, OsuDifficultyHitObject.MIN_DELTA_TIME); + DeltaCount++; } public int Delta { get; private set; } @@ -188,15 +200,18 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators return HashCode.Combine(Delta, DeltaCount); } - public bool Equals(Island other) + public bool Equals(Island? other) { + if (other == null) + return false; + return Math.Abs(Delta - other.Delta) < deltaDifferenceEpsilon && DeltaCount == other.DeltaCount; } - public override bool Equals(object? obj) + public override string ToString() { - return obj?.GetHashCode() == GetHashCode(); + return $"{Delta}x{DeltaCount}"; } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index f7f081b7ea..37420ed026 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// public class Speed : OsuStrainSkill { - private double skillMultiplier => 1.375; + private double skillMultiplier => 1.38; private double strainDecayBase => 0.3; private double currentStrain; From e986a7dfe117ff8b0ed1bbd691404bc5be1b7dcf Mon Sep 17 00:00:00 2001 From: StanR Date: Thu, 19 Sep 2024 19:15:54 +0500 Subject: [PATCH 225/924] Fix repetition nerf not updating the counts --- .../Difficulty/Evaluators/RhythmEvaluator.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index d6ed60cc77..e34989f162 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Lists; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Objects; @@ -113,6 +112,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators if (islandCount != default) { + int countIndex = islandCounts.IndexOf(islandCount); + // only add island to island counts if they're going one after another if (previousIsland.Equals(island)) islandCount.Count++; @@ -120,6 +121,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators // repeated island (ex: triplet -> triplet) double power = logistic(island.Delta, 4, 0.165, 10); effectiveRatio *= Math.Min(3.0 / islandCount.Count, Math.Pow(1.0 / islandCount.Count, power)); + + islandCounts[countIndex] = (islandCount.Island, islandCount.Count); } else { From 34087a0f1a303d90bf443fd2d4b71e07049ee450 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 19 Sep 2024 16:20:26 +0200 Subject: [PATCH 226/924] Reduce sample point pieces to pink dot if zoomed out --- .../Timeline/HitObjectPointPiece.cs | 4 ++- .../Components/Timeline/SamplePointPiece.cs | 30 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs index 4b357d3a62..76323ac08c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/HitObjectPointPiece.cs @@ -17,6 +17,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { protected OsuSpriteText Label { get; private set; } + protected Container LabelContainer { get; private set; } + [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -26,7 +28,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline InternalChildren = new Drawable[] { - new Container + LabelContainer = new Container { AutoSizeAxes = Axes.X, Height = 16, diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 3843d56d06..1638700735 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -41,6 +41,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private Editor? editor { get; set; } + [Resolved] + private Timeline? timeline { get; set; } + private Bindable samplesVisible = null!; public SamplePointPiece(HitObject hitObject) @@ -59,6 +62,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void load(OsuConfigManager config) { HitObject.DefaultsApplied += _ => updateText(); + Label.AllowMultiline = false; + LabelContainer.AutoSizeAxes = Axes.None; updateText(); if (editor != null) @@ -75,6 +80,30 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline this.FadeTo(samplesVisible.Value ? 1 : 0); } + private float lastZoom; + private const float zoom_threshold = 40f; + + protected override void Update() + { + base.Update(); + + // Retract visual state if the timeline is zoomed out too far. + if (timeline is null || timeline.Zoom == lastZoom) return; + + lastZoom = timeline.Zoom; + + if (timeline.Zoom < zoom_threshold) + { + Label.FadeOut(200, Easing.OutQuint); + LabelContainer.ResizeWidthTo(16, 200, Easing.OutQuint); + } + else + { + Label.FadeIn(200, Easing.OutQuint); + LabelContainer.ResizeWidthTo(Label.Width, 200, Easing.OutQuint); + } + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -100,6 +129,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateText() { Label.Text = $"{abbreviateBank(GetBankValue(GetSamples()))} {GetVolumeValue(GetSamples())}"; + LabelContainer.ResizeWidthTo(Label.Width, 200, Easing.OutQuint); } private static string? abbreviateBank(string? bank) From 8c72feda09feeac72b91542081cf745eaf5fbd97 Mon Sep 17 00:00:00 2001 From: PowerDaniex <140076282+u4vh3@users.noreply.github.com> Date: Wed, 10 Jul 2024 18:36:01 +0200 Subject: [PATCH 227/924] Add colour customization to the layout editor --- .../Configuration/SettingSourceAttribute.cs | 13 +++ osu.Game/Overlays/Settings/SettingsColour.cs | 79 +++++++++++++++++++ osu.Game/Overlays/SkinEditor/SkinEditor.cs | 5 ++ 3 files changed, 97 insertions(+) create mode 100644 osu.Game/Overlays/Settings/SettingsColour.cs diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 1e425c88a6..3ba46144ca 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -186,6 +186,16 @@ namespace osu.Game.Configuration break; + case BindableColour4 bColour: + yield return new SettingsColour + { + LabelText = attr.Label, + TooltipText = attr.Description, + Current = bColour + }; + + break; + case IBindable bindable: var dropdownType = typeof(ModSettingsEnumDropdown<>).MakeGenericType(bindable.GetType().GetGenericArguments()[0]); var dropdown = (Drawable)Activator.CreateInstance(dropdownType)!; @@ -227,6 +237,9 @@ namespace osu.Game.Configuration case Bindable b: return b.Value; + case BindableColour4 c: + return c.Value.ToHex(); + case IBindable u: // An unknown (e.g. enum) generic type. var valueMethod = u.GetType().GetProperty(nameof(IBindable.Value)); diff --git a/osu.Game/Overlays/Settings/SettingsColour.cs b/osu.Game/Overlays/Settings/SettingsColour.cs new file mode 100644 index 0000000000..a58c20adea --- /dev/null +++ b/osu.Game/Overlays/Settings/SettingsColour.cs @@ -0,0 +1,79 @@ +// 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.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterfaceV2; + +namespace osu.Game.Overlays.Settings +{ + public partial class SettingsColour : SettingsItem + { + protected override Drawable CreateControl() => new ColourControl(); + + public partial class ColourControl : OsuClickableContainer, IHasPopover, IHasCurrentValue + { + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + private readonly Box fill; + private readonly OsuSpriteText colourHexCode; + + public ColourControl() + { + RelativeSizeAxes = Axes.X; + Height = 40; + CornerRadius = 20; + Masking = true; + Action = this.ShowPopover; + + Children = new Drawable[] + { + fill = new Box + { + RelativeSizeAxes = Axes.Both + }, + colourHexCode = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Default.With(size: 20) + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(_ => updateColour(), true); + } + + private void updateColour() + { + fill.Colour = Current.Value; + colourHexCode.Text = Current.Value.ToHex(); + colourHexCode.Colour = OsuColour.ForegroundTextColourFor(Current.Value); + } + + public Popover GetPopover() => new OsuPopover(false) + { + Child = new OsuColourPicker + { + Current = { BindTarget = Current } + } + }; + } + } +} diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index d1e9676de7..b8e859bd63 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -33,6 +33,7 @@ using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Skinning; +using osu.Framework.Graphics.Cursor; namespace osu.Game.Overlays.SkinEditor { @@ -117,6 +118,9 @@ namespace osu.Game.Overlays.SkinEditor InternalChild = new OsuContextMenuContainer { + RelativeSizeAxes = Axes.Both, + Child = new PopoverContainer + { RelativeSizeAxes = Axes.Both, Child = new GridContainer { @@ -221,6 +225,7 @@ namespace osu.Game.Overlays.SkinEditor }, } } + } }; clipboardContent = clipboard.Content.GetBoundCopy(); From 6ec3f715d2a796413ab4e29b11d76413356725d8 Mon Sep 17 00:00:00 2001 From: PowerDaniex <140076282+u4vh3@users.noreply.github.com> Date: Wed, 10 Jul 2024 18:39:10 +0200 Subject: [PATCH 228/924] Fix formatting --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 182 ++++++++++----------- 1 file changed, 91 insertions(+), 91 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index b8e859bd63..6f7781ee9c 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -120,112 +120,112 @@ namespace osu.Game.Overlays.SkinEditor { RelativeSizeAxes = Axes.Both, Child = new PopoverContainer - { - RelativeSizeAxes = Axes.Both, - Child = new GridContainer { RelativeSizeAxes = Axes.Both, - RowDimensions = new[] + Child = new GridContainer { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - }, + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + }, - Content = new[] - { - new Drawable[] + Content = new[] { - new Container + new Drawable[] { - Name = @"Menu container", - RelativeSizeAxes = Axes.X, - Depth = float.MinValue, - Height = MENU_HEIGHT, - Children = new Drawable[] + new Container { - new EditorMenuBar + Name = @"Menu container", + RelativeSizeAxes = Axes.X, + Depth = float.MinValue, + Height = MENU_HEIGHT, + Children = new Drawable[] { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - Items = new[] + new EditorMenuBar { - new MenuItem(CommonStrings.MenuBarFile) - { - Items = new OsuMenuItem[] - { - new EditorMenuItem(Web.CommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()), - new EditorMenuItem(CommonStrings.Export, MenuItemType.Standard, () => skins.ExportCurrentSkin()) { Action = { Disabled = !RuntimeInfo.IsDesktop } }, - new OsuMenuItemSpacer(), - new EditorMenuItem(CommonStrings.RevertToDefault, MenuItemType.Destructive, () => dialogOverlay?.Push(new RevertConfirmDialog(revert))), - new OsuMenuItemSpacer(), - new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, () => skinEditorOverlay?.Hide()), - }, - }, - new MenuItem(CommonStrings.MenuBarEdit) - { - Items = new OsuMenuItem[] - { - undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo), - redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo), - new OsuMenuItemSpacer(), - cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut), - copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy), - pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste), - cloneMenuItem = new EditorMenuItem(CommonStrings.Clone, MenuItemType.Standard, Clone), - } - }, - } - }, - headerText = new OsuTextFlowContainer - { - TextAnchor = Anchor.TopRight, - Padding = new MarginPadding(5), - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - }, - }, - }, - }, - new Drawable[] - { - new SkinEditorSceneLibrary - { - RelativeSizeAxes = Axes.X, - }, - }, - new Drawable[] - { - new GridContainer - { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - }, - Content = new[] - { - new Drawable[] - { - componentsSidebar = new EditorSidebar(), - content = new Container - { - Depth = float.MaxValue, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Both, + Items = new[] + { + new MenuItem(CommonStrings.MenuBarFile) + { + Items = new OsuMenuItem[] + { + new EditorMenuItem(Web.CommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()), + new EditorMenuItem(CommonStrings.Export, MenuItemType.Standard, () => skins.ExportCurrentSkin()) { Action = { Disabled = !RuntimeInfo.IsDesktop } }, + new OsuMenuItemSpacer(), + new EditorMenuItem(CommonStrings.RevertToDefault, MenuItemType.Destructive, () => dialogOverlay?.Push(new RevertConfirmDialog(revert))), + new OsuMenuItemSpacer(), + new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, () => skinEditorOverlay?.Hide()), + }, + }, + new MenuItem(CommonStrings.MenuBarEdit) + { + Items = new OsuMenuItem[] + { + undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo), + redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo), + new OsuMenuItemSpacer(), + cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut), + copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy), + pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste), + cloneMenuItem = new EditorMenuItem(CommonStrings.Clone, MenuItemType.Standard, Clone), + } + }, + } }, - settingsSidebar = new EditorSidebar(), + headerText = new OsuTextFlowContainer + { + TextAnchor = Anchor.TopRight, + Padding = new MarginPadding(5), + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + }, + }, + }, + }, + new Drawable[] + { + new SkinEditorSceneLibrary + { + RelativeSizeAxes = Axes.X, + }, + }, + new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new Drawable[] + { + componentsSidebar = new EditorSidebar(), + content = new Container + { + Depth = float.MaxValue, + RelativeSizeAxes = Axes.Both, + }, + settingsSidebar = new EditorSidebar(), + } } } - } - }, + }, + } } } - } }; clipboardContent = clipboard.Content.GetBoundCopy(); From e81e356d59afc04d13e6d1738ef1c1fe8445879e Mon Sep 17 00:00:00 2001 From: Daniel Cios Date: Tue, 6 Aug 2024 11:06:08 +0200 Subject: [PATCH 229/924] Add colour customisation to skin components --- .../SkinnableComponentStrings.cs | 20 +++++++++++++++++++ .../Screens/Play/HUD/ArgonSongProgress.cs | 4 ++++ osu.Game/Screens/Play/HUD/ArgonWedgePiece.cs | 7 ++++++- .../Screens/Play/HUD/DefaultSongProgress.cs | 5 +++++ .../Components/BeatmapAttributeText.cs | 2 ++ osu.Game/Skinning/Components/BoxElement.cs | 4 ++++ osu.Game/Skinning/Components/PlayerName.cs | 2 ++ osu.Game/Skinning/Components/TextElement.cs | 2 ++ .../Skinning/FontAdjustableSkinComponent.cs | 8 ++++++++ 9 files changed, 53 insertions(+), 1 deletion(-) diff --git a/osu.Game/Localisation/SkinComponents/SkinnableComponentStrings.cs b/osu.Game/Localisation/SkinComponents/SkinnableComponentStrings.cs index d5c8d5ccec..bd22527f67 100644 --- a/osu.Game/Localisation/SkinComponents/SkinnableComponentStrings.cs +++ b/osu.Game/Localisation/SkinComponents/SkinnableComponentStrings.cs @@ -59,6 +59,26 @@ namespace osu.Game.Localisation.SkinComponents /// public static LocalisableString ShowLabelDescription => new TranslatableString(getKey(@"show_label_description"), @"Whether the component's label should be shown."); + /// + /// "Colour" + /// + public static LocalisableString Colour => new TranslatableString(getKey(@"colour"), @"Colour"); + + /// + /// "The colour of the component." + /// + public static LocalisableString ColourDescription => new TranslatableString(getKey(@"colour_description"), @"The colour of the component."); + + /// + /// "Font colour" + /// + public static LocalisableString FontColour => new TranslatableString(getKey(@"font_colour"), @"Font colour"); + + /// + /// "The colour of the font." + /// + public static LocalisableString FontColourDescription => new TranslatableString(getKey(@"font_colour_description"), @"The colour of the font."); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs index ebebfebfb3..696369921a 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Localisation.HUD; +using osu.Game.Localisation.SkinComponents; using osu.Game.Rulesets.Objects; namespace osu.Game.Screens.Play.HUD @@ -28,6 +29,8 @@ namespace osu.Game.Screens.Play.HUD [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowTime), nameof(SongProgressStrings.ShowTimeDescription))] public Bindable ShowTime { get; } = new BindableBool(true); + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour), nameof(SkinnableComponentStrings.ColourDescription))] + public new BindableColour4 Colour { get; } = new BindableColour4(Colour4.White); [Resolved] private Player? player { get; set; } @@ -114,6 +117,7 @@ namespace osu.Game.Screens.Play.HUD base.Update(); content.Height = bar.Height + bar_height + info.Height; graphContainer.Height = bar.Height; + base.Colour = Colour.Value; } protected override void UpdateProgress(double progress, bool isIntro) diff --git a/osu.Game/Screens/Play/HUD/ArgonWedgePiece.cs b/osu.Game/Screens/Play/HUD/ArgonWedgePiece.cs index 3c2e3e05ea..837e9547f0 100644 --- a/osu.Game/Screens/Play/HUD/ArgonWedgePiece.cs +++ b/osu.Game/Screens/Play/HUD/ArgonWedgePiece.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Configuration; +using osu.Game.Localisation.SkinComponents; using osu.Game.Skinning; using osuTK; @@ -21,6 +22,9 @@ namespace osu.Game.Screens.Play.HUD [SettingSource("Inverted shear")] public BindableBool InvertShear { get; } = new BindableBool(); + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour), nameof(SkinnableComponentStrings.ColourDescription))] + public new BindableColour4 Colour { get; } = new BindableColour4(Color4Extensions.FromHex("#66CCFF")); + public ArgonWedgePiece() { CornerRadius = 10f; @@ -37,7 +41,7 @@ namespace osu.Game.Screens.Play.HUD InternalChild = new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("#66CCFF").Opacity(0.0f), Color4Extensions.FromHex("#66CCFF").Opacity(0.25f)), + Colour = ColourInfo.GradientVertical(Colour.Value.Opacity(0.0f), Colour.Value.Opacity(0.25f)), }; } @@ -46,6 +50,7 @@ namespace osu.Game.Screens.Play.HUD base.LoadComplete(); InvertShear.BindValueChanged(v => Shear = new Vector2(0.8f, 0f) * (v.NewValue ? -1 : 1), true); + Colour.BindValueChanged(c => InternalChild.Colour = ColourInfo.GradientVertical(Colour.Value.Opacity(0.0f), Colour.Value.Opacity(0.25f))); } } } diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index 6b2bb2b718..512edd7106 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -10,6 +10,7 @@ using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Localisation.HUD; +using osu.Game.Localisation.SkinComponents; using osu.Game.Rulesets.Objects; using osuTK; @@ -35,6 +36,8 @@ namespace osu.Game.Screens.Play.HUD [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowTime), nameof(SongProgressStrings.ShowTimeDescription))] public Bindable ShowTime { get; } = new BindableBool(true); + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour), nameof(SkinnableComponentStrings.ColourDescription))] + public new BindableColour4 Colour { get; } = new BindableColour4(Colour4.White); [Resolved] private Player? player { get; set; } @@ -114,6 +117,8 @@ namespace osu.Game.Screens.Play.HUD if (!Precision.AlmostEquals(Height, newHeight, 5f)) content.Height = newHeight; + + base.Colour = Colour.Value; } private void updateBarVisibility() diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index c467b2e946..06f0d9cea9 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -123,6 +123,8 @@ namespace osu.Game.Skinning.Components } protected override void SetFont(FontUsage font) => text.Font = font.With(size: 40); + + protected override void SetFontColour(Colour4 fontColour) => text.Colour = fontColour; } // WARNING: DO NOT ADD ANY VALUES TO THIS ENUM ANYWHERE ELSE THAN AT THE END. diff --git a/osu.Game/Skinning/Components/BoxElement.cs b/osu.Game/Skinning/Components/BoxElement.cs index 34d389728c..e49ec0cc4d 100644 --- a/osu.Game/Skinning/Components/BoxElement.cs +++ b/osu.Game/Skinning/Components/BoxElement.cs @@ -27,6 +27,9 @@ namespace osu.Game.Skinning.Components Precision = 0.01f }; + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour), nameof(SkinnableComponentStrings.ColourDescription))] + public new BindableColour4 Colour { get; } = new BindableColour4(Colour4.White); + public BoxElement() { Size = new Vector2(400, 80); @@ -48,6 +51,7 @@ namespace osu.Game.Skinning.Components base.Update(); base.CornerRadius = CornerRadius.Value * Math.Min(DrawWidth, DrawHeight); + base.Colour = Colour.Value; } } } diff --git a/osu.Game/Skinning/Components/PlayerName.cs b/osu.Game/Skinning/Components/PlayerName.cs index 21bf615bc6..70672a1f58 100644 --- a/osu.Game/Skinning/Components/PlayerName.cs +++ b/osu.Game/Skinning/Components/PlayerName.cs @@ -53,5 +53,7 @@ namespace osu.Game.Skinning.Components } protected override void SetFont(FontUsage font) => text.Font = font.With(size: 40); + + protected override void SetFontColour(Colour4 fontColour) => text.Colour = fontColour; } } diff --git a/osu.Game/Skinning/Components/TextElement.cs b/osu.Game/Skinning/Components/TextElement.cs index 936f6a529b..9d66c58ae8 100644 --- a/osu.Game/Skinning/Components/TextElement.cs +++ b/osu.Game/Skinning/Components/TextElement.cs @@ -36,5 +36,7 @@ namespace osu.Game.Skinning.Components } protected override void SetFont(FontUsage font) => text.Font = font.With(size: 40); + + protected override void SetFontColour(Colour4 fontColour) => text.Colour = fontColour; } } diff --git a/osu.Game/Skinning/FontAdjustableSkinComponent.cs b/osu.Game/Skinning/FontAdjustableSkinComponent.cs index 8f3a1d41c6..e3052aee5c 100644 --- a/osu.Game/Skinning/FontAdjustableSkinComponent.cs +++ b/osu.Game/Skinning/FontAdjustableSkinComponent.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; @@ -20,11 +21,16 @@ namespace osu.Game.Skinning [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Font), nameof(SkinnableComponentStrings.FontDescription))] public Bindable Font { get; } = new Bindable(Typeface.Torus); + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.FontColour), nameof(SkinnableComponentStrings.FontColourDescription))] + public BindableColour4 FontColour { get; } = new BindableColour4(Colour4.White); + /// /// Implement to apply the user font selection to one or more components. /// protected abstract void SetFont(FontUsage font); + protected abstract void SetFontColour(Colour4 fontColour); + protected override void LoadComplete() { base.LoadComplete(); @@ -37,6 +43,8 @@ namespace osu.Game.Skinning FontUsage f = OsuFont.GetFont(e.NewValue, weight: fontWeight); SetFont(f); }, true); + + FontColour.BindValueChanged(e => SetFontColour(e.NewValue), true); } } } From 67f04f75a6c7a05c3135ea8328747669d4237624 Mon Sep 17 00:00:00 2001 From: Daniel Cios Date: Thu, 19 Sep 2024 15:26:27 +0200 Subject: [PATCH 230/924] Fix default color --- osu.Game/Overlays/Settings/SettingsColour.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/SettingsColour.cs b/osu.Game/Overlays/Settings/SettingsColour.cs index a58c20adea..db248331d3 100644 --- a/osu.Game/Overlays/Settings/SettingsColour.cs +++ b/osu.Game/Overlays/Settings/SettingsColour.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Settings public partial class ColourControl : OsuClickableContainer, IHasPopover, IHasCurrentValue { - private readonly BindableWithCurrent current = new BindableWithCurrent(); + private readonly BindableWithCurrent current = new BindableWithCurrent(Colour4.White); public Bindable Current { From c77afe2a132e22162be34d3703076c2f57a63f34 Mon Sep 17 00:00:00 2001 From: Daniel Cios Date: Thu, 19 Sep 2024 16:04:42 +0200 Subject: [PATCH 231/924] Add tests --- .../Settings/TestSceneSettingsSource.cs | 28 +++++-- .../UserInterface/TestSceneSettingsColour.cs | 75 +++++++++++++++++++ 2 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneSettingsColour.cs diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs index 309438e51c..f589a3baa1 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Overlays.Settings; @@ -19,16 +20,20 @@ namespace osu.Game.Tests.Visual.Settings { Children = new Drawable[] { - new FillFlowContainer + new PopoverContainer() { RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(20), - Width = 0.5f, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Padding = new MarginPadding(50), - ChildrenEnumerable = new TestTargetClass().CreateSettingsControls() + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(20), + Width = 0.5f, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Padding = new MarginPadding(50), + ChildrenEnumerable = new TestTargetClass().CreateSettingsControls() + }, }, }; } @@ -66,6 +71,13 @@ namespace osu.Game.Tests.Visual.Settings [SettingSource("Sample number textbox", "Textbox number entry", SettingControlType = typeof(SettingsNumberBox))] public Bindable IntTextBoxBindable { get; } = new Bindable(); + + [SettingSource("Sample colour", "Change the colour", SettingControlType = typeof(SettingsColour))] + public BindableColour4 ColourBindable { get; } = new BindableColour4() + { + Default = Colour4.White, + Value = Colour4.Red + }; } private enum TestEnum diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsColour.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsColour.cs new file mode 100644 index 0000000000..d3de5a8319 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsColour.cs @@ -0,0 +1,75 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Testing; +using osu.Framework.Utils; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays.Settings; +using osuTK.Graphics; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public partial class TestSceneSettingsColour : OsuManualInputManagerTestScene + { + private SettingsColour component; + + [Test] + public void TestColour() + { + createContent(); + + AddRepeatStep("set random colour", () => component.Current.Value = randomColour(), 4); + } + + [Test] + public void TestUserInteractions() + { + createContent(); + + AddStep("click colour", () => + { + InputManager.MoveMouseTo(component); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("colour picker spawned", () => this.ChildrenOfType().Any()); + } + + private void createContent() + { + Child = new PopoverContainer + { + RelativeSizeAxes = Axes.Both, + Child = new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 500, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + component = new SettingsColour + { + LabelText = "a sample component", + }, + }, + }, + }; + } + + private Colour4 randomColour() => new Color4( + RNG.NextSingle(), + RNG.NextSingle(), + RNG.NextSingle(), + 1); + } +} From 94c2f522ffa735bac22d91a07e0a92d04d9d5bff Mon Sep 17 00:00:00 2001 From: Daniel Cios Date: Thu, 19 Sep 2024 17:31:33 +0200 Subject: [PATCH 232/924] Fix spacing --- osu.Game/Screens/Play/HUD/ArgonSongProgress.cs | 1 + osu.Game/Screens/Play/HUD/DefaultSongProgress.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs index 696369921a..3a4dc42484 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs @@ -29,6 +29,7 @@ namespace osu.Game.Screens.Play.HUD [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowTime), nameof(SongProgressStrings.ShowTimeDescription))] public Bindable ShowTime { get; } = new BindableBool(true); + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour), nameof(SkinnableComponentStrings.ColourDescription))] public new BindableColour4 Colour { get; } = new BindableColour4(Colour4.White); diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index 512edd7106..25d3c5588d 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -36,6 +36,7 @@ namespace osu.Game.Screens.Play.HUD [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowTime), nameof(SongProgressStrings.ShowTimeDescription))] public Bindable ShowTime { get; } = new BindableBool(true); + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour), nameof(SkinnableComponentStrings.ColourDescription))] public new BindableColour4 Colour { get; } = new BindableColour4(Colour4.White); From b86f246095fb79b9b67df4b0ec3b16155e997dd7 Mon Sep 17 00:00:00 2001 From: Daniel Cios Date: Thu, 19 Sep 2024 19:24:05 +0200 Subject: [PATCH 233/924] Fix code inspection failure --- osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs | 4 ++-- .../Visual/UserInterface/TestSceneSettingsColour.cs | 1 - osu.Game/Overlays/Settings/SettingsColour.cs | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs index f589a3baa1..9544f77940 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Settings { Children = new Drawable[] { - new PopoverContainer() + new PopoverContainer { RelativeSizeAxes = Axes.Both, Child = new FillFlowContainer @@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Settings public Bindable IntTextBoxBindable { get; } = new Bindable(); [SettingSource("Sample colour", "Change the colour", SettingControlType = typeof(SettingsColour))] - public BindableColour4 ColourBindable { get; } = new BindableColour4() + public BindableColour4 ColourBindable { get; } = new BindableColour4 { Default = Colour4.White, Value = Colour4.Red diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsColour.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsColour.cs index d3de5a8319..75ddacc110 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsColour.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsColour.cs @@ -3,7 +3,6 @@ #nullable disable -using System.Diagnostics.CodeAnalysis; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; diff --git a/osu.Game/Overlays/Settings/SettingsColour.cs b/osu.Game/Overlays/Settings/SettingsColour.cs index db248331d3..7a091f1a54 100644 --- a/osu.Game/Overlays/Settings/SettingsColour.cs +++ b/osu.Game/Overlays/Settings/SettingsColour.cs @@ -1,5 +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 osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; From d2f97f5908176696709e4608e3813196682ec7d2 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 19 Sep 2024 20:18:24 +0200 Subject: [PATCH 234/924] take into account rotation period for each grid type --- .../Edit/OsuGridToolboxGroup.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 4b0353bd36..8752e17dad 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -104,9 +104,11 @@ namespace osu.Game.Rulesets.Osu.Edit StartPositionY.Value = point1.Y; // Get the angle between the two points and normalize to the valid range. - const float period = 180; - GridLinesRotation.Value = (MathHelper.RadiansToDegrees(MathF.Atan2(point2.Y - point1.Y, point2.X - point1.X)) - + period * 1.5f) % period - period * 0.5f; + if (!GridLinesRotation.Disabled) + { + float period = GridLinesRotation.MaxValue - GridLinesRotation.MinValue; + GridLinesRotation.Value = normalizeRotation(MathHelper.RadiansToDegrees(MathF.Atan2(point2.Y - point1.Y, point2.X - point1.X)), period); + } // Divide the distance so that there is a good density of grid lines. float dist = Vector2.Distance(point1, point2); @@ -231,13 +233,13 @@ namespace osu.Game.Rulesets.Osu.Edit switch (v.NewValue) { case PositionSnapGridType.Square: - GridLinesRotation.Value = ((GridLinesRotation.Value + 405) % 90) - 45; + GridLinesRotation.Value = normalizeRotation(GridLinesRotation.Value, 90); GridLinesRotation.MinValue = -45; GridLinesRotation.MaxValue = 45; break; case PositionSnapGridType.Triangle: - GridLinesRotation.Value = ((GridLinesRotation.Value + 390) % 60) - 30; + GridLinesRotation.Value = normalizeRotation(GridLinesRotation.Value, 60); GridLinesRotation.MinValue = -30; GridLinesRotation.MaxValue = 30; break; @@ -245,6 +247,11 @@ namespace osu.Game.Rulesets.Osu.Edit }, true); } + private float normalizeRotation(float rotation, float period) + { + return ((rotation + 360 + period * 0.5f) % period) - period * 0.5f; + } + private void nextGridSize() { Spacing.Value = Spacing.Value * 2 >= max_automatic_spacing ? Spacing.Value / 8 : Spacing.Value * 2; From 1a48b46536537eb050dc888b61c59e6c6e1eb851 Mon Sep 17 00:00:00 2001 From: Daniel Cios Date: Thu, 19 Sep 2024 21:50:59 +0200 Subject: [PATCH 235/924] Fix test failures --- .../UserInterface/TestSceneSettingsColour.cs | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsColour.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsColour.cs index 75ddacc110..6bed5f91c5 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsColour.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsColour.cs @@ -45,24 +45,27 @@ namespace osu.Game.Tests.Visual.UserInterface private void createContent() { - Child = new PopoverContainer + AddStep("create component", () => { - RelativeSizeAxes = Axes.Both, - Child = new FillFlowContainer + Child = new PopoverContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Width = 500, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] + RelativeSizeAxes = Axes.Both, + Child = new FillFlowContainer { - component = new SettingsColour + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 500, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] { - LabelText = "a sample component", + component = new SettingsColour + { + LabelText = "a sample component", + }, }, }, - }, - }; + }; + }); } private Colour4 randomColour() => new Color4( From 59ab71f786f13e258479769f21065adfcaadd234 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 20 Sep 2024 01:06:52 +0200 Subject: [PATCH 236/924] Implement minimum enclosing circle --- osu.Game/Utils/GeometryUtils.cs | 133 ++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index 8572ac6609..7e6db10a28 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; +using osu.Framework.Utils; using osu.Game.Rulesets.Objects.Types; using osuTK; @@ -218,5 +219,137 @@ namespace osu.Game.Utils return new[] { h.Position }; }); + + #region welzl_helpers + + // Function to check whether a point lies inside or on the boundaries of the circle + private static bool isInside((Vector2, float) c, Vector2 p) + { + return Precision.AlmostBigger(c.Item2, Vector2.Distance(c.Item1, p)); + } + + // Function to return a unique circle that intersects three points + private static (Vector2, float) circleFrom(Vector2 a, Vector2 b, Vector2 c) + { + if (Precision.AlmostEquals(0, (b.Y - a.Y) * (c.X - a.X) - (b.X - a.X) * (c.Y - a.Y))) + return circleFrom(a, b); + + // See: https://en.wikipedia.org/wiki/Circumscribed_circle#Cartesian_coordinates_2 + float d = 2 * (a.X * (b - c).Y + b.X * (c - a).Y + c.X * (a - b).Y); + float aSq = a.LengthSquared; + float bSq = b.LengthSquared; + float cSq = c.LengthSquared; + + var centre = new Vector2( + aSq * (b - c).Y + bSq * (c - a).Y + cSq * (a - b).Y, + aSq * (c - b).X + bSq * (a - c).X + cSq * (b - a).X) / d; + + return (centre, Vector2.Distance(a, centre)); + } + + // Function to return the smallest circle that intersects 2 points + private static (Vector2, float) circleFrom(Vector2 a, Vector2 b) + { + var centre = (a + b) / 2.0f; + return (centre, Vector2.Distance(a, b) / 2.0f); + } + + // Function to check whether a circle encloses the given points + private static bool isValidCircle((Vector2, float) c, ReadOnlySpan points) + { + // Iterating through all the points to check whether the points lie inside the circle or not + foreach (Vector2 p in points) + { + if (!isInside(c, p)) return false; + } + + return true; + } + + // Function to return the minimum enclosing circle for N <= 3 + private static (Vector2, float) minCircleTrivial(ReadOnlySpan points) + { + switch (points.Length) + { + case 0: + return (new Vector2(0, 0), 0); + + case 1: + return (points[0], 0); + + case 2: + return circleFrom(points[0], points[1]); + } + + // To check if MEC can be determined by 2 points only + for (int i = 0; i < 3; i++) + { + for (int j = i + 1; j < 3; j++) + { + var c = circleFrom(points[i], points[j]); + + if (isValidCircle(c, points)) + return c; + } + } + + return circleFrom(points[0], points[1], points[2]); + } + + // Returns the MEC using Welzl's algorithm + // Takes a set of input points P and a set R + // points on the circle boundary. + // n represents the number of points in P that are not yet processed. + private static (Vector2, float) welzlHelper(List points, ReadOnlySpan r, int n, Random random) + { + // Base case when all points processed or |R| = 3 + if (n == 0 || r.Length == 3) + return minCircleTrivial(r); + + // Pick a random point randomly + int idx = random.Next(n); + Vector2 p = points[idx]; + + // Put the picked point at the end of P since it's more efficient than + // deleting from the middle of the list + (points[idx], points[n - 1]) = (points[n - 1], points[idx]); + + // Get the MEC circle d from the set of points P - {p} + var d = welzlHelper(points, r, n - 1, random); + + // If d contains p, return d + if (isInside(d, p)) + return d; + + // Otherwise, must be on the boundary of the MEC + // Stackalloc to avoid allocations. It's safe to assume that the length of r will be at most 3 + Span r2 = stackalloc Vector2[r.Length + 1]; + r.CopyTo(r2); + r2[r.Length] = p; + + // Return the MEC for P - {p} and R U {p} + return welzlHelper(points, r2, n - 1, random); + } + + #endregion + + /// + /// Function to find the minimum enclosing circle for a collection of points. + /// + /// A tuple containing the circle center and radius. + public static (Vector2, float) MinimumEnclosingCircle(IEnumerable points) + { + // Using Welzl's algorithm to find the minimum enclosing circle + // https://www.geeksforgeeks.org/minimum-enclosing-circle-using-welzls-algorithm/ + List pCopy = points.ToList(); + return welzlHelper(pCopy, Array.Empty(), pCopy.Count, new Random()); + } + + /// + /// Function to find the minimum enclosing circle for a collection of hit objects. + /// + /// A tuple containing the circle center and radius. + public static (Vector2, float) MinimumEnclosingCircle(IEnumerable hitObjects) => + MinimumEnclosingCircle(enumerateStartAndEndPositions(hitObjects)); } } From ee006247516569cb9fdd380d66612f21290d48ee Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 20 Sep 2024 01:07:47 +0200 Subject: [PATCH 237/924] use minimum enclosing circle selection centre in rotation --- .../Edit/OsuSelectionRotationHandler.cs | 9 ++++----- .../SkinEditor/SkinSelectionRotationHandler.cs | 9 ++++----- .../Compose/Components/SelectionBoxRotationHandle.cs | 10 +++++++--- .../Compose/Components/SelectionRotationHandler.cs | 6 ++++++ 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs index 62a39d3702..44d1543ae4 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs @@ -47,7 +47,6 @@ namespace osu.Game.Rulesets.Osu.Edit private OsuHitObject[]? objectsInRotation; - private Vector2? defaultOrigin; private Dictionary? originalPositions; private Dictionary? originalPathControlPointPositions; @@ -61,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Edit changeHandler?.BeginChange(); objectsInRotation = selectedMovableObjects.ToArray(); - defaultOrigin = GeometryUtils.GetSurroundingQuad(objectsInRotation).Centre; + DefaultOrigin = GeometryUtils.MinimumEnclosingCircle(objectsInRotation).Item1; originalPositions = objectsInRotation.ToDictionary(obj => obj, obj => obj.Position); originalPathControlPointPositions = objectsInRotation.OfType().ToDictionary( obj => obj, @@ -73,9 +72,9 @@ namespace osu.Game.Rulesets.Osu.Edit if (!OperationInProgress.Value) throw new InvalidOperationException($"Cannot {nameof(Update)} a rotate operation without calling {nameof(Begin)} first!"); - Debug.Assert(objectsInRotation != null && originalPositions != null && originalPathControlPointPositions != null && defaultOrigin != null); + Debug.Assert(objectsInRotation != null && originalPositions != null && originalPathControlPointPositions != null && DefaultOrigin != null); - Vector2 actualOrigin = origin ?? defaultOrigin.Value; + Vector2 actualOrigin = origin ?? DefaultOrigin.Value; foreach (var ho in objectsInRotation) { @@ -103,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Edit objectsInRotation = null; originalPositions = null; originalPathControlPointPositions = null; - defaultOrigin = null; + DefaultOrigin = null; } private IEnumerable selectedMovableObjects => selectedItems.Cast() diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs index 36b38543d1..9fd28a1cad 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs @@ -46,7 +46,6 @@ namespace osu.Game.Overlays.SkinEditor private Drawable[]? objectsInRotation; - private Vector2? defaultOrigin; private Dictionary? originalRotations; private Dictionary? originalPositions; @@ -60,7 +59,7 @@ namespace osu.Game.Overlays.SkinEditor objectsInRotation = selectedItems.Cast().ToArray(); originalRotations = objectsInRotation.ToDictionary(d => d, d => d.Rotation); originalPositions = objectsInRotation.ToDictionary(d => d, d => d.ToScreenSpace(d.OriginPosition)); - defaultOrigin = GeometryUtils.GetSurroundingQuad(objectsInRotation.SelectMany(d => d.ScreenSpaceDrawQuad.GetVertices().ToArray())).Centre; + DefaultOrigin = GeometryUtils.GetSurroundingQuad(objectsInRotation.SelectMany(d => d.ScreenSpaceDrawQuad.GetVertices().ToArray())).Centre; base.Begin(); } @@ -70,7 +69,7 @@ namespace osu.Game.Overlays.SkinEditor if (objectsInRotation == null) throw new InvalidOperationException($"Cannot {nameof(Update)} a rotate operation without calling {nameof(Begin)} first!"); - Debug.Assert(originalRotations != null && originalPositions != null && defaultOrigin != null); + Debug.Assert(originalRotations != null && originalPositions != null && DefaultOrigin != null); if (objectsInRotation.Length == 1 && origin == null) { @@ -79,7 +78,7 @@ namespace osu.Game.Overlays.SkinEditor return; } - var actualOrigin = origin ?? defaultOrigin.Value; + var actualOrigin = origin ?? DefaultOrigin.Value; foreach (var drawableItem in objectsInRotation) { @@ -100,7 +99,7 @@ namespace osu.Game.Overlays.SkinEditor objectsInRotation = null; originalPositions = null; originalRotations = null; - defaultOrigin = null; + DefaultOrigin = null; base.Commit(); } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs index c62e0e0d41..898efc8b5e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs @@ -113,9 +113,13 @@ namespace osu.Game.Screens.Edit.Compose.Components private float convertDragEventToAngleOfRotation(DragEvent e) { - // Adjust coordinate system to the center of SelectionBox - float startAngle = MathF.Atan2(e.LastMousePosition.Y - selectionBox.DrawHeight / 2, e.LastMousePosition.X - selectionBox.DrawWidth / 2); - float endAngle = MathF.Atan2(e.MousePosition.Y - selectionBox.DrawHeight / 2, e.MousePosition.X - selectionBox.DrawWidth / 2); + // Adjust coordinate system to the center of the selection + Vector2 center = rotationHandler?.DefaultOrigin is not null + ? selectionBox.ToLocalSpace(rotationHandler.ToScreenSpace(rotationHandler.DefaultOrigin.Value)) + : selectionBox.DrawSize / 2; + + float startAngle = MathF.Atan2(e.LastMousePosition.Y - center.Y, e.LastMousePosition.X - center.X); + float endAngle = MathF.Atan2(e.MousePosition.Y - center.Y, e.MousePosition.X - center.X); return (endAngle - startAngle) * 180 / MathF.PI; } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs index 532daaf7fa..680acad114 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs @@ -27,6 +27,12 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public Bindable CanRotateAroundPlayfieldOrigin { get; private set; } = new BindableBool(); + /// + /// Implementation-defined origin point to rotate around when no explicit origin is provided. + /// This field is only assigned during a rotation operation. + /// + public Vector2? DefaultOrigin { get; protected set; } + /// /// Performs a single, instant, atomic rotation operation. /// From 8e11cda41a35919cfddf5b6e601686b4f549b335 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 20 Sep 2024 01:07:54 +0200 Subject: [PATCH 238/924] use minimum enclosing circle selection centre in scale --- osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs | 2 +- osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 56c3ba9315..e9d5b3105a 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -84,10 +84,10 @@ namespace osu.Game.Rulesets.Osu.Edit OriginalSurroundingQuad = objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider ? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => slider.Position + p.Position)) : GeometryUtils.GetSurroundingQuad(objectsInScale.Keys); - defaultOrigin = OriginalSurroundingQuad.Value.Centre; originalConvexHull = objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider2 ? GeometryUtils.GetConvexHull(slider2.Path.ControlPoints.Select(p => slider2.Position + p.Position)) : GeometryUtils.GetConvexHull(objectsInScale.Keys); + defaultOrigin = GeometryUtils.MinimumEnclosingCircle(originalConvexHull).Item1; } public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs index 977aaade99..6915769212 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs @@ -67,7 +67,7 @@ namespace osu.Game.Overlays.SkinEditor objectsInScale = selectedItems.Cast().ToDictionary(d => d, d => new OriginalDrawableState(d)); OriginalSurroundingQuad = ToLocalSpace(GeometryUtils.GetSurroundingQuad(objectsInScale.SelectMany(d => d.Key.ScreenSpaceDrawQuad.GetVertices().ToArray()))); - defaultOrigin = OriginalSurroundingQuad.Value.Centre; + defaultOrigin = ToLocalSpace(GeometryUtils.MinimumEnclosingCircle(objectsInScale.SelectMany(d => d.Key.ScreenSpaceDrawQuad.GetVertices().ToArray())).Item1); isFlippedX = false; isFlippedY = false; From ec575e9de4a8a5ffc87afe58ea954443a3aa0ba3 Mon Sep 17 00:00:00 2001 From: Daniel Cios Date: Fri, 20 Sep 2024 16:38:26 +0200 Subject: [PATCH 239/924] Rename Colour to AccentColour --- osu.Game/Screens/Play/HUD/ArgonSongProgress.cs | 4 ++-- osu.Game/Screens/Play/HUD/ArgonWedgePiece.cs | 6 +++--- osu.Game/Screens/Play/HUD/DefaultSongProgress.cs | 4 ++-- osu.Game/Skinning/Components/BoxElement.cs | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs index 3a4dc42484..1a18466743 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.Play.HUD public Bindable ShowTime { get; } = new BindableBool(true); [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour), nameof(SkinnableComponentStrings.ColourDescription))] - public new BindableColour4 Colour { get; } = new BindableColour4(Colour4.White); + public BindableColour4 AccentColour { get; } = new BindableColour4(Colour4.White); [Resolved] private Player? player { get; set; } @@ -118,7 +118,7 @@ namespace osu.Game.Screens.Play.HUD base.Update(); content.Height = bar.Height + bar_height + info.Height; graphContainer.Height = bar.Height; - base.Colour = Colour.Value; + Colour = AccentColour.Value; } protected override void UpdateProgress(double progress, bool isIntro) diff --git a/osu.Game/Screens/Play/HUD/ArgonWedgePiece.cs b/osu.Game/Screens/Play/HUD/ArgonWedgePiece.cs index 837e9547f0..fb2e93b62b 100644 --- a/osu.Game/Screens/Play/HUD/ArgonWedgePiece.cs +++ b/osu.Game/Screens/Play/HUD/ArgonWedgePiece.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.Play.HUD public BindableBool InvertShear { get; } = new BindableBool(); [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour), nameof(SkinnableComponentStrings.ColourDescription))] - public new BindableColour4 Colour { get; } = new BindableColour4(Color4Extensions.FromHex("#66CCFF")); + public BindableColour4 AccentColour { get; } = new BindableColour4(Color4Extensions.FromHex("#66CCFF")); public ArgonWedgePiece() { @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Play.HUD InternalChild = new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Colour.Value.Opacity(0.0f), Colour.Value.Opacity(0.25f)), + Colour = ColourInfo.GradientVertical(AccentColour.Value.Opacity(0.0f), AccentColour.Value.Opacity(0.25f)), }; } @@ -50,7 +50,7 @@ namespace osu.Game.Screens.Play.HUD base.LoadComplete(); InvertShear.BindValueChanged(v => Shear = new Vector2(0.8f, 0f) * (v.NewValue ? -1 : 1), true); - Colour.BindValueChanged(c => InternalChild.Colour = ColourInfo.GradientVertical(Colour.Value.Opacity(0.0f), Colour.Value.Opacity(0.25f))); + AccentColour.BindValueChanged(c => InternalChild.Colour = ColourInfo.GradientVertical(AccentColour.Value.Opacity(0.0f), AccentColour.Value.Opacity(0.25f))); } } } diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index 25d3c5588d..93d75a22ba 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Play.HUD public Bindable ShowTime { get; } = new BindableBool(true); [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour), nameof(SkinnableComponentStrings.ColourDescription))] - public new BindableColour4 Colour { get; } = new BindableColour4(Colour4.White); + public BindableColour4 AccentColour { get; } = new BindableColour4(Colour4.White); [Resolved] private Player? player { get; set; } @@ -119,7 +119,7 @@ namespace osu.Game.Screens.Play.HUD if (!Precision.AlmostEquals(Height, newHeight, 5f)) content.Height = newHeight; - base.Colour = Colour.Value; + Colour = AccentColour.Value; } private void updateBarVisibility() diff --git a/osu.Game/Skinning/Components/BoxElement.cs b/osu.Game/Skinning/Components/BoxElement.cs index e49ec0cc4d..633fb0c327 100644 --- a/osu.Game/Skinning/Components/BoxElement.cs +++ b/osu.Game/Skinning/Components/BoxElement.cs @@ -28,7 +28,7 @@ namespace osu.Game.Skinning.Components }; [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour), nameof(SkinnableComponentStrings.ColourDescription))] - public new BindableColour4 Colour { get; } = new BindableColour4(Colour4.White); + public BindableColour4 AccentColour { get; } = new BindableColour4(Colour4.White); public BoxElement() { @@ -51,7 +51,7 @@ namespace osu.Game.Skinning.Components base.Update(); base.CornerRadius = CornerRadius.Value * Math.Min(DrawWidth, DrawHeight); - base.Colour = Colour.Value; + Colour = AccentColour.Value; } } } From 73b6744a97ed3ca36db2c9ca99a1f451320f962c Mon Sep 17 00:00:00 2001 From: Daniel Cios Date: Fri, 20 Sep 2024 16:50:17 +0200 Subject: [PATCH 240/924] Rename FontColour to TextColour --- .../SkinComponents/SkinnableComponentStrings.cs | 8 ++++---- osu.Game/Skinning/Components/BeatmapAttributeText.cs | 2 +- osu.Game/Skinning/Components/PlayerName.cs | 2 +- osu.Game/Skinning/Components/TextElement.cs | 2 +- osu.Game/Skinning/FontAdjustableSkinComponent.cs | 8 ++++---- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Localisation/SkinComponents/SkinnableComponentStrings.cs b/osu.Game/Localisation/SkinComponents/SkinnableComponentStrings.cs index bd22527f67..33fda23cb0 100644 --- a/osu.Game/Localisation/SkinComponents/SkinnableComponentStrings.cs +++ b/osu.Game/Localisation/SkinComponents/SkinnableComponentStrings.cs @@ -70,14 +70,14 @@ namespace osu.Game.Localisation.SkinComponents public static LocalisableString ColourDescription => new TranslatableString(getKey(@"colour_description"), @"The colour of the component."); /// - /// "Font colour" + /// "Text colour" /// - public static LocalisableString FontColour => new TranslatableString(getKey(@"font_colour"), @"Font colour"); + public static LocalisableString TextColour => new TranslatableString(getKey(@"text_colour"), @"Text colour"); /// - /// "The colour of the font." + /// "The colour of the text." /// - public static LocalisableString FontColourDescription => new TranslatableString(getKey(@"font_colour_description"), @"The colour of the font."); + public static LocalisableString TextColourDescription => new TranslatableString(getKey(@"text_colour_description"), @"The colour of the text."); private static string getKey(string key) => $@"{prefix}:{key}"; } diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index 06f0d9cea9..6e1d655cef 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -124,7 +124,7 @@ namespace osu.Game.Skinning.Components protected override void SetFont(FontUsage font) => text.Font = font.With(size: 40); - protected override void SetFontColour(Colour4 fontColour) => text.Colour = fontColour; + protected override void SetTextColour(Colour4 textColour) => text.Colour = textColour; } // WARNING: DO NOT ADD ANY VALUES TO THIS ENUM ANYWHERE ELSE THAN AT THE END. diff --git a/osu.Game/Skinning/Components/PlayerName.cs b/osu.Game/Skinning/Components/PlayerName.cs index 70672a1f58..5b6ded0cc5 100644 --- a/osu.Game/Skinning/Components/PlayerName.cs +++ b/osu.Game/Skinning/Components/PlayerName.cs @@ -54,6 +54,6 @@ namespace osu.Game.Skinning.Components protected override void SetFont(FontUsage font) => text.Font = font.With(size: 40); - protected override void SetFontColour(Colour4 fontColour) => text.Colour = fontColour; + protected override void SetTextColour(Colour4 textColour) => text.Colour = textColour; } } diff --git a/osu.Game/Skinning/Components/TextElement.cs b/osu.Game/Skinning/Components/TextElement.cs index 9d66c58ae8..6e875c5590 100644 --- a/osu.Game/Skinning/Components/TextElement.cs +++ b/osu.Game/Skinning/Components/TextElement.cs @@ -37,6 +37,6 @@ namespace osu.Game.Skinning.Components protected override void SetFont(FontUsage font) => text.Font = font.With(size: 40); - protected override void SetFontColour(Colour4 fontColour) => text.Colour = fontColour; + protected override void SetTextColour(Colour4 textColour) => text.Colour = textColour; } } diff --git a/osu.Game/Skinning/FontAdjustableSkinComponent.cs b/osu.Game/Skinning/FontAdjustableSkinComponent.cs index e3052aee5c..0821edf7fc 100644 --- a/osu.Game/Skinning/FontAdjustableSkinComponent.cs +++ b/osu.Game/Skinning/FontAdjustableSkinComponent.cs @@ -21,15 +21,15 @@ namespace osu.Game.Skinning [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Font), nameof(SkinnableComponentStrings.FontDescription))] public Bindable Font { get; } = new Bindable(Typeface.Torus); - [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.FontColour), nameof(SkinnableComponentStrings.FontColourDescription))] - public BindableColour4 FontColour { get; } = new BindableColour4(Colour4.White); + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.TextColour), nameof(SkinnableComponentStrings.TextColourDescription))] + public BindableColour4 TextColour { get; } = new BindableColour4(Colour4.White); /// /// Implement to apply the user font selection to one or more components. /// protected abstract void SetFont(FontUsage font); - protected abstract void SetFontColour(Colour4 fontColour); + protected abstract void SetTextColour(Colour4 textColour); protected override void LoadComplete() { @@ -44,7 +44,7 @@ namespace osu.Game.Skinning SetFont(f); }, true); - FontColour.BindValueChanged(e => SetFontColour(e.NewValue), true); + TextColour.BindValueChanged(e => SetTextColour(e.NewValue), true); } } } From 8ceea9a5f7c80b6bc8d799380f9b70438f4dc65a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Fri, 20 Sep 2024 17:19:38 +0200 Subject: [PATCH 241/924] Use scale origin when scaling sliders --- .../Edit/OsuSelectionScaleHandler.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 56c3ba9315..ea16946dcb 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -105,9 +105,7 @@ namespace osu.Game.Rulesets.Osu.Edit // is not looking to change the duration of the slider but expand the whole pattern. if (objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider) { - var originalInfo = objectsInScale[slider]; - Debug.Assert(originalInfo.PathControlPointPositions != null && originalInfo.PathControlPointTypes != null); - scaleSlider(slider, scale, originalInfo.PathControlPointPositions, originalInfo.PathControlPointTypes, axisRotation); + scaleSlider(slider, scale, actualOrigin, objectsInScale[slider], axisRotation); } else { @@ -159,21 +157,25 @@ namespace osu.Game.Rulesets.Osu.Edit return scale; } - private void scaleSlider(Slider slider, Vector2 scale, Vector2[] originalPathPositions, PathType?[] originalPathTypes, float axisRotation = 0) + private void scaleSlider(Slider slider, Vector2 scale, Vector2 origin, OriginalHitObjectState originalInfo, float axisRotation = 0) { + Debug.Assert(originalInfo.PathControlPointPositions != null && originalInfo.PathControlPointTypes != null); + scale = Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON)); // Maintain the path types in case they were defaulted to bezier at some point during scaling for (int i = 0; i < slider.Path.ControlPoints.Count; i++) { - slider.Path.ControlPoints[i].Position = GeometryUtils.GetScaledPosition(scale, Vector2.Zero, originalPathPositions[i], axisRotation); - slider.Path.ControlPoints[i].Type = originalPathTypes[i]; + slider.Path.ControlPoints[i].Position = GeometryUtils.GetScaledPosition(scale, Vector2.Zero, originalInfo.PathControlPointPositions[i], axisRotation); + slider.Path.ControlPoints[i].Type = originalInfo.PathControlPointTypes[i]; } // Snap the slider's length to the current beat divisor // to calculate the final resulting duration / bounding box before the final checks. slider.SnapTo(snapProvider); + slider.Position = GeometryUtils.GetScaledPosition(scale, origin, originalInfo.Position, axisRotation); + //if sliderhead or sliderend end up outside playfield, revert scaling. Quad scaledQuad = GeometryUtils.GetSurroundingQuad(new OsuHitObject[] { slider }); (bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad); @@ -182,7 +184,9 @@ namespace osu.Game.Rulesets.Osu.Edit return; for (int i = 0; i < slider.Path.ControlPoints.Count; i++) - slider.Path.ControlPoints[i].Position = originalPathPositions[i]; + slider.Path.ControlPoints[i].Position = originalInfo.PathControlPointPositions[i]; + + slider.Position = originalInfo.Position; // Snap the slider's length again to undo the potentially-invalid length applied by the previous snap. slider.SnapTo(snapProvider); From 8bca8d60722039460f2446d5a083f151b4100e37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Fri, 20 Sep 2024 17:38:49 +0200 Subject: [PATCH 242/924] Restore previous scale behavior when using scale popover --- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index dff370d259..ec347939e7 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -10,7 +10,10 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; +using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.RadioButtons; using osuTK; @@ -35,6 +38,8 @@ namespace osu.Game.Rulesets.Osu.Edit private OsuCheckbox xCheckBox = null!; private OsuCheckbox yCheckBox = null!; + private BindableList selectedItems { get; } = new BindableList(); + public PreciseScalePopover(OsuSelectionScaleHandler scaleHandler, OsuGridToolboxGroup gridToolbox) { this.scaleHandler = scaleHandler; @@ -44,8 +49,10 @@ namespace osu.Game.Rulesets.Osu.Edit } [BackgroundDependencyLoader] - private void load() + private void load(EditorBeatmap editorBeatmap) { + selectedItems.BindTo(editorBeatmap.SelectedHitObjects); + Child = new FillFlowContainer { Width = 220, @@ -196,6 +203,7 @@ namespace osu.Game.Rulesets.Osu.Edit { ScaleOrigin.GridCentre => gridToolbox.StartPosition.Value, ScaleOrigin.PlayfieldCentre => OsuPlayfield.BASE_SIZE / 2, + ScaleOrigin.SelectionCentre when selectedItems.Count == 1 && selectedItems.First() is Slider slider => slider.Position, ScaleOrigin.SelectionCentre => null, _ => throw new ArgumentOutOfRangeException(nameof(scale)) }; From 59df9cbf0ff76e1bdf3d3b391600fe6444aeba71 Mon Sep 17 00:00:00 2001 From: Daniel Cios Date: Fri, 20 Sep 2024 18:07:26 +0200 Subject: [PATCH 243/924] Remove nullable disable --- .../Visual/UserInterface/TestSceneSettingsColour.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsColour.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsColour.cs index 6bed5f91c5..8d28116950 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsColour.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsColour.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; @@ -19,14 +17,14 @@ namespace osu.Game.Tests.Visual.UserInterface { public partial class TestSceneSettingsColour : OsuManualInputManagerTestScene { - private SettingsColour component; + private SettingsColour? component; [Test] public void TestColour() { createContent(); - AddRepeatStep("set random colour", () => component.Current.Value = randomColour(), 4); + AddRepeatStep("set random colour", () => component!.Current.Value = randomColour(), 4); } [Test] @@ -36,7 +34,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("click colour", () => { - InputManager.MoveMouseTo(component); + InputManager.MoveMouseTo(component!); InputManager.Click(MouseButton.Left); }); From 2dbbbe270daf475afeec30539af438d08de1956e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Sat, 21 Sep 2024 13:37:41 +0200 Subject: [PATCH 244/924] Scale around center when pressing alt while dragging selection box scale handle --- .../Edit/Compose/Components/SelectionBoxScaleHandle.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs index 7b0943c1d0..42e7b8c219 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs @@ -50,14 +50,14 @@ namespace osu.Game.Screens.Edit.Compose.Components rawScale = convertDragEventToScaleMultiplier(e); - applyScale(shouldLockAspectRatio: isCornerAnchor(originalAnchor) && e.ShiftPressed); + applyScale(shouldLockAspectRatio: isCornerAnchor(originalAnchor) && e.ShiftPressed, ignoreAnchor: e.AltPressed); } protected override bool OnKeyDown(KeyDownEvent e) { if (IsDragged) { - applyScale(shouldLockAspectRatio: isCornerAnchor(originalAnchor) && e.ShiftPressed); + applyScale(shouldLockAspectRatio: isCornerAnchor(originalAnchor) && e.ShiftPressed, ignoreAnchor: e.AltPressed); return true; } @@ -69,7 +69,7 @@ namespace osu.Game.Screens.Edit.Compose.Components base.OnKeyUp(e); if (IsDragged) - applyScale(shouldLockAspectRatio: isCornerAnchor(originalAnchor) && e.ShiftPressed); + applyScale(shouldLockAspectRatio: isCornerAnchor(originalAnchor) && e.ShiftPressed, ignoreAnchor: e.AltPressed); } protected override void OnDragEnd(DragEndEvent e) @@ -100,13 +100,13 @@ namespace osu.Game.Screens.Edit.Compose.Components if ((originalAnchor & Anchor.y0) > 0) scale.Y = -scale.Y; } - private void applyScale(bool shouldLockAspectRatio) + private void applyScale(bool shouldLockAspectRatio, bool ignoreAnchor = false) { var newScale = shouldLockAspectRatio ? new Vector2((rawScale.X + rawScale.Y) * 0.5f) : rawScale; - var scaleOrigin = originalAnchor.Opposite().PositionOnQuad(scaleHandler!.OriginalSurroundingQuad!.Value); + Vector2? scaleOrigin = ignoreAnchor ? null : originalAnchor.Opposite().PositionOnQuad(scaleHandler!.OriginalSurroundingQuad!.Value); scaleHandler!.Update(newScale, scaleOrigin, getAdjustAxis()); } From 3180468db1001294266b2f59f1451802c6e2b1f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Sat, 21 Sep 2024 14:22:17 +0200 Subject: [PATCH 245/924] Prevent the distance snap grid from being activated by alt key while dragging select box handle --- .../Edit/CatchHitObjectComposer.cs | 20 +++++++++++++++++++ .../Edit/OsuHitObjectComposer.cs | 2 ++ .../Edit/ComposerDistanceSnapProvider.cs | 17 +--------------- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index 83f48816f9..978aeba4ce 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -114,6 +114,26 @@ namespace osu.Game.Rulesets.Catch.Edit { } + protected override bool OnKeyDown(KeyDownEvent e) + { + if (e.Repeat) + return false; + + handleToggleViaKey(e); + return base.OnKeyDown(e); + } + + protected override void OnKeyUp(KeyUpEvent e) + { + handleToggleViaKey(e); + base.OnKeyUp(e); + } + + private void handleToggleViaKey(KeyboardEvent key) + { + DistanceSnapProvider.HandleToggleViaKey(key); + } + public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All) { var result = base.FindSnappedPositionAndTime(screenSpacePosition, snapType); diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 8fc2a9b7d3..c94dba6b23 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -369,6 +369,8 @@ namespace osu.Game.Rulesets.Osu.Edit gridSnapMomentary = shiftPressed; rectangularGridSnapToggle.Value = rectangularGridSnapToggle.Value == TernaryState.False ? TernaryState.True : TernaryState.False; } + + DistanceSnapProvider.HandleToggleViaKey(key); } private DistanceSnapGrid createDistanceSnapGrid(IEnumerable selectedHitObjects) diff --git a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs index b9850a94a3..979492fd8b 100644 --- a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs @@ -195,22 +195,7 @@ namespace osu.Game.Rulesets.Edit new TernaryButton(DistanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = OsuIcon.EditorDistanceSnap }) }; - protected override bool OnKeyDown(KeyDownEvent e) - { - if (e.Repeat) - return false; - - handleToggleViaKey(e); - return base.OnKeyDown(e); - } - - protected override void OnKeyUp(KeyUpEvent e) - { - handleToggleViaKey(e); - base.OnKeyUp(e); - } - - private void handleToggleViaKey(KeyboardEvent key) + public void HandleToggleViaKey(KeyboardEvent key) { bool altPressed = key.AltPressed; From 0077ba72ecac49a7b79916a76956c7dd02f89038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Sat, 21 Sep 2024 14:59:47 +0200 Subject: [PATCH 246/924] Freeze select box buttons in place as long as they are hovered --- .../Edit/Compose/Components/SelectionBox.cs | 26 +++++++++++++++++++ .../Compose/Components/SelectionBoxButton.cs | 9 +++++++ 2 files changed, 35 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 0cc8a8273f..39f0011a12 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; @@ -284,8 +285,12 @@ namespace osu.Game.Screens.Edit.Compose.Components Action = action }; + button.OperationStarted += freezeButtonPosition; + button.HoverLost += unfreezeButtonPosition; + button.OperationStarted += operationStarted; button.OperationEnded += operationEnded; + buttons.Add(button); return button; @@ -357,8 +362,29 @@ namespace osu.Game.Screens.Edit.Compose.Components OperationStarted?.Invoke(); } + private Quad? frozenButtonsDrawQuad; + + private void freezeButtonPosition() + { + frozenButtonsDrawQuad = buttons.ScreenSpaceDrawQuad; + } + + private void unfreezeButtonPosition() + { + frozenButtonsDrawQuad = null; + } + private void ensureButtonsOnScreen() { + if (frozenButtonsDrawQuad != null) + { + buttons.Anchor = Anchor.TopLeft; + buttons.Origin = Anchor.TopLeft; + + buttons.Position = ToLocalSpace(frozenButtonsDrawQuad.Value.TopLeft) - new Vector2(button_padding); + return; + } + buttons.Position = Vector2.Zero; var thisQuad = ScreenSpaceDrawQuad; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs index 6108d44c81..e355add40b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs @@ -21,6 +21,8 @@ namespace osu.Game.Screens.Edit.Compose.Components public Action? Action; + public event Action? HoverLost; + public SelectionBoxButton(IconUsage iconUsage, string tooltip) { this.iconUsage = iconUsage; @@ -61,6 +63,13 @@ namespace osu.Game.Screens.Edit.Compose.Components icon.FadeColour(!IsHeld && IsHovered ? Color4.White : Color4.Black, TRANSFORM_DURATION, Easing.OutQuint); } + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + + HoverLost?.Invoke(); + } + public LocalisableString TooltipText { get; } } } From 1095f35025603ca1e948483e732d6d4346f6c51c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Sat, 21 Sep 2024 15:25:37 +0200 Subject: [PATCH 247/924] Only store position instead of entire draw quad --- .../Screens/Edit/Compose/Components/SelectionBox.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 39f0011a12..4eae2b77f6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -6,7 +6,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; @@ -362,26 +361,26 @@ namespace osu.Game.Screens.Edit.Compose.Components OperationStarted?.Invoke(); } - private Quad? frozenButtonsDrawQuad; + private Vector2? frozenButtonsPosition; private void freezeButtonPosition() { - frozenButtonsDrawQuad = buttons.ScreenSpaceDrawQuad; + frozenButtonsPosition = buttons.ScreenSpaceDrawQuad.TopLeft; } private void unfreezeButtonPosition() { - frozenButtonsDrawQuad = null; + frozenButtonsPosition = null; } private void ensureButtonsOnScreen() { - if (frozenButtonsDrawQuad != null) + if (frozenButtonsPosition != null) { buttons.Anchor = Anchor.TopLeft; buttons.Origin = Anchor.TopLeft; - buttons.Position = ToLocalSpace(frozenButtonsDrawQuad.Value.TopLeft) - new Vector2(button_padding); + buttons.Position = ToLocalSpace(frozenButtonsPosition.Value) - new Vector2(button_padding); return; } From 1b77b3912baf0d991da00c01cd986b0439398cb5 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 22 Sep 2024 15:01:58 +0300 Subject: [PATCH 248/924] initial commit --- .../Difficulty/CatchDifficultyCalculator.cs | 2 +- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 3 +-- .../Difficulty/TaikoDifficultyCalculator.cs | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index 0899212b6c..f78a6b4703 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty StarRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier, Mods = mods, ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0, - MaxCombo = beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet)), + MaxCombo = beatmap.GetMaxCombo(), }; return attributes; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index e93475ecff..c4fcd1f760 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -81,7 +81,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate; double drainRate = beatmap.Difficulty.DrainRate; - int maxCombo = beatmap.GetMaxCombo(); int hitCirclesCount = beatmap.HitObjects.Count(h => h is HitCircle); int sliderCount = beatmap.HitObjects.Count(h => h is Slider); @@ -104,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, OverallDifficulty = (80 - hitWindowGreat) / 6, DrainRate = drainRate, - MaxCombo = maxCombo, + MaxCombo = beatmap.GetMaxCombo(), HitCircleCount = hitCirclesCount, SliderCount = sliderCount, SpinnerCount = spinnerCount, diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 28323693d0..6a1a047b7a 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty ColourDifficulty = colourRating, PeakDifficulty = combinedRating, GreatHitWindow = hitWindows.WindowFor(HitResult.Great) / clockRate, - MaxCombo = beatmap.HitObjects.Count(h => h is Hit), + MaxCombo = beatmap.GetMaxCombo(), }; return attributes; From 2995df7903c8c22bb14eec4ebf79ca6284a4f98e Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 22 Sep 2024 15:04:21 +0300 Subject: [PATCH 249/924] removed unnecessary includes --- osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs | 1 - osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index f78a6b4703..7d21409ee8 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Difficulty.Preprocessing; diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 6a1a047b7a..e3c550fbe9 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -14,7 +14,6 @@ using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour; using osu.Game.Rulesets.Taiko.Difficulty.Skills; using osu.Game.Rulesets.Taiko.Mods; -using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Scoring; namespace osu.Game.Rulesets.Taiko.Difficulty From 881c9dfbba2753c76aabba81948ec200b507b8fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 23 Sep 2024 09:49:25 +0200 Subject: [PATCH 250/924] Fix score being cloned in async method causing random errors (again) Compare: https://github.com/ppy/osu/pull/24548. I don't have a reproduction scenario (judging from the stack trace of the crash it's likely to be nigh-impossible to concoct a reliable one), but there is some circumstantial evidence that this might help, namely that that previous fix above worked, and the pathway that's failing here is similarly async to the one that pull fixed. So I'm just gonna start with that and hope that it does the job. --- osu.Game/Screens/Play/SubmittingPlayer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index aea3bf6d5c..24c5b2c3d4 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -234,9 +234,12 @@ namespace osu.Game.Screens.Play { if (LoadedBeatmapSuccessfully) { + // compare: https://github.com/ppy/osu/blob/ccf1acce56798497edfaf92d3ece933469edcf0a/osu.Game/Screens/Play/Player.cs#L848-L851 + var scoreCopy = Score.DeepClone(); + Task.Run(async () => { - await submitScore(Score.DeepClone()).ConfigureAwait(false); + await submitScore(scoreCopy).ConfigureAwait(false); spectatorClient.EndPlaying(GameplayState); }).FireAndForget(); } From e04b88a9b0093c0902e4f6ecf7e6ad2d64174a84 Mon Sep 17 00:00:00 2001 From: StanR Date: Mon, 23 Sep 2024 13:49:25 +0500 Subject: [PATCH 251/924] Replace speed buff with aim buff --- osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs | 2 +- osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 2 +- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index e34989f162..9c7191dec5 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators { private const int history_time_max = 4 * 1000; // 4 seconds private const int history_objects_max = 24; - private const double rhythm_overall_multiplier = 1.0; + private const double rhythm_overall_multiplier = 0.95; private const double rhythm_ratio_multiplier = 14.0; /// diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 3f6b22bbb1..7963e92c8d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private double currentStrain; - private double skillMultiplier => 23.55; + private double skillMultiplier => 23.65; private double strainDecayBase => 0.15; private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 37420ed026..f7f081b7ea 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// public class Speed : OsuStrainSkill { - private double skillMultiplier => 1.38; + private double skillMultiplier => 1.375; private double strainDecayBase => 0.3; private double currentStrain; From 92b5650ff8dab72c298a396960cb5ef51e1a5d3f Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 23 Sep 2024 10:56:03 +0200 Subject: [PATCH 252/924] fix outdated comment --- .../Screens/Edit/Compose/Components/SelectionRotationHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs index 680acad114..af3b3d6489 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs @@ -13,7 +13,7 @@ namespace osu.Game.Screens.Edit.Compose.Components public partial class SelectionRotationHandler : Component { /// - /// Whether there is any ongoing scale operation right now. + /// Whether there is any ongoing rotation operation right now. /// public Bindable OperationInProgress { get; private set; } = new BindableBool(); From 0f758ca25f6d68a0b4a0c57bc3ea0e730d854172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 23 Sep 2024 11:08:31 +0200 Subject: [PATCH 253/924] Continue displaying storyboard even if fully dimmed in specific circumstances Closes https://github.com/ppy/osu/issues/9315. Closes https://github.com/ppy/osu/issues/29867. Notably, this does nothing about https://github.com/ppy/osu/issues/25075, but I'm not sure what to do with that one in the first place. --- osu.Game/Screens/Play/DimmableStoryboard.cs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/DimmableStoryboard.cs b/osu.Game/Screens/Play/DimmableStoryboard.cs index 40cc0f66ad..84d99ea863 100644 --- a/osu.Game/Screens/Play/DimmableStoryboard.cs +++ b/osu.Game/Screens/Play/DimmableStoryboard.cs @@ -3,7 +3,9 @@ #nullable disable +using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; @@ -24,6 +26,21 @@ namespace osu.Game.Screens.Play private readonly Storyboard storyboard; private readonly IReadOnlyList mods; + /// + /// In certain circumstances, the storyboard cannot be hidden entirely even if it is fully dimmed. Such circumstances include: + /// + /// + /// cases where the storyboard has an overlay layer sprite, as it should continue to display fully dimmed + /// in front of the playfield (https://github.com/ppy/osu/issues/29867), + /// + /// + /// cases where the storyboard includes samples - as they are played back via drawable samples, + /// they must be present for the playback to occur (https://github.com/ppy/osu/issues/9315). + /// + /// + /// + private readonly Lazy storyboardMustAlwaysBePresent; + private DrawableStoryboard drawableStoryboard; /// @@ -38,6 +55,8 @@ namespace osu.Game.Screens.Play { this.storyboard = storyboard; this.mods = mods; + + storyboardMustAlwaysBePresent = new Lazy(() => storyboard.GetLayer(@"Overlay").Elements.Any() || storyboard.Layers.Any(l => l.Elements.OfType().Any())); } [BackgroundDependencyLoader] @@ -54,7 +73,7 @@ namespace osu.Game.Screens.Play base.LoadComplete(); } - protected override bool ShowDimContent => IgnoreUserSettings.Value || (ShowStoryboard.Value && DimLevel < 1); + protected override bool ShowDimContent => IgnoreUserSettings.Value || (ShowStoryboard.Value && (DimLevel < 1 || storyboardMustAlwaysBePresent.Value)); private void initializeStoryboard(bool async) { From a9ebfbe431e4616a7a0c3ea49182065839471014 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 23 Sep 2024 11:37:42 +0200 Subject: [PATCH 254/924] Assert default origin not null in rotation handle --- osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs | 1 + .../Edit/Compose/Components/SelectionBoxRotationHandle.cs | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs index 30f397f518..2bf07d8e27 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs @@ -84,6 +84,7 @@ namespace osu.Game.Tests.Visual.Editing targetContainer = getTargetContainer(); initialRotation = targetContainer!.Rotation; + DefaultOrigin = ToLocalSpace(targetContainer.ToScreenSpace(Vector2.Zero)); base.Begin(); } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs index 898efc8b5e..03d600bfa2 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs @@ -77,6 +77,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { base.OnDrag(e); + if (rotationHandler == null || !rotationHandler.OperationInProgress.Value) return; + rawCumulativeRotation += convertDragEventToAngleOfRotation(e); applyRotation(shouldSnap: e.ShiftPressed); @@ -114,9 +116,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private float convertDragEventToAngleOfRotation(DragEvent e) { // Adjust coordinate system to the center of the selection - Vector2 center = rotationHandler?.DefaultOrigin is not null - ? selectionBox.ToLocalSpace(rotationHandler.ToScreenSpace(rotationHandler.DefaultOrigin.Value)) - : selectionBox.DrawSize / 2; + Vector2 center = selectionBox.ToLocalSpace(rotationHandler!.ToScreenSpace(rotationHandler!.DefaultOrigin!.Value)); float startAngle = MathF.Atan2(e.LastMousePosition.Y - center.Y, e.LastMousePosition.X - center.X); float endAngle = MathF.Atan2(e.MousePosition.Y - center.Y, e.MousePosition.X - center.X); From 0d06b122c1630e277864118b9cde787747902a21 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 23 Sep 2024 11:39:42 +0200 Subject: [PATCH 255/924] rename region --- osu.Game/Utils/GeometryUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index 7e6db10a28..c933006cc5 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -220,7 +220,7 @@ namespace osu.Game.Utils return new[] { h.Position }; }); - #region welzl_helpers + #region Welzl helpers // Function to check whether a point lies inside or on the boundaries of the circle private static bool isInside((Vector2, float) c, Vector2 p) From 447d178e0104bc0fb03199a7c5af20918ea69cf2 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 23 Sep 2024 11:42:02 +0200 Subject: [PATCH 256/924] use named tuple members --- osu.Game/Utils/GeometryUtils.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index c933006cc5..51777f8ea0 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -223,9 +223,9 @@ namespace osu.Game.Utils #region Welzl helpers // Function to check whether a point lies inside or on the boundaries of the circle - private static bool isInside((Vector2, float) c, Vector2 p) + private static bool isInside((Vector2 Centre, float Radius) c, Vector2 p) { - return Precision.AlmostBigger(c.Item2, Vector2.Distance(c.Item1, p)); + return Precision.AlmostBigger(c.Radius, Vector2.Distance(c.Centre, p)); } // Function to return a unique circle that intersects three points @@ -336,7 +336,7 @@ namespace osu.Game.Utils /// /// Function to find the minimum enclosing circle for a collection of points. /// - /// A tuple containing the circle center and radius. + /// A tuple containing the circle centre and radius. public static (Vector2, float) MinimumEnclosingCircle(IEnumerable points) { // Using Welzl's algorithm to find the minimum enclosing circle @@ -348,7 +348,7 @@ namespace osu.Game.Utils /// /// Function to find the minimum enclosing circle for a collection of hit objects. /// - /// A tuple containing the circle center and radius. + /// A tuple containing the circle centre and radius. public static (Vector2, float) MinimumEnclosingCircle(IEnumerable hitObjects) => MinimumEnclosingCircle(enumerateStartAndEndPositions(hitObjects)); } From d0f12006a4e8755179fa9cd0faf979dab93ae526 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 23 Sep 2024 11:42:28 +0200 Subject: [PATCH 257/924] update wikipedia url --- osu.Game/Utils/GeometryUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index 51777f8ea0..8395c3a090 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -234,7 +234,7 @@ namespace osu.Game.Utils if (Precision.AlmostEquals(0, (b.Y - a.Y) * (c.X - a.X) - (b.X - a.X) * (c.Y - a.Y))) return circleFrom(a, b); - // See: https://en.wikipedia.org/wiki/Circumscribed_circle#Cartesian_coordinates_2 + // See: https://en.wikipedia.org/wiki/Circumcircle#Cartesian_coordinates float d = 2 * (a.X * (b - c).Y + b.X * (c - a).Y + c.X * (a - b).Y); float aSq = a.LengthSquared; float bSq = b.LengthSquared; From 40cfaabc53cc310809d91a89baebd0e279894bc0 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 23 Sep 2024 11:43:36 +0200 Subject: [PATCH 258/924] verify n<=3 in minCircleTrivial --- osu.Game/Utils/GeometryUtils.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index 8395c3a090..93991efa22 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -269,6 +269,9 @@ namespace osu.Game.Utils // Function to return the minimum enclosing circle for N <= 3 private static (Vector2, float) minCircleTrivial(ReadOnlySpan points) { + if (points.Length > 3) + throw new ArgumentException("Number of points must be at most 3", nameof(points)); + switch (points.Length) { case 0: From 42549e81aa9c750a6603c2e403ff403040a90c93 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 23 Sep 2024 11:44:07 +0200 Subject: [PATCH 259/924] use RNG.Next --- osu.Game/Utils/GeometryUtils.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index 93991efa22..d4968749bf 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -303,14 +303,14 @@ namespace osu.Game.Utils // Takes a set of input points P and a set R // points on the circle boundary. // n represents the number of points in P that are not yet processed. - private static (Vector2, float) welzlHelper(List points, ReadOnlySpan r, int n, Random random) + private static (Vector2, float) welzlHelper(List points, ReadOnlySpan r, int n) { // Base case when all points processed or |R| = 3 if (n == 0 || r.Length == 3) return minCircleTrivial(r); // Pick a random point randomly - int idx = random.Next(n); + int idx = RNG.Next(n); Vector2 p = points[idx]; // Put the picked point at the end of P since it's more efficient than @@ -318,7 +318,7 @@ namespace osu.Game.Utils (points[idx], points[n - 1]) = (points[n - 1], points[idx]); // Get the MEC circle d from the set of points P - {p} - var d = welzlHelper(points, r, n - 1, random); + var d = welzlHelper(points, r, n - 1); // If d contains p, return d if (isInside(d, p)) @@ -331,7 +331,7 @@ namespace osu.Game.Utils r2[r.Length] = p; // Return the MEC for P - {p} and R U {p} - return welzlHelper(points, r2, n - 1, random); + return welzlHelper(points, r2, n - 1); } #endregion @@ -345,7 +345,7 @@ namespace osu.Game.Utils // Using Welzl's algorithm to find the minimum enclosing circle // https://www.geeksforgeeks.org/minimum-enclosing-circle-using-welzls-algorithm/ List pCopy = points.ToList(); - return welzlHelper(pCopy, Array.Empty(), pCopy.Count, new Random()); + return welzlHelper(pCopy, Array.Empty(), pCopy.Count); } /// From 4b349ba38738fbf862acccaeb43e6954782f6ba2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 23 Sep 2024 11:53:32 +0200 Subject: [PATCH 260/924] Use cache for beatmap lookups on spectate screen @peppy noticed recently that attempting to spectate just a few users was very likely to end up in requests very quickly being rejected with code 429 ("Too Many Requests"). I'm somewhat certain that the reason for that is that a significant number of players is wont to retry a lot in quick succession. That means that spectator server is going to note a lot of gameplay start and end messages in quick succession, too. And as it turns out, every gameplay start would end up triggering a new beatmap set fetch request: https://github.com/ppy/osu/blob/ccf1acce56798497edfaf92d3ece933469edcf0a/osu.Game/Screens/Spectate/SpectatorScreen.cs#L131-L134 https://github.com/ppy/osu/blob/ccf1acce56798497edfaf92d3ece933469edcf0a/osu.Game/Screens/Play/SoloSpectatorScreen.cs#L168-L172 https://github.com/ppy/osu/blob/ccf1acce56798497edfaf92d3ece933469edcf0a/osu.Game/Screens/Play/SoloSpectatorScreen.cs#L243-L256 To attempt to curtail that, use the beatmap cache instead, which should prevent these unnecessary requests from firing in the first place, therefore reducing the chance of the client getting throttled. This technically means that a different endpoint is used to fetch the data (`GET /beatmaps/?ids[]=` rather than `GET /beatmapsets/lookup?beatmap_id={id}`), but docs claim that both should return the same data, and it looks to work fine in practice. --- osu.Game/Screens/Play/SoloSpectatorScreen.cs | 26 +++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Play/SoloSpectatorScreen.cs b/osu.Game/Screens/Play/SoloSpectatorScreen.cs index 95eb2d4376..269bc3bb92 100644 --- a/osu.Game/Screens/Play/SoloSpectatorScreen.cs +++ b/osu.Game/Screens/Play/SoloSpectatorScreen.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -13,12 +14,11 @@ using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables.Cards; using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Localisation; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Spectator; using osu.Game.Overlays; @@ -34,7 +34,7 @@ namespace osu.Game.Screens.Play public partial class SoloSpectatorScreen : SpectatorScreen, IPreviewTrackOwner { [Resolved] - private IAPIProvider api { get; set; } = null!; + private BeatmapLookupCache beatmapLookupCache { get; set; } = null!; [Resolved] private PreviewTrackManager previewTrackManager { get; set; } = null!; @@ -60,7 +60,7 @@ namespace osu.Game.Screens.Play /// private SpectatorGameplayState? immediateSpectatorGameplayState; - private GetBeatmapSetRequest? onlineBeatmapRequest; + private ScheduledDelegate? beatmapFetchCallback; private APIBeatmapSet? beatmapSet; @@ -210,7 +210,7 @@ namespace osu.Game.Screens.Play private void clearDisplay() { watchButton.Enabled.Value = false; - onlineBeatmapRequest?.Cancel(); + beatmapFetchCallback?.Cancel(); beatmapPanelContainer.Clear(); previewTrackManager.StopAnyPlaying(this); } @@ -244,15 +244,17 @@ namespace osu.Game.Screens.Play { Debug.Assert(state.BeatmapID != null); - onlineBeatmapRequest = new GetBeatmapSetRequest(state.BeatmapID.Value, BeatmapSetLookupType.BeatmapId); - onlineBeatmapRequest.Success += beatmapSet => Schedule(() => + beatmapLookupCache.GetBeatmapAsync(state.BeatmapID.Value).ContinueWith(t => beatmapFetchCallback = Schedule(() => { - this.beatmapSet = beatmapSet; - beatmapPanelContainer.Child = new BeatmapCardNormal(this.beatmapSet, allowExpansion: false); - checkForAutomaticDownload(); - }); + var beatmap = t.GetResultSafely(); - api.Queue(onlineBeatmapRequest); + if (beatmap?.BeatmapSet == null) + return; + + beatmapSet = beatmap.BeatmapSet; + beatmapPanelContainer.Child = new BeatmapCardNormal(beatmapSet, allowExpansion: false); + checkForAutomaticDownload(); + })); } private void checkForAutomaticDownload() From 86817d0cfc9be71adbd9f6ceb7ff369e880becd4 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 23 Sep 2024 12:15:31 +0200 Subject: [PATCH 261/924] Add benchmark for minimum enclosing circle --- osu.Game.Benchmarks/BenchmarkGeometryUtils.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 osu.Game.Benchmarks/BenchmarkGeometryUtils.cs diff --git a/osu.Game.Benchmarks/BenchmarkGeometryUtils.cs b/osu.Game.Benchmarks/BenchmarkGeometryUtils.cs new file mode 100644 index 0000000000..2ab4d3369a --- /dev/null +++ b/osu.Game.Benchmarks/BenchmarkGeometryUtils.cs @@ -0,0 +1,29 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using BenchmarkDotNet.Attributes; +using osu.Framework.Utils; +using osu.Game.Utils; +using osuTK; + +namespace osu.Game.Benchmarks +{ + public class BenchmarkGeometryUtils : BenchmarkTest + { + [Params(100, 1000, 2000, 4000, 8000, 10000)] + public int N; + + private Vector2[] points = null!; + + public override void SetUp() + { + points = new Vector2[N]; + + for (int i = 0; i < points.Length; ++i) + points[i] = new Vector2(RNG.Next(512), RNG.Next(384)); + } + + [Benchmark] + public void MinimumEnclosingCircle() => GeometryUtils.MinimumEnclosingCircle(points); + } +} From 203951780ed50a9ab3338548c9dd9bf32131f14e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 23 Sep 2024 12:15:42 +0200 Subject: [PATCH 262/924] use collection expression instead of stackalloc --- osu.Game/Utils/GeometryUtils.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index d4968749bf..e365a00862 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -325,13 +325,8 @@ namespace osu.Game.Utils return d; // Otherwise, must be on the boundary of the MEC - // Stackalloc to avoid allocations. It's safe to assume that the length of r will be at most 3 - Span r2 = stackalloc Vector2[r.Length + 1]; - r.CopyTo(r2); - r2[r.Length] = p; - // Return the MEC for P - {p} and R U {p} - return welzlHelper(points, r2, n - 1); + return welzlHelper(points, [..r, p], n - 1); } #endregion From eead6b9eaea9aad7c5ed1b9afe6ef067de0afd3b Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 23 Sep 2024 13:13:33 +0200 Subject: [PATCH 263/924] return to stackalloc because its faster --- osu.Game/Utils/GeometryUtils.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index e365a00862..d4968749bf 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -325,8 +325,13 @@ namespace osu.Game.Utils return d; // Otherwise, must be on the boundary of the MEC + // Stackalloc to avoid allocations. It's safe to assume that the length of r will be at most 3 + Span r2 = stackalloc Vector2[r.Length + 1]; + r.CopyTo(r2); + r2[r.Length] = p; + // Return the MEC for P - {p} and R U {p} - return welzlHelper(points, [..r, p], n - 1); + return welzlHelper(points, r2, n - 1); } #endregion From bf245aa9d61d2fc1f3cffede114f0ecd2a34a7e6 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 23 Sep 2024 13:16:45 +0200 Subject: [PATCH 264/924] add a max depth to prevent stack overflow --- osu.Game/Utils/GeometryUtils.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index d4968749bf..c4c63903bb 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -305,8 +305,11 @@ namespace osu.Game.Utils // n represents the number of points in P that are not yet processed. private static (Vector2, float) welzlHelper(List points, ReadOnlySpan r, int n) { + const int max_depth = 4000; + // Base case when all points processed or |R| = 3 - if (n == 0 || r.Length == 3) + // To prevent stack overflow, we stop at a certain depth and give an approximate answer + if (n == 0 || r.Length == 3 || points.Count - n >= max_depth) return minCircleTrivial(r); // Pick a random point randomly From 41826d0606d37e8d1b46bcad8e774d0b69af9521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 23 Sep 2024 13:17:46 +0200 Subject: [PATCH 265/924] Add failing test case to demonstrate failure --- .../VolumeAwareHitSampleInfoTest.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/VolumeAwareHitSampleInfoTest.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/VolumeAwareHitSampleInfoTest.cs b/osu.Game.Rulesets.Taiko.Tests/VolumeAwareHitSampleInfoTest.cs new file mode 100644 index 0000000000..2b3a922067 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/VolumeAwareHitSampleInfoTest.cs @@ -0,0 +1,27 @@ +// 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.Audio; +using osu.Game.Rulesets.Taiko.Skinning.Argon; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + [TestFixture] + public class VolumeAwareHitSampleInfoTest + { + [Test] + public void TestVolumeAwareHitSampleInfoIsNotEqualToItsUnderlyingSample( + [Values(HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP)] + string sample, + [Values(HitSampleInfo.BANK_NORMAL, HitSampleInfo.BANK_SOFT)] + string bank, + [Values(30, 70, 100)] int volume) + { + var underlyingSample = new HitSampleInfo(sample, bank, volume: volume); + var volumeAwareSample = new VolumeAwareHitSampleInfo(underlyingSample); + + Assert.That(underlyingSample, Is.Not.EqualTo(volumeAwareSample)); + } + } +} From 08bded82fdfd1cf63d2a72b51cedb5be0de01c60 Mon Sep 17 00:00:00 2001 From: StanR Date: Mon, 23 Sep 2024 16:30:02 +0500 Subject: [PATCH 266/924] Remove GetHashCode --- .../Difficulty/Evaluators/RhythmEvaluator.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index 9c7191dec5..abf9dbe725 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -198,11 +198,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators Math.Abs(Delta - other.Delta) < deltaDifferenceEpsilon; } - public override int GetHashCode() - { - return HashCode.Combine(Delta, DeltaCount); - } - public bool Equals(Island? other) { if (other == null) From e8a394f89485e61b37c61f095c7c9ed1c5c3b121 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 23 Sep 2024 13:27:36 +0200 Subject: [PATCH 267/924] Fix argon volume-aware hitsounds not correctly playing immediately after object placement Closes https://github.com/ppy/osu/issues/29832. The underlying reason for the incorrect sample playback was an equality comparer failure. Samples are contained in several pools which are managed by the playfield. In particular, the pools are keyed by `ISampleInfo` instances. This means that for correct operation, `ISampleInfo` has to implement `IEquatable` and also provide an appropriately correct `GetHashCode()` implementation. Different audible samples must not compare equal to each other when represented by `ISampleInfo`. As it turns out, `VolumeAwareHitSampleInfo` failed on this, due to not overriding equality members. Therefore, a `new HitSampleInfo(HitSampleInfo.HIT_NORMAL, HitSampleInfo.BANK_NORMAL, volume: 70)` was allowed to compare equal to a `VolumeAwareHitSampleInfo` wrapping it, *even though they correspond to completely different sounds and go through entirely different lookup path sequences*. Therefore, to fix, provide more proper equality implementations for `VolumeAwareHitSampleInfo`. When testing note that this issue *only occurs immediately after placing an object*. Saving and re-entering editor makes this issue go away. I haven't looked too long into why, but the general gist of it is ordering; it appears that a `normal-hitnormal` pool exists at point of query of a new object placement, but does not seem to exist when entering editor afresh. That said I'm not sure that ordering aspect of this bug matters much if at all, since the two `IHitSampleInfo`s should never be allowed to alias with each other at all wrt equality. --- .../Argon/VolumeAwareHitSampleInfo.cs | 20 +++++++++++++++++++ osu.Game/Audio/HitSampleInfo.cs | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Argon/VolumeAwareHitSampleInfo.cs b/osu.Game.Rulesets.Taiko/Skinning/Argon/VolumeAwareHitSampleInfo.cs index 3ca4b5a3c7..288ffde052 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Argon/VolumeAwareHitSampleInfo.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Argon/VolumeAwareHitSampleInfo.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using osu.Game.Audio; @@ -48,5 +49,24 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon return originalBank; } } + + public override bool Equals(HitSampleInfo? other) => other is VolumeAwareHitSampleInfo && base.Equals(other); + + /// + /// + /// This override attempts to match the override above, but in theory it is not strictly necessary. + /// Recall that must meet the following requirements: + /// + /// + /// "If two objects compare as equal, the method for each object must return the same value. + /// However, if two objects do not compare as equal, methods for the two objects do not have to return different values." + /// + /// + /// Making this override combine the value generated by the base implementation with a constant means + /// that and instances which have the same values of their members + /// will not have equal hash codes, which is slightly more efficient when these objects are used as dictionary keys. + /// + /// + public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), 1); } } diff --git a/osu.Game/Audio/HitSampleInfo.cs b/osu.Game/Audio/HitSampleInfo.cs index f9c93d72ff..ce5e217532 100644 --- a/osu.Game/Audio/HitSampleInfo.cs +++ b/osu.Game/Audio/HitSampleInfo.cs @@ -96,7 +96,7 @@ namespace osu.Game.Audio public virtual HitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newSuffix = default, Optional newVolume = default) => new HitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newSuffix.GetOr(Suffix), newVolume.GetOr(Volume)); - public bool Equals(HitSampleInfo? other) + public virtual bool Equals(HitSampleInfo? other) => other != null && Name == other.Name && Bank == other.Bank && Suffix == other.Suffix; public override bool Equals(object? obj) From d6c17f6ac0b08b5a5a4ba541b82fc300301d5918 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 23 Sep 2024 14:29:56 +0200 Subject: [PATCH 268/924] Implement "form" dropdown control --- .../UserInterface/TestSceneFormControls.cs | 6 + .../Graphics/UserInterfaceV2/FormDropdown.cs | 251 ++++++++++++++++++ 2 files changed, 257 insertions(+) create mode 100644 osu.Game/Graphics/UserInterfaceV2/FormDropdown.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs index b456da0f26..89b4ae9f97 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs @@ -5,6 +5,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Game.Beatmaps; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Localisation; @@ -94,6 +95,11 @@ namespace osu.Game.Tests.Visual.UserInterface Instantaneous = false, TabbableContentContainer = this, }, + new FormEnumDropdown + { + Caption = EditorSetupStrings.EnableCountdown, + HintText = EditorSetupStrings.CountdownDescription, + }, }, }, } diff --git a/osu.Game/Graphics/UserInterfaceV2/FormDropdown.cs b/osu.Game/Graphics/UserInterfaceV2/FormDropdown.cs new file mode 100644 index 0000000000..d47b9ac73d --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/FormDropdown.cs @@ -0,0 +1,251 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Framework.Localisation; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public partial class FormDropdown : OsuDropdown + { + /// + /// Caption describing this slider bar, displayed on top of the controls. + /// + public LocalisableString Caption { get; init; } + + /// + /// Hint text containing an extended description of this slider bar, displayed in a tooltip when hovering the caption. + /// + public LocalisableString HintText { get; init; } + + private FormDropdownHeader header = null!; + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + + header.Caption = Caption; + header.HintText = HintText; + } + + protected override DropdownHeader CreateHeader() => header = new FormDropdownHeader + { + Dropdown = this, + }; + + protected override DropdownMenu CreateMenu() => new FormDropdownMenu(); + + private partial class FormDropdownHeader : DropdownHeader + { + public FormDropdown Dropdown { get; set; } = null!; + + protected override DropdownSearchBar CreateSearchBar() => SearchBar = new FormDropdownSearchBar(); + + private LocalisableString captionText; + private LocalisableString hintText; + private LocalisableString labelText; + + public LocalisableString Caption + { + get => captionText; + set + { + captionText = value; + + if (caption.IsNotNull()) + caption.Caption = value; + } + } + + public LocalisableString HintText + { + get => hintText; + set + { + hintText = value; + + if (caption.IsNotNull()) + caption.TooltipText = value; + } + } + + protected override LocalisableString Label + { + get => labelText; + set + { + labelText = value; + + if (label.IsNotNull()) + label.Text = labelText; + } + } + + protected new FormDropdownSearchBar SearchBar { get; set; } = null!; + + private FormFieldCaption caption = null!; + private OsuSpriteText label = null!; + private SpriteIcon chevron = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.None; + Height = 50; + + Masking = true; + CornerRadius = 5; + + Foreground.AutoSizeAxes = Axes.None; + Foreground.RelativeSizeAxes = Axes.Both; + Foreground.Padding = new MarginPadding(9); + Foreground.Children = new Drawable[] + { + caption = new FormFieldCaption + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + Caption = Caption, + TooltipText = HintText, + }, + label = new OsuSpriteText + { + RelativeSizeAxes = Axes.X, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + }, + chevron = new SpriteIcon + { + Icon = FontAwesome.Solid.ChevronDown, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Size = new Vector2(16), + }, + }; + + AddInternal(new HoverClickSounds()); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Dropdown.Current.BindDisabledChanged(_ => updateState()); + SearchBar.SearchTerm.BindValueChanged(_ => updateState(), true); + Dropdown.Menu.StateChanged += _ => + { + updateState(); + updateChevron(); + }; + SearchBar.TextBox.OnCommit += (_, _) => + { + Background.FlashColour(ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark2), 800, Easing.OutQuint); + }; + } + + protected override bool OnHover(HoverEvent e) + { + updateState(); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + updateState(); + } + + private void updateState() + { + label.Alpha = string.IsNullOrEmpty(SearchBar.SearchTerm.Value) ? 1 : 0; + + caption.Colour = Dropdown.Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content2; + label.Colour = Dropdown.Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content1; + chevron.Colour = Dropdown.Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content1; + DisabledColour = Colour4.White; + + bool dropdownOpen = Dropdown.Menu.State == MenuState.Open; + + if (!Dropdown.Current.Disabled) + { + BorderThickness = IsHovered || dropdownOpen ? 2 : 0; + BorderColour = dropdownOpen ? colourProvider.Highlight1 : colourProvider.Light4; + + if (dropdownOpen) + Background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark3); + else if (IsHovered) + Background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark4); + else + Background.Colour = colourProvider.Background5; + } + else + { + Background.Colour = colourProvider.Background4; + } + } + + private void updateChevron() + { + bool open = Dropdown.Menu.State == MenuState.Open; + chevron.ScaleTo(open ? new Vector2(1f, -1f) : Vector2.One, 300, Easing.OutQuint); + } + } + + private partial class FormDropdownSearchBar : DropdownSearchBar + { + public FormTextBox.InnerTextBox TextBox { get; private set; } = null!; + + protected override void PopIn() => this.FadeIn(); + protected override void PopOut() => this.FadeOut(); + + protected override TextBox CreateTextBox() => TextBox = new FormTextBox.InnerTextBox(); + + [BackgroundDependencyLoader] + private void load() + { + TextBox.Anchor = Anchor.BottomLeft; + TextBox.Origin = Anchor.BottomLeft; + TextBox.RelativeSizeAxes = Axes.X; + TextBox.Margin = new MarginPadding(9); + } + } + + private partial class FormDropdownMenu : OsuDropdownMenu + { + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + ItemsContainer.Padding = new MarginPadding(9); + Margin = new MarginPadding { Top = 5 }; + + MaskingContainer.BorderThickness = 2; + MaskingContainer.BorderColour = colourProvider.Highlight1; + } + } + } + + public partial class FormEnumDropdown : FormDropdown + where T : struct, Enum + { + public FormEnumDropdown() + { + Items = Enum.GetValues(); + } + } +} From 1a81e12192b6b6d2c1c8d1b9256cb2242e8d66e3 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 23 Sep 2024 16:33:36 +0200 Subject: [PATCH 269/924] Refactor PlacementBlueprint to not be hitobject specific --- .../CatchPlacementBlueprintTestScene.cs | 2 +- ...TestSceneBananaShowerPlacementBlueprint.cs | 4 +- .../TestSceneFruitPlacementBlueprint.cs | 2 +- .../TestSceneJuiceStreamPlacementBlueprint.cs | 2 +- .../Edit/BananaShowerCompositionTool.cs | 4 +- .../Blueprints/CatchPlacementBlueprint.cs | 2 +- .../Edit/CatchHitObjectComposer.cs | 4 +- .../Edit/FruitCompositionTool.cs | 4 +- .../Edit/JuiceStreamCompositionTool.cs | 4 +- .../ManiaPlacementBlueprintTestScene.cs | 2 +- .../TestSceneHoldNotePlacementBlueprint.cs | 2 +- .../Editor/TestSceneNotePlacementBlueprint.cs | 2 +- .../Blueprints/ManiaPlacementBlueprint.cs | 2 +- .../Edit/HoldNoteCompositionTool.cs | 4 +- .../Edit/ManiaHitObjectComposer.cs | 2 +- .../Edit/NoteCompositionTool.cs | 4 +- .../TestSceneHitCirclePlacementBlueprint.cs | 2 +- .../TestSceneSliderPlacementBlueprint.cs | 2 +- .../TestSceneSpinnerPlacementBlueprint.cs | 2 +- .../HitCircles/HitCirclePlacementBlueprint.cs | 2 +- .../Sliders/SliderPlacementBlueprint.cs | 2 +- .../Spinners/SpinnerPlacementBlueprint.cs | 2 +- .../Edit/HitCircleCompositionTool.cs | 4 +- .../Edit/OsuHitObjectComposer.cs | 2 +- .../Edit/SliderCompositionTool.cs | 4 +- .../Edit/SpinnerCompositionTool.cs | 4 +- .../Edit/Blueprints/HitPlacementBlueprint.cs | 2 +- .../Blueprints/TaikoSpanPlacementBlueprint.cs | 2 +- .../Edit/DrumRollCompositionTool.cs | 4 +- .../Edit/HitCompositionTool.cs | 4 +- .../Edit/SwellCompositionTool.cs | 4 +- .../Edit/TaikoHitObjectComposer.cs | 2 +- .../Editing/TestScenePlacementBlueprint.cs | 2 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 4 +- .../Edit/HitObjectCompositionToolButton.cs | 4 +- .../Edit/HitObjectPlacementBlueprint.cs | 126 ++++++++++++++++++ osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 122 +++-------------- ...tCompositionTool.cs => CompositionTool.cs} | 4 +- osu.Game/Rulesets/Edit/Tools/SelectTool.cs | 4 +- .../Components/ComposeBlueprintContainer.cs | 22 +-- .../Visual/PlacementBlueprintTestScene.cs | 8 +- 41 files changed, 212 insertions(+), 174 deletions(-) create mode 100644 osu.Game/Rulesets/Edit/HitObjectPlacementBlueprint.cs rename osu.Game/Rulesets/Edit/Tools/{HitObjectCompositionTool.cs => CompositionTool.cs} (84%) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs index aae759d934..0578010c25 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor contentContainer.Playfield.HitObjectContainer.Add(hitObject); } - protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint) + protected override SnapResult SnapForBlueprint(HitObjectPlacementBlueprint blueprint) { var result = base.SnapForBlueprint(blueprint); result.Time = Math.Round(HitObjectContainer.TimeAtScreenSpacePosition(result.ScreenSpacePosition) / TIME_SNAP) * TIME_SNAP; diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs index ed37ff4ef3..badd8e967d 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor { protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableBananaShower((BananaShower)hitObject); - protected override PlacementBlueprint CreateBlueprint() => new BananaShowerPlacementBlueprint(); + protected override HitObjectPlacementBlueprint CreateBlueprint() => new BananaShowerPlacementBlueprint(); protected override void AddHitObject(DrawableHitObject hitObject) { @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor AddClickStep(MouseButton.Left); AddClickStep(MouseButton.Right); AddAssert("banana shower is not placed", () => LastObject == null); - AddAssert("state is waiting", () => CurrentBlueprint?.PlacementActive == PlacementBlueprint.PlacementState.Waiting); + AddAssert("state is waiting", () => CurrentBlueprint?.PlacementActive == HitObjectPlacementBlueprint.PlacementState.Waiting); } [Test] diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs index 75d3c3753a..80cd948e26 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor { protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableFruit((Fruit)hitObject); - protected override PlacementBlueprint CreateBlueprint() => new FruitPlacementBlueprint(); + protected override HitObjectPlacementBlueprint CreateBlueprint() => new FruitPlacementBlueprint(); [Test] public void TestFruitPlacementPosition() diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs index d010bb02ad..8bd60078c6 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableJuiceStream((JuiceStream)hitObject); - protected override PlacementBlueprint CreateBlueprint() => new JuiceStreamPlacementBlueprint(); + protected override HitObjectPlacementBlueprint CreateBlueprint() => new JuiceStreamPlacementBlueprint(); private void addMoveAndClickSteps(double time, float position, bool end = false) { diff --git a/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs b/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs index 31075db7d1..be93ba0242 100644 --- a/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs +++ b/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Edit.Tools; namespace osu.Game.Rulesets.Catch.Edit { - public class BananaShowerCompositionTool : HitObjectCompositionTool + public class BananaShowerCompositionTool : CompositionTool { public BananaShowerCompositionTool() : base(nameof(BananaShower)) @@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Catch.Edit public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners); - public override PlacementBlueprint CreatePlacementBlueprint() => new BananaShowerPlacementBlueprint(); + public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new BananaShowerPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs index 1a2990e4ac..aa862375c5 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Catch.Edit.Blueprints { - public partial class CatchPlacementBlueprint : PlacementBlueprint + public partial class CatchPlacementBlueprint : HitObjectPlacementBlueprint where THitObject : CatchHitObject, new() { protected new THitObject HitObject => (THitObject)base.HitObject; diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index 83f48816f9..8460e238f6 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Catch.Edit protected override BeatSnapGrid CreateBeatSnapGrid() => new CatchBeatSnapGrid(); - protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] + protected override IReadOnlyList CompositionTools => new CompositionTool[] { new FruitCompositionTool(), new JuiceStreamCompositionTool(), @@ -168,7 +168,7 @@ namespace osu.Game.Rulesets.Catch.Edit if (EditorBeatmap.PlacementObject.Value is JuiceStream) { // Juice stream path is not subject to snapping. - if (BlueprintContainer.CurrentPlacement.PlacementActive is PlacementBlueprint.PlacementState.Active) + if (BlueprintContainer.CurrentPlacement.PlacementActive is HitObjectPlacementBlueprint.PlacementState.Active) return null; } diff --git a/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs b/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs index f776fe39c1..71c1e66903 100644 --- a/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs +++ b/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Edit.Tools; namespace osu.Game.Rulesets.Catch.Edit { - public class FruitCompositionTool : HitObjectCompositionTool + public class FruitCompositionTool : CompositionTool { public FruitCompositionTool() : base(nameof(Fruit)) @@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Catch.Edit public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles); - public override PlacementBlueprint CreatePlacementBlueprint() => new FruitPlacementBlueprint(); + public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new FruitPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Catch/Edit/JuiceStreamCompositionTool.cs b/osu.Game.Rulesets.Catch/Edit/JuiceStreamCompositionTool.cs index cb66e2952e..7a567820f3 100644 --- a/osu.Game.Rulesets.Catch/Edit/JuiceStreamCompositionTool.cs +++ b/osu.Game.Rulesets.Catch/Edit/JuiceStreamCompositionTool.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Edit.Tools; namespace osu.Game.Rulesets.Catch.Edit { - public class JuiceStreamCompositionTool : HitObjectCompositionTool + public class JuiceStreamCompositionTool : CompositionTool { public JuiceStreamCompositionTool() : base(nameof(JuiceStream)) @@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Catch.Edit public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders); - public override PlacementBlueprint CreatePlacementBlueprint() => new JuiceStreamPlacementBlueprint(); + public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new JuiceStreamPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs index d2a44122aa..5e633c3161 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor }); } - protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint) + protected override SnapResult SnapForBlueprint(HitObjectPlacementBlueprint blueprint) { double time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position); var pos = column.ScreenSpacePositionAtTime(time); diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs index b79bcb7682..2006879ab3 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs @@ -13,6 +13,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor public partial class TestSceneHoldNotePlacementBlueprint : ManiaPlacementBlueprintTestScene { protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHoldNote((HoldNote)hitObject); - protected override PlacementBlueprint CreateBlueprint() => new HoldNotePlacementBlueprint(); + protected override HitObjectPlacementBlueprint CreateBlueprint() => new HoldNotePlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs index a446f13cbf..0cb9639cd1 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs @@ -64,6 +64,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor private Note getNote() => this.ChildrenOfType().FirstOrDefault()?.HitObject; protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableNote((Note)hitObject); - protected override PlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint(); + protected override HitObjectPlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index 5e0512b5dc..a68bd5d6d6 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -15,7 +15,7 @@ using osuTK.Input; namespace osu.Game.Rulesets.Mania.Edit.Blueprints { - public abstract partial class ManiaPlacementBlueprint : PlacementBlueprint + public abstract partial class ManiaPlacementBlueprint : HitObjectPlacementBlueprint where T : ManiaHitObject { protected new T HitObject => (T)base.HitObject; diff --git a/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs b/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs index 99e1ce04b1..592f8d9af7 100644 --- a/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs +++ b/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets.Mania.Edit.Blueprints; namespace osu.Game.Rulesets.Mania.Edit { - public class HoldNoteCompositionTool : HitObjectCompositionTool + public class HoldNoteCompositionTool : CompositionTool { public HoldNoteCompositionTool() : base("Hold") @@ -18,6 +18,6 @@ namespace osu.Game.Rulesets.Mania.Edit public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders); - public override PlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint(); + public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 02a4f3a022..e3b4fa2fb7 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Mania.Edit protected override BeatSnapGrid CreateBeatSnapGrid() => new ManiaBeatSnapGrid(); - protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] + protected override IReadOnlyList CompositionTools => new CompositionTool[] { new NoteCompositionTool(), new HoldNoteCompositionTool() diff --git a/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs b/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs index 08ee05ad3f..2e54d63525 100644 --- a/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs +++ b/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Mania.Objects; namespace osu.Game.Rulesets.Mania.Edit { - public class NoteCompositionTool : HitObjectCompositionTool + public class NoteCompositionTool : CompositionTool { public NoteCompositionTool() : base(nameof(Note)) @@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Mania.Edit public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles); - public override PlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint(); + public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCirclePlacementBlueprint.cs index a49afd82f3..a105d860bf 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCirclePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCirclePlacementBlueprint.cs @@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor public partial class TestSceneHitCirclePlacementBlueprint : PlacementBlueprintTestScene { protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHitCircle((HitCircle)hitObject); - protected override PlacementBlueprint CreateBlueprint() => new HitCirclePlacementBlueprint(); + protected override HitObjectPlacementBlueprint CreateBlueprint() => new HitCirclePlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index aa6a6f08d8..019565ae29 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -514,6 +514,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private Slider? getSlider() => HitObjectContainer.Count > 0 ? ((DrawableSlider)HitObjectContainer[0]).HitObject : null; protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSlider((Slider)hitObject); - protected override PlacementBlueprint CreateBlueprint() => new SliderPlacementBlueprint(); + protected override HitObjectPlacementBlueprint CreateBlueprint() => new SliderPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerPlacementBlueprint.cs index 0e8673319e..d7b5cc73be 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerPlacementBlueprint.cs @@ -15,6 +15,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSpinner((Spinner)hitObject); - protected override PlacementBlueprint CreateBlueprint() => new SpinnerPlacementBlueprint(); + protected override HitObjectPlacementBlueprint CreateBlueprint() => new SpinnerPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs index 20ad99baa2..78a0e36dc2 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs @@ -9,7 +9,7 @@ using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles { - public partial class HitCirclePlacementBlueprint : PlacementBlueprint + public partial class HitCirclePlacementBlueprint : HitObjectPlacementBlueprint { public new HitCircle HitObject => (HitCircle)base.HitObject; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 42945295b8..6ffe27dc13 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -21,7 +21,7 @@ using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { - public partial class SliderPlacementBlueprint : PlacementBlueprint + public partial class SliderPlacementBlueprint : HitObjectPlacementBlueprint { public new Slider HitObject => (Slider)base.HitObject; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs index f59be0e0e9..17d2dcd75c 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs @@ -13,7 +13,7 @@ using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners { - public partial class SpinnerPlacementBlueprint : PlacementBlueprint + public partial class SpinnerPlacementBlueprint : HitObjectPlacementBlueprint { public new Spinner HitObject => (Spinner)base.HitObject; diff --git a/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs b/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs index c41ae10b2e..d3116ede30 100644 --- a/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs +++ b/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Edit { - public class HitCircleCompositionTool : HitObjectCompositionTool + public class HitCircleCompositionTool : CompositionTool { public HitCircleCompositionTool() : base(nameof(HitCircle)) @@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Osu.Edit public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles); - public override PlacementBlueprint CreatePlacementBlueprint() => new HitCirclePlacementBlueprint(); + public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new HitCirclePlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 68e57060ad..e6a9a95b6b 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Edit protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) => new DrawableOsuEditorRuleset(ruleset, beatmap, mods); - protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] + protected override IReadOnlyList CompositionTools => new CompositionTool[] { new HitCircleCompositionTool(), new SliderCompositionTool(), diff --git a/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs b/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs index 617cc1c19b..d697a2ebe6 100644 --- a/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs +++ b/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Edit { - public class SliderCompositionTool : HitObjectCompositionTool + public class SliderCompositionTool : CompositionTool { public SliderCompositionTool() : base(nameof(Slider)) @@ -26,6 +26,6 @@ namespace osu.Game.Rulesets.Osu.Edit public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders); - public override PlacementBlueprint CreatePlacementBlueprint() => new SliderPlacementBlueprint(); + public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new SliderPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs b/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs index c8160617c9..de1506e4a9 100644 --- a/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs +++ b/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Edit { - public class SpinnerCompositionTool : HitObjectCompositionTool + public class SpinnerCompositionTool : CompositionTool { public SpinnerCompositionTool() : base(nameof(Spinner)) @@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Osu.Edit public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners); - public override PlacementBlueprint CreatePlacementBlueprint() => new SpinnerPlacementBlueprint(); + public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new SpinnerPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs index 329fff5b42..7f45123bd6 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs @@ -10,7 +10,7 @@ using osuTK.Input; namespace osu.Game.Rulesets.Taiko.Edit.Blueprints { - public partial class HitPlacementBlueprint : PlacementBlueprint + public partial class HitPlacementBlueprint : HitObjectPlacementBlueprint { private readonly HitPiece piece; diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs index cd52398086..de3a4d96eb 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs @@ -17,7 +17,7 @@ using osuTK.Input; namespace osu.Game.Rulesets.Taiko.Edit.Blueprints { - public partial class TaikoSpanPlacementBlueprint : PlacementBlueprint + public partial class TaikoSpanPlacementBlueprint : HitObjectPlacementBlueprint { private readonly HitPiece headPiece; private readonly HitPiece tailPiece; diff --git a/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs index f332441875..ba0fda6771 100644 --- a/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs +++ b/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Edit { - public class DrumRollCompositionTool : HitObjectCompositionTool + public class DrumRollCompositionTool : CompositionTool { public DrumRollCompositionTool() : base(nameof(DrumRoll)) @@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Taiko.Edit public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders); - public override PlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint(); + public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs index fa50841893..f58defba83 100644 --- a/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs +++ b/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Edit { - public class HitCompositionTool : HitObjectCompositionTool + public class HitCompositionTool : CompositionTool { public HitCompositionTool() : base(nameof(Hit)) @@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Taiko.Edit public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles); - public override PlacementBlueprint CreatePlacementBlueprint() => new HitPlacementBlueprint(); + public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new HitPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs index 4d4ee8effe..4ec623e29e 100644 --- a/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs +++ b/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Edit { - public class SwellCompositionTool : HitObjectCompositionTool + public class SwellCompositionTool : CompositionTool { public SwellCompositionTool() : base(nameof(Swell)) @@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Taiko.Edit public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners); - public override PlacementBlueprint CreatePlacementBlueprint() => new SwellPlacementBlueprint(); + public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new SwellPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs index 6020f6e04c..d97a854ff7 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Taiko.Edit { } - protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] + protected override IReadOnlyList CompositionTools => new CompositionTool[] { new HitCompositionTool(), new DrumRollCompositionTool(), diff --git a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs index fe74e1b346..6f3342f8ce 100644 --- a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs @@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("editor is still current", () => Editor.IsCurrentScreen()); AddAssert("slider not placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(0)); AddAssert("no active placement", () => this.ChildrenOfType().Single().CurrentPlacement.PlacementActive, - () => Is.EqualTo(PlacementBlueprint.PlacementState.Waiting)); + () => Is.EqualTo(HitObjectPlacementBlueprint.PlacementState.Waiting)); } [Test] diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 87c35eafb0..b83c8acec6 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -326,7 +326,7 @@ namespace osu.Game.Rulesets.Edit /// /// A "select" tool is automatically added as the first tool. /// - protected abstract IReadOnlyList CompositionTools { get; } + protected abstract IReadOnlyList CompositionTools { get; } /// /// A collection of states which will be displayed to the user in the toolbox. @@ -469,7 +469,7 @@ namespace osu.Game.Rulesets.Edit private void setSelectTool() => toolboxCollection.Items.First().Select(); - private void toolSelected(HitObjectCompositionTool tool) + private void toolSelected(CompositionTool tool) { BlueprintContainer.CurrentTool = tool; diff --git a/osu.Game/Rulesets/Edit/HitObjectCompositionToolButton.cs b/osu.Game/Rulesets/Edit/HitObjectCompositionToolButton.cs index ba566ff5c0..641d60dbd3 100644 --- a/osu.Game/Rulesets/Edit/HitObjectCompositionToolButton.cs +++ b/osu.Game/Rulesets/Edit/HitObjectCompositionToolButton.cs @@ -9,9 +9,9 @@ namespace osu.Game.Rulesets.Edit { public class HitObjectCompositionToolButton : RadioButton { - public HitObjectCompositionTool Tool { get; } + public CompositionTool Tool { get; } - public HitObjectCompositionToolButton(HitObjectCompositionTool tool, Action? action) + public HitObjectCompositionToolButton(CompositionTool tool, Action? action) : base(tool.Name, action, tool.CreateIcon) { Tool = tool; diff --git a/osu.Game/Rulesets/Edit/HitObjectPlacementBlueprint.cs b/osu.Game/Rulesets/Edit/HitObjectPlacementBlueprint.cs new file mode 100644 index 0000000000..74025b4260 --- /dev/null +++ b/osu.Game/Rulesets/Edit/HitObjectPlacementBlueprint.cs @@ -0,0 +1,126 @@ +// 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 System.Threading; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Compose; + +namespace osu.Game.Rulesets.Edit +{ + /// + /// A blueprint which governs the creation of a new to actualisation. + /// + public abstract partial class HitObjectPlacementBlueprint : PlacementBlueprint + { + /// + /// Whether the sample bank should be taken from the previous hit object. + /// + public bool AutomaticBankAssignment { get; set; } + + /// + /// The that is being placed. + /// + public readonly HitObject HitObject; + + [Resolved] + protected EditorClock EditorClock { get; private set; } = null!; + + [Resolved] + private EditorBeatmap beatmap { get; set; } = null!; + + private Bindable startTimeBindable = null!; + + private HitObject? getPreviousHitObject() => beatmap.HitObjects.TakeWhile(h => h.StartTime <= startTimeBindable.Value).LastOrDefault(); + + [Resolved] + private IPlacementHandler placementHandler { get; set; } = null!; + + protected HitObjectPlacementBlueprint(HitObject hitObject) + { + HitObject = hitObject; + + // adding the default hit sample should be the case regardless of the ruleset. + HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_NORMAL)); + } + + [BackgroundDependencyLoader] + private void load() + { + startTimeBindable = HitObject.StartTimeBindable.GetBoundCopy(); + startTimeBindable.BindValueChanged(_ => ApplyDefaultsToHitObject(), true); + } + + protected override void BeginPlacement(bool commitStart = false) + { + base.BeginPlacement(commitStart); + + placementHandler.BeginPlacement(HitObject); + } + + public override void EndPlacement(bool commit) + { + base.EndPlacement(commit); + + placementHandler.EndPlacement(HitObject, IsValidForPlacement && commit); + } + + /// + /// Updates the time and position of this based on the provided snap information. + /// + /// The snap result information. + public override void UpdateTimeAndPosition(SnapResult result) + { + if (PlacementActive == PlacementState.Waiting) + { + HitObject.StartTime = result.Time ?? EditorClock.CurrentTime; + + if (HitObject is IHasComboInformation comboInformation) + comboInformation.UpdateComboInformation(getPreviousHitObject() as IHasComboInformation); + } + + var lastHitObject = getPreviousHitObject(); + + if (AutomaticBankAssignment) + { + // Create samples based on the sample settings of the previous hit object + if (lastHitObject != null) + { + for (int i = 0; i < HitObject.Samples.Count; i++) + HitObject.Samples[i] = lastHitObject.CreateHitSampleInfo(HitObject.Samples[i].Name); + } + } + else + { + var lastHitNormal = lastHitObject?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL); + + if (lastHitNormal != null) + { + // Only inherit the volume from the previous hit object + for (int i = 0; i < HitObject.Samples.Count; i++) + HitObject.Samples[i] = HitObject.Samples[i].With(newVolume: lastHitNormal.Volume); + } + } + + if (HitObject is IHasRepeats hasRepeats) + { + // Make sure all the node samples are identical to the hit object's samples + for (int i = 0; i < hasRepeats.NodeSamples.Count; i++) + hasRepeats.NodeSamples[i] = HitObject.Samples.Select(o => o.With()).ToList(); + } + } + + /// + /// Invokes , + /// refreshing and parameters for the . + /// + protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); + } +} diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index a50a7f4169..d2a54e8e03 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -1,29 +1,19 @@ // 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 System.Threading; -using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Game.Audio; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Compose; using osuTK; using osuTK.Input; namespace osu.Game.Rulesets.Edit { /// - /// A blueprint which governs the creation of a new to actualisation. + /// A blueprint which governs the placement of something. /// public abstract partial class PlacementBlueprint : CompositeDrawable, IKeyBindingHandler { @@ -32,29 +22,6 @@ namespace osu.Game.Rulesets.Edit /// public PlacementState PlacementActive { get; private set; } - /// - /// Whether the sample bank should be taken from the previous hit object. - /// - public bool AutomaticBankAssignment { get; set; } - - /// - /// The that is being placed. - /// - public readonly HitObject HitObject; - - [Resolved] - protected EditorClock EditorClock { get; private set; } = null!; - - [Resolved] - private EditorBeatmap beatmap { get; set; } = null!; - - private Bindable startTimeBindable = null!; - - private HitObject? getPreviousHitObject() => beatmap.HitObjects.TakeWhile(h => h.StartTime <= startTimeBindable.Value).LastOrDefault(); - - [Resolved] - private IPlacementHandler placementHandler { get; set; } = null!; - /// /// Whether this blueprint is currently in a state that can be committed. /// @@ -64,13 +31,8 @@ namespace osu.Game.Rulesets.Edit /// protected virtual bool IsValidForPlacement => true; - protected PlacementBlueprint(HitObject hitObject) + protected PlacementBlueprint() { - HitObject = hitObject; - - // adding the default hit sample should be the case regardless of the ruleset. - HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_NORMAL)); - RelativeSizeAxes = Axes.Both; // This is required to allow the blueprint's position to be updated via OnMouseMove/Handle @@ -78,30 +40,22 @@ namespace osu.Game.Rulesets.Edit AlwaysPresent = true; } - [BackgroundDependencyLoader] - private void load() - { - startTimeBindable = HitObject.StartTimeBindable.GetBoundCopy(); - startTimeBindable.BindValueChanged(_ => ApplyDefaultsToHitObject(), true); - } - /// - /// Signals that the placement of has started. + /// Signals that the placement has started. /// - /// Whether this call is committing a value for HitObject.StartTime and continuing with further adjustments. - protected void BeginPlacement(bool commitStart = false) + /// Whether this call is committing a value and continuing with further adjustments. + protected virtual void BeginPlacement(bool commitStart = false) { - placementHandler.BeginPlacement(HitObject); if (commitStart) PlacementActive = PlacementState.Active; } /// /// Signals that the placement of has finished. - /// This will destroy this , and add the HitObject.StartTime to the . + /// This will destroy this , and commit the changes. /// - /// Whether the object should be committed. Note that a commit may fail if is false. - public void EndPlacement(bool commit) + /// Whether the changes should be committed. Note that a commit may fail if is false. + public virtual void EndPlacement(bool commit) { switch (PlacementActive) { @@ -114,10 +68,17 @@ namespace osu.Game.Rulesets.Edit break; } - placementHandler.EndPlacement(HitObject, IsValidForPlacement && commit); PlacementActive = PlacementState.Finished; } + /// + /// Updates the time and position of this based on the provided snap information. + /// + /// The snap result information. + public virtual void UpdateTimeAndPosition(SnapResult result) + { + } + public bool OnPressed(KeyBindingPressEvent e) { if (PlacementActive == PlacementState.Waiting) @@ -138,57 +99,6 @@ namespace osu.Game.Rulesets.Edit { } - /// - /// Updates the time and position of this based on the provided snap information. - /// - /// The snap result information. - public virtual void UpdateTimeAndPosition(SnapResult result) - { - if (PlacementActive == PlacementState.Waiting) - { - HitObject.StartTime = result.Time ?? EditorClock.CurrentTime; - - if (HitObject is IHasComboInformation comboInformation) - comboInformation.UpdateComboInformation(getPreviousHitObject() as IHasComboInformation); - } - - var lastHitObject = getPreviousHitObject(); - - if (AutomaticBankAssignment) - { - // Create samples based on the sample settings of the previous hit object - if (lastHitObject != null) - { - for (int i = 0; i < HitObject.Samples.Count; i++) - HitObject.Samples[i] = lastHitObject.CreateHitSampleInfo(HitObject.Samples[i].Name); - } - } - else - { - var lastHitNormal = lastHitObject?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL); - - if (lastHitNormal != null) - { - // Only inherit the volume from the previous hit object - for (int i = 0; i < HitObject.Samples.Count; i++) - HitObject.Samples[i] = HitObject.Samples[i].With(newVolume: lastHitNormal.Volume); - } - } - - if (HitObject is IHasRepeats hasRepeats) - { - // Make sure all the node samples are identical to the hit object's samples - for (int i = 0; i < hasRepeats.NodeSamples.Count; i++) - hasRepeats.NodeSamples[i] = HitObject.Samples.Select(o => o.With()).ToList(); - } - } - - /// - /// Invokes , - /// refreshing and parameters for the . - /// - protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; protected override bool Handle(UIEvent e) diff --git a/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs b/osu.Game/Rulesets/Edit/Tools/CompositionTool.cs similarity index 84% rename from osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs rename to osu.Game/Rulesets/Edit/Tools/CompositionTool.cs index ba1dc817bb..f509302daa 100644 --- a/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs +++ b/osu.Game/Rulesets/Edit/Tools/CompositionTool.cs @@ -6,13 +6,13 @@ using osu.Framework.Localisation; namespace osu.Game.Rulesets.Edit.Tools { - public abstract class HitObjectCompositionTool + public abstract class CompositionTool { public readonly string Name; public LocalisableString TooltipText { get; init; } - protected HitObjectCompositionTool(string name) + protected CompositionTool(string name) { Name = name; } diff --git a/osu.Game/Rulesets/Edit/Tools/SelectTool.cs b/osu.Game/Rulesets/Edit/Tools/SelectTool.cs index a272e9f480..7f8889bfca 100644 --- a/osu.Game/Rulesets/Edit/Tools/SelectTool.cs +++ b/osu.Game/Rulesets/Edit/Tools/SelectTool.cs @@ -9,7 +9,7 @@ using osu.Game.Graphics; namespace osu.Game.Rulesets.Edit.Tools { - public class SelectTool : HitObjectCompositionTool + public class SelectTool : CompositionTool { public SelectTool() : base("Select") @@ -18,6 +18,6 @@ namespace osu.Game.Rulesets.Edit.Tools public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorSelect }; - public override PlacementBlueprint CreatePlacementBlueprint() => null; + public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => null; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index f1294ccc3c..f0296d45aa 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -41,6 +41,8 @@ namespace osu.Game.Screens.Edit.Compose.Components public PlacementBlueprint CurrentPlacement { get; private set; } + public HitObjectPlacementBlueprint CurrentHitObjectPlacement => CurrentPlacement as HitObjectPlacementBlueprint; + [Resolved(canBeNull: true)] private EditorScreenWithTimeline editorScreen { get; set; } @@ -164,13 +166,13 @@ namespace osu.Game.Screens.Edit.Compose.Components private void updatePlacementNewCombo() { - if (CurrentPlacement?.HitObject is IHasComboInformation c) + if (CurrentHitObjectPlacement?.HitObject is IHasComboInformation c) c.NewCombo = NewCombo.Value == TernaryState.True; } private void updatePlacementSamples() { - if (CurrentPlacement == null) return; + if (CurrentHitObjectPlacement == null) return; foreach (var kvp in SelectionHandler.SelectionSampleStates) sampleChanged(kvp.Key, kvp.Value.Value); @@ -181,9 +183,9 @@ namespace osu.Game.Screens.Edit.Compose.Components private void sampleChanged(string sampleName, TernaryState state) { - if (CurrentPlacement == null) return; + if (CurrentHitObjectPlacement == null) return; - var samples = CurrentPlacement.HitObject.Samples; + var samples = CurrentHitObjectPlacement.HitObject.Samples; var existingSample = samples.FirstOrDefault(s => s.Name == sampleName); @@ -196,19 +198,19 @@ namespace osu.Game.Screens.Edit.Compose.Components case TernaryState.True: if (existingSample == null) - samples.Add(CurrentPlacement.HitObject.CreateHitSampleInfo(sampleName)); + samples.Add(CurrentHitObjectPlacement.HitObject.CreateHitSampleInfo(sampleName)); break; } } private void bankChanged(string bankName, TernaryState state) { - if (CurrentPlacement == null) return; + if (CurrentHitObjectPlacement == null) return; if (bankName == EditorSelectionHandler.HIT_BANK_AUTO) - CurrentPlacement.AutomaticBankAssignment = state == TernaryState.True; + CurrentHitObjectPlacement.AutomaticBankAssignment = state == TernaryState.True; else if (state == TernaryState.True) - CurrentPlacement.HitObject.Samples = CurrentPlacement.HitObject.Samples.Select(s => s.With(newBank: bankName)).ToList(); + CurrentHitObjectPlacement.HitObject.Samples = CurrentHitObjectPlacement.HitObject.Samples.Select(s => s.With(newBank: bankName)).ToList(); } public readonly Bindable NewCombo = new Bindable { Description = "New Combo" }; @@ -386,12 +388,12 @@ namespace osu.Game.Screens.Edit.Compose.Components CurrentPlacement = null; } - private HitObjectCompositionTool currentTool; + private CompositionTool currentTool; /// /// The current placement tool. /// - public HitObjectCompositionTool CurrentTool + public CompositionTool CurrentTool { get => currentTool; diff --git a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs index 0027e03492..c8d9ef8fc8 100644 --- a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual public abstract partial class PlacementBlueprintTestScene : OsuManualInputManagerTestScene, IPlacementHandler { protected readonly Container HitObjectContainer; - protected PlacementBlueprint CurrentBlueprint { get; private set; } + protected HitObjectPlacementBlueprint CurrentBlueprint { get; private set; } protected PlacementBlueprintTestScene() { @@ -87,14 +87,14 @@ namespace osu.Game.Tests.Visual CurrentBlueprint.UpdateTimeAndPosition(SnapForBlueprint(CurrentBlueprint)); } - protected virtual SnapResult SnapForBlueprint(PlacementBlueprint blueprint) => + protected virtual SnapResult SnapForBlueprint(HitObjectPlacementBlueprint blueprint) => new SnapResult(InputManager.CurrentState.Mouse.Position, null); public override void Add(Drawable drawable) { base.Add(drawable); - if (drawable is PlacementBlueprint blueprint) + if (drawable is HitObjectPlacementBlueprint blueprint) { blueprint.Show(); blueprint.UpdateTimeAndPosition(SnapForBlueprint(blueprint)); @@ -106,6 +106,6 @@ namespace osu.Game.Tests.Visual protected virtual Container CreateHitObjectContainer() => new Container { RelativeSizeAxes = Axes.Both }; protected abstract DrawableHitObject CreateHitObject(HitObject hitObject); - protected abstract PlacementBlueprint CreateBlueprint(); + protected abstract HitObjectPlacementBlueprint CreateBlueprint(); } } From 0a5a4633807d20d4d95e36e9969112328d89781f Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 23 Sep 2024 16:36:11 +0200 Subject: [PATCH 270/924] Convert 'grid from points' button to placement tool --- .../Edit/Blueprints/GridPlacementBlueprint.cs | 83 +++++++++++++++++++ .../Edit/GridFromPointsTool.cs | 78 ++++------------- .../Edit/OsuGridToolboxGroup.cs | 12 --- .../Edit/OsuHitObjectComposer.cs | 15 +--- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 13 ++- 5 files changed, 105 insertions(+), 96 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs new file mode 100644 index 0000000000..f632828f62 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs @@ -0,0 +1,83 @@ +// 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.Input.Events; +using osu.Game.Rulesets.Edit; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints +{ + public partial class GridPlacementBlueprint : PlacementBlueprint + { + [Resolved] + private HitObjectComposer? hitObjectComposer { get; set; } + + private OsuGridToolboxGroup gridToolboxGroup = null!; + private Vector2 originalOrigin; + private float originalSpacing; + private float originalRotation; + + [BackgroundDependencyLoader] + private void load(OsuGridToolboxGroup gridToolboxGroup) + { + this.gridToolboxGroup = gridToolboxGroup; + originalOrigin = gridToolboxGroup.StartPosition.Value; + originalSpacing = gridToolboxGroup.Spacing.Value; + originalRotation = gridToolboxGroup.GridLinesRotation.Value; + } + + public override void EndPlacement(bool commit) + { + if (!commit && PlacementActive != PlacementState.Finished) + { + gridToolboxGroup.StartPosition.Value = originalOrigin; + gridToolboxGroup.Spacing.Value = originalSpacing; + gridToolboxGroup.GridLinesRotation.Value = originalRotation; + } + + base.EndPlacement(commit); + + // You typically only place the grid once, so we switch back to the select tool after placement. + if (commit && hitObjectComposer is OsuHitObjectComposer osuHitObjectComposer) + osuHitObjectComposer.SetSelectTool(); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + switch (e.Button) + { + case MouseButton.Right: + EndPlacement(true); + return true; + + case MouseButton.Left: + switch (PlacementActive) + { + case PlacementState.Waiting: + BeginPlacement(true); + return true; + + case PlacementState.Active: + EndPlacement(true); + return true; + } + + break; + } + + return base.OnMouseDown(e); + } + + public override void UpdateTimeAndPosition(SnapResult result) + { + var pos = ToLocalSpace(result.ScreenSpacePosition); + + if (PlacementActive != PlacementState.Active) + gridToolboxGroup.StartPosition.Value = pos; + else + gridToolboxGroup.SetGridFromPoints(gridToolboxGroup.StartPosition.Value, pos); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs index 9bbeeaafc3..1714dacd17 100644 --- a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs +++ b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs @@ -1,79 +1,29 @@ // 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.Input.Events; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; using osu.Game.Rulesets.Edit; -using osuTK; -using osuTK.Input; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Osu.Edit.Blueprints; namespace osu.Game.Rulesets.Osu.Edit { - public partial class GridFromPointsTool : Drawable + public partial class GridFromPointsTool : CompositionTool { - [Resolved] - private OsuGridToolboxGroup gridToolboxGroup { get; set; } = null!; - - [Resolved(canBeNull: true)] - private IPositionSnapProvider? snapProvider { get; set; } - - public bool IsPlacing { get; private set; } - - private Vector2? startPosition; - - protected override void LoadComplete() + public GridFromPointsTool() + : base("Change grid") { - base.LoadComplete(); - - gridToolboxGroup.GridFromPointsClicked += BeginPlacement; + TooltipText = """ + Left click to set the origin. + Left click again to set the rotation and spacing. + Right click to only set the origin. + """; } - public void BeginPlacement() - { - IsPlacing = true; - startPosition = null; - } + public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorGridSnap }; - protected override bool OnMouseDown(MouseDownEvent e) - { - if (!IsPlacing) - return base.OnMouseDown(e); - - var pos = snappedLocalPosition(e); - - if (!startPosition.HasValue) - startPosition = pos; - else - { - gridToolboxGroup.SetGridFromPoints(startPosition.Value, pos); - IsPlacing = false; - } - - if (e.Button == MouseButton.Right) - IsPlacing = false; - - return true; - } - - protected override bool OnMouseMove(MouseMoveEvent e) - { - if (!IsPlacing) - return base.OnMouseMove(e); - - var pos = snappedLocalPosition(e); - - if (!startPosition.HasValue) - gridToolboxGroup.StartPosition.Value = pos; - else - gridToolboxGroup.SetGridFromPoints(startPosition.Value, pos); - - return true; - } - - private Vector2 snappedLocalPosition(UIEvent e) - { - return ToLocalSpace(snapProvider?.FindSnappedPositionAndTime(e.ScreenSpaceMousePosition, ~SnapType.GlobalGrids).ScreenSpacePosition ?? e.ScreenSpaceMousePosition); - } + public override PlacementBlueprint CreatePlacementBlueprint() => new GridPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 8752e17dad..db2909a6af 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -13,7 +13,6 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; -using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.UI; @@ -86,11 +85,8 @@ namespace osu.Game.Rulesets.Osu.Edit private ExpandableSlider startPositionYSlider = null!; private ExpandableSlider spacingSlider = null!; private ExpandableSlider gridLinesRotationSlider = null!; - private RoundedButton gridFromPointsButton = null!; private EditorRadioButtonCollection gridTypeButtons = null!; - public event Action? GridFromPointsClicked; - public OsuGridToolboxGroup() : base("grid") { @@ -149,12 +145,6 @@ namespace osu.Game.Rulesets.Osu.Edit Spacing = new Vector2(0f, 10f), Children = new Drawable[] { - gridFromPointsButton = new RoundedButton - { - Action = () => GridFromPointsClicked?.Invoke(), - RelativeSizeAxes = Axes.X, - Text = "Grid from points", - }, gridTypeButtons = new EditorRadioButtonCollection { RelativeSizeAxes = Axes.X, @@ -220,8 +210,6 @@ namespace osu.Game.Rulesets.Osu.Edit expandingContainer?.Expanded.BindValueChanged(v => { - gridFromPointsButton.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint); - gridFromPointsButton.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None; gridTypeButtons.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint); gridTypeButtons.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None; }, true); diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index e6a9a95b6b..3e24a48d49 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -45,7 +45,8 @@ namespace osu.Game.Rulesets.Osu.Edit { new HitCircleCompositionTool(), new SliderCompositionTool(), - new SpinnerCompositionTool() + new SpinnerCompositionTool(), + new GridFromPointsTool(), }; private readonly Bindable rectangularGridSnapToggle = new Bindable(); @@ -61,8 +62,6 @@ namespace osu.Game.Rulesets.Osu.Edit private Bindable placementObject; - private GridFromPointsTool gridFromPointsTool; - [Cached(typeof(IDistanceSnapProvider))] public readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider(); @@ -100,14 +99,6 @@ namespace osu.Game.Rulesets.Osu.Edit OsuGridToolboxGroup.GridType.BindValueChanged(updatePositionSnapGrid, true); - LayerAboveRuleset.Add( - // Place it above the playfield and blueprints, so it takes priority when handling input. - gridFromPointsTool = new GridFromPointsTool - { - RelativeSizeAxes = Axes.Both, - } - ); - RightToolbox.AddRange(new Drawable[] { OsuGridToolboxGroup, @@ -291,7 +282,7 @@ namespace osu.Game.Rulesets.Osu.Edit foreach (var b in blueprints) { - if (b.IsSelected && !gridFromPointsTool.IsPlacing) + if (b.IsSelected) continue; var snapPositions = b.ScreenSpaceSnapPoints; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index b83c8acec6..2053f9ff5d 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -77,8 +77,6 @@ namespace osu.Game.Rulesets.Edit protected readonly Container LayerBelowRuleset = new Container { RelativeSizeAxes = Axes.Both }; - protected readonly Container LayerAboveRuleset = new Container { RelativeSizeAxes = Axes.Both }; - protected InputManager InputManager { get; private set; } private Box leftToolboxBackground; @@ -145,8 +143,7 @@ namespace osu.Game.Rulesets.Edit drawableRulesetWrapper, // layers above playfield drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer() - .WithChild(blueprintContainer = CreateBlueprintContainer()), - drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer().WithChild(LayerAboveRuleset), + .WithChild(blueprintContainer = CreateBlueprintContainer()) } }, new Container @@ -234,7 +231,7 @@ namespace osu.Game.Rulesets.Edit sampleBankTogglesCollection.AddRange(BlueprintContainer.SampleBankTernaryStates.Select(b => new DrawableTernaryButton(b))); - setSelectTool(); + SetSelectTool(); EditorBeatmap.SelectedHitObjects.CollectionChanged += selectionChanged; } @@ -259,7 +256,7 @@ namespace osu.Game.Rulesets.Edit { // it's important this is performed before the similar code in EditorRadioButton disables the button. if (!timing.NewValue) - setSelectTool(); + SetSelectTool(); }); EditorBeatmap.HasTiming.BindValueChanged(hasTiming => @@ -463,11 +460,11 @@ namespace osu.Game.Rulesets.Edit if (EditorBeatmap.SelectedHitObjects.Any()) { // ensure in selection mode if a selection is made. - setSelectTool(); + SetSelectTool(); } } - private void setSelectTool() => toolboxCollection.Items.First().Select(); + public void SetSelectTool() => toolboxCollection.Items.First().Select(); private void toolSelected(CompositionTool tool) { From fe106217718ce733a9acc1d886f94e0e25a4ca5b Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 23 Sep 2024 16:39:09 +0200 Subject: [PATCH 271/924] Clarify criteria of grid spacing subdivision --- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index db2909a6af..487d73693f 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -107,6 +107,7 @@ namespace osu.Game.Rulesets.Osu.Edit } // Divide the distance so that there is a good density of grid lines. + // This matches the maximum grid size of the grid size cycling hotkey. float dist = Vector2.Distance(point1, point2); while (dist >= max_automatic_spacing) dist /= 2; From 0f0f490598a575cfa934628f4f7d461767b27f46 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 23 Sep 2024 17:24:09 +0200 Subject: [PATCH 272/924] Don't snap to global grid while placing grid --- osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs | 2 ++ osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 2 ++ .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs index f632828f62..d45d82579f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs @@ -70,6 +70,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints return base.OnMouseDown(e); } + public override SnapType SnapType => ~SnapType.GlobalGrids; + public override void UpdateTimeAndPosition(SnapResult result) { var pos = ToLocalSpace(result.ScreenSpacePosition); diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index d2a54e8e03..2b346d750f 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -71,6 +71,8 @@ namespace osu.Game.Rulesets.Edit PlacementActive = PlacementState.Finished; } + public virtual SnapType SnapType => SnapType.All; + /// /// Updates the time and position of this based on the provided snap information. /// diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index f0296d45aa..cbec8fc7a3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -297,7 +297,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void updatePlacementPosition() { - var snapResult = Composer.FindSnappedPositionAndTime(InputManager.CurrentState.Mouse.Position); + var snapResult = Composer.FindSnappedPositionAndTime(InputManager.CurrentState.Mouse.Position, CurrentPlacement.SnapType); // if no time was found from positional snapping, we should still quantize to the beat. snapResult.Time ??= Beatmap.SnapTime(EditorClock.CurrentTime, null); From b274ed9427b040aaa441be6227c8e6f0d7f1dd13 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 23 Sep 2024 17:34:45 +0200 Subject: [PATCH 273/924] fix warnings --- .../Editor/TestSceneBananaShowerPlacementBlueprint.cs | 2 +- osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs | 2 +- osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs index badd8e967d..296d34d628 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor AddClickStep(MouseButton.Left); AddClickStep(MouseButton.Right); AddAssert("banana shower is not placed", () => LastObject == null); - AddAssert("state is waiting", () => CurrentBlueprint?.PlacementActive == HitObjectPlacementBlueprint.PlacementState.Waiting); + AddAssert("state is waiting", () => CurrentBlueprint?.PlacementActive == PlacementBlueprint.PlacementState.Waiting); } [Test] diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index 8460e238f6..7323c7a91a 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -168,7 +168,7 @@ namespace osu.Game.Rulesets.Catch.Edit if (EditorBeatmap.PlacementObject.Value is JuiceStream) { // Juice stream path is not subject to snapping. - if (BlueprintContainer.CurrentPlacement.PlacementActive is HitObjectPlacementBlueprint.PlacementState.Active) + if (BlueprintContainer.CurrentPlacement.PlacementActive is PlacementBlueprint.PlacementState.Active) return null; } diff --git a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs index 6f3342f8ce..fe74e1b346 100644 --- a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs @@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("editor is still current", () => Editor.IsCurrentScreen()); AddAssert("slider not placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(0)); AddAssert("no active placement", () => this.ChildrenOfType().Single().CurrentPlacement.PlacementActive, - () => Is.EqualTo(HitObjectPlacementBlueprint.PlacementState.Waiting)); + () => Is.EqualTo(PlacementBlueprint.PlacementState.Waiting)); } [Test] From 4da78a8c009c4f2b0c5a254c923d194046b0c5c9 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Tue, 24 Sep 2024 10:06:07 +0100 Subject: [PATCH 274/924] make speed bonuses additive, scale distanceBonus --- .../Difficulty/Evaluators/SpeedEvaluator.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs index 37fd11391c..4dbc3d9a23 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs @@ -13,6 +13,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators private const double single_spacing_threshold = 125; // 1.25 circles distance between centers private const double min_speed_bonus = 75; // ~200BPM private const double speed_balancing_factor = 40; + private const double distance_multiplier = 0.95; /// /// Evaluates the difficulty of tapping the current object, based on: @@ -51,11 +52,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators strainTime /= Math.Clamp((strainTime / osuCurrObj.HitWindowGreat) / 0.93, 0.92, 1); // speedBonus will be 1.0 for BPM < 200 - double speedBonus = 1.0; + double speedBonus = 0.0; // Add additional scaling bonus for streams/bursts higher than 200bpm if (strainTime < min_speed_bonus) - speedBonus = 1 + 0.75 * Math.Pow((min_speed_bonus - strainTime) / speed_balancing_factor, 2); + speedBonus = 0.75 * Math.Pow((min_speed_bonus - strainTime) / speed_balancing_factor, 2); double travelDistance = osuPrevObj?.TravelDistance ?? 0; double distance = travelDistance + osuCurrObj.MinimumJumpDistance; @@ -64,10 +65,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators distance = Math.Min(distance, single_spacing_threshold); // Max distance bonus is 2 at single_spacing_threshold - double distanceBonus = 1 + Math.Pow(distance / single_spacing_threshold, 3.5); + double distanceBonus = Math.Pow(distance / single_spacing_threshold, 3.75) * distance_multiplier; // Base difficulty with all bonuses - double difficulty = speedBonus * distanceBonus * 1000 / strainTime; + double difficulty = (1 + speedBonus + distanceBonus) * 1000 / strainTime; // Apply penalty if there's doubletappable doubles return difficulty * doubletapness; From c857de3a9a45f691235a1ac9d4ddd5381e6e5042 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 24 Sep 2024 11:44:02 +0200 Subject: [PATCH 275/924] Revert "add a max depth to prevent stack overflow" This reverts commit bf245aa9d61d2fc1f3cffede114f0ecd2a34a7e6. --- osu.Game/Utils/GeometryUtils.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index c4c63903bb..d4968749bf 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -305,11 +305,8 @@ namespace osu.Game.Utils // n represents the number of points in P that are not yet processed. private static (Vector2, float) welzlHelper(List points, ReadOnlySpan r, int n) { - const int max_depth = 4000; - // Base case when all points processed or |R| = 3 - // To prevent stack overflow, we stop at a certain depth and give an approximate answer - if (n == 0 || r.Length == 3 || points.Count - n >= max_depth) + if (n == 0 || r.Length == 3) return minCircleTrivial(r); // Pick a random point randomly From 86432078dd04ea69de005fac9f98c1353a59905d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 24 Sep 2024 11:53:02 +0200 Subject: [PATCH 276/924] Remove usage of switch expression syntax It's not universally accepted here and a `when` crept in that can be bypassed entirely using rather clean baseline language constructs, so why bother at this point. --- .../Edit/PreciseScalePopover.cs | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index ec347939e7..33b0c14185 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -198,15 +198,26 @@ namespace osu.Game.Rulesets.Osu.Edit updateAxisCheckBoxesEnabled(); } - private Vector2? getOriginPosition(PreciseScaleInfo scale) => - scale.Origin switch + private Vector2? getOriginPosition(PreciseScaleInfo scale) + { + switch (scale.Origin) { - ScaleOrigin.GridCentre => gridToolbox.StartPosition.Value, - ScaleOrigin.PlayfieldCentre => OsuPlayfield.BASE_SIZE / 2, - ScaleOrigin.SelectionCentre when selectedItems.Count == 1 && selectedItems.First() is Slider slider => slider.Position, - ScaleOrigin.SelectionCentre => null, - _ => throw new ArgumentOutOfRangeException(nameof(scale)) - }; + case ScaleOrigin.GridCentre: + return gridToolbox.StartPosition.Value; + + case ScaleOrigin.PlayfieldCentre: + return OsuPlayfield.BASE_SIZE / 2; + + case ScaleOrigin.SelectionCentre: + if (selectedItems.Count == 1 && selectedItems.First() is Slider slider) + return slider.Position; + + return null; + + default: + throw new ArgumentOutOfRangeException(nameof(scale)); + } + } private Axes getAdjustAxis(PreciseScaleInfo scale) => scale.XAxis ? scale.YAxis ? Axes.Both : Axes.X : Axes.Y; From 3031b68552cc9b2caa498a92ca4c72c4711a8871 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 24 Sep 2024 11:56:04 +0200 Subject: [PATCH 277/924] add TestMinimumEnclosingCircle --- osu.Game.Tests/Utils/GeometryUtilsTest.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/osu.Game.Tests/Utils/GeometryUtilsTest.cs b/osu.Game.Tests/Utils/GeometryUtilsTest.cs index ded4656ac1..f73175bb5b 100644 --- a/osu.Game.Tests/Utils/GeometryUtilsTest.cs +++ b/osu.Game.Tests/Utils/GeometryUtilsTest.cs @@ -29,5 +29,23 @@ namespace osu.Game.Tests.Utils Assert.That(hull, Is.EquivalentTo(expectedPoints)); } + + [TestCase(new int[] { }, 0, 0, 0)] + [TestCase(new[] { 0, 0 }, 0, 0, 0)] + [TestCase(new[] { 0, 0, 1, 1, 1, -1, 2, 0 }, 1, 0, 1)] + [TestCase(new[] { 0, 0, 1, 1, 1, -1, 2, 0, 1, 0 }, 1, 0, 1)] + [TestCase(new[] { 0, 0, 1, 1, 2, -1, 2, 0, 1, 0, 4, 10 }, 3, 4.5f, 5.5901699f)] + public void TestMinimumEnclosingCircle(int[] values, float x, float y, float r) + { + var points = new Vector2[values.Length / 2]; + for (int i = 0; i < values.Length; i += 2) + points[i / 2] = new Vector2(values[i], values[i + 1]); + + (var centre, float radius) = GeometryUtils.MinimumEnclosingCircle(points); + + Assert.That(centre.X, Is.EqualTo(x).Within(0.0001)); + Assert.That(centre.Y, Is.EqualTo(y).Within(0.0001)); + Assert.That(radius, Is.EqualTo(r).Within(0.0001)); + } } } From ac9c1508b1367ab24c7c479636508a89c8bfb3b7 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Tue, 24 Sep 2024 11:22:46 +0100 Subject: [PATCH 278/924] update incorrect code comment Co-authored-by: StanR --- osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs index 4dbc3d9a23..6a62fa32f3 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators // 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly, whilst 0.92 limits the effect of the cap. strainTime /= Math.Clamp((strainTime / osuCurrObj.HitWindowGreat) / 0.93, 0.92, 1); - // speedBonus will be 1.0 for BPM < 200 + // speedBonus will be 0.0 for BPM < 200 double speedBonus = 0.0; // Add additional scaling bonus for streams/bursts higher than 200bpm From 98d9b5eec8a1b3ba58e6373dc8c5192a3646b622 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Tue, 24 Sep 2024 11:23:34 +0100 Subject: [PATCH 279/924] correct `distanceBonus` code comment --- osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs index 6a62fa32f3..ec0bec9b08 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators // Cap distance at single_spacing_threshold distance = Math.Min(distance, single_spacing_threshold); - // Max distance bonus is 2 at single_spacing_threshold + // Max distance bonus is 1 * `distance_multiplier` at single_spacing_threshold double distanceBonus = Math.Pow(distance / single_spacing_threshold, 3.75) * distance_multiplier; // Base difficulty with all bonuses From ce5c666c346ec61a42c16eb8e6b422d01cb95881 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Tue, 24 Sep 2024 11:28:15 +0100 Subject: [PATCH 280/924] bump global multiplier --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 67d88b6b01..33c33d82ab 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty { public class OsuPerformanceCalculator : PerformanceCalculator { - public const double PERFORMANCE_BASE_MULTIPLIER = 1.14; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things. + public const double PERFORMANCE_BASE_MULTIPLIER = 1.15; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things. private double accuracy; private int scoreMaxCombo; From b54b4063bece8eac19e4364773c2ab842fafc636 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Tue, 24 Sep 2024 12:40:28 +0200 Subject: [PATCH 281/924] Rename parameter --- .../Edit/Compose/Components/SelectionBoxScaleHandle.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs index 42e7b8c219..3b7e29cf3d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs @@ -50,14 +50,14 @@ namespace osu.Game.Screens.Edit.Compose.Components rawScale = convertDragEventToScaleMultiplier(e); - applyScale(shouldLockAspectRatio: isCornerAnchor(originalAnchor) && e.ShiftPressed, ignoreAnchor: e.AltPressed); + applyScale(shouldLockAspectRatio: isCornerAnchor(originalAnchor) && e.ShiftPressed, useDefaultOrigin: e.AltPressed); } protected override bool OnKeyDown(KeyDownEvent e) { if (IsDragged) { - applyScale(shouldLockAspectRatio: isCornerAnchor(originalAnchor) && e.ShiftPressed, ignoreAnchor: e.AltPressed); + applyScale(shouldLockAspectRatio: isCornerAnchor(originalAnchor) && e.ShiftPressed, useDefaultOrigin: e.AltPressed); return true; } @@ -69,7 +69,7 @@ namespace osu.Game.Screens.Edit.Compose.Components base.OnKeyUp(e); if (IsDragged) - applyScale(shouldLockAspectRatio: isCornerAnchor(originalAnchor) && e.ShiftPressed, ignoreAnchor: e.AltPressed); + applyScale(shouldLockAspectRatio: isCornerAnchor(originalAnchor) && e.ShiftPressed, useDefaultOrigin: e.AltPressed); } protected override void OnDragEnd(DragEndEvent e) @@ -100,13 +100,13 @@ namespace osu.Game.Screens.Edit.Compose.Components if ((originalAnchor & Anchor.y0) > 0) scale.Y = -scale.Y; } - private void applyScale(bool shouldLockAspectRatio, bool ignoreAnchor = false) + private void applyScale(bool shouldLockAspectRatio, bool useDefaultOrigin = false) { var newScale = shouldLockAspectRatio ? new Vector2((rawScale.X + rawScale.Y) * 0.5f) : rawScale; - Vector2? scaleOrigin = ignoreAnchor ? null : originalAnchor.Opposite().PositionOnQuad(scaleHandler!.OriginalSurroundingQuad!.Value); + Vector2? scaleOrigin = useDefaultOrigin ? null : originalAnchor.Opposite().PositionOnQuad(scaleHandler!.OriginalSurroundingQuad!.Value); scaleHandler!.Update(newScale, scaleOrigin, getAdjustAxis()); } From 4c2ebdb2dbb3250b4f05d98bfda869e341916519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 24 Sep 2024 12:53:54 +0200 Subject: [PATCH 282/924] Simplify accent colour assignment in argon wedge piece --- osu.Game/Screens/Play/HUD/ArgonWedgePiece.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonWedgePiece.cs b/osu.Game/Screens/Play/HUD/ArgonWedgePiece.cs index fb2e93b62b..46a658cd1c 100644 --- a/osu.Game/Screens/Play/HUD/ArgonWedgePiece.cs +++ b/osu.Game/Screens/Play/HUD/ArgonWedgePiece.cs @@ -41,7 +41,6 @@ namespace osu.Game.Screens.Play.HUD InternalChild = new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(AccentColour.Value.Opacity(0.0f), AccentColour.Value.Opacity(0.25f)), }; } @@ -50,7 +49,7 @@ namespace osu.Game.Screens.Play.HUD base.LoadComplete(); InvertShear.BindValueChanged(v => Shear = new Vector2(0.8f, 0f) * (v.NewValue ? -1 : 1), true); - AccentColour.BindValueChanged(c => InternalChild.Colour = ColourInfo.GradientVertical(AccentColour.Value.Opacity(0.0f), AccentColour.Value.Opacity(0.25f))); + AccentColour.BindValueChanged(c => InternalChild.Colour = ColourInfo.GradientVertical(AccentColour.Value.Opacity(0.0f), AccentColour.Value.Opacity(0.25f)), true); } } } From 5eb23d3a7157ce32e41e8c5cbcedfc1771c15aac Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Tue, 24 Sep 2024 12:24:54 +0100 Subject: [PATCH 283/924] balancing attempts --- osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs | 4 ++-- osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs index ec0bec9b08..da1cb4b3b3 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators private const double single_spacing_threshold = 125; // 1.25 circles distance between centers private const double min_speed_bonus = 75; // ~200BPM private const double speed_balancing_factor = 40; - private const double distance_multiplier = 0.95; + private const double distance_multiplier = 0.94; /// /// Evaluates the difficulty of tapping the current object, based on: @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators distance = Math.Min(distance, single_spacing_threshold); // Max distance bonus is 1 * `distance_multiplier` at single_spacing_threshold - double distanceBonus = Math.Pow(distance / single_spacing_threshold, 3.75) * distance_multiplier; + double distanceBonus = Math.Pow(distance / single_spacing_threshold, 3.95) * distance_multiplier; // Base difficulty with all bonuses double difficulty = (1 + speedBonus + distanceBonus) * 1000 / strainTime; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 1fbe03395c..48e421211f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private double currentStrain; - private double skillMultiplier => 24.963; + private double skillMultiplier => 24.983; private double strainDecayBase => 0.15; private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); From 3ad734296473eae7fcfd91b3ed11b43fcc0d4774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Tue, 24 Sep 2024 13:35:56 +0200 Subject: [PATCH 284/924] Add tests for shift and alt modifiers in select box --- .../Editing/TestSceneComposerSelection.cs | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs index 3884a3108f..3d7aef5a65 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; using osu.Framework.Testing; @@ -36,6 +37,9 @@ namespace osu.Game.Tests.Visual.Editing private ContextMenuContainer contextMenuContainer => Editor.ChildrenOfType().First(); + private SelectionBoxScaleHandle getScaleHandle(Anchor anchor) + => Editor.ChildrenOfType().First(it => it.Anchor == anchor); + private void moveMouseToObject(Func targetFunc) { AddStep("move mouse to object", () => @@ -519,5 +523,137 @@ namespace osu.Game.Tests.Visual.Editing AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); } + + [Test] + public void TestShiftModifierMaintainsAspectRatio() + { + HitCircle[] addedObjects = null!; + + float aspectRatioBeforeDrag = 0; + + float getAspectRatio() => (addedObjects[1].X - addedObjects[0].X) / (addedObjects[1].Y - addedObjects[0].Y); + + AddStep("add hitobjects", () => + { + EditorBeatmap.AddRange(addedObjects = new[] + { + new HitCircle { StartTime = 100, Position = new Vector2(150, 150) }, + new HitCircle { StartTime = 200, Position = new Vector2(250, 200) }, + }); + + aspectRatioBeforeDrag = getAspectRatio(); + }); + + AddStep("select objects", () => EditorBeatmap.SelectedHitObjects.AddRange(addedObjects)); + + AddStep("move mouse to handle", () => InputManager.MoveMouseTo(getScaleHandle(Anchor.BottomRight).ScreenSpaceDrawQuad.Centre)); + + AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left)); + + AddStep("move mouse", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(50))); + + AddStep("aspect ratio does not equal", () => Assert.AreNotEqual(aspectRatioBeforeDrag, getAspectRatio())); + + AddStep("press shift", () => InputManager.PressKey(Key.ShiftLeft)); + + AddStep("aspect ratio does equal", () => Assert.AreEqual(aspectRatioBeforeDrag, getAspectRatio())); + + AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left)); + + AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); + } + + [Test] + public void TestAltModifierScalesAroundCenter() + { + HitCircle[] addedObjects = null!; + + Vector2 centerBeforeDrag = Vector2.Zero; + + Vector2 getCenter() => (addedObjects[0].Position + addedObjects[1].Position) / 2; + + AddStep("add hitobjects", () => + { + EditorBeatmap.AddRange(addedObjects = new[] + { + new HitCircle { StartTime = 100, Position = new Vector2(150, 150) }, + new HitCircle { StartTime = 200, Position = new Vector2(250, 200) }, + }); + + centerBeforeDrag = getCenter(); + }); + + AddStep("select objects", () => EditorBeatmap.SelectedHitObjects.AddRange(addedObjects)); + + AddStep("move mouse to handle", () => InputManager.MoveMouseTo(getScaleHandle(Anchor.BottomRight).ScreenSpaceDrawQuad.Centre)); + + AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left)); + + AddStep("move mouse", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(50))); + + AddStep("center does not equal", () => Assert.AreNotEqual(centerBeforeDrag, getCenter())); + + AddStep("press alt", () => InputManager.PressKey(Key.AltLeft)); + + AddStep("center does equal", () => Assert.AreEqual(centerBeforeDrag, getCenter())); + + AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left)); + + AddStep("release alt", () => InputManager.ReleaseKey(Key.AltLeft)); + } + + [Test] + public void TestShiftAndAltModifierKeys() + { + HitCircle[] addedObjects = null!; + + float aspectRatioBeforeDrag = 0; + + Vector2 centerBeforeDrag = Vector2.Zero; + + float getAspectRatio() => (addedObjects[1].X - addedObjects[0].X) / (addedObjects[1].Y - addedObjects[0].Y); + + Vector2 getCenter() => (addedObjects[0].Position + addedObjects[1].Position) / 2; + + AddStep("add hitobjects", () => + { + EditorBeatmap.AddRange(addedObjects = new[] + { + new HitCircle { StartTime = 100, Position = new Vector2(150, 150) }, + new HitCircle { StartTime = 200, Position = new Vector2(250, 200) }, + }); + + aspectRatioBeforeDrag = getAspectRatio(); + centerBeforeDrag = getCenter(); + }); + + AddStep("select objects", () => EditorBeatmap.SelectedHitObjects.AddRange(addedObjects)); + + AddStep("move mouse to handle", () => InputManager.MoveMouseTo(getScaleHandle(Anchor.BottomRight).ScreenSpaceDrawQuad.Centre)); + + AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left)); + + AddStep("move mouse", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(50))); + + AddStep("aspect ratio does not equal", () => Assert.AreNotEqual(aspectRatioBeforeDrag, getAspectRatio())); + + AddStep("center does not equal", () => Assert.AreNotEqual(centerBeforeDrag, getCenter())); + + AddStep("press shift", () => InputManager.PressKey(Key.ShiftLeft)); + + AddStep("aspect ratio does equal", () => Assert.AreEqual(aspectRatioBeforeDrag, getAspectRatio())); + + AddStep("center does not equal", () => Assert.AreNotEqual(centerBeforeDrag, getCenter())); + + AddStep("press alt", () => InputManager.PressKey(Key.AltLeft)); + + AddStep("center does equal", () => Assert.AreEqual(centerBeforeDrag, getCenter())); + + AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left)); + + AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); + + AddStep("release alt", () => InputManager.ReleaseKey(Key.AltLeft)); + } } } From 15c4b1dc8f81dd4db100854cde33f928db6307ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Tue, 24 Sep 2024 13:45:03 +0200 Subject: [PATCH 285/924] Move mouse horizontally in test to make sure it doesn't accidentally maintain aspect ratio --- osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs index 3d7aef5a65..cbc9088d04 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs @@ -550,7 +550,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left)); - AddStep("move mouse", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(50))); + AddStep("move mouse", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(50, 0))); AddStep("aspect ratio does not equal", () => Assert.AreNotEqual(aspectRatioBeforeDrag, getAspectRatio())); @@ -589,7 +589,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left)); - AddStep("move mouse", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(50))); + AddStep("move mouse", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(50, 0))); AddStep("center does not equal", () => Assert.AreNotEqual(centerBeforeDrag, getCenter())); @@ -633,7 +633,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left)); - AddStep("move mouse", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(50))); + AddStep("move mouse", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(50, 0))); AddStep("aspect ratio does not equal", () => Assert.AreNotEqual(aspectRatioBeforeDrag, getAspectRatio())); From 7f8b64bb6db05cacb52a1224a2d07c4fd4b9b5f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 27 Aug 2024 15:59:42 +0200 Subject: [PATCH 286/924] Redesign directory & file selector (and update usages accordingly) --- .../Settings/TestSceneDirectorySelector.cs | 5 ++ .../Visual/Settings/TestSceneFileSelector.cs | 34 +++++--- .../Screens/Setup/StablePathSelectScreen.cs | 3 + .../UserInterfaceV2/OsuDirectorySelector.cs | 43 ++++++++-- .../OsuDirectorySelectorBreadcrumbDisplay.cs | 79 ++++++++++++++++--- .../OsuDirectorySelectorDirectory.cs | 31 +------- .../OsuDirectorySelectorHiddenToggle.cs | 3 +- .../OsuDirectorySelectorParentDirectory.cs | 8 ++ .../UserInterfaceV2/OsuFileSelector.cs | 52 +++++++++--- .../FirstRunSetup/ScreenImportFromStable.cs | 8 ++ .../Maintenance/DirectorySelectScreen.cs | 7 +- .../Screens/Edit/Setup/LabelledFileChooser.cs | 9 +++ osu.Game/Screens/Import/FileImportScreen.cs | 12 +-- 13 files changed, 220 insertions(+), 74 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneDirectorySelector.cs b/osu.Game.Tests/Visual/Settings/TestSceneDirectorySelector.cs index 3ef0ffc13a..03ecd4af61 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneDirectorySelector.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneDirectorySelector.cs @@ -9,6 +9,11 @@ namespace osu.Game.Tests.Visual.Settings { public partial class TestSceneDirectorySelector : ThemeComparisonTestScene { + public TestSceneDirectorySelector() + : base(false) + { + } + protected override Drawable CreateContent() => new OsuDirectorySelector { RelativeSizeAxes = Axes.Both diff --git a/osu.Game.Tests/Visual/Settings/TestSceneFileSelector.cs b/osu.Game.Tests/Visual/Settings/TestSceneFileSelector.cs index c70277987e..cf8a589152 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneFileSelector.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneFileSelector.cs @@ -1,37 +1,49 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays; using osu.Game.Tests.Visual.UserInterface; namespace osu.Game.Tests.Visual.Settings { public partial class TestSceneFileSelector : ThemeComparisonTestScene { - [Resolved] - private OsuColour colours { get; set; } = null!; + public TestSceneFileSelector() + : base(false) + { + } [Test] public void TestJpgFilesOnly() { AddStep("create", () => { - ContentContainer.Children = new Drawable[] + var colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + + ContentContainer.Child = new DependencyProvidingContainer { - new Box + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] { - RelativeSizeAxes = Axes.Both, - Colour = colours.GreySeaFoam + (typeof(OverlayColourProvider), colourProvider) }, - new OsuFileSelector(validFileExtensions: new[] { ".jpg" }) + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background3 + }, + new OsuFileSelector(validFileExtensions: new[] { ".jpg" }) + { + RelativeSizeAxes = Axes.Both, + }, + } }; }); } diff --git a/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs index 74404e06f8..91b03ed085 100644 --- a/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs @@ -27,6 +27,9 @@ namespace osu.Game.Tournament.Screens.Setup [Resolved] private MatchIPCInfo ipc { get; set; } = null!; + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + private OsuDirectorySelector directorySelector = null!; private DialogOverlay? overlay; diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs index 21f926ba42..4002480e7f 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs @@ -7,14 +7,18 @@ using System.IO; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.Containers; +using osu.Game.Overlays; namespace osu.Game.Graphics.UserInterfaceV2 { public partial class OsuDirectorySelector : DirectorySelector { - public const float ITEM_HEIGHT = 20; + public const float ITEM_HEIGHT = 16; + + private Box hiddenToggleBackground = null!; public OsuDirectorySelector(string initialPath = null) : base(initialPath) @@ -22,16 +26,45 @@ namespace osu.Game.Graphics.UserInterfaceV2 } [BackgroundDependencyLoader] - private void load() + private void load(OverlayColourProvider colourProvider) { - Padding = new MarginPadding(10); + AddInternal(new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background5, + Depth = float.MaxValue, + }); + + hiddenToggleBackground.Colour = colourProvider.Background4; } - protected override ScrollContainer CreateScrollContainer() => new OsuScrollContainer(); + protected override ScrollContainer CreateScrollContainer() => new OsuScrollContainer + { + Padding = new MarginPadding + { + Horizontal = 20, + Vertical = 15, + } + }; protected override DirectorySelectorBreadcrumbDisplay CreateBreadcrumb() => new OsuDirectorySelectorBreadcrumbDisplay(); - protected override Drawable CreateHiddenToggleButton() => new OsuDirectorySelectorHiddenToggle { Current = { BindTarget = ShowHiddenItems } }; + protected override Drawable CreateHiddenToggleButton() => new Container + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Children = new Drawable[] + { + hiddenToggleBackground = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new OsuDirectorySelectorHiddenToggle + { + Current = { BindTarget = ShowHiddenItems }, + }, + } + }; protected override DirectorySelectorDirectory CreateParentDirectoryItem(DirectoryInfo directory) => new OsuDirectorySelectorParentDirectory(directory); diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorBreadcrumbDisplay.cs b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorBreadcrumbDisplay.cs index 0917b9db97..5b52663198 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorBreadcrumbDisplay.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorBreadcrumbDisplay.cs @@ -6,28 +6,48 @@ using System.IO; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; using osuTK; namespace osu.Game.Graphics.UserInterfaceV2 { internal partial class OsuDirectorySelectorBreadcrumbDisplay : DirectorySelectorBreadcrumbDisplay { - protected override Drawable CreateCaption() => new OsuSpriteText + public const float HEIGHT = 45; + public const float HORIZONTAL_PADDING = 20; + + protected override Drawable CreateCaption() => Empty().With(d => { - Text = "Current Directory: ", - Font = OsuFont.Default.With(size: OsuDirectorySelector.ITEM_HEIGHT), - }; + d.Origin = Anchor.CentreLeft; + d.Anchor = Anchor.CentreLeft; + d.Alpha = 0; + }); protected override DirectorySelectorDirectory CreateRootDirectoryItem() => new OsuBreadcrumbDisplayComputer(); protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuBreadcrumbDisplayDirectory(directory, displayName); - public OsuDirectorySelectorBreadcrumbDisplay() + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) { - Padding = new MarginPadding(15); + ((FillFlowContainer)InternalChild).Padding = new MarginPadding + { + Horizontal = HORIZONTAL_PADDING, + Vertical = 10, + }; + + AddInternal(new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background4, + Depth = 1, + }); } private partial class OsuBreadcrumbDisplayComputer : OsuBreadcrumbDisplayDirectory @@ -40,26 +60,67 @@ namespace osu.Game.Graphics.UserInterfaceV2 } } - private partial class OsuBreadcrumbDisplayDirectory : OsuDirectorySelectorDirectory + private partial class OsuBreadcrumbDisplayDirectory : DirectorySelectorDirectory { public OsuBreadcrumbDisplayDirectory(DirectoryInfo directory, string displayName = null) : base(directory, displayName) { } + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + [BackgroundDependencyLoader] private void load() { + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + + Flow.AutoSizeAxes = Axes.X; + Flow.Height = 25; + Flow.Margin = new MarginPadding { Horizontal = 10, }; + + AddRangeInternal(new Drawable[] + { + new Background + { + Depth = 1 + }, + new HoverClickSounds(), + }); + Flow.Add(new SpriteIcon { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Icon = FontAwesome.Solid.ChevronRight, - Size = new Vector2(FONT_SIZE / 2) + Size = new Vector2(FONT_SIZE / 2), + Margin = new MarginPadding { Left = 5, }, }); + Flow.Colour = colourProvider.Light3; } - protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) ? base.Icon : null; + protected override SpriteText CreateSpriteText() => new OsuSpriteText().With(t => t.Font = OsuFont.Default.With(weight: FontWeight.SemiBold)); + + protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) ? FontAwesome.Solid.Database : null; + + internal partial class Background : CompositeDrawable + { + [BackgroundDependencyLoader] + private void load(OverlayColourProvider overlayColourProvider) + { + RelativeSizeAxes = Axes.Both; + + Masking = true; + CornerRadius = 5; + + InternalChild = new Box + { + Colour = overlayColourProvider.Background3, + RelativeSizeAxes = Axes.Both, + }; + } + } } } } diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs index 932017b03e..a1d76dd7e3 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs @@ -6,13 +6,10 @@ using System.IO; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays; namespace osu.Game.Graphics.UserInterfaceV2 { @@ -24,43 +21,23 @@ namespace osu.Game.Graphics.UserInterfaceV2 } [BackgroundDependencyLoader] - private void load() + private void load(OsuColour colours) { Flow.AutoSizeAxes = Axes.X; Flow.Height = OsuDirectorySelector.ITEM_HEIGHT; AddRangeInternal(new Drawable[] { - new Background - { - Depth = 1 - }, new HoverClickSounds() }); + + Colour = colours.Orange1; } - protected override SpriteText CreateSpriteText() => new OsuSpriteText(); + protected override SpriteText CreateSpriteText() => new OsuSpriteText().With(t => t.Font = OsuFont.Default.With(weight: FontWeight.Bold)); protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) ? FontAwesome.Solid.Database : FontAwesome.Regular.Folder; - - internal partial class Background : CompositeDrawable - { - [BackgroundDependencyLoader(true)] - private void load(OverlayColourProvider overlayColourProvider, OsuColour colours) - { - RelativeSizeAxes = Axes.Both; - - Masking = true; - CornerRadius = 5; - - InternalChild = new Box - { - Colour = overlayColourProvider?.Background5 ?? colours.GreySeaFoamDarker, - RelativeSizeAxes = Axes.Both, - }; - } - } } } diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorHiddenToggle.cs b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorHiddenToggle.cs index 7665ed507f..ffedc9386f 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorHiddenToggle.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorHiddenToggle.cs @@ -16,7 +16,8 @@ namespace osu.Game.Graphics.UserInterfaceV2 { RelativeSizeAxes = Axes.None; AutoSizeAxes = Axes.None; - Size = new Vector2(100, 50); + Size = new Vector2(100, OsuDirectorySelectorBreadcrumbDisplay.HEIGHT); + Margin = new MarginPadding { Right = OsuDirectorySelectorBreadcrumbDisplay.HORIZONTAL_PADDING, }; Anchor = Anchor.CentreLeft; Origin = Anchor.CentreLeft; LabelTextFlowContainer.Anchor = Anchor.CentreLeft; diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorParentDirectory.cs b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorParentDirectory.cs index c0ac9f21ca..d274a0ecfe 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorParentDirectory.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorParentDirectory.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System.IO; +using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; +using osu.Game.Overlays; namespace osu.Game.Graphics.UserInterfaceV2 { @@ -14,5 +16,11 @@ namespace osu.Game.Graphics.UserInterfaceV2 : base(directory, "..") { } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + Colour = colourProvider.Content1; + } } } diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs b/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs index 7097102335..feedeb8ff3 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs @@ -8,32 +8,64 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; namespace osu.Game.Graphics.UserInterfaceV2 { public partial class OsuFileSelector : FileSelector { + private Box hiddenToggleBackground = null!; + public OsuFileSelector(string initialPath = null, string[] validFileExtensions = null) - : base(initialPath, validFileExtensions) { } [BackgroundDependencyLoader] - private void load() + private void load(OverlayColourProvider colourProvider) { - Padding = new MarginPadding(10); + AddInternal(new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background5, + Depth = float.MaxValue, + }); + + hiddenToggleBackground.Colour = colourProvider.Background4; } - protected override ScrollContainer CreateScrollContainer() => new OsuScrollContainer(); + protected override ScrollContainer CreateScrollContainer() => new OsuScrollContainer + { + Padding = new MarginPadding + { + Horizontal = 20, + Vertical = 15, + } + }; protected override DirectorySelectorBreadcrumbDisplay CreateBreadcrumb() => new OsuDirectorySelectorBreadcrumbDisplay(); - protected override Drawable CreateHiddenToggleButton() => new OsuDirectorySelectorHiddenToggle { Current = { BindTarget = ShowHiddenItems } }; + protected override Drawable CreateHiddenToggleButton() => new Container + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Children = new Drawable[] + { + hiddenToggleBackground = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new OsuDirectorySelectorHiddenToggle + { + Current = { BindTarget = ShowHiddenItems }, + }, + } + }; protected override DirectorySelectorDirectory CreateParentDirectoryItem(DirectoryInfo directory) => new OsuDirectorySelectorParentDirectory(directory); @@ -51,19 +83,17 @@ namespace osu.Game.Graphics.UserInterfaceV2 } [BackgroundDependencyLoader] - private void load() + private void load(OverlayColourProvider colourProvider) { Flow.AutoSizeAxes = Axes.X; Flow.Height = OsuDirectorySelector.ITEM_HEIGHT; AddRangeInternal(new Drawable[] { - new OsuDirectorySelectorDirectory.Background - { - Depth = 1 - }, new HoverClickSounds() }); + + Colour = colourProvider.Light3; } protected override IconUsage? Icon @@ -91,7 +121,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 } } - protected override SpriteText CreateSpriteText() => new OsuSpriteText(); + protected override SpriteText CreateSpriteText() => new OsuSpriteText().With(t => t.Font = OsuFont.Default.With(weight: FontWeight.SemiBold)); } } } diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 983cb0bbb4..5eb38b6e11 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -314,6 +314,7 @@ namespace osu.Game.Overlays.FirstRunSetup private partial class DirectoryChooserPopover : OsuPopover { public DirectoryChooserPopover(Bindable currentDirectory) + : base(false) { Child = new Container { @@ -325,6 +326,13 @@ namespace osu.Game.Overlays.FirstRunSetup }, }; } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + Body.BorderColour = colourProvider.Highlight1; + Body.BorderThickness = 2; + } } } } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs index e87ca32bf6..f6f8d3b336 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs @@ -48,8 +48,11 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance /// protected virtual DirectoryInfo InitialPath => null; + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { InternalChild = new Container { @@ -64,7 +67,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance new Box { RelativeSizeAxes = Axes.Both, - Colour = colours.GreySeaFoamDark + Colour = colourProvider.Background4, }, new GridContainer { diff --git a/osu.Game/Screens/Edit/Setup/LabelledFileChooser.cs b/osu.Game/Screens/Edit/Setup/LabelledFileChooser.cs index 61f33c4bdc..a113ca5407 100644 --- a/osu.Game/Screens/Edit/Setup/LabelledFileChooser.cs +++ b/osu.Game/Screens/Edit/Setup/LabelledFileChooser.cs @@ -18,6 +18,7 @@ using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Game.Database; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays; using osuTK; namespace osu.Game.Screens.Edit.Setup @@ -118,6 +119,7 @@ namespace osu.Game.Screens.Edit.Setup protected override string PopOutSampleName => "UI/overlay-big-pop-out"; public FileChooserPopover(string[] handledExtensions, Bindable currentFile, string? chooserPath) + : base(false) { Child = new Container { @@ -129,6 +131,13 @@ namespace osu.Game.Screens.Edit.Setup }, }; } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + Body.BorderColour = colourProvider.Highlight1; + Body.BorderThickness = 2; + } } } } diff --git a/osu.Game/Screens/Import/FileImportScreen.cs b/osu.Game/Screens/Import/FileImportScreen.cs index 6b7a269d12..1bdacae87f 100644 --- a/osu.Game/Screens/Import/FileImportScreen.cs +++ b/osu.Game/Screens/Import/FileImportScreen.cs @@ -15,6 +15,7 @@ using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays; using osuTK; namespace osu.Game.Screens.Import @@ -36,8 +37,8 @@ namespace osu.Game.Screens.Import [Resolved] private OsuGameBase game { get; set; } - [Resolved] - private OsuColour colours { get; set; } + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); [BackgroundDependencyLoader(true)] private void load() @@ -52,11 +53,6 @@ namespace osu.Game.Screens.Import Size = new Vector2(0.9f, 0.8f), Children = new Drawable[] { - new Box - { - Colour = colours.GreySeaFoamDark, - RelativeSizeAxes = Axes.Both, - }, fileSelector = new OsuFileSelector(validFileExtensions: game.HandledExtensions.ToArray()) { RelativeSizeAxes = Axes.Both, @@ -72,7 +68,7 @@ namespace osu.Game.Screens.Import { new Box { - Colour = colours.GreySeaFoamDarker, + Colour = colourProvider.Background4, RelativeSizeAxes = Axes.Both }, new Container From 16fc413a4ac0a933dda126bf275b610cf1b1eef5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 27 Aug 2024 16:01:49 +0200 Subject: [PATCH 287/924] Apply NRT to directory & file selectors --- osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs | 6 ++---- .../OsuDirectorySelectorBreadcrumbDisplay.cs | 8 +++----- .../UserInterfaceV2/OsuDirectorySelectorDirectory.cs | 4 +--- osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs | 7 +++---- 4 files changed, 9 insertions(+), 16 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs index 4002480e7f..85599a5d45 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.IO; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -20,7 +18,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 private Box hiddenToggleBackground = null!; - public OsuDirectorySelector(string initialPath = null) + public OsuDirectorySelector(string? initialPath = null) : base(initialPath) { } @@ -68,7 +66,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 protected override DirectorySelectorDirectory CreateParentDirectoryItem(DirectoryInfo directory) => new OsuDirectorySelectorParentDirectory(directory); - protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuDirectorySelectorDirectory(directory, displayName); + protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string? displayName = null) => new OsuDirectorySelectorDirectory(directory, displayName); protected override void NotifySelectionError() => this.FlashColour(Colour4.Red, 300); } diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorBreadcrumbDisplay.cs b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorBreadcrumbDisplay.cs index 5b52663198..e91076498c 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorBreadcrumbDisplay.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorBreadcrumbDisplay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.IO; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -31,7 +29,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 protected override DirectorySelectorDirectory CreateRootDirectoryItem() => new OsuBreadcrumbDisplayComputer(); - protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuBreadcrumbDisplayDirectory(directory, displayName); + protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string? displayName = null) => new OsuBreadcrumbDisplayDirectory(directory, displayName); [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) @@ -62,13 +60,13 @@ namespace osu.Game.Graphics.UserInterfaceV2 private partial class OsuBreadcrumbDisplayDirectory : DirectorySelectorDirectory { - public OsuBreadcrumbDisplayDirectory(DirectoryInfo directory, string displayName = null) + public OsuBreadcrumbDisplayDirectory(DirectoryInfo? directory, string? displayName = null) : base(directory, displayName) { } [Resolved] - private OverlayColourProvider colourProvider { get; set; } + private OverlayColourProvider colourProvider { get; set; } = null!; [BackgroundDependencyLoader] private void load() diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs index a1d76dd7e3..a36804658a 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.IO; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -15,7 +13,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 { internal partial class OsuDirectorySelectorDirectory : DirectorySelectorDirectory { - public OsuDirectorySelectorDirectory(DirectoryInfo directory, string displayName = null) + public OsuDirectorySelectorDirectory(DirectoryInfo directory, string? displayName = null) : base(directory, displayName) { } diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs b/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs index feedeb8ff3..7ce5f63656 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.IO; using System.Linq; using osu.Framework.Allocation; @@ -22,7 +20,8 @@ namespace osu.Game.Graphics.UserInterfaceV2 { private Box hiddenToggleBackground = null!; - public OsuFileSelector(string initialPath = null, string[] validFileExtensions = null) + public OsuFileSelector(string? initialPath = null, string[]? validFileExtensions = null) + : base(initialPath, validFileExtensions) { } @@ -69,7 +68,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 protected override DirectorySelectorDirectory CreateParentDirectoryItem(DirectoryInfo directory) => new OsuDirectorySelectorParentDirectory(directory); - protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuDirectorySelectorDirectory(directory, displayName); + protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string? displayName = null) => new OsuDirectorySelectorDirectory(directory, displayName); protected override DirectoryListingFile CreateFileItem(FileInfo file) => new OsuDirectoryListingFile(file); From 75dc822540c882ad2cfa867cba7ec687d3f3d814 Mon Sep 17 00:00:00 2001 From: StanR Date: Tue, 24 Sep 2024 17:57:31 +0500 Subject: [PATCH 288/924] Adjust some multipliers --- .../Difficulty/Evaluators/RhythmEvaluator.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index abf9dbe725..7bda1c29a3 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -13,9 +13,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators public static class RhythmEvaluator { private const int history_time_max = 4 * 1000; // 4 seconds - private const int history_objects_max = 24; - private const double rhythm_overall_multiplier = 0.95; - private const double rhythm_ratio_multiplier = 14.0; + private const int history_objects_max = 32; + private const double rhythm_overall_multiplier = 0.9; + private const double rhythm_ratio_multiplier = 11.5; /// /// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current . @@ -78,15 +78,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators double effectiveRatio = windowPenalty * currRatio * fractionMultiplier; - // bpm change is into slider, this is easy acc window - if (currObj.BaseObject is Slider) - effectiveRatio *= 0.125; - - // bpm change was from a slider, this is easier typically than circle -> circle - // unintentional side effect is that bursts with kicksliders at the ends might have lower difficulty than bursts without sliders - if (prevObj.BaseObject is Slider) - effectiveRatio *= 0.2; - if (firstDeltaSwitch) { if (Math.Abs(prevDelta - currDelta) < deltaDifferenceEpsilon) @@ -96,6 +87,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators } else { + // bpm change is into slider, this is easy acc window + if (currObj.BaseObject is Slider) + effectiveRatio *= 0.125; + + // bpm change was from a slider, this is easier typically than circle -> circle + // unintentional side effect is that bursts with kicksliders at the ends might have lower difficulty than bursts without sliders + if (prevObj.BaseObject is Slider) + effectiveRatio *= 0.15; + // repeated island polarity (2 -> 4, 3 -> 5) if (island.IsSimilarPolarity(previousIsland)) effectiveRatio *= 0.3; @@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators islandCount.Count++; // repeated island (ex: triplet -> triplet) - double power = logistic(island.Delta, 4, 0.165, 10); + double power = logistic(island.Delta, 2, 0.165, 10); effectiveRatio *= Math.Min(3.0 / islandCount.Count, Math.Pow(1.0 / islandCount.Count, power)); islandCounts[countIndex] = (islandCount.Island, islandCount.Count); From 9f4e48dde78eb1b76c6131999aa21aeb4dc42843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 24 Sep 2024 15:15:28 +0200 Subject: [PATCH 289/924] Actually use bindables rather than stick things in `Update()` --- osu.Game/Screens/Play/HUD/ArgonSongProgress.cs | 2 +- osu.Game/Screens/Play/HUD/DefaultSongProgress.cs | 3 +-- osu.Game/Skinning/Components/BoxElement.cs | 8 +++++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs index 1a18466743..92ac863e98 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs @@ -98,6 +98,7 @@ namespace osu.Game.Screens.Play.HUD Interactive.BindValueChanged(_ => bar.Interactive = Interactive.Value, true); ShowGraph.BindValueChanged(_ => updateGraphVisibility(), true); ShowTime.BindValueChanged(_ => info.FadeTo(ShowTime.Value ? 1 : 0, 200, Easing.In), true); + AccentColour.BindValueChanged(_ => Colour = AccentColour.Value, true); } protected override void UpdateObjects(IEnumerable objects) @@ -118,7 +119,6 @@ namespace osu.Game.Screens.Play.HUD base.Update(); content.Height = bar.Height + bar_height + info.Height; graphContainer.Height = bar.Height; - Colour = AccentColour.Value; } protected override void UpdateProgress(double progress, bool isIntro) diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index 93d75a22ba..4e41901ee3 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -90,6 +90,7 @@ namespace osu.Game.Screens.Play.HUD Interactive.BindValueChanged(_ => updateBarVisibility(), true); ShowGraph.BindValueChanged(_ => updateGraphVisibility(), true); ShowTime.BindValueChanged(_ => updateTimeVisibility(), true); + AccentColour.BindValueChanged(_ => Colour = AccentColour.Value, true); base.LoadComplete(); } @@ -118,8 +119,6 @@ namespace osu.Game.Screens.Play.HUD if (!Precision.AlmostEquals(Height, newHeight, 5f)) content.Height = newHeight; - - Colour = AccentColour.Value; } private void updateBarVisibility() diff --git a/osu.Game/Skinning/Components/BoxElement.cs b/osu.Game/Skinning/Components/BoxElement.cs index 633fb0c327..7f052a8523 100644 --- a/osu.Game/Skinning/Components/BoxElement.cs +++ b/osu.Game/Skinning/Components/BoxElement.cs @@ -46,12 +46,18 @@ namespace osu.Game.Skinning.Components Masking = true; } + protected override void LoadComplete() + { + base.LoadComplete(); + + AccentColour.BindValueChanged(_ => Colour = AccentColour.Value, true); + } + protected override void Update() { base.Update(); base.CornerRadius = CornerRadius.Value * Math.Min(DrawWidth, DrawHeight); - Colour = AccentColour.Value; } } } From 872628b8b87c73d21579a2ffd7aa26058cd6c551 Mon Sep 17 00:00:00 2001 From: StanR Date: Tue, 24 Sep 2024 18:24:00 +0500 Subject: [PATCH 290/924] Some extra tweaking --- .../Difficulty/Evaluators/RhythmEvaluator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index 7bda1c29a3..edb67f37d9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators { private const int history_time_max = 4 * 1000; // 4 seconds private const int history_objects_max = 32; - private const double rhythm_overall_multiplier = 0.9; + private const double rhythm_overall_multiplier = 0.92; private const double rhythm_ratio_multiplier = 11.5; /// @@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators islandCount.Count++; // repeated island (ex: triplet -> triplet) - double power = logistic(island.Delta, 2, 0.165, 10); + double power = logistic(island.Delta, 2.75, 0.24, 14); effectiveRatio *= Math.Min(3.0 / islandCount.Count, Math.Pow(1.0 / islandCount.Count, power)); islandCounts[countIndex] = (islandCount.Island, islandCount.Count); From 99a80b399cbeb1ac3a95bbec1a1ca2639f920caf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Tue, 24 Sep 2024 16:42:37 +0200 Subject: [PATCH 291/924] Animate SelectionBox buttons on unfreeze --- .../Edit/Compose/Components/SelectionBox.cs | 55 +++++++++++++++---- 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 4eae2b77f6..d685fe74b0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -370,10 +371,14 @@ namespace osu.Game.Screens.Edit.Compose.Components private void unfreezeButtonPosition() { - frozenButtonsPosition = null; + if (frozenButtonsPosition != null) + { + frozenButtonsPosition = null; + ensureButtonsOnScreen(true); + } } - private void ensureButtonsOnScreen() + private void ensureButtonsOnScreen(bool animated = false) { if (frozenButtonsPosition != null) { @@ -384,7 +389,8 @@ namespace osu.Game.Screens.Edit.Compose.Components return; } - buttons.Position = Vector2.Zero; + if (!animated && buttons.Transforms.Any()) + return; var thisQuad = ScreenSpaceDrawQuad; @@ -399,24 +405,51 @@ namespace osu.Game.Screens.Edit.Compose.Components float minHeight = buttons.ScreenSpaceDrawQuad.Height; + Anchor targetAnchor; + Anchor targetOrigin; + Vector2 targetPosition = Vector2.Zero; + if (topExcess < minHeight && bottomExcess < minHeight) { - buttons.Anchor = Anchor.BottomCentre; - buttons.Origin = Anchor.BottomCentre; - buttons.Y = Math.Min(0, ToLocalSpace(Parent!.ScreenSpaceDrawQuad.BottomLeft).Y - DrawHeight); + targetAnchor = Anchor.BottomCentre; + targetOrigin = Anchor.BottomCentre; + targetPosition.Y = Math.Min(0, ToLocalSpace(Parent!.ScreenSpaceDrawQuad.BottomLeft).Y - DrawHeight); } else if (topExcess > bottomExcess) { - buttons.Anchor = Anchor.TopCentre; - buttons.Origin = Anchor.BottomCentre; + targetAnchor = Anchor.TopCentre; + targetOrigin = Anchor.BottomCentre; } else { - buttons.Anchor = Anchor.BottomCentre; - buttons.Origin = Anchor.TopCentre; + targetAnchor = Anchor.BottomCentre; + targetOrigin = Anchor.TopCentre; } - buttons.X += ToLocalSpace(thisQuad.TopLeft - new Vector2(Math.Min(0, leftExcess)) + new Vector2(Math.Min(0, rightExcess))).X; + targetPosition.X += ToLocalSpace(thisQuad.TopLeft - new Vector2(Math.Min(0, leftExcess)) + new Vector2(Math.Min(0, rightExcess))).X; + + if (animated) + { + var originalPosition = ToLocalSpace(buttons.ScreenSpaceDrawQuad.TopLeft); + + buttons.Origin = targetOrigin; + buttons.Anchor = targetAnchor; + buttons.Position = targetPosition; + + var newPosition = ToLocalSpace(buttons.ScreenSpaceDrawQuad.TopLeft); + + var delta = newPosition - originalPosition; + + buttons.Position -= delta; + + buttons.MoveTo(targetPosition, 300, Easing.OutQuint); + } + else + { + buttons.Anchor = targetAnchor; + buttons.Origin = targetOrigin; + buttons.Position = targetPosition; + } } } } From 555d4ffe897e9a55c674064116b51a9bd08f106d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 24 Sep 2024 17:51:54 +0200 Subject: [PATCH 292/924] Add failing test case --- .../Ranking/TestSceneStatisticsPanel.cs | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index acfa519c81..f46f76cbb8 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -5,14 +5,18 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using NUnit.Framework; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; @@ -23,6 +27,7 @@ using osu.Game.Screens.Ranking.Statistics; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Ranking.Statistics.User; using osu.Game.Tests.Resources; using osu.Game.Users; using osuTK; @@ -80,6 +85,69 @@ namespace osu.Game.Tests.Visual.Ranking loadPanel(null); } + [Test] + public void TestStatisticsShownCorrectlyIfUpdateDeliveredBeforeLoad() + { + UserStatisticsWatcher userStatisticsWatcher = null!; + ScoreInfo score = null!; + + AddStep("create user statistics watcher", () => Add(userStatisticsWatcher = new UserStatisticsWatcher())); + AddStep("set user statistics update", () => + { + score = TestResources.CreateTestScoreInfo(); + score.OnlineID = 1234; + ((Bindable)userStatisticsWatcher.LatestUpdate).Value = new UserStatisticsUpdate(score, + new UserStatistics + { + Level = new UserStatistics.LevelInfo + { + Current = 5, + Progress = 20, + }, + GlobalRank = 38000, + CountryRank = 12006, + PP = 2134, + RankedScore = 21123849, + Accuracy = 0.985, + PlayCount = 13375, + PlayTime = 354490, + TotalScore = 128749597, + TotalHits = 0, + MaxCombo = 1233, + }, new UserStatistics + { + Level = new UserStatistics.LevelInfo + { + Current = 5, + Progress = 30, + }, + GlobalRank = 36000, + CountryRank = 12000, + PP = (decimal)2134.5, + RankedScore = 23897015, + Accuracy = 0.984, + PlayCount = 13376, + PlayTime = 35789, + TotalScore = 132218497, + TotalHits = 0, + MaxCombo = 1233, + }); + }); + AddStep("load user statistics panel", () => Child = new DependencyProvidingContainer + { + CachedDependencies = [(typeof(UserStatisticsWatcher), userStatisticsWatcher)], + RelativeSizeAxes = Axes.Both, + Child = new UserStatisticsPanel(score) + { + RelativeSizeAxes = Axes.Both, + State = { Value = Visibility.Visible }, + Score = { Value = score, } + } + }); + AddUntilStep("overall ranking present", () => this.ChildrenOfType().Any()); + AddUntilStep("loading spinner not visible", () => this.ChildrenOfType().All(l => l.State.Value == Visibility.Hidden)); + } + private void loadPanel(ScoreInfo score) => AddStep("load panel", () => { Child = new UserStatisticsPanel(score) From 20e7ade3b0a48f670da7f6f1d4ddedda1f2ddc44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 24 Sep 2024 17:52:19 +0200 Subject: [PATCH 293/924] Fix statistics update not being shown on results screen if it arrives too fast As reported in https://discord.com/channels/188630481301012481/1097318920991559880/1288160137286258799. --- osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs index fa3bb1a375..4e9c07ab7b 100644 --- a/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Ranking.Statistics { if (update.NewValue?.Score.MatchesOnlineID(achievedScore) == true) DisplayedUserStatisticsUpdate.Value = update.NewValue; - }); + }, true); } } From 2d95c0b0bbaf6d97c940414f2e6afc61aa15e656 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 24 Sep 2024 18:45:52 +0200 Subject: [PATCH 294/924] remove tail recursion form welzl --- osu.Game/Utils/GeometryUtils.cs | 54 ++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index d4968749bf..877f58769b 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -305,33 +305,37 @@ namespace osu.Game.Utils // n represents the number of points in P that are not yet processed. private static (Vector2, float) welzlHelper(List points, ReadOnlySpan r, int n) { - // Base case when all points processed or |R| = 3 - if (n == 0 || r.Length == 3) - return minCircleTrivial(r); - - // Pick a random point randomly - int idx = RNG.Next(n); - Vector2 p = points[idx]; - - // Put the picked point at the end of P since it's more efficient than - // deleting from the middle of the list - (points[idx], points[n - 1]) = (points[n - 1], points[idx]); - - // Get the MEC circle d from the set of points P - {p} - var d = welzlHelper(points, r, n - 1); - - // If d contains p, return d - if (isInside(d, p)) - return d; - - // Otherwise, must be on the boundary of the MEC - // Stackalloc to avoid allocations. It's safe to assume that the length of r will be at most 3 - Span r2 = stackalloc Vector2[r.Length + 1]; + Span r2 = stackalloc Vector2[3]; + int rLength = r.Length; r.CopyTo(r2); - r2[r.Length] = p; - // Return the MEC for P - {p} and R U {p} - return welzlHelper(points, r2, n - 1); + while (true) + { + // Base case when all points processed or |R| = 3 + if (n == 0 || rLength == 3) return minCircleTrivial(r2[..rLength]); + + // Pick a random point randomly + int idx = RNG.Next(n); + Vector2 p = points[idx]; + + // Put the picked point at the end of P since it's more efficient than + // deleting from the middle of the list + (points[idx], points[n - 1]) = (points[n - 1], points[idx]); + + // Get the MEC circle d from the set of points P - {p} + var d = welzlHelper(points, r2[..rLength], n - 1); + + // If d contains p, return d + if (isInside(d, p)) return d; + + // Otherwise, must be on the boundary of the MEC + // Stackalloc to avoid allocations. It's safe to assume that the length of r will be at most 3 + r2[rLength] = p; + rLength++; + + // Return the MEC for P - {p} and R U {p} + n--; + } } #endregion From 796fc948e138f839b083826fb69e6130e303c0c2 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 24 Sep 2024 20:15:03 +0200 Subject: [PATCH 295/924] Rewrite Welzl's algorithm to use no recursion --- osu.Game/Utils/GeometryUtils.cs | 104 ++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 45 deletions(-) diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index 877f58769b..e9e79deb49 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -255,7 +255,7 @@ namespace osu.Game.Utils } // Function to check whether a circle encloses the given points - private static bool isValidCircle((Vector2, float) c, ReadOnlySpan points) + private static bool isValidCircle((Vector2, float) c, List points) { // Iterating through all the points to check whether the points lie inside the circle or not foreach (Vector2 p in points) @@ -267,12 +267,12 @@ namespace osu.Game.Utils } // Function to return the minimum enclosing circle for N <= 3 - private static (Vector2, float) minCircleTrivial(ReadOnlySpan points) + private static (Vector2, float) minCircleTrivial(List points) { - if (points.Length > 3) + if (points.Count > 3) throw new ArgumentException("Number of points must be at most 3", nameof(points)); - switch (points.Length) + switch (points.Count) { case 0: return (new Vector2(0, 0), 0); @@ -299,45 +299,6 @@ namespace osu.Game.Utils return circleFrom(points[0], points[1], points[2]); } - // Returns the MEC using Welzl's algorithm - // Takes a set of input points P and a set R - // points on the circle boundary. - // n represents the number of points in P that are not yet processed. - private static (Vector2, float) welzlHelper(List points, ReadOnlySpan r, int n) - { - Span r2 = stackalloc Vector2[3]; - int rLength = r.Length; - r.CopyTo(r2); - - while (true) - { - // Base case when all points processed or |R| = 3 - if (n == 0 || rLength == 3) return minCircleTrivial(r2[..rLength]); - - // Pick a random point randomly - int idx = RNG.Next(n); - Vector2 p = points[idx]; - - // Put the picked point at the end of P since it's more efficient than - // deleting from the middle of the list - (points[idx], points[n - 1]) = (points[n - 1], points[idx]); - - // Get the MEC circle d from the set of points P - {p} - var d = welzlHelper(points, r2[..rLength], n - 1); - - // If d contains p, return d - if (isInside(d, p)) return d; - - // Otherwise, must be on the boundary of the MEC - // Stackalloc to avoid allocations. It's safe to assume that the length of r will be at most 3 - r2[rLength] = p; - rLength++; - - // Return the MEC for P - {p} and R U {p} - n--; - } - } - #endregion /// @@ -348,8 +309,61 @@ namespace osu.Game.Utils { // Using Welzl's algorithm to find the minimum enclosing circle // https://www.geeksforgeeks.org/minimum-enclosing-circle-using-welzls-algorithm/ - List pCopy = points.ToList(); - return welzlHelper(pCopy, Array.Empty(), pCopy.Count); + List P = points.ToList(); + + var stack = new Stack<(Vector2?, int)>(); + var r = new List(3); + (Vector2, float) d = (Vector2.Zero, 0); + + stack.Push((null, P.Count)); + + while (stack.Count > 0) + { + // n represents the number of points in P that are not yet processed. + // p represents the point that was randomly picked to process. + (Vector2? p, int n) = stack.Pop(); + + if (!p.HasValue) + { + // Base case when all points processed or |R| = 3 + if (n == 0 || r.Count == 3) + { + d = minCircleTrivial(r); + continue; + } + + // Pick a random point randomly + int idx = RNG.Next(n); + p = P[idx]; + + // Put the picked point at the end of P since it's more efficient than + // deleting from the middle of the list + (P[idx], P[n - 1]) = (P[n - 1], P[idx]); + + // Schedule processing of p after we get the MEC circle d from the set of points P - {p} + stack.Push((p, n)); + // Get the MEC circle d from the set of points P - {p} + stack.Push((null, n - 1)); + } + else + { + // If d contains p, return d + if (isInside(d, p.Value)) + continue; + + // Remove points from R that were added in a deeper recursion + // |R| = |P| - |stack| - n + int removeCount = r.Count - (P.Count - stack.Count - n); + r.RemoveRange(r.Count - removeCount, removeCount); + + // Otherwise, must be on the boundary of the MEC + r.Add(p.Value); + // Return the MEC for P - {p} and R U {p} + stack.Push((null, n - 1)); + } + } + + return d; } /// From e3b4483872ab71fcc1e700dd1195294f650e5c1b Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 23 Sep 2024 16:33:36 +0200 Subject: [PATCH 296/924] Refactor PlacementBlueprint to not be hitobject specific --- .../CatchPlacementBlueprintTestScene.cs | 2 +- ...TestSceneBananaShowerPlacementBlueprint.cs | 4 +- .../TestSceneFruitPlacementBlueprint.cs | 2 +- .../TestSceneJuiceStreamPlacementBlueprint.cs | 2 +- .../Edit/BananaShowerCompositionTool.cs | 4 +- .../Blueprints/CatchPlacementBlueprint.cs | 2 +- .../Edit/CatchHitObjectComposer.cs | 4 +- .../Edit/FruitCompositionTool.cs | 4 +- .../Edit/JuiceStreamCompositionTool.cs | 4 +- .../ManiaPlacementBlueprintTestScene.cs | 2 +- .../TestSceneHoldNotePlacementBlueprint.cs | 2 +- .../Editor/TestSceneNotePlacementBlueprint.cs | 2 +- .../Blueprints/ManiaPlacementBlueprint.cs | 2 +- .../Edit/HoldNoteCompositionTool.cs | 4 +- .../Edit/ManiaHitObjectComposer.cs | 2 +- .../Edit/NoteCompositionTool.cs | 4 +- .../TestSceneHitCirclePlacementBlueprint.cs | 2 +- .../TestSceneSliderPlacementBlueprint.cs | 2 +- .../TestSceneSpinnerPlacementBlueprint.cs | 2 +- .../HitCircles/HitCirclePlacementBlueprint.cs | 2 +- .../Sliders/SliderPlacementBlueprint.cs | 2 +- .../Spinners/SpinnerPlacementBlueprint.cs | 2 +- .../Edit/HitCircleCompositionTool.cs | 4 +- .../Edit/OsuHitObjectComposer.cs | 2 +- .../Edit/SliderCompositionTool.cs | 4 +- .../Edit/SpinnerCompositionTool.cs | 4 +- .../Edit/Blueprints/HitPlacementBlueprint.cs | 2 +- .../Blueprints/TaikoSpanPlacementBlueprint.cs | 2 +- .../Edit/DrumRollCompositionTool.cs | 4 +- .../Edit/HitCompositionTool.cs | 4 +- .../Edit/SwellCompositionTool.cs | 4 +- .../Edit/TaikoHitObjectComposer.cs | 2 +- .../Editing/TestScenePlacementBlueprint.cs | 2 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 4 +- .../Edit/HitObjectCompositionToolButton.cs | 4 +- .../Edit/HitObjectPlacementBlueprint.cs | 126 ++++++++++++++++++ osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 122 +++-------------- ...tCompositionTool.cs => CompositionTool.cs} | 4 +- osu.Game/Rulesets/Edit/Tools/SelectTool.cs | 4 +- .../Components/ComposeBlueprintContainer.cs | 22 +-- .../Visual/PlacementBlueprintTestScene.cs | 8 +- 41 files changed, 212 insertions(+), 174 deletions(-) create mode 100644 osu.Game/Rulesets/Edit/HitObjectPlacementBlueprint.cs rename osu.Game/Rulesets/Edit/Tools/{HitObjectCompositionTool.cs => CompositionTool.cs} (84%) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs index aae759d934..0578010c25 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor contentContainer.Playfield.HitObjectContainer.Add(hitObject); } - protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint) + protected override SnapResult SnapForBlueprint(HitObjectPlacementBlueprint blueprint) { var result = base.SnapForBlueprint(blueprint); result.Time = Math.Round(HitObjectContainer.TimeAtScreenSpacePosition(result.ScreenSpacePosition) / TIME_SNAP) * TIME_SNAP; diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs index ed37ff4ef3..badd8e967d 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor { protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableBananaShower((BananaShower)hitObject); - protected override PlacementBlueprint CreateBlueprint() => new BananaShowerPlacementBlueprint(); + protected override HitObjectPlacementBlueprint CreateBlueprint() => new BananaShowerPlacementBlueprint(); protected override void AddHitObject(DrawableHitObject hitObject) { @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor AddClickStep(MouseButton.Left); AddClickStep(MouseButton.Right); AddAssert("banana shower is not placed", () => LastObject == null); - AddAssert("state is waiting", () => CurrentBlueprint?.PlacementActive == PlacementBlueprint.PlacementState.Waiting); + AddAssert("state is waiting", () => CurrentBlueprint?.PlacementActive == HitObjectPlacementBlueprint.PlacementState.Waiting); } [Test] diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs index 75d3c3753a..80cd948e26 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor { protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableFruit((Fruit)hitObject); - protected override PlacementBlueprint CreateBlueprint() => new FruitPlacementBlueprint(); + protected override HitObjectPlacementBlueprint CreateBlueprint() => new FruitPlacementBlueprint(); [Test] public void TestFruitPlacementPosition() diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs index d010bb02ad..8bd60078c6 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableJuiceStream((JuiceStream)hitObject); - protected override PlacementBlueprint CreateBlueprint() => new JuiceStreamPlacementBlueprint(); + protected override HitObjectPlacementBlueprint CreateBlueprint() => new JuiceStreamPlacementBlueprint(); private void addMoveAndClickSteps(double time, float position, bool end = false) { diff --git a/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs b/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs index 31075db7d1..be93ba0242 100644 --- a/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs +++ b/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Edit.Tools; namespace osu.Game.Rulesets.Catch.Edit { - public class BananaShowerCompositionTool : HitObjectCompositionTool + public class BananaShowerCompositionTool : CompositionTool { public BananaShowerCompositionTool() : base(nameof(BananaShower)) @@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Catch.Edit public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners); - public override PlacementBlueprint CreatePlacementBlueprint() => new BananaShowerPlacementBlueprint(); + public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new BananaShowerPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs index 1a2990e4ac..aa862375c5 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Catch.Edit.Blueprints { - public partial class CatchPlacementBlueprint : PlacementBlueprint + public partial class CatchPlacementBlueprint : HitObjectPlacementBlueprint where THitObject : CatchHitObject, new() { protected new THitObject HitObject => (THitObject)base.HitObject; diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index 83f48816f9..8460e238f6 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Catch.Edit protected override BeatSnapGrid CreateBeatSnapGrid() => new CatchBeatSnapGrid(); - protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] + protected override IReadOnlyList CompositionTools => new CompositionTool[] { new FruitCompositionTool(), new JuiceStreamCompositionTool(), @@ -168,7 +168,7 @@ namespace osu.Game.Rulesets.Catch.Edit if (EditorBeatmap.PlacementObject.Value is JuiceStream) { // Juice stream path is not subject to snapping. - if (BlueprintContainer.CurrentPlacement.PlacementActive is PlacementBlueprint.PlacementState.Active) + if (BlueprintContainer.CurrentPlacement.PlacementActive is HitObjectPlacementBlueprint.PlacementState.Active) return null; } diff --git a/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs b/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs index f776fe39c1..71c1e66903 100644 --- a/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs +++ b/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Edit.Tools; namespace osu.Game.Rulesets.Catch.Edit { - public class FruitCompositionTool : HitObjectCompositionTool + public class FruitCompositionTool : CompositionTool { public FruitCompositionTool() : base(nameof(Fruit)) @@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Catch.Edit public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles); - public override PlacementBlueprint CreatePlacementBlueprint() => new FruitPlacementBlueprint(); + public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new FruitPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Catch/Edit/JuiceStreamCompositionTool.cs b/osu.Game.Rulesets.Catch/Edit/JuiceStreamCompositionTool.cs index cb66e2952e..7a567820f3 100644 --- a/osu.Game.Rulesets.Catch/Edit/JuiceStreamCompositionTool.cs +++ b/osu.Game.Rulesets.Catch/Edit/JuiceStreamCompositionTool.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Edit.Tools; namespace osu.Game.Rulesets.Catch.Edit { - public class JuiceStreamCompositionTool : HitObjectCompositionTool + public class JuiceStreamCompositionTool : CompositionTool { public JuiceStreamCompositionTool() : base(nameof(JuiceStream)) @@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Catch.Edit public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders); - public override PlacementBlueprint CreatePlacementBlueprint() => new JuiceStreamPlacementBlueprint(); + public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new JuiceStreamPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs index d2a44122aa..5e633c3161 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor }); } - protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint) + protected override SnapResult SnapForBlueprint(HitObjectPlacementBlueprint blueprint) { double time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position); var pos = column.ScreenSpacePositionAtTime(time); diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs index b79bcb7682..2006879ab3 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs @@ -13,6 +13,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor public partial class TestSceneHoldNotePlacementBlueprint : ManiaPlacementBlueprintTestScene { protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHoldNote((HoldNote)hitObject); - protected override PlacementBlueprint CreateBlueprint() => new HoldNotePlacementBlueprint(); + protected override HitObjectPlacementBlueprint CreateBlueprint() => new HoldNotePlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs index a446f13cbf..0cb9639cd1 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs @@ -64,6 +64,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor private Note getNote() => this.ChildrenOfType().FirstOrDefault()?.HitObject; protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableNote((Note)hitObject); - protected override PlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint(); + protected override HitObjectPlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index 5e0512b5dc..a68bd5d6d6 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -15,7 +15,7 @@ using osuTK.Input; namespace osu.Game.Rulesets.Mania.Edit.Blueprints { - public abstract partial class ManiaPlacementBlueprint : PlacementBlueprint + public abstract partial class ManiaPlacementBlueprint : HitObjectPlacementBlueprint where T : ManiaHitObject { protected new T HitObject => (T)base.HitObject; diff --git a/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs b/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs index 99e1ce04b1..592f8d9af7 100644 --- a/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs +++ b/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets.Mania.Edit.Blueprints; namespace osu.Game.Rulesets.Mania.Edit { - public class HoldNoteCompositionTool : HitObjectCompositionTool + public class HoldNoteCompositionTool : CompositionTool { public HoldNoteCompositionTool() : base("Hold") @@ -18,6 +18,6 @@ namespace osu.Game.Rulesets.Mania.Edit public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders); - public override PlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint(); + public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 02a4f3a022..e3b4fa2fb7 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Mania.Edit protected override BeatSnapGrid CreateBeatSnapGrid() => new ManiaBeatSnapGrid(); - protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] + protected override IReadOnlyList CompositionTools => new CompositionTool[] { new NoteCompositionTool(), new HoldNoteCompositionTool() diff --git a/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs b/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs index 08ee05ad3f..2e54d63525 100644 --- a/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs +++ b/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Mania.Objects; namespace osu.Game.Rulesets.Mania.Edit { - public class NoteCompositionTool : HitObjectCompositionTool + public class NoteCompositionTool : CompositionTool { public NoteCompositionTool() : base(nameof(Note)) @@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Mania.Edit public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles); - public override PlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint(); + public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCirclePlacementBlueprint.cs index a49afd82f3..a105d860bf 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCirclePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCirclePlacementBlueprint.cs @@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor public partial class TestSceneHitCirclePlacementBlueprint : PlacementBlueprintTestScene { protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHitCircle((HitCircle)hitObject); - protected override PlacementBlueprint CreateBlueprint() => new HitCirclePlacementBlueprint(); + protected override HitObjectPlacementBlueprint CreateBlueprint() => new HitCirclePlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index aa6a6f08d8..019565ae29 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -514,6 +514,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private Slider? getSlider() => HitObjectContainer.Count > 0 ? ((DrawableSlider)HitObjectContainer[0]).HitObject : null; protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSlider((Slider)hitObject); - protected override PlacementBlueprint CreateBlueprint() => new SliderPlacementBlueprint(); + protected override HitObjectPlacementBlueprint CreateBlueprint() => new SliderPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerPlacementBlueprint.cs index 0e8673319e..d7b5cc73be 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerPlacementBlueprint.cs @@ -15,6 +15,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSpinner((Spinner)hitObject); - protected override PlacementBlueprint CreateBlueprint() => new SpinnerPlacementBlueprint(); + protected override HitObjectPlacementBlueprint CreateBlueprint() => new SpinnerPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs index 20ad99baa2..78a0e36dc2 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs @@ -9,7 +9,7 @@ using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles { - public partial class HitCirclePlacementBlueprint : PlacementBlueprint + public partial class HitCirclePlacementBlueprint : HitObjectPlacementBlueprint { public new HitCircle HitObject => (HitCircle)base.HitObject; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 42945295b8..6ffe27dc13 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -21,7 +21,7 @@ using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { - public partial class SliderPlacementBlueprint : PlacementBlueprint + public partial class SliderPlacementBlueprint : HitObjectPlacementBlueprint { public new Slider HitObject => (Slider)base.HitObject; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs index f59be0e0e9..17d2dcd75c 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs @@ -13,7 +13,7 @@ using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners { - public partial class SpinnerPlacementBlueprint : PlacementBlueprint + public partial class SpinnerPlacementBlueprint : HitObjectPlacementBlueprint { public new Spinner HitObject => (Spinner)base.HitObject; diff --git a/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs b/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs index c41ae10b2e..d3116ede30 100644 --- a/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs +++ b/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Edit { - public class HitCircleCompositionTool : HitObjectCompositionTool + public class HitCircleCompositionTool : CompositionTool { public HitCircleCompositionTool() : base(nameof(HitCircle)) @@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Osu.Edit public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles); - public override PlacementBlueprint CreatePlacementBlueprint() => new HitCirclePlacementBlueprint(); + public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new HitCirclePlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 8fc2a9b7d3..4368a338b2 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Edit protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) => new DrawableOsuEditorRuleset(ruleset, beatmap, mods); - protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] + protected override IReadOnlyList CompositionTools => new CompositionTool[] { new HitCircleCompositionTool(), new SliderCompositionTool(), diff --git a/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs b/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs index 617cc1c19b..d697a2ebe6 100644 --- a/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs +++ b/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Edit { - public class SliderCompositionTool : HitObjectCompositionTool + public class SliderCompositionTool : CompositionTool { public SliderCompositionTool() : base(nameof(Slider)) @@ -26,6 +26,6 @@ namespace osu.Game.Rulesets.Osu.Edit public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders); - public override PlacementBlueprint CreatePlacementBlueprint() => new SliderPlacementBlueprint(); + public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new SliderPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs b/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs index c8160617c9..de1506e4a9 100644 --- a/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs +++ b/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Edit { - public class SpinnerCompositionTool : HitObjectCompositionTool + public class SpinnerCompositionTool : CompositionTool { public SpinnerCompositionTool() : base(nameof(Spinner)) @@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Osu.Edit public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners); - public override PlacementBlueprint CreatePlacementBlueprint() => new SpinnerPlacementBlueprint(); + public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new SpinnerPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs index 329fff5b42..7f45123bd6 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs @@ -10,7 +10,7 @@ using osuTK.Input; namespace osu.Game.Rulesets.Taiko.Edit.Blueprints { - public partial class HitPlacementBlueprint : PlacementBlueprint + public partial class HitPlacementBlueprint : HitObjectPlacementBlueprint { private readonly HitPiece piece; diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs index cd52398086..de3a4d96eb 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs @@ -17,7 +17,7 @@ using osuTK.Input; namespace osu.Game.Rulesets.Taiko.Edit.Blueprints { - public partial class TaikoSpanPlacementBlueprint : PlacementBlueprint + public partial class TaikoSpanPlacementBlueprint : HitObjectPlacementBlueprint { private readonly HitPiece headPiece; private readonly HitPiece tailPiece; diff --git a/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs index f332441875..ba0fda6771 100644 --- a/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs +++ b/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Edit { - public class DrumRollCompositionTool : HitObjectCompositionTool + public class DrumRollCompositionTool : CompositionTool { public DrumRollCompositionTool() : base(nameof(DrumRoll)) @@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Taiko.Edit public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders); - public override PlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint(); + public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs index fa50841893..f58defba83 100644 --- a/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs +++ b/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Edit { - public class HitCompositionTool : HitObjectCompositionTool + public class HitCompositionTool : CompositionTool { public HitCompositionTool() : base(nameof(Hit)) @@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Taiko.Edit public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles); - public override PlacementBlueprint CreatePlacementBlueprint() => new HitPlacementBlueprint(); + public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new HitPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs index 4d4ee8effe..4ec623e29e 100644 --- a/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs +++ b/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Edit { - public class SwellCompositionTool : HitObjectCompositionTool + public class SwellCompositionTool : CompositionTool { public SwellCompositionTool() : base(nameof(Swell)) @@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Taiko.Edit public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners); - public override PlacementBlueprint CreatePlacementBlueprint() => new SwellPlacementBlueprint(); + public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new SwellPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs index 6020f6e04c..d97a854ff7 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Taiko.Edit { } - protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] + protected override IReadOnlyList CompositionTools => new CompositionTool[] { new HitCompositionTool(), new DrumRollCompositionTool(), diff --git a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs index fe74e1b346..6f3342f8ce 100644 --- a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs @@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("editor is still current", () => Editor.IsCurrentScreen()); AddAssert("slider not placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(0)); AddAssert("no active placement", () => this.ChildrenOfType().Single().CurrentPlacement.PlacementActive, - () => Is.EqualTo(PlacementBlueprint.PlacementState.Waiting)); + () => Is.EqualTo(HitObjectPlacementBlueprint.PlacementState.Waiting)); } [Test] diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index c2a7bec9f9..00de46b726 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -323,7 +323,7 @@ namespace osu.Game.Rulesets.Edit /// /// A "select" tool is automatically added as the first tool. /// - protected abstract IReadOnlyList CompositionTools { get; } + protected abstract IReadOnlyList CompositionTools { get; } /// /// A collection of states which will be displayed to the user in the toolbox. @@ -466,7 +466,7 @@ namespace osu.Game.Rulesets.Edit private void setSelectTool() => toolboxCollection.Items.First().Select(); - private void toolSelected(HitObjectCompositionTool tool) + private void toolSelected(CompositionTool tool) { BlueprintContainer.CurrentTool = tool; diff --git a/osu.Game/Rulesets/Edit/HitObjectCompositionToolButton.cs b/osu.Game/Rulesets/Edit/HitObjectCompositionToolButton.cs index ba566ff5c0..641d60dbd3 100644 --- a/osu.Game/Rulesets/Edit/HitObjectCompositionToolButton.cs +++ b/osu.Game/Rulesets/Edit/HitObjectCompositionToolButton.cs @@ -9,9 +9,9 @@ namespace osu.Game.Rulesets.Edit { public class HitObjectCompositionToolButton : RadioButton { - public HitObjectCompositionTool Tool { get; } + public CompositionTool Tool { get; } - public HitObjectCompositionToolButton(HitObjectCompositionTool tool, Action? action) + public HitObjectCompositionToolButton(CompositionTool tool, Action? action) : base(tool.Name, action, tool.CreateIcon) { Tool = tool; diff --git a/osu.Game/Rulesets/Edit/HitObjectPlacementBlueprint.cs b/osu.Game/Rulesets/Edit/HitObjectPlacementBlueprint.cs new file mode 100644 index 0000000000..74025b4260 --- /dev/null +++ b/osu.Game/Rulesets/Edit/HitObjectPlacementBlueprint.cs @@ -0,0 +1,126 @@ +// 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 System.Threading; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Compose; + +namespace osu.Game.Rulesets.Edit +{ + /// + /// A blueprint which governs the creation of a new to actualisation. + /// + public abstract partial class HitObjectPlacementBlueprint : PlacementBlueprint + { + /// + /// Whether the sample bank should be taken from the previous hit object. + /// + public bool AutomaticBankAssignment { get; set; } + + /// + /// The that is being placed. + /// + public readonly HitObject HitObject; + + [Resolved] + protected EditorClock EditorClock { get; private set; } = null!; + + [Resolved] + private EditorBeatmap beatmap { get; set; } = null!; + + private Bindable startTimeBindable = null!; + + private HitObject? getPreviousHitObject() => beatmap.HitObjects.TakeWhile(h => h.StartTime <= startTimeBindable.Value).LastOrDefault(); + + [Resolved] + private IPlacementHandler placementHandler { get; set; } = null!; + + protected HitObjectPlacementBlueprint(HitObject hitObject) + { + HitObject = hitObject; + + // adding the default hit sample should be the case regardless of the ruleset. + HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_NORMAL)); + } + + [BackgroundDependencyLoader] + private void load() + { + startTimeBindable = HitObject.StartTimeBindable.GetBoundCopy(); + startTimeBindable.BindValueChanged(_ => ApplyDefaultsToHitObject(), true); + } + + protected override void BeginPlacement(bool commitStart = false) + { + base.BeginPlacement(commitStart); + + placementHandler.BeginPlacement(HitObject); + } + + public override void EndPlacement(bool commit) + { + base.EndPlacement(commit); + + placementHandler.EndPlacement(HitObject, IsValidForPlacement && commit); + } + + /// + /// Updates the time and position of this based on the provided snap information. + /// + /// The snap result information. + public override void UpdateTimeAndPosition(SnapResult result) + { + if (PlacementActive == PlacementState.Waiting) + { + HitObject.StartTime = result.Time ?? EditorClock.CurrentTime; + + if (HitObject is IHasComboInformation comboInformation) + comboInformation.UpdateComboInformation(getPreviousHitObject() as IHasComboInformation); + } + + var lastHitObject = getPreviousHitObject(); + + if (AutomaticBankAssignment) + { + // Create samples based on the sample settings of the previous hit object + if (lastHitObject != null) + { + for (int i = 0; i < HitObject.Samples.Count; i++) + HitObject.Samples[i] = lastHitObject.CreateHitSampleInfo(HitObject.Samples[i].Name); + } + } + else + { + var lastHitNormal = lastHitObject?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL); + + if (lastHitNormal != null) + { + // Only inherit the volume from the previous hit object + for (int i = 0; i < HitObject.Samples.Count; i++) + HitObject.Samples[i] = HitObject.Samples[i].With(newVolume: lastHitNormal.Volume); + } + } + + if (HitObject is IHasRepeats hasRepeats) + { + // Make sure all the node samples are identical to the hit object's samples + for (int i = 0; i < hasRepeats.NodeSamples.Count; i++) + hasRepeats.NodeSamples[i] = HitObject.Samples.Select(o => o.With()).ToList(); + } + } + + /// + /// Invokes , + /// refreshing and parameters for the . + /// + protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); + } +} diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index a50a7f4169..d2a54e8e03 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -1,29 +1,19 @@ // 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 System.Threading; -using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Game.Audio; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Compose; using osuTK; using osuTK.Input; namespace osu.Game.Rulesets.Edit { /// - /// A blueprint which governs the creation of a new to actualisation. + /// A blueprint which governs the placement of something. /// public abstract partial class PlacementBlueprint : CompositeDrawable, IKeyBindingHandler { @@ -32,29 +22,6 @@ namespace osu.Game.Rulesets.Edit /// public PlacementState PlacementActive { get; private set; } - /// - /// Whether the sample bank should be taken from the previous hit object. - /// - public bool AutomaticBankAssignment { get; set; } - - /// - /// The that is being placed. - /// - public readonly HitObject HitObject; - - [Resolved] - protected EditorClock EditorClock { get; private set; } = null!; - - [Resolved] - private EditorBeatmap beatmap { get; set; } = null!; - - private Bindable startTimeBindable = null!; - - private HitObject? getPreviousHitObject() => beatmap.HitObjects.TakeWhile(h => h.StartTime <= startTimeBindable.Value).LastOrDefault(); - - [Resolved] - private IPlacementHandler placementHandler { get; set; } = null!; - /// /// Whether this blueprint is currently in a state that can be committed. /// @@ -64,13 +31,8 @@ namespace osu.Game.Rulesets.Edit /// protected virtual bool IsValidForPlacement => true; - protected PlacementBlueprint(HitObject hitObject) + protected PlacementBlueprint() { - HitObject = hitObject; - - // adding the default hit sample should be the case regardless of the ruleset. - HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_NORMAL)); - RelativeSizeAxes = Axes.Both; // This is required to allow the blueprint's position to be updated via OnMouseMove/Handle @@ -78,30 +40,22 @@ namespace osu.Game.Rulesets.Edit AlwaysPresent = true; } - [BackgroundDependencyLoader] - private void load() - { - startTimeBindable = HitObject.StartTimeBindable.GetBoundCopy(); - startTimeBindable.BindValueChanged(_ => ApplyDefaultsToHitObject(), true); - } - /// - /// Signals that the placement of has started. + /// Signals that the placement has started. /// - /// Whether this call is committing a value for HitObject.StartTime and continuing with further adjustments. - protected void BeginPlacement(bool commitStart = false) + /// Whether this call is committing a value and continuing with further adjustments. + protected virtual void BeginPlacement(bool commitStart = false) { - placementHandler.BeginPlacement(HitObject); if (commitStart) PlacementActive = PlacementState.Active; } /// /// Signals that the placement of has finished. - /// This will destroy this , and add the HitObject.StartTime to the . + /// This will destroy this , and commit the changes. /// - /// Whether the object should be committed. Note that a commit may fail if is false. - public void EndPlacement(bool commit) + /// Whether the changes should be committed. Note that a commit may fail if is false. + public virtual void EndPlacement(bool commit) { switch (PlacementActive) { @@ -114,10 +68,17 @@ namespace osu.Game.Rulesets.Edit break; } - placementHandler.EndPlacement(HitObject, IsValidForPlacement && commit); PlacementActive = PlacementState.Finished; } + /// + /// Updates the time and position of this based on the provided snap information. + /// + /// The snap result information. + public virtual void UpdateTimeAndPosition(SnapResult result) + { + } + public bool OnPressed(KeyBindingPressEvent e) { if (PlacementActive == PlacementState.Waiting) @@ -138,57 +99,6 @@ namespace osu.Game.Rulesets.Edit { } - /// - /// Updates the time and position of this based on the provided snap information. - /// - /// The snap result information. - public virtual void UpdateTimeAndPosition(SnapResult result) - { - if (PlacementActive == PlacementState.Waiting) - { - HitObject.StartTime = result.Time ?? EditorClock.CurrentTime; - - if (HitObject is IHasComboInformation comboInformation) - comboInformation.UpdateComboInformation(getPreviousHitObject() as IHasComboInformation); - } - - var lastHitObject = getPreviousHitObject(); - - if (AutomaticBankAssignment) - { - // Create samples based on the sample settings of the previous hit object - if (lastHitObject != null) - { - for (int i = 0; i < HitObject.Samples.Count; i++) - HitObject.Samples[i] = lastHitObject.CreateHitSampleInfo(HitObject.Samples[i].Name); - } - } - else - { - var lastHitNormal = lastHitObject?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL); - - if (lastHitNormal != null) - { - // Only inherit the volume from the previous hit object - for (int i = 0; i < HitObject.Samples.Count; i++) - HitObject.Samples[i] = HitObject.Samples[i].With(newVolume: lastHitNormal.Volume); - } - } - - if (HitObject is IHasRepeats hasRepeats) - { - // Make sure all the node samples are identical to the hit object's samples - for (int i = 0; i < hasRepeats.NodeSamples.Count; i++) - hasRepeats.NodeSamples[i] = HitObject.Samples.Select(o => o.With()).ToList(); - } - } - - /// - /// Invokes , - /// refreshing and parameters for the . - /// - protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; protected override bool Handle(UIEvent e) diff --git a/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs b/osu.Game/Rulesets/Edit/Tools/CompositionTool.cs similarity index 84% rename from osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs rename to osu.Game/Rulesets/Edit/Tools/CompositionTool.cs index ba1dc817bb..f509302daa 100644 --- a/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs +++ b/osu.Game/Rulesets/Edit/Tools/CompositionTool.cs @@ -6,13 +6,13 @@ using osu.Framework.Localisation; namespace osu.Game.Rulesets.Edit.Tools { - public abstract class HitObjectCompositionTool + public abstract class CompositionTool { public readonly string Name; public LocalisableString TooltipText { get; init; } - protected HitObjectCompositionTool(string name) + protected CompositionTool(string name) { Name = name; } diff --git a/osu.Game/Rulesets/Edit/Tools/SelectTool.cs b/osu.Game/Rulesets/Edit/Tools/SelectTool.cs index a272e9f480..7f8889bfca 100644 --- a/osu.Game/Rulesets/Edit/Tools/SelectTool.cs +++ b/osu.Game/Rulesets/Edit/Tools/SelectTool.cs @@ -9,7 +9,7 @@ using osu.Game.Graphics; namespace osu.Game.Rulesets.Edit.Tools { - public class SelectTool : HitObjectCompositionTool + public class SelectTool : CompositionTool { public SelectTool() : base("Select") @@ -18,6 +18,6 @@ namespace osu.Game.Rulesets.Edit.Tools public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorSelect }; - public override PlacementBlueprint CreatePlacementBlueprint() => null; + public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => null; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index f1294ccc3c..f0296d45aa 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -41,6 +41,8 @@ namespace osu.Game.Screens.Edit.Compose.Components public PlacementBlueprint CurrentPlacement { get; private set; } + public HitObjectPlacementBlueprint CurrentHitObjectPlacement => CurrentPlacement as HitObjectPlacementBlueprint; + [Resolved(canBeNull: true)] private EditorScreenWithTimeline editorScreen { get; set; } @@ -164,13 +166,13 @@ namespace osu.Game.Screens.Edit.Compose.Components private void updatePlacementNewCombo() { - if (CurrentPlacement?.HitObject is IHasComboInformation c) + if (CurrentHitObjectPlacement?.HitObject is IHasComboInformation c) c.NewCombo = NewCombo.Value == TernaryState.True; } private void updatePlacementSamples() { - if (CurrentPlacement == null) return; + if (CurrentHitObjectPlacement == null) return; foreach (var kvp in SelectionHandler.SelectionSampleStates) sampleChanged(kvp.Key, kvp.Value.Value); @@ -181,9 +183,9 @@ namespace osu.Game.Screens.Edit.Compose.Components private void sampleChanged(string sampleName, TernaryState state) { - if (CurrentPlacement == null) return; + if (CurrentHitObjectPlacement == null) return; - var samples = CurrentPlacement.HitObject.Samples; + var samples = CurrentHitObjectPlacement.HitObject.Samples; var existingSample = samples.FirstOrDefault(s => s.Name == sampleName); @@ -196,19 +198,19 @@ namespace osu.Game.Screens.Edit.Compose.Components case TernaryState.True: if (existingSample == null) - samples.Add(CurrentPlacement.HitObject.CreateHitSampleInfo(sampleName)); + samples.Add(CurrentHitObjectPlacement.HitObject.CreateHitSampleInfo(sampleName)); break; } } private void bankChanged(string bankName, TernaryState state) { - if (CurrentPlacement == null) return; + if (CurrentHitObjectPlacement == null) return; if (bankName == EditorSelectionHandler.HIT_BANK_AUTO) - CurrentPlacement.AutomaticBankAssignment = state == TernaryState.True; + CurrentHitObjectPlacement.AutomaticBankAssignment = state == TernaryState.True; else if (state == TernaryState.True) - CurrentPlacement.HitObject.Samples = CurrentPlacement.HitObject.Samples.Select(s => s.With(newBank: bankName)).ToList(); + CurrentHitObjectPlacement.HitObject.Samples = CurrentHitObjectPlacement.HitObject.Samples.Select(s => s.With(newBank: bankName)).ToList(); } public readonly Bindable NewCombo = new Bindable { Description = "New Combo" }; @@ -386,12 +388,12 @@ namespace osu.Game.Screens.Edit.Compose.Components CurrentPlacement = null; } - private HitObjectCompositionTool currentTool; + private CompositionTool currentTool; /// /// The current placement tool. /// - public HitObjectCompositionTool CurrentTool + public CompositionTool CurrentTool { get => currentTool; diff --git a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs index 0027e03492..c8d9ef8fc8 100644 --- a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual public abstract partial class PlacementBlueprintTestScene : OsuManualInputManagerTestScene, IPlacementHandler { protected readonly Container HitObjectContainer; - protected PlacementBlueprint CurrentBlueprint { get; private set; } + protected HitObjectPlacementBlueprint CurrentBlueprint { get; private set; } protected PlacementBlueprintTestScene() { @@ -87,14 +87,14 @@ namespace osu.Game.Tests.Visual CurrentBlueprint.UpdateTimeAndPosition(SnapForBlueprint(CurrentBlueprint)); } - protected virtual SnapResult SnapForBlueprint(PlacementBlueprint blueprint) => + protected virtual SnapResult SnapForBlueprint(HitObjectPlacementBlueprint blueprint) => new SnapResult(InputManager.CurrentState.Mouse.Position, null); public override void Add(Drawable drawable) { base.Add(drawable); - if (drawable is PlacementBlueprint blueprint) + if (drawable is HitObjectPlacementBlueprint blueprint) { blueprint.Show(); blueprint.UpdateTimeAndPosition(SnapForBlueprint(blueprint)); @@ -106,6 +106,6 @@ namespace osu.Game.Tests.Visual protected virtual Container CreateHitObjectContainer() => new Container { RelativeSizeAxes = Axes.Both }; protected abstract DrawableHitObject CreateHitObject(HitObject hitObject); - protected abstract PlacementBlueprint CreateBlueprint(); + protected abstract HitObjectPlacementBlueprint CreateBlueprint(); } } From d26e677bb7ceb9e35e7af22c77c9ccfe39672483 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 23 Sep 2024 17:34:45 +0200 Subject: [PATCH 297/924] fix warnings --- .../Editor/TestSceneBananaShowerPlacementBlueprint.cs | 2 +- osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs | 2 +- osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs index badd8e967d..296d34d628 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor AddClickStep(MouseButton.Left); AddClickStep(MouseButton.Right); AddAssert("banana shower is not placed", () => LastObject == null); - AddAssert("state is waiting", () => CurrentBlueprint?.PlacementActive == HitObjectPlacementBlueprint.PlacementState.Waiting); + AddAssert("state is waiting", () => CurrentBlueprint?.PlacementActive == PlacementBlueprint.PlacementState.Waiting); } [Test] diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index 8460e238f6..7323c7a91a 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -168,7 +168,7 @@ namespace osu.Game.Rulesets.Catch.Edit if (EditorBeatmap.PlacementObject.Value is JuiceStream) { // Juice stream path is not subject to snapping. - if (BlueprintContainer.CurrentPlacement.PlacementActive is HitObjectPlacementBlueprint.PlacementState.Active) + if (BlueprintContainer.CurrentPlacement.PlacementActive is PlacementBlueprint.PlacementState.Active) return null; } diff --git a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs index 6f3342f8ce..fe74e1b346 100644 --- a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs @@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("editor is still current", () => Editor.IsCurrentScreen()); AddAssert("slider not placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(0)); AddAssert("no active placement", () => this.ChildrenOfType().Single().CurrentPlacement.PlacementActive, - () => Is.EqualTo(HitObjectPlacementBlueprint.PlacementState.Waiting)); + () => Is.EqualTo(PlacementBlueprint.PlacementState.Waiting)); } [Test] From 3ab04d98f618df16362e5caf25c908df07012226 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 25 Sep 2024 16:45:37 +0900 Subject: [PATCH 298/924] Fix Realm-related iOS crashes by removing object references --- osu.Game/Beatmaps/BeatmapImporter.cs | 4 +++- osu.Game/Online/BeatmapDownloadTracker.cs | 4 +++- osu.Game/Online/ScoreDownloadTracker.cs | 10 +++++++--- osu.Game/Skinning/RealmBackedResourceStore.cs | 4 +++- osu.Game/Skinning/SkinManager.cs | 4 +++- 5 files changed, 19 insertions(+), 7 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 8acaebd1a8..63d8215d73 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -198,8 +198,10 @@ namespace osu.Game.Beatmaps if (beatmapSet.OnlineID > 0) { + int onlineId = beatmapSet.OnlineID; + // OnlineID should really be unique, but to avoid catastrophic failure let's iterate just to be sure. - foreach (var existingSetWithSameOnlineID in realm.All().Where(b => b.OnlineID == beatmapSet.OnlineID)) + foreach (var existingSetWithSameOnlineID in realm.All().Where(b => b.OnlineID == onlineId)) { existingSetWithSameOnlineID.DeletePending = true; existingSetWithSameOnlineID.OnlineID = -1; diff --git a/osu.Game/Online/BeatmapDownloadTracker.cs b/osu.Game/Online/BeatmapDownloadTracker.cs index 3db602c353..c1c3d17ff6 100644 --- a/osu.Game/Online/BeatmapDownloadTracker.cs +++ b/osu.Game/Online/BeatmapDownloadTracker.cs @@ -40,7 +40,9 @@ namespace osu.Game.Online // Used to interact with manager classes that don't support interface types. Will eventually be replaced. var beatmapSetInfo = new BeatmapSetInfo { OnlineID = TrackedItem.OnlineID }; - realmSubscription = realm.RegisterForNotifications(r => r.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, _) => + int onlineId = TrackedItem.OnlineID; + + realmSubscription = realm.RegisterForNotifications(r => r.All().Where(s => s.OnlineID == onlineId && !s.DeletePending), (items, _) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index dfdac24d19..eb687a7023 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -46,10 +46,14 @@ namespace osu.Game.Online Downloader.DownloadBegan += downloadBegan; Downloader.DownloadFailed += downloadFailed; + long onlineId = TrackedItem.OnlineID; + long legacyOnlineId = TrackedItem.LegacyOnlineID; + string hash = TrackedItem.Hash; + realmSubscription = realm.RegisterForNotifications(r => r.All().Where(s => - ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) - || (s.LegacyOnlineID > 0 && s.LegacyOnlineID == TrackedItem.LegacyOnlineID) - || (!string.IsNullOrEmpty(s.Hash) && s.Hash == TrackedItem.Hash)) + ((s.OnlineID > 0 && s.OnlineID == onlineId) + || (s.LegacyOnlineID > 0 && s.LegacyOnlineID == legacyOnlineId) + || (!string.IsNullOrEmpty(s.Hash) && s.Hash == hash)) && !s.DeletePending), (items, _) => { if (items.Any()) diff --git a/osu.Game/Skinning/RealmBackedResourceStore.cs b/osu.Game/Skinning/RealmBackedResourceStore.cs index cce099a268..b1289bd0c5 100644 --- a/osu.Game/Skinning/RealmBackedResourceStore.cs +++ b/osu.Game/Skinning/RealmBackedResourceStore.cs @@ -29,7 +29,9 @@ namespace osu.Game.Skinning invalidateCache(); Debug.Assert(fileToStoragePathMapping != null); - realmSubscription = realm?.RegisterForNotifications(r => r.All().Where(s => s.ID == source.ID), skinChanged); + Guid id = source.ID; + + realmSubscription = realm?.RegisterForNotifications(r => r.All().Where(s => s.ID == id), skinChanged); } protected override void Dispose(bool disposing) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 4f816d88d2..cd431bd80c 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -131,9 +131,11 @@ namespace osu.Game.Skinning { Realm.Run(r => { + Guid currentSkinId = CurrentSkinInfo.Value.ID; + // choose from only user skins, removing the current selection to ensure a new one is chosen. var randomChoices = r.All() - .Where(s => !s.DeletePending && s.ID != CurrentSkinInfo.Value.ID) + .Where(s => !s.DeletePending && s.ID != currentSkinId) .ToArray(); if (randomChoices.Length == 0) From fd4891cf31de0f5e75b937fdf168a17c16e65a3b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 25 Sep 2024 20:34:50 +0900 Subject: [PATCH 299/924] Fix similar Bindable-related crashes --- .../Utils/BindableValueAccessorTest.cs | 52 +++++++++++++++++++ .../Configuration/SettingSourceAttribute.cs | 7 +-- osu.Game/Rulesets/Mods/Mod.cs | 3 +- osu.Game/Utils/BindableValueAccessor.cs | 44 ++++++++++++++++ 4 files changed, 99 insertions(+), 7 deletions(-) create mode 100644 osu.Game.Tests/Utils/BindableValueAccessorTest.cs create mode 100644 osu.Game/Utils/BindableValueAccessor.cs diff --git a/osu.Game.Tests/Utils/BindableValueAccessorTest.cs b/osu.Game.Tests/Utils/BindableValueAccessorTest.cs new file mode 100644 index 0000000000..f09623dbfc --- /dev/null +++ b/osu.Game.Tests/Utils/BindableValueAccessorTest.cs @@ -0,0 +1,52 @@ +// 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.Bindables; +using osu.Game.Utils; + +namespace osu.Game.Tests.Utils +{ + [TestFixture] + public class BindableValueAccessorTest + { + [Test] + public void GetValue() + { + const int value = 1337; + + BindableInt bindable = new BindableInt(value); + Assert.That(BindableValueAccessor.GetValue(bindable), Is.EqualTo(value)); + } + + [Test] + public void SetValue() + { + const int value = 1337; + + BindableInt bindable = new BindableInt(); + BindableValueAccessor.SetValue(bindable, value); + + Assert.That(bindable.Value, Is.EqualTo(value)); + } + + [Test] + public void GetInvalidBindable() + { + BindableList list = new BindableList(); + Assert.That(BindableValueAccessor.GetValue(list), Is.EqualTo(list)); + } + + [Test] + public void SetInvalidBindable() + { + const int value = 1337; + + BindableList list = new BindableList { value }; + BindableValueAccessor.SetValue(list, 2); + + Assert.That(list, Has.Exactly(1).Items); + Assert.That(list[0], Is.EqualTo(value)); + } + } +} diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 1e425c88a6..580366a75a 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Reflection; using JetBrains.Annotations; @@ -15,6 +14,7 @@ using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; +using osu.Game.Utils; namespace osu.Game.Configuration { @@ -228,10 +228,7 @@ namespace osu.Game.Configuration return b.Value; case IBindable u: - // An unknown (e.g. enum) generic type. - var valueMethod = u.GetType().GetProperty(nameof(IBindable.Value)); - Debug.Assert(valueMethod != null); - return valueMethod.GetValue(u)!; + return BindableValueAccessor.GetValue(u); default: // fall back for non-bindable cases. diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index b9a937b1a2..1b21216235 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -266,8 +266,7 @@ namespace osu.Game.Rulesets.Mods // TODO: special case for handling number types - PropertyInfo property = targetSetting.GetType().GetProperty(nameof(Bindable.Value))!; - property.SetValue(targetSetting, property.GetValue(sourceSetting)); + BindableValueAccessor.SetValue(targetSetting, BindableValueAccessor.GetValue(sourceSetting)); } } diff --git a/osu.Game/Utils/BindableValueAccessor.cs b/osu.Game/Utils/BindableValueAccessor.cs new file mode 100644 index 0000000000..dd097ada36 --- /dev/null +++ b/osu.Game/Utils/BindableValueAccessor.cs @@ -0,0 +1,44 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using System.Reflection; +using osu.Framework.Bindables; +using osu.Framework.Extensions.TypeExtensions; + +namespace osu.Game.Utils +{ + internal static class BindableValueAccessor + { + private static readonly MethodInfo get_method = typeof(BindableValueAccessor).GetMethod(nameof(getValue), BindingFlags.Static | BindingFlags.NonPublic)!; + private static readonly MethodInfo set_method = typeof(BindableValueAccessor).GetMethod(nameof(setValue), BindingFlags.Static | BindingFlags.NonPublic)!; + + public static object GetValue(IBindable bindable) + { + Type? bindableWithValueType = bindable.GetType().GetInterfaces().FirstOrDefault(isBindableT); + if (bindableWithValueType == null) + return bindable; + + return get_method.MakeGenericMethod(bindableWithValueType.GenericTypeArguments[0]).Invoke(null, [bindable])!; + } + + public static void SetValue(IBindable bindable, object value) + { + Type? bindableWithValueType = bindable.GetType().EnumerateBaseTypes().FirstOrDefault(isBindableT); + if (bindableWithValueType == null) + return; + + set_method.MakeGenericMethod(bindableWithValueType.GenericTypeArguments[0]).Invoke(null, [bindable, value]); + } + + private static bool isBindableT(Type type) + => type.IsGenericType + && (type.GetGenericTypeDefinition() == typeof(Bindable<>) + || type.GetGenericTypeDefinition() == typeof(IBindable<>)); + + private static object getValue(object bindable) => ((IBindable)bindable).Value!; + + private static object setValue(object bindable, object value) => ((Bindable)bindable).Value = (T)value; + } +} From 2fe229d62073a80c1a9f07155a510d9f80713584 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 25 Sep 2024 22:46:53 +0900 Subject: [PATCH 300/924] Inline condition --- osu.Game/Utils/BindableValueAccessor.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game/Utils/BindableValueAccessor.cs b/osu.Game/Utils/BindableValueAccessor.cs index dd097ada36..a4cd356339 100644 --- a/osu.Game/Utils/BindableValueAccessor.cs +++ b/osu.Game/Utils/BindableValueAccessor.cs @@ -16,7 +16,7 @@ namespace osu.Game.Utils public static object GetValue(IBindable bindable) { - Type? bindableWithValueType = bindable.GetType().GetInterfaces().FirstOrDefault(isBindableT); + Type? bindableWithValueType = bindable.GetType().GetInterfaces().FirstOrDefault(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IBindable<>)); if (bindableWithValueType == null) return bindable; @@ -25,18 +25,13 @@ namespace osu.Game.Utils public static void SetValue(IBindable bindable, object value) { - Type? bindableWithValueType = bindable.GetType().EnumerateBaseTypes().FirstOrDefault(isBindableT); + Type? bindableWithValueType = bindable.GetType().EnumerateBaseTypes().SingleOrDefault(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Bindable<>)); if (bindableWithValueType == null) return; set_method.MakeGenericMethod(bindableWithValueType.GenericTypeArguments[0]).Invoke(null, [bindable, value]); } - private static bool isBindableT(Type type) - => type.IsGenericType - && (type.GetGenericTypeDefinition() == typeof(Bindable<>) - || type.GetGenericTypeDefinition() == typeof(IBindable<>)); - private static object getValue(object bindable) => ((IBindable)bindable).Value!; private static object setValue(object bindable, object value) => ((Bindable)bindable).Value = (T)value; From fe8b9536ffa085326b889dce768ed2b035c3a67f Mon Sep 17 00:00:00 2001 From: StanR Date: Wed, 25 Sep 2024 18:58:24 +0500 Subject: [PATCH 301/924] Bring back some old nerfs as balancing factor --- .../Difficulty/Evaluators/RhythmEvaluator.cs | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index edb67f37d9..08efb187fe 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -12,10 +12,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators { public static class RhythmEvaluator { - private const int history_time_max = 4 * 1000; // 4 seconds + private const int history_time_max = 5 * 1000; // 5 seconds private const int history_objects_max = 32; - private const double rhythm_overall_multiplier = 0.92; - private const double rhythm_ratio_multiplier = 11.5; + private const double rhythm_overall_multiplier = 0.95; + private const double rhythm_ratio_multiplier = 12.0; /// /// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current . @@ -94,19 +94,20 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators // bpm change was from a slider, this is easier typically than circle -> circle // unintentional side effect is that bursts with kicksliders at the ends might have lower difficulty than bursts without sliders if (prevObj.BaseObject is Slider) - effectiveRatio *= 0.15; + effectiveRatio *= 0.3; // repeated island polarity (2 -> 4, 3 -> 5) if (island.IsSimilarPolarity(previousIsland)) - effectiveRatio *= 0.3; + effectiveRatio *= 0.5; // previous increase happened a note ago, 1/1->1/2-1/4, dont want to buff this. if (lastDelta > prevDelta + deltaDifferenceEpsilon && prevDelta > currDelta + deltaDifferenceEpsilon) effectiveRatio *= 0.125; - // singletaps are easier to control - if (island.DeltaCount == 1) - effectiveRatio *= 0.7; + // repeated island size (ex: triplet -> triplet) + // TODO: remove this nerf since its staying here only for balancing purposes because of the flawed ratio calculation + if (previousIsland.DeltaCount == island.DeltaCount) + effectiveRatio *= 0.5; var islandCount = islandCounts.FirstOrDefault(x => x.Island.Equals(island)); @@ -150,6 +151,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators // Begin counting island until we change speed again. firstDeltaSwitch = true; + // bpm change is into slider, this is easy acc window + if (currObj.BaseObject is Slider) + effectiveRatio *= 0.6; + + // bpm change was from a slider, this is easier typically than circle -> circle + // unintentional side effect is that bursts with kicksliders at the ends might have lower difficulty than bursts without sliders + if (prevObj.BaseObject is Slider) + effectiveRatio *= 0.6; + startRatio = effectiveRatio; island = new Island((int)currDelta, deltaDifferenceEpsilon); @@ -180,12 +190,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators DeltaCount++; } - public int Delta { get; private set; } + public int Delta { get; private set; } = int.MaxValue; public int DeltaCount { get; private set; } public void AddDelta(int delta) { - if (Delta == default) + if (Delta == int.MaxValue) Delta = Math.Max(delta, OsuDifficultyHitObject.MIN_DELTA_TIME); DeltaCount++; @@ -193,9 +203,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators public bool IsSimilarPolarity(Island other) { - // consider islands to be of similar polarity only if they're having the same average delta (we don't want to consider 3 singletaps similar to a triple) - return DeltaCount % 2 == other.DeltaCount % 2 && - Math.Abs(Delta - other.Delta) < deltaDifferenceEpsilon; + // TODO: consider islands to be of similar polarity only if they're having the same average delta (we don't want to consider 3 singletaps similar to a triple) + // naively adding delta check here breaks _a lot_ of maps because of the flawed ratio calculation + return DeltaCount % 2 == other.DeltaCount % 2; } public bool Equals(Island? other) From df0966abb2475a53fed24acec5429c44dfb29bb8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Sep 2024 01:11:23 +0900 Subject: [PATCH 302/924] Update velopack and switch to using async version of `WaitExitThenApplyUpdates` --- osu.Desktop/Updater/VelopackUpdateManager.cs | 15 ++++++++------- osu.Desktop/osu.Desktop.csproj | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Desktop/Updater/VelopackUpdateManager.cs b/osu.Desktop/Updater/VelopackUpdateManager.cs index ae58a8793c..7a79284533 100644 --- a/osu.Desktop/Updater/VelopackUpdateManager.cs +++ b/osu.Desktop/Updater/VelopackUpdateManager.cs @@ -66,7 +66,7 @@ namespace osu.Desktop.Updater { Activated = () => { - restartToApplyUpdate(); + Task.Run(restartToApplyUpdate); return true; } }); @@ -88,7 +88,11 @@ namespace osu.Desktop.Updater { notification = new UpdateProgressNotification { - CompletionClickAction = restartToApplyUpdate, + CompletionClickAction = () => + { + Task.Run(restartToApplyUpdate); + return true; + }, }; Schedule(() => notificationOverlay.Post(notification)); @@ -127,13 +131,10 @@ namespace osu.Desktop.Updater return true; } - private bool restartToApplyUpdate() + private async Task restartToApplyUpdate() { - // TODO: Migrate this to async flow whenever available (see https://github.com/ppy/osu/pull/28743#discussion_r1740505665). - // Currently there's an internal Thread.Sleep(300) which will cause a stutter when the user clicks to restart. - updateManager.WaitExitThenApplyUpdates(pendingUpdate?.TargetFullRelease); + await updateManager.WaitExitThenApplyUpdatesAsync(pendingUpdate?.TargetFullRelease).ConfigureAwait(false); Schedule(() => game.AttemptExit()); - return true; } } } diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index bf5f26b352..3df8c16f08 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -26,7 +26,7 @@ - + From f4055d923f7fe28e0aed4947ca2d66cc5e8bd0c6 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Wed, 25 Sep 2024 18:14:15 +0100 Subject: [PATCH 303/924] increase aim skill multiplier --- osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 1fbe03395c..faf91e4652 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private double currentStrain; - private double skillMultiplier => 24.963; + private double skillMultiplier => 25.18; private double strainDecayBase => 0.15; private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); From 89e8baf1d35dabc6133d609e9af5e1da50cff976 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Sep 2024 16:48:42 +0900 Subject: [PATCH 304/924] Add inline comments for iOS locals --- osu.Game/Beatmaps/BeatmapImporter.cs | 1 + osu.Game/Online/BeatmapDownloadTracker.cs | 1 + osu.Game/Online/ScoreDownloadTracker.cs | 1 + osu.Game/Skinning/RealmBackedResourceStore.cs | 1 + osu.Game/Skinning/SkinManager.cs | 1 + 5 files changed, 5 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 63d8215d73..94144e4695 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -198,6 +198,7 @@ namespace osu.Game.Beatmaps if (beatmapSet.OnlineID > 0) { + // Required local for iOS. Will cause runtime crash if inlined. int onlineId = beatmapSet.OnlineID; // OnlineID should really be unique, but to avoid catastrophic failure let's iterate just to be sure. diff --git a/osu.Game/Online/BeatmapDownloadTracker.cs b/osu.Game/Online/BeatmapDownloadTracker.cs index c1c3d17ff6..6a2163c3a2 100644 --- a/osu.Game/Online/BeatmapDownloadTracker.cs +++ b/osu.Game/Online/BeatmapDownloadTracker.cs @@ -40,6 +40,7 @@ namespace osu.Game.Online // Used to interact with manager classes that don't support interface types. Will eventually be replaced. var beatmapSetInfo = new BeatmapSetInfo { OnlineID = TrackedItem.OnlineID }; + // Required local for iOS. Will cause runtime crash if inlined. int onlineId = TrackedItem.OnlineID; realmSubscription = realm.RegisterForNotifications(r => r.All().Where(s => s.OnlineID == onlineId && !s.DeletePending), (items, _) => diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index eb687a7023..5f6ba15d05 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -46,6 +46,7 @@ namespace osu.Game.Online Downloader.DownloadBegan += downloadBegan; Downloader.DownloadFailed += downloadFailed; + // Required local for iOS. Will cause runtime crash if inlined. long onlineId = TrackedItem.OnlineID; long legacyOnlineId = TrackedItem.LegacyOnlineID; string hash = TrackedItem.Hash; diff --git a/osu.Game/Skinning/RealmBackedResourceStore.cs b/osu.Game/Skinning/RealmBackedResourceStore.cs index b1289bd0c5..f41bd89b7a 100644 --- a/osu.Game/Skinning/RealmBackedResourceStore.cs +++ b/osu.Game/Skinning/RealmBackedResourceStore.cs @@ -29,6 +29,7 @@ namespace osu.Game.Skinning invalidateCache(); Debug.Assert(fileToStoragePathMapping != null); + // Required local for iOS. Will cause runtime crash if inlined. Guid id = source.ID; realmSubscription = realm?.RegisterForNotifications(r => r.All().Where(s => s.ID == id), skinChanged); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index cd431bd80c..9018c2e2c3 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -131,6 +131,7 @@ namespace osu.Game.Skinning { Realm.Run(r => { + // Required local for iOS. Will cause runtime crash if inlined. Guid currentSkinId = CurrentSkinInfo.Value.ID; // choose from only user skins, removing the current selection to ensure a new one is chosen. From b1a05f463e3bfe927dea17513953ea56cf890a38 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Sep 2024 19:42:20 +0900 Subject: [PATCH 305/924] Reduce size of hidden toggle slightly --- .../UserInterfaceV2/OsuDirectorySelectorHiddenToggle.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorHiddenToggle.cs b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorHiddenToggle.cs index ffedc9386f..521ebebf91 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorHiddenToggle.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorHiddenToggle.cs @@ -16,13 +16,15 @@ namespace osu.Game.Graphics.UserInterfaceV2 { RelativeSizeAxes = Axes.None; AutoSizeAxes = Axes.None; - Size = new Vector2(100, OsuDirectorySelectorBreadcrumbDisplay.HEIGHT); + Size = new Vector2(140, OsuDirectorySelectorBreadcrumbDisplay.HEIGHT); Margin = new MarginPadding { Right = OsuDirectorySelectorBreadcrumbDisplay.HORIZONTAL_PADDING, }; Anchor = Anchor.CentreLeft; Origin = Anchor.CentreLeft; LabelTextFlowContainer.Anchor = Anchor.CentreLeft; LabelTextFlowContainer.Origin = Anchor.CentreLeft; LabelText = @"Show hidden"; + + Scale = new Vector2(0.8f); } [BackgroundDependencyLoader(true)] From f4a4807449b6ef1d8504b655a16d77a77ed79e95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Aug 2024 10:29:10 +0200 Subject: [PATCH 306/924] Implement "form" file picker --- .../UserInterface/TestSceneFormControls.cs | 5 + .../UserInterfaceV2/FormFileSelector.cs | 262 ++++++++++++++++++ 2 files changed, 267 insertions(+) create mode 100644 osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs index 89b4ae9f97..2a0b0515a1 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs @@ -100,6 +100,11 @@ namespace osu.Game.Tests.Visual.UserInterface Caption = EditorSetupStrings.EnableCountdown, HintText = EditorSetupStrings.CountdownDescription, }, + new FormFileSelector + { + Caption = "Audio file", + PlaceholderText = "Select an audio file", + }, }, }, } diff --git a/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs b/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs new file mode 100644 index 0000000000..66f68f3e3b --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs @@ -0,0 +1,262 @@ +// 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.IO; +using System.Linq; +using System.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Framework.Localisation; +using osu.Framework.Platform; +using osu.Game.Database; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public partial class FormFileSelector : CompositeDrawable, IHasCurrentValue, ICanAcceptFiles, IHasPopover + { + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public IEnumerable HandledExtensions => handledExtensions; + + private readonly string[] handledExtensions; + + /// + /// The initial path to use when displaying the . + /// + /// + /// Uses a value before the first selection is made + /// to ensure that the first selection starts at . + /// + private string? initialChooserPath; + + private readonly Bindable popoverState = new Bindable(); + + /// + /// Caption describing this file selector, displayed on top of the controls. + /// + public LocalisableString Caption { get; init; } + + /// + /// Hint text containing an extended description of this file selector, displayed in a tooltip when hovering the caption. + /// + public LocalisableString HintText { get; init; } + + /// + /// Text displayed in the selector when no file is selected. + /// + public LocalisableString PlaceholderText { get; init; } + + private Box background = null!; + + private FormFieldCaption caption = null!; + private OsuSpriteText placeholderText = null!; + private OsuSpriteText filenameText = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + [Resolved] + private OsuGameBase game { get; set; } = null!; + + public FormFileSelector(params string[] handledExtensions) + { + this.handledExtensions = handledExtensions; + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + Height = 50; + + Masking = true; + CornerRadius = 5; + + InternalChildren = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background5, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(9), + Children = new Drawable[] + { + caption = new FormFieldCaption + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + Caption = Caption, + TooltipText = HintText, + }, + placeholderText = new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Width = 1, + Text = PlaceholderText, + Colour = colourProvider.Foreground1, + }, + filenameText = new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Width = 1, + }, + new SpriteIcon + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Icon = FontAwesome.Solid.FolderOpen, + Size = new Vector2(16), + Colour = colourProvider.Light1, + } + }, + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + popoverState.BindValueChanged(_ => updateState()); + current.BindValueChanged(_ => + { + updateState(); + onFileSelected(); + }); + current.BindDisabledChanged(_ => updateState(), true); + game.RegisterImportHandler(this); + } + + private void onFileSelected() + { + if (Current.Value != null) + this.HidePopover(); + + initialChooserPath = Current.Value?.DirectoryName; + placeholderText.Alpha = Current.Value == null ? 1 : 0; + filenameText.Text = Current.Value?.Name ?? string.Empty; + background.FlashColour(ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark2), 800, Easing.OutQuint); + } + + protected override bool OnClick(ClickEvent e) + { + this.ShowPopover(); + return true; + } + + protected override bool OnHover(HoverEvent e) + { + updateState(); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + updateState(); + } + + private void updateState() + { + caption.Colour = Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content2; + filenameText.Colour = Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content1; + + if (!Current.Disabled) + { + BorderThickness = IsHovered || popoverState.Value == Visibility.Visible ? 2 : 0; + BorderColour = popoverState.Value == Visibility.Visible ? colourProvider.Highlight1 : colourProvider.Light4; + + if (popoverState.Value == Visibility.Visible) + background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark3); + else if (IsHovered) + background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark4); + else + background.Colour = colourProvider.Background5; + } + else + { + background.Colour = colourProvider.Background4; + } + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (game.IsNotNull()) + game.UnregisterImportHandler(this); + } + + Task ICanAcceptFiles.Import(params string[] paths) + { + Schedule(() => Current.Value = new FileInfo(paths.First())); + return Task.CompletedTask; + } + + Task ICanAcceptFiles.Import(ImportTask[] tasks, ImportParameters parameters) => throw new NotImplementedException(); + + public Popover GetPopover() + { + var popover = new FileChooserPopover(handledExtensions, Current, initialChooserPath); + popoverState.UnbindBindings(); + popoverState.BindTo(popover.State); + return popover; + } + + private partial class FileChooserPopover : OsuPopover + { + protected override string PopInSampleName => "UI/overlay-big-pop-in"; + protected override string PopOutSampleName => "UI/overlay-big-pop-out"; + + public FileChooserPopover(string[] handledExtensions, Bindable currentFile, string? chooserPath) + : base(false) + { + Child = new Container + { + Size = new Vector2(600, 400), + Child = new OsuFileSelector(chooserPath, handledExtensions) + { + RelativeSizeAxes = Axes.Both, + CurrentFile = { BindTarget = currentFile } + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + Body.BorderThickness = 2; + Body.BorderColour = colourProvider.Highlight1; + } + } + } +} From 9e9bfc3721db83e5926fb6ee9bcfa0f4a2a1f684 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Sep 2024 12:48:50 +0900 Subject: [PATCH 307/924] Update velopack with zstd changes Closes https://github.com/ppy/osu/issues/29810. --- osu.Desktop/osu.Desktop.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 3df8c16f08..342b28f5ef 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -26,7 +26,7 @@ - + From cbeeb4a2b4b5cc7485f3f44012a14103ad8f2987 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Sep 2024 13:43:57 +0900 Subject: [PATCH 308/924] Add basic hover states for file selector elements --- .../UserInterfaceV2/FormFileSelector.cs | 19 +++++- .../OsuDirectorySelectorBreadcrumbDisplay.cs | 20 +------ .../OsuDirectorySelectorDirectory.cs | 6 +- .../UserInterfaceV2/OsuFileSelector.cs | 6 +- .../OsuFileSelectorBackgroundLayer.cs | 59 +++++++++++++++++++ 5 files changed, 79 insertions(+), 31 deletions(-) create mode 100644 osu.Game/Graphics/UserInterfaceV2/OsuFileSelectorBackgroundLayer.cs diff --git a/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs b/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs index 66f68f3e3b..55cc026d7c 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs @@ -24,6 +24,7 @@ using osu.Game.Database; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; using osuTK; +using osuTK.Graphics; namespace osu.Game.Graphics.UserInterfaceV2 { @@ -254,8 +255,22 @@ namespace osu.Game.Graphics.UserInterfaceV2 [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { - Body.BorderThickness = 2; - Body.BorderColour = colourProvider.Highlight1; + Add(new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = 2, + CornerRadius = 10, + BorderColour = colourProvider.Highlight1, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Transparent, + RelativeSizeAxes = Axes.Both, + }, + } + }); } } } diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorBreadcrumbDisplay.cs b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorBreadcrumbDisplay.cs index e91076498c..3fd1fa998f 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorBreadcrumbDisplay.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorBreadcrumbDisplay.cs @@ -80,7 +80,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 AddRangeInternal(new Drawable[] { - new Background + new OsuFileSelectorBackgroundLayer(0.5f) { Depth = 1 }, @@ -101,24 +101,6 @@ namespace osu.Game.Graphics.UserInterfaceV2 protected override SpriteText CreateSpriteText() => new OsuSpriteText().With(t => t.Font = OsuFont.Default.With(weight: FontWeight.SemiBold)); protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) ? FontAwesome.Solid.Database : null; - - internal partial class Background : CompositeDrawable - { - [BackgroundDependencyLoader] - private void load(OverlayColourProvider overlayColourProvider) - { - RelativeSizeAxes = Axes.Both; - - Masking = true; - CornerRadius = 5; - - InternalChild = new Box - { - Colour = overlayColourProvider.Background3, - RelativeSizeAxes = Axes.Both, - }; - } - } } } } diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs index a36804658a..4240eb73a4 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Graphics.UserInterfaceV2 { @@ -24,10 +23,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 Flow.AutoSizeAxes = Axes.X; Flow.Height = OsuDirectorySelector.ITEM_HEIGHT; - AddRangeInternal(new Drawable[] - { - new HoverClickSounds() - }); + AddInternal(new OsuFileSelectorBackgroundLayer()); Colour = colours.Orange1; } diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs b/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs index 7ce5f63656..f54bfeebba 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; namespace osu.Game.Graphics.UserInterfaceV2 @@ -87,10 +86,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 Flow.AutoSizeAxes = Axes.X; Flow.Height = OsuDirectorySelector.ITEM_HEIGHT; - AddRangeInternal(new Drawable[] - { - new HoverClickSounds() - }); + AddInternal(new OsuFileSelectorBackgroundLayer()); Colour = colourProvider.Light3; } diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuFileSelectorBackgroundLayer.cs b/osu.Game/Graphics/UserInterfaceV2/OsuFileSelectorBackgroundLayer.cs new file mode 100644 index 0000000000..ee5e7f014d --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/OsuFileSelectorBackgroundLayer.cs @@ -0,0 +1,59 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + internal partial class OsuFileSelectorBackgroundLayer : CompositeDrawable + { + private Box background = null!; + + private readonly float defaultAlpha; + + public OsuFileSelectorBackgroundLayer(float defaultAlpha = 0f) + { + Depth = float.MaxValue; + + this.defaultAlpha = defaultAlpha; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider overlayColourProvider) + { + RelativeSizeAxes = Axes.Both; + + Masking = true; + CornerRadius = 5; + + InternalChildren = new Drawable[] + { + new HoverClickSounds(), + background = new Box + { + Alpha = defaultAlpha, + Colour = overlayColourProvider.Background3, + RelativeSizeAxes = Axes.Both, + }, + }; + } + + protected override bool OnHover(HoverEvent e) + { + background.FadeTo(1, 200, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + background.FadeTo(defaultAlpha, 500, Easing.OutQuint); + } + } +} From eacd9b9756583950a78b91affcccd92f74d2162c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Sep 2024 13:57:05 +0900 Subject: [PATCH 309/924] Move dependent files to namespace --- .../BackgroundLayer.cs} | 6 +++--- .../HiddenFilesToggleCheckbox.cs} | 6 +++--- .../OsuDirectorySelectorBreadcrumbDisplay.cs | 4 ++-- .../{ => FileSelection}/OsuDirectorySelectorDirectory.cs | 4 ++-- .../OsuDirectorySelectorParentDirectory.cs | 2 +- osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs | 3 ++- osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs | 5 +++-- 7 files changed, 16 insertions(+), 14 deletions(-) rename osu.Game/Graphics/UserInterfaceV2/{OsuFileSelectorBackgroundLayer.cs => FileSelection/BackgroundLayer.cs} (88%) rename osu.Game/Graphics/UserInterfaceV2/{OsuDirectorySelectorHiddenToggle.cs => FileSelection/HiddenFilesToggleCheckbox.cs} (88%) rename osu.Game/Graphics/UserInterfaceV2/{ => FileSelection}/OsuDirectorySelectorBreadcrumbDisplay.cs (97%) rename osu.Game/Graphics/UserInterfaceV2/{ => FileSelection}/OsuDirectorySelectorDirectory.cs (91%) rename osu.Game/Graphics/UserInterfaceV2/{ => FileSelection}/OsuDirectorySelectorParentDirectory.cs (92%) diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuFileSelectorBackgroundLayer.cs b/osu.Game/Graphics/UserInterfaceV2/FileSelection/BackgroundLayer.cs similarity index 88% rename from osu.Game/Graphics/UserInterfaceV2/OsuFileSelectorBackgroundLayer.cs rename to osu.Game/Graphics/UserInterfaceV2/FileSelection/BackgroundLayer.cs index ee5e7f014d..cd3199c6f5 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuFileSelectorBackgroundLayer.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FileSelection/BackgroundLayer.cs @@ -9,15 +9,15 @@ using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; -namespace osu.Game.Graphics.UserInterfaceV2 +namespace osu.Game.Graphics.UserInterfaceV2.FileSelection { - internal partial class OsuFileSelectorBackgroundLayer : CompositeDrawable + internal partial class BackgroundLayer : CompositeDrawable { private Box background = null!; private readonly float defaultAlpha; - public OsuFileSelectorBackgroundLayer(float defaultAlpha = 0f) + public BackgroundLayer(float defaultAlpha = 0f) { Depth = float.MaxValue; diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorHiddenToggle.cs b/osu.Game/Graphics/UserInterfaceV2/FileSelection/HiddenFilesToggleCheckbox.cs similarity index 88% rename from osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorHiddenToggle.cs rename to osu.Game/Graphics/UserInterfaceV2/FileSelection/HiddenFilesToggleCheckbox.cs index 521ebebf91..07d84a0095 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorHiddenToggle.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FileSelection/HiddenFilesToggleCheckbox.cs @@ -8,11 +8,11 @@ using osu.Game.Overlays; using osuTK; using osuTK.Graphics; -namespace osu.Game.Graphics.UserInterfaceV2 +namespace osu.Game.Graphics.UserInterfaceV2.FileSelection { - internal partial class OsuDirectorySelectorHiddenToggle : OsuCheckbox + internal partial class HiddenFilesToggleCheckbox : OsuCheckbox { - public OsuDirectorySelectorHiddenToggle() + public HiddenFilesToggleCheckbox() { RelativeSizeAxes = Axes.None; AutoSizeAxes = Axes.None; diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorBreadcrumbDisplay.cs b/osu.Game/Graphics/UserInterfaceV2/FileSelection/OsuDirectorySelectorBreadcrumbDisplay.cs similarity index 97% rename from osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorBreadcrumbDisplay.cs rename to osu.Game/Graphics/UserInterfaceV2/FileSelection/OsuDirectorySelectorBreadcrumbDisplay.cs index 3fd1fa998f..aeeda82bfb 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorBreadcrumbDisplay.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FileSelection/OsuDirectorySelectorBreadcrumbDisplay.cs @@ -13,7 +13,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osuTK; -namespace osu.Game.Graphics.UserInterfaceV2 +namespace osu.Game.Graphics.UserInterfaceV2.FileSelection { internal partial class OsuDirectorySelectorBreadcrumbDisplay : DirectorySelectorBreadcrumbDisplay { @@ -80,7 +80,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 AddRangeInternal(new Drawable[] { - new OsuFileSelectorBackgroundLayer(0.5f) + new BackgroundLayer(0.5f) { Depth = 1 }, diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs b/osu.Game/Graphics/UserInterfaceV2/FileSelection/OsuDirectorySelectorDirectory.cs similarity index 91% rename from osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs rename to osu.Game/Graphics/UserInterfaceV2/FileSelection/OsuDirectorySelectorDirectory.cs index 4240eb73a4..0da4e1929f 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FileSelection/OsuDirectorySelectorDirectory.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.Sprites; -namespace osu.Game.Graphics.UserInterfaceV2 +namespace osu.Game.Graphics.UserInterfaceV2.FileSelection { internal partial class OsuDirectorySelectorDirectory : DirectorySelectorDirectory { @@ -23,7 +23,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 Flow.AutoSizeAxes = Axes.X; Flow.Height = OsuDirectorySelector.ITEM_HEIGHT; - AddInternal(new OsuFileSelectorBackgroundLayer()); + AddInternal(new BackgroundLayer()); Colour = colours.Orange1; } diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorParentDirectory.cs b/osu.Game/Graphics/UserInterfaceV2/FileSelection/OsuDirectorySelectorParentDirectory.cs similarity index 92% rename from osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorParentDirectory.cs rename to osu.Game/Graphics/UserInterfaceV2/FileSelection/OsuDirectorySelectorParentDirectory.cs index d274a0ecfe..e5e1e0b7f3 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorParentDirectory.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FileSelection/OsuDirectorySelectorParentDirectory.cs @@ -6,7 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using osu.Game.Overlays; -namespace osu.Game.Graphics.UserInterfaceV2 +namespace osu.Game.Graphics.UserInterfaceV2.FileSelection { internal partial class OsuDirectorySelectorParentDirectory : OsuDirectorySelectorDirectory { diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs index 85599a5d45..65ffdcaa5b 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelector.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterfaceV2.FileSelection; using osu.Game.Overlays; namespace osu.Game.Graphics.UserInterfaceV2 @@ -57,7 +58,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 { RelativeSizeAxes = Axes.Both, }, - new OsuDirectorySelectorHiddenToggle + new HiddenFilesToggleCheckbox { Current = { BindTarget = ShowHiddenItems }, }, diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs b/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs index f54bfeebba..c7b559d9ed 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterfaceV2.FileSelection; using osu.Game.Overlays; namespace osu.Game.Graphics.UserInterfaceV2 @@ -58,7 +59,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 { RelativeSizeAxes = Axes.Both, }, - new OsuDirectorySelectorHiddenToggle + new HiddenFilesToggleCheckbox { Current = { BindTarget = ShowHiddenItems }, }, @@ -86,7 +87,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 Flow.AutoSizeAxes = Axes.X; Flow.Height = OsuDirectorySelector.ITEM_HEIGHT; - AddInternal(new OsuFileSelectorBackgroundLayer()); + AddInternal(new BackgroundLayer()); Colour = colourProvider.Light3; } From b2983e25629e407c3045a65bded33bebe43931cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Sep 2024 14:21:16 +0900 Subject: [PATCH 310/924] Update shader preloader with missing shader usages --- osu.Game/Screens/Loader.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index 57e3998646..f64ae196a0 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -118,13 +118,20 @@ namespace osu.Game.Screens { loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE)); loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.BLUR)); + loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE)); - loadTargets.Add(manager.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE)); - - loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, "TriangleBorder")); - loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, "FastCircle")); + loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"TriangleBorder")); + loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"FastCircle")); + loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"CircularProgress")); + loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"ArgonBarPath")); + loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"ArgonBarPathBackground")); + loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"SaturationSelectorBackground")); + loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"HueSelectorBackground")); loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE)); + + loadTargets.Add(manager.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE)); + loadTargets.Add(manager.Load(@"LogoAnimation", @"LogoAnimation")); } protected virtual bool AllLoaded => loadTargets.All(s => s.IsLoaded); From 4205a21c0c595be177cd46b00f7e9b23d1361c69 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Sep 2024 16:11:24 +0900 Subject: [PATCH 311/924] Add one more shader usage --- osu.Game/Screens/Loader.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index f64ae196a0..4a59b180f5 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -127,6 +127,8 @@ namespace osu.Game.Screens loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"ArgonBarPathBackground")); loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"SaturationSelectorBackground")); loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"HueSelectorBackground")); + // Ruleset local shader usage (should probably move somewhere else). + loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"SpinnerGlow")); loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE)); From 5be63ee304e54852723cd4b043459b4aa733eb9a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Sep 2024 16:16:17 +0900 Subject: [PATCH 312/924] Reorganise with ruleset shader separated out --- osu.Game/Screens/Loader.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index 4a59b180f5..d71ee05b27 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -118,7 +118,7 @@ namespace osu.Game.Screens { loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE)); loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.BLUR)); - loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE)); + loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE)); loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"TriangleBorder")); loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"FastCircle")); @@ -127,13 +127,11 @@ namespace osu.Game.Screens loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"ArgonBarPathBackground")); loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"SaturationSelectorBackground")); loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"HueSelectorBackground")); + loadTargets.Add(manager.Load(@"LogoAnimation", @"LogoAnimation")); + // Ruleset local shader usage (should probably move somewhere else). loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, @"SpinnerGlow")); - - loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE)); - loadTargets.Add(manager.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE)); - loadTargets.Add(manager.Load(@"LogoAnimation", @"LogoAnimation")); } protected virtual bool AllLoaded => loadTargets.All(s => s.IsLoaded); From 21796900e2eeed8e8b9d707b285eca5a4184f6fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 Sep 2024 09:26:08 +0200 Subject: [PATCH 313/924] Fix code quality naming issue --- osu.Game/Utils/GeometryUtils.cs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index e9e79deb49..eac86a9c02 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -309,21 +309,21 @@ namespace osu.Game.Utils { // Using Welzl's algorithm to find the minimum enclosing circle // https://www.geeksforgeeks.org/minimum-enclosing-circle-using-welzls-algorithm/ - List P = points.ToList(); + List p = points.ToList(); var stack = new Stack<(Vector2?, int)>(); var r = new List(3); (Vector2, float) d = (Vector2.Zero, 0); - stack.Push((null, P.Count)); + stack.Push((null, p.Count)); while (stack.Count > 0) { - // n represents the number of points in P that are not yet processed. - // p represents the point that was randomly picked to process. - (Vector2? p, int n) = stack.Pop(); + // `n` represents the number of points in P that are not yet processed. + // `point` represents the point that was randomly picked to process. + (Vector2? point, int n) = stack.Pop(); - if (!p.HasValue) + if (!point.HasValue) { // Base case when all points processed or |R| = 3 if (n == 0 || r.Count == 3) @@ -334,30 +334,30 @@ namespace osu.Game.Utils // Pick a random point randomly int idx = RNG.Next(n); - p = P[idx]; + point = p[idx]; // Put the picked point at the end of P since it's more efficient than // deleting from the middle of the list - (P[idx], P[n - 1]) = (P[n - 1], P[idx]); + (p[idx], p[n - 1]) = (p[n - 1], p[idx]); // Schedule processing of p after we get the MEC circle d from the set of points P - {p} - stack.Push((p, n)); + stack.Push((point, n)); // Get the MEC circle d from the set of points P - {p} stack.Push((null, n - 1)); } else { // If d contains p, return d - if (isInside(d, p.Value)) + if (isInside(d, point.Value)) continue; // Remove points from R that were added in a deeper recursion // |R| = |P| - |stack| - n - int removeCount = r.Count - (P.Count - stack.Count - n); + int removeCount = r.Count - (p.Count - stack.Count - n); r.RemoveRange(r.Count - removeCount, removeCount); // Otherwise, must be on the boundary of the MEC - r.Add(p.Value); + r.Add(point.Value); // Return the MEC for P - {p} and R U {p} stack.Push((null, n - 1)); } From cb51e12d1393dab1a9c00ec542889b64fa5d73a2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 27 Sep 2024 16:24:51 +0900 Subject: [PATCH 314/924] Fix iOS CI build --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4abd55e3f4..6fbb74dfba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -135,5 +135,8 @@ jobs: - name: Install .NET Workloads run: dotnet workload install maui-ios + - name: Select Xcode 16 + run: sudo xcode-select -s /Applications/Xcode_16.app/Contents/Developer + - name: Build run: dotnet build -c Debug osu.iOS From 1dd6082aa9ea6c06647ddadfaddd1ae1720b9fba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Sep 2024 16:56:22 +0900 Subject: [PATCH 315/924] Rename method to be more appropriate --- .../HUD/JudgementCounter/JudgementCountController.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCountController.cs b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCountController.cs index 2562e26127..c00cb3487b 100644 --- a/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCountController.cs +++ b/osu.Game/Screens/Play/HUD/JudgementCounter/JudgementCountController.cs @@ -52,16 +52,16 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter { base.LoadComplete(); - scoreProcessor.OnResetFromReplayFrame += updateAllCounts; + scoreProcessor.OnResetFromReplayFrame += updateAllCountsFromReplayFrame; scoreProcessor.NewJudgement += judgement => updateCount(judgement, false); scoreProcessor.JudgementReverted += judgement => updateCount(judgement, true); } - private bool hasUpdatedCounts; + private bool hasUpdatedCountsFromReplayFrame; - private void updateAllCounts() + private void updateAllCountsFromReplayFrame() { - if (hasUpdatedCounts) + if (hasUpdatedCountsFromReplayFrame) return; foreach (var kvp in scoreProcessor.Statistics) @@ -72,7 +72,7 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter count.ResultCount.Value = kvp.Value; } - hasUpdatedCounts = true; + hasUpdatedCountsFromReplayFrame = true; } private void updateCount(JudgementResult judgement, bool revert) From ce41dc46293875b9cb6ac9cd132c0e59c694c69c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Sep 2024 17:13:26 +0900 Subject: [PATCH 316/924] Use bindable flow for zoom handling --- .../Edit/ManiaHitObjectComposer.cs | 2 +- .../Components/Timeline/SamplePointPiece.cs | 39 ++++++++----------- .../Timeline/ZoomableScrollContainer.cs | 15 +++---- 3 files changed, 26 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 02a4f3a022..4a8f4aa279 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Mania.Edit base.Update(); if (screenWithTimeline?.TimelineArea.Timeline != null) - drawableRuleset.TimelineTimeRange = EditorClock.TrackLength / screenWithTimeline.TimelineArea.Timeline.CurrentZoom / 2; + drawableRuleset.TimelineTimeRange = EditorClock.TrackLength / screenWithTimeline.TimelineArea.Timeline.CurrentZoom.Value / 2; } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 1638700735..fda907bf24 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -72,36 +72,31 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline samplesVisible = config.GetBindable(OsuSetting.EditorTimelineShowSamples); } + private BindableNumber? timelineZoom; + protected override void LoadComplete() { base.LoadComplete(); samplesVisible.BindValueChanged(visible => this.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint)); this.FadeTo(samplesVisible.Value ? 1 : 0); - } - private float lastZoom; - private const float zoom_threshold = 40f; - - protected override void Update() - { - base.Update(); - - // Retract visual state if the timeline is zoomed out too far. - if (timeline is null || timeline.Zoom == lastZoom) return; - - lastZoom = timeline.Zoom; - - if (timeline.Zoom < zoom_threshold) + timelineZoom = timeline?.CurrentZoom.GetBoundCopy(); + timelineZoom?.BindValueChanged(zoom => { - Label.FadeOut(200, Easing.OutQuint); - LabelContainer.ResizeWidthTo(16, 200, Easing.OutQuint); - } - else - { - Label.FadeIn(200, Easing.OutQuint); - LabelContainer.ResizeWidthTo(Label.Width, 200, Easing.OutQuint); - } + const float zoom_threshold = 40f; + + if (zoom.NewValue < zoom_threshold) + { + Label.FadeOut(200, Easing.OutQuint); + LabelContainer.ResizeWidthTo(16, 200, Easing.OutQuint); + } + else + { + Label.FadeIn(200, Easing.OutQuint); + LabelContainer.ResizeWidthTo(Label.Width, 200, Easing.OutQuint); + } + }, true); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 848c8f9a0f..31a0936eb4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Transforms; @@ -32,10 +33,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override Container Content => zoomedContent; /// - /// The current zoom level of . + /// The current (final) zoom level of . /// It may differ from during transitions. /// - public float CurrentZoom { get; private set; } = 1; + public BindableFloat CurrentZoom { get; private set; } = new BindableFloat(1); private bool isZoomSetUp; @@ -98,7 +99,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline minZoom = minimum; maxZoom = maximum; - CurrentZoom = zoomTarget = initial; + CurrentZoom.Value = zoomTarget = initial; zoomedContentWidthCache.Invalidate(); isZoomSetUp = true; @@ -124,7 +125,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (IsLoaded) setZoomTarget(newZoom, ToSpaceOfOtherDrawable(new Vector2(DrawWidth / 2, 0), zoomedContent).X); else - CurrentZoom = zoomTarget = newZoom; + CurrentZoom.Value = zoomTarget = newZoom; } protected override void UpdateAfterChildren() @@ -154,7 +155,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateZoomedContentWidth() { - zoomedContent.Width = DrawWidth * CurrentZoom; + zoomedContent.Width = DrawWidth * CurrentZoom.Value; zoomedContentWidthCache.Validate(); } @@ -238,7 +239,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline float expectedWidth = d.DrawWidth * newZoom; float targetOffset = expectedWidth * (focusPoint / contentSize) - focusOffset; - d.CurrentZoom = newZoom; + d.CurrentZoom.Value = newZoom; d.updateZoomedContentWidth(); // Temporarily here to make sure ScrollTo gets the correct DrawSize for scrollable area. @@ -247,7 +248,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline d.ScrollTo(targetOffset, false); } - protected override void ReadIntoStartValue(ZoomableScrollContainer d) => StartValue = d.CurrentZoom; + protected override void ReadIntoStartValue(ZoomableScrollContainer d) => StartValue = d.CurrentZoom.Value; } } } From 9dcce67b81032cedd9e7d0695d22a70edb70cff0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Sep 2024 17:24:24 +0900 Subject: [PATCH 317/924] Make points slightly smaller when contracted and fix width getting obliterated --- .../Components/Timeline/SamplePointPiece.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index fda907bf24..1076255b6a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -74,6 +74,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private BindableNumber? timelineZoom; + private bool contracted; + protected override void LoadComplete() { base.LoadComplete(); @@ -88,13 +90,17 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (zoom.NewValue < zoom_threshold) { + contracted = true; Label.FadeOut(200, Easing.OutQuint); - LabelContainer.ResizeWidthTo(16, 200, Easing.OutQuint); + LabelContainer.ResizeTo(new Vector2(12), 200, Easing.OutQuint); + LabelContainer.CornerRadius = 6; } else { + contracted = false; Label.FadeIn(200, Easing.OutQuint); - LabelContainer.ResizeWidthTo(Label.Width, 200, Easing.OutQuint); + LabelContainer.ResizeTo(new Vector2(Label.Width, 16), 200, Easing.OutQuint); + LabelContainer.CornerRadius = 8; } }, true); } @@ -124,7 +130,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateText() { Label.Text = $"{abbreviateBank(GetBankValue(GetSamples()))} {GetVolumeValue(GetSamples())}"; - LabelContainer.ResizeWidthTo(Label.Width, 200, Easing.OutQuint); + + if (!contracted) + LabelContainer.ResizeWidthTo(Label.Width, 200, Easing.OutQuint); } private static string? abbreviateBank(string? bank) From 1c23fd31d74265710b9a7b2be28de0242e8b23aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 Sep 2024 10:27:54 +0200 Subject: [PATCH 318/924] Ensure `Slider.updateNestedPositions()` actually updates the position of all nesteds --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 27 ++++++++++++++----- osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs | 2 ++ osu.Game.Rulesets.Osu/Objects/SliderTick.cs | 1 + 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 2b3bb18844..e484efb408 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -204,6 +204,7 @@ namespace osu.Game.Rulesets.Osu.Objects SpanStartTime = e.SpanStartTime, StartTime = e.Time, Position = Position + Path.PositionAt(e.PathProgress), + PathProgress = e.PathProgress, StackHeight = StackHeight, }); break; @@ -236,6 +237,7 @@ namespace osu.Game.Rulesets.Osu.Objects StartTime = StartTime + (e.SpanIndex + 1) * SpanDuration, Position = Position + Path.PositionAt(e.PathProgress), StackHeight = StackHeight, + PathProgress = e.PathProgress, }); break; } @@ -248,14 +250,27 @@ namespace osu.Game.Rulesets.Osu.Objects { endPositionCache.Invalidate(); - if (HeadCircle != null) - HeadCircle.Position = Position; + foreach (var nested in NestedHitObjects) + { + switch (nested) + { + case SliderHeadCircle headCircle: + headCircle.Position = Position; + break; - if (TailCircle != null) - TailCircle.Position = EndPosition; + case SliderTailCircle tailCircle: + tailCircle.Position = EndPosition; + break; - if (LastRepeat != null) - LastRepeat.Position = RepeatCount % 2 == 0 ? Position : Position + Path.PositionAt(1); + case SliderRepeat repeat: + repeat.Position = Position + Path.PositionAt(repeat.PathProgress); + break; + + case SliderTick tick: + tick.Position = Position + Path.PositionAt(tick.PathProgress); + break; + } + } } protected void UpdateNestedSamples() diff --git a/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs index e95cfd369d..1bbd1e8070 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs @@ -5,6 +5,8 @@ namespace osu.Game.Rulesets.Osu.Objects { public class SliderRepeat : SliderEndCircle { + public double PathProgress { get; set; } + public SliderRepeat(Slider slider) : base(slider) { diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs index 74ec4d6eb3..219c2be00b 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs @@ -13,6 +13,7 @@ namespace osu.Game.Rulesets.Osu.Objects { public int SpanIndex { get; set; } public double SpanStartTime { get; set; } + public double PathProgress { get; set; } protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { From 89f47c46544e7f1928f8b281230fbefd74904619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 Sep 2024 10:28:32 +0200 Subject: [PATCH 319/924] Fix random mod needlessly trying to fix nested object positions on its own --- .../OsuHitObjectGenerationUtils_Reposition.cs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index a9ae313a31..f7a0543739 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -232,8 +232,6 @@ namespace osu.Game.Rulesets.Osu.Utils slider.Position = workingObject.PositionModified = new Vector2(newX, newY); workingObject.EndPositionModified = slider.EndPosition; - shiftNestedObjects(slider, workingObject.PositionModified - workingObject.PositionOriginal); - return workingObject.PositionModified - previousPosition; } @@ -307,22 +305,6 @@ namespace osu.Game.Rulesets.Osu.Utils return new RectangleF(left, top, right - left, bottom - top); } - /// - /// Shifts all nested s and s by the specified shift. - /// - /// whose nested s and s should be shifted - /// The the 's nested s and s should be shifted by - private static void shiftNestedObjects(Slider slider, Vector2 shift) - { - foreach (var hitObject in slider.NestedHitObjects.Where(o => o is SliderTick || o is SliderRepeat)) - { - if (!(hitObject is OsuHitObject osuHitObject)) - continue; - - osuHitObject.Position += shift; - } - } - /// /// Clamp a position to playfield, keeping a specified distance from the edges. /// From 92ee86e3dd210e2b05877e2477ee27d54a086be3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Sep 2024 17:40:06 +0900 Subject: [PATCH 320/924] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index c7ce707562..6b42258b49 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index bb20125282..8acd1deff1 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + From 371cee1617854c76f8cfd98427aae75bdf460ac1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Sep 2024 17:41:27 +0900 Subject: [PATCH 321/924] Consume framework change to avoid weird unbind flow --- osu.Game/OsuGame.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 1af86b2d83..44ba78762a 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -446,11 +446,7 @@ namespace osu.Game case LinkAction.SearchBeatmapSet: if (link.Argument is LocalisableString localisable) - { - var localised = Localisation.GetLocalisedBindableString(localisable); - SearchBeatmapSet(localised.Value); - localised.UnbindAll(); - } + SearchBeatmapSet(Localisation.GetLocalisedString(localisable)); else SearchBeatmapSet(argString); From e7c44512066454c21726a4c24b6e93233571e25b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Sep 2024 18:20:16 +0900 Subject: [PATCH 322/924] Reduce brightness of hover effect --- osu.Game/Overlays/Mods/ModCustomisationHeader.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs index 1d40fb3f5c..54fbd37dbe 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs @@ -29,7 +29,7 @@ namespace osu.Game.Overlays.Mods [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; - public readonly Bindable ExpandedState = new Bindable(ModCustomisationPanelState.Collapsed); + public readonly Bindable ExpandedState = new Bindable(); private readonly ModCustomisationPanel panel; @@ -54,7 +54,7 @@ namespace osu.Game.Overlays.Mods hoverBackground = new Box { RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(80).Opacity(180), + Colour = OsuColour.Gray(50), Blending = BlendingParameters.Additive, Alpha = 0, }, @@ -134,16 +134,13 @@ namespace osu.Game.Overlays.Mods if (panel.ExpandedState.Value == ModCustomisationPanelState.Collapsed) panel.ExpandedState.Value = ModCustomisationPanelState.Expanded; - hoverBackground.FadeIn(200); - + hoverBackground.FadeTo(0.4f, 200, Easing.OutQuint); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - if (Enabled.Value) - hoverBackground.FadeOut(200); - + hoverBackground.FadeOut(200, Easing.OutQuint); base.OnHoverLost(e); } } From eb725ec1fb19c2348c9a8c8442644ef4f8cc33ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 Sep 2024 12:13:11 +0200 Subject: [PATCH 323/924] Nudge test coverage to also cover discovered fail case --- .../Visual/Editing/TestSceneComposerSelection.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs index 765d7ee21e..13d5a7e3b2 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs @@ -244,11 +244,8 @@ namespace osu.Game.Tests.Visual.Editing InputManager.PressKey(Key.ControlLeft); }); AddStep("drag to left corner", () => InputManager.MoveMouseTo(blueprintContainer.ScreenSpaceDrawQuad.TopRight + new Vector2(5, -5))); - AddStep("end dragging", () => - { - InputManager.ReleaseButton(MouseButton.Left); - InputManager.ReleaseKey(Key.ControlLeft); - }); + AddStep("end dragging", () => InputManager.ReleaseButton(MouseButton.Left)); + AddStep("release control", () => InputManager.ReleaseKey(Key.ControlLeft)); AddAssert("4 hitobjects selected", () => EditorBeatmap.SelectedHitObjects, () => Has.Count.EqualTo(4)); From d60733175563068265fb76baf649646e6843c726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 Sep 2024 12:15:08 +0200 Subject: [PATCH 324/924] Fix control-drag selection expansion deselecting object if control is released over one of the blueprints --- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 9776e64855..30c1258f93 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -432,7 +432,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private bool endClickSelection(MouseButtonEvent e) { // If already handled a selection, double-click, or drag, we don't want to perform a mouse up / click action. - if (clickSelectionHandled || doubleClickHandled || isDraggingBlueprint) return true; + if (clickSelectionHandled || doubleClickHandled || isDraggingBlueprint || wasDragStarted) return true; if (e.Button != MouseButton.Left) return false; @@ -448,7 +448,7 @@ namespace osu.Game.Screens.Edit.Compose.Components return false; } - if (!wasDragStarted && selectedBlueprintAlreadySelectedOnMouseDown && SelectedItems.Count == 1) + if (selectedBlueprintAlreadySelectedOnMouseDown && SelectedItems.Count == 1) { // If a click occurred and was handled by the currently selected blueprint but didn't result in a drag, // cycle between other blueprints which are also under the cursor. From f6c5f975ee8e1389f2c8f708b99cdacaabf056e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Sep 2024 20:08:26 +0900 Subject: [PATCH 325/924] Add failing test showing url decoding is not being performed --- .../Visual/Editing/TestSceneOpenEditorTimestamp.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs index 971eb223eb..955ded97af 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneOpenEditorTimestamp.cs @@ -100,6 +100,20 @@ namespace osu.Game.Tests.Visual.Editing assertOnScreenAt(EditorScreenMode.Compose, 0); } + [Test] + public void TestUrlDecodingOfArgs() + { + setUpEditor(new OsuRuleset().RulesetInfo); + AddAssert("is osu! ruleset", () => editorBeatmap.BeatmapInfo.Ruleset.Equals(new OsuRuleset().RulesetInfo)); + + AddStep("jump to encoded link", () => Game.HandleLink("osu://edit/00:14:142%20(1)")); + + AddUntilStep("wait for seek", () => editorClock.SeekingOrStopped.Value); + + AddAssert("time is correct", () => editorClock.CurrentTime, () => Is.EqualTo(14_142)); + AddAssert("selected object is correct", () => editorBeatmap.SelectedHitObjects.Single().StartTime, () => Is.EqualTo(14_142)); + } + private void addStepClickLink(string timestamp, string step = "", bool waitForSeek = true) { AddStep($"{step} {timestamp}", () => From 9647a1be7d928fec2cd76b48533ee5180e529851 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Sep 2024 20:08:38 +0900 Subject: [PATCH 326/924] Ensure editor timestamp args are URL decoded --- osu.Game/Online/Chat/MessageFormatter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 77454c4775..f354eea027 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; +using System.Web; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Edit; @@ -234,7 +235,7 @@ namespace osu.Game.Online.Chat return new LinkDetails(LinkAction.External, url); } - return new LinkDetails(linkType, args[2]); + return new LinkDetails(linkType, HttpUtility.UrlDecode(args[2])); case "osump": return new LinkDetails(LinkAction.JoinMultiplayerMatch, args[1]); From 132931f6054d24b116798937ce3f087299523d7b Mon Sep 17 00:00:00 2001 From: Fina <75299710+Finadoggie@users.noreply.github.com> Date: Fri, 27 Sep 2024 14:04:48 -0700 Subject: [PATCH 327/924] Create variable to check if using slideracc --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index a4ebcd15a4..946a0535db 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty { public const double PERFORMANCE_BASE_MULTIPLIER = 1.14; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things. + private bool usingClassicSliderAccuracy; + private double accuracy; private int scoreMaxCombo; private int countGreat; @@ -33,6 +35,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty { var osuAttributes = (OsuDifficultyAttributes)attributes; + usingClassicSliderAccuracy = score.Mods.OfType().All(m => m.NoSliderHeadAccuracy.Value); accuracy = score.Accuracy; scoreMaxCombo = score.MaxCombo; countGreat = score.Statistics.GetValueOrDefault(HitResult.Great); @@ -192,7 +195,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty // This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window. double betterAccuracyPercentage; int amountHitObjectsWithAccuracy = attributes.HitCircleCount; - if (score.Mods.OfType().All(m => !m.NoSliderHeadAccuracy.Value)) + if (!usingClassicSliderAccuracy) amountHitObjectsWithAccuracy += attributes.SliderCount; if (amountHitObjectsWithAccuracy > 0) From 7a849c7e206e599d00735823b10407febf4d1aa3 Mon Sep 17 00:00:00 2001 From: Finadoggie <75299710+Finadoggie@users.noreply.github.com> Date: Fri, 27 Sep 2024 14:11:12 -0700 Subject: [PATCH 328/924] Fix formatting --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 946a0535db..148d2a8130 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -36,6 +36,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty var osuAttributes = (OsuDifficultyAttributes)attributes; usingClassicSliderAccuracy = score.Mods.OfType().All(m => m.NoSliderHeadAccuracy.Value); + accuracy = score.Accuracy; scoreMaxCombo = score.MaxCombo; countGreat = score.Statistics.GetValueOrDefault(HitResult.Great); From 9b1ae2fe26f53b6f0c529a6f191a1ba55a5b4a9f Mon Sep 17 00:00:00 2001 From: Finadoggie <75299710+Finadoggie@users.noreply.github.com> Date: Fri, 27 Sep 2024 14:21:15 -0700 Subject: [PATCH 329/924] fix the code such that it actually works when testing it --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 148d2a8130..dc2113ed40 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty { var osuAttributes = (OsuDifficultyAttributes)attributes; - usingClassicSliderAccuracy = score.Mods.OfType().All(m => m.NoSliderHeadAccuracy.Value); + usingClassicSliderAccuracy = !(score.Mods.OfType().All(m => !m.NoSliderHeadAccuracy.Value)); accuracy = score.Accuracy; scoreMaxCombo = score.MaxCombo; From 4e2bc0d1bdae7b7f55476de675a1e1caf1d25500 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 28 Sep 2024 15:56:37 +0200 Subject: [PATCH 330/924] place grid with drag instead --- .../Edit/Blueprints/GridPlacementBlueprint.cs | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs index d45d82579f..a5b9efd930 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs @@ -44,30 +44,34 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints osuHitObjectComposer.SetSelectTool(); } - protected override bool OnMouseDown(MouseDownEvent e) + protected override bool OnClick(ClickEvent e) { - switch (e.Button) + if (e.Button == MouseButton.Left) { - case MouseButton.Right: - EndPlacement(true); - return true; - - case MouseButton.Left: - switch (PlacementActive) - { - case PlacementState.Waiting: - BeginPlacement(true); - return true; - - case PlacementState.Active: - EndPlacement(true); - return true; - } - - break; + EndPlacement(true); + return true; } - return base.OnMouseDown(e); + return base.OnClick(e); + } + + protected override bool OnDragStart(DragStartEvent e) + { + if (e.Button == MouseButton.Left) + { + BeginPlacement(true); + return true; + } + + return base.OnDragStart(e); + } + + protected override void OnDragEnd(DragEndEvent e) + { + if (PlacementActive == PlacementState.Active) + EndPlacement(true); + + base.OnDragEnd(e); } public override SnapType SnapType => ~SnapType.GlobalGrids; From b1e381a391fb1b3a64d141c38c92fe8abab34b33 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 28 Sep 2024 15:59:07 +0200 Subject: [PATCH 331/924] Update tooltip for drag --- osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs index 1714dacd17..396fac4754 100644 --- a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs +++ b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs @@ -17,8 +17,7 @@ namespace osu.Game.Rulesets.Osu.Edit { TooltipText = """ Left click to set the origin. - Left click again to set the rotation and spacing. - Right click to only set the origin. + Click and drag to set the origin, rotation and spacing. """; } From 1c6e42671acfe269955355ce2d7c875f3568861b Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 28 Sep 2024 17:04:11 +0200 Subject: [PATCH 332/924] return grid placement tool to right toolbox --- osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs | 9 --------- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 12 ++++++++++++ osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 4 +++- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 8 ++++++++ 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs index 396fac4754..f357d3024f 100644 --- a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs +++ b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs @@ -1,9 +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 osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Osu.Edit.Blueprints; @@ -15,14 +12,8 @@ namespace osu.Game.Rulesets.Osu.Edit public GridFromPointsTool() : base("Change grid") { - TooltipText = """ - Left click to set the origin. - Click and drag to set the origin, rotation and spacing. - """; } - public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorGridSnap }; - public override PlacementBlueprint CreatePlacementBlueprint() => new GridPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 487d73693f..b08ecb0e61 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -13,6 +13,7 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.UI; @@ -85,8 +86,11 @@ namespace osu.Game.Rulesets.Osu.Edit private ExpandableSlider startPositionYSlider = null!; private ExpandableSlider spacingSlider = null!; private ExpandableSlider gridLinesRotationSlider = null!; + private RoundedButton gridFromPointsButton = null!; private EditorRadioButtonCollection gridTypeButtons = null!; + public event Action? GridFromPointsClicked; + public OsuGridToolboxGroup() : base("grid") { @@ -146,6 +150,12 @@ namespace osu.Game.Rulesets.Osu.Edit Spacing = new Vector2(0f, 10f), Children = new Drawable[] { + gridFromPointsButton = new RoundedButton + { + Action = () => GridFromPointsClicked?.Invoke(), + RelativeSizeAxes = Axes.X, + Text = "Grid from points", + }, gridTypeButtons = new EditorRadioButtonCollection { RelativeSizeAxes = Axes.X, @@ -211,6 +221,8 @@ namespace osu.Game.Rulesets.Osu.Edit expandingContainer?.Expanded.BindValueChanged(v => { + gridFromPointsButton.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint); + gridFromPointsButton.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None; gridTypeButtons.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint); gridTypeButtons.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None; }, true); diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 3e24a48d49..d9ae312b29 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -46,9 +46,10 @@ namespace osu.Game.Rulesets.Osu.Edit new HitCircleCompositionTool(), new SliderCompositionTool(), new SpinnerCompositionTool(), - new GridFromPointsTool(), }; + private readonly GridFromPointsTool gridFromPointsTool = new GridFromPointsTool(); + private readonly Bindable rectangularGridSnapToggle = new Bindable(); protected override Drawable CreateHitObjectInspector() => new OsuHitObjectInspector(); @@ -98,6 +99,7 @@ namespace osu.Game.Rulesets.Osu.Edit updateDistanceSnapGrid(); OsuGridToolboxGroup.GridType.BindValueChanged(updatePositionSnapGrid, true); + OsuGridToolboxGroup.GridFromPointsClicked += () => SetCustomTool(gridFromPointsTool); RightToolbox.AddRange(new Drawable[] { diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 2053f9ff5d..cf1607acde 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -466,6 +466,14 @@ namespace osu.Game.Rulesets.Edit public void SetSelectTool() => toolboxCollection.Items.First().Select(); + protected void SetCustomTool(CompositionTool tool) + { + foreach (var toolBoxRadioButton in toolboxCollection.Items) + toolBoxRadioButton.Deselect(); + + toolSelected(tool); + } + private void toolSelected(CompositionTool tool) { BlueprintContainer.CurrentTool = tool; From 3e4cd0aeacf6b5c795261f5d2adae60624d4a003 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 28 Sep 2024 17:04:20 +0200 Subject: [PATCH 333/924] Add tooltip to rounded button --- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 4 ++++ osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index b08ecb0e61..a5771c595b 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -155,6 +155,10 @@ namespace osu.Game.Rulesets.Osu.Edit Action = () => GridFromPointsClicked?.Invoke(), RelativeSizeAxes = Axes.X, Text = "Grid from points", + TooltipText = """ + Left click to set the origin. + Click and drag to set the origin, rotation and spacing. + """ }, gridTypeButtons = new EditorRadioButtonCollection { diff --git a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs index 6aded3fe32..a993867a93 100644 --- a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs +++ b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs @@ -8,6 +8,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Graphics.Backgrounds; @@ -17,7 +18,7 @@ using osuTK.Graphics; namespace osu.Game.Graphics.UserInterfaceV2 { - public partial class RoundedButton : OsuButton, IFilterable + public partial class RoundedButton : OsuButton, IFilterable, IHasTooltip { protected TrianglesV2? Triangles { get; private set; } @@ -107,5 +108,6 @@ namespace osu.Game.Graphics.UserInterfaceV2 } public bool FilteringActive { get; set; } + public LocalisableString TooltipText { get; set; } } } From 4568af8fdae457c121fb53712c62acd6eb79372a Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 28 Sep 2024 17:26:39 +0200 Subject: [PATCH 334/924] Combine drag and clicky interactions --- .../Edit/Blueprints/GridPlacementBlueprint.cs | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs index a5b9efd930..d0caec269f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs @@ -47,12 +47,31 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints protected override bool OnClick(ClickEvent e) { if (e.Button == MouseButton.Left) + { + switch (PlacementActive) + { + case PlacementState.Waiting: + BeginPlacement(true); + return true; + + case PlacementState.Active: + EndPlacement(true); + return true; + } + } + + return base.OnClick(e); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (e.Button == MouseButton.Right) { EndPlacement(true); return true; } - return base.OnClick(e); + return base.OnMouseDown(e); } protected override bool OnDragStart(DragStartEvent e) @@ -83,7 +102,18 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints if (PlacementActive != PlacementState.Active) gridToolboxGroup.StartPosition.Value = pos; else - gridToolboxGroup.SetGridFromPoints(gridToolboxGroup.StartPosition.Value, pos); + { + // Default to the original spacing and rotation if the distance is too small. + if (Vector2.Distance(gridToolboxGroup.StartPosition.Value, pos) < 2) + { + gridToolboxGroup.Spacing.Value = originalSpacing; + gridToolboxGroup.GridLinesRotation.Value = originalRotation; + } + else + { + gridToolboxGroup.SetGridFromPoints(gridToolboxGroup.StartPosition.Value, pos); + } + } } } } From 1912b1fcf3b5743459ab2981a81c6260f3f22f52 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 28 Sep 2024 17:33:24 +0200 Subject: [PATCH 335/924] Revert "Add tooltip to rounded button" This reverts commit 3e4cd0aeacf6b5c795261f5d2adae60624d4a003. --- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 4 ---- osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs | 4 +--- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index a5771c595b..b08ecb0e61 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -155,10 +155,6 @@ namespace osu.Game.Rulesets.Osu.Edit Action = () => GridFromPointsClicked?.Invoke(), RelativeSizeAxes = Axes.X, Text = "Grid from points", - TooltipText = """ - Left click to set the origin. - Click and drag to set the origin, rotation and spacing. - """ }, gridTypeButtons = new EditorRadioButtonCollection { diff --git a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs index a993867a93..6aded3fe32 100644 --- a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs +++ b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs @@ -8,7 +8,6 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Graphics.Backgrounds; @@ -18,7 +17,7 @@ using osuTK.Graphics; namespace osu.Game.Graphics.UserInterfaceV2 { - public partial class RoundedButton : OsuButton, IFilterable, IHasTooltip + public partial class RoundedButton : OsuButton, IFilterable { protected TrianglesV2? Triangles { get; private set; } @@ -108,6 +107,5 @@ namespace osu.Game.Graphics.UserInterfaceV2 } public bool FilteringActive { get; set; } - public LocalisableString TooltipText { get; set; } } } From f473f4398c90e477686b48307ae9f9ec26e3a906 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 28 Sep 2024 22:37:16 +0300 Subject: [PATCH 336/924] Fix text in FormFileSelector bleeding through the border --- osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs b/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs index 55cc026d7c..42bf9c7b9f 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs @@ -244,6 +244,9 @@ namespace osu.Game.Graphics.UserInterfaceV2 Child = new Container { Size = new Vector2(600, 400), + // simplest solution to avoid underlying text to bleed through the bottom border + // https://github.com/ppy/osu/pull/30005#issuecomment-2378884430 + Padding = new MarginPadding { Bottom = 1 }, Child = new OsuFileSelector(chooserPath, handledExtensions) { RelativeSizeAxes = Axes.Both, From 3fac9baa9f97e1918d3bfb3760bf3c157be2fb86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Sep 2024 08:38:11 +0200 Subject: [PATCH 337/924] Add test steps demonstrating failure case --- .../Visual/SongSelect/TestScenePlaySongSelect.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 6b8fa94336..aae0648157 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -175,6 +175,20 @@ namespace osu.Game.Tests.Visual.SongSelect increaseModSpeed(); AddAssert("adaptive speed still active", () => songSelect!.Mods.Value.First() is ModAdaptiveSpeed); + OsuModDoubleTime dtWithAdjustPitch = new OsuModDoubleTime + { + SpeedChange = { Value = 1.05 }, + AdjustPitch = { Value = true }, + }; + changeMods(dtWithAdjustPitch); + + decreaseModSpeed(); + AddAssert("no mods selected", () => songSelect!.Mods.Value.Count == 0); + + decreaseModSpeed(); + AddAssert("half time activated at 0.95x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005)); + AddAssert("half time has adjust pitch active", () => songSelect!.Mods.Value.OfType().Single().AdjustPitch.Value, () => Is.True); + void increaseModSpeed() => AddStep("increase mod speed", () => { InputManager.PressKey(Key.ControlLeft); From 23b8354af4b5564b94f4d17282f517f71f8db398 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Sep 2024 08:46:45 +0200 Subject: [PATCH 338/924] Add more test steps demonstrating another failure case --- .../Visual/SongSelect/TestScenePlaySongSelect.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index aae0648157..3a95aca6b9 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -189,6 +189,15 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("half time activated at 0.95x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005)); AddAssert("half time has adjust pitch active", () => songSelect!.Mods.Value.OfType().Single().AdjustPitch.Value, () => Is.True); + AddStep("turn off adjust pitch", () => songSelect!.Mods.Value.OfType().Single().AdjustPitch.Value = false); + + increaseModSpeed(); + AddAssert("no mods selected", () => songSelect!.Mods.Value.Count == 0); + + increaseModSpeed(); + AddAssert("double time activated at 1.05x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.05).Within(0.005)); + AddAssert("double time has adjust pitch inactive", () => songSelect!.Mods.Value.OfType().Single().AdjustPitch.Value, () => Is.False); + void increaseModSpeed() => AddStep("increase mod speed", () => { InputManager.PressKey(Key.ControlLeft); From 5e5bb49cd8d8726223fca0eacd531fc797fd4c94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Sep 2024 08:47:02 +0200 Subject: [PATCH 339/924] Fix rate change hotkeys sometimes losing track of adjust pitch setting Fixes https://osu.ppy.sh/community/forums/topics/1983327. The cause of the bug is a bit convoluted, and stems from the fact that the mod select overlay controls all of the game-global mod instances if present. `ModSpeedHotkeyHandler` would store the last spotted instance of a rate adjust mod - which in this case is a problem, because on deselection of a mod, the mod select overlay resets its settings to defaults: https://github.com/ppy/osu/blob/a258059d4338b999b8e065e48b952d14a6d14fb8/osu.Game/Overlays/Mods/ModSelectOverlay.cs#L424-L425 A way to defend against this is a clone, but this reveals another issue, in that the existing code was *relying* on the reference to the mod remaining the same in any other case, to read the latest valid settings of the mod. This basically only mattered in the edge case wherein Double Time would swap places with Half Time and vice versa (think [0.95,1.05] range). Therefore, track mod settings too explicitly to ensure that the stored clone is as up-to-date as possible. --- osu.Game/Screens/Select/ModSpeedHotkeyHandler.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/ModSpeedHotkeyHandler.cs b/osu.Game/Screens/Select/ModSpeedHotkeyHandler.cs index af64002bcf..c4cd44705e 100644 --- a/osu.Game/Screens/Select/ModSpeedHotkeyHandler.cs +++ b/osu.Game/Screens/Select/ModSpeedHotkeyHandler.cs @@ -27,6 +27,7 @@ namespace osu.Game.Screens.Select private OnScreenDisplay? onScreenDisplay { get; set; } private ModRateAdjust? lastActiveRateAdjustMod; + private ModSettingChangeTracker? settingChangeTracker; protected override void LoadComplete() { @@ -34,10 +35,19 @@ namespace osu.Game.Screens.Select selectedMods.BindValueChanged(val => { - lastActiveRateAdjustMod = val.NewValue.OfType().SingleOrDefault() ?? lastActiveRateAdjustMod; + storeLastActiveRateAdjustMod(); + + settingChangeTracker?.Dispose(); + settingChangeTracker = new ModSettingChangeTracker(val.NewValue); + settingChangeTracker.SettingChanged += _ => storeLastActiveRateAdjustMod(); }, true); } + private void storeLastActiveRateAdjustMod() + { + lastActiveRateAdjustMod = (ModRateAdjust?)selectedMods.Value.OfType().SingleOrDefault()?.DeepClone() ?? lastActiveRateAdjustMod; + } + public bool ChangeSpeed(double delta, IEnumerable availableMods) { double targetSpeed = (selectedMods.Value.OfType().SingleOrDefault()?.SpeedChange.Value ?? 1) + delta; From 31d1ba494938201f6f9fd85ef75b52d576ac2ab8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Sep 2024 09:30:06 +0200 Subject: [PATCH 340/924] Remove unused member --- .../Utils/OsuHitObjectGenerationUtils_Reposition.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index f7a0543739..7073abbc89 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -413,7 +413,6 @@ namespace osu.Game.Rulesets.Osu.Utils private class WorkingObject { public float RotationOriginal { get; } - public Vector2 PositionOriginal { get; } public Vector2 PositionModified { get; set; } public Vector2 EndPositionModified { get; set; } @@ -424,7 +423,7 @@ namespace osu.Game.Rulesets.Osu.Utils { PositionInfo = positionInfo; RotationOriginal = HitObject is Slider slider ? getSliderRotation(slider) : 0; - PositionModified = PositionOriginal = HitObject.Position; + PositionModified = HitObject.Position; EndPositionModified = HitObject.EndPosition; } } From e91c8fb4bd0a74b9530b37a0c68fa94f5d478ce1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Sep 2024 11:02:00 +0200 Subject: [PATCH 341/924] Properly disable comment box on beatmaps that cannot be commented on Closes https://github.com/ppy/osu/issues/30052. Compare: - https://github.com/ppy/osu-web/blob/83816dbe24ad2927273cba968f2fcd2694a121a9/resources/js/components/comment-editor.tsx#L54-L60 - https://github.com/ppy/osu-web/blob/83816dbe24ad2927273cba968f2fcd2694a121a9/resources/js/components/comment-editor.tsx#L47-L52 --- .../UserInterface/TestSceneCommentEditor.cs | 34 ++++++++++++-- .../API/Requests/Responses/CommentableMeta.cs | 9 ++++ osu.Game/Overlays/Comments/CommentEditor.cs | 47 +++++++++++++++---- .../Overlays/Comments/CommentsContainer.cs | 8 ++-- osu.Game/Overlays/Comments/DrawableComment.cs | 2 +- .../Overlays/Comments/ReplyCommentEditor.cs | 7 +-- 6 files changed, 89 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneCommentEditor.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneCommentEditor.cs index e1d40882be..ac6ca218c4 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneCommentEditor.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneCommentEditor.cs @@ -12,6 +12,7 @@ using osu.Framework.Localisation; using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.Comments; using osuTK; @@ -133,6 +134,34 @@ namespace osu.Game.Tests.Visual.UserInterface assertLoggedInState(); } + [Test] + public void TestCommentsDisabled() + { + AddStep("no reason for disable", () => commentEditor.CommentableMeta.Value = new CommentableMeta + { + CurrentUserAttributes = new CommentableMeta.CommentableCurrentUserAttributes(), + }); + AddAssert("textbox enabled", () => commentEditor.ChildrenOfType().Single().ReadOnly, () => Is.False); + + AddStep("specific reason for disable", () => commentEditor.CommentableMeta.Value = new CommentableMeta + { + CurrentUserAttributes = new CommentableMeta.CommentableCurrentUserAttributes + { + CanNewCommentReason = "This comment section is disabled. For reasons.", + } + }); + AddAssert("textbox disabled", () => commentEditor.ChildrenOfType().Single().ReadOnly, () => Is.True); + + AddStep("entire commentable meta missing", () => commentEditor.CommentableMeta.Value = null); + AddAssert("textbox enabled", () => commentEditor.ChildrenOfType().Single().ReadOnly, () => Is.False); + + AddStep("current user attributes missing", () => commentEditor.CommentableMeta.Value = new CommentableMeta + { + CurrentUserAttributes = null, + }); + AddAssert("textbox enabled", () => commentEditor.ChildrenOfType().Single().ReadOnly, () => Is.True); + } + [Test] public void TestCancelAction() { @@ -167,8 +196,7 @@ namespace osu.Game.Tests.Visual.UserInterface protected override LocalisableString GetButtonText(bool isLoggedIn) => isLoggedIn ? @"Commit" : "You're logged out!"; - protected override LocalisableString GetPlaceholderText(bool isLoggedIn) => - isLoggedIn ? @"This text box is empty" : "Still empty, but now you can't type in it."; + protected override LocalisableString GetPlaceholderText() => @"This text box is empty"; } private partial class TestCancellableCommentEditor : CancellableCommentEditor @@ -189,7 +217,7 @@ namespace osu.Game.Tests.Visual.UserInterface } protected override LocalisableString GetButtonText(bool isLoggedIn) => @"Save"; - protected override LocalisableString GetPlaceholderText(bool isLoggedIn) => @"Multiline textboxes soon"; + protected override LocalisableString GetPlaceholderText() => @"Multiline textboxes soon"; } } } diff --git a/osu.Game/Online/API/Requests/Responses/CommentableMeta.cs b/osu.Game/Online/API/Requests/Responses/CommentableMeta.cs index 1084f1c900..4b4595fef6 100644 --- a/osu.Game/Online/API/Requests/Responses/CommentableMeta.cs +++ b/osu.Game/Online/API/Requests/Responses/CommentableMeta.cs @@ -24,5 +24,14 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("url")] public string Url { get; set; } = string.Empty; + + [JsonProperty("current_user_attributes")] + public CommentableCurrentUserAttributes? CurrentUserAttributes { get; set; } + + public struct CommentableCurrentUserAttributes + { + [JsonProperty("can_new_comment_reason")] + public string? CanNewCommentReason { get; set; } + } } } diff --git a/osu.Game/Overlays/Comments/CommentEditor.cs b/osu.Game/Overlays/Comments/CommentEditor.cs index 02bcbb9d05..b75e5aa8d8 100644 --- a/osu.Game/Overlays/Comments/CommentEditor.cs +++ b/osu.Game/Overlays/Comments/CommentEditor.cs @@ -14,6 +14,8 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Resources.Localisation.Web; using osuTK; using osuTK.Graphics; @@ -21,6 +23,8 @@ namespace osu.Game.Overlays.Comments { public abstract partial class CommentEditor : CompositeDrawable { + public Bindable CommentableMeta { get; set; } = new Bindable(); + private const int side_padding = 8; protected abstract LocalisableString FooterText { get; } @@ -53,8 +57,7 @@ namespace osu.Game.Overlays.Comments /// /// Returns the placeholder text for the comment box. /// - /// Whether the current user is logged in. - protected abstract LocalisableString GetPlaceholderText(bool isLoggedIn); + protected abstract LocalisableString GetPlaceholderText(); protected bool ShowLoadingSpinner { @@ -168,7 +171,8 @@ namespace osu.Game.Overlays.Comments { base.LoadComplete(); Current.BindValueChanged(_ => updateCommitButtonState(), true); - apiState.BindValueChanged(updateStateForLoggedIn, true); + apiState.BindValueChanged(_ => updateEnabledState()); + CommentableMeta.BindValueChanged(_ => updateEnabledState(), true); } protected abstract void OnCommit(string text); @@ -176,16 +180,25 @@ namespace osu.Game.Overlays.Comments private void updateCommitButtonState() => commitButton.Enabled.Value = loadingSpinner.State.Value == Visibility.Hidden && !string.IsNullOrEmpty(Current.Value); - private void updateStateForLoggedIn(ValueChangedEvent state) => Schedule(() => + private void updateEnabledState() => Schedule(() => { - bool isAvailable = state.NewValue > APIState.Offline; + bool isOnline = apiState.Value > APIState.Offline; + var canNewCommentReason = CommentEditor.canNewCommentReason(CommentableMeta.Value); + bool commentsDisabled = canNewCommentReason != null; + bool canComment = isOnline && !commentsDisabled; - TextBox.PlaceholderText = GetPlaceholderText(isAvailable); - TextBox.ReadOnly = !isAvailable; + if (!isOnline) + TextBox.PlaceholderText = AuthorizationStrings.RequireLogin; + else if (canNewCommentReason != null) + TextBox.PlaceholderText = canNewCommentReason.Value; + else + TextBox.PlaceholderText = GetPlaceholderText(); + TextBox.ReadOnly = !canComment; - if (isAvailable) + if (isOnline) { commitButton.Show(); + commitButton.Enabled.Value = !commentsDisabled; logInButton.Hide(); } else @@ -195,6 +208,24 @@ namespace osu.Game.Overlays.Comments } }); + // https://github.com/ppy/osu-web/blob/83816dbe24ad2927273cba968f2fcd2694a121a9/resources/js/components/comment-editor.tsx#L54-L60 + // careful here, logic is VERY finicky. + private static LocalisableString? canNewCommentReason(CommentableMeta? meta) + { + if (meta == null) + return null; + + if (meta.CurrentUserAttributes != null) + { + if (meta.CurrentUserAttributes.Value.CanNewCommentReason is string reason) + return reason; + + return null; + } + + return AuthorizationStrings.CommentStoreDisabled; + } + private partial class EditorTextBox : OsuTextBox { protected override float LeftRightPadding => side_padding; diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index 2e5f13aa99..921c1682f5 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -20,6 +20,7 @@ using System.Collections.Generic; using JetBrains.Annotations; using osu.Framework.Localisation; using osu.Framework.Logging; +using osu.Game.Extensions; using osu.Game.Graphics.Sprites; using osu.Game.Resources.Localisation.Web; using osu.Game.Users.Drawables; @@ -49,6 +50,7 @@ namespace osu.Game.Overlays.Comments private int currentPage; private FillFlowContainer pinnedContent; + private NewCommentEditor newCommentEditor; private FillFlowContainer content; private DeletedCommentsCounter deletedCommentsCounter; private CommentsShowMoreButton moreButton; @@ -114,7 +116,7 @@ namespace osu.Game.Overlays.Comments Padding = new MarginPadding { Left = 60 }, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Child = new NewCommentEditor + Child = newCommentEditor = new NewCommentEditor { OnPost = prependPostedComments } @@ -242,6 +244,7 @@ namespace osu.Game.Overlays.Comments protected void OnSuccess(CommentBundle response) { commentCounter.Current.Value = response.Total; + newCommentEditor.CommentableMeta.Value = response.CommentableMeta.SingleOrDefault(m => m.Id == id.Value && m.Type == type.Value.ToString().ToSnakeCase().ToLowerInvariant()); if (!response.Comments.Any()) { @@ -413,8 +416,7 @@ namespace osu.Game.Overlays.Comments protected override LocalisableString GetButtonText(bool isLoggedIn) => isLoggedIn ? CommonStrings.ButtonsPost : CommentsStrings.GuestButtonNew; - protected override LocalisableString GetPlaceholderText(bool isLoggedIn) => - isLoggedIn ? CommentsStrings.PlaceholderNew : AuthorizationStrings.RequireLogin; + protected override LocalisableString GetPlaceholderText() => CommentsStrings.PlaceholderNew; protected override void OnCommit(string text) { diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 296f90872e..d664a44be9 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -428,7 +428,7 @@ namespace osu.Game.Overlays.Comments if (replyEditorContainer.Count == 0) { replyEditorContainer.Show(); - replyEditorContainer.Add(new ReplyCommentEditor(Comment) + replyEditorContainer.Add(new ReplyCommentEditor(Comment, Meta) { OnPost = comments => { diff --git a/osu.Game/Overlays/Comments/ReplyCommentEditor.cs b/osu.Game/Overlays/Comments/ReplyCommentEditor.cs index d5ae4f92ab..8350887ec0 100644 --- a/osu.Game/Overlays/Comments/ReplyCommentEditor.cs +++ b/osu.Game/Overlays/Comments/ReplyCommentEditor.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Localisation; @@ -26,12 +27,12 @@ namespace osu.Game.Overlays.Comments protected override LocalisableString GetButtonText(bool isLoggedIn) => isLoggedIn ? CommonStrings.ButtonsReply : CommentsStrings.GuestButtonReply; - protected override LocalisableString GetPlaceholderText(bool isLoggedIn) => - isLoggedIn ? CommentsStrings.PlaceholderReply : AuthorizationStrings.RequireLogin; + protected override LocalisableString GetPlaceholderText() => CommentsStrings.PlaceholderReply; - public ReplyCommentEditor(Comment parent) + public ReplyCommentEditor(Comment parent, IEnumerable meta) { parentComment = parent; + CommentableMeta.Value = meta.SingleOrDefault(m => m.Id == parent.CommentableId && m.Type == parent.CommentableType); } protected override void LoadComplete() From 4723efaf41b56f86ed4817ff81aa873938074bd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Sep 2024 12:52:51 +0200 Subject: [PATCH 342/924] Add failing test coverage for incorrect distance snapping --- ...tSceneHitObjectComposerDistanceSnapping.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs index cf8c3c6ef1..700aafb62d 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs @@ -227,6 +227,42 @@ namespace osu.Game.Tests.Editing assertSnappedDistance(400, 400); } + [Test] + public void TestUnsnappedObject() + { + var slider = new Slider + { + StartTime = 0, + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint(), + // simulate object snapped to 1/3rds + // this object's end time will be 2000 / 3 = 666.66... ms + new PathControlPoint(new Vector2(200 / 3f, 0)), + } + } + }; + + AddStep("add slider", () => composer.EditorBeatmap.Add(slider)); + AddStep("set snap to 1/4", () => BeatDivisor.Value = 4); + + // with default beat length of 1000ms and snap at 1/4, the valid snap times are 500ms, 750ms, and 1000ms + // with default settings, the snapped distance will be a tenth of the difference of the time delta + + // (500 - 666.66...) / 10 = -16.66... = -100 / 6 + assertSnappedDistance(0, -100 / 6f, slider); + assertSnappedDistance(7, -100 / 6f, slider); + + // (750 - 666.66...) / 10 = 8.33... = 100 / 12 + assertSnappedDistance(9, 100 / 12f, slider); + assertSnappedDistance(33, 100 / 12f, slider); + + // (1000 - 666.66...) / 10 = 33.33... = 100 / 3 + assertSnappedDistance(34, 100 / 3f, slider); + } + [Test] public void TestUseCurrentSnap() { From 75fc57c34bb4efd1a05bfb1fda7bbe14471b499b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Sep 2024 12:26:08 +0200 Subject: [PATCH 343/924] Fix distance spacing grid displaying incorrectly for unsnapped objects with duration --- .../Sliders/SliderPlacementBlueprint.cs | 2 +- .../Sliders/SliderSelectionBlueprint.cs | 2 +- ...tSceneHitObjectComposerDistanceSnapping.cs | 2 +- .../Editing/TestSceneDistanceSnapGrid.cs | 2 +- .../Edit/ComposerDistanceSnapProvider.cs | 30 ++++++++++++++----- .../Rulesets/Edit/IDistanceSnapProvider.cs | 9 +++++- .../Rulesets/Objects/SliderPathExtensions.cs | 2 +- .../Components/CircularDistanceSnapGrid.cs | 4 +-- 8 files changed, 37 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 6ffe27dc13..cb57c8e6e0 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -401,7 +401,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders if (state == SliderPlacementState.Drawing) HitObject.Path.ExpectedDistance.Value = (float)HitObject.Path.CalculatedDistance; else - HitObject.Path.ExpectedDistance.Value = distanceSnapProvider?.FindSnappedDistance(HitObject, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance; + HitObject.Path.ExpectedDistance.Value = distanceSnapProvider?.FindSnappedDistance(HitObject, (float)HitObject.Path.CalculatedDistance, DistanceSnapTarget.Start) ?? (float)HitObject.Path.CalculatedDistance; bodyPiece.UpdateFrom(HitObject); headCirclePiece.UpdateFrom(HitObject.HeadCircle); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 1debb09099..cd66f8d796 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -269,7 +269,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { double minDistance = distanceSnapProvider?.GetBeatSnapDistanceAt(HitObject, false) * oldVelocityMultiplier ?? 1; // Add a small amount to the proposed distance to make it easier to snap to the full length of the slider. - proposedDistance = distanceSnapProvider?.FindSnappedDistance(HitObject, (float)proposedDistance + 1) ?? proposedDistance; + proposedDistance = distanceSnapProvider?.FindSnappedDistance(HitObject, (float)proposedDistance + 1, DistanceSnapTarget.Start) ?? proposedDistance; proposedDistance = MathHelper.Clamp(proposedDistance, minDistance, HitObject.Path.CalculatedDistance); } diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs index 700aafb62d..2503d5a954 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs @@ -298,7 +298,7 @@ namespace osu.Game.Tests.Editing => AddAssert($"distance = {distance} -> duration = {expectedDuration} (snapped)", () => composer.DistanceSnapProvider.FindSnappedDuration(referenceObject ?? new HitObject(), distance), () => Is.EqualTo(expectedDuration).Within(Precision.FLOAT_EPSILON)); private void assertSnappedDistance(float distance, float expectedDistance, HitObject? referenceObject = null) - => AddAssert($"distance = {distance} -> distance = {expectedDistance} (snapped)", () => composer.DistanceSnapProvider.FindSnappedDistance(referenceObject ?? new HitObject(), distance), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON)); + => AddAssert($"distance = {distance} -> distance = {expectedDistance} (snapped)", () => composer.DistanceSnapProvider.FindSnappedDistance(referenceObject ?? new HitObject(), distance, DistanceSnapTarget.End), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON)); private partial class TestHitObjectComposer : OsuHitObjectComposer { diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs index f2a015402a..c1a788cd22 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs @@ -199,7 +199,7 @@ namespace osu.Game.Tests.Visual.Editing public double FindSnappedDuration(HitObject referenceObject, float distance) => 0; - public float FindSnappedDistance(HitObject referenceObject, float distance) => 0; + public float FindSnappedDistance(HitObject referenceObject, float distance, DistanceSnapTarget target) => 0; } } } diff --git a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs index 979492fd8b..7ed692ad3d 100644 --- a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs @@ -280,22 +280,36 @@ namespace osu.Game.Rulesets.Edit public virtual double FindSnappedDuration(HitObject referenceObject, float distance) => beatSnapProvider.SnapTime(referenceObject.StartTime + DistanceToDuration(referenceObject, distance), referenceObject.StartTime) - referenceObject.StartTime; - public virtual float FindSnappedDistance(HitObject referenceObject, float distance) + public virtual float FindSnappedDistance(HitObject referenceObject, float distance, DistanceSnapTarget target) { - double startTime = referenceObject.StartTime; + double referenceTime; - double actualDuration = startTime + DistanceToDuration(referenceObject, distance); + switch (target) + { + case DistanceSnapTarget.Start: + referenceTime = referenceObject.StartTime; + break; - double snappedEndTime = beatSnapProvider.SnapTime(actualDuration, startTime); + case DistanceSnapTarget.End: + referenceTime = referenceObject.GetEndTime(); + break; - double beatLength = beatSnapProvider.GetBeatLengthAtTime(startTime); + default: + throw new ArgumentOutOfRangeException(nameof(target), target, $"Unknown {nameof(DistanceSnapTarget)} value"); + } + + double actualDuration = referenceTime + DistanceToDuration(referenceObject, distance); + + double snappedTime = beatSnapProvider.SnapTime(actualDuration, referenceTime); + + double beatLength = beatSnapProvider.GetBeatLengthAtTime(referenceTime); // we don't want to exceed the actual duration and snap to a point in the future. // as we are snapping to beat length via SnapTime (which will round-to-nearest), check for snapping in the forward direction and reverse it. - if (snappedEndTime > actualDuration + 1) - snappedEndTime -= beatLength; + if (snappedTime > actualDuration + 1) + snappedTime -= beatLength; - return DurationToDistance(referenceObject, snappedEndTime - startTime); + return DurationToDistance(referenceObject, snappedTime - referenceTime); } #endregion diff --git a/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs index 380038eadf..17fae9e8b2 100644 --- a/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs @@ -58,10 +58,17 @@ namespace osu.Game.Rulesets.Edit /// /// An object to be used as a reference point for this operation. /// The distance to convert. + /// Whether the distance measured should be from the start or the end of . /// /// A value that represents snapped to the closest beat of the timing point. /// The distance will always be less than or equal to the provided . /// - float FindSnappedDistance(HitObject referenceObject, float distance); + float FindSnappedDistance(HitObject referenceObject, float distance, DistanceSnapTarget target); + } + + public enum DistanceSnapTarget + { + Start, + End, } } diff --git a/osu.Game/Rulesets/Objects/SliderPathExtensions.cs b/osu.Game/Rulesets/Objects/SliderPathExtensions.cs index c03d3646da..a631274f74 100644 --- a/osu.Game/Rulesets/Objects/SliderPathExtensions.cs +++ b/osu.Game/Rulesets/Objects/SliderPathExtensions.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Objects public static void SnapTo(this THitObject hitObject, IDistanceSnapProvider? snapProvider) where THitObject : HitObject, IHasPath { - hitObject.Path.ExpectedDistance.Value = snapProvider?.FindSnappedDistance(hitObject, (float)hitObject.Path.CalculatedDistance) ?? hitObject.Path.CalculatedDistance; + hitObject.Path.ExpectedDistance.Value = snapProvider?.FindSnappedDistance(hitObject, (float)hitObject.Path.CalculatedDistance, DistanceSnapTarget.Start) ?? hitObject.Path.CalculatedDistance; } /// diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index 92fe52148c..bd750dac76 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -59,7 +59,7 @@ namespace osu.Game.Screens.Edit.Compose.Components // Picture the scenario where the user has just placed an object on a 1/2 snap, then changes to // 1/3 snap and expects to be able to place the next object on a valid 1/3 snap, regardless of the // fact that the 1/2 snap reference object is not valid for 1/3 snapping. - float offset = SnapProvider.FindSnappedDistance(ReferenceObject, 0); + float offset = SnapProvider.FindSnappedDistance(ReferenceObject, 0, DistanceSnapTarget.End); for (int i = 0; i < requiredCircles; i++) { @@ -104,7 +104,7 @@ namespace osu.Game.Screens.Edit.Compose.Components ? SnapProvider.DurationToDistance(ReferenceObject, editorClock.CurrentTime - ReferenceObject.GetEndTime()) // When interacting with the resolved snap provider, the distance spacing multiplier should first be removed // to allow for snapping at a non-multiplied ratio. - : SnapProvider.FindSnappedDistance(ReferenceObject, travelLength / distanceSpacingMultiplier); + : SnapProvider.FindSnappedDistance(ReferenceObject, travelLength / distanceSpacingMultiplier, DistanceSnapTarget.End); double snappedTime = StartTime + SnapProvider.DistanceToDuration(ReferenceObject, snappedDistance); From 11fc1f9a1c632b0ecac600d80e87cfe3345fd6f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Sep 2024 13:32:19 +0200 Subject: [PATCH 344/924] Fix distance snap grid using wrong colour when reference object is unsnapped --- osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 8aa2fa9f45..7003d632ca 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -155,7 +155,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { var timingPoint = Beatmap.ControlPointInfo.TimingPointAt(StartTime); double beatLength = timingPoint.BeatLength / beatDivisor.Value; - int beatIndex = (int)Math.Round((StartTime - timingPoint.Time) / beatLength); + int beatIndex = (int)Math.Floor((StartTime - timingPoint.Time) / beatLength); var colour = BindableBeatDivisor.GetColourFor(BindableBeatDivisor.GetDivisorForBeatIndex(beatIndex + placementIndex + 1, beatDivisor.Value), Colours); From 155d6e57be1805c83ff12316e515d8bb4d06ec00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Sep 2024 14:05:20 +0200 Subject: [PATCH 345/924] Isolate tests properly --- .../Visual/UserInterface/TestSceneCommentEditor.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneCommentEditor.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneCommentEditor.cs index ac6ca218c4..721e231577 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneCommentEditor.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneCommentEditor.cs @@ -29,9 +29,10 @@ namespace osu.Game.Tests.Visual.UserInterface private TestCancellableCommentEditor cancellableCommentEditor = null!; private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; - [SetUp] - public void SetUp() => Schedule(() => - Add(new FillFlowContainer + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create content", () => Child = new FillFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -44,7 +45,8 @@ namespace osu.Game.Tests.Visual.UserInterface commentEditor = new TestCommentEditor(), cancellableCommentEditor = new TestCancellableCommentEditor() } - })); + }); + } [Test] public void TestCommitViaKeyboard() From 74a9899fc078528fc70c17fef32370b8cb54283f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Sep 2024 14:05:23 +0200 Subject: [PATCH 346/924] Fix doubled-up enabled state management of commit button --- osu.Game/Overlays/Comments/CommentEditor.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/Comments/CommentEditor.cs b/osu.Game/Overlays/Comments/CommentEditor.cs index b75e5aa8d8..ccb912253a 100644 --- a/osu.Game/Overlays/Comments/CommentEditor.cs +++ b/osu.Game/Overlays/Comments/CommentEditor.cs @@ -68,7 +68,7 @@ namespace osu.Game.Overlays.Comments else loadingSpinner.Hide(); - updateCommitButtonState(); + updateState(); } } @@ -170,17 +170,15 @@ namespace osu.Game.Overlays.Comments protected override void LoadComplete() { base.LoadComplete(); - Current.BindValueChanged(_ => updateCommitButtonState(), true); - apiState.BindValueChanged(_ => updateEnabledState()); - CommentableMeta.BindValueChanged(_ => updateEnabledState(), true); + Current.BindValueChanged(_ => updateState()); + apiState.BindValueChanged(_ => Scheduler.AddOnce(updateState)); + CommentableMeta.BindValueChanged(_ => Scheduler.AddOnce(updateState)); + updateState(); } protected abstract void OnCommit(string text); - private void updateCommitButtonState() => - commitButton.Enabled.Value = loadingSpinner.State.Value == Visibility.Hidden && !string.IsNullOrEmpty(Current.Value); - - private void updateEnabledState() => Schedule(() => + private void updateState() { bool isOnline = apiState.Value > APIState.Offline; var canNewCommentReason = CommentEditor.canNewCommentReason(CommentableMeta.Value); @@ -198,7 +196,7 @@ namespace osu.Game.Overlays.Comments if (isOnline) { commitButton.Show(); - commitButton.Enabled.Value = !commentsDisabled; + commitButton.Enabled.Value = !commentsDisabled && loadingSpinner.State.Value == Visibility.Hidden && !string.IsNullOrEmpty(Current.Value); logInButton.Hide(); } else @@ -206,7 +204,7 @@ namespace osu.Game.Overlays.Comments commitButton.Hide(); logInButton.Show(); } - }); + } // https://github.com/ppy/osu-web/blob/83816dbe24ad2927273cba968f2fcd2694a121a9/resources/js/components/comment-editor.tsx#L54-L60 // careful here, logic is VERY finicky. From 48b03a328b3debf30c64fafb618e981c90fd0524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Sep 2024 14:26:30 +0200 Subject: [PATCH 347/924] Ensure sliders are snapped when changing path types Closes https://github.com/ppy/osu/issues/29915. Uses behaviour suggested in https://github.com/ppy/osu/issues/29915#issuecomment-2361843011. --- .../Sliders/Components/PathControlPointVisualiser.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index df369dcef5..d90aab5788 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -353,6 +353,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { changeHandler?.BeginChange(); + double originalDistance = hitObject.Path.Distance; + foreach (var p in Pieces.Where(p => p.IsSelected.Value)) { var pointsInSegment = hitObject.Path.PointsInSegment(p.ControlPoint); @@ -375,6 +377,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components EnsureValidPathTypes(); + if (hitObject.Path.Distance < originalDistance) + hitObject.SnapTo(distanceSnapProvider); + else + hitObject.Path.ExpectedDistance.Value = originalDistance; + changeHandler?.EndChange(); } From 493dcc7a1cc987c0e02b7a9d1272792ba84ae76a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Sep 2024 14:32:11 +0200 Subject: [PATCH 348/924] Fix test being dodgy Hitobjects are in an indeterminate state until defaults are applied. Adding the object to the beatmap will do this. --- .../Editing/TestSceneHitObjectComposerDistanceSnapping.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs index cf8c3c6ef1..d16199b0f5 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs @@ -112,6 +112,7 @@ namespace osu.Game.Tests.Editing { SliderVelocityMultiplier = slider_velocity }; + AddStep("add to beatmap", () => composer.EditorBeatmap.Add(referenceObject)); assertSnapDistance(base_distance * slider_velocity, referenceObject, true); assertSnappedDistance(base_distance * slider_velocity + 10, base_distance * slider_velocity, referenceObject); From 0409edccce27fc7dfb11fb39ce4a1a6722d7ca6a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Oct 2024 15:14:40 +0900 Subject: [PATCH 349/924] Add button to centre editor grid to current hit object --- .../Edit/OsuGridToolboxGroup.cs | 24 +++++++++++++++++++ osu.Game/Rulesets/Edit/ExpandableButton.cs | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 73ecb2fe7c..0fe9d85635 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -14,6 +14,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.RadioButtons; @@ -90,6 +91,8 @@ namespace osu.Game.Rulesets.Osu.Edit private ExpandableSlider gridLinesRotationSlider = null!; private EditorRadioButtonCollection gridTypeButtons = null!; + private ExpandableButton useSelectedObjectPositionButton = null!; + public OsuGridToolboxGroup() : base("grid") { @@ -112,6 +115,19 @@ namespace osu.Game.Rulesets.Osu.Edit Current = StartPositionY, KeyboardStep = 1, }, + useSelectedObjectPositionButton = new ExpandableButton + { + ExpandedLabelText = "Centre on selected object", + Action = () => + { + if (editorBeatmap.SelectedHitObjects.Count != 1) + return; + + StartPosition.Value = ((IHasPosition)editorBeatmap.SelectedHitObjects.Single()).Position; + updateEnabledStates(); + }, + RelativeSizeAxes = Axes.X, + }, spacingSlider = new ExpandableSlider { Current = Spacing, @@ -211,6 +227,14 @@ namespace osu.Game.Rulesets.Osu.Edit break; } }, true); + + editorBeatmap.BeatmapReprocessed += updateEnabledStates; + editorBeatmap.SelectedHitObjects.BindCollectionChanged((_, _) => updateEnabledStates(), true); + } + + private void updateEnabledStates() + { + useSelectedObjectPositionButton.Enabled.Value = editorBeatmap.SelectedHitObjects.Count == 1 && StartPosition.Value != ((IHasPosition)editorBeatmap.SelectedHitObjects.Single()).Position; } private void nextGridSize() diff --git a/osu.Game/Rulesets/Edit/ExpandableButton.cs b/osu.Game/Rulesets/Edit/ExpandableButton.cs index a708f76845..9139802d68 100644 --- a/osu.Game/Rulesets/Edit/ExpandableButton.cs +++ b/osu.Game/Rulesets/Edit/ExpandableButton.cs @@ -11,7 +11,7 @@ using osu.Game.Graphics.UserInterfaceV2; namespace osu.Game.Rulesets.Edit { - internal partial class ExpandableButton : RoundedButton, IExpandable + public partial class ExpandableButton : RoundedButton, IExpandable { private float actualHeight; From 0a78eb96289c5f38ab8447daeaedbcb55a290a55 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 29 Aug 2024 21:54:47 +0200 Subject: [PATCH 350/924] Implement auto additions editor-only --- osu.Game/Audio/HitSampleInfo.cs | 13 +++- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- osu.Game/Rulesets/Objects/HitObject.cs | 2 +- .../Objects/Legacy/ConvertHitObjectParser.cs | 33 ++++++---- .../Components/EditorSelectionHandler.cs | 60 ++++++++----------- .../Components/Timeline/SamplePointPiece.cs | 37 ++++++++---- 6 files changed, 87 insertions(+), 60 deletions(-) diff --git a/osu.Game/Audio/HitSampleInfo.cs b/osu.Game/Audio/HitSampleInfo.cs index ce5e217532..19273e3714 100644 --- a/osu.Game/Audio/HitSampleInfo.cs +++ b/osu.Game/Audio/HitSampleInfo.cs @@ -60,12 +60,18 @@ namespace osu.Game.Audio /// public int Volume { get; } - public HitSampleInfo(string name, string bank = SampleControlPoint.DEFAULT_BANK, string? suffix = null, int volume = 100) + /// + /// Whether this sample should automatically assign the bank of the normal sample whenever it is set in the editor. + /// + public bool EditorAutoBank { get; } + + public HitSampleInfo(string name, string bank = SampleControlPoint.DEFAULT_BANK, string? suffix = null, int volume = 100, bool editorAutoBank = true) { Name = name; Bank = bank; Suffix = suffix; Volume = volume; + EditorAutoBank = editorAutoBank; } /// @@ -92,9 +98,10 @@ namespace osu.Game.Audio /// An optional new sample bank. /// An optional new lookup suffix. /// An optional new volume. + /// An optional new editor auto bank flag. /// The new . - public virtual HitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newSuffix = default, Optional newVolume = default) - => new HitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newSuffix.GetOr(Suffix), newVolume.GetOr(Volume)); + public virtual HitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newSuffix = default, Optional newVolume = default, Optional newEditorAutoBank = default) + => new HitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newSuffix.GetOr(Suffix), newVolume.GetOr(Volume), newEditorAutoBank.GetOr(EditorAutoBank)); public virtual bool Equals(HitSampleInfo? other) => other != null && Name == other.Name && Bank == other.Bank && Suffix == other.Suffix; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index b0173b3ae3..c14648caf6 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -539,7 +539,7 @@ namespace osu.Game.Beatmaps.Formats private string getSampleBank(IList samples, bool banksOnly = false) { LegacySampleBank normalBank = toLegacySampleBank(samples.SingleOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank); - LegacySampleBank addBank = toLegacySampleBank(samples.FirstOrDefault(s => !string.IsNullOrEmpty(s.Name) && s.Name != HitSampleInfo.HIT_NORMAL)?.Bank); + LegacySampleBank addBank = toLegacySampleBank(samples.FirstOrDefault(s => !string.IsNullOrEmpty(s.Name) && s.Name != HitSampleInfo.HIT_NORMAL && !s.EditorAutoBank)?.Bank); StringBuilder sb = new StringBuilder(); diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index c547a7a718..9f980769e2 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -233,7 +233,7 @@ namespace osu.Game.Rulesets.Objects // Fall back to using the normal sample bank otherwise. if (Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL) is HitSampleInfo existingNormal) - return existingNormal.With(newName: sampleName); + return existingNormal.With(newName: sampleName, newEditorAutoBank: true); return new HitSampleInfo(sampleName); } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index c518a3e8b2..fa3ee41b47 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -204,8 +204,14 @@ namespace osu.Game.Rulesets.Objects.Legacy if (stringBank == @"none") stringBank = null; string stringAddBank = addBank.ToString().ToLowerInvariant(); + if (stringAddBank == @"none") + { + bankInfo.EditorAutoBank = true; stringAddBank = null; + } + else + bankInfo.EditorAutoBank = false; bankInfo.BankForNormal = stringBank; bankInfo.BankForAdditions = string.IsNullOrEmpty(stringAddBank) ? stringBank : stringAddBank; @@ -477,7 +483,7 @@ namespace osu.Game.Rulesets.Objects.Legacy if (string.IsNullOrEmpty(bankInfo.Filename)) { - soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_NORMAL, bankInfo.BankForNormal, bankInfo.Volume, bankInfo.CustomSampleBank, + soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_NORMAL, bankInfo.BankForNormal, bankInfo.Volume, true, bankInfo.CustomSampleBank, // if the sound type doesn't have the Normal flag set, attach it anyway as a layered sample. // None also counts as a normal non-layered sample: https://osu.ppy.sh/help/wiki/osu!_File_Formats/Osu_(file_format)#hitsounds type != LegacyHitSoundType.None && !type.HasFlag(LegacyHitSoundType.Normal))); @@ -489,13 +495,13 @@ namespace osu.Game.Rulesets.Objects.Legacy } if (type.HasFlag(LegacyHitSoundType.Finish)) - soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_FINISH, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.CustomSampleBank)); + soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_FINISH, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.EditorAutoBank, bankInfo.CustomSampleBank)); if (type.HasFlag(LegacyHitSoundType.Whistle)) - soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_WHISTLE, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.CustomSampleBank)); + soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_WHISTLE, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.EditorAutoBank, bankInfo.CustomSampleBank)); if (type.HasFlag(LegacyHitSoundType.Clap)) - soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_CLAP, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.CustomSampleBank)); + soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_CLAP, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.EditorAutoBank, bankInfo.CustomSampleBank)); return soundTypes; } @@ -534,6 +540,11 @@ namespace osu.Game.Rulesets.Objects.Legacy /// public int CustomSampleBank; + /// + /// Whether the bank for additions should be inherited from the normal sample in edit. + /// + public bool EditorAutoBank; + public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone(); } @@ -558,21 +569,21 @@ namespace osu.Game.Rulesets.Objects.Legacy /// public bool BankSpecified; - public LegacyHitSampleInfo(string name, string? bank = null, int volume = 0, int customSampleBank = 0, bool isLayered = false) - : base(name, bank ?? SampleControlPoint.DEFAULT_BANK, customSampleBank >= 2 ? customSampleBank.ToString() : null, volume) + public LegacyHitSampleInfo(string name, string? bank = null, int volume = 0, bool editorAutoBank = false, int customSampleBank = 0, bool isLayered = false) + : base(name, bank ?? SampleControlPoint.DEFAULT_BANK, customSampleBank >= 2 ? customSampleBank.ToString() : null, volume, editorAutoBank) { CustomSampleBank = customSampleBank; BankSpecified = !string.IsNullOrEmpty(bank); IsLayered = isLayered; } - public sealed override HitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newSuffix = default, Optional newVolume = default) - => With(newName, newBank, newVolume); + public sealed override HitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newSuffix = default, Optional newVolume = default, Optional newEditorAutoBank = default) + => With(newName, newBank, newVolume, newEditorAutoBank); - public virtual LegacyHitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newVolume = default, + public virtual LegacyHitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newVolume = default, Optional newEditorAutoBank = default, Optional newCustomSampleBank = default, Optional newIsLayered = default) - => new LegacyHitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newVolume.GetOr(Volume), newCustomSampleBank.GetOr(CustomSampleBank), newIsLayered.GetOr(IsLayered)); + => new LegacyHitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newVolume.GetOr(Volume), newEditorAutoBank.GetOr(EditorAutoBank), newCustomSampleBank.GetOr(CustomSampleBank), newIsLayered.GetOr(IsLayered)); public bool Equals(LegacyHitSampleInfo? other) // The additions to equality checks here are *required* to ensure that pooling works correctly. @@ -604,7 +615,7 @@ namespace osu.Game.Rulesets.Objects.Legacy Path.ChangeExtension(Filename, null) }; - public sealed override LegacyHitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newVolume = default, + public sealed override LegacyHitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newVolume = default, Optional newEditorAutoBank = default, Optional newCustomSampleBank = default, Optional newIsLayered = default) => new FileHitSampleInfo(Filename, newVolume.GetOr(Volume)); diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index 769240839a..8de3f3052f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -152,10 +152,6 @@ namespace osu.Game.Screens.Edit.Compose.Components } else { - // Auto should never apply when there is a selection made. - if (bankName == HIT_BANK_AUTO) - break; - // Completely empty selections should be allowed in the case that none of the selected objects have any addition samples. // This is also required to stop a bindable feedback loop when a HitObject has zero addition samples (and LINQ `All` below becomes true). if (SelectedItems.SelectMany(enumerateAllSamples).All(h => h.All(o => o.Name == HitSampleInfo.HIT_NORMAL))) @@ -163,8 +159,16 @@ namespace osu.Game.Screens.Edit.Compose.Components // Never remove a sample bank. // These are basically radio buttons, not toggles. - if (SelectedItems.SelectMany(enumerateAllSamples).All(h => h.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName))) - bindable.Value = TernaryState.True; + if (bankName == HIT_BANK_AUTO) + { + if (SelectedItems.SelectMany(enumerateAllSamples).All(h => h.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.EditorAutoBank))) + bindable.Value = TernaryState.True; + } + else + { + if (SelectedItems.SelectMany(enumerateAllSamples).All(h => h.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName && !s.EditorAutoBank))) + bindable.Value = TernaryState.True; + } } break; @@ -182,14 +186,6 @@ namespace osu.Game.Screens.Edit.Compose.Components } else { - // Auto should just not apply if there's a selection already made. - // Maybe we could make it a disabled button in the future, but right now the editor buttons don't support disabled state. - if (bankName == HIT_BANK_AUTO) - { - bindable.Value = TernaryState.False; - break; - } - // If none of the selected objects have any addition samples, we should not apply the addition bank. if (SelectedItems.SelectMany(enumerateAllSamples).All(h => h.All(o => o.Name == HitSampleInfo.HIT_NORMAL))) { @@ -209,7 +205,7 @@ namespace osu.Game.Screens.Edit.Compose.Components // start with normal selected. SelectionBankStates[SampleControlPoint.DEFAULT_BANK].Value = TernaryState.True; - SelectionAdditionBankStates[SampleControlPoint.DEFAULT_BANK].Value = TernaryState.True; + SelectionAdditionBankStates[HIT_BANK_AUTO].Value = TernaryState.True; foreach (string sampleName in HitSampleInfo.AllAdditions) { @@ -272,7 +268,7 @@ namespace osu.Game.Screens.Edit.Compose.Components foreach ((string bankName, var bindable) in SelectionAdditionBankStates) { - bindable.Value = GetStateFromSelection(samplesInSelection.SelectMany(s => s).Where(o => o.Name != HitSampleInfo.HIT_NORMAL), h => h.Bank == bankName); + bindable.Value = GetStateFromSelection(samplesInSelection.SelectMany(s => s).Where(o => o.Name != HitSampleInfo.HIT_NORMAL), h => (bankName != HIT_BANK_AUTO && h.Bank == bankName && !h.EditorAutoBank) || (bankName == HIT_BANK_AUTO && h.EditorAutoBank)); } } @@ -336,33 +332,29 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The name of the sample bank. public void SetSampleAdditionBank(string bankName) { - bool hasRelevantBank(HitObject hitObject) - { - bool result = hitObject.Samples.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName); - - if (hitObject is IHasRepeats hasRepeats) - { - foreach (var node in hasRepeats.NodeSamples) - result &= node.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName); - } - - return result; - } + bool hasRelevantBank(HitObject hitObject) => + bankName == HIT_BANK_AUTO + ? enumerateAllSamples(hitObject).SelectMany(o => o).Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.EditorAutoBank) + : enumerateAllSamples(hitObject).SelectMany(o => o).Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName && !s.EditorAutoBank); if (SelectedItems.All(hasRelevantBank)) return; EditorBeatmap.PerformOnSelection(h => { - if (enumerateAllSamples(h).SelectMany(o => o).Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName)) + if (hasRelevantBank(h)) return; - h.Samples = h.Samples.Select(s => s.Name != HitSampleInfo.HIT_NORMAL ? s.With(newBank: bankName) : s).ToList(); + string normalBank = h.Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank ?? HitSampleInfo.BANK_SOFT; + h.Samples = h.Samples.Select(s => s.Name != HitSampleInfo.HIT_NORMAL ? bankName == HIT_BANK_AUTO ? s.With(newBank: normalBank, newEditorAutoBank: true) : s.With(newBank: bankName, newEditorAutoBank: false) : s).ToList(); if (h is IHasRepeats hasRepeats) { for (int i = 0; i < hasRepeats.NodeSamples.Count; ++i) - hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(s => s.Name != HitSampleInfo.HIT_NORMAL ? s.With(newBank: bankName) : s).ToList(); + { + normalBank = hasRepeats.NodeSamples[i].FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank ?? HitSampleInfo.BANK_SOFT; + hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(s => s.Name != HitSampleInfo.HIT_NORMAL ? bankName == HIT_BANK_AUTO ? s.With(newBank: normalBank, newEditorAutoBank: true) : s.With(newBank: bankName, newEditorAutoBank: false) : s).ToList(); + } } EditorBeatmap.Update(h); @@ -406,9 +398,9 @@ namespace osu.Game.Screens.Edit.Compose.Components var hitSample = h.CreateHitSampleInfo(sampleName); - string? existingAdditionBank = node.FirstOrDefault(s => s.Name != HitSampleInfo.HIT_NORMAL)?.Bank; - if (existingAdditionBank != null) - hitSample = hitSample.With(newBank: existingAdditionBank); + HitSampleInfo? existingAddition = node.FirstOrDefault(s => s.Name != HitSampleInfo.HIT_NORMAL); + if (existingAddition != null) + hitSample = hitSample.With(newBank: existingAddition.Bank, newEditorAutoBank: existingAddition.EditorAutoBank); node.Add(hitSample); } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 6276090557..07833694c5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -107,7 +107,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public static string? GetAdditionBankValue(IEnumerable samples) { - return samples.FirstOrDefault(o => o.Name != HitSampleInfo.HIT_NORMAL)?.Bank; + var firstAddition = samples.FirstOrDefault(o => o.Name != HitSampleInfo.HIT_NORMAL); + if (firstAddition == null) + return null; + + return firstAddition.EditorAutoBank ? EditorSelectionHandler.HIT_BANK_AUTO : firstAddition.Bank; } public static int GetVolumeValue(ICollection samples) @@ -320,7 +324,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { for (int i = 0; i < relevantSamples.Count; i++) { - if (relevantSamples[i].Name != HitSampleInfo.HIT_NORMAL) continue; + if (relevantSamples[i].Name != HitSampleInfo.HIT_NORMAL && !relevantSamples[i].EditorAutoBank) continue; relevantSamples[i] = relevantSamples[i].With(newBank: newBank); } @@ -331,11 +335,20 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { updateAllRelevantSamples((_, relevantSamples) => { + string normalBank = relevantSamples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank ?? HitSampleInfo.BANK_SOFT; + for (int i = 0; i < relevantSamples.Count; i++) { - if (relevantSamples[i].Name == HitSampleInfo.HIT_NORMAL) continue; + if (relevantSamples[i].Name == HitSampleInfo.HIT_NORMAL) + continue; - relevantSamples[i] = relevantSamples[i].With(newBank: newBank); + // Addition samples with bank set to auto should inherit the bank of the normal sample + if (newBank == EditorSelectionHandler.HIT_BANK_AUTO) + { + relevantSamples[i] = relevantSamples[i].With(newBank: normalBank, newEditorAutoBank: true); + } + else + relevantSamples[i] = relevantSamples[i].With(newBank: newBank, newEditorAutoBank: false); } }); } @@ -383,7 +396,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline selectionSampleStates[sampleName] = bindable; } - banks.AddRange(HitSampleInfo.AllBanks); + banks.AddRange(HitSampleInfo.AllBanks.Prepend(EditorSelectionHandler.HIT_BANK_AUTO)); } private void updateTernaryStates() @@ -448,7 +461,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (string.IsNullOrEmpty(newBank)) return true; - if (e.ShiftPressed) + if (e.ShiftPressed && newBank != EditorSelectionHandler.HIT_BANK_AUTO) { setBank(newBank); updatePrimaryBankState(); @@ -462,7 +475,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } else { - var item = togglesCollection.ElementAtOrDefault(rightIndex); + var item = togglesCollection.ElementAtOrDefault(rightIndex - 1); if (item is not DrawableTernaryButton button) return base.OnKeyDown(e); @@ -476,18 +489,22 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { switch (key) { - case Key.W: + case Key.Q: index = 0; break; - case Key.E: + case Key.W: index = 1; break; - case Key.R: + case Key.E: index = 2; break; + case Key.R: + index = 3; + break; + default: index = -1; break; From 861da968d4d3fce9ec40149622e6b2bcba973098 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Oct 2024 16:48:20 +0900 Subject: [PATCH 351/924] Fix banana override --- osu.Game.Rulesets.Catch/Objects/Banana.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Banana.cs b/osu.Game.Rulesets.Catch/Objects/Banana.cs index b80527f379..b724c50d0f 100644 --- a/osu.Game.Rulesets.Catch/Objects/Banana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Banana.cs @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Catch.Objects { } - public sealed override HitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newSuffix = default, Optional newVolume = default) + public sealed override HitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newSuffix = default, Optional newVolume = default, Optional newEditorAutoBank = default) => new BananaHitSampleInfo(newVolume.GetOr(Volume)); public bool Equals(BananaHitSampleInfo? other) From 8d2f2517a312d5482f8a4719d3f6a4c008a34fa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Oct 2024 10:01:31 +0200 Subject: [PATCH 352/924] Specify type explicitly --- osu.Game/Overlays/Comments/CommentEditor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Comments/CommentEditor.cs b/osu.Game/Overlays/Comments/CommentEditor.cs index ccb912253a..c456592383 100644 --- a/osu.Game/Overlays/Comments/CommentEditor.cs +++ b/osu.Game/Overlays/Comments/CommentEditor.cs @@ -181,7 +181,7 @@ namespace osu.Game.Overlays.Comments private void updateState() { bool isOnline = apiState.Value > APIState.Offline; - var canNewCommentReason = CommentEditor.canNewCommentReason(CommentableMeta.Value); + LocalisableString? canNewCommentReason = CommentEditor.canNewCommentReason(CommentableMeta.Value); bool commentsDisabled = canNewCommentReason != null; bool canComment = isOnline && !commentsDisabled; From a2d9302f4ad472b23c102794a43a4cc9879aa5bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Oct 2024 10:12:38 +0200 Subject: [PATCH 353/924] Move shuffle button to left side --- osu.Game/Overlays/NowPlayingOverlay.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index e1e5aa9426..f4da9a92dc 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -164,15 +164,16 @@ namespace osu.Game.Overlays Action = () => musicController.NextTrack(), Icon = FontAwesome.Solid.StepForward, }, - shuffleButton = new MusicIconButton - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Action = shuffle.Toggle, - Icon = FontAwesome.Solid.Random, - } } }, + shuffleButton = new MusicIconButton + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + Position = new Vector2(bottom_black_area_height / 2, 0), + Action = shuffle.Toggle, + Icon = FontAwesome.Solid.Random, + }, playlistButton = new MusicIconButton { Origin = Anchor.Centre, From 2a214f7c9fb82de43c0b36ef4aab3d4cea301dca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Oct 2024 10:19:59 +0200 Subject: [PATCH 354/924] Fix incorrect implementation of next track choice `SkipWhile()` in this context does not correctly ensure that `ElementAtOrDefault(1)` is not a protected track. An explicit `Where()` does. Spotted accidentally when I noticed that skipping to next track can select a protected track, but skipping to previous cannot. --- osu.Game/Overlays/MusicController.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index a7bca536df..600c014a95 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -351,7 +351,9 @@ namespace osu.Game.Overlays playableSet = getNextRandom(1, allowProtectedTracks); else { - playableSet = getBeatmapSets().AsEnumerable().SkipWhile(i => !i.Equals(current?.BeatmapSetInfo) || (i.Protected && !allowProtectedTracks)).ElementAtOrDefault(1) + playableSet = getBeatmapSets().AsEnumerable().SkipWhile(i => !i.Equals(current?.BeatmapSetInfo)) + .Where(i => !i.Protected || allowProtectedTracks) + .ElementAtOrDefault(1) ?? getBeatmapSets().AsEnumerable().FirstOrDefault(i => !i.Protected || allowProtectedTracks); } From ad3007eaadf62916e9af4616e08e2942d0756b44 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Oct 2024 17:00:32 +0900 Subject: [PATCH 355/924] Adjust `ILocalUserPlayInfo` to expose whether gameplay is in a paused/break state --- osu.Desktop/Windows/GameplayWinKeyBlocker.cs | 6 ++-- .../BackgroundDataStoreProcessorTests.cs | 10 +++--- .../Input/ConfineMouseTrackerTest.cs | 15 +++++++- .../Gameplay/TestSceneOverlayActivation.cs | 4 +-- .../TestSceneGameplayChatDisplay.cs | 6 ++-- .../Database/BackgroundDataStoreProcessor.cs | 2 +- osu.Game/Input/ConfineMouseTracker.cs | 8 ++--- osu.Game/Input/OsuUserInputManager.cs | 5 +-- osu.Game/OsuGame.cs | 36 +++++++++---------- osu.Game/Overlays/BeatmapListingOverlay.cs | 2 +- osu.Game/Overlays/Chat/DaySeparator.cs | 4 +-- osu.Game/Overlays/Settings/SettingsSidebar.cs | 2 +- .../Multiplayer/GameplayChatDisplay.cs | 8 ++--- osu.Game/Screens/Play/ILocalUserPlayInfo.cs | 4 +-- .../Screens/Play/LocalUserPlayingStates.cs | 23 ++++++++++++ osu.Game/Screens/Play/Player.cs | 19 ++++++---- 16 files changed, 97 insertions(+), 57 deletions(-) create mode 100644 osu.Game/Screens/Play/LocalUserPlayingStates.cs diff --git a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs index 560f6fdd7f..268dacc077 100644 --- a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs +++ b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs @@ -13,7 +13,7 @@ namespace osu.Desktop.Windows public partial class GameplayWinKeyBlocker : Component { private Bindable disableWinKey = null!; - private IBindable localUserPlaying = null!; + private IBindable localUserPlaying = null!; private IBindable isActive = null!; [Resolved] @@ -22,7 +22,7 @@ namespace osu.Desktop.Windows [BackgroundDependencyLoader] private void load(ILocalUserPlayInfo localUserInfo, OsuConfigManager config) { - localUserPlaying = localUserInfo.IsPlaying.GetBoundCopy(); + localUserPlaying = localUserInfo.PlayingState.GetBoundCopy(); localUserPlaying.BindValueChanged(_ => updateBlocking()); isActive = host.IsActive.GetBoundCopy(); @@ -34,7 +34,7 @@ namespace osu.Desktop.Windows private void updateBlocking() { - bool shouldDisable = isActive.Value && disableWinKey.Value && localUserPlaying.Value; + bool shouldDisable = isActive.Value && disableWinKey.Value && localUserPlaying.Value == LocalUserPlayingStates.Playing; if (shouldDisable) host.InputThread.Scheduler.Add(WindowsKey.Disable); diff --git a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs index f9f9fa2622..21193b5161 100644 --- a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs +++ b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs @@ -22,9 +22,9 @@ namespace osu.Game.Tests.Database [HeadlessTest] public partial class BackgroundDataStoreProcessorTests : OsuTestScene, ILocalUserPlayInfo { - public IBindable IsPlaying => isPlaying; + public IBindable PlayingState => isPlaying; - private readonly Bindable isPlaying = new Bindable(); + private readonly Bindable isPlaying = new Bindable(); private BeatmapSetInfo importedSet = null!; @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Database [SetUpSteps] public void SetUpSteps() { - AddStep("Set not playing", () => isPlaying.Value = false); + AddStep("Set not playing", () => isPlaying.Value = LocalUserPlayingStates.NotPlaying); } [Test] @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Database }); }); - AddStep("Set playing", () => isPlaying.Value = true); + AddStep("Set playing", () => isPlaying.Value = LocalUserPlayingStates.Playing); AddStep("Reset difficulty", () => { @@ -117,7 +117,7 @@ namespace osu.Game.Tests.Database }); }); - AddStep("Set not playing", () => isPlaying.Value = false); + AddStep("Set not playing", () => isPlaying.Value = LocalUserPlayingStates.NotPlaying); AddUntilStep("wait for difficulties repopulated", () => { diff --git a/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs b/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs index 6b43ab83c5..c4fdb2b427 100644 --- a/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs +++ b/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs @@ -3,11 +3,13 @@ using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Framework.Input; using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Input; +using osu.Game.Screens.Play; using osu.Game.Tests.Visual; namespace osu.Game.Tests.Input @@ -15,9 +17,20 @@ namespace osu.Game.Tests.Input [HeadlessTest] public partial class ConfineMouseTrackerTest : OsuGameTestScene { + private readonly Bindable playingState = new Bindable(); + [Resolved] private FrameworkConfigManager frameworkConfigManager { get; set; } = null!; + [SetUpSteps] + public override void SetUpSteps() + { + base.SetUpSteps(); + + // a bit dodgy. + AddStep("bind playing state", () => ((IBindable)playingState).BindTo(((ILocalUserPlayInfo)Game).PlayingState)); + } + [TestCase(WindowMode.Windowed)] [TestCase(WindowMode.Borderless)] public void TestDisableConfining(WindowMode windowMode) @@ -88,7 +101,7 @@ namespace osu.Game.Tests.Input => AddStep($"set {mode} game-side", () => Game.LocalConfig.SetValue(OsuSetting.ConfineMouseMode, mode)); private void setLocalUserPlayingTo(bool playing) - => AddStep($"local user {(playing ? "playing" : "not playing")}", () => Game.LocalUserPlaying.Value = playing); + => AddStep($"local user {(playing ? "playing" : "not playing")}", () => playingState.Value = playing ? LocalUserPlayingStates.Playing : LocalUserPlayingStates.NotPlaying); private void gameSideModeIs(OsuConfineMouseMode mode) => AddAssert($"mode is {mode} game-side", () => Game.LocalConfig.Get(OsuSetting.ConfineMouseMode) == mode); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs index 269d104fa3..751405b1d6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Game.Overlays; @@ -12,7 +10,7 @@ namespace osu.Game.Tests.Visual.Gameplay { public partial class TestSceneOverlayActivation : OsuPlayerTestScene { - protected new OverlayTestPlayer Player => base.Player as OverlayTestPlayer; + protected new OverlayTestPlayer Player => (OverlayTestPlayer)base.Player; public override void SetUpSteps() { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.cs index d1a914300f..ca3d484115 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.cs @@ -25,14 +25,14 @@ namespace osu.Game.Tests.Visual.Multiplayer [Cached(typeof(ILocalUserPlayInfo))] private ILocalUserPlayInfo localUserInfo; - private readonly Bindable localUserPlaying = new Bindable(); + private readonly Bindable playingState = new Bindable(); private TextBox textBox => chatDisplay.ChildrenOfType().First(); public TestSceneGameplayChatDisplay() { var mockLocalUserInfo = new Mock(); - mockLocalUserInfo.SetupGet(i => i.IsPlaying).Returns(localUserPlaying); + mockLocalUserInfo.SetupGet(i => i.PlayingState).Returns(playingState); localUserInfo = mockLocalUserInfo.Object; } @@ -124,6 +124,6 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert($"chat {(isFocused ? "focused" : "not focused")}", () => textBox.HasFocus == isFocused); private void setLocalUserPlaying(bool playing) => - AddStep($"local user {(playing ? "playing" : "not playing")}", () => localUserPlaying.Value = playing); + AddStep($"local user {(playing ? "playing" : "not playing")}", () => playingState.Value = playing ? LocalUserPlayingStates.Playing : LocalUserPlayingStates.NotPlaying); } } diff --git a/osu.Game/Database/BackgroundDataStoreProcessor.cs b/osu.Game/Database/BackgroundDataStoreProcessor.cs index 0fa785e494..cc7746e34b 100644 --- a/osu.Game/Database/BackgroundDataStoreProcessor.cs +++ b/osu.Game/Database/BackgroundDataStoreProcessor.cs @@ -606,7 +606,7 @@ namespace osu.Game.Database { // Importantly, also sleep if high performance session is active. // If we don't do this, memory usage can become runaway due to GC running in a more lenient mode. - while (localUserPlayInfo?.IsPlaying.Value == true || highPerformanceSessionManager?.IsSessionActive == true) + while (localUserPlayInfo?.PlayingState.Value != LocalUserPlayingStates.NotPlaying || highPerformanceSessionManager?.IsSessionActive == true) { Logger.Log("Background processing sleeping due to active gameplay..."); Thread.Sleep(TimeToSleepDuringGameplay); diff --git a/osu.Game/Input/ConfineMouseTracker.cs b/osu.Game/Input/ConfineMouseTracker.cs index 926f68df45..0f4363e00f 100644 --- a/osu.Game/Input/ConfineMouseTracker.cs +++ b/osu.Game/Input/ConfineMouseTracker.cs @@ -15,7 +15,7 @@ namespace osu.Game.Input { /// /// Connects with . - /// If is true, we should also confine the mouse cursor if it has been + /// If is playing, we should also confine the mouse cursor if it has been /// requested with . /// public partial class ConfineMouseTracker : Component @@ -25,7 +25,7 @@ namespace osu.Game.Input private Bindable frameworkMinimiseOnFocusLossInFullscreen; private Bindable osuConfineMode; - private IBindable localUserPlaying; + private IBindable localUserPlaying; [BackgroundDependencyLoader] private void load(ILocalUserPlayInfo localUserInfo, FrameworkConfigManager frameworkConfigManager, OsuConfigManager osuConfigManager) @@ -37,7 +37,7 @@ namespace osu.Game.Input frameworkMinimiseOnFocusLossInFullscreen.BindValueChanged(_ => updateConfineMode()); osuConfineMode = osuConfigManager.GetBindable(OsuSetting.ConfineMouseMode); - localUserPlaying = localUserInfo.IsPlaying.GetBoundCopy(); + localUserPlaying = localUserInfo.PlayingState.GetBoundCopy(); osuConfineMode.ValueChanged += _ => updateConfineMode(); localUserPlaying.BindValueChanged(_ => updateConfineMode(), true); @@ -63,7 +63,7 @@ namespace osu.Game.Input break; case OsuConfineMouseMode.DuringGameplay: - frameworkConfineMode.Value = localUserPlaying.Value ? ConfineMouseMode.Always : ConfineMouseMode.Never; + frameworkConfineMode.Value = localUserPlaying.Value == LocalUserPlayingStates.Playing ? ConfineMouseMode.Always : ConfineMouseMode.Never; break; case OsuConfineMouseMode.Always: diff --git a/osu.Game/Input/OsuUserInputManager.cs b/osu.Game/Input/OsuUserInputManager.cs index b8fd0bb11c..ff1b88b65a 100644 --- a/osu.Game/Input/OsuUserInputManager.cs +++ b/osu.Game/Input/OsuUserInputManager.cs @@ -3,15 +3,16 @@ using osu.Framework.Bindables; using osu.Framework.Input; +using osu.Game.Screens.Play; using osuTK.Input; namespace osu.Game.Input { public partial class OsuUserInputManager : UserInputManager { - protected override bool AllowRightClickFromLongTouch => !LocalUserPlaying.Value; + protected override bool AllowRightClickFromLongTouch => PlayingState.Value == LocalUserPlayingStates.NotPlaying; - public readonly BindableBool LocalUserPlaying = new BindableBool(); + public readonly Bindable PlayingState = new Bindable(); internal OsuUserInputManager() { diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 44ba78762a..902a4ddb33 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -175,14 +175,9 @@ namespace osu.Game /// public readonly IBindable OverlayActivationMode = new Bindable(); - /// - /// Whether the local user is currently interacting with the game in a way that should not be interrupted. - /// - /// - /// This is exclusively managed by . If other components are mutating this state, a more - /// resilient method should be used to ensure correct state. - /// - public Bindable LocalUserPlaying = new BindableBool(); + IBindable ILocalUserPlayInfo.PlayingState => playingState; + + private readonly Bindable playingState = new Bindable(); protected OsuScreenStack ScreenStack; @@ -302,7 +297,7 @@ namespace osu.Game protected override UserInputManager CreateUserInputManager() { var userInputManager = base.CreateUserInputManager(); - (userInputManager as OsuUserInputManager)?.LocalUserPlaying.BindTo(LocalUserPlaying); + (userInputManager as OsuUserInputManager)?.PlayingState.BindTo(playingState); return userInputManager; } @@ -391,11 +386,11 @@ namespace osu.Game // Transfer any runtime changes back to configuration file. SkinManager.CurrentSkinInfo.ValueChanged += skin => configSkin.Value = skin.NewValue.ID.ToString(); - LocalUserPlaying.BindValueChanged(p => + playingState.BindValueChanged(p => { - BeatmapManager.PauseImports = p.NewValue; - SkinManager.PauseImports = p.NewValue; - ScoreManager.PauseImports = p.NewValue; + BeatmapManager.PauseImports = p.NewValue != LocalUserPlayingStates.NotPlaying; + SkinManager.PauseImports = p.NewValue != LocalUserPlayingStates.NotPlaying; + ScoreManager.PauseImports = p.NewValue != LocalUserPlayingStates.NotPlaying; }, true); IsActive.BindValueChanged(active => updateActiveState(active.NewValue), true); @@ -1553,6 +1548,12 @@ namespace osu.Game scope.SetTag(@"screen", newScreen?.GetType().ReadableName() ?? @"none"); }); + // reset on screen change for sanity. + playingState.Value = LocalUserPlayingStates.NotPlaying; + + if (current is Player oldPlayer) + oldPlayer.PlayingState.UnbindFrom(playingState); + switch (newScreen) { case IntroScreen intro: @@ -1565,14 +1566,15 @@ namespace osu.Game versionManager?.Show(); break; + case Player player: + player.PlayingState.BindTo(playingState); + break; + default: versionManager?.Hide(); break; } - // reset on screen change for sanity. - LocalUserPlaying.Value = false; - if (current is IOsuScreen currentOsuScreen) { OverlayActivationMode.UnbindFrom(currentOsuScreen.OverlayActivationMode); @@ -1621,7 +1623,5 @@ namespace osu.Game if (newScreen == null) Exit(); } - - IBindable ILocalUserPlayInfo.IsPlaying => LocalUserPlaying; } } diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 9b2f26e8ae..b47e2b82c0 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -98,7 +98,7 @@ namespace osu.Game.Overlays apiUser.BindValueChanged(_ => Schedule(() => { if (api.IsLoggedIn) - replaceResultsAreaContent(Drawable.Empty()); + replaceResultsAreaContent(Empty()); })); } diff --git a/osu.Game/Overlays/Chat/DaySeparator.cs b/osu.Game/Overlays/Chat/DaySeparator.cs index fd6b15c778..c371877fcb 100644 --- a/osu.Game/Overlays/Chat/DaySeparator.cs +++ b/osu.Game/Overlays/Chat/DaySeparator.cs @@ -75,7 +75,7 @@ namespace osu.Game.Overlays.Chat Height = LineHeight, Colour = colourProvider?.Background5 ?? Colour4.White, }, - Drawable.Empty(), + Empty(), new OsuSpriteText { Anchor = Anchor.CentreRight, @@ -87,7 +87,7 @@ namespace osu.Game.Overlays.Chat } }, }, - Drawable.Empty(), + Empty(), new Circle { Anchor = Anchor.Centre, diff --git a/osu.Game/Overlays/Settings/SettingsSidebar.cs b/osu.Game/Overlays/Settings/SettingsSidebar.cs index ddbcd60ef6..d24c0a778c 100644 --- a/osu.Game/Overlays/Settings/SettingsSidebar.cs +++ b/osu.Game/Overlays/Settings/SettingsSidebar.cs @@ -66,7 +66,7 @@ namespace osu.Game.Overlays.Settings [BackgroundDependencyLoader] private void load() { - Size = new Vector2(SettingsSidebar.EXPANDED_WIDTH); + Size = new Vector2(EXPANDED_WIDTH); Padding = new MarginPadding(40); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs index d1a73457e3..165e3a2440 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs @@ -23,9 +23,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [CanBeNull] private ILocalUserPlayInfo localUserInfo { get; set; } - private readonly IBindable localUserPlaying = new Bindable(); + private readonly IBindable localUserPlaying = new Bindable(); - public override bool PropagatePositionalInputSubTree => !localUserPlaying.Value; + public override bool PropagatePositionalInputSubTree => localUserPlaying.Value != LocalUserPlayingStates.Playing; public Bindable Expanded = new Bindable(); @@ -58,7 +58,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer base.LoadComplete(); if (localUserInfo != null) - localUserPlaying.BindTo(localUserInfo.IsPlaying); + localUserPlaying.BindTo(localUserInfo.PlayingState); localUserPlaying.BindValueChanged(playing => { @@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer TextBox.HoldFocus = false; // only hold focus (after sending a message) during breaks - TextBox.ReleaseFocusOnCommit = playing.NewValue; + TextBox.ReleaseFocusOnCommit = playing.NewValue == LocalUserPlayingStates.Playing; }, true); Expanded.BindValueChanged(_ => updateExpandedState(), true); diff --git a/osu.Game/Screens/Play/ILocalUserPlayInfo.cs b/osu.Game/Screens/Play/ILocalUserPlayInfo.cs index 2d181a09d4..48b610dd8e 100644 --- a/osu.Game/Screens/Play/ILocalUserPlayInfo.cs +++ b/osu.Game/Screens/Play/ILocalUserPlayInfo.cs @@ -10,8 +10,8 @@ namespace osu.Game.Screens.Play public interface ILocalUserPlayInfo { /// - /// Whether the local user is currently playing. + /// Whether the local user is currently interacting (playing) with the game in a way that should not be interrupted. /// - IBindable IsPlaying { get; } + IBindable PlayingState { get; } } } diff --git a/osu.Game/Screens/Play/LocalUserPlayingStates.cs b/osu.Game/Screens/Play/LocalUserPlayingStates.cs new file mode 100644 index 0000000000..89f9ea16f7 --- /dev/null +++ b/osu.Game/Screens/Play/LocalUserPlayingStates.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Screens.Play +{ + public enum LocalUserPlayingStates + { + /// + /// The local player is not current in gameplay. + /// + NotPlaying, + + /// + /// The local player is in a break, paused, or failed or passed but still at the gameplay screen. + /// + Break, + + /// + /// The local user is in active gameplay. + /// + Playing, + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 2a66c3d5d3..ee11512bd4 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -94,6 +94,7 @@ namespace osu.Game.Screens.Play public IBindable LocalUserPlaying => localUserPlaying; private readonly Bindable localUserPlaying = new Bindable(); + private readonly Bindable playingState = new Bindable(); public int RestartCount; @@ -231,9 +232,6 @@ namespace osu.Game.Screens.Play if (game != null) gameActive.BindTo(game.IsActive); - if (game is OsuGame osuGame) - LocalUserPlaying.BindTo(osuGame.LocalUserPlaying); - DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, gameplayMods); dependencies.CacheAs(DrawableRuleset); @@ -510,9 +508,16 @@ namespace osu.Game.Screens.Play private void updateGameplayState() { - bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !DrawableRuleset.IsPaused.Value && !breakTracker.IsBreakTime.Value && !GameplayState.HasFailed; - OverlayActivationMode.Value = inGameplay ? OverlayActivation.Disabled : OverlayActivation.UserTriggered; - localUserPlaying.Value = inGameplay; + bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !GameplayState.HasFailed; + bool inBreak = breakTracker.IsBreakTime.Value || DrawableRuleset.IsPaused.Value; + + if (inGameplay) + playingState.Value = inBreak ? LocalUserPlayingStates.Break : LocalUserPlayingStates.Playing; + else + playingState.Value = LocalUserPlayingStates.NotPlaying; + + localUserPlaying.Value = playingState.Value != LocalUserPlayingStates.NotPlaying; + OverlayActivationMode.Value = playingState.Value != LocalUserPlayingStates.NotPlaying ? OverlayActivation.Disabled : OverlayActivation.UserTriggered; } private void updateSampleDisabledState() @@ -1279,6 +1284,6 @@ namespace osu.Game.Screens.Play IBindable ISamplePlaybackDisabler.SamplePlaybackDisabled => samplePlaybackDisabled; - IBindable ILocalUserPlayInfo.IsPlaying => LocalUserPlaying; + public IBindable PlayingState => playingState; } } From 3d54f4a5ab61c41b42d7bffc879b216161f1a634 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Oct 2024 17:41:59 +0900 Subject: [PATCH 356/924] Make states better defined --- osu.Game/Screens/Play/LocalUserPlayingStates.cs | 4 ++-- osu.Game/Screens/Play/Player.cs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/LocalUserPlayingStates.cs b/osu.Game/Screens/Play/LocalUserPlayingStates.cs index 89f9ea16f7..d1761692a7 100644 --- a/osu.Game/Screens/Play/LocalUserPlayingStates.cs +++ b/osu.Game/Screens/Play/LocalUserPlayingStates.cs @@ -6,12 +6,12 @@ namespace osu.Game.Screens.Play public enum LocalUserPlayingStates { /// - /// The local player is not current in gameplay. + /// The local player is not current in gameplay. If watching a replay, gameplay always remains in this state. /// NotPlaying, /// - /// The local player is in a break, paused, or failed or passed but still at the gameplay screen. + /// The local player is in a break, paused, or failed but still at the gameplay screen. /// Break, diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ee11512bd4..0e0f3ae8a3 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -508,16 +508,16 @@ namespace osu.Game.Screens.Play private void updateGameplayState() { - bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !GameplayState.HasFailed; - bool inBreak = breakTracker.IsBreakTime.Value || DrawableRuleset.IsPaused.Value; + bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value; + bool inBreak = breakTracker.IsBreakTime.Value || DrawableRuleset.IsPaused.Value || GameplayState.HasFailed; if (inGameplay) playingState.Value = inBreak ? LocalUserPlayingStates.Break : LocalUserPlayingStates.Playing; else playingState.Value = LocalUserPlayingStates.NotPlaying; - localUserPlaying.Value = playingState.Value != LocalUserPlayingStates.NotPlaying; - OverlayActivationMode.Value = playingState.Value != LocalUserPlayingStates.NotPlaying ? OverlayActivation.Disabled : OverlayActivation.UserTriggered; + localUserPlaying.Value = playingState.Value == LocalUserPlayingStates.Playing; + OverlayActivationMode.Value = playingState.Value == LocalUserPlayingStates.Playing ? OverlayActivation.Disabled : OverlayActivation.UserTriggered; } private void updateSampleDisabledState() From 4b1c2c09ee3051c173e0b3942e0f0abc6399c2ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Oct 2024 17:53:25 +0900 Subject: [PATCH 357/924] Avoid updates and update notifications appearing in more gameplay cases --- osu.Desktop/Updater/VelopackUpdateManager.cs | 32 +++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/osu.Desktop/Updater/VelopackUpdateManager.cs b/osu.Desktop/Updater/VelopackUpdateManager.cs index 7a79284533..dd0014a61f 100644 --- a/osu.Desktop/Updater/VelopackUpdateManager.cs +++ b/osu.Desktop/Updater/VelopackUpdateManager.cs @@ -25,6 +25,8 @@ namespace osu.Desktop.Updater [Resolved] private ILocalUserPlayInfo? localUserInfo { get; set; } + private bool isInGameplay => localUserInfo?.PlayingState.Value != LocalUserPlayingStates.NotPlaying; + private UpdateInfo? pendingUpdate; public VelopackUpdateManager() @@ -51,7 +53,7 @@ namespace osu.Desktop.Updater try { // Avoid any kind of update checking while gameplay is running. - if (localUserInfo?.IsPlaying.Value == true) + if (isInGameplay) { scheduleRecheck = true; return false; @@ -61,14 +63,17 @@ namespace osu.Desktop.Updater // Velopack does support this scenario (see https://github.com/ppy/osu/pull/28743#discussion_r1743495975). if (pendingUpdate != null) { - // If there is an update pending restart, show the notification to restart again. - notificationOverlay.Post(new UpdateApplicationCompleteNotification + runOutsideOfGameplay(() => { - Activated = () => + // If there is an update pending restart, show the notification to restart again. + notificationOverlay.Post(new UpdateApplicationCompleteNotification { - Task.Run(restartToApplyUpdate); - return true; - } + Activated = () => + { + Task.Run(restartToApplyUpdate); + return true; + } + }); }); return true; @@ -104,7 +109,7 @@ namespace osu.Desktop.Updater { await updateManager.DownloadUpdatesAsync(pendingUpdate, p => notification.Progress = p / 100f).ConfigureAwait(false); - notification.State = ProgressNotificationState.Completed; + runOutsideOfGameplay(() => notification.State = ProgressNotificationState.Completed); } catch (Exception e) { @@ -131,6 +136,17 @@ namespace osu.Desktop.Updater return true; } + private void runOutsideOfGameplay(Action action) + { + if (isInGameplay) + { + Scheduler.AddDelayed(() => runOutsideOfGameplay(action), 1000); + return; + } + + action(); + } + private async Task restartToApplyUpdate() { await updateManager.WaitExitThenApplyUpdatesAsync(pendingUpdate?.TargetFullRelease).ConfigureAwait(false); From 307cc5581dad4b4f03a80ccb39a93a6b100c59ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Oct 2024 18:01:06 +0900 Subject: [PATCH 358/924] Fix usage of locked context without locking Raised in the latest Rider EAP, seems valid enough. --- osu.Game/Beatmaps/WorkingBeatmap.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 25159996f3..07bf4c028a 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -183,7 +183,14 @@ namespace osu.Game.Beatmaps #region Beatmap - public virtual bool BeatmapLoaded => beatmapLoadTask?.IsCompleted ?? false; + public virtual bool BeatmapLoaded + { + get + { + lock (beatmapFetchLock) + return beatmapLoadTask?.IsCompleted ?? false; + } + } public IBeatmap Beatmap { From 3769b0da5da447465a4b47897a21ca78eb624bac Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 1 Oct 2024 11:02:46 +0200 Subject: [PATCH 359/924] Fix TestHotkeysUnifySliderSamplesAndNodeSamples --- .../TestSceneHitObjectSampleAdjustments.cs | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs index f0eadf8f9e..5cc1e64197 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs @@ -739,20 +739,37 @@ namespace osu.Game.Tests.Visual.Editing InputManager.ReleaseKey(Key.LShift); }); - hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT); + hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_SOFT); hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); - hitObjectNodeHasSampleBank(0, 0, HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSampleNormalBank(0, 0, HitSampleInfo.BANK_SOFT); hitObjectNodeHasSamples(0, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP); - hitObjectNodeHasSampleBank(0, 1, HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSampleNormalBank(0, 1, HitSampleInfo.BANK_SOFT); hitObjectNodeHasSamples(0, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); AddStep("unify whistle addition", () => InputManager.Key(Key.W)); - hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT); + hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_SOFT); hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); - hitObjectNodeHasSampleBank(0, 0, HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSampleNormalBank(0, 0, HitSampleInfo.BANK_SOFT); hitObjectNodeHasSamples(0, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_WHISTLE); - hitObjectNodeHasSampleBank(0, 1, HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSampleNormalBank(0, 1, HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSamples(0, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); + + AddStep("set drum addition bank", () => + { + InputManager.PressKey(Key.LAlt); + InputManager.Key(Key.R); + InputManager.ReleaseKey(Key.LAlt); + }); + + hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_SOFT); + hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM); + hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); + hitObjectNodeHasSampleNormalBank(0, 0, HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSampleAdditionBank(0, 0, HitSampleInfo.BANK_DRUM); + hitObjectNodeHasSamples(0, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_WHISTLE); + hitObjectNodeHasSampleNormalBank(0, 1, HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSampleAdditionBank(0, 1, HitSampleInfo.BANK_DRUM); hitObjectNodeHasSamples(0, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); } From b741bfb6d9ab98b43374d5fa9df583291804b6b6 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 1 Oct 2024 11:02:46 +0200 Subject: [PATCH 360/924] Fix TestHotkeysUnifySliderSamplesAndNodeSamples --- .../TestSceneHitObjectSampleAdjustments.cs | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs index f0eadf8f9e..5cc1e64197 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs @@ -739,20 +739,37 @@ namespace osu.Game.Tests.Visual.Editing InputManager.ReleaseKey(Key.LShift); }); - hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT); + hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_SOFT); hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); - hitObjectNodeHasSampleBank(0, 0, HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSampleNormalBank(0, 0, HitSampleInfo.BANK_SOFT); hitObjectNodeHasSamples(0, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP); - hitObjectNodeHasSampleBank(0, 1, HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSampleNormalBank(0, 1, HitSampleInfo.BANK_SOFT); hitObjectNodeHasSamples(0, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); AddStep("unify whistle addition", () => InputManager.Key(Key.W)); - hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT); + hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_SOFT); hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); - hitObjectNodeHasSampleBank(0, 0, HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSampleNormalBank(0, 0, HitSampleInfo.BANK_SOFT); hitObjectNodeHasSamples(0, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_WHISTLE); - hitObjectNodeHasSampleBank(0, 1, HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSampleNormalBank(0, 1, HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSamples(0, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); + + AddStep("set drum addition bank", () => + { + InputManager.PressKey(Key.LAlt); + InputManager.Key(Key.R); + InputManager.ReleaseKey(Key.LAlt); + }); + + hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_SOFT); + hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM); + hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); + hitObjectNodeHasSampleNormalBank(0, 0, HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSampleAdditionBank(0, 0, HitSampleInfo.BANK_DRUM); + hitObjectNodeHasSamples(0, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_WHISTLE); + hitObjectNodeHasSampleNormalBank(0, 1, HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSampleAdditionBank(0, 1, HitSampleInfo.BANK_DRUM); hitObjectNodeHasSamples(0, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); } From 44b982864c27bce354d8f7a7de34418b6eaa74f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Oct 2024 11:12:56 +0200 Subject: [PATCH 361/924] Fix test failures --- .../TestSceneHitObjectComposerDistanceSnapping.cs | 13 ++++++++----- .../Rulesets/Edit/ComposerDistanceSnapProvider.cs | 1 + 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs index cf8c3c6ef1..156675a066 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs @@ -230,23 +230,26 @@ namespace osu.Game.Tests.Editing [Test] public void TestUseCurrentSnap() { + ExpandableButton getCurrentSnapButton() => composer.ChildrenOfType().Single(g => g.Name == "snapping") + .ChildrenOfType().Single(); + AddStep("add objects to beatmap", () => { editorBeatmap.Add(new HitCircle { StartTime = 1000 }); editorBeatmap.Add(new HitCircle { Position = new Vector2(100), StartTime = 2000 }); }); - AddStep("hover use current snap button", () => InputManager.MoveMouseTo(composer.ChildrenOfType().Single())); - AddUntilStep("use current snap expanded", () => composer.ChildrenOfType().Single().Expanded.Value, () => Is.True); + AddStep("hover use current snap button", () => InputManager.MoveMouseTo(getCurrentSnapButton())); + AddUntilStep("use current snap expanded", () => getCurrentSnapButton().Expanded.Value, () => Is.True); AddStep("seek before first object", () => EditorClock.Seek(0)); - AddUntilStep("use current snap not available", () => composer.ChildrenOfType().Single().Enabled.Value, () => Is.False); + AddUntilStep("use current snap not available", () => getCurrentSnapButton().Enabled.Value, () => Is.False); AddStep("seek to between objects", () => EditorClock.Seek(1500)); - AddUntilStep("use current snap available", () => composer.ChildrenOfType().Single().Enabled.Value, () => Is.True); + AddUntilStep("use current snap available", () => getCurrentSnapButton().Enabled.Value, () => Is.True); AddStep("seek after last object", () => EditorClock.Seek(2500)); - AddUntilStep("use current snap not available", () => composer.ChildrenOfType().Single().Enabled.Value, () => Is.False); + AddUntilStep("use current snap not available", () => getCurrentSnapButton().Enabled.Value, () => Is.False); } private void assertSnapDistance(float expectedDistance, HitObject? referenceObject, bool includeSliderVelocity) diff --git a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs index 979492fd8b..d8f493405f 100644 --- a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs @@ -74,6 +74,7 @@ namespace osu.Game.Rulesets.Edit toolboxContainer.Add(toolboxGroup = new EditorToolboxGroup("snapping") { + Name = "snapping", Alpha = DistanceSpacingMultiplier.Disabled ? 0 : 1, Children = new Drawable[] { From 6be94a3a964d6edc606f4b7bb2b63f0d26775bc4 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 1 Oct 2024 11:13:44 +0200 Subject: [PATCH 362/924] Fix TestSplitRetainsHitsounds --- osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs index d68cbe6265..d5bacc25bc 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs @@ -180,7 +180,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { if (slider == null) return; - sample = new HitSampleInfo("hitwhistle", HitSampleInfo.BANK_SOFT, volume: 70); + sample = new HitSampleInfo("hitwhistle", HitSampleInfo.BANK_SOFT, volume: 70, editorAutoBank: false); slider.Samples.Add(sample.With()); }); From 8f0fedbd2205ce1a886d8c61fcf8693466c9ab84 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Oct 2024 18:21:15 +0900 Subject: [PATCH 363/924] Fix local scores never importing due to new conditionals --- osu.Game/OsuGame.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 902a4ddb33..23a1ad2f4a 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -390,7 +390,8 @@ namespace osu.Game { BeatmapManager.PauseImports = p.NewValue != LocalUserPlayingStates.NotPlaying; SkinManager.PauseImports = p.NewValue != LocalUserPlayingStates.NotPlaying; - ScoreManager.PauseImports = p.NewValue != LocalUserPlayingStates.NotPlaying; + // For scores, we need to allow imports during "Break" state else local user's scores will never be imported. + ScoreManager.PauseImports = p.NewValue == LocalUserPlayingStates.Playing; }, true); IsActive.BindValueChanged(active => updateActiveState(active.NewValue), true); From 7977ce8a0e6265e9a948525d919ab7280c967fec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Oct 2024 18:25:45 +0900 Subject: [PATCH 364/924] Attempt to fix android class --- osu.Android/GameplayScreenRotationLocker.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Android/GameplayScreenRotationLocker.cs b/osu.Android/GameplayScreenRotationLocker.cs index e5fc354db7..ffd4218ea9 100644 --- a/osu.Android/GameplayScreenRotationLocker.cs +++ b/osu.Android/GameplayScreenRotationLocker.cs @@ -6,28 +6,29 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game; +using osu.Game.Screens.Play; namespace osu.Android { public partial class GameplayScreenRotationLocker : Component { - private Bindable localUserPlaying = null!; + private IBindable localUserPlaying = null!; [Resolved] private OsuGameActivity gameActivity { get; set; } = null!; [BackgroundDependencyLoader] - private void load(OsuGame game) + private void load(ILocalUserPlayInfo localUserPlayInfo) { - localUserPlaying = game.LocalUserPlaying.GetBoundCopy(); + localUserPlaying = localUserPlayInfo.PlayingState.GetBoundCopy(); localUserPlaying.BindValueChanged(updateLock, true); } - private void updateLock(ValueChangedEvent userPlaying) + private void updateLock(ValueChangedEvent userPlaying) { gameActivity.RunOnUiThread(() => { - gameActivity.RequestedOrientation = userPlaying.NewValue ? ScreenOrientation.Locked : gameActivity.DefaultOrientation; + gameActivity.RequestedOrientation = userPlaying.NewValue != LocalUserPlayingStates.NotPlaying ? ScreenOrientation.Locked : gameActivity.DefaultOrientation; }); } } From 9dad38c457aa68b9d4cbccb56226d3a3b33ddd0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Oct 2024 11:27:45 +0200 Subject: [PATCH 365/924] Fix button being interactable when collapsed Did not match the expandable button in distance snap toolbox. --- .../Edit/OsuGridToolboxGroup.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 0fe9d85635..4fa8852770 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -202,12 +202,6 @@ namespace osu.Game.Rulesets.Osu.Edit gridLinesRotationSlider.ExpandedLabelText = $"Rotation: {rotation.NewValue:#,0.##}"; }, true); - expandingContainer?.Expanded.BindValueChanged(v => - { - gridTypeButtons.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint); - gridTypeButtons.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None; - }, true); - GridType.BindValueChanged(v => { GridLinesRotation.Disabled = v.NewValue == PositionSnapGridType.Circle; @@ -229,12 +223,20 @@ namespace osu.Game.Rulesets.Osu.Edit }, true); editorBeatmap.BeatmapReprocessed += updateEnabledStates; - editorBeatmap.SelectedHitObjects.BindCollectionChanged((_, _) => updateEnabledStates(), true); + editorBeatmap.SelectedHitObjects.BindCollectionChanged((_, _) => updateEnabledStates()); + expandingContainer?.Expanded.BindValueChanged(v => + { + gridTypeButtons.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint); + gridTypeButtons.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None; + updateEnabledStates(); + }, true); } private void updateEnabledStates() { - useSelectedObjectPositionButton.Enabled.Value = editorBeatmap.SelectedHitObjects.Count == 1 && StartPosition.Value != ((IHasPosition)editorBeatmap.SelectedHitObjects.Single()).Position; + useSelectedObjectPositionButton.Enabled.Value = expandingContainer?.Expanded.Value == true + && editorBeatmap.SelectedHitObjects.Count == 1 + && StartPosition.Value != ((IHasPosition)editorBeatmap.SelectedHitObjects.Single()).Position; } private void nextGridSize() From ae75bfd9667b0692dec761b81fdaa570e46dd2aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Oct 2024 18:41:53 +0900 Subject: [PATCH 366/924] Rename keyboard mapping methods to make more sense now that everything's on the left --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 00de46b726..316e8e55e8 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -363,7 +363,7 @@ namespace osu.Game.Rulesets.Edit if (e.ControlPressed || e.AltPressed || e.SuperPressed) return false; - if (checkLeftToggleFromKey(e.Key, out int leftIndex)) + if (checkToolboxMappingFromKey(e.Key, out int leftIndex)) { var item = toolboxCollection.Items.ElementAtOrDefault(leftIndex); @@ -375,7 +375,7 @@ namespace osu.Game.Rulesets.Edit } } - if (checkRightToggleFromKey(e.Key, out int rightIndex)) + if (checkToggleMappingFromKey(e.Key, out int rightIndex)) { var item = e.ShiftPressed ? sampleBankTogglesCollection.ElementAtOrDefault(rightIndex) @@ -391,7 +391,7 @@ namespace osu.Game.Rulesets.Edit return base.OnKeyDown(e); } - private bool checkLeftToggleFromKey(Key key, out int index) + private bool checkToolboxMappingFromKey(Key key, out int index) { if (key < Key.Number1 || key > Key.Number9) { @@ -403,7 +403,7 @@ namespace osu.Game.Rulesets.Edit return true; } - private bool checkRightToggleFromKey(Key key, out int index) + private bool checkToggleMappingFromKey(Key key, out int index) { switch (key) { From 24d534929d3b6f64da7a1aa53cbf01b5715857cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Oct 2024 18:44:27 +0900 Subject: [PATCH 367/924] Less `var` please --- .../Components/PathControlPointVisualiser.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 21d63a0ea3..bb8ee11e49 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components if (segment.Count == 0) return; - var first = segment[0]; + PathControlPoint first = segment[0]; if (first.Type != PathType.PERFECT_CURVE) return; @@ -273,10 +273,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components if (selectedPieces.Length != 1) return false; - var selectedPiece = selectedPieces.Single(); - var selectedPoint = selectedPiece.ControlPoint; + PathControlPointPiece selectedPiece = selectedPieces.Single(); + PathControlPoint selectedPoint = selectedPiece.ControlPoint; - var validTypes = path_types; + PathType?[] validTypes = path_types; if (selectedPoint == controlPoints[0]) validTypes = validTypes.Where(t => t != null).ToArray(); @@ -313,7 +313,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components if (Pieces.All(p => !p.IsSelected.Value)) return false; - var type = path_types[e.Key - Key.Number1]; + PathType? type = path_types[e.Key - Key.Number1]; // The first control point can never be inherit type if (Pieces[0].IsSelected.Value && type == null) @@ -355,7 +355,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components foreach (var p in Pieces.Where(p => p.IsSelected.Value)) { - var pointsInSegment = hitObject.Path.PointsInSegment(p.ControlPoint); + List pointsInSegment = hitObject.Path.PointsInSegment(p.ControlPoint); int indexInSegment = pointsInSegment.IndexOf(p.ControlPoint); if (type?.Type == SplineType.PerfectCurve) @@ -405,14 +405,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public void DragInProgress(DragEvent e) { Vector2[] oldControlPoints = hitObject.Path.ControlPoints.Select(cp => cp.Position).ToArray(); - var oldPosition = hitObject.Position; + Vector2 oldPosition = hitObject.Position; double oldStartTime = hitObject.StartTime; if (selectedControlPoints.Contains(hitObject.Path.ControlPoints[0])) { // Special handling for selections containing head control point - the position of the hit object changes which means the snapped position and time have to be taken into account Vector2 newHeadPosition = Parent!.ToScreenSpace(e.MousePosition + (dragStartPositions[0] - dragStartPositions[draggedControlPointIndex])); - var result = positionSnapProvider?.FindSnappedPositionAndTime(newHeadPosition); + SnapResult result = positionSnapProvider?.FindSnappedPositionAndTime(newHeadPosition); Vector2 movementDelta = Parent!.ToLocalSpace(result?.ScreenSpacePosition ?? newHeadPosition) - hitObject.Position; @@ -421,7 +421,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components for (int i = 1; i < hitObject.Path.ControlPoints.Count; i++) { - var controlPoint = hitObject.Path.ControlPoints[i]; + PathControlPoint controlPoint = hitObject.Path.ControlPoints[i]; // Since control points are relative to the position of the hit object, all points that are _not_ selected // need to be offset _back_ by the delta corresponding to the movement of the head point. // All other selected control points (if any) will move together with the head point @@ -432,13 +432,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components } else { - var result = positionSnapProvider?.FindSnappedPositionAndTime(Parent!.ToScreenSpace(e.MousePosition), SnapType.GlobalGrids); + SnapResult result = positionSnapProvider?.FindSnappedPositionAndTime(Parent!.ToScreenSpace(e.MousePosition), SnapType.GlobalGrids); Vector2 movementDelta = Parent!.ToLocalSpace(result?.ScreenSpacePosition ?? Parent!.ToScreenSpace(e.MousePosition)) - dragStartPositions[draggedControlPointIndex] - hitObject.Position; for (int i = 0; i < controlPoints.Count; ++i) { - var controlPoint = controlPoints[i]; + PathControlPoint controlPoint = controlPoints[i]; if (selectedControlPoints.Contains(controlPoint)) controlPoint.Position = dragStartPositions[i] + movementDelta; } @@ -490,7 +490,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components for (int i = 0; i < path_types.Length; ++i) { - var type = path_types[i]; + PathType? type = path_types[i]; // special inherit case if (type == null) From 162558e0b8253eb4588117dbc270d78510068f3c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Oct 2024 18:48:09 +0900 Subject: [PATCH 368/924] Use `record` `struct` See https://www.jetbrains.com/help/rider/UsageOfDefaultStructEquality.html. --- osu.Game/Graphics/UserInterface/Hotkey.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/Hotkey.cs b/osu.Game/Graphics/UserInterface/Hotkey.cs index 0b5176a02e..8b3014bdc5 100644 --- a/osu.Game/Graphics/UserInterface/Hotkey.cs +++ b/osu.Game/Graphics/UserInterface/Hotkey.cs @@ -11,7 +11,7 @@ using osu.Game.Input.Bindings; namespace osu.Game.Graphics.UserInterface { - public readonly struct Hotkey + public readonly record struct Hotkey { public KeyCombination[]? KeyCombinations { get; init; } public GlobalAction? GlobalAction { get; init; } From 2381c2c72c1f59f73ba8d4bf17818ab8197b4e62 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 1 Oct 2024 12:11:44 +0200 Subject: [PATCH 369/924] Fix hitobjects without custom sample banks parsing as not auto sample bank --- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index fa3ee41b47..89fd5f7384 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -543,7 +543,7 @@ namespace osu.Game.Rulesets.Objects.Legacy /// /// Whether the bank for additions should be inherited from the normal sample in edit. /// - public bool EditorAutoBank; + public bool EditorAutoBank = true; public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone(); } From 8dba4cf760acd07735a12c3b74da7f7a15e1a34d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Oct 2024 19:55:31 +0900 Subject: [PATCH 370/924] Passing means `NotPlaying` --- osu.Game/OsuGame.cs | 3 +-- osu.Game/Screens/Play/Player.cs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 23a1ad2f4a..902a4ddb33 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -390,8 +390,7 @@ namespace osu.Game { BeatmapManager.PauseImports = p.NewValue != LocalUserPlayingStates.NotPlaying; SkinManager.PauseImports = p.NewValue != LocalUserPlayingStates.NotPlaying; - // For scores, we need to allow imports during "Break" state else local user's scores will never be imported. - ScoreManager.PauseImports = p.NewValue == LocalUserPlayingStates.Playing; + ScoreManager.PauseImports = p.NewValue != LocalUserPlayingStates.NotPlaying; }, true); IsActive.BindValueChanged(active => updateActiveState(active.NewValue), true); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 0e0f3ae8a3..e9e3757629 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -508,7 +508,7 @@ namespace osu.Game.Screens.Play private void updateGameplayState() { - bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value; + bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !GameplayState.HasPassed; bool inBreak = breakTracker.IsBreakTime.Value || DrawableRuleset.IsPaused.Value || GameplayState.HasFailed; if (inGameplay) From 8773c34fddea196d1c8b21a364587803a9c59fff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Oct 2024 19:55:46 +0900 Subject: [PATCH 371/924] Rename enum to non-plural now that it won't conflict --- osu.Desktop/Updater/VelopackUpdateManager.cs | 2 +- osu.Desktop/Windows/GameplayWinKeyBlocker.cs | 4 ++-- .../Database/BackgroundDataStoreProcessorTests.cs | 10 +++++----- osu.Game.Tests/Input/ConfineMouseTrackerTest.cs | 6 +++--- .../Multiplayer/TestSceneGameplayChatDisplay.cs | 4 ++-- osu.Game/Database/BackgroundDataStoreProcessor.cs | 2 +- osu.Game/Input/ConfineMouseTracker.cs | 4 ++-- osu.Game/Input/OsuUserInputManager.cs | 4 ++-- osu.Game/OsuGame.cs | 12 ++++++------ .../OnlinePlay/Multiplayer/GameplayChatDisplay.cs | 6 +++--- osu.Game/Screens/Play/ILocalUserPlayInfo.cs | 2 +- ...UserPlayingStates.cs => LocalUserPlayingState.cs} | 2 +- osu.Game/Screens/Play/Player.cs | 12 ++++++------ 13 files changed, 35 insertions(+), 35 deletions(-) rename osu.Game/Screens/Play/{LocalUserPlayingStates.cs => LocalUserPlayingState.cs} (94%) diff --git a/osu.Desktop/Updater/VelopackUpdateManager.cs b/osu.Desktop/Updater/VelopackUpdateManager.cs index dd0014a61f..5dda03a3d3 100644 --- a/osu.Desktop/Updater/VelopackUpdateManager.cs +++ b/osu.Desktop/Updater/VelopackUpdateManager.cs @@ -25,7 +25,7 @@ namespace osu.Desktop.Updater [Resolved] private ILocalUserPlayInfo? localUserInfo { get; set; } - private bool isInGameplay => localUserInfo?.PlayingState.Value != LocalUserPlayingStates.NotPlaying; + private bool isInGameplay => localUserInfo?.PlayingState.Value != LocalUserPlayingState.NotPlaying; private UpdateInfo? pendingUpdate; diff --git a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs index 268dacc077..085c198cc1 100644 --- a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs +++ b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs @@ -13,7 +13,7 @@ namespace osu.Desktop.Windows public partial class GameplayWinKeyBlocker : Component { private Bindable disableWinKey = null!; - private IBindable localUserPlaying = null!; + private IBindable localUserPlaying = null!; private IBindable isActive = null!; [Resolved] @@ -34,7 +34,7 @@ namespace osu.Desktop.Windows private void updateBlocking() { - bool shouldDisable = isActive.Value && disableWinKey.Value && localUserPlaying.Value == LocalUserPlayingStates.Playing; + bool shouldDisable = isActive.Value && disableWinKey.Value && localUserPlaying.Value == LocalUserPlayingState.Playing; if (shouldDisable) host.InputThread.Scheduler.Add(WindowsKey.Disable); diff --git a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs index 21193b5161..c40624a3a0 100644 --- a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs +++ b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs @@ -22,9 +22,9 @@ namespace osu.Game.Tests.Database [HeadlessTest] public partial class BackgroundDataStoreProcessorTests : OsuTestScene, ILocalUserPlayInfo { - public IBindable PlayingState => isPlaying; + public IBindable PlayingState => isPlaying; - private readonly Bindable isPlaying = new Bindable(); + private readonly Bindable isPlaying = new Bindable(); private BeatmapSetInfo importedSet = null!; @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Database [SetUpSteps] public void SetUpSteps() { - AddStep("Set not playing", () => isPlaying.Value = LocalUserPlayingStates.NotPlaying); + AddStep("Set not playing", () => isPlaying.Value = LocalUserPlayingState.NotPlaying); } [Test] @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Database }); }); - AddStep("Set playing", () => isPlaying.Value = LocalUserPlayingStates.Playing); + AddStep("Set playing", () => isPlaying.Value = LocalUserPlayingState.Playing); AddStep("Reset difficulty", () => { @@ -117,7 +117,7 @@ namespace osu.Game.Tests.Database }); }); - AddStep("Set not playing", () => isPlaying.Value = LocalUserPlayingStates.NotPlaying); + AddStep("Set not playing", () => isPlaying.Value = LocalUserPlayingState.NotPlaying); AddUntilStep("wait for difficulties repopulated", () => { diff --git a/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs b/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs index c4fdb2b427..42f50efdbf 100644 --- a/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs +++ b/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs @@ -17,7 +17,7 @@ namespace osu.Game.Tests.Input [HeadlessTest] public partial class ConfineMouseTrackerTest : OsuGameTestScene { - private readonly Bindable playingState = new Bindable(); + private readonly Bindable playingState = new Bindable(); [Resolved] private FrameworkConfigManager frameworkConfigManager { get; set; } = null!; @@ -28,7 +28,7 @@ namespace osu.Game.Tests.Input base.SetUpSteps(); // a bit dodgy. - AddStep("bind playing state", () => ((IBindable)playingState).BindTo(((ILocalUserPlayInfo)Game).PlayingState)); + AddStep("bind playing state", () => ((IBindable)playingState).BindTo(((ILocalUserPlayInfo)Game).PlayingState)); } [TestCase(WindowMode.Windowed)] @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Input => AddStep($"set {mode} game-side", () => Game.LocalConfig.SetValue(OsuSetting.ConfineMouseMode, mode)); private void setLocalUserPlayingTo(bool playing) - => AddStep($"local user {(playing ? "playing" : "not playing")}", () => playingState.Value = playing ? LocalUserPlayingStates.Playing : LocalUserPlayingStates.NotPlaying); + => AddStep($"local user {(playing ? "playing" : "not playing")}", () => playingState.Value = playing ? LocalUserPlayingState.Playing : LocalUserPlayingState.NotPlaying); private void gameSideModeIs(OsuConfineMouseMode mode) => AddAssert($"mode is {mode} game-side", () => Game.LocalConfig.Get(OsuSetting.ConfineMouseMode) == mode); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.cs index ca3d484115..6a500bbe55 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Cached(typeof(ILocalUserPlayInfo))] private ILocalUserPlayInfo localUserInfo; - private readonly Bindable playingState = new Bindable(); + private readonly Bindable playingState = new Bindable(); private TextBox textBox => chatDisplay.ChildrenOfType().First(); @@ -124,6 +124,6 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert($"chat {(isFocused ? "focused" : "not focused")}", () => textBox.HasFocus == isFocused); private void setLocalUserPlaying(bool playing) => - AddStep($"local user {(playing ? "playing" : "not playing")}", () => playingState.Value = playing ? LocalUserPlayingStates.Playing : LocalUserPlayingStates.NotPlaying); + AddStep($"local user {(playing ? "playing" : "not playing")}", () => playingState.Value = playing ? LocalUserPlayingState.Playing : LocalUserPlayingState.NotPlaying); } } diff --git a/osu.Game/Database/BackgroundDataStoreProcessor.cs b/osu.Game/Database/BackgroundDataStoreProcessor.cs index cc7746e34b..3efd4da3aa 100644 --- a/osu.Game/Database/BackgroundDataStoreProcessor.cs +++ b/osu.Game/Database/BackgroundDataStoreProcessor.cs @@ -606,7 +606,7 @@ namespace osu.Game.Database { // Importantly, also sleep if high performance session is active. // If we don't do this, memory usage can become runaway due to GC running in a more lenient mode. - while (localUserPlayInfo?.PlayingState.Value != LocalUserPlayingStates.NotPlaying || highPerformanceSessionManager?.IsSessionActive == true) + while (localUserPlayInfo?.PlayingState.Value != LocalUserPlayingState.NotPlaying || highPerformanceSessionManager?.IsSessionActive == true) { Logger.Log("Background processing sleeping due to active gameplay..."); Thread.Sleep(TimeToSleepDuringGameplay); diff --git a/osu.Game/Input/ConfineMouseTracker.cs b/osu.Game/Input/ConfineMouseTracker.cs index 0f4363e00f..eeda92a585 100644 --- a/osu.Game/Input/ConfineMouseTracker.cs +++ b/osu.Game/Input/ConfineMouseTracker.cs @@ -25,7 +25,7 @@ namespace osu.Game.Input private Bindable frameworkMinimiseOnFocusLossInFullscreen; private Bindable osuConfineMode; - private IBindable localUserPlaying; + private IBindable localUserPlaying; [BackgroundDependencyLoader] private void load(ILocalUserPlayInfo localUserInfo, FrameworkConfigManager frameworkConfigManager, OsuConfigManager osuConfigManager) @@ -63,7 +63,7 @@ namespace osu.Game.Input break; case OsuConfineMouseMode.DuringGameplay: - frameworkConfineMode.Value = localUserPlaying.Value == LocalUserPlayingStates.Playing ? ConfineMouseMode.Always : ConfineMouseMode.Never; + frameworkConfineMode.Value = localUserPlaying.Value == LocalUserPlayingState.Playing ? ConfineMouseMode.Always : ConfineMouseMode.Never; break; case OsuConfineMouseMode.Always: diff --git a/osu.Game/Input/OsuUserInputManager.cs b/osu.Game/Input/OsuUserInputManager.cs index ff1b88b65a..26ce2312d5 100644 --- a/osu.Game/Input/OsuUserInputManager.cs +++ b/osu.Game/Input/OsuUserInputManager.cs @@ -10,9 +10,9 @@ namespace osu.Game.Input { public partial class OsuUserInputManager : UserInputManager { - protected override bool AllowRightClickFromLongTouch => PlayingState.Value == LocalUserPlayingStates.NotPlaying; + protected override bool AllowRightClickFromLongTouch => PlayingState.Value == LocalUserPlayingState.NotPlaying; - public readonly Bindable PlayingState = new Bindable(); + public readonly Bindable PlayingState = new Bindable(); internal OsuUserInputManager() { diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 902a4ddb33..935631c8e9 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -175,9 +175,9 @@ namespace osu.Game /// public readonly IBindable OverlayActivationMode = new Bindable(); - IBindable ILocalUserPlayInfo.PlayingState => playingState; + IBindable ILocalUserPlayInfo.PlayingState => playingState; - private readonly Bindable playingState = new Bindable(); + private readonly Bindable playingState = new Bindable(); protected OsuScreenStack ScreenStack; @@ -388,9 +388,9 @@ namespace osu.Game playingState.BindValueChanged(p => { - BeatmapManager.PauseImports = p.NewValue != LocalUserPlayingStates.NotPlaying; - SkinManager.PauseImports = p.NewValue != LocalUserPlayingStates.NotPlaying; - ScoreManager.PauseImports = p.NewValue != LocalUserPlayingStates.NotPlaying; + BeatmapManager.PauseImports = p.NewValue != LocalUserPlayingState.NotPlaying; + SkinManager.PauseImports = p.NewValue != LocalUserPlayingState.NotPlaying; + ScoreManager.PauseImports = p.NewValue != LocalUserPlayingState.NotPlaying; }, true); IsActive.BindValueChanged(active => updateActiveState(active.NewValue), true); @@ -1549,7 +1549,7 @@ namespace osu.Game }); // reset on screen change for sanity. - playingState.Value = LocalUserPlayingStates.NotPlaying; + playingState.Value = LocalUserPlayingState.NotPlaying; if (current is Player oldPlayer) oldPlayer.PlayingState.UnbindFrom(playingState); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs index 165e3a2440..d4483044e0 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs @@ -23,9 +23,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [CanBeNull] private ILocalUserPlayInfo localUserInfo { get; set; } - private readonly IBindable localUserPlaying = new Bindable(); + private readonly IBindable localUserPlaying = new Bindable(); - public override bool PropagatePositionalInputSubTree => localUserPlaying.Value != LocalUserPlayingStates.Playing; + public override bool PropagatePositionalInputSubTree => localUserPlaying.Value != LocalUserPlayingState.Playing; public Bindable Expanded = new Bindable(); @@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer TextBox.HoldFocus = false; // only hold focus (after sending a message) during breaks - TextBox.ReleaseFocusOnCommit = playing.NewValue == LocalUserPlayingStates.Playing; + TextBox.ReleaseFocusOnCommit = playing.NewValue == LocalUserPlayingState.Playing; }, true); Expanded.BindValueChanged(_ => updateExpandedState(), true); diff --git a/osu.Game/Screens/Play/ILocalUserPlayInfo.cs b/osu.Game/Screens/Play/ILocalUserPlayInfo.cs index 48b610dd8e..dd24549c55 100644 --- a/osu.Game/Screens/Play/ILocalUserPlayInfo.cs +++ b/osu.Game/Screens/Play/ILocalUserPlayInfo.cs @@ -12,6 +12,6 @@ namespace osu.Game.Screens.Play /// /// Whether the local user is currently interacting (playing) with the game in a way that should not be interrupted. /// - IBindable PlayingState { get; } + IBindable PlayingState { get; } } } diff --git a/osu.Game/Screens/Play/LocalUserPlayingStates.cs b/osu.Game/Screens/Play/LocalUserPlayingState.cs similarity index 94% rename from osu.Game/Screens/Play/LocalUserPlayingStates.cs rename to osu.Game/Screens/Play/LocalUserPlayingState.cs index d1761692a7..9ae4130298 100644 --- a/osu.Game/Screens/Play/LocalUserPlayingStates.cs +++ b/osu.Game/Screens/Play/LocalUserPlayingState.cs @@ -3,7 +3,7 @@ namespace osu.Game.Screens.Play { - public enum LocalUserPlayingStates + public enum LocalUserPlayingState { /// /// The local player is not current in gameplay. If watching a replay, gameplay always remains in this state. diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e9e3757629..d6ac279494 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -94,7 +94,7 @@ namespace osu.Game.Screens.Play public IBindable LocalUserPlaying => localUserPlaying; private readonly Bindable localUserPlaying = new Bindable(); - private readonly Bindable playingState = new Bindable(); + private readonly Bindable playingState = new Bindable(); public int RestartCount; @@ -512,12 +512,12 @@ namespace osu.Game.Screens.Play bool inBreak = breakTracker.IsBreakTime.Value || DrawableRuleset.IsPaused.Value || GameplayState.HasFailed; if (inGameplay) - playingState.Value = inBreak ? LocalUserPlayingStates.Break : LocalUserPlayingStates.Playing; + playingState.Value = inBreak ? LocalUserPlayingState.Break : LocalUserPlayingState.Playing; else - playingState.Value = LocalUserPlayingStates.NotPlaying; + playingState.Value = LocalUserPlayingState.NotPlaying; - localUserPlaying.Value = playingState.Value == LocalUserPlayingStates.Playing; - OverlayActivationMode.Value = playingState.Value == LocalUserPlayingStates.Playing ? OverlayActivation.Disabled : OverlayActivation.UserTriggered; + localUserPlaying.Value = playingState.Value == LocalUserPlayingState.Playing; + OverlayActivationMode.Value = playingState.Value == LocalUserPlayingState.Playing ? OverlayActivation.Disabled : OverlayActivation.UserTriggered; } private void updateSampleDisabledState() @@ -1284,6 +1284,6 @@ namespace osu.Game.Screens.Play IBindable ISamplePlaybackDisabler.SamplePlaybackDisabled => samplePlaybackDisabled; - public IBindable PlayingState => playingState; + public IBindable PlayingState => playingState; } } From 1960a0d44dab6aa0e3ad9bc3fdfa6c5eb3e1c11d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 1 Oct 2024 13:07:25 +0200 Subject: [PATCH 372/924] Add tooltip to explain why addition banks dont work when no additions exist --- .../Components/TernaryButtons/DrawableTernaryButton.cs | 6 +++++- .../Edit/Components/TernaryButtons/TernaryButton.cs | 2 ++ .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 8 ++++++++ .../Edit/Compose/Components/EditorSelectionHandler.cs | 7 +++++++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs b/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs index 9e528aa21b..7a9bdfc41f 100644 --- a/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs +++ b/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs @@ -4,8 +4,10 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; @@ -14,7 +16,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.Edit.Components.TernaryButtons { - public partial class DrawableTernaryButton : OsuButton + public partial class DrawableTernaryButton : OsuButton, IHasTooltip { private Color4 defaultBackgroundColour; private Color4 defaultIconColour; @@ -98,5 +100,7 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons Anchor = Anchor.CentreLeft, X = 40f }; + + public LocalisableString TooltipText => Button.Tooltip; } } diff --git a/osu.Game/Screens/Edit/Components/TernaryButtons/TernaryButton.cs b/osu.Game/Screens/Edit/Components/TernaryButtons/TernaryButton.cs index 0ff2aa83b5..b025e4fbf5 100644 --- a/osu.Game/Screens/Edit/Components/TernaryButtons/TernaryButton.cs +++ b/osu.Game/Screens/Edit/Components/TernaryButtons/TernaryButton.cs @@ -19,6 +19,8 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons /// public readonly Func? CreateIcon; + public string Tooltip { get; set; } = string.Empty; + public TernaryButton(Bindable bindable, string description, Func? createIcon = null) { Bindable = bindable; diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 76e35a35c6..bfa6da8c7c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -68,6 +68,8 @@ namespace osu.Game.Screens.Edit.Compose.Components SampleBankTernaryStates = createSampleBankTernaryButtons(SelectionHandler.SelectionBankStates).ToArray(); SampleAdditionBankTernaryStates = createSampleBankTernaryButtons(SelectionHandler.SelectionAdditionBankStates).ToArray(); + SelectionHandler.SelectionAdditionBanksEnabled.BindValueChanged(_ => updateTernaryButtonTooltips()); + AddInternal(new DrawableRulesetDependenciesProvidingContainer(Composer.Ruleset) { Child = placementBlueprintContainer @@ -288,6 +290,12 @@ namespace osu.Game.Screens.Edit.Compose.Components return null; } + private void updateTernaryButtonTooltips() + { + foreach (var ternaryButton in SampleAdditionBankTernaryStates) + ternaryButton.Tooltip = !SelectionHandler.SelectionAdditionBanksEnabled.Value ? "Add an addition sample first to be able to set a bank" : string.Empty; + } + #region Placement /// diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index 8de3f3052f..a68d3afef8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -63,6 +63,11 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public readonly Dictionary> SelectionAdditionBankStates = new Dictionary>(); + /// + /// Whether the selection contains any addition samples and the can be used. + /// + public readonly Bindable SelectionAdditionBanksEnabled = new Bindable(); + /// /// Set up ternary state bindables and bind them to selection/hitobject changes (in both directions) /// @@ -266,6 +271,8 @@ namespace osu.Game.Screens.Edit.Compose.Components bindable.Value = GetStateFromSelection(samplesInSelection.SelectMany(s => s).Where(o => o.Name == HitSampleInfo.HIT_NORMAL), h => h.Bank == bankName); } + SelectionAdditionBanksEnabled.Value = samplesInSelection.SelectMany(s => s).Any(o => o.Name != HitSampleInfo.HIT_NORMAL); + foreach ((string bankName, var bindable) in SelectionAdditionBankStates) { bindable.Value = GetStateFromSelection(samplesInSelection.SelectMany(s => s).Where(o => o.Name != HitSampleInfo.HIT_NORMAL), h => (bankName != HIT_BANK_AUTO && h.Bank == bankName && !h.EditorAutoBank) || (bankName == HIT_BANK_AUTO && h.EditorAutoBank)); From 154a3eebc666a7856aaaecbacb72c2ded417aebd Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 1 Oct 2024 13:22:20 +0200 Subject: [PATCH 373/924] Reset the sample bank states to auto when selection is cleared this matches behaviour in stable --- .../Components/EditorSelectionHandler.cs | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index a68d3afef8..c30a418652 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -3,13 +3,13 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using Humanizer; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.UserInterface; using osu.Game.Audio; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -36,7 +36,7 @@ namespace osu.Game.Screens.Edit.Compose.Components // bring in updates from selection changes EditorBeatmap.HitObjectUpdated += _ => Scheduler.AddOnce(UpdateTernaryStates); - SelectedItems.CollectionChanged += (_, _) => Scheduler.AddOnce(UpdateTernaryStates); + SelectedItems.CollectionChanged += onSelectedItemsChanged; } protected override void DeleteItems(IEnumerable items) => EditorBeatmap.RemoveRange(items); @@ -208,9 +208,7 @@ namespace osu.Game.Screens.Edit.Compose.Components SelectionAdditionBankStates[bankName] = bindable; } - // start with normal selected. - SelectionBankStates[SampleControlPoint.DEFAULT_BANK].Value = TernaryState.True; - SelectionAdditionBankStates[HIT_BANK_AUTO].Value = TernaryState.True; + resetTernaryStates(); foreach (string sampleName in HitSampleInfo.AllAdditions) { @@ -252,6 +250,12 @@ namespace osu.Game.Screens.Edit.Compose.Components }; } + private void resetTernaryStates() + { + SelectionBankStates[HIT_BANK_AUTO].Value = TernaryState.True; + SelectionAdditionBankStates[HIT_BANK_AUTO].Value = TernaryState.True; + } + /// /// Called when context menu ternary states may need to be recalculated (selection changed or hitobject updated). /// @@ -279,6 +283,15 @@ namespace osu.Game.Screens.Edit.Compose.Components } } + private void onSelectedItemsChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + // Reset the ternary states when the selection is cleared. + if (e.OldStartingIndex >= 0 && e.NewStartingIndex < 0) + Scheduler.AddOnce(resetTernaryStates); + else + Scheduler.AddOnce(UpdateTernaryStates); + } + private IEnumerable> enumerateAllSamples(HitObject hitObject) { yield return hitObject.Samples; From 4f16ecdf1b5bd1dffff3ee00b13c977058ec0558 Mon Sep 17 00:00:00 2001 From: CloneWith Date: Tue, 1 Oct 2024 20:22:46 +0800 Subject: [PATCH 374/924] Add progress tooltip for ArgonSongProgressBar --- .../Screens/Play/HUD/ArgonSongProgressBar.cs | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs index 7a7870a775..87f5c495ff 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs @@ -6,16 +6,21 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Framework.Utils; +using osu.Game.Extensions; using osu.Game.Graphics; using osuTK; namespace osu.Game.Screens.Play.HUD { - public partial class ArgonSongProgressBar : SongProgressBar + public partial class ArgonSongProgressBar : SongProgressBar, IHasTooltip { + // Parent will handle restricting the area of valid input. public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; @@ -33,6 +38,13 @@ namespace osu.Game.Screens.Play.HUD private double trackTime => (EndTime - StartTime) * Progress; + private float relativePositionX; + + private InputManager? inputManager; + + public LocalisableString TooltipText => $"{(relativePositionX > 0 ? Math.Round(EndTime * relativePositionX / DrawWidth, 2) : relativePositionX > DrawWidth ? EndTime : 0).ToEditorFormattedString()}" + + $" - {(relativePositionX > 0 ? Math.Round(relativePositionX / DrawWidth * 100, 2) : relativePositionX > DrawWidth ? 100 : 0)}%"; + public ArgonSongProgressBar(float barHeight) { RelativeSizeAxes = Axes.X; @@ -74,6 +86,7 @@ namespace osu.Game.Screens.Play.HUD private void load(OsuColour colours) { catchUpColour = colours.BlueDark; + inputManager = GetContainingInputManager(); } protected override void LoadComplete() @@ -102,6 +115,18 @@ namespace osu.Game.Screens.Play.HUD { base.Update(); + if (inputManager != null) + { + // Update the cursor position in time + var cursorPosition = inputManager.CurrentState.Mouse.Position; + relativePositionX = ToLocalSpace(cursorPosition).X; + } + else + { + // If null (e.g. before the game starts), try getting the input manager again + inputManager = GetContainingInputManager(); + } + playfieldBar.Length = (float)Interpolation.Lerp(playfieldBar.Length, Progress, Math.Clamp(Time.Elapsed / 40, 0, 1)); audioBar.Length = (float)Interpolation.Lerp(audioBar.Length, AudioProgress, Math.Clamp(Time.Elapsed / 40, 0, 1)); From 5af05f1cc987f198a128f071efb345c052bf46fc Mon Sep 17 00:00:00 2001 From: CloneWith Date: Tue, 1 Oct 2024 20:53:15 +0800 Subject: [PATCH 375/924] Use play length for timestamp calculation --- osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs index 87f5c495ff..ea1ebce5f7 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Play.HUD private InputManager? inputManager; - public LocalisableString TooltipText => $"{(relativePositionX > 0 ? Math.Round(EndTime * relativePositionX / DrawWidth, 2) : relativePositionX > DrawWidth ? EndTime : 0).ToEditorFormattedString()}" + public LocalisableString TooltipText => $"{(relativePositionX > 0 ? (EndTime - StartTime) * relativePositionX / DrawWidth : relativePositionX > DrawWidth ? EndTime : 0).ToEditorFormattedString()}" + $" - {(relativePositionX > 0 ? Math.Round(relativePositionX / DrawWidth * 100, 2) : relativePositionX > DrawWidth ? 100 : 0)}%"; public ArgonSongProgressBar(float barHeight) From 5dd28d53521b31bc77c55f7d4fd4b6da80b7252a Mon Sep 17 00:00:00 2001 From: CloneWith Date: Tue, 1 Oct 2024 21:08:31 +0800 Subject: [PATCH 376/924] Fix extra newline --- osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs index ea1ebce5f7..28a2e1030f 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs @@ -20,7 +20,6 @@ namespace osu.Game.Screens.Play.HUD { public partial class ArgonSongProgressBar : SongProgressBar, IHasTooltip { - // Parent will handle restricting the area of valid input. public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; From 4de73dd9c8fc79d4e91402cb7ff7dadc2854fecf Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 1 Oct 2024 15:28:13 +0200 Subject: [PATCH 377/924] Reapply "Add tooltip to rounded button" This reverts commit 1912b1fcf3b5743459ab2981a81c6260f3f22f52. --- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 4 ++++ osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index b08ecb0e61..a5771c595b 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -155,6 +155,10 @@ namespace osu.Game.Rulesets.Osu.Edit Action = () => GridFromPointsClicked?.Invoke(), RelativeSizeAxes = Axes.X, Text = "Grid from points", + TooltipText = """ + Left click to set the origin. + Click and drag to set the origin, rotation and spacing. + """ }, gridTypeButtons = new EditorRadioButtonCollection { diff --git a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs index 6aded3fe32..a993867a93 100644 --- a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs +++ b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs @@ -8,6 +8,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Graphics.Backgrounds; @@ -17,7 +18,7 @@ using osuTK.Graphics; namespace osu.Game.Graphics.UserInterfaceV2 { - public partial class RoundedButton : OsuButton, IFilterable + public partial class RoundedButton : OsuButton, IFilterable, IHasTooltip { protected TrianglesV2? Triangles { get; private set; } @@ -107,5 +108,6 @@ namespace osu.Game.Graphics.UserInterfaceV2 } public bool FilteringActive { get; set; } + public LocalisableString TooltipText { get; set; } } } From 9fa2849b14fe6499a6740547a9db5f7ece23fcb4 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 1 Oct 2024 15:34:12 +0200 Subject: [PATCH 378/924] Fixed tooltip inheritors of RoundedButton --- osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs | 3 ++- osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs | 5 ++--- osu.Game/Overlays/Settings/SettingsButton.cs | 4 +--- osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs | 5 ++--- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs index a993867a93..9b57ebb200 100644 --- a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs +++ b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs @@ -108,6 +108,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 } public bool FilteringActive { get; set; } - public LocalisableString TooltipText { get; set; } + + public virtual LocalisableString TooltipText { get; set; } } } diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs index cbdb2ea190..eab394c8f6 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs @@ -7,7 +7,6 @@ using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; @@ -21,7 +20,7 @@ using APIUser = osu.Game.Online.API.Requests.Responses.APIUser; namespace osu.Game.Overlays.BeatmapSet.Buttons { - public partial class FavouriteButton : HeaderButton, IHasTooltip + public partial class FavouriteButton : HeaderButton { public readonly Bindable BeatmapSet = new Bindable(); @@ -32,7 +31,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons private readonly IBindable localUser = new Bindable(); - public LocalisableString TooltipText + public override LocalisableString TooltipText { get { diff --git a/osu.Game/Overlays/Settings/SettingsButton.cs b/osu.Game/Overlays/Settings/SettingsButton.cs index a837444758..d964a33578 100644 --- a/osu.Game/Overlays/Settings/SettingsButton.cs +++ b/osu.Game/Overlays/Settings/SettingsButton.cs @@ -12,7 +12,7 @@ using osu.Game.Graphics.UserInterfaceV2; namespace osu.Game.Overlays.Settings { - public partial class SettingsButton : RoundedButton, IHasTooltip, IConditionalFilterable + public partial class SettingsButton : RoundedButton, IConditionalFilterable { public SettingsButton() { @@ -20,8 +20,6 @@ namespace osu.Game.Overlays.Settings Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS }; } - public LocalisableString TooltipText { get; set; } - public IEnumerable Keywords { get; set; } = Array.Empty(); public BindableBool CanBeShown { get; } = new BindableBool(true); diff --git a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs index 2e669fd1b2..56e2719e9c 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs @@ -3,7 +3,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics.Cursor; using osu.Framework.Localisation; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Online; @@ -11,7 +10,7 @@ using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Components { - public abstract partial class ReadyButton : RoundedButton, IHasTooltip + public abstract partial class ReadyButton : RoundedButton { public new readonly BindableBool Enabled = new BindableBool(); @@ -29,7 +28,7 @@ namespace osu.Game.Screens.OnlinePlay.Components private void updateState() => base.Enabled.Value = availability.Value.State == DownloadState.LocallyAvailable && Enabled.Value; - public virtual LocalisableString TooltipText + public override LocalisableString TooltipText { get { From c84bb4b7978dd4e0be87c2a17912b2f204f95207 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 1 Oct 2024 15:38:21 +0200 Subject: [PATCH 379/924] Update tooltip --- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index a5771c595b..9ef1aa0ff8 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -157,7 +157,9 @@ namespace osu.Game.Rulesets.Osu.Edit Text = "Grid from points", TooltipText = """ Left click to set the origin. - Click and drag to set the origin, rotation and spacing. + Left click again to set the spacing and rotation. + Right click to only set the origin. + Click and drag to set the origin, spacing and rotation. """ }, gridTypeButtons = new EditorRadioButtonCollection From 4959045851609627cd8326cf4e2ceedd3f5578ac Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 1 Oct 2024 15:53:39 +0200 Subject: [PATCH 380/924] Remove 'Centre on selected object' button --- .../Edit/OsuGridToolboxGroup.cs | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 563135a1dd..089a8a84cd 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -16,7 +16,6 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.RadioButtons; @@ -88,7 +87,6 @@ namespace osu.Game.Rulesets.Osu.Edit private ExpandableSlider spacingSlider = null!; private ExpandableSlider gridLinesRotationSlider = null!; private RoundedButton gridFromPointsButton = null!; - private ExpandableButton useSelectedObjectPositionButton = null!; private EditorRadioButtonCollection gridTypeButtons = null!; public event Action? GridFromPointsClicked; @@ -135,19 +133,6 @@ namespace osu.Game.Rulesets.Osu.Edit Current = StartPositionY, KeyboardStep = 1, }, - useSelectedObjectPositionButton = new ExpandableButton - { - ExpandedLabelText = "Centre on selected object", - Action = () => - { - if (editorBeatmap.SelectedHitObjects.Count != 1) - return; - - StartPosition.Value = ((IHasPosition)editorBeatmap.SelectedHitObjects.Single()).Position; - updateEnabledStates(); - }, - RelativeSizeAxes = Axes.X, - }, spacingSlider = new ExpandableSlider { Current = Spacing, @@ -260,25 +245,15 @@ namespace osu.Game.Rulesets.Osu.Edit } }, true); - editorBeatmap.BeatmapReprocessed += updateEnabledStates; - editorBeatmap.SelectedHitObjects.BindCollectionChanged((_, _) => updateEnabledStates()); expandingContainer?.Expanded.BindValueChanged(v => { gridFromPointsButton.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint); gridFromPointsButton.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None; gridTypeButtons.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint); gridTypeButtons.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None; - updateEnabledStates(); }, true); } - private void updateEnabledStates() - { - useSelectedObjectPositionButton.Enabled.Value = expandingContainer?.Expanded.Value == true - && editorBeatmap.SelectedHitObjects.Count == 1 - && StartPosition.Value != ((IHasPosition)editorBeatmap.SelectedHitObjects.Single()).Position; - } - private float normalizeRotation(float rotation, float period) { return ((rotation + 360 + period * 0.5f) % period) - period * 0.5f; From 4bbefa360cd6b9c2358530f157b6f3d19f16f68e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 1 Oct 2024 15:57:34 +0200 Subject: [PATCH 381/924] fix using directive --- osu.Game/Overlays/Settings/SettingsButton.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/SettingsButton.cs b/osu.Game/Overlays/Settings/SettingsButton.cs index d964a33578..196ddca953 100644 --- a/osu.Game/Overlays/Settings/SettingsButton.cs +++ b/osu.Game/Overlays/Settings/SettingsButton.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Localisation; using osu.Game.Graphics.UserInterfaceV2; From 06774309756a1b6ee4eec0cc70bf2f2e3faa900f Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 21 Sep 2024 20:34:29 +0200 Subject: [PATCH 382/924] compute lower bound in clamp scale --- .../Edit/OsuSelectionScaleHandler.cs | 25 +++++++++++++------ .../Edit/PreciseScalePopover.cs | 10 ++++++++ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index fc85865dd2..fa2aa3e39a 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -241,33 +241,42 @@ namespace osu.Game.Rulesets.Osu.Edit foreach (var point in points) { - scale = clampToBound(scale, point, Vector2.Zero); - scale = clampToBound(scale, point, OsuPlayfield.BASE_SIZE); + scale = clampToBound(scale, point, Vector2.Zero, OsuPlayfield.BASE_SIZE); } return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON)); - float minPositiveComponent(Vector2 v) => MathF.Min(v.X < 0 ? float.PositiveInfinity : v.X, v.Y < 0 ? float.PositiveInfinity : v.Y); + float minComponent(Vector2 v) => MathF.Min(v.X, v.Y); + float maxComponent(Vector2 v) => MathF.Max(v.X, v.Y); - Vector2 clampToBound(Vector2 s, Vector2 p, Vector2 bound) + Vector2 clampToBound(Vector2 s, Vector2 p, Vector2 lowerBound, Vector2 upperBound) { p -= actualOrigin; - bound -= actualOrigin; + lowerBound -= actualOrigin; + upperBound -= actualOrigin; var a = new Vector2(cos * cos * p.X - sin * cos * p.Y, -sin * cos * p.X + sin * sin * p.Y); var b = new Vector2(sin * sin * p.X + sin * cos * p.Y, sin * cos * p.X + cos * cos * p.Y); switch (adjustAxis) { case Axes.X: - s.X = MathF.Min(scale.X, minPositiveComponent(Vector2.Divide(bound - b, a))); + var lowerBounds = Vector2.Divide(lowerBound - b, a); + var upperBounds = Vector2.Divide(upperBound - b, a); + if (a.X < 0) + (lowerBounds, upperBounds) = (upperBounds, lowerBounds); + s.X = MathHelper.Clamp(s.X, maxComponent(lowerBounds), minComponent(upperBounds)); break; case Axes.Y: - s.Y = MathF.Min(scale.Y, minPositiveComponent(Vector2.Divide(bound - a, b))); + var lowerBoundsY = Vector2.Divide(lowerBound - a, b); + var upperBoundsY = Vector2.Divide(upperBound - a, b); + if (b.Y < 0) + (lowerBoundsY, upperBoundsY) = (upperBoundsY, lowerBoundsY); + s.Y = MathHelper.Clamp(s.Y, maxComponent(lowerBoundsY), minComponent(upperBoundsY)); break; case Axes.Both: - s = Vector2.ComponentMin(s, s * minPositiveComponent(Vector2.Divide(bound, a * s.X + b * s.Y))); + // s = Vector2.ComponentMin(s, s * minPositiveComponent(Vector2.Divide(bound, a * s.X + b * s.Y))); break; } diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index 33b0c14185..cea0864052 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -189,6 +189,16 @@ namespace osu.Game.Rulesets.Osu.Edit scale.Y = max_scale; scaleInputBindable.MaxValue = MathF.Max(1, MathF.Min(scale.X, scale.Y)); + + const float min_scale = -10; + scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(min_scale), getOriginPosition(scaleInfo.Value), getAdjustAxis(scaleInfo.Value), getRotation(scaleInfo.Value)); + + if (!scaleInfo.Value.XAxis) + scale.X = max_scale; + if (!scaleInfo.Value.YAxis) + scale.Y = max_scale; + + scaleInputBindable.MinValue = MathF.Min(-1, MathF.Max(scale.X, scale.Y)); } private void setOrigin(ScaleOrigin origin) From 08e1698bb6a7f27de3f0b737b1998e2108de3bdb Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 21 Sep 2024 22:04:11 +0200 Subject: [PATCH 383/924] fix scale clamp computation and code cleanup --- .../Edit/OsuSelectionScaleHandler.cs | 58 ++++++++++++------- .../Edit/PreciseScalePopover.cs | 9 +-- 2 files changed, 43 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index fa2aa3e39a..77fa64b0b1 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -240,48 +240,66 @@ namespace osu.Game.Rulesets.Osu.Edit points = originalConvexHull!; foreach (var point in points) - { scale = clampToBound(scale, point, Vector2.Zero, OsuPlayfield.BASE_SIZE); - } - return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON)); + return scale; - float minComponent(Vector2 v) => MathF.Min(v.X, v.Y); - float maxComponent(Vector2 v) => MathF.Max(v.X, v.Y); - - Vector2 clampToBound(Vector2 s, Vector2 p, Vector2 lowerBound, Vector2 upperBound) + Vector2 clampToBound(Vector2 s, Vector2 p, Vector2 lowerBounds, Vector2 upperBounds) { p -= actualOrigin; - lowerBound -= actualOrigin; - upperBound -= actualOrigin; + lowerBounds -= actualOrigin; + upperBounds -= actualOrigin; + // a.X is the rotated X component of p with respect to the X bounds + // a.Y is the rotated X component of p with respect to the Y bounds + // b.X is the rotated Y component of p with respect to the X bounds + // b.Y is the rotated Y component of p with respect to the Y bounds var a = new Vector2(cos * cos * p.X - sin * cos * p.Y, -sin * cos * p.X + sin * sin * p.Y); var b = new Vector2(sin * sin * p.X + sin * cos * p.Y, sin * cos * p.X + cos * cos * p.Y); + float sLowerBound, sUpperBound; + switch (adjustAxis) { case Axes.X: - var lowerBounds = Vector2.Divide(lowerBound - b, a); - var upperBounds = Vector2.Divide(upperBound - b, a); - if (a.X < 0) - (lowerBounds, upperBounds) = (upperBounds, lowerBounds); - s.X = MathHelper.Clamp(s.X, maxComponent(lowerBounds), minComponent(upperBounds)); + (sLowerBound, sUpperBound) = computeBounds(lowerBounds - b, upperBounds - b, a); + s.X = MathHelper.Clamp(s.X, sLowerBound, sUpperBound); break; case Axes.Y: - var lowerBoundsY = Vector2.Divide(lowerBound - a, b); - var upperBoundsY = Vector2.Divide(upperBound - a, b); - if (b.Y < 0) - (lowerBoundsY, upperBoundsY) = (upperBoundsY, lowerBoundsY); - s.Y = MathHelper.Clamp(s.Y, maxComponent(lowerBoundsY), minComponent(upperBoundsY)); + (sLowerBound, sUpperBound) = computeBounds(lowerBounds - a, upperBounds - a, b); + s.Y = MathHelper.Clamp(s.Y, sLowerBound, sUpperBound); break; case Axes.Both: - // s = Vector2.ComponentMin(s, s * minPositiveComponent(Vector2.Divide(bound, a * s.X + b * s.Y))); + // Here we compute the bounds for the magnitude multiplier of the scale vector + // Therefore the ratio s.X / s.Y will be maintained + (sLowerBound, sUpperBound) = computeBounds(lowerBounds, upperBounds, a * s.X + b * s.Y); + s.X = s.X < 0 + ? MathHelper.Clamp(s.X, s.X * sUpperBound, s.X * sLowerBound) + : MathHelper.Clamp(s.X, s.X * sLowerBound, s.X * sUpperBound); + s.Y = s.Y < 0 + ? MathHelper.Clamp(s.Y, s.Y * sUpperBound, s.Y * sLowerBound) + : MathHelper.Clamp(s.Y, s.Y * sLowerBound, s.Y * sUpperBound); break; } return s; } + + (float, float) computeBounds(Vector2 lowerBounds, Vector2 upperBounds, Vector2 p) + { + var sLowerBounds = Vector2.Divide(lowerBounds, p); + var sUpperBounds = Vector2.Divide(upperBounds, p); + if (p.X < 0) + (sLowerBounds.X, sUpperBounds.X) = (sUpperBounds.X, sLowerBounds.X); + if (p.Y < 0) + (sLowerBounds.Y, sUpperBounds.Y) = (sUpperBounds.Y, sLowerBounds.Y); + if (Precision.AlmostEquals(p.X, 0)) + (sLowerBounds.X, sUpperBounds.X) = (float.NegativeInfinity, float.PositiveInfinity); + if (Precision.AlmostEquals(p.Y, 0)) + (sLowerBounds.Y, sUpperBounds.Y) = (float.NegativeInfinity, float.PositiveInfinity); + return (MathF.Max(sLowerBounds.X, sLowerBounds.Y), MathF.Min(sUpperBounds.X, sUpperBounds.Y)); + } } private void moveSelectionInBounds() diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index cea0864052..2c17229e02 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -180,7 +180,9 @@ namespace osu.Game.Rulesets.Osu.Edit if (!scaleHandler.OriginalSurroundingQuad.HasValue) return; + const float min_scale = -10; const float max_scale = 10; + var scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(max_scale), getOriginPosition(scaleInfo.Value), getAdjustAxis(scaleInfo.Value), getRotation(scaleInfo.Value)); if (!scaleInfo.Value.XAxis) @@ -190,15 +192,14 @@ namespace osu.Game.Rulesets.Osu.Edit scaleInputBindable.MaxValue = MathF.Max(1, MathF.Min(scale.X, scale.Y)); - const float min_scale = -10; scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(min_scale), getOriginPosition(scaleInfo.Value), getAdjustAxis(scaleInfo.Value), getRotation(scaleInfo.Value)); if (!scaleInfo.Value.XAxis) - scale.X = max_scale; + scale.X = min_scale; if (!scaleInfo.Value.YAxis) - scale.Y = max_scale; + scale.Y = min_scale; - scaleInputBindable.MinValue = MathF.Min(-1, MathF.Max(scale.X, scale.Y)); + scaleInputBindable.MinValue = MathF.Min(1, MathF.Max(scale.X, scale.Y)); } private void setOrigin(ScaleOrigin origin) From 0695d51968310671da063a0fa4ddf08d808f8a6b Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 1 Oct 2024 16:48:55 +0200 Subject: [PATCH 384/924] Set minimum scale to 0.5 --- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index 2c17229e02..cf4473bd41 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -180,7 +180,7 @@ namespace osu.Game.Rulesets.Osu.Edit if (!scaleHandler.OriginalSurroundingQuad.HasValue) return; - const float min_scale = -10; + const float min_scale = 0.5f; const float max_scale = 10; var scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(max_scale), getOriginPosition(scaleInfo.Value), getAdjustAxis(scaleInfo.Value), getRotation(scaleInfo.Value)); From 87835f2481f5945262dc4e94810e6ae6c9577005 Mon Sep 17 00:00:00 2001 From: StanR Date: Wed, 2 Oct 2024 19:47:22 +0500 Subject: [PATCH 385/924] Uncap speed od accuracy scaling --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 67d88b6b01..2941aeb936 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -177,7 +177,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double relevantAccuracy = attributes.SpeedNoteCount == 0 ? 0 : (relevantCountGreat * 6.0 + relevantCountOk * 2.0 + relevantCountMeh) / (attributes.SpeedNoteCount * 6.0); // Scale the speed value with accuracy and OD. - speedValue *= (0.95 + Math.Pow(attributes.OverallDifficulty, 2) / 750) * Math.Pow((accuracy + relevantAccuracy) / 2.0, (14.5 - Math.Max(attributes.OverallDifficulty, 8)) / 2); + speedValue *= (0.95 + Math.Pow(attributes.OverallDifficulty, 2) / 750) * Math.Pow((accuracy + relevantAccuracy) / 2.0, (14.5 - attributes.OverallDifficulty) / 2); // Scale the speed value with # of 50s to punish doubletapping. speedValue *= Math.Pow(0.99, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0); From 1039d4e45de192aedd8e97ed05addd068df8dacc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Oct 2024 16:21:27 +0900 Subject: [PATCH 386/924] Increase chat font size again --- osu.Game/Online/Chat/StandAloneChatDisplay.cs | 1 - osu.Game/Overlays/Chat/ChatLine.cs | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index e100b5fe5b..187191d232 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -205,7 +205,6 @@ namespace osu.Game.Online.Chat protected partial class StandAloneMessage : ChatLine { - protected override float FontSize => 13; protected override float Spacing => 5; protected override float UsernameWidth => 90; diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 3f8862de36..e386f2ac09 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Chat public IReadOnlyCollection DrawableContentFlow => drawableContentFlow; - protected virtual float FontSize => 12; + private const float font_size = 13; protected virtual float Spacing => 15; @@ -183,13 +183,13 @@ namespace osu.Game.Overlays.Chat Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, Spacing = new Vector2(-1, 0), - Font = OsuFont.GetFont(size: FontSize, weight: FontWeight.SemiBold, fixedWidth: true), + Font = OsuFont.GetFont(size: font_size, weight: FontWeight.SemiBold, fixedWidth: true), AlwaysPresent = true, }, drawableUsername = new DrawableChatUsername(message.Sender) { Width = UsernameWidth, - FontSize = FontSize, + FontSize = font_size, AutoSizeAxes = Axes.Y, Origin = Anchor.TopRight, Anchor = Anchor.TopRight, @@ -258,7 +258,7 @@ namespace osu.Game.Overlays.Chat private void styleMessageContent(SpriteText text) { text.Shadow = false; - text.Font = text.Font.With(size: FontSize, italics: Message.IsAction, weight: isMention ? FontWeight.SemiBold : FontWeight.Medium); + text.Font = text.Font.With(size: font_size, italics: Message.IsAction, weight: isMention ? FontWeight.SemiBold : FontWeight.Medium); Color4 messageColour = colourProvider?.Content1 ?? Colour4.White; From ecf144f4a5866a29ac6d792c6ed73c0619f3b8bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Oct 2024 17:11:25 +0900 Subject: [PATCH 387/924] Add failing test of importing failed replay in `OsuGame` flow We had a test covering this but it wasn't within `OsuGame` so didn't have full import blocking coverage (see https://github.com/ppy/osu/blob/cbbe2f9dc04abb052a0d762aa798eba094fdd80c/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs#L89-L88). --- .../Navigation/TestSceneScreenNavigation.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index eda7ce925a..d374ae1bd9 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -364,6 +364,37 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != getOriginalPlayer() && Game.ScreenStack.CurrentScreen is Player); } + [Test] + public void TestSaveFailedReplay() + { + Player player = null; + + Screens.Select.SongSelect songSelect = null; + PushAndConfirm(() => songSelect = new TestPlaySongSelect()); + AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + + AddStep("import beatmap", () => BeatmapImportHelper.LoadOszIntoOsu(Game, virtualTrack: true).WaitSafely()); + + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + + AddStep("press enter", () => InputManager.Key(Key.Enter)); + + AddUntilStep("wait for player", () => + { + DismissAnyNotifications(); + return (player = Game.ScreenStack.CurrentScreen as Player) != null; + }); + + AddUntilStep("wait for fail", () => player.GameplayState.HasFailed); + + AddUntilStep("fail screen displayed", () => player.ChildrenOfType().First().State.Value == Visibility.Visible); + AddUntilStep("wait for button clickable", () => player.ChildrenOfType().First().ChildrenOfType().First().Enabled.Value); + + AddUntilStep("score not in database", () => Realm.Run(r => r.Find(player.Score.ScoreInfo.ID) == null)); + AddStep("click save button", () => player.ChildrenOfType().First().ChildrenOfType().First().TriggerClick()); + AddUntilStep("score in database", () => Realm.Run(r => r.Find(player.Score.ScoreInfo.ID) != null)); + } + [Test] public void TestExitImmediatelyAfterCompletion() { From 24d6ea5444a5a782d29ad64dc129237587bae222 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Oct 2024 17:11:41 +0900 Subject: [PATCH 388/924] Change failed states to be considered as `NotPlaying` --- osu.Game/Screens/Play/Player.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index d6ac279494..536050c9bd 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -508,8 +508,8 @@ namespace osu.Game.Screens.Play private void updateGameplayState() { - bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !GameplayState.HasPassed; - bool inBreak = breakTracker.IsBreakTime.Value || DrawableRuleset.IsPaused.Value || GameplayState.HasFailed; + bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !GameplayState.HasPassed && !GameplayState.HasFailed; + bool inBreak = breakTracker.IsBreakTime.Value || DrawableRuleset.IsPaused.Value; if (inGameplay) playingState.Value = inBreak ? LocalUserPlayingState.Break : LocalUserPlayingState.Playing; From 80dffa905a2596ba43805f3653d91b4eeed6ff1f Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 3 Oct 2024 11:13:49 +0200 Subject: [PATCH 389/924] Use new keyword instead of overriding TooltipText to remove setter --- osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs | 2 +- osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs | 2 +- osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs index 9b57ebb200..5875874cf6 100644 --- a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs +++ b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs @@ -109,6 +109,6 @@ namespace osu.Game.Graphics.UserInterfaceV2 public bool FilteringActive { get; set; } - public virtual LocalisableString TooltipText { get; set; } + public LocalisableString TooltipText { get; set; } } } diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs index eab394c8f6..9d6d617082 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs @@ -31,7 +31,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons private readonly IBindable localUser = new Bindable(); - public override LocalisableString TooltipText + public new LocalisableString TooltipText { get { diff --git a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs index 56e2719e9c..95fb48e3ce 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.OnlinePlay.Components private void updateState() => base.Enabled.Value = availability.Value.State == DownloadState.LocallyAvailable && Enabled.Value; - public override LocalisableString TooltipText + public new virtual LocalisableString TooltipText { get { From 2c39ecbda3ee3b20958b0904519b534788f61477 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 3 Oct 2024 11:16:37 +0200 Subject: [PATCH 390/924] Add xmldoc to SnapType --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 2b346d750f..a36de02433 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -71,6 +71,9 @@ namespace osu.Game.Rulesets.Edit PlacementActive = PlacementState.Finished; } + /// + /// Determines which objects to snap to for the snap result in . + /// public virtual SnapType SnapType => SnapType.All; /// From df730e6b6f1fb98e902a1df561272af9ba5fb058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Aug 2024 11:45:32 +0200 Subject: [PATCH 391/924] Implement "form" colour palette control --- .../UserInterface/TestSceneFormControls.cs | 11 + .../UserInterfaceV2/FormColourPalette.cs | 226 ++++++++++++++++++ 2 files changed, 237 insertions(+) create mode 100644 osu.Game/Graphics/UserInterfaceV2/FormColourPalette.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs index 2a0b0515a1..518c3fc693 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs @@ -105,6 +105,17 @@ namespace osu.Game.Tests.Visual.UserInterface Caption = "Audio file", PlaceholderText = "Select an audio file", }, + new FormColourPalette + { + Caption = "Combo colours", + Colours = + { + Colour4.Red, + Colour4.Green, + Colour4.Blue, + Colour4.Yellow, + } + }, }, }, } diff --git a/osu.Game/Graphics/UserInterfaceV2/FormColourPalette.cs b/osu.Game/Graphics/UserInterfaceV2/FormColourPalette.cs new file mode 100644 index 0000000000..a2c1c1622e --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/FormColourPalette.cs @@ -0,0 +1,226 @@ +// 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.Specialized; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Framework.Localisation; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; +using osu.Game.Resources.Localisation.Web; +using osuTK; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public partial class FormColourPalette : CompositeDrawable + { + public BindableList Colours { get; } = new BindableList(); + + public LocalisableString Caption { get; init; } + public LocalisableString HintText { get; init; } + + private Box background = null!; + private FormFieldCaption caption = null!; + private FillFlowContainer flow = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Masking = true; + CornerRadius = 5; + + RoundedButton button; + + InternalChildren = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background5, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(9), + Spacing = new Vector2(7), + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + caption = new FormFieldCaption + { + Caption = Caption, + TooltipText = HintText, + }, + flow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + Spacing = new Vector2(5), + Child = button = new RoundedButton + { + Action = () => Colours.Add(Colour4.White), + Size = new Vector2(70, 25), + Text = "+", + } + } + }, + }, + }; + + flow.SetLayoutPosition(button, float.MaxValue); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Colours.BindCollectionChanged((_, args) => + { + if (args.Action != NotifyCollectionChangedAction.Replace) + updateColours(); + }, true); + updateState(); + } + + protected override bool OnHover(HoverEvent e) + { + updateState(); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + updateState(); + } + + private void updateState() + { + background.Colour = colourProvider.Background5; + caption.Colour = colourProvider.Content2; + + BorderThickness = IsHovered ? 2 : 0; + + if (IsHovered) + BorderColour = colourProvider.Light4; + } + + private void updateColours() + { + flow.RemoveAll(d => d is ColourButton, true); + + for (int i = 0; i < Colours.Count; ++i) + { + // copy to avoid accesses to modified closure. + int colourIndex = i; + var colourButton = new ColourButton { Current = { Value = Colours[colourIndex] } }; + colourButton.Current.BindValueChanged(colour => Colours[colourIndex] = colour.NewValue); + colourButton.DeleteRequested = () => Colours.RemoveAt(flow.IndexOf(colourButton)); + flow.Add(colourButton); + } + } + + private partial class ColourButton : OsuClickableContainer, IHasPopover, IHasContextMenu + { + public Bindable Current { get; } = new Bindable(); + public Action? DeleteRequested { get; set; } + + private Box background = null!; + private OsuSpriteText hexCode = null!; + + [BackgroundDependencyLoader] + private void load() + { + Size = new Vector2(70, 25); + + Masking = true; + CornerRadius = 3; + Action = this.ShowPopover; + + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + hexCode = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(_ => updateState(), true); + } + + public Popover GetPopover() => new ColourPickerPopover + { + Current = { BindTarget = Current } + }; + + public MenuItem[] ContextMenuItems => new MenuItem[] + { + new OsuMenuItem(CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => DeleteRequested?.Invoke()) + }; + + private void updateState() + { + background.Colour = Current.Value; + hexCode.Text = Current.Value.ToHex(); + hexCode.Colour = OsuColour.ForegroundTextColourFor(Current.Value); + } + } + + private partial class ColourPickerPopover : OsuPopover, IHasCurrentValue + { + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public ColourPickerPopover() + : base(false) + { + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + Child = new OsuColourPicker + { + Current = { BindTarget = Current } + }; + + Body.BorderThickness = 2; + Body.BorderColour = colourProvider.Highlight1; + Content.Padding = new MarginPadding(2); + } + } + } +} From b15608343b8d201c77ffeafd5081faead2257d09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Aug 2024 12:17:39 +0200 Subject: [PATCH 392/924] Replace setup screen controls with new "form" controls --- .../Edit/Setup/ManiaDifficultySection.cs | 69 ++++++----- .../Edit/Setup/OsuDifficultySection.cs | 88 +++++++------- .../Edit/Setup/TaikoDifficultySection.cs | 52 ++++----- .../Visual/Editing/TestSceneDesignSection.cs | 6 +- .../Editing/TestSceneMetadataSection.cs | 12 +- .../UserInterfaceV2/FormColourPalette.cs | 2 +- .../UserInterfaceV2/FormFileSelector.cs | 6 +- osu.Game/Screens/Edit/Setup/ColoursSection.cs | 8 +- osu.Game/Screens/Edit/Setup/DesignSection.cs | 60 +++++----- .../Screens/Edit/Setup/DifficultySection.cs | 76 ++++++------ .../Edit/Setup/LabelledRomanisedTextBox.cs | 22 ---- .../Screens/Edit/Setup/MetadataSection.cs | 60 +++++----- .../Screens/Edit/Setup/ResourcesSection.cs | 35 ++---- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 110 +++++++++++------- osu.Game/Screens/Edit/Setup/SetupSection.cs | 17 +-- 15 files changed, 306 insertions(+), 317 deletions(-) delete mode 100644 osu.Game/Screens/Edit/Setup/LabelledRomanisedTextBox.cs diff --git a/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs b/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs index 7168504309..ed1de591f6 100644 --- a/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs +++ b/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs @@ -19,12 +19,12 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup { public override LocalisableString Title => EditorSetupStrings.DifficultyHeader; - private LabelledSliderBar keyCountSlider { get; set; } = null!; - private LabelledSwitchButton specialStyle { get; set; } = null!; - private LabelledSliderBar healthDrainSlider { get; set; } = null!; - private LabelledSliderBar overallDifficultySlider { get; set; } = null!; - private LabelledSliderBar baseVelocitySlider { get; set; } = null!; - private LabelledSliderBar tickRateSlider { get; set; } = null!; + private FormSliderBar keyCountSlider { get; set; } = null!; + private FormCheckBox specialStyle { get; set; } = null!; + private FormSliderBar healthDrainSlider { get; set; } = null!; + private FormSliderBar overallDifficultySlider { get; set; } = null!; + private FormSliderBar baseVelocitySlider { get; set; } = null!; + private FormSliderBar tickRateSlider { get; set; } = null!; [Resolved] private Editor? editor { get; set; } @@ -37,77 +37,76 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup { Children = new Drawable[] { - keyCountSlider = new LabelledSliderBar + keyCountSlider = new FormSliderBar { - Label = BeatmapsetsStrings.ShowStatsCsMania, - FixedLabelWidth = LABEL_WIDTH, - Description = "The number of columns in the beatmap", + Caption = BeatmapsetsStrings.ShowStatsCsMania, + HintText = "The number of columns in the beatmap", Current = new BindableFloat(Beatmap.Difficulty.CircleSize) { Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, MinValue = 0, MaxValue = 10, Precision = 1, - } + }, + TabbableContentContainer = this, }, - specialStyle = new LabelledSwitchButton + specialStyle = new FormCheckBox { - Label = "Use special (N+1) style", - FixedLabelWidth = LABEL_WIDTH, - Description = "Changes one column to act as a classic \"scratch\" or \"special\" column, which can be moved around by the user's skin (to the left/right/centre). Generally used in 6K (5+1) or 8K (7+1) configurations.", + Caption = "Use special (N+1) style", + HintText = "Changes one column to act as a classic \"scratch\" or \"special\" column, which can be moved around by the user's skin (to the left/right/centre). Generally used in 6K (5+1) or 8K (7+1) configurations.", Current = { Value = Beatmap.BeatmapInfo.SpecialStyle } }, - healthDrainSlider = new LabelledSliderBar + healthDrainSlider = new FormSliderBar { - Label = BeatmapsetsStrings.ShowStatsDrain, - FixedLabelWidth = LABEL_WIDTH, - Description = EditorSetupStrings.DrainRateDescription, + Caption = BeatmapsetsStrings.ShowStatsDrain, + HintText = EditorSetupStrings.DrainRateDescription, Current = new BindableFloat(Beatmap.Difficulty.DrainRate) { Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, MinValue = 0, MaxValue = 10, Precision = 0.1f, - } + }, + TabbableContentContainer = this, }, - overallDifficultySlider = new LabelledSliderBar + overallDifficultySlider = new FormSliderBar { - Label = BeatmapsetsStrings.ShowStatsAccuracy, - FixedLabelWidth = LABEL_WIDTH, - Description = EditorSetupStrings.OverallDifficultyDescription, + Caption = BeatmapsetsStrings.ShowStatsAccuracy, + HintText = EditorSetupStrings.OverallDifficultyDescription, Current = new BindableFloat(Beatmap.Difficulty.OverallDifficulty) { Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, MinValue = 0, MaxValue = 10, Precision = 0.1f, - } + }, + TabbableContentContainer = this, }, - baseVelocitySlider = new LabelledSliderBar + baseVelocitySlider = new FormSliderBar { - Label = EditorSetupStrings.BaseVelocity, - FixedLabelWidth = LABEL_WIDTH, - Description = EditorSetupStrings.BaseVelocityDescription, + Caption = EditorSetupStrings.BaseVelocity, + HintText = EditorSetupStrings.BaseVelocityDescription, Current = new BindableDouble(Beatmap.Difficulty.SliderMultiplier) { Default = 1.4, MinValue = 0.4, MaxValue = 3.6, Precision = 0.01f, - } + }, + TabbableContentContainer = this, }, - tickRateSlider = new LabelledSliderBar + tickRateSlider = new FormSliderBar { - Label = EditorSetupStrings.TickRate, - FixedLabelWidth = LABEL_WIDTH, - Description = EditorSetupStrings.TickRateDescription, + Caption = EditorSetupStrings.TickRate, + HintText = EditorSetupStrings.TickRateDescription, Current = new BindableDouble(Beatmap.Difficulty.SliderTickRate) { Default = 1, MinValue = 1, MaxValue = 4, Precision = 1, - } + }, + TabbableContentContainer = this, }, }; diff --git a/osu.Game.Rulesets.Osu/Edit/Setup/OsuDifficultySection.cs b/osu.Game.Rulesets.Osu/Edit/Setup/OsuDifficultySection.cs index b61faa0ae9..7008c87d41 100644 --- a/osu.Game.Rulesets.Osu/Edit/Setup/OsuDifficultySection.cs +++ b/osu.Game.Rulesets.Osu/Edit/Setup/OsuDifficultySection.cs @@ -16,13 +16,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Setup { public partial class OsuDifficultySection : SetupSection { - private LabelledSliderBar circleSizeSlider { get; set; } = null!; - private LabelledSliderBar healthDrainSlider { get; set; } = null!; - private LabelledSliderBar approachRateSlider { get; set; } = null!; - private LabelledSliderBar overallDifficultySlider { get; set; } = null!; - private LabelledSliderBar baseVelocitySlider { get; set; } = null!; - private LabelledSliderBar tickRateSlider { get; set; } = null!; - private LabelledSliderBar stackLeniency { get; set; } = null!; + private FormSliderBar circleSizeSlider { get; set; } = null!; + private FormSliderBar healthDrainSlider { get; set; } = null!; + private FormSliderBar approachRateSlider { get; set; } = null!; + private FormSliderBar overallDifficultySlider { get; set; } = null!; + private FormSliderBar baseVelocitySlider { get; set; } = null!; + private FormSliderBar tickRateSlider { get; set; } = null!; + private FormSliderBar stackLeniency { get; set; } = null!; public override LocalisableString Title => EditorSetupStrings.DifficultyHeader; @@ -31,103 +31,103 @@ namespace osu.Game.Rulesets.Osu.Edit.Setup { Children = new Drawable[] { - circleSizeSlider = new LabelledSliderBar + circleSizeSlider = new FormSliderBar { - Label = BeatmapsetsStrings.ShowStatsCs, - FixedLabelWidth = LABEL_WIDTH, - Description = EditorSetupStrings.CircleSizeDescription, + Caption = BeatmapsetsStrings.ShowStatsCs, + HintText = EditorSetupStrings.CircleSizeDescription, Current = new BindableFloat(Beatmap.Difficulty.CircleSize) { Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, MinValue = 0, MaxValue = 10, Precision = 0.1f, - } + }, + TabbableContentContainer = this, }, - healthDrainSlider = new LabelledSliderBar + healthDrainSlider = new FormSliderBar { - Label = BeatmapsetsStrings.ShowStatsDrain, - FixedLabelWidth = LABEL_WIDTH, - Description = EditorSetupStrings.DrainRateDescription, + Caption = BeatmapsetsStrings.ShowStatsDrain, + HintText = EditorSetupStrings.DrainRateDescription, Current = new BindableFloat(Beatmap.Difficulty.DrainRate) { Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, MinValue = 0, MaxValue = 10, Precision = 0.1f, - } + }, + TabbableContentContainer = this, }, - approachRateSlider = new LabelledSliderBar + approachRateSlider = new FormSliderBar { - Label = BeatmapsetsStrings.ShowStatsAr, - FixedLabelWidth = LABEL_WIDTH, - Description = EditorSetupStrings.ApproachRateDescription, + Caption = BeatmapsetsStrings.ShowStatsAr, + HintText = EditorSetupStrings.ApproachRateDescription, Current = new BindableFloat(Beatmap.Difficulty.ApproachRate) { Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, MinValue = 0, MaxValue = 10, Precision = 0.1f, - } + }, + TabbableContentContainer = this, }, - overallDifficultySlider = new LabelledSliderBar + overallDifficultySlider = new FormSliderBar { - Label = BeatmapsetsStrings.ShowStatsAccuracy, - FixedLabelWidth = LABEL_WIDTH, - Description = EditorSetupStrings.OverallDifficultyDescription, + Caption = BeatmapsetsStrings.ShowStatsAccuracy, + HintText = EditorSetupStrings.OverallDifficultyDescription, Current = new BindableFloat(Beatmap.Difficulty.OverallDifficulty) { Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, MinValue = 0, MaxValue = 10, Precision = 0.1f, - } + }, + TabbableContentContainer = this, }, - baseVelocitySlider = new LabelledSliderBar + baseVelocitySlider = new FormSliderBar { - Label = EditorSetupStrings.BaseVelocity, - FixedLabelWidth = LABEL_WIDTH, - Description = EditorSetupStrings.BaseVelocityDescription, + Caption = EditorSetupStrings.BaseVelocity, + HintText = EditorSetupStrings.BaseVelocityDescription, Current = new BindableDouble(Beatmap.Difficulty.SliderMultiplier) { Default = 1.4, MinValue = 0.4, MaxValue = 3.6, Precision = 0.01f, - } + }, + TabbableContentContainer = this, }, - tickRateSlider = new LabelledSliderBar + tickRateSlider = new FormSliderBar { - Label = EditorSetupStrings.TickRate, - FixedLabelWidth = LABEL_WIDTH, - Description = EditorSetupStrings.TickRateDescription, + Caption = EditorSetupStrings.TickRate, + HintText = EditorSetupStrings.TickRateDescription, Current = new BindableDouble(Beatmap.Difficulty.SliderTickRate) { Default = 1, MinValue = 1, MaxValue = 4, Precision = 1, - } + }, + TabbableContentContainer = this, }, - stackLeniency = new LabelledSliderBar + stackLeniency = new FormSliderBar { - Label = "Stack Leniency", - FixedLabelWidth = LABEL_WIDTH, - Description = "In play mode, osu! automatically stacks notes which occur at the same location. Increasing this value means it is more likely to snap notes of further time-distance.", + Caption = "Stack Leniency", + HintText = "In play mode, osu! automatically stacks notes which occur at the same location. Increasing this value means it is more likely to snap notes of further time-distance.", Current = new BindableFloat(Beatmap.BeatmapInfo.StackLeniency) { Default = 0.7f, MinValue = 0, MaxValue = 1, Precision = 0.1f - } + }, + TabbableContentContainer = this, }, }; - foreach (var item in Children.OfType>()) + foreach (var item in Children.OfType>()) item.Current.ValueChanged += _ => updateValues(); - foreach (var item in Children.OfType>()) + foreach (var item in Children.OfType>()) item.Current.ValueChanged += _ => updateValues(); } diff --git a/osu.Game.Rulesets.Taiko/Edit/Setup/TaikoDifficultySection.cs b/osu.Game.Rulesets.Taiko/Edit/Setup/TaikoDifficultySection.cs index 2aaa16ee0b..e191169929 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Setup/TaikoDifficultySection.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Setup/TaikoDifficultySection.cs @@ -16,10 +16,10 @@ namespace osu.Game.Rulesets.Taiko.Edit.Setup { public partial class TaikoDifficultySection : SetupSection { - private LabelledSliderBar healthDrainSlider { get; set; } = null!; - private LabelledSliderBar overallDifficultySlider { get; set; } = null!; - private LabelledSliderBar baseVelocitySlider { get; set; } = null!; - private LabelledSliderBar tickRateSlider { get; set; } = null!; + private FormSliderBar healthDrainSlider { get; set; } = null!; + private FormSliderBar overallDifficultySlider { get; set; } = null!; + private FormSliderBar baseVelocitySlider { get; set; } = null!; + private FormSliderBar tickRateSlider { get; set; } = null!; public override LocalisableString Title => EditorSetupStrings.DifficultyHeader; @@ -28,64 +28,64 @@ namespace osu.Game.Rulesets.Taiko.Edit.Setup { Children = new Drawable[] { - healthDrainSlider = new LabelledSliderBar + healthDrainSlider = new FormSliderBar { - Label = BeatmapsetsStrings.ShowStatsDrain, - FixedLabelWidth = LABEL_WIDTH, - Description = EditorSetupStrings.DrainRateDescription, + Caption = BeatmapsetsStrings.ShowStatsDrain, + HintText = EditorSetupStrings.DrainRateDescription, Current = new BindableFloat(Beatmap.Difficulty.DrainRate) { Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, MinValue = 0, MaxValue = 10, Precision = 0.1f, - } + }, + TabbableContentContainer = this, }, - overallDifficultySlider = new LabelledSliderBar + overallDifficultySlider = new FormSliderBar { - Label = BeatmapsetsStrings.ShowStatsAccuracy, - FixedLabelWidth = LABEL_WIDTH, - Description = EditorSetupStrings.OverallDifficultyDescription, + Caption = BeatmapsetsStrings.ShowStatsAccuracy, + HintText = EditorSetupStrings.OverallDifficultyDescription, Current = new BindableFloat(Beatmap.Difficulty.OverallDifficulty) { Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, MinValue = 0, MaxValue = 10, Precision = 0.1f, - } + }, + TabbableContentContainer = this, }, - baseVelocitySlider = new LabelledSliderBar + baseVelocitySlider = new FormSliderBar { - Label = EditorSetupStrings.BaseVelocity, - FixedLabelWidth = LABEL_WIDTH, - Description = EditorSetupStrings.BaseVelocityDescription, + Caption = EditorSetupStrings.BaseVelocity, + HintText = EditorSetupStrings.BaseVelocityDescription, Current = new BindableDouble(Beatmap.Difficulty.SliderMultiplier) { Default = 1.4, MinValue = 0.4, MaxValue = 3.6, Precision = 0.01f, - } + }, + TabbableContentContainer = this, }, - tickRateSlider = new LabelledSliderBar + tickRateSlider = new FormSliderBar() { - Label = EditorSetupStrings.TickRate, - FixedLabelWidth = LABEL_WIDTH, - Description = EditorSetupStrings.TickRateDescription, + Caption = EditorSetupStrings.TickRate, + HintText = EditorSetupStrings.TickRateDescription, Current = new BindableDouble(Beatmap.Difficulty.SliderTickRate) { Default = 1, MinValue = 1, MaxValue = 4, Precision = 1, - } + }, + TabbableContentContainer = this, }, }; - foreach (var item in Children.OfType>()) + foreach (var item in Children.OfType>()) item.Current.ValueChanged += _ => updateValues(); - foreach (var item in Children.OfType>()) + foreach (var item in Children.OfType>()) item.Current.ValueChanged += _ => updateValues(); } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs b/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs index 9a66e1676d..143547dfc9 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs @@ -99,11 +99,11 @@ namespace osu.Game.Tests.Visual.Editing private partial class TestDesignSection : DesignSection { - public new LabelledSwitchButton EnableCountdown => base.EnableCountdown; + public new FormCheckBox EnableCountdown => base.EnableCountdown; public new FillFlowContainer CountdownSettings => base.CountdownSettings; - public new LabelledEnumDropdown CountdownSpeed => base.CountdownSpeed; - public new LabelledNumberBox CountdownOffset => base.CountdownOffset; + public new FormEnumDropdown CountdownSpeed => base.CountdownSpeed; + public new FormTextBox CountdownOffset => base.CountdownOffset; } } } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs b/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs index 8b6f31d599..653e3e7ff9 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs @@ -212,7 +212,7 @@ namespace osu.Game.Tests.Visual.Editing private void assertRomanisedArtist(string expected, bool editable) { AddAssert($"romanised artist is {expected}", () => metadataSection.RomanisedArtistTextBox.Current.Value, () => Is.EqualTo(expected)); - AddAssert($"romanised artist is {(editable ? "" : "not ")}editable", () => metadataSection.RomanisedArtistTextBox.ReadOnly == !editable); + AddAssert($"romanised artist is {(editable ? "" : "not ")}editable", () => metadataSection.RomanisedArtistTextBox.Current.Disabled == !editable); } private void assertTitle(string expected) @@ -221,16 +221,16 @@ namespace osu.Game.Tests.Visual.Editing private void assertRomanisedTitle(string expected, bool editable) { AddAssert($"romanised title is {expected}", () => metadataSection.RomanisedTitleTextBox.Current.Value, () => Is.EqualTo(expected)); - AddAssert($"romanised title is {(editable ? "" : "not ")}editable", () => metadataSection.RomanisedTitleTextBox.ReadOnly == !editable); + AddAssert($"romanised title is {(editable ? "" : "not ")}editable", () => metadataSection.RomanisedTitleTextBox.Current.Disabled == !editable); } private partial class TestMetadataSection : MetadataSection { - public new LabelledTextBox ArtistTextBox => base.ArtistTextBox; - public new LabelledTextBox RomanisedArtistTextBox => base.RomanisedArtistTextBox; + public new FormTextBox ArtistTextBox => base.ArtistTextBox; + public new FormTextBox RomanisedArtistTextBox => base.RomanisedArtistTextBox; - public new LabelledTextBox TitleTextBox => base.TitleTextBox; - public new LabelledTextBox RomanisedTitleTextBox => base.RomanisedTitleTextBox; + public new FormTextBox TitleTextBox => base.TitleTextBox; + public new FormTextBox RomanisedTitleTextBox => base.RomanisedTitleTextBox; } } } diff --git a/osu.Game/Graphics/UserInterfaceV2/FormColourPalette.cs b/osu.Game/Graphics/UserInterfaceV2/FormColourPalette.cs index a2c1c1622e..672df1d0a5 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormColourPalette.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormColourPalette.cs @@ -133,7 +133,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 int colourIndex = i; var colourButton = new ColourButton { Current = { Value = Colours[colourIndex] } }; colourButton.Current.BindValueChanged(colour => Colours[colourIndex] = colour.NewValue); - colourButton.DeleteRequested = () => Colours.RemoveAt(flow.IndexOf(colourButton)); + colourButton.DeleteRequested = () => Colours.RemoveAt(colourIndex); flow.Add(colourButton); } } diff --git a/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs b/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs index 42bf9c7b9f..f5b6cb3e64 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs @@ -148,12 +148,12 @@ namespace osu.Game.Graphics.UserInterfaceV2 base.LoadComplete(); popoverState.BindValueChanged(_ => updateState()); + current.BindDisabledChanged(_ => updateState()); current.BindValueChanged(_ => { updateState(); onFileSelected(); - }); - current.BindDisabledChanged(_ => updateState(), true); + }, true); game.RegisterImportHandler(this); } @@ -189,7 +189,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 private void updateState() { caption.Colour = Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content2; - filenameText.Colour = Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content1; + filenameText.Colour = Current.Disabled || Current.Value == null ? colourProvider.Foreground1 : colourProvider.Content1; if (!Current.Disabled) { diff --git a/osu.Game/Screens/Edit/Setup/ColoursSection.cs b/osu.Game/Screens/Edit/Setup/ColoursSection.cs index a5d79b5b52..01ca114e4f 100644 --- a/osu.Game/Screens/Edit/Setup/ColoursSection.cs +++ b/osu.Game/Screens/Edit/Setup/ColoursSection.cs @@ -13,18 +13,16 @@ namespace osu.Game.Screens.Edit.Setup { public override LocalisableString Title => EditorSetupStrings.ColoursHeader; - private LabelledColourPalette comboColours = null!; + private FormColourPalette comboColours = null!; [BackgroundDependencyLoader] private void load() { Children = new Drawable[] { - comboColours = new LabelledColourPalette + comboColours = new FormColourPalette { - Label = EditorSetupStrings.HitCircleSliderCombos, - FixedLabelWidth = LABEL_WIDTH, - ColourNamePrefix = EditorSetupStrings.ComboColourPrefix + Caption = EditorSetupStrings.HitCircleSliderCombos, } }; diff --git a/osu.Game/Screens/Edit/Setup/DesignSection.cs b/osu.Game/Screens/Edit/Setup/DesignSection.cs index b05a073146..e3c01fc9bf 100644 --- a/osu.Game/Screens/Edit/Setup/DesignSection.cs +++ b/osu.Game/Screens/Edit/Setup/DesignSection.cs @@ -17,75 +17,75 @@ namespace osu.Game.Screens.Edit.Setup { internal partial class DesignSection : SetupSection { - protected LabelledSwitchButton EnableCountdown = null!; + protected FormCheckBox EnableCountdown = null!; protected FillFlowContainer CountdownSettings = null!; - protected LabelledEnumDropdown CountdownSpeed = null!; - protected LabelledNumberBox CountdownOffset = null!; + protected FormEnumDropdown CountdownSpeed = null!; + protected FormNumberBox CountdownOffset = null!; - private LabelledSwitchButton widescreenSupport = null!; - private LabelledSwitchButton epilepsyWarning = null!; - private LabelledSwitchButton letterboxDuringBreaks = null!; - private LabelledSwitchButton samplesMatchPlaybackRate = null!; + private FormCheckBox widescreenSupport = null!; + private FormCheckBox epilepsyWarning = null!; + private FormCheckBox letterboxDuringBreaks = null!; + private FormCheckBox samplesMatchPlaybackRate = null!; public override LocalisableString Title => EditorSetupStrings.DesignHeader; [BackgroundDependencyLoader] private void load() { - Children = new[] + Children = new Drawable[] { - EnableCountdown = new LabelledSwitchButton + EnableCountdown = new FormCheckBox { - Label = EditorSetupStrings.EnableCountdown, + Caption = EditorSetupStrings.EnableCountdown, + HintText = EditorSetupStrings.CountdownDescription, Current = { Value = Beatmap.BeatmapInfo.Countdown != CountdownType.None }, - Description = EditorSetupStrings.CountdownDescription }, CountdownSettings = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Spacing = new Vector2(10), + Spacing = new Vector2(5), Direction = FillDirection.Vertical, Children = new Drawable[] { - CountdownSpeed = new LabelledEnumDropdown + CountdownSpeed = new FormEnumDropdown { - Label = EditorSetupStrings.CountdownSpeed, + Caption = EditorSetupStrings.CountdownSpeed, Current = { Value = Beatmap.BeatmapInfo.Countdown != CountdownType.None ? Beatmap.BeatmapInfo.Countdown : CountdownType.Normal }, Items = Enum.GetValues().Where(type => type != CountdownType.None) }, - CountdownOffset = new LabelledNumberBox + CountdownOffset = new FormNumberBox { - Label = EditorSetupStrings.CountdownOffset, + Caption = EditorSetupStrings.CountdownOffset, + HintText = EditorSetupStrings.CountdownOffsetDescription, Current = { Value = Beatmap.BeatmapInfo.CountdownOffset.ToString() }, - Description = EditorSetupStrings.CountdownOffsetDescription, + TabbableContentContainer = this, } } }, - Empty(), - widescreenSupport = new LabelledSwitchButton + widescreenSupport = new FormCheckBox { - Label = EditorSetupStrings.WidescreenSupport, - Description = EditorSetupStrings.WidescreenSupportDescription, + Caption = EditorSetupStrings.WidescreenSupport, + HintText = EditorSetupStrings.WidescreenSupportDescription, Current = { Value = Beatmap.BeatmapInfo.WidescreenStoryboard } }, - epilepsyWarning = new LabelledSwitchButton + epilepsyWarning = new FormCheckBox { - Label = EditorSetupStrings.EpilepsyWarning, - Description = EditorSetupStrings.EpilepsyWarningDescription, + Caption = EditorSetupStrings.EpilepsyWarning, + HintText = EditorSetupStrings.EpilepsyWarningDescription, Current = { Value = Beatmap.BeatmapInfo.EpilepsyWarning } }, - letterboxDuringBreaks = new LabelledSwitchButton + letterboxDuringBreaks = new FormCheckBox { - Label = EditorSetupStrings.LetterboxDuringBreaks, - Description = EditorSetupStrings.LetterboxDuringBreaksDescription, + Caption = EditorSetupStrings.LetterboxDuringBreaks, + HintText = EditorSetupStrings.LetterboxDuringBreaksDescription, Current = { Value = Beatmap.BeatmapInfo.LetterboxInBreaks } }, - samplesMatchPlaybackRate = new LabelledSwitchButton + samplesMatchPlaybackRate = new FormCheckBox { - Label = EditorSetupStrings.SamplesMatchPlaybackRate, - Description = EditorSetupStrings.SamplesMatchPlaybackRateDescription, + Caption = EditorSetupStrings.SamplesMatchPlaybackRate, + HintText = EditorSetupStrings.SamplesMatchPlaybackRateDescription, Current = { Value = Beatmap.BeatmapInfo.SamplesMatchPlaybackRate } } }; diff --git a/osu.Game/Screens/Edit/Setup/DifficultySection.cs b/osu.Game/Screens/Edit/Setup/DifficultySection.cs index b9ba2d9cb7..a27a7258c7 100644 --- a/osu.Game/Screens/Edit/Setup/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Setup/DifficultySection.cs @@ -15,12 +15,12 @@ namespace osu.Game.Screens.Edit.Setup { public partial class DifficultySection : SetupSection { - private LabelledSliderBar circleSizeSlider { get; set; } = null!; - private LabelledSliderBar healthDrainSlider { get; set; } = null!; - private LabelledSliderBar approachRateSlider { get; set; } = null!; - private LabelledSliderBar overallDifficultySlider { get; set; } = null!; - private LabelledSliderBar baseVelocitySlider { get; set; } = null!; - private LabelledSliderBar tickRateSlider { get; set; } = null!; + private FormSliderBar circleSizeSlider { get; set; } = null!; + private FormSliderBar healthDrainSlider { get; set; } = null!; + private FormSliderBar approachRateSlider { get; set; } = null!; + private FormSliderBar overallDifficultySlider { get; set; } = null!; + private FormSliderBar baseVelocitySlider { get; set; } = null!; + private FormSliderBar tickRateSlider { get; set; } = null!; public override LocalisableString Title => EditorSetupStrings.DifficultyHeader; @@ -29,90 +29,90 @@ namespace osu.Game.Screens.Edit.Setup { Children = new Drawable[] { - circleSizeSlider = new LabelledSliderBar + circleSizeSlider = new FormSliderBar { - Label = BeatmapsetsStrings.ShowStatsCs, - FixedLabelWidth = LABEL_WIDTH, - Description = EditorSetupStrings.CircleSizeDescription, + Caption = BeatmapsetsStrings.ShowStatsCs, + HintText = EditorSetupStrings.CircleSizeDescription, Current = new BindableFloat(Beatmap.Difficulty.CircleSize) { Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, MinValue = 0, MaxValue = 10, Precision = 0.1f, - } + }, + TabbableContentContainer = this, }, - healthDrainSlider = new LabelledSliderBar + healthDrainSlider = new FormSliderBar { - Label = BeatmapsetsStrings.ShowStatsDrain, - FixedLabelWidth = LABEL_WIDTH, - Description = EditorSetupStrings.DrainRateDescription, + Caption = BeatmapsetsStrings.ShowStatsDrain, + HintText = EditorSetupStrings.DrainRateDescription, Current = new BindableFloat(Beatmap.Difficulty.DrainRate) { Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, MinValue = 0, MaxValue = 10, Precision = 0.1f, - } + }, + TabbableContentContainer = this, }, - approachRateSlider = new LabelledSliderBar + approachRateSlider = new FormSliderBar { - Label = BeatmapsetsStrings.ShowStatsAr, - FixedLabelWidth = LABEL_WIDTH, - Description = EditorSetupStrings.ApproachRateDescription, + Caption = BeatmapsetsStrings.ShowStatsAr, + HintText = EditorSetupStrings.ApproachRateDescription, Current = new BindableFloat(Beatmap.Difficulty.ApproachRate) { Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, MinValue = 0, MaxValue = 10, Precision = 0.1f, - } + }, + TabbableContentContainer = this, }, - overallDifficultySlider = new LabelledSliderBar + overallDifficultySlider = new FormSliderBar { - Label = BeatmapsetsStrings.ShowStatsAccuracy, - FixedLabelWidth = LABEL_WIDTH, - Description = EditorSetupStrings.OverallDifficultyDescription, + Caption = BeatmapsetsStrings.ShowStatsAccuracy, + HintText = EditorSetupStrings.OverallDifficultyDescription, Current = new BindableFloat(Beatmap.Difficulty.OverallDifficulty) { Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, MinValue = 0, MaxValue = 10, Precision = 0.1f, - } + }, + TabbableContentContainer = this, }, - baseVelocitySlider = new LabelledSliderBar + baseVelocitySlider = new FormSliderBar { - Label = EditorSetupStrings.BaseVelocity, - FixedLabelWidth = LABEL_WIDTH, - Description = EditorSetupStrings.BaseVelocityDescription, + Caption = EditorSetupStrings.BaseVelocity, + HintText = EditorSetupStrings.BaseVelocityDescription, Current = new BindableDouble(Beatmap.Difficulty.SliderMultiplier) { Default = 1.4, MinValue = 0.4, MaxValue = 3.6, Precision = 0.01f, - } + }, + TabbableContentContainer = this, }, - tickRateSlider = new LabelledSliderBar + tickRateSlider = new FormSliderBar { - Label = EditorSetupStrings.TickRate, - FixedLabelWidth = LABEL_WIDTH, - Description = EditorSetupStrings.TickRateDescription, + Caption = EditorSetupStrings.TickRate, + HintText = EditorSetupStrings.TickRateDescription, Current = new BindableDouble(Beatmap.Difficulty.SliderTickRate) { Default = 1, MinValue = 1, MaxValue = 4, Precision = 1, - } + }, + TabbableContentContainer = this, }, }; - foreach (var item in Children.OfType>()) + foreach (var item in Children.OfType>()) item.Current.ValueChanged += _ => updateValues(); - foreach (var item in Children.OfType>()) + foreach (var item in Children.OfType>()) item.Current.ValueChanged += _ => updateValues(); } diff --git a/osu.Game/Screens/Edit/Setup/LabelledRomanisedTextBox.cs b/osu.Game/Screens/Edit/Setup/LabelledRomanisedTextBox.cs deleted file mode 100644 index 85c697bf14..0000000000 --- a/osu.Game/Screens/Edit/Setup/LabelledRomanisedTextBox.cs +++ /dev/null @@ -1,22 +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.Beatmaps; -using osu.Game.Graphics.UserInterface; -using osu.Game.Graphics.UserInterfaceV2; - -namespace osu.Game.Screens.Edit.Setup -{ - internal partial class LabelledRomanisedTextBox : LabelledTextBox - { - protected override OsuTextBox CreateTextBox() => new RomanisedTextBox(); - - private partial class RomanisedTextBox : OsuTextBox - { - protected override bool AllowIme => false; - - protected override bool CanAddCharacter(char character) - => MetadataUtils.IsRomanised(character); - } - } -} diff --git a/osu.Game/Screens/Edit/Setup/MetadataSection.cs b/osu.Game/Screens/Edit/Setup/MetadataSection.cs index 19071dc806..20c0a74d84 100644 --- a/osu.Game/Screens/Edit/Setup/MetadataSection.cs +++ b/osu.Game/Screens/Edit/Setup/MetadataSection.cs @@ -14,16 +14,16 @@ namespace osu.Game.Screens.Edit.Setup { public partial class MetadataSection : SetupSection { - protected LabelledTextBox ArtistTextBox = null!; - protected LabelledTextBox RomanisedArtistTextBox = null!; + protected FormTextBox ArtistTextBox = null!; + protected FormTextBox RomanisedArtistTextBox = null!; - protected LabelledTextBox TitleTextBox = null!; - protected LabelledTextBox RomanisedTitleTextBox = null!; + protected FormTextBox TitleTextBox = null!; + protected FormTextBox RomanisedTitleTextBox = null!; - private LabelledTextBox creatorTextBox = null!; - private LabelledTextBox difficultyTextBox = null!; - private LabelledTextBox sourceTextBox = null!; - private LabelledTextBox tagsTextBox = null!; + private FormTextBox creatorTextBox = null!; + private FormTextBox difficultyTextBox = null!; + private FormTextBox sourceTextBox = null!; + private FormTextBox tagsTextBox = null!; public override LocalisableString Title => EditorSetupStrings.MetadataHeader; @@ -34,33 +34,26 @@ namespace osu.Game.Screens.Edit.Setup Children = new[] { - ArtistTextBox = createTextBox(EditorSetupStrings.Artist, + ArtistTextBox = createTextBox(EditorSetupStrings.Artist, !string.IsNullOrEmpty(metadata.ArtistUnicode) ? metadata.ArtistUnicode : metadata.Artist), - RomanisedArtistTextBox = createTextBox(EditorSetupStrings.RomanisedArtist, + RomanisedArtistTextBox = createTextBox(EditorSetupStrings.RomanisedArtist, !string.IsNullOrEmpty(metadata.Artist) ? metadata.Artist : MetadataUtils.StripNonRomanisedCharacters(metadata.ArtistUnicode)), - - Empty(), - - TitleTextBox = createTextBox(EditorSetupStrings.Title, + TitleTextBox = createTextBox(EditorSetupStrings.Title, !string.IsNullOrEmpty(metadata.TitleUnicode) ? metadata.TitleUnicode : metadata.Title), - RomanisedTitleTextBox = createTextBox(EditorSetupStrings.RomanisedTitle, + RomanisedTitleTextBox = createTextBox(EditorSetupStrings.RomanisedTitle, !string.IsNullOrEmpty(metadata.Title) ? metadata.Title : MetadataUtils.StripNonRomanisedCharacters(metadata.ArtistUnicode)), - - Empty(), - - creatorTextBox = createTextBox(EditorSetupStrings.Creator, metadata.Author.Username), - difficultyTextBox = createTextBox(EditorSetupStrings.DifficultyName, Beatmap.BeatmapInfo.DifficultyName), - sourceTextBox = createTextBox(BeatmapsetsStrings.ShowInfoSource, metadata.Source), - tagsTextBox = createTextBox(BeatmapsetsStrings.ShowInfoTags, metadata.Tags) + creatorTextBox = createTextBox(EditorSetupStrings.Creator, metadata.Author.Username), + difficultyTextBox = createTextBox(EditorSetupStrings.DifficultyName, Beatmap.BeatmapInfo.DifficultyName), + sourceTextBox = createTextBox(BeatmapsetsStrings.ShowInfoSource, metadata.Source), + tagsTextBox = createTextBox(BeatmapsetsStrings.ShowInfoTags, metadata.Tags) }; } private TTextBox createTextBox(LocalisableString label, string initialValue) - where TTextBox : LabelledTextBox, new() + where TTextBox : FormTextBox, new() => new TTextBox { - Label = label, - FixedLabelWidth = LABEL_WIDTH, + Caption = label, Current = { Value = initialValue }, TabbableContentContainer = this }; @@ -75,13 +68,13 @@ namespace osu.Game.Screens.Edit.Setup ArtistTextBox.Current.BindValueChanged(artist => transferIfRomanised(artist.NewValue, RomanisedArtistTextBox)); TitleTextBox.Current.BindValueChanged(title => transferIfRomanised(title.NewValue, RomanisedTitleTextBox)); - foreach (var item in Children.OfType()) + foreach (var item in Children.OfType()) item.OnCommit += onCommit; updateReadOnlyState(); } - private void transferIfRomanised(string value, LabelledTextBox target) + private void transferIfRomanised(string value, FormTextBox target) { if (MetadataUtils.IsRomanised(value)) target.Current.Value = value; @@ -119,5 +112,18 @@ namespace osu.Game.Screens.Edit.Setup Beatmap.SaveState(); } + + private partial class FormRomanisedTextBox : FormTextBox + { + internal override InnerTextBox CreateTextBox() => new RomanisedTextBox(); + + private partial class RomanisedTextBox : InnerTextBox + { + protected override bool AllowIme => false; + + protected override bool CanAddCharacter(char character) + => MetadataUtils.IsRomanised(character); + } + } } } diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index f6d20319cb..3ce9f01b2b 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; using osu.Game.Localisation; @@ -14,8 +15,8 @@ namespace osu.Game.Screens.Edit.Setup { internal partial class ResourcesSection : SetupSection { - private LabelledFileChooser audioTrackChooser = null!; - private LabelledFileChooser backgroundChooser = null!; + private FormFileSelector audioTrackChooser = null!; + private FormFileSelector backgroundChooser = null!; public override LocalisableString Title => EditorSetupStrings.ResourcesHeader; @@ -35,24 +36,22 @@ namespace osu.Game.Screens.Edit.Setup private Editor? editor { get; set; } [Resolved] - private SetupScreenHeader header { get; set; } = null!; + private SetupScreenHeaderBackground headerBackground { get; set; } = null!; [BackgroundDependencyLoader] private void load() { Children = new Drawable[] { - backgroundChooser = new LabelledFileChooser(".jpg", ".jpeg", ".png") + backgroundChooser = new FormFileSelector(".jpg", ".jpeg", ".png") { - Label = GameplaySettingsStrings.BackgroundHeader, - FixedLabelWidth = LABEL_WIDTH, - TabbableContentContainer = this + Caption = GameplaySettingsStrings.BackgroundHeader, + PlaceholderText = EditorSetupStrings.ClickToSelectBackground, }, - audioTrackChooser = new LabelledFileChooser(".mp3", ".ogg") + audioTrackChooser = new FormFileSelector(".mp3", ".ogg") { - Label = EditorSetupStrings.AudioTrack, - FixedLabelWidth = LABEL_WIDTH, - TabbableContentContainer = this + Caption = EditorSetupStrings.AudioTrack, + PlaceholderText = EditorSetupStrings.ClickToSelectTrack, }, }; @@ -64,8 +63,6 @@ namespace osu.Game.Screens.Edit.Setup backgroundChooser.Current.BindValueChanged(backgroundChanged); audioTrackChooser.Current.BindValueChanged(audioTrackChanged); - - updatePlaceholderText(); } public bool ChangeBackgroundImage(FileInfo source) @@ -92,7 +89,7 @@ namespace osu.Game.Screens.Edit.Setup editorBeatmap.SaveState(); working.Value.Metadata.BackgroundFile = destination.Name; - header.Background.UpdateBackground(); + headerBackground.UpdateBackground(); editor?.ApplyToBackground(bg => bg.RefreshBackground()); @@ -132,22 +129,12 @@ namespace osu.Game.Screens.Edit.Setup { if (file.NewValue == null || !ChangeBackgroundImage(file.NewValue)) backgroundChooser.Current.Value = file.OldValue; - - updatePlaceholderText(); } private void audioTrackChanged(ValueChangedEvent file) { if (file.NewValue == null || !ChangeAudioTrack(file.NewValue)) audioTrackChooser.Current.Value = file.OldValue; - - updatePlaceholderText(); - } - - private void updatePlaceholderText() - { - audioTrackChooser.Text = audioTrackChooser.Current.Value?.Name ?? EditorSetupStrings.ClickToSelectTrack; - backgroundChooser.Text = backgroundChooser.Current.Value?.Name ?? EditorSetupStrings.ClickToSelectBackground; } } } diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 17bbc7daa2..4b9a7a858f 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -1,55 +1,97 @@ // 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.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; using osu.Game.Graphics.Containers; using osu.Game.Overlays; +using osuTK; namespace osu.Game.Screens.Edit.Setup { public partial class SetupScreen : EditorScreen { - [Cached] - private SectionsContainer sections { get; } = new SetupScreenSectionsContainer(); - - [Cached] - private SetupScreenHeader header = new SetupScreenHeader(); - public SetupScreen() : base(EditorScreenMode.SongSetup) { } + [Cached] + private SetupScreenHeaderBackground background = new SetupScreenHeaderBackground { RelativeSizeAxes = Axes.Both, }; + [BackgroundDependencyLoader] private void load(EditorBeatmap beatmap, OverlayColourProvider colourProvider) { var ruleset = beatmap.BeatmapInfo.Ruleset.CreateInstance(); - List sectionsEnumerable = - [ - new ResourcesSection(), - new MetadataSection() - ]; - - sectionsEnumerable.AddRange(ruleset.CreateEditorSetupSections()); - sectionsEnumerable.Add(new DesignSection()); - - Add(new Box + Children = new Drawable[] { - Colour = colourProvider.Background3, - RelativeSizeAxes = Axes.Both, - }); - - Add(sections.With(s => - { - s.RelativeSizeAxes = Axes.Both; - s.ChildrenEnumerable = sectionsEnumerable; - s.FixedHeader = header; - })); + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background3, + }, + new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = + [ + new Dimension(GridSizeMode.Absolute, 110), + new Dimension() + ], + Content = new[] + { + new Drawable[] + { + background, + }, + new Drawable[] + { + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(15), + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + Spacing = new Vector2(28), + Children = new Drawable[] + { + new FillFlowContainer + { + Width = 450, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Spacing = new Vector2(25), + Children = new Drawable[] + { + new ResourcesSection(), + new MetadataSection(), + } + }, + new FillFlowContainer + { + Width = 450, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Spacing = new Vector2(25), + ChildrenEnumerable = ruleset.CreateEditorSetupSections().Append(new DesignSection()), + }, + } + } + } + } + } + } + }; } public override void OnExiting(ScreenExitEvent e) @@ -62,19 +104,5 @@ namespace osu.Game.Screens.Edit.Setup // (and potentially block the exit procedure for save). GetContainingFocusManager()?.TriggerFocusContention(this); } - - private partial class SetupScreenSectionsContainer : SectionsContainer - { - protected override UserTrackingScrollContainer CreateScrollContainer() - { - var scrollContainer = base.CreateScrollContainer(); - - // Workaround for masking issues (see https://github.com/ppy/osu-framework/issues/1675#issuecomment-910023157) - // Note that this actually causes the full scroll range to be reduced by 2px at the bottom, but it's not really noticeable. - scrollContainer.Margin = new MarginPadding { Top = 2 }; - - return scrollContainer; - } - } } } diff --git a/osu.Game/Screens/Edit/Setup/SetupSection.cs b/osu.Game/Screens/Edit/Setup/SetupSection.cs index 5f676798f1..d3b231de25 100644 --- a/osu.Game/Screens/Edit/Setup/SetupSection.cs +++ b/osu.Game/Screens/Edit/Setup/SetupSection.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osuTK; @@ -37,30 +37,23 @@ namespace osu.Game.Screens.Edit.Setup RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - Padding = new MarginPadding - { - Vertical = 10, - Horizontal = 100 - }; - InternalChild = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Spacing = new Vector2(20), + Spacing = new Vector2(10), Direction = FillDirection.Vertical, Children = new Drawable[] { - new OsuSpriteText + new SectionHeader(Title) { - Font = OsuFont.GetFont(weight: FontWeight.Bold), - Text = Title + Margin = new MarginPadding { Left = 9, }, }, flow = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Spacing = new Vector2(10), + Spacing = new Vector2(5), Direction = FillDirection.Vertical, } } From 09441a53c283662e0646962a2f8c311133179e32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Oct 2024 11:10:55 +0200 Subject: [PATCH 393/924] Fix "form" file selector displaying commit animation on initial show --- osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs b/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs index f5b6cb3e64..3b822a1b2f 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs @@ -154,6 +154,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 updateState(); onFileSelected(); }, true); + FinishTransforms(true); game.RegisterImportHandler(this); } From cde348bfb811accf8d8508afd35ae2e7bb310068 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Oct 2024 11:10:58 +0200 Subject: [PATCH 394/924] Fix "form" textbox not dropping border if disabled when hovered --- osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs index 9bbb5cba99..973419310c 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs @@ -202,6 +202,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 } else { + BorderThickness = 0; background.Colour = colourProvider.Background4; } } From a567c6369d7e74a32ed47ed326643651930f3953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Oct 2024 11:11:02 +0200 Subject: [PATCH 395/924] Autoselect contents of "form" number box --- osu.Game/Graphics/UserInterfaceV2/FormNumberBox.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Graphics/UserInterfaceV2/FormNumberBox.cs b/osu.Game/Graphics/UserInterfaceV2/FormNumberBox.cs index 66f1a45210..8ce6c85fa9 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormNumberBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormNumberBox.cs @@ -10,6 +10,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 internal override InnerTextBox CreateTextBox() => new InnerNumberBox { AllowDecimals = AllowDecimals, + SelectAllOnFocus = true, }; internal partial class InnerNumberBox : InnerTextBox From 19d8be4890fdbd15ec00030397db2be2b45a4e21 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 3 Oct 2024 11:49:45 +0200 Subject: [PATCH 396/924] Add more comments --- osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 77fa64b0b1..8e94112866 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -244,6 +244,7 @@ namespace osu.Game.Rulesets.Osu.Edit return scale; + // Clamps the scale vector s such that the point p scaled by s is within the rectangle defined by lowerBounds and upperBounds Vector2 clampToBound(Vector2 s, Vector2 p, Vector2 lowerBounds, Vector2 upperBounds) { p -= actualOrigin; @@ -286,18 +287,25 @@ namespace osu.Game.Rulesets.Osu.Edit return s; } + // Computes the bounds for the magnitude of the scaled point p with respect to the bounds lowerBounds and upperBounds (float, float) computeBounds(Vector2 lowerBounds, Vector2 upperBounds, Vector2 p) { var sLowerBounds = Vector2.Divide(lowerBounds, p); var sUpperBounds = Vector2.Divide(upperBounds, p); + + // If the point is negative, then the bounds are flipped if (p.X < 0) (sLowerBounds.X, sUpperBounds.X) = (sUpperBounds.X, sLowerBounds.X); if (p.Y < 0) (sLowerBounds.Y, sUpperBounds.Y) = (sUpperBounds.Y, sLowerBounds.Y); + + // If the point is at zero, then any scale will have no effect on the point so the bounds are infinite + // The float division would already give us infinity for the bounds, but the sign is not consistent so we have to manually set it if (Precision.AlmostEquals(p.X, 0)) (sLowerBounds.X, sUpperBounds.X) = (float.NegativeInfinity, float.PositiveInfinity); if (Precision.AlmostEquals(p.Y, 0)) (sLowerBounds.Y, sUpperBounds.Y) = (float.NegativeInfinity, float.PositiveInfinity); + return (MathF.Max(sLowerBounds.X, sLowerBounds.Y), MathF.Min(sUpperBounds.X, sUpperBounds.Y)); } } From 19356d0487dd1f8f7796e5c0787ee578841c5917 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Oct 2024 19:33:48 +0900 Subject: [PATCH 397/924] Match corner radius of "new" button --- osu.Game/Graphics/UserInterfaceV2/FormColourPalette.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/FormColourPalette.cs b/osu.Game/Graphics/UserInterfaceV2/FormColourPalette.cs index a2c1c1622e..e3ae38ac65 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormColourPalette.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormColourPalette.cs @@ -152,7 +152,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 Size = new Vector2(70, 25); Masking = true; - CornerRadius = 3; + CornerRadius = 12.5f; Action = this.ShowPopover; Children = new Drawable[] From ddfa877b12d2093a1d69a7c1198557cbd16eef4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Oct 2024 13:08:04 +0200 Subject: [PATCH 398/924] Fix code quality --- osu.Game.Rulesets.Taiko/Edit/Setup/TaikoDifficultySection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Edit/Setup/TaikoDifficultySection.cs b/osu.Game.Rulesets.Taiko/Edit/Setup/TaikoDifficultySection.cs index e191169929..8fce59e791 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Setup/TaikoDifficultySection.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Setup/TaikoDifficultySection.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Taiko.Edit.Setup }, TabbableContentContainer = this, }, - tickRateSlider = new FormSliderBar() + tickRateSlider = new FormSliderBar { Caption = EditorSetupStrings.TickRate, HintText = EditorSetupStrings.TickRateDescription, From 8a650deab66e30d115ac4542b04ff9bf635b3d04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Oct 2024 13:09:35 +0200 Subject: [PATCH 399/924] Fix tests --- .../Editor/TestSceneManiaEditorSaving.cs | 4 ++-- osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs | 5 +++++ osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs | 8 ++++++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaEditorSaving.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaEditorSaving.cs index 9765648f44..d9ba721646 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaEditorSaving.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaEditorSaving.cs @@ -20,10 +20,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Test] public void TestKeyCountChange() { - LabelledSliderBar keyCount = null!; + FormSliderBar keyCount = null!; AddStep("go to setup screen", () => InputManager.Key(Key.F4)); - AddUntilStep("retrieve key count slider", () => keyCount = Editor.ChildrenOfType().Single().ChildrenOfType>().First(), () => Is.Not.Null); + AddUntilStep("retrieve key count slider", () => keyCount = Editor.ChildrenOfType().Single().ChildrenOfType>().First(), () => Is.Not.Null); AddAssert("key count is 5", () => keyCount.Current.Value, () => Is.EqualTo(5)); AddStep("change key count to 8", () => { diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs b/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs index 143547dfc9..a4f250675e 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs @@ -7,12 +7,14 @@ using System; using System.Globalization; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Setup; @@ -25,6 +27,9 @@ namespace osu.Game.Tests.Visual.Editing private TestDesignSection designSection; private EditorBeatmap editorBeatmap { get; set; } + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + [SetUpSteps] public void SetUp() { diff --git a/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs b/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs index 653e3e7ff9..167230f5a0 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs @@ -11,6 +11,7 @@ using osu.Framework.Input; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Setup; @@ -20,6 +21,9 @@ namespace osu.Game.Tests.Visual.Editing { public partial class TestSceneMetadataSection : OsuManualInputManagerTestScene { + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + [Cached] private EditorBeatmap editorBeatmap = new EditorBeatmap(new Beatmap { @@ -212,7 +216,7 @@ namespace osu.Game.Tests.Visual.Editing private void assertRomanisedArtist(string expected, bool editable) { AddAssert($"romanised artist is {expected}", () => metadataSection.RomanisedArtistTextBox.Current.Value, () => Is.EqualTo(expected)); - AddAssert($"romanised artist is {(editable ? "" : "not ")}editable", () => metadataSection.RomanisedArtistTextBox.Current.Disabled == !editable); + AddAssert($"romanised artist is {(editable ? "" : "not ")}editable", () => metadataSection.RomanisedArtistTextBox.ReadOnly == !editable); } private void assertTitle(string expected) @@ -221,7 +225,7 @@ namespace osu.Game.Tests.Visual.Editing private void assertRomanisedTitle(string expected, bool editable) { AddAssert($"romanised title is {expected}", () => metadataSection.RomanisedTitleTextBox.Current.Value, () => Is.EqualTo(expected)); - AddAssert($"romanised title is {(editable ? "" : "not ")}editable", () => metadataSection.RomanisedTitleTextBox.Current.Disabled == !editable); + AddAssert($"romanised title is {(editable ? "" : "not ")}editable", () => metadataSection.RomanisedTitleTextBox.ReadOnly == !editable); } private partial class TestMetadataSection : MetadataSection From 99eb26b7d55a4a88c5ea567cb70b6d0f4b0b3ea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Oct 2024 13:53:21 +0200 Subject: [PATCH 400/924] Redo the layout of sections based on discord feedback See https://discord.com/channels/188630481301012481/188630652340404224/1291358770064130140 and everything after. --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 24 +++++++++++++++++-- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 5 +++- osu.Game.Rulesets.Osu/OsuRuleset.cs | 24 +++++++++++++++++-- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 5 +++- osu.Game/Rulesets/Ruleset.cs | 24 +++++++++++++++++-- osu.Game/Screens/Edit/Setup/DesignSection.cs | 2 +- .../Screens/Edit/Setup/ResourcesSection.cs | 2 +- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 18 ++++---------- osu.Game/Screens/Edit/Setup/SetupSection.cs | 1 - 9 files changed, 80 insertions(+), 25 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 7eaf4f2b18..9f48da599e 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Localisation; @@ -31,6 +32,7 @@ using osu.Game.Scoring; using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Ranking.Statistics; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Catch { @@ -223,10 +225,28 @@ namespace osu.Game.Rulesets.Catch public override HitObjectComposer CreateHitObjectComposer() => new CatchHitObjectComposer(this); - public override IEnumerable CreateEditorSetupSections() => + public override IEnumerable CreateEditorSetupSections() => [ + new MetadataSection(), new DifficultySection(), - new ColoursSection(), + new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(25), + Children = new Drawable[] + { + new ResourcesSection + { + RelativeSizeAxes = Axes.X, + }, + new ColoursSection + { + RelativeSizeAxes = Axes.X, + } + } + }, + new DesignSection(), ]; public override IBeatmapVerifier CreateBeatmapVerifier() => new CatchBeatmapVerifier(); diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index c01fa508fe..cdc7b0a951 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -419,9 +419,12 @@ namespace osu.Game.Rulesets.Mania return new ManiaFilterCriteria(); } - public override IEnumerable CreateEditorSetupSections() => + public override IEnumerable CreateEditorSetupSections() => [ + new MetadataSection(), new ManiaDifficultySection(), + new ResourcesSection(), + new DesignSection(), ]; public int GetKeyCount(IBeatmapInfo beatmapInfo, IReadOnlyList? mods = null) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index be48ef9acc..9f2a5b2066 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Localisation; @@ -39,6 +40,7 @@ using osu.Game.Scoring; using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Ranking.Statistics; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Osu { @@ -336,10 +338,28 @@ namespace osu.Game.Rulesets.Osu }; } - public override IEnumerable CreateEditorSetupSections() => + public override IEnumerable CreateEditorSetupSections() => [ + new MetadataSection(), new OsuDifficultySection(), - new ColoursSection(), + new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(25), + Children = new Drawable[] + { + new ResourcesSection + { + RelativeSizeAxes = Axes.X, + }, + new ColoursSection + { + RelativeSizeAxes = Axes.X, + } + } + }, + new DesignSection(), ]; /// diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 2447a4a247..70e429a344 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -190,9 +190,12 @@ namespace osu.Game.Rulesets.Taiko public override HitObjectComposer CreateHitObjectComposer() => new TaikoHitObjectComposer(this); - public override IEnumerable CreateEditorSetupSections() => + public override IEnumerable CreateEditorSetupSections() => [ + new MetadataSection(), new TaikoDifficultySection(), + new ResourcesSection(), + new DesignSection(), ]; public override IBeatmapVerifier CreateBeatmapVerifier() => new TaikoBeatmapVerifier(); diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 5af1fd386c..d4989642c2 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -8,6 +8,7 @@ using System.Linq; using osu.Framework.Extensions; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.IO.Stores; @@ -30,6 +31,7 @@ using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Ranking.Statistics; using osu.Game.Skinning; using osu.Game.Users; +using osuTK; namespace osu.Game.Rulesets { @@ -396,10 +398,28 @@ namespace osu.Game.Rulesets /// /// Can be overridden to add ruleset-specific sections to the editor beatmap setup screen. /// - public virtual IEnumerable CreateEditorSetupSections() => + public virtual IEnumerable CreateEditorSetupSections() => [ + new MetadataSection(), new DifficultySection(), - new ColoursSection(), + new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(25), + Children = new Drawable[] + { + new ResourcesSection + { + RelativeSizeAxes = Axes.X, + }, + new ColoursSection + { + RelativeSizeAxes = Axes.X, + } + } + }, + new DesignSection(), ]; /// diff --git a/osu.Game/Screens/Edit/Setup/DesignSection.cs b/osu.Game/Screens/Edit/Setup/DesignSection.cs index e3c01fc9bf..7def5394e6 100644 --- a/osu.Game/Screens/Edit/Setup/DesignSection.cs +++ b/osu.Game/Screens/Edit/Setup/DesignSection.cs @@ -15,7 +15,7 @@ using osu.Game.Localisation; namespace osu.Game.Screens.Edit.Setup { - internal partial class DesignSection : SetupSection + public partial class DesignSection : SetupSection { protected FormCheckBox EnableCountdown = null!; diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 3ce9f01b2b..6fec7078a8 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -13,7 +13,7 @@ using osu.Game.Localisation; namespace osu.Game.Screens.Edit.Setup { - internal partial class ResourcesSection : SetupSection + public partial class ResourcesSection : SetupSection { private FormFileSelector audioTrackChooser = null!; private FormFileSelector backgroundChooser = null!; diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 4b9a7a858f..1af54d55d6 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -65,25 +65,15 @@ namespace osu.Game.Screens.Edit.Setup { new FillFlowContainer { - Width = 450, + Width = 925, AutoSizeAxes = Axes.Y, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Spacing = new Vector2(25), - Children = new Drawable[] + ChildrenEnumerable = ruleset.CreateEditorSetupSections().Select(section => section.With(s => { - new ResourcesSection(), - new MetadataSection(), - } - }, - new FillFlowContainer - { - Width = 450, - AutoSizeAxes = Axes.Y, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Spacing = new Vector2(25), - ChildrenEnumerable = ruleset.CreateEditorSetupSections().Append(new DesignSection()), + s.Width = 450; + })), }, } } diff --git a/osu.Game/Screens/Edit/Setup/SetupSection.cs b/osu.Game/Screens/Edit/Setup/SetupSection.cs index d3b231de25..bd1eb51b48 100644 --- a/osu.Game/Screens/Edit/Setup/SetupSection.cs +++ b/osu.Game/Screens/Edit/Setup/SetupSection.cs @@ -34,7 +34,6 @@ namespace osu.Game.Screens.Edit.Setup [BackgroundDependencyLoader] private void load() { - RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; InternalChild = new FillFlowContainer From 2c0a7d4c182e0b9cbb79d9ffae0d5afba97b57df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Oct 2024 14:32:46 +0200 Subject: [PATCH 401/924] Adjust slider bar padding https://discord.com/channels/188630481301012481/188630652340404224/1291374650256916482 --- osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs index ac3730598f..78c197dee6 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs @@ -107,7 +107,12 @@ namespace osu.Game.Graphics.UserInterfaceV2 new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(9), + Padding = new MarginPadding + { + Vertical = 9, + Left = 9, + Right = 5, + }, Children = new Drawable[] { caption = new FormFieldCaption From 090c8ee602407334d694fa5cfce25fc61459bda1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Oct 2024 14:35:03 +0200 Subject: [PATCH 402/924] Make colour palette things circular again --- osu.Game/Graphics/UserInterfaceV2/FormColourPalette.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/FormColourPalette.cs b/osu.Game/Graphics/UserInterfaceV2/FormColourPalette.cs index 00bac0c346..9575ebaa3b 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormColourPalette.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormColourPalette.cs @@ -77,7 +77,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 Child = button = new RoundedButton { Action = () => Colours.Add(Colour4.White), - Size = new Vector2(70, 25), + Size = new Vector2(70), Text = "+", } } @@ -149,10 +149,10 @@ namespace osu.Game.Graphics.UserInterfaceV2 [BackgroundDependencyLoader] private void load() { - Size = new Vector2(70, 25); + Size = new Vector2(70); Masking = true; - CornerRadius = 12.5f; + CornerRadius = 35; Action = this.ShowPopover; Children = new Drawable[] From 1bab2236fe40c4b87f39ebac16bbfafda370b898 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Oct 2024 15:01:23 +0200 Subject: [PATCH 403/924] Ensure columns collapse into one correctly if no space --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 50 ++++++++++++++-------- 3 files changed, 35 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 9f48da599e..5bd7a0ff00 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -233,7 +233,7 @@ namespace osu.Game.Rulesets.Catch { AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(25), + Spacing = new Vector2(SetupScreen.SPACING), Children = new Drawable[] { new ResourcesSection diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 9f2a5b2066..2f928aaefa 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -346,7 +346,7 @@ namespace osu.Game.Rulesets.Osu { AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(25), + Spacing = new Vector2(SetupScreen.SPACING), Children = new Drawable[] { new ResourcesSection diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 1af54d55d6..38720f6333 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -15,6 +15,10 @@ namespace osu.Game.Screens.Edit.Setup { public partial class SetupScreen : EditorScreen { + public const float COLUMN_WIDTH = 450; + public const float SPACING = 28; + public const float MAX_WIDTH = 2 * COLUMN_WIDTH + SPACING; + public SetupScreen() : base(EditorScreenMode.SongSetup) { @@ -23,6 +27,9 @@ namespace osu.Game.Screens.Edit.Setup [Cached] private SetupScreenHeaderBackground background = new SetupScreenHeaderBackground { RelativeSizeAxes = Axes.Both, }; + private OsuScrollContainer scroll = null!; + private FillFlowContainer flow = null!; + [BackgroundDependencyLoader] private void load(EditorBeatmap beatmap, OverlayColourProvider colourProvider) { @@ -51,31 +58,24 @@ namespace osu.Game.Screens.Edit.Setup }, new Drawable[] { - new OsuScrollContainer + scroll = new OsuScrollContainer { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(15), - Child = new FillFlowContainer + Child = flow = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Full, - Spacing = new Vector2(28), - Children = new Drawable[] + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Spacing = new Vector2(25), + ChildrenEnumerable = ruleset.CreateEditorSetupSections().Select(section => section.With(s => { - new FillFlowContainer - { - Width = 925, - AutoSizeAxes = Axes.Y, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Spacing = new Vector2(25), - ChildrenEnumerable = ruleset.CreateEditorSetupSections().Select(section => section.With(s => - { - s.Width = 450; - })), - }, - } + s.Width = 450; + s.Anchor = Anchor.TopCentre; + s.Origin = Anchor.TopCentre; + })), } } } @@ -84,6 +84,22 @@ namespace osu.Game.Screens.Edit.Setup }; } + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + if (scroll.DrawWidth > MAX_WIDTH) + { + flow.RelativeSizeAxes = Axes.None; + flow.Width = MAX_WIDTH; + } + else + { + flow.RelativeSizeAxes = Axes.X; + flow.Width = 1; + } + } + public override void OnExiting(ScreenExitEvent e) { base.OnExiting(e); From 1280d7ea15d57a4197c80e712965ca87c0cdaa74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Oct 2024 15:05:15 +0200 Subject: [PATCH 404/924] Fix tests again --- osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs | 2 +- osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs b/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs index a4f250675e..4dd27a7b6e 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.Editing { (typeof(EditorBeatmap), editorBeatmap) }, - Child = designSection = new TestDesignSection() + Child = designSection = new TestDesignSection { RelativeSizeAxes = Axes.X } }); } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs b/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs index 167230f5a0..743529d40c 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Testing; @@ -205,7 +206,7 @@ namespace osu.Game.Tests.Visual.Editing } private void createSection() - => AddStep("create metadata section", () => Child = metadataSection = new TestMetadataSection()); + => AddStep("create metadata section", () => Child = metadataSection = new TestMetadataSection { RelativeSizeAxes = Axes.X }); private void assertArtistMetadata(string expected) => AddAssert($"artist metadata is {expected}", () => editorBeatmap.Metadata.Artist, () => Is.EqualTo(expected)); From 29418226c05eda74d72875441656683e65989b45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Oct 2024 15:32:25 +0200 Subject: [PATCH 405/924] Do not add checkbox padding to the left of menu items if no item actually needs it RFC. As per https://discord.com/channels/188630481301012481/188630652340404224/1291346164976980009. The diff is extremely dumb but I tried a few smarter methods and they're either not fully correct or don't work. Primary problem is that menu items are mutable externally and there's no hook provided by the framework to know that items changed. One could probably be made but I'd prefer that this change be examined visually first before I engage in too much ceremony and start changing framework around. --- .../Graphics/UserInterface/DrawableOsuMenuItem.cs | 6 ++++++ osu.Game/Graphics/UserInterface/OsuMenu.cs | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs index 20de8e3c9f..cd44bd8fb9 100644 --- a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -26,6 +27,8 @@ namespace osu.Game.Graphics.UserInterface public const int TEXT_SIZE = 17; public const int TRANSITION_LENGTH = 80; + public BindableBool ShowCheckbox { get; } = new BindableBool(); + private TextContainer text; private HotkeyDisplay hotkey; private HoverClickSounds hoverClickSounds; @@ -72,6 +75,7 @@ namespace osu.Game.Graphics.UserInterface { base.LoadComplete(); + ShowCheckbox.BindValueChanged(_ => updateState()); Item.Action.BindDisabledChanged(_ => updateState(), true); FinishTransforms(); } @@ -138,6 +142,8 @@ namespace osu.Game.Graphics.UserInterface text.BoldText.FadeOut(TRANSITION_LENGTH, Easing.OutQuint); text.NormalText.FadeIn(TRANSITION_LENGTH, Easing.OutQuint); } + + text.CheckboxContainer.Alpha = ShowCheckbox.Value ? 1 : 0; } protected sealed override Drawable CreateContent() => text = CreateTextContainer(); diff --git a/osu.Game/Graphics/UserInterface/OsuMenu.cs b/osu.Game/Graphics/UserInterface/OsuMenu.cs index 2b9a26166f..719100e138 100644 --- a/osu.Game/Graphics/UserInterface/OsuMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuMenu.cs @@ -3,6 +3,7 @@ #nullable disable +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -42,6 +43,16 @@ namespace osu.Game.Graphics.UserInterface sampleClose = audio.Samples.Get(@"UI/dropdown-close"); } + protected override void Update() + { + base.Update(); + + bool showCheckboxes = Items.Any(i => i is StatefulMenuItem); + + foreach (var drawableItem in ItemsContainer.OfType()) + drawableItem.ShowCheckbox.Value = showCheckboxes; + } + protected override void AnimateOpen() { if (!TopLevelMenu && !wasOpened) From 114e53f8b2afb595081bb9894e84639faa9b13ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 4 Oct 2024 09:46:10 +0200 Subject: [PATCH 406/924] Add failing test --- .../Visual/Editing/TestSceneColoursSection.cs | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneColoursSection.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneColoursSection.cs b/osu.Game.Tests/Visual/Editing/TestSceneColoursSection.cs new file mode 100644 index 0000000000..5a3329bbc9 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneColoursSection.cs @@ -0,0 +1,123 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Setup; +using osu.Game.Skinning; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Editing +{ + [HeadlessTest] + public partial class TestSceneColoursSection : OsuManualInputManagerTestScene + { + [Test] + public void TestNoBeatmapSkinColours() + { + LegacyBeatmapSkin skin = null!; + ColoursSection coloursSection = null!; + + AddStep("create beatmap skin", () => skin = new LegacyBeatmapSkin(new BeatmapInfo(), null)); + AddStep("create colours section", () => Child = new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = + [ + (typeof(EditorBeatmap), new EditorBeatmap(new Beatmap + { + BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo } + }, skin)), + (typeof(OverlayColourProvider), new OverlayColourProvider(OverlayColourScheme.Aquamarine)) + ], + Child = coloursSection = new ColoursSection + { + RelativeSizeAxes = Axes.X, + } + }); + AddAssert("beatmap skin has no colours", () => skin.Configuration.CustomComboColours, () => Is.Empty); + AddAssert("section displays default combo colours", + () => coloursSection.ChildrenOfType().Single().Colours, + () => Is.EquivalentTo(new Colour4[] + { + SkinConfiguration.DefaultComboColours[1], + SkinConfiguration.DefaultComboColours[2], + SkinConfiguration.DefaultComboColours[3], + SkinConfiguration.DefaultComboColours[0], + })); + + AddStep("add a colour", () => coloursSection.ChildrenOfType().Single().Colours.Add(Colour4.Aqua)); + AddAssert("beatmap skin has colours", + () => skin.Configuration.CustomComboColours, + () => Is.EquivalentTo(new[] + { + SkinConfiguration.DefaultComboColours[1], + SkinConfiguration.DefaultComboColours[2], + SkinConfiguration.DefaultComboColours[3], + Color4.Aqua, + SkinConfiguration.DefaultComboColours[0], + })); + } + + [Test] + public void TestExistingColours() + { + LegacyBeatmapSkin skin = null!; + ColoursSection coloursSection = null!; + + AddStep("create beatmap skin", () => + { + skin = new LegacyBeatmapSkin(new BeatmapInfo(), null); + skin.Configuration.CustomComboColours = new List + { + Color4.Azure, + Color4.Beige, + Color4.Chartreuse + }; + }); + AddStep("create colours section", () => Child = new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = + [ + (typeof(EditorBeatmap), new EditorBeatmap(new Beatmap + { + BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo } + }, skin)), + (typeof(OverlayColourProvider), new OverlayColourProvider(OverlayColourScheme.Aquamarine)) + ], + Child = coloursSection = new ColoursSection + { + RelativeSizeAxes = Axes.X, + } + }); + AddAssert("section displays combo colours", + () => coloursSection.ChildrenOfType().Single().Colours, + () => Is.EquivalentTo(new[] + { + Colour4.Beige, + Colour4.Chartreuse, + Colour4.Azure, + })); + + AddStep("add a colour", () => coloursSection.ChildrenOfType().Single().Colours.Add(Colour4.Aqua)); + AddAssert("beatmap skin has colours", + () => skin.Configuration.CustomComboColours, + () => Is.EquivalentTo(new[] + { + Color4.Azure, + Color4.Beige, + Color4.Aqua, + Color4.Chartreuse + })); + } + } +} From 6e5a38c6c8f974f3ad89b629990dd8e81acd9616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 4 Oct 2024 10:01:11 +0200 Subject: [PATCH 407/924] Initialise colours section with default combo colours if none present Closes https://github.com/ppy/osu/issues/30100. --- osu.Game/Screens/Edit/Setup/ColoursSection.cs | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Setup/ColoursSection.cs b/osu.Game/Screens/Edit/Setup/ColoursSection.cs index 01ca114e4f..ee76ec1f6d 100644 --- a/osu.Game/Screens/Edit/Setup/ColoursSection.cs +++ b/osu.Game/Screens/Edit/Setup/ColoursSection.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Localisation; +using osu.Game.Skinning; namespace osu.Game.Screens.Edit.Setup { @@ -25,9 +26,50 @@ namespace osu.Game.Screens.Edit.Setup Caption = EditorSetupStrings.HitCircleSliderCombos, } }; + } + private bool syncingColours = false; + + protected override void LoadComplete() + { if (Beatmap.BeatmapSkin != null) - comboColours.Colours.BindTo(Beatmap.BeatmapSkin.ComboColours); + comboColours.Colours.AddRange(Beatmap.BeatmapSkin.ComboColours); + + if (comboColours.Colours.Count == 0) + { + // compare ctor of `EditorBeatmapSkin` + for (int i = 0; i < SkinConfiguration.DefaultComboColours.Count; ++i) + comboColours.Colours.Add(SkinConfiguration.DefaultComboColours[(i + 1) % SkinConfiguration.DefaultComboColours.Count]); + } + + comboColours.Colours.BindCollectionChanged((_, _) => + { + if (Beatmap.BeatmapSkin != null) + { + if (syncingColours) + return; + + syncingColours = true; + + Beatmap.BeatmapSkin.ComboColours.Clear(); + Beatmap.BeatmapSkin.ComboColours.AddRange(comboColours.Colours); + + syncingColours = false; + } + }); + + Beatmap.BeatmapSkin?.ComboColours.BindCollectionChanged((_, _) => + { + if (syncingColours) + return; + + syncingColours = true; + + comboColours.Colours.Clear(); + comboColours.Colours.AddRange(Beatmap.BeatmapSkin?.ComboColours); + + syncingColours = false; + }); } } } From 2d7fdaf89271da7fc93516b82c5f301f9a2510cd Mon Sep 17 00:00:00 2001 From: CloneWith Date: Fri, 4 Oct 2024 16:42:48 +0800 Subject: [PATCH 408/924] Override OnMouseMove for cursor position fetching --- .../Screens/Play/HUD/ArgonSongProgressBar.cs | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs index 28a2e1030f..3c9553ee55 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Utils; @@ -39,8 +38,6 @@ namespace osu.Game.Screens.Play.HUD private float relativePositionX; - private InputManager? inputManager; - public LocalisableString TooltipText => $"{(relativePositionX > 0 ? (EndTime - StartTime) * relativePositionX / DrawWidth : relativePositionX > DrawWidth ? EndTime : 0).ToEditorFormattedString()}" + $" - {(relativePositionX > 0 ? Math.Round(relativePositionX / DrawWidth * 100, 2) : relativePositionX > DrawWidth ? 100 : 0)}%"; @@ -85,7 +82,6 @@ namespace osu.Game.Screens.Play.HUD private void load(OsuColour colours) { catchUpColour = colours.BlueDark; - inputManager = GetContainingInputManager(); } protected override void LoadComplete() @@ -96,6 +92,16 @@ namespace osu.Game.Screens.Play.HUD playfieldBar.TransformTo(nameof(playfieldBar.AccentColour), mainColour, 200, Easing.In); } + protected override bool OnMouseMove(MouseMoveEvent e) + { + base.OnMouseMove(e); + + var cursorPosition = e.ScreenSpaceMousePosition; + relativePositionX = ToLocalSpace(cursorPosition).X; + + return true; + } + protected override bool OnHover(HoverEvent e) { if (Interactive) @@ -114,18 +120,6 @@ namespace osu.Game.Screens.Play.HUD { base.Update(); - if (inputManager != null) - { - // Update the cursor position in time - var cursorPosition = inputManager.CurrentState.Mouse.Position; - relativePositionX = ToLocalSpace(cursorPosition).X; - } - else - { - // If null (e.g. before the game starts), try getting the input manager again - inputManager = GetContainingInputManager(); - } - playfieldBar.Length = (float)Interpolation.Lerp(playfieldBar.Length, Progress, Math.Clamp(Time.Elapsed / 40, 0, 1)); audioBar.Length = (float)Interpolation.Lerp(audioBar.Length, AudioProgress, Math.Clamp(Time.Elapsed / 40, 0, 1)); From fd5655455a6611488db0636c61973272d91bea27 Mon Sep 17 00:00:00 2001 From: CloneWith Date: Fri, 4 Oct 2024 16:56:16 +0800 Subject: [PATCH 409/924] Adjust tooltip text format --- osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs index 3c9553ee55..a33c965417 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Utils; -using osu.Game.Extensions; using osu.Game.Graphics; using osuTK; @@ -38,8 +37,9 @@ namespace osu.Game.Screens.Play.HUD private float relativePositionX; - public LocalisableString TooltipText => $"{(relativePositionX > 0 ? (EndTime - StartTime) * relativePositionX / DrawWidth : relativePositionX > DrawWidth ? EndTime : 0).ToEditorFormattedString()}" - + $" - {(relativePositionX > 0 ? Math.Round(relativePositionX / DrawWidth * 100, 2) : relativePositionX > DrawWidth ? 100 : 0)}%"; + public LocalisableString TooltipText => $"{formatTime(TimeSpan.FromSeconds(relativePositionX > 0 ? Math.Round((EndTime - StartTime) * relativePositionX / DrawWidth / 1000) + : relativePositionX > DrawWidth ? Math.Round(EndTime / 1000) : 0))}" + + $" - {(relativePositionX > 0 ? Math.Round(relativePositionX / DrawWidth * 100, 1) : relativePositionX > DrawWidth ? 100 : 0)}%"; public ArgonSongProgressBar(float barHeight) { @@ -191,5 +191,7 @@ namespace osu.Game.Screens.Play.HUD set => fill.Colour = value; } } + + private string formatTime(TimeSpan timeSpan) => $"{(timeSpan < TimeSpan.Zero ? "-" : "")}{Math.Floor(timeSpan.Duration().TotalMinutes)}:{timeSpan.Duration().Seconds:D2}"; } } From 7cd724f342adca027eee034ce352a1a4dfc1a458 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 4 Oct 2024 11:09:14 +0200 Subject: [PATCH 410/924] Move setup screen background preview to appropriate form control See https://discord.com/channels/188630481301012481/188630652340404224/1291361342971707463. --- .../UserInterfaceV2/FormFileSelector.cs | 20 +++++++- .../Screens/Edit/Setup/ResourcesSection.cs | 11 +++- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 50 ++++++------------- .../Edit/Setup/SetupScreenHeaderBackground.cs | 3 +- 4 files changed, 43 insertions(+), 41 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs b/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs index 3b822a1b2f..81023417a5 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs @@ -68,6 +68,8 @@ namespace osu.Game.Graphics.UserInterfaceV2 /// public LocalisableString PlaceholderText { get; init; } + public Container PreviewContainer { get; private set; } = null!; + private Box background = null!; private FormFieldCaption caption = null!; @@ -89,7 +91,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 private void load() { RelativeSizeAxes = Axes.X; - Height = 50; + AutoSizeAxes = Axes.Y; Masking = true; CornerRadius = 5; @@ -101,9 +103,23 @@ namespace osu.Game.Graphics.UserInterfaceV2 RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background5, }, + PreviewContainer = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding + { + Horizontal = 1.5f, + Top = 1.5f, + Bottom = 50 + }, + }, new Container { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + Height = 50, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, Padding = new MarginPadding(9), Children = new Drawable[] { diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 6fec7078a8..845c21b598 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -35,12 +35,17 @@ namespace osu.Game.Screens.Edit.Setup [Resolved] private Editor? editor { get; set; } - [Resolved] - private SetupScreenHeaderBackground headerBackground { get; set; } = null!; + private SetupScreenHeaderBackground headerBackground = null!; [BackgroundDependencyLoader] private void load() { + headerBackground = new SetupScreenHeaderBackground + { + RelativeSizeAxes = Axes.X, + Height = 110, + }; + Children = new Drawable[] { backgroundChooser = new FormFileSelector(".jpg", ".jpeg", ".png") @@ -55,6 +60,8 @@ namespace osu.Game.Screens.Edit.Setup }, }; + backgroundChooser.PreviewContainer.Add(headerBackground); + if (!string.IsNullOrEmpty(working.Value.Metadata.BackgroundFile)) backgroundChooser.Current.Value = new FileInfo(working.Value.Metadata.BackgroundFile); diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 38720f6333..f8c4998263 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -24,9 +24,6 @@ namespace osu.Game.Screens.Edit.Setup { } - [Cached] - private SetupScreenHeaderBackground background = new SetupScreenHeaderBackground { RelativeSizeAxes = Axes.Both, }; - private OsuScrollContainer scroll = null!; private FillFlowContainer flow = null!; @@ -42,43 +39,24 @@ namespace osu.Game.Screens.Edit.Setup RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background3, }, - new GridContainer + scroll = new OsuScrollContainer { RelativeSizeAxes = Axes.Both, - RowDimensions = - [ - new Dimension(GridSizeMode.Absolute, 110), - new Dimension() - ], - Content = new[] + Padding = new MarginPadding(15), + Child = flow = new FillFlowContainer { - new Drawable[] + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Spacing = new Vector2(25), + ChildrenEnumerable = ruleset.CreateEditorSetupSections().Select(section => section.With(s => { - background, - }, - new Drawable[] - { - scroll = new OsuScrollContainer - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(15), - Child = flow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Full, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Spacing = new Vector2(25), - ChildrenEnumerable = ruleset.CreateEditorSetupSections().Select(section => section.With(s => - { - s.Width = 450; - s.Anchor = Anchor.TopCentre; - s.Origin = Anchor.TopCentre; - })), - } - } - } + s.Width = 450; + s.Anchor = Anchor.TopCentre; + s.Origin = Anchor.TopCentre; + })), } } }; diff --git a/osu.Game/Screens/Edit/Setup/SetupScreenHeaderBackground.cs b/osu.Game/Screens/Edit/Setup/SetupScreenHeaderBackground.cs index 033e5361bb..5f3e6eb469 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreenHeaderBackground.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreenHeaderBackground.cs @@ -29,7 +29,8 @@ namespace osu.Game.Screens.Edit.Setup InternalChild = content = new Container { RelativeSizeAxes = Axes.Both, - Masking = true + Masking = true, + CornerRadius = 3.5f, }; } From 61103cc712672f5f9069fa5f34ce0cfd2b5cdf55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 4 Oct 2024 11:18:09 +0200 Subject: [PATCH 411/924] Remove redundant initialiser --- osu.Game/Screens/Edit/Setup/ColoursSection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Setup/ColoursSection.cs b/osu.Game/Screens/Edit/Setup/ColoursSection.cs index ee76ec1f6d..8de7f86523 100644 --- a/osu.Game/Screens/Edit/Setup/ColoursSection.cs +++ b/osu.Game/Screens/Edit/Setup/ColoursSection.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Edit.Setup }; } - private bool syncingColours = false; + private bool syncingColours; protected override void LoadComplete() { From e136568a18785f45d42a3d0590c5cdc1ea05215b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 4 Oct 2024 11:25:21 +0200 Subject: [PATCH 412/924] Remove linq usage to kill allocations --- osu.Game/Graphics/UserInterface/OsuMenu.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuMenu.cs b/osu.Game/Graphics/UserInterface/OsuMenu.cs index 719100e138..6e7dad2b5f 100644 --- a/osu.Game/Graphics/UserInterface/OsuMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuMenu.cs @@ -3,7 +3,6 @@ #nullable disable -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -47,10 +46,19 @@ namespace osu.Game.Graphics.UserInterface { base.Update(); - bool showCheckboxes = Items.Any(i => i is StatefulMenuItem); + bool showCheckboxes = false; - foreach (var drawableItem in ItemsContainer.OfType()) - drawableItem.ShowCheckbox.Value = showCheckboxes; + foreach (var drawableItem in ItemsContainer) + { + if (drawableItem.Item is StatefulMenuItem) + showCheckboxes = true; + } + + foreach (var drawableItem in ItemsContainer) + { + if (drawableItem is DrawableOsuMenuItem osuItem) + osuItem.ShowCheckbox.Value = showCheckboxes; + } } protected override void AnimateOpen() From ff2777a3b941e7d7efc4fdc2298739342762f515 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Oct 2024 18:43:39 +0900 Subject: [PATCH 413/924] When adding a new combo colour, use the last colour as a starting point Also opens the popover automatically because you always want to edit it. --- .../Graphics/UserInterfaceV2/FormColourPalette.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/FormColourPalette.cs b/osu.Game/Graphics/UserInterfaceV2/FormColourPalette.cs index 9575ebaa3b..fad58841e3 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormColourPalette.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormColourPalette.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Specialized; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; @@ -19,6 +20,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Resources.Localisation.Web; using osuTK; +using osuTK.Graphics; namespace osu.Game.Graphics.UserInterfaceV2 { @@ -76,7 +78,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 Spacing = new Vector2(5), Child = button = new RoundedButton { - Action = () => Colours.Add(Colour4.White), + Action = addNewColour, Size = new Vector2(70), Text = "+", } @@ -112,6 +114,16 @@ namespace osu.Game.Graphics.UserInterfaceV2 updateState(); } + private void addNewColour() + { + Color4 startingColour = Colours.Count > 0 + ? Colours.Last() + : Colour4.White; + + Colours.Add(startingColour); + flow.OfType().Last().TriggerClick(); + } + private void updateState() { background.Colour = colourProvider.Background5; From 45a6a743a226becf932488075fd91617f4a7f440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 4 Oct 2024 13:12:25 +0200 Subject: [PATCH 414/924] Fix improper handling of decimal separator in "form" number boxes / sliders Spotted in passing in https://discord.com/channels/188630481301012481/1097318920991559880/1291693852981329981. --- osu.Game/Graphics/UserInterfaceV2/FormNumberBox.cs | 4 +++- osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs | 13 ++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/FormNumberBox.cs b/osu.Game/Graphics/UserInterfaceV2/FormNumberBox.cs index 8ce6c85fa9..c3256e0038 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormNumberBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormNumberBox.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. +using System.Globalization; + namespace osu.Game.Graphics.UserInterfaceV2 { public partial class FormNumberBox : FormTextBox @@ -18,7 +20,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 public bool AllowDecimals { get; init; } protected override bool CanAddCharacter(char character) - => char.IsAsciiDigit(character) || (AllowDecimals && character == '.'); + => char.IsAsciiDigit(character) || (AllowDecimals && CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator.Contains(character)); } } } diff --git a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs index 78c197dee6..a29e33a421 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs @@ -17,6 +17,7 @@ using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; +using osu.Game.Localisation; using osu.Game.Overlays; namespace osu.Game.Graphics.UserInterfaceV2 @@ -83,8 +84,10 @@ namespace osu.Game.Graphics.UserInterfaceV2 [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; + private readonly Bindable currentLanguage = new Bindable(); + [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, OsuGame? game) { RelativeSizeAxes = Axes.X; Height = 50; @@ -150,6 +153,9 @@ namespace osu.Game.Graphics.UserInterfaceV2 }, }, }; + + if (game != null) + currentLanguage.BindTo(game.CurrentLanguage); } protected override void LoadComplete() @@ -164,10 +170,11 @@ namespace osu.Game.Graphics.UserInterfaceV2 slider.IsDragging.BindValueChanged(_ => updateState()); + currentLanguage.BindValueChanged(_ => Schedule(updateValueDisplay)); current.BindValueChanged(_ => { updateState(); - updateTextBoxFromSlider(); + updateValueDisplay(); }, true); } @@ -258,7 +265,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 background.Colour = colourProvider.Background5; } - private void updateTextBoxFromSlider() + private void updateValueDisplay() { if (updatingFromTextBox) return; From 86c3e3e987740745ddebed4680dc1e3ff380ffa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 4 Oct 2024 13:59:04 +0200 Subject: [PATCH 415/924] Replace `FormSliderBar.Instantaneous` with `TransferValueOnCommit` Rather than control the propagation of the value between the slider and the textbox, add a property that controls the propagation of the value between the bindables inside the form control to external bindables. This will help alleviate issues where the external bindable update incurs overheads due to having heavy change callbacks attached. --- .../UserInterface/TestSceneFormControls.cs | 15 +--- .../UserInterface/TestSceneFormSliderBar.cs | 63 ++++++++++++++ .../Graphics/UserInterfaceV2/FormSliderBar.cs | 86 ++++++++++++------- 3 files changed, 118 insertions(+), 46 deletions(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneFormSliderBar.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs index 518c3fc693..c6fd65b973 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.UserInterface }, new FormSliderBar { - Caption = "Instantaneous slider", + Caption = "Slider", Current = new BindableFloat { MinValue = 0, @@ -82,19 +82,6 @@ namespace osu.Game.Tests.Visual.UserInterface }, TabbableContentContainer = this, }, - new FormSliderBar - { - Caption = "Non-instantaneous slider", - Current = new BindableFloat - { - MinValue = 0, - MaxValue = 10, - Value = 5, - Precision = 0.1f, - }, - Instantaneous = false, - TabbableContentContainer = this, - }, new FormEnumDropdown { Caption = EditorSetupStrings.EnableCountdown, diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFormSliderBar.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFormSliderBar.cs new file mode 100644 index 0000000000..97835a993d --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFormSliderBar.cs @@ -0,0 +1,63 @@ +// 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.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public partial class TestSceneFormSliderBar : OsuTestScene + { + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + + [Test] + public void TestTransferValueOnCommit() + { + OsuSpriteText text; + FormSliderBar slider = null!; + + AddStep("create content", () => + { + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 0.5f, + Direction = FillDirection.Vertical, + Spacing = new Vector2(10), + Children = new Drawable[] + { + text = new OsuSpriteText(), + slider = new FormSliderBar + { + Caption = "Slider", + Current = new BindableFloat + { + MinValue = 0, + MaxValue = 10, + Precision = 0.1f, + Default = 5f, + } + }, + } + }; + slider.Current.BindValueChanged(_ => text.Text = $"Current value is: {slider.Current.Value}", true); + }); + AddToggleStep("toggle transfer value on commit", b => + { + if (slider.IsNotNull()) + slider.TransferValueOnCommit = b; + }); + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs index a29e33a421..da28437eee 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs @@ -28,27 +28,23 @@ namespace osu.Game.Graphics.UserInterfaceV2 public Bindable Current { get => current.Current; - set => current.Current = value; - } - - private bool instantaneous = true; - - /// - /// Whether changes to the slider should instantaneously transfer to the text box (and vice versa). - /// If , the transfer will happen on text box commit (explicit, or implicit via focus loss), or on slider drag end. - /// - public bool Instantaneous - { - get => instantaneous; set { - instantaneous = value; - - if (slider.IsNotNull()) - slider.TransferValueOnCommit = !instantaneous; + current.Current = value; + currentNumberInstantaneous.Default = current.Default; } } + private readonly BindableNumberWithCurrent current = new BindableNumberWithCurrent(); + + private readonly BindableNumber currentNumberInstantaneous = new BindableNumber(); + + /// + /// Whether changes to the value should instantaneously transfer to outside bindables. + /// If , the transfer will happen on text box commit (explicit, or implicit via focus loss), or on slider commit. + /// + public bool TransferValueOnCommit { get; set; } + private CompositeDrawable? tabbableContentContainer; public CompositeDrawable? TabbableContentContainer @@ -62,8 +58,6 @@ namespace osu.Game.Graphics.UserInterfaceV2 } } - private readonly BindableNumberWithCurrent current = new BindableNumberWithCurrent(); - /// /// Caption describing this slider bar, displayed on top of the controls. /// @@ -147,8 +141,8 @@ namespace osu.Game.Graphics.UserInterfaceV2 Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.X, Width = 0.5f, - Current = Current, - TransferValueOnCommit = !instantaneous, + Current = currentNumberInstantaneous, + OnCommit = () => current.Value = currentNumberInstantaneous.Value, } }, }, @@ -170,9 +164,28 @@ namespace osu.Game.Graphics.UserInterfaceV2 slider.IsDragging.BindValueChanged(_ => updateState()); - currentLanguage.BindValueChanged(_ => Schedule(updateValueDisplay)); - current.BindValueChanged(_ => + current.ValueChanged += e => currentNumberInstantaneous.Value = e.NewValue; + current.MinValueChanged += v => currentNumberInstantaneous.MinValue = v; + current.MaxValueChanged += v => currentNumberInstantaneous.MaxValue = v; + current.PrecisionChanged += v => currentNumberInstantaneous.Precision = v; + current.DisabledChanged += disabled => { + if (disabled) + { + // revert any changes before disabling to make sure we are in a consistent state. + currentNumberInstantaneous.Value = current.Value; + } + + currentNumberInstantaneous.Disabled = disabled; + }; + + current.CopyTo(currentNumberInstantaneous); + currentLanguage.BindValueChanged(_ => Schedule(updateValueDisplay)); + currentNumberInstantaneous.BindValueChanged(e => + { + if (!TransferValueOnCommit) + current.Value = e.NewValue; + updateState(); updateValueDisplay(); }, true); @@ -182,17 +195,15 @@ namespace osu.Game.Graphics.UserInterfaceV2 private void textChanged(ValueChangedEvent change) { - if (!instantaneous) return; - tryUpdateSliderFromTextBox(); } private void textCommitted(TextBox t, bool isNew) { tryUpdateSliderFromTextBox(); - // If the attempted update above failed, restore text box to match the slider. - Current.TriggerChange(); + currentNumberInstantaneous.TriggerChange(); + current.Value = currentNumberInstantaneous.Value; flashLayer.Colour = ColourInfo.GradientVertical(colourProvider.Dark2.Opacity(0), colourProvider.Dark2); flashLayer.FadeOutFromOne(800, Easing.OutQuint); @@ -204,7 +215,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 try { - switch (Current) + switch (currentNumberInstantaneous) { case Bindable bindableInt: bindableInt.Value = int.Parse(textBox.Current.Value); @@ -215,7 +226,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 break; default: - Current.Parse(textBox.Current.Value, CultureInfo.CurrentCulture); + currentNumberInstantaneous.Parse(textBox.Current.Value, CultureInfo.CurrentCulture); break; } } @@ -250,9 +261,9 @@ namespace osu.Game.Graphics.UserInterfaceV2 { textBox.Alpha = 1; - background.Colour = Current.Disabled ? colourProvider.Background4 : colourProvider.Background5; - caption.Colour = Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content2; - textBox.Colour = Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content1; + background.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Background4 : colourProvider.Background5; + caption.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Foreground1 : colourProvider.Content2; + textBox.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Foreground1 : colourProvider.Content1; BorderThickness = IsHovered || textBox.Focused.Value || slider.IsDragging.Value ? 2 : 0; BorderColour = textBox.Focused.Value ? colourProvider.Highlight1 : colourProvider.Light4; @@ -269,12 +280,13 @@ namespace osu.Game.Graphics.UserInterfaceV2 { if (updatingFromTextBox) return; - textBox.Text = slider.GetDisplayableValue(Current.Value).ToString(); + textBox.Text = slider.GetDisplayableValue(currentNumberInstantaneous.Value).ToString(); } private partial class Slider : OsuSliderBar { public BindableBool IsDragging { get; set; } = new BindableBool(); + public Action? OnCommit { get; set; } private Box leftBox = null!; private Box rightBox = null!; @@ -381,6 +393,16 @@ namespace osu.Game.Graphics.UserInterfaceV2 { nub.MoveToX(value, 200, Easing.OutPow10); } + + protected override bool Commit() + { + bool result = base.Commit(); + + if (result) + OnCommit?.Invoke(); + + return result; + } } } } From 7816c41b94e367f90f7c3c7ecb142c0e0d116272 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 4 Oct 2024 14:03:38 +0200 Subject: [PATCH 416/924] Only transfer difficulty slider values on commit Closes https://github.com/ppy/osu/issues/30112. --- .../Edit/Setup/ManiaDifficultySection.cs | 5 +++++ osu.Game.Rulesets.Osu/Edit/Setup/OsuDifficultySection.cs | 7 +++++++ .../Edit/Setup/TaikoDifficultySection.cs | 4 ++++ osu.Game/Screens/Edit/Setup/DifficultySection.cs | 6 ++++++ 4 files changed, 22 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs b/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs index ed1de591f6..a23988362a 100644 --- a/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs +++ b/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs @@ -48,6 +48,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup MaxValue = 10, Precision = 1, }, + TransferValueOnCommit = true, TabbableContentContainer = this, }, specialStyle = new FormCheckBox @@ -67,6 +68,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup MaxValue = 10, Precision = 0.1f, }, + TransferValueOnCommit = true, TabbableContentContainer = this, }, overallDifficultySlider = new FormSliderBar @@ -80,6 +82,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup MaxValue = 10, Precision = 0.1f, }, + TransferValueOnCommit = true, TabbableContentContainer = this, }, baseVelocitySlider = new FormSliderBar @@ -93,6 +96,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup MaxValue = 3.6, Precision = 0.01f, }, + TransferValueOnCommit = true, TabbableContentContainer = this, }, tickRateSlider = new FormSliderBar @@ -106,6 +110,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup MaxValue = 4, Precision = 1, }, + TransferValueOnCommit = true, TabbableContentContainer = this, }, }; diff --git a/osu.Game.Rulesets.Osu/Edit/Setup/OsuDifficultySection.cs b/osu.Game.Rulesets.Osu/Edit/Setup/OsuDifficultySection.cs index 7008c87d41..7a01646b35 100644 --- a/osu.Game.Rulesets.Osu/Edit/Setup/OsuDifficultySection.cs +++ b/osu.Game.Rulesets.Osu/Edit/Setup/OsuDifficultySection.cs @@ -42,6 +42,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Setup MaxValue = 10, Precision = 0.1f, }, + TransferValueOnCommit = true, TabbableContentContainer = this, }, healthDrainSlider = new FormSliderBar @@ -55,6 +56,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Setup MaxValue = 10, Precision = 0.1f, }, + TransferValueOnCommit = true, TabbableContentContainer = this, }, approachRateSlider = new FormSliderBar @@ -68,6 +70,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Setup MaxValue = 10, Precision = 0.1f, }, + TransferValueOnCommit = true, TabbableContentContainer = this, }, overallDifficultySlider = new FormSliderBar @@ -81,6 +84,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Setup MaxValue = 10, Precision = 0.1f, }, + TransferValueOnCommit = true, TabbableContentContainer = this, }, baseVelocitySlider = new FormSliderBar @@ -94,6 +98,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Setup MaxValue = 3.6, Precision = 0.01f, }, + TransferValueOnCommit = true, TabbableContentContainer = this, }, tickRateSlider = new FormSliderBar @@ -107,6 +112,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Setup MaxValue = 4, Precision = 1, }, + TransferValueOnCommit = true, TabbableContentContainer = this, }, stackLeniency = new FormSliderBar @@ -120,6 +126,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Setup MaxValue = 1, Precision = 0.1f }, + TransferValueOnCommit = true, TabbableContentContainer = this, }, }; diff --git a/osu.Game.Rulesets.Taiko/Edit/Setup/TaikoDifficultySection.cs b/osu.Game.Rulesets.Taiko/Edit/Setup/TaikoDifficultySection.cs index 8fce59e791..52f7176b3f 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Setup/TaikoDifficultySection.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Setup/TaikoDifficultySection.cs @@ -39,6 +39,7 @@ namespace osu.Game.Rulesets.Taiko.Edit.Setup MaxValue = 10, Precision = 0.1f, }, + TransferValueOnCommit = true, TabbableContentContainer = this, }, overallDifficultySlider = new FormSliderBar @@ -52,6 +53,7 @@ namespace osu.Game.Rulesets.Taiko.Edit.Setup MaxValue = 10, Precision = 0.1f, }, + TransferValueOnCommit = true, TabbableContentContainer = this, }, baseVelocitySlider = new FormSliderBar @@ -65,6 +67,7 @@ namespace osu.Game.Rulesets.Taiko.Edit.Setup MaxValue = 3.6, Precision = 0.01f, }, + TransferValueOnCommit = true, TabbableContentContainer = this, }, tickRateSlider = new FormSliderBar @@ -78,6 +81,7 @@ namespace osu.Game.Rulesets.Taiko.Edit.Setup MaxValue = 4, Precision = 1, }, + TransferValueOnCommit = true, TabbableContentContainer = this, }, }; diff --git a/osu.Game/Screens/Edit/Setup/DifficultySection.cs b/osu.Game/Screens/Edit/Setup/DifficultySection.cs index a27a7258c7..88241451cf 100644 --- a/osu.Game/Screens/Edit/Setup/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Setup/DifficultySection.cs @@ -40,6 +40,7 @@ namespace osu.Game.Screens.Edit.Setup MaxValue = 10, Precision = 0.1f, }, + TransferValueOnCommit = true, TabbableContentContainer = this, }, healthDrainSlider = new FormSliderBar @@ -53,6 +54,7 @@ namespace osu.Game.Screens.Edit.Setup MaxValue = 10, Precision = 0.1f, }, + TransferValueOnCommit = true, TabbableContentContainer = this, }, approachRateSlider = new FormSliderBar @@ -66,6 +68,7 @@ namespace osu.Game.Screens.Edit.Setup MaxValue = 10, Precision = 0.1f, }, + TransferValueOnCommit = true, TabbableContentContainer = this, }, overallDifficultySlider = new FormSliderBar @@ -79,6 +82,7 @@ namespace osu.Game.Screens.Edit.Setup MaxValue = 10, Precision = 0.1f, }, + TransferValueOnCommit = true, TabbableContentContainer = this, }, baseVelocitySlider = new FormSliderBar @@ -92,6 +96,7 @@ namespace osu.Game.Screens.Edit.Setup MaxValue = 3.6, Precision = 0.01f, }, + TransferValueOnCommit = true, TabbableContentContainer = this, }, tickRateSlider = new FormSliderBar @@ -105,6 +110,7 @@ namespace osu.Game.Screens.Edit.Setup MaxValue = 4, Precision = 1, }, + TransferValueOnCommit = true, TabbableContentContainer = this, }, }; From 7cfc389d0391a91debe52b9a5ff01b6b3b469bce Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 4 Oct 2024 13:37:05 +0100 Subject: [PATCH 417/924] remove double-negative on `usingClassicSliderHeadAccuracy` --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index dc2113ed40..a5c2fa09c0 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty { var osuAttributes = (OsuDifficultyAttributes)attributes; - usingClassicSliderAccuracy = !(score.Mods.OfType().All(m => !m.NoSliderHeadAccuracy.Value)); + usingClassicSliderAccuracy = score.Mods.OfType().Any(m => m.NoSliderHeadAccuracy.Value); accuracy = score.Accuracy; scoreMaxCombo = score.MaxCombo; From d4a00d75e80de49a72a8fdf20e623e437f4bbeb5 Mon Sep 17 00:00:00 2001 From: StanR Date: Fri, 4 Oct 2024 17:42:15 +0500 Subject: [PATCH 418/924] Update osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs Co-authored-by: James Wilson --- osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index 08efb187fe..d10d2c5c05 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -131,7 +131,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators } // scale down the difficulty if the object is doubletappable - double doubletapness = prevObj.GetDoubletapness((OsuDifficultyHitObject?)prevObj.Next(0)); + double doubletapness = prevObj.GetDoubletapness(currObj); effectiveRatio *= 1 - doubletapness * 0.75; rhythmComplexitySum += Math.Sqrt(effectiveRatio * startRatio) * currHistoricalDecay; From 94aecae0cee7be5611fa6f4e5bae25bed55126a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 4 Oct 2024 14:48:48 +0200 Subject: [PATCH 419/924] Fix tests --- .../OsuDifficultyCalculatorTest.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index e35cf10d95..17b51085da 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -15,22 +15,22 @@ namespace osu.Game.Rulesets.Osu.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Osu.Tests"; - [TestCase(6.710442985146793d, 239, "diffcalc-test")] - [TestCase(1.4386882251130073d, 54, "zero-length-sliders")] - [TestCase(0.42506480230838789d, 4, "very-fast-slider")] - [TestCase(0.14102693012101306d, 2, "nan-slider")] + [TestCase(6.7154251995274938d, 239, "diffcalc-test")] + [TestCase(1.4430610657612626d, 54, "zero-length-sliders")] + [TestCase(0.42630400627180914d, 4, "very-fast-slider")] + [TestCase(0.14143808967817237d, 2, "nan-slider")] public void Test(double expectedStarRating, int expectedMaxCombo, string name) => base.Test(expectedStarRating, expectedMaxCombo, name); - [TestCase(8.9742952703071666d, 239, "diffcalc-test")] - [TestCase(1.743180218215227d, 54, "zero-length-sliders")] - [TestCase(0.55071082800473514d, 4, "very-fast-slider")] + [TestCase(8.9808183779700208d, 239, "diffcalc-test")] + [TestCase(1.7483507893412422d, 54, "zero-length-sliders")] + [TestCase(0.55231632896800109d, 4, "very-fast-slider")] public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name) => Test(expectedStarRating, expectedMaxCombo, name, new OsuModDoubleTime()); - [TestCase(6.710442985146793d, 239, "diffcalc-test")] - [TestCase(1.4386882251130073d, 54, "zero-length-sliders")] - [TestCase(0.42506480230838789d, 4, "very-fast-slider")] + [TestCase(6.7154251995274938d, 239, "diffcalc-test")] + [TestCase(1.4430610657612626d, 54, "zero-length-sliders")] + [TestCase(0.42630400627180914d, 4, "very-fast-slider")] public void TestClassicMod(double expectedStarRating, int expectedMaxCombo, string name) => Test(expectedStarRating, expectedMaxCombo, name, new OsuModClassic()); From d7ba4ce7f2be89c74b5dc13a07ab094f1cc5408f Mon Sep 17 00:00:00 2001 From: CloneWith Date: Fri, 4 Oct 2024 23:30:50 +0800 Subject: [PATCH 420/924] Refactor progress tooltip updating method * Rewrite calculating logic * Remove useless variables --- .../Screens/Play/HUD/ArgonSongProgressBar.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs index a33c965417..4f0f94b606 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs @@ -37,9 +37,7 @@ namespace osu.Game.Screens.Play.HUD private float relativePositionX; - public LocalisableString TooltipText => $"{formatTime(TimeSpan.FromSeconds(relativePositionX > 0 ? Math.Round((EndTime - StartTime) * relativePositionX / DrawWidth / 1000) - : relativePositionX > DrawWidth ? Math.Round(EndTime / 1000) : 0))}" - + $" - {(relativePositionX > 0 ? Math.Round(relativePositionX / DrawWidth * 100, 1) : relativePositionX > DrawWidth ? 100 : 0)}%"; + public LocalisableString TooltipText => updateTooltip(); public ArgonSongProgressBar(float barHeight) { @@ -84,6 +82,19 @@ namespace osu.Game.Screens.Play.HUD catchUpColour = colours.BlueDark; } + private LocalisableString updateTooltip() + { + // clamping in case the cursor lays out of the progress bar horizontally + double progress = Math.Clamp(relativePositionX, 0, DrawWidth) / DrawWidth; + + TimeSpan currentSpan = TimeSpan.FromMilliseconds(Math.Round((EndTime - StartTime) * progress)); + int currentSeconds = currentSpan.Duration().Seconds; + // merging hours and minutes, e.g. 1:15:55 -> 75:55 + int currentMinutes = (int)Math.Floor(currentSpan.Duration().TotalMinutes); + + return $"{currentMinutes}:{currentSeconds:D2} - {progress:P1}"; + } + protected override void LoadComplete() { base.LoadComplete(); @@ -191,7 +202,5 @@ namespace osu.Game.Screens.Play.HUD set => fill.Colour = value; } } - - private string formatTime(TimeSpan timeSpan) => $"{(timeSpan < TimeSpan.Zero ? "-" : "")}{Math.Floor(timeSpan.Duration().TotalMinutes)}:{timeSpan.Duration().Seconds:D2}"; } } From ad734b1a136b72d6304a00aca8aed4c090acbddd Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 5 Oct 2024 15:10:36 +0200 Subject: [PATCH 421/924] Revert "Use new keyword instead of overriding TooltipText to remove setter" This reverts commit 80dffa905a2596ba43805f3653d91b4eeed6ff1f. --- osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs | 2 +- osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs | 2 +- osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs index 5875874cf6..9b57ebb200 100644 --- a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs +++ b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs @@ -109,6 +109,6 @@ namespace osu.Game.Graphics.UserInterfaceV2 public bool FilteringActive { get; set; } - public LocalisableString TooltipText { get; set; } + public virtual LocalisableString TooltipText { get; set; } } } diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs index 9d6d617082..eab394c8f6 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs @@ -31,7 +31,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons private readonly IBindable localUser = new Bindable(); - public new LocalisableString TooltipText + public override LocalisableString TooltipText { get { diff --git a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs index 95fb48e3ce..56e2719e9c 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.OnlinePlay.Components private void updateState() => base.Enabled.Value = availability.Value.State == DownloadState.LocallyAvailable && Enabled.Value; - public new virtual LocalisableString TooltipText + public override LocalisableString TooltipText { get { From cc29e8c16d2580108df16b7c63685c12e85b81db Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 5 Oct 2024 15:16:29 +0200 Subject: [PATCH 422/924] introduce tooltip to rounded button with subclass instead --- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 9 ++++++++- osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs | 5 +---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 089a8a84cd..4ac8d55ec3 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -7,10 +7,12 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; 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.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; @@ -150,7 +152,7 @@ namespace osu.Game.Rulesets.Osu.Edit Spacing = new Vector2(0f, 10f), Children = new Drawable[] { - gridFromPointsButton = new RoundedButton + gridFromPointsButton = new TooltipRoundedButton { Action = () => GridFromPointsClicked?.Invoke(), RelativeSizeAxes = Axes.X, @@ -311,6 +313,11 @@ namespace osu.Game.Rulesets.Osu.Edit } } + public partial class TooltipRoundedButton : RoundedButton, IHasTooltip + { + public virtual LocalisableString TooltipText { get; set; } + } + public enum PositionSnapGridType { Square, diff --git a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs index 9b57ebb200..6aded3fe32 100644 --- a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs +++ b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs @@ -8,7 +8,6 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Graphics.Backgrounds; @@ -18,7 +17,7 @@ using osuTK.Graphics; namespace osu.Game.Graphics.UserInterfaceV2 { - public partial class RoundedButton : OsuButton, IFilterable, IHasTooltip + public partial class RoundedButton : OsuButton, IFilterable { protected TrianglesV2? Triangles { get; private set; } @@ -108,7 +107,5 @@ namespace osu.Game.Graphics.UserInterfaceV2 } public bool FilteringActive { get; set; } - - public virtual LocalisableString TooltipText { get; set; } } } From 232381c9fbfa2b0a00c8840f703634e982f3ba54 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 5 Oct 2024 23:00:04 +0900 Subject: [PATCH 423/924] Rollback iOS workload to last known working version --- .github/workflows/ci.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6fbb74dfba..fc6e231c4b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -133,10 +133,7 @@ jobs: dotnet-version: "8.0.x" - name: Install .NET Workloads - run: dotnet workload install maui-ios - - - name: Select Xcode 16 - run: sudo xcode-select -s /Applications/Xcode_16.app/Contents/Developer + run: dotnet workload install ios --from-rollback-file https://raw.githubusercontent.com/ppy/osu-framework/refs/heads/master/workloads.json - name: Build run: dotnet build -c Debug osu.iOS From 48da758c3913f69bcc3cd8cb3096f5fe3a6d5406 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 5 Oct 2024 20:52:45 +0200 Subject: [PATCH 424/924] contract sample point pieces based on smallest gap on the timeline It finds the smallest distance between two sample point pieces on the alive timeline blueprints --- .../Components/Timeline/SamplePointPiece.cs | 20 ++++----- .../Timeline/TimelineBlueprintContainer.cs | 41 +++++++++++++++++++ 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 1076255b6a..ded7c66058 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private Editor? editor { get; set; } [Resolved] - private Timeline? timeline { get; set; } + private TimelineBlueprintContainer? timelineBlueprintContainer { get; set; } private Bindable samplesVisible = null!; @@ -72,9 +72,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline samplesVisible = config.GetBindable(OsuSetting.EditorTimelineShowSamples); } - private BindableNumber? timelineZoom; - - private bool contracted; + private readonly Bindable contracted = new Bindable(); protected override void LoadComplete() { @@ -83,21 +81,19 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline samplesVisible.BindValueChanged(visible => this.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint)); this.FadeTo(samplesVisible.Value ? 1 : 0); - timelineZoom = timeline?.CurrentZoom.GetBoundCopy(); - timelineZoom?.BindValueChanged(zoom => - { - const float zoom_threshold = 40f; + if (timelineBlueprintContainer != null) + contracted.BindTo(timelineBlueprintContainer.SamplePointContracted); - if (zoom.NewValue < zoom_threshold) + contracted.BindValueChanged(v => + { + if (v.NewValue) { - contracted = true; Label.FadeOut(200, Easing.OutQuint); LabelContainer.ResizeTo(new Vector2(12), 200, Easing.OutQuint); LabelContainer.CornerRadius = 6; } else { - contracted = false; Label.FadeIn(200, Easing.OutQuint); LabelContainer.ResizeTo(new Vector2(Label.Width, 16), 200, Easing.OutQuint); LabelContainer.CornerRadius = 8; @@ -131,7 +127,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { Label.Text = $"{abbreviateBank(GetBankValue(GetSamples()))} {GetVolumeValue(GetSamples())}"; - if (!contracted) + if (!contracted.Value) LabelContainer.ResizeWidthTo(Label.Width, 200, Easing.OutQuint); } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 740f0b6aac..b50739c80f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -15,16 +15,19 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Layout; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; using osuTK; using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { + [Cached] internal partial class TimelineBlueprintContainer : EditorBlueprintContainer { [Resolved(CanBeNull = true)] @@ -35,6 +38,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private bool hitObjectDragged; + private readonly LayoutValue samplePointContractedStateCache = new LayoutValue(Invalidation.DrawSize); + /// /// Positional input must be received outside the container's bounds, /// in order to handle timeline blueprints which are stacked offscreen. @@ -49,6 +54,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Origin = Anchor.Centre; Height = 0.6f; + + AddLayout(samplePointContractedStateCache); } [BackgroundDependencyLoader] @@ -116,11 +123,43 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Composer.Playfield.FutureLifetimeExtension = timeline.VisibleRange / 2; } + updateSamplePointContractedState(); + base.Update(); updateStacking(); } + public Bindable SamplePointContracted = new Bindable(); + + private void updateSamplePointContractedState() + { + if (samplePointContractedStateCache.IsValid) + return; + + const double minimum_gap = 28; + + // Find the smallest time gap between any two sample point pieces + double smallestTimeGap = double.PositiveInfinity; + double lastTime = double.PositiveInfinity; + + // The blueprints are ordered in reverse chronological order + foreach (var selectionBlueprint in SelectionBlueprints) + { + var hitObject = selectionBlueprint.Item; + + if (hitObject is IHasRepeats hasRepeats) + smallestTimeGap = Math.Min(smallestTimeGap, hasRepeats.Duration / hasRepeats.SpanCount() / 2); + + smallestTimeGap = Math.Min(smallestTimeGap, lastTime - hitObject.GetEndTime()); + lastTime = hitObject.StartTime; + } + + double smallestAbsoluteGap = ((TimelineSelectionBlueprintContainer)SelectionBlueprints).ContentRelativeToAbsoluteFactor.X * smallestTimeGap; + SamplePointContracted.Value = smallestAbsoluteGap < minimum_gap; + samplePointContractedStateCache.Validate(); + } + private readonly Stack currentConcurrentObjects = new Stack(); private void updateStacking() @@ -288,6 +327,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { protected override Container> Content { get; } + public Vector2 ContentRelativeToAbsoluteFactor => Content.RelativeToAbsoluteFactor; + public TimelineSelectionBlueprintContainer() { AddInternal(new TimelinePart>(Content = new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both }) { RelativeSizeAxes = Axes.Both }); From ae8abc7f3576309bd68076c23d7358b02a94637d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 5 Oct 2024 21:46:00 +0200 Subject: [PATCH 425/924] fix readybutton and favouritebutton --- osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs | 5 +++-- osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs index eab394c8f6..cbdb2ea190 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; @@ -20,7 +21,7 @@ using APIUser = osu.Game.Online.API.Requests.Responses.APIUser; namespace osu.Game.Overlays.BeatmapSet.Buttons { - public partial class FavouriteButton : HeaderButton + public partial class FavouriteButton : HeaderButton, IHasTooltip { public readonly Bindable BeatmapSet = new Bindable(); @@ -31,7 +32,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons private readonly IBindable localUser = new Bindable(); - public override LocalisableString TooltipText + public LocalisableString TooltipText { get { diff --git a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs index 56e2719e9c..20203fedac 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics.Cursor; using osu.Framework.Localisation; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Online; @@ -10,7 +11,7 @@ using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Components { - public abstract partial class ReadyButton : RoundedButton + public abstract partial class ReadyButton : RoundedButton, IHasTooltip { public new readonly BindableBool Enabled = new BindableBool(); @@ -28,7 +29,7 @@ namespace osu.Game.Screens.OnlinePlay.Components private void updateState() => base.Enabled.Value = availability.Value.State == DownloadState.LocallyAvailable && Enabled.Value; - public override LocalisableString TooltipText + public LocalisableString TooltipText { get { From 72ac2eeb1da5fcb12147c8f5038a2fe8f94d9db4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 6 Oct 2024 07:54:56 +0900 Subject: [PATCH 426/924] Update osu!framework package --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 6b42258b49..d1f91abd1a 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index 8acd1deff1..8e2c3b4bd0 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + From 2bcbaed5b8e17efee1f095116322362a7f0440cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Oct 2024 14:11:31 +0900 Subject: [PATCH 427/924] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 6b42258b49..f943ee727c 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index 8acd1deff1..51b7bfbf91 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + From b5cc45bdda059665d8c0a5c9c791342e57f321cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Oct 2024 14:26:42 +0900 Subject: [PATCH 428/924] Simplify format code (and adjust formatting slightly) --- .../Screens/Play/HUD/ArgonSongProgressBar.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs index 4f0f94b606..7d9cd25c4e 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs @@ -37,7 +37,20 @@ namespace osu.Game.Screens.Play.HUD private float relativePositionX; - public LocalisableString TooltipText => updateTooltip(); + public LocalisableString TooltipText + { + get + { + double progress = Math.Clamp(relativePositionX, 0, DrawWidth) / DrawWidth; + + TimeSpan currentSpan = TimeSpan.FromMilliseconds(Math.Round((EndTime - StartTime) * progress)); + + int seconds = currentSpan.Duration().Seconds; + int minutes = (int)Math.Floor(currentSpan.Duration().TotalMinutes); + + return $"{minutes}:{seconds:D2} ({progress:P0})"; + } + } public ArgonSongProgressBar(float barHeight) { @@ -82,19 +95,6 @@ namespace osu.Game.Screens.Play.HUD catchUpColour = colours.BlueDark; } - private LocalisableString updateTooltip() - { - // clamping in case the cursor lays out of the progress bar horizontally - double progress = Math.Clamp(relativePositionX, 0, DrawWidth) / DrawWidth; - - TimeSpan currentSpan = TimeSpan.FromMilliseconds(Math.Round((EndTime - StartTime) * progress)); - int currentSeconds = currentSpan.Duration().Seconds; - // merging hours and minutes, e.g. 1:15:55 -> 75:55 - int currentMinutes = (int)Math.Floor(currentSpan.Duration().TotalMinutes); - - return $"{currentMinutes}:{currentSeconds:D2} - {progress:P1}"; - } - protected override void LoadComplete() { base.LoadComplete(); From 6e4eed657ccf520e110f64c9aa4772a647a68b34 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Oct 2024 14:32:31 +0900 Subject: [PATCH 429/924] Fix weird mouse position handling and don't return `true` to event --- osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs index 7d9cd25c4e..ace21fa955 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs @@ -35,13 +35,13 @@ namespace osu.Game.Screens.Play.HUD private double trackTime => (EndTime - StartTime) * Progress; - private float relativePositionX; + private float lastMouseX; public LocalisableString TooltipText { get { - double progress = Math.Clamp(relativePositionX, 0, DrawWidth) / DrawWidth; + double progress = Math.Clamp(lastMouseX, 0, DrawWidth) / DrawWidth; TimeSpan currentSpan = TimeSpan.FromMilliseconds(Math.Round((EndTime - StartTime) * progress)); @@ -107,10 +107,8 @@ namespace osu.Game.Screens.Play.HUD { base.OnMouseMove(e); - var cursorPosition = e.ScreenSpaceMousePosition; - relativePositionX = ToLocalSpace(cursorPosition).X; - - return true; + lastMouseX = e.MousePosition.X; + return false; } protected override bool OnHover(HoverEvent e) From c7f2564c0ace0f0a7e184d1dfcba232ef8f357c8 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 7 Oct 2024 15:50:59 +0900 Subject: [PATCH 430/924] Make diffcalc workflow recreate comment on completion --- .github/workflows/diffcalc.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml index 9f129a697c..7fb0709dec 100644 --- a/.github/workflows/diffcalc.yml +++ b/.github/workflows/diffcalc.yml @@ -361,8 +361,7 @@ jobs: uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0 with: comment_tag: ${{ env.EXECUTION_ID }} - mode: upsert - create_if_not_exists: false + mode: recreate message: | Target: ${{ needs.generator.outputs.TARGET }} Spreadsheet: ${{ needs.generator.outputs.SPREADSHEET_LINK }} @@ -372,8 +371,7 @@ jobs: uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0 with: comment_tag: ${{ env.EXECUTION_ID }} - mode: upsert - create_if_not_exists: false + mode: recreate message: | Difficulty calculation failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} From 75f15ccabad3d5680dd82c4e2910524353512631 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Oct 2024 16:04:24 +0900 Subject: [PATCH 431/924] Maybe fix compilation? Fuck knows if this is correct. --- osu.Game/Overlays/Settings/SettingsButton.cs | 2 ++ osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/SettingsButton.cs b/osu.Game/Overlays/Settings/SettingsButton.cs index 196ddca953..52ea8caed9 100644 --- a/osu.Game/Overlays/Settings/SettingsButton.cs +++ b/osu.Game/Overlays/Settings/SettingsButton.cs @@ -24,6 +24,8 @@ namespace osu.Game.Overlays.Settings public BindableBool CanBeShown { get; } = new BindableBool(true); IBindable IConditionalFilterable.CanBeShown => CanBeShown; + public virtual LocalisableString TooltipText { get; set; } + public override IEnumerable FilterTerms { get diff --git a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs index 20203fedac..2e669fd1b2 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.OnlinePlay.Components private void updateState() => base.Enabled.Value = availability.Value.State == DownloadState.LocallyAvailable && Enabled.Value; - public LocalisableString TooltipText + public virtual LocalisableString TooltipText { get { From 9508b0ecab62a5e93b41b3b240e5617a6b82dc98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 7 Oct 2024 09:32:23 +0200 Subject: [PATCH 432/924] Fix tests --- .../OsuDifficultyCalculatorTest.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index 17b51085da..efda3fa369 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -15,21 +15,21 @@ namespace osu.Game.Rulesets.Osu.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Osu.Tests"; - [TestCase(6.7154251995274938d, 239, "diffcalc-test")] - [TestCase(1.4430610657612626d, 54, "zero-length-sliders")] + [TestCase(6.7171144000821119d, 239, "diffcalc-test")] + [TestCase(1.4485749025771304d, 54, "zero-length-sliders")] [TestCase(0.42630400627180914d, 4, "very-fast-slider")] [TestCase(0.14143808967817237d, 2, "nan-slider")] public void Test(double expectedStarRating, int expectedMaxCombo, string name) => base.Test(expectedStarRating, expectedMaxCombo, name); - [TestCase(8.9808183779700208d, 239, "diffcalc-test")] - [TestCase(1.7483507893412422d, 54, "zero-length-sliders")] + [TestCase(8.9825709931204205d, 239, "diffcalc-test")] + [TestCase(1.7550169162648608d, 54, "zero-length-sliders")] [TestCase(0.55231632896800109d, 4, "very-fast-slider")] public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name) => Test(expectedStarRating, expectedMaxCombo, name, new OsuModDoubleTime()); - [TestCase(6.7154251995274938d, 239, "diffcalc-test")] - [TestCase(1.4430610657612626d, 54, "zero-length-sliders")] + [TestCase(6.7171144000821119d, 239, "diffcalc-test")] + [TestCase(1.4485749025771304d, 54, "zero-length-sliders")] [TestCase(0.42630400627180914d, 4, "very-fast-slider")] public void TestClassicMod(double expectedStarRating, int expectedMaxCombo, string name) => Test(expectedStarRating, expectedMaxCombo, name, new OsuModClassic()); From a41c6dce04f7185ea49d89801491a913a888fb51 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Oct 2024 16:43:50 +0900 Subject: [PATCH 433/924] Fix android build failure due to enum rename --- osu.Android/GameplayScreenRotationLocker.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android/GameplayScreenRotationLocker.cs b/osu.Android/GameplayScreenRotationLocker.cs index ffd4218ea9..26234545ef 100644 --- a/osu.Android/GameplayScreenRotationLocker.cs +++ b/osu.Android/GameplayScreenRotationLocker.cs @@ -12,7 +12,7 @@ namespace osu.Android { public partial class GameplayScreenRotationLocker : Component { - private IBindable localUserPlaying = null!; + private IBindable localUserPlaying = null!; [Resolved] private OsuGameActivity gameActivity { get; set; } = null!; @@ -24,11 +24,11 @@ namespace osu.Android localUserPlaying.BindValueChanged(updateLock, true); } - private void updateLock(ValueChangedEvent userPlaying) + private void updateLock(ValueChangedEvent userPlaying) { gameActivity.RunOnUiThread(() => { - gameActivity.RequestedOrientation = userPlaying.NewValue != LocalUserPlayingStates.NotPlaying ? ScreenOrientation.Locked : gameActivity.DefaultOrientation; + gameActivity.RequestedOrientation = userPlaying.NewValue != LocalUserPlayingState.NotPlaying ? ScreenOrientation.Locked : gameActivity.DefaultOrientation; }); } } From 11fc811e2fed2f2f9ebf9eb7114e7552f9d7cfd5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Oct 2024 16:44:10 +0900 Subject: [PATCH 434/924] Fix delete dialogs having generic "Caution" header text Regressed in https://github.com/ppy/osu/pull/28363. --- .../Editors/Components/DeleteRoundDialog.cs | 4 +--- .../Editors/Components/DeleteTeamDialog.cs | 4 +--- .../Collections/DeleteCollectionDialog.cs | 2 +- osu.Game/Localisation/DialogStrings.cs | 7 ++++++- osu.Game/Online/Chat/ExternalLinkOpener.cs | 2 +- .../Overlays/Dialog/DangerousActionDialog.cs | 4 ++-- osu.Game/Overlays/Dialog/DeletionDialog.cs | 20 +++++++++++++++++++ .../Overlays/Mods/DeleteModPresetDialog.cs | 2 +- .../MassDeleteConfirmationDialog.cs | 2 ++ .../DeleteDifficultyConfirmationDialog.cs | 2 +- .../Screens/Select/BeatmapDeleteDialog.cs | 2 +- .../Screens/Select/LocalScoreDeleteDialog.cs | 5 +---- osu.Game/Screens/Select/SkinDeleteDialog.cs | 2 +- 13 files changed, 39 insertions(+), 19 deletions(-) create mode 100644 osu.Game/Overlays/Dialog/DeletionDialog.cs diff --git a/osu.Game.Tournament/Screens/Editors/Components/DeleteRoundDialog.cs b/osu.Game.Tournament/Screens/Editors/Components/DeleteRoundDialog.cs index 769412bf94..6fff5111bd 100644 --- a/osu.Game.Tournament/Screens/Editors/Components/DeleteRoundDialog.cs +++ b/osu.Game.Tournament/Screens/Editors/Components/DeleteRoundDialog.cs @@ -2,18 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Dialog; using osu.Game.Tournament.Models; namespace osu.Game.Tournament.Screens.Editors.Components { - public partial class DeleteRoundDialog : DangerousActionDialog + public partial class DeleteRoundDialog : DeletionDialog { public DeleteRoundDialog(TournamentRound round, Action action) { HeaderText = round.Name.Value.Length > 0 ? $@"Delete round ""{round.Name.Value}""?" : @"Delete unnamed round?"; - Icon = FontAwesome.Solid.Trash; DangerousAction = action; } } diff --git a/osu.Game.Tournament/Screens/Editors/Components/DeleteTeamDialog.cs b/osu.Game.Tournament/Screens/Editors/Components/DeleteTeamDialog.cs index 65fb47cf94..cf1dffba0c 100644 --- a/osu.Game.Tournament/Screens/Editors/Components/DeleteTeamDialog.cs +++ b/osu.Game.Tournament/Screens/Editors/Components/DeleteTeamDialog.cs @@ -2,20 +2,18 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Dialog; using osu.Game.Tournament.Models; namespace osu.Game.Tournament.Screens.Editors.Components { - public partial class DeleteTeamDialog : DangerousActionDialog + public partial class DeleteTeamDialog : DeletionDialog { public DeleteTeamDialog(TournamentTeam team, Action action) { HeaderText = team.FullName.Value.Length > 0 ? $@"Delete team ""{team.FullName.Value}""?" : team.Acronym.Value.Length > 0 ? $@"Delete team ""{team.Acronym.Value}""?" : @"Delete unnamed team?"; - Icon = FontAwesome.Solid.Trash; DangerousAction = action; } } diff --git a/osu.Game/Collections/DeleteCollectionDialog.cs b/osu.Game/Collections/DeleteCollectionDialog.cs index 9edc213077..80a7fa4bc0 100644 --- a/osu.Game/Collections/DeleteCollectionDialog.cs +++ b/osu.Game/Collections/DeleteCollectionDialog.cs @@ -8,7 +8,7 @@ using osu.Game.Overlays.Dialog; namespace osu.Game.Collections { - public partial class DeleteCollectionDialog : DangerousActionDialog + public partial class DeleteCollectionDialog : DeletionDialog { public DeleteCollectionDialog(Live collection, Action deleteAction) { diff --git a/osu.Game/Localisation/DialogStrings.cs b/osu.Game/Localisation/DialogStrings.cs index 043a3f5b4c..a7634575b8 100644 --- a/osu.Game/Localisation/DialogStrings.cs +++ b/osu.Game/Localisation/DialogStrings.cs @@ -12,7 +12,12 @@ namespace osu.Game.Localisation /// /// "Caution" /// - public static LocalisableString Caution => new TranslatableString(getKey(@"header_text"), @"Caution"); + public static LocalisableString CautionHeaderText => new TranslatableString(getKey(@"header_text"), @"Caution"); + + /// + /// "Are you sure you want to delete the following:" + /// + public static LocalisableString DeletionHeaderText => new TranslatableString(getKey(@"deletion_header_text"), @"Are you sure you want to delete the following:"); /// /// "Yes. Go for it." diff --git a/osu.Game/Online/Chat/ExternalLinkOpener.cs b/osu.Game/Online/Chat/ExternalLinkOpener.cs index 90fec5fafd..1c48a4fe6d 100644 --- a/osu.Game/Online/Chat/ExternalLinkOpener.cs +++ b/osu.Game/Online/Chat/ExternalLinkOpener.cs @@ -46,7 +46,7 @@ namespace osu.Game.Online.Chat { public ExternalLinkDialog(string url, Action openExternalLinkAction, Action copyExternalLinkAction) { - HeaderText = DialogStrings.Caution; + HeaderText = DialogStrings.CautionHeaderText; BodyText = $"Are you sure you want to open the following link in a web browser?\n\n{url}"; Icon = FontAwesome.Solid.ExclamationTriangle; diff --git a/osu.Game/Overlays/Dialog/DangerousActionDialog.cs b/osu.Game/Overlays/Dialog/DangerousActionDialog.cs index 31160d1832..287b0fa2c6 100644 --- a/osu.Game/Overlays/Dialog/DangerousActionDialog.cs +++ b/osu.Game/Overlays/Dialog/DangerousActionDialog.cs @@ -30,9 +30,9 @@ namespace osu.Game.Overlays.Dialog protected DangerousActionDialog() { - HeaderText = DialogStrings.Caution; + HeaderText = DialogStrings.CautionHeaderText; - Icon = FontAwesome.Regular.TrashAlt; + Icon = FontAwesome.Solid.ExclamationTriangle; Buttons = new PopupDialogButton[] { diff --git a/osu.Game/Overlays/Dialog/DeletionDialog.cs b/osu.Game/Overlays/Dialog/DeletionDialog.cs new file mode 100644 index 0000000000..26a29068a9 --- /dev/null +++ b/osu.Game/Overlays/Dialog/DeletionDialog.cs @@ -0,0 +1,20 @@ +// 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.Sprites; +using osu.Game.Localisation; + +namespace osu.Game.Overlays.Dialog +{ + /// + /// A dialog which provides confirmation for deletion of something. + /// + public abstract partial class DeletionDialog : DangerousActionDialog + { + protected DeletionDialog() + { + HeaderText = DialogStrings.DeletionHeaderText; + Icon = FontAwesome.Solid.Trash; + } + } +} diff --git a/osu.Game/Overlays/Mods/DeleteModPresetDialog.cs b/osu.Game/Overlays/Mods/DeleteModPresetDialog.cs index 9788764453..5651ecb34c 100644 --- a/osu.Game/Overlays/Mods/DeleteModPresetDialog.cs +++ b/osu.Game/Overlays/Mods/DeleteModPresetDialog.cs @@ -7,7 +7,7 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Overlays.Mods { - public partial class DeleteModPresetDialog : DangerousActionDialog + public partial class DeleteModPresetDialog : DeletionDialog { public DeleteModPresetDialog(Live modPreset) { diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MassDeleteConfirmationDialog.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MassDeleteConfirmationDialog.cs index 7ead815fe9..a7a7ee2590 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MassDeleteConfirmationDialog.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MassDeleteConfirmationDialog.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Overlays.Dialog; @@ -12,6 +13,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance public MassDeleteConfirmationDialog(Action deleteAction, LocalisableString deleteContent) { BodyText = deleteContent; + Icon = FontAwesome.Solid.Trash; DangerousAction = deleteAction; } } diff --git a/osu.Game/Screens/Edit/DeleteDifficultyConfirmationDialog.cs b/osu.Game/Screens/Edit/DeleteDifficultyConfirmationDialog.cs index 8556949528..1aeb1d8a40 100644 --- a/osu.Game/Screens/Edit/DeleteDifficultyConfirmationDialog.cs +++ b/osu.Game/Screens/Edit/DeleteDifficultyConfirmationDialog.cs @@ -7,7 +7,7 @@ using osu.Game.Overlays.Dialog; namespace osu.Game.Screens.Edit { - public partial class DeleteDifficultyConfirmationDialog : DangerousActionDialog + public partial class DeleteDifficultyConfirmationDialog : DeletionDialog { public DeleteDifficultyConfirmationDialog(BeatmapInfo beatmapInfo, Action deleteAction) { diff --git a/osu.Game/Screens/Select/BeatmapDeleteDialog.cs b/osu.Game/Screens/Select/BeatmapDeleteDialog.cs index e98af8cca2..8bc40dbd9e 100644 --- a/osu.Game/Screens/Select/BeatmapDeleteDialog.cs +++ b/osu.Game/Screens/Select/BeatmapDeleteDialog.cs @@ -7,7 +7,7 @@ using osu.Game.Overlays.Dialog; namespace osu.Game.Screens.Select { - public partial class BeatmapDeleteDialog : DangerousActionDialog + public partial class BeatmapDeleteDialog : DeletionDialog { private readonly BeatmapSetInfo beatmapSet; diff --git a/osu.Game/Screens/Select/LocalScoreDeleteDialog.cs b/osu.Game/Screens/Select/LocalScoreDeleteDialog.cs index cd98872b65..ec2b8437e1 100644 --- a/osu.Game/Screens/Select/LocalScoreDeleteDialog.cs +++ b/osu.Game/Screens/Select/LocalScoreDeleteDialog.cs @@ -4,11 +4,10 @@ using osu.Framework.Allocation; using osu.Game.Overlays.Dialog; using osu.Game.Scoring; -using osu.Framework.Graphics.Sprites; namespace osu.Game.Screens.Select { - public partial class LocalScoreDeleteDialog : DangerousActionDialog + public partial class LocalScoreDeleteDialog : DeletionDialog { private readonly ScoreInfo score; @@ -21,8 +20,6 @@ namespace osu.Game.Screens.Select private void load(ScoreManager scoreManager) { BodyText = $"{score.User} ({score.DisplayAccuracy}, {score.Rank})"; - - Icon = FontAwesome.Regular.TrashAlt; DangerousAction = () => scoreManager.Delete(score); } } diff --git a/osu.Game/Screens/Select/SkinDeleteDialog.cs b/osu.Game/Screens/Select/SkinDeleteDialog.cs index 6612ae837a..cd14b5b6d2 100644 --- a/osu.Game/Screens/Select/SkinDeleteDialog.cs +++ b/osu.Game/Screens/Select/SkinDeleteDialog.cs @@ -7,7 +7,7 @@ using osu.Game.Overlays.Dialog; namespace osu.Game.Screens.Select { - public partial class SkinDeleteDialog : DangerousActionDialog + public partial class SkinDeleteDialog : DeletionDialog { private readonly Skin skin; From 1f45b2134f9b579374119ff04b04921bb1592335 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Oct 2024 17:04:52 +0900 Subject: [PATCH 435/924] Remove unnecessary `runOutsideOfGameplay` call --- osu.Desktop/Updater/VelopackUpdateManager.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Desktop/Updater/VelopackUpdateManager.cs b/osu.Desktop/Updater/VelopackUpdateManager.cs index 5dda03a3d3..5a02e95e1f 100644 --- a/osu.Desktop/Updater/VelopackUpdateManager.cs +++ b/osu.Desktop/Updater/VelopackUpdateManager.cs @@ -63,17 +63,14 @@ namespace osu.Desktop.Updater // Velopack does support this scenario (see https://github.com/ppy/osu/pull/28743#discussion_r1743495975). if (pendingUpdate != null) { - runOutsideOfGameplay(() => + // If there is an update pending restart, show the notification to restart again. + notificationOverlay.Post(new UpdateApplicationCompleteNotification { - // If there is an update pending restart, show the notification to restart again. - notificationOverlay.Post(new UpdateApplicationCompleteNotification + Activated = () => { - Activated = () => - { - Task.Run(restartToApplyUpdate); - return true; - } - }); + Task.Run(restartToApplyUpdate); + return true; + } }); return true; From 38ee824b124b03d09dc658da9b556e7ba9a95aa9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Oct 2024 17:07:25 +0900 Subject: [PATCH 436/924] Add second call of `runOutsideGameplay` on update progress notification At this point the update is already started in the background but I guess we can still block the notification from interrupting the user. --- osu.Desktop/Updater/VelopackUpdateManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/Updater/VelopackUpdateManager.cs b/osu.Desktop/Updater/VelopackUpdateManager.cs index 5a02e95e1f..46c5793fc8 100644 --- a/osu.Desktop/Updater/VelopackUpdateManager.cs +++ b/osu.Desktop/Updater/VelopackUpdateManager.cs @@ -97,7 +97,7 @@ namespace osu.Desktop.Updater }, }; - Schedule(() => notificationOverlay.Post(notification)); + runOutsideOfGameplay(() => notificationOverlay.Post(notification)); } notification.StartDownload(); From 3da59f44b59aa7a098d72769bfca46ff737ae842 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 7 Oct 2024 10:28:44 +0200 Subject: [PATCH 437/924] Fix clicking "centre on selected object" button not updating slider state --- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 4fa8852770..a8331ab4aa 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -188,6 +188,12 @@ namespace osu.Game.Rulesets.Osu.Edit StartPosition.Value = new Vector2(StartPosition.Value.X, y.NewValue); }, true); + StartPosition.BindValueChanged(pos => + { + StartPositionX.Value = pos.NewValue.X; + StartPositionY.Value = pos.NewValue.Y; + }); + Spacing.BindValueChanged(spacing => { spacingSlider.ContractedLabelText = $"S: {spacing.NewValue:N0}"; From 04c65ad91969e2835e4f382a026a53df07abc215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 7 Oct 2024 10:29:18 +0200 Subject: [PATCH 438/924] Fix "centre on selected object" sometimes remaining disabled after moving grid --- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index a8331ab4aa..dff2347ca5 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -192,6 +192,7 @@ namespace osu.Game.Rulesets.Osu.Edit { StartPositionX.Value = pos.NewValue.X; StartPositionY.Value = pos.NewValue.Y; + updateEnabledStates(); }); Spacing.BindValueChanged(spacing => From 2b5ddddf4f9d24b000e01d1ef7c8929beaaa3b18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 7 Oct 2024 10:29:45 +0200 Subject: [PATCH 439/924] Fix "centre on selected object" button not respecting precision of allowable grid positions --- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index dff2347ca5..972224a230 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -10,6 +11,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; @@ -123,7 +125,8 @@ namespace osu.Game.Rulesets.Osu.Edit if (editorBeatmap.SelectedHitObjects.Count != 1) return; - StartPosition.Value = ((IHasPosition)editorBeatmap.SelectedHitObjects.Single()).Position; + var position = ((IHasPosition)editorBeatmap.SelectedHitObjects.Single()).Position; + StartPosition.Value = new Vector2(MathF.Round(position.X), MathF.Round(position.Y)); updateEnabledStates(); }, RelativeSizeAxes = Axes.X, @@ -243,7 +246,7 @@ namespace osu.Game.Rulesets.Osu.Edit { useSelectedObjectPositionButton.Enabled.Value = expandingContainer?.Expanded.Value == true && editorBeatmap.SelectedHitObjects.Count == 1 - && StartPosition.Value != ((IHasPosition)editorBeatmap.SelectedHitObjects.Single()).Position; + && !Precision.AlmostEquals(StartPosition.Value, ((IHasPosition)editorBeatmap.SelectedHitObjects.Single()).Position, 0.5f); } private void nextGridSize() From 0a7d2395d2f437fb9cf9611cb4bffee114576ad1 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 7 Oct 2024 10:43:24 +0200 Subject: [PATCH 440/924] fix tooltip in SettingsButton --- osu.Game/Overlays/Settings/SettingsButton.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsButton.cs b/osu.Game/Overlays/Settings/SettingsButton.cs index 52ea8caed9..3f5d612eb8 100644 --- a/osu.Game/Overlays/Settings/SettingsButton.cs +++ b/osu.Game/Overlays/Settings/SettingsButton.cs @@ -6,12 +6,13 @@ using System.Collections.Generic; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Localisation; using osu.Game.Graphics.UserInterfaceV2; namespace osu.Game.Overlays.Settings { - public partial class SettingsButton : RoundedButton, IConditionalFilterable + public partial class SettingsButton : RoundedButton, IHasTooltip, IConditionalFilterable { public SettingsButton() { @@ -24,7 +25,7 @@ namespace osu.Game.Overlays.Settings public BindableBool CanBeShown { get; } = new BindableBool(true); IBindable IConditionalFilterable.CanBeShown => CanBeShown; - public virtual LocalisableString TooltipText { get; set; } + public LocalisableString TooltipText { get; set; } public override IEnumerable FilterTerms { From 19b586e6f7c98721d8c252b08ebb3ab3440a3536 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 7 Oct 2024 17:45:18 +0900 Subject: [PATCH 441/924] Remove unrelated test --- .../Navigation/TestSceneScreenNavigation.cs | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index d374ae1bd9..eda7ce925a 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -364,37 +364,6 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != getOriginalPlayer() && Game.ScreenStack.CurrentScreen is Player); } - [Test] - public void TestSaveFailedReplay() - { - Player player = null; - - Screens.Select.SongSelect songSelect = null; - PushAndConfirm(() => songSelect = new TestPlaySongSelect()); - AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); - - AddStep("import beatmap", () => BeatmapImportHelper.LoadOszIntoOsu(Game, virtualTrack: true).WaitSafely()); - - AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); - - AddStep("press enter", () => InputManager.Key(Key.Enter)); - - AddUntilStep("wait for player", () => - { - DismissAnyNotifications(); - return (player = Game.ScreenStack.CurrentScreen as Player) != null; - }); - - AddUntilStep("wait for fail", () => player.GameplayState.HasFailed); - - AddUntilStep("fail screen displayed", () => player.ChildrenOfType().First().State.Value == Visibility.Visible); - AddUntilStep("wait for button clickable", () => player.ChildrenOfType().First().ChildrenOfType().First().Enabled.Value); - - AddUntilStep("score not in database", () => Realm.Run(r => r.Find(player.Score.ScoreInfo.ID) == null)); - AddStep("click save button", () => player.ChildrenOfType().First().ChildrenOfType().First().TriggerClick()); - AddUntilStep("score in database", () => Realm.Run(r => r.Find(player.Score.ScoreInfo.ID) != null)); - } - [Test] public void TestExitImmediatelyAfterCompletion() { From 6e659e156e4ab28acf18aabb8d0079beb9a79121 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 7 Oct 2024 17:50:23 +0900 Subject: [PATCH 442/924] Refactoring for correctness --- osu.Game/Input/OsuUserInputManager.cs | 2 +- osu.Game/OsuGame.cs | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game/Input/OsuUserInputManager.cs b/osu.Game/Input/OsuUserInputManager.cs index 26ce2312d5..252c0eb7f8 100644 --- a/osu.Game/Input/OsuUserInputManager.cs +++ b/osu.Game/Input/OsuUserInputManager.cs @@ -12,7 +12,7 @@ namespace osu.Game.Input { protected override bool AllowRightClickFromLongTouch => PlayingState.Value == LocalUserPlayingState.NotPlaying; - public readonly Bindable PlayingState = new Bindable(); + public readonly IBindable PlayingState = new Bindable(); internal OsuUserInputManager() { diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 935631c8e9..dce24c6ee7 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1548,11 +1548,15 @@ namespace osu.Game scope.SetTag(@"screen", newScreen?.GetType().ReadableName() ?? @"none"); }); - // reset on screen change for sanity. - playingState.Value = LocalUserPlayingState.NotPlaying; + switch (current) + { + case Player player: + player.PlayingState.UnbindFrom(playingState); - if (current is Player oldPlayer) - oldPlayer.PlayingState.UnbindFrom(playingState); + // reset for sanity. + playingState.Value = LocalUserPlayingState.NotPlaying; + break; + } switch (newScreen) { From 639caf167df19a6390922e4cdd3cd4799c09d979 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 7 Oct 2024 18:27:28 +0900 Subject: [PATCH 443/924] Save master state in workflow --- .github/workflows/diffcalc.yml | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml index 7fb0709dec..ebf22b8a0e 100644 --- a/.github/workflows/diffcalc.yml +++ b/.github/workflows/diffcalc.yml @@ -104,6 +104,25 @@ env: EXECUTION_ID: execution-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} jobs: + master-environment: + name: Save master environment + runs-on: ubuntu-latest + outputs: + HEAD: ${{ steps.get-head.outputs.HEAD }} + steps: + - name: Checkout osu + uses: actions/checkout@v4 + with: + ref: master + sparse-checkout: | + README.md + + - name: Get HEAD ref + id: get-head + run: | + ref=$(git log -1 --format='%H') + echo "HEAD=https://github.com/${{ github.repository }}/commit/${ref}" >> "${GITHUB_OUTPUT}" + check-permissions: name: Check permissions runs-on: ubuntu-latest @@ -121,7 +140,7 @@ jobs: create-comment: name: Create PR comment - needs: check-permissions + needs: [ master-environment, check-permissions ] runs-on: ubuntu-latest if: ${{ github.event_name == 'issue_comment' && github.event.issue.pull_request }} steps: @@ -158,7 +177,7 @@ jobs: environment: name: Setup environment - needs: directory + needs: [ master-environment, directory ] runs-on: self-hosted env: VARS_JSON: ${{ toJSON(vars) }} @@ -182,6 +201,10 @@ jobs: fi done + - name: Add master environment + run: | + sed -i "s;^OSU_A=.*$;OSU_A=${{ needs.master-environment.outputs.HEAD }};" "${{ needs.directory.outputs.GENERATOR_ENV }}" + - name: Add pull-request environment if: ${{ github.event_name == 'issue_comment' && github.event.issue.pull_request }} run: | From aee5f0ebf532a18510b956b29be8ab92e80ea195 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 7 Oct 2024 19:01:25 +0900 Subject: [PATCH 444/924] Fix incorrect condition --- osu.Game/Input/OsuUserInputManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Input/OsuUserInputManager.cs b/osu.Game/Input/OsuUserInputManager.cs index 252c0eb7f8..2138a8b247 100644 --- a/osu.Game/Input/OsuUserInputManager.cs +++ b/osu.Game/Input/OsuUserInputManager.cs @@ -10,7 +10,7 @@ namespace osu.Game.Input { public partial class OsuUserInputManager : UserInputManager { - protected override bool AllowRightClickFromLongTouch => PlayingState.Value == LocalUserPlayingState.NotPlaying; + protected override bool AllowRightClickFromLongTouch => PlayingState.Value != LocalUserPlayingState.Playing; public readonly IBindable PlayingState = new Bindable(); From 7b998219d733b01de79013e74de8102bf26e1b6c Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 7 Oct 2024 12:04:14 +0200 Subject: [PATCH 445/924] move grid placement tool to left toolbox --- .../Edit/GridFromPointsTool.cs | 11 ++++++++ .../Edit/OsuGridToolboxGroup.cs | 25 ------------------- .../Edit/OsuHitObjectComposer.cs | 4 +-- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 8 ------ 4 files changed, 12 insertions(+), 36 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs index f357d3024f..638833f581 100644 --- a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs +++ b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs @@ -1,6 +1,9 @@ // 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.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Osu.Edit.Blueprints; @@ -12,8 +15,16 @@ namespace osu.Game.Rulesets.Osu.Edit public GridFromPointsTool() : base("Change grid") { + TooltipText = """ + Left click to set the origin. + Left click again to set the spacing and rotation. + Right click to only set the origin. + Click and drag to set the origin, spacing and rotation. + """; } + public override Drawable? CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorGridSnap }; + public override PlacementBlueprint CreatePlacementBlueprint() => new GridPlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 4ac8d55ec3..f71243a109 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -7,15 +7,12 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; 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.Input.Bindings; using osu.Framework.Input.Events; -using osu.Framework.Localisation; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; -using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.UI; @@ -88,11 +85,8 @@ namespace osu.Game.Rulesets.Osu.Edit private ExpandableSlider startPositionYSlider = null!; private ExpandableSlider spacingSlider = null!; private ExpandableSlider gridLinesRotationSlider = null!; - private RoundedButton gridFromPointsButton = null!; private EditorRadioButtonCollection gridTypeButtons = null!; - public event Action? GridFromPointsClicked; - public OsuGridToolboxGroup() : base("grid") { @@ -152,18 +146,6 @@ namespace osu.Game.Rulesets.Osu.Edit Spacing = new Vector2(0f, 10f), Children = new Drawable[] { - gridFromPointsButton = new TooltipRoundedButton - { - Action = () => GridFromPointsClicked?.Invoke(), - RelativeSizeAxes = Axes.X, - Text = "Grid from points", - TooltipText = """ - Left click to set the origin. - Left click again to set the spacing and rotation. - Right click to only set the origin. - Click and drag to set the origin, spacing and rotation. - """ - }, gridTypeButtons = new EditorRadioButtonCollection { RelativeSizeAxes = Axes.X, @@ -249,8 +231,6 @@ namespace osu.Game.Rulesets.Osu.Edit expandingContainer?.Expanded.BindValueChanged(v => { - gridFromPointsButton.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint); - gridFromPointsButton.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None; gridTypeButtons.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint); gridTypeButtons.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None; }, true); @@ -313,11 +293,6 @@ namespace osu.Game.Rulesets.Osu.Edit } } - public partial class TooltipRoundedButton : RoundedButton, IHasTooltip - { - public virtual LocalisableString TooltipText { get; set; } - } - public enum PositionSnapGridType { Square, diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index dcfe6eeca7..7c50558b92 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -46,10 +46,9 @@ namespace osu.Game.Rulesets.Osu.Edit new HitCircleCompositionTool(), new SliderCompositionTool(), new SpinnerCompositionTool(), + new GridFromPointsTool() }; - private readonly GridFromPointsTool gridFromPointsTool = new GridFromPointsTool(); - private readonly Bindable rectangularGridSnapToggle = new Bindable(); protected override Drawable CreateHitObjectInspector() => new OsuHitObjectInspector(); @@ -99,7 +98,6 @@ namespace osu.Game.Rulesets.Osu.Edit updateDistanceSnapGrid(); OsuGridToolboxGroup.GridType.BindValueChanged(updatePositionSnapGrid, true); - OsuGridToolboxGroup.GridFromPointsClicked += () => SetCustomTool(gridFromPointsTool); RightToolbox.AddRange(new Drawable[] { diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 1c6bed5fa8..b52228ccb9 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -466,14 +466,6 @@ namespace osu.Game.Rulesets.Edit public void SetSelectTool() => toolboxCollection.Items.First().Select(); - protected void SetCustomTool(CompositionTool tool) - { - foreach (var toolBoxRadioButton in toolboxCollection.Items) - toolBoxRadioButton.Deselect(); - - toolSelected(tool); - } - private void toolSelected(CompositionTool tool) { BlueprintContainer.CurrentTool = tool; From a755ae70a300c9f1fd5aaf821913f202b9f9711b Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 7 Oct 2024 12:13:56 +0200 Subject: [PATCH 446/924] fix warning --- osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs index 638833f581..b429d47e6d 100644 --- a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs +++ b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Edit """; } - public override Drawable? CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorGridSnap }; + public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorGridSnap }; public override PlacementBlueprint CreatePlacementBlueprint() => new GridPlacementBlueprint(); } From 7cc6fe3819ff355bccd8100842eea4d65f8c2ed2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 7 Oct 2024 19:35:09 +0900 Subject: [PATCH 447/924] Return true while in gameplay A `false` value marks the user as being on the latest release, and notifies them as such when clicking the button in settings. In reality, we don't know whether this is the case yet - we're just deferring the check. Somewhat minor change because the chance of a user manually going into settings and clicking the button is very small. --- osu.Desktop/Updater/VelopackUpdateManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/Updater/VelopackUpdateManager.cs b/osu.Desktop/Updater/VelopackUpdateManager.cs index 46c5793fc8..d92d43b0e7 100644 --- a/osu.Desktop/Updater/VelopackUpdateManager.cs +++ b/osu.Desktop/Updater/VelopackUpdateManager.cs @@ -56,7 +56,7 @@ namespace osu.Desktop.Updater if (isInGameplay) { scheduleRecheck = true; - return false; + return true; } // TODO: we should probably be checking if there's a more recent update, rather than shortcutting here. From c3f2c82b108bdd1d8658f0fcec1427fd35b13d4f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 7 Oct 2024 19:48:57 +0900 Subject: [PATCH 448/924] Remove unused parameter --- osu.Desktop/Updater/VelopackUpdateManager.cs | 21 ++++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/osu.Desktop/Updater/VelopackUpdateManager.cs b/osu.Desktop/Updater/VelopackUpdateManager.cs index d92d43b0e7..33ff6c2b37 100644 --- a/osu.Desktop/Updater/VelopackUpdateManager.cs +++ b/osu.Desktop/Updater/VelopackUpdateManager.cs @@ -45,7 +45,7 @@ namespace osu.Desktop.Updater protected override async Task PerformUpdateCheck() => await checkForUpdateAsync().ConfigureAwait(false); - private async Task checkForUpdateAsync(UpdateProgressNotification? notification = null) + private async Task checkForUpdateAsync() { // whether to check again in 30 minutes. generally only if there's an error or no update was found (yet). bool scheduleRecheck = false; @@ -86,26 +86,21 @@ namespace osu.Desktop.Updater } // An update is found, let's notify the user and start downloading it. - if (notification == null) + UpdateProgressNotification notification = new UpdateProgressNotification { - notification = new UpdateProgressNotification + CompletionClickAction = () => { - CompletionClickAction = () => - { - Task.Run(restartToApplyUpdate); - return true; - }, - }; - - runOutsideOfGameplay(() => notificationOverlay.Post(notification)); - } + Task.Run(restartToApplyUpdate); + return true; + }, + }; + runOutsideOfGameplay(() => notificationOverlay.Post(notification)); notification.StartDownload(); try { await updateManager.DownloadUpdatesAsync(pendingUpdate, p => notification.Progress = p / 100f).ConfigureAwait(false); - runOutsideOfGameplay(() => notification.State = ProgressNotificationState.Completed); } catch (Exception e) From ec5f5a2336f6d85ab73d517f8a3aff470a8beb31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 7 Oct 2024 12:38:29 +0200 Subject: [PATCH 449/924] Send mods in spectator frame headers --- osu.Game/Online/Spectator/FrameHeader.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game/Online/Spectator/FrameHeader.cs b/osu.Game/Online/Spectator/FrameHeader.cs index 45f920e65b..e0fd1f0682 100644 --- a/osu.Game/Online/Spectator/FrameHeader.cs +++ b/osu.Game/Online/Spectator/FrameHeader.cs @@ -3,8 +3,10 @@ using System; using System.Collections.Generic; +using System.Linq; using MessagePack; using Newtonsoft.Json; +using osu.Game.Online.API; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -56,6 +58,17 @@ namespace osu.Game.Online.Spectator [Key(6)] public DateTimeOffset ReceivedTime { get; set; } + /// + /// The set of mods currently active. + /// + /// + /// Nullable for backwards compatibility with older clients + /// (these structures are also used server-side, and will be used as marker that the data isn't there). + /// can be made non-nullable 20250407 + /// + [Key(7)] + public APIMod[]? Mods { get; set; } + /// /// Construct header summary information from a point-in-time reference to a score which is actively being played. /// @@ -69,6 +82,7 @@ namespace osu.Game.Online.Spectator MaxCombo = score.MaxCombo; // copy for safety Statistics = new Dictionary(score.Statistics); + Mods = score.APIMods.ToArray(); ScoreProcessorStatistics = statistics; } From 1af464d5ae28eea7d7cb78b6ba6ffb71809ddbea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 7 Oct 2024 15:38:41 +0200 Subject: [PATCH 450/924] Bump difficulty calculator versions In order for the new star difficulty to be shown to users on the next release. catch's difficulty calculator version is not bumped because the only catch change pending deploy is https://github.com/ppy/osu/pull/28353 and that affects performance points only. --- osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs | 2 +- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 2 +- osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index efe27e8d6b..ff9aa4aa7b 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty private readonly bool isForCurrentRuleset; private readonly double originalOverallDifficulty; - public override int Version => 20230817; + public override int Version => 20241007; public ManiaDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 59a59ccd30..acf01b2a83 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty { private const double difficulty_multiplier = 0.0675; - public override int Version => 20220902; + public override int Version => 20241007; public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 86fa92ad03..18223e74fa 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private const double colour_skill_multiplier = 0.375 * difficulty_multiplier; private const double stamina_skill_multiplier = 0.375 * difficulty_multiplier; - public override int Version => 20221107; + public override int Version => 20241007; public TaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) From 533ed609fbf51ef82a5bb7943defd99caf2b807b Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 7 Oct 2024 16:19:01 +0200 Subject: [PATCH 451/924] Fix crash on placing circular grid --- .../Edit/Blueprints/GridPlacementBlueprint.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs index d0caec269f..4f23be2bee 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs @@ -34,7 +34,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints { gridToolboxGroup.StartPosition.Value = originalOrigin; gridToolboxGroup.Spacing.Value = originalSpacing; - gridToolboxGroup.GridLinesRotation.Value = originalRotation; + if (!gridToolboxGroup.GridLinesRotation.Disabled) + gridToolboxGroup.GridLinesRotation.Value = originalRotation; } base.EndPlacement(commit); @@ -107,7 +108,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints if (Vector2.Distance(gridToolboxGroup.StartPosition.Value, pos) < 2) { gridToolboxGroup.Spacing.Value = originalSpacing; - gridToolboxGroup.GridLinesRotation.Value = originalRotation; + if (!gridToolboxGroup.GridLinesRotation.Disabled) + gridToolboxGroup.GridLinesRotation.Value = originalRotation; } else { From db10202642d12ce594ad8b66fe3d7658f7166524 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 7 Oct 2024 16:51:53 +0200 Subject: [PATCH 452/924] Use new place grid icon --- osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs | 2 +- osu.Game/Graphics/OsuIcon.cs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs index b429d47e6d..bc143886ce 100644 --- a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs +++ b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Edit """; } - public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorGridSnap }; + public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorPlaceGrid }; public override PlacementBlueprint CreatePlacementBlueprint() => new GridPlacementBlueprint(); } diff --git a/osu.Game/Graphics/OsuIcon.cs b/osu.Game/Graphics/OsuIcon.cs index 9879ef5d14..77ab83ece6 100644 --- a/osu.Game/Graphics/OsuIcon.cs +++ b/osu.Game/Graphics/OsuIcon.cs @@ -178,6 +178,7 @@ namespace osu.Game.Graphics public static IconUsage EditorWhistle => get(OsuIconMapping.EditorWhistle); public static IconUsage Tortoise => get(OsuIconMapping.Tortoise); public static IconUsage Hare => get(OsuIconMapping.Hare); + public static IconUsage EditorPlaceGrid => get(OsuIconMapping.EditorPlaceGrid); private static IconUsage get(OsuIconMapping glyph) => new IconUsage((char)glyph, FONT_NAME); @@ -392,6 +393,9 @@ namespace osu.Game.Graphics [Description(@"hare")] Hare, + + [Description(@"Editor/place-grid")] + EditorPlaceGrid, } public class OsuIconStore : ITextureStore, ITexturedGlyphLookupStore From a02cda652884fbc87767f849fd5ade6075773f95 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Oct 2024 01:51:56 +0900 Subject: [PATCH 453/924] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index b00aa9c999..452528857c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From 076c8dec5a9e6c54406a291324776479eb080425 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 7 Oct 2024 19:06:32 +0200 Subject: [PATCH 454/924] Revert "Use new place grid icon" This reverts commit db10202642d12ce594ad8b66fe3d7658f7166524. --- osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs | 2 +- osu.Game/Graphics/OsuIcon.cs | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs index bc143886ce..b429d47e6d 100644 --- a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs +++ b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Edit """; } - public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorPlaceGrid }; + public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorGridSnap }; public override PlacementBlueprint CreatePlacementBlueprint() => new GridPlacementBlueprint(); } diff --git a/osu.Game/Graphics/OsuIcon.cs b/osu.Game/Graphics/OsuIcon.cs index 77ab83ece6..9879ef5d14 100644 --- a/osu.Game/Graphics/OsuIcon.cs +++ b/osu.Game/Graphics/OsuIcon.cs @@ -178,7 +178,6 @@ namespace osu.Game.Graphics public static IconUsage EditorWhistle => get(OsuIconMapping.EditorWhistle); public static IconUsage Tortoise => get(OsuIconMapping.Tortoise); public static IconUsage Hare => get(OsuIconMapping.Hare); - public static IconUsage EditorPlaceGrid => get(OsuIconMapping.EditorPlaceGrid); private static IconUsage get(OsuIconMapping glyph) => new IconUsage((char)glyph, FONT_NAME); @@ -393,9 +392,6 @@ namespace osu.Game.Graphics [Description(@"hare")] Hare, - - [Description(@"Editor/place-grid")] - EditorPlaceGrid, } public class OsuIconStore : ITextureStore, ITexturedGlyphLookupStore From 66459c50de4be931ee26be13245a6b9ff812d1ee Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 7 Oct 2024 19:09:09 +0200 Subject: [PATCH 455/924] Rename to 'Grid' --- osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs index b429d47e6d..7d4d925ac4 100644 --- a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs +++ b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Edit public partial class GridFromPointsTool : CompositionTool { public GridFromPointsTool() - : base("Change grid") + : base("Grid") { TooltipText = """ Left click to set the origin. From dc267733d13d0a4f3edfc74f567d62bd42d22472 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 7 Oct 2024 19:09:21 +0200 Subject: [PATCH 456/924] Use FA icon instead --- osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs index 7d4d925ac4..5d6bc6f525 100644 --- a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs +++ b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs @@ -3,7 +3,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Osu.Edit.Blueprints; @@ -23,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Edit """; } - public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorGridSnap }; + public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.DraftingCompass }; public override PlacementBlueprint CreatePlacementBlueprint() => new GridPlacementBlueprint(); } From b1be31cd6a31f9a4837be62cba783b74addb8c06 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Oct 2024 17:34:51 +0900 Subject: [PATCH 457/924] Switch back to last tool after using grid tool, rather than always select tool --- .../Edit/Blueprints/GridPlacementBlueprint.cs | 2 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs index 4f23be2bee..be33e0e8c8 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints // You typically only place the grid once, so we switch back to the select tool after placement. if (commit && hitObjectComposer is OsuHitObjectComposer osuHitObjectComposer) - osuHitObjectComposer.SetSelectTool(); + osuHitObjectComposer.SetLastTool(); } protected override bool OnClick(ClickEvent e) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index b52228ccb9..0499e10607 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -90,6 +90,9 @@ namespace osu.Game.Rulesets.Edit private Bindable autoSeekOnPlacement; private readonly Bindable composerFocusMode = new Bindable(); + [CanBeNull] + private RadioButton lastTool; + protected DrawableRuleset DrawableRuleset { get; private set; } protected HitObjectComposer(Ruleset ruleset) @@ -213,8 +216,7 @@ namespace osu.Game.Rulesets.Edit }, }; - toolboxCollection.Items = CompositionTools - .Prepend(new SelectTool()) + toolboxCollection.Items = (CompositionTools.Prepend(new SelectTool())) .Select(t => new HitObjectCompositionToolButton(t, () => toolSelected(t))) .ToList(); @@ -466,8 +468,12 @@ namespace osu.Game.Rulesets.Edit public void SetSelectTool() => toolboxCollection.Items.First().Select(); + public void SetLastTool() => (lastTool ?? toolboxCollection.Items.First()).Select(); + private void toolSelected(CompositionTool tool) { + lastTool = toolboxCollection.Items.OfType().FirstOrDefault(i => i.Tool == BlueprintContainer.CurrentTool); + BlueprintContainer.CurrentTool = tool; if (!(tool is SelectTool)) From e794862da59c0836df08107c7915d690ff84145f Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 8 Oct 2024 10:46:10 +0200 Subject: [PATCH 458/924] update comment --- osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs index be33e0e8c8..85042e0e32 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints base.EndPlacement(commit); - // You typically only place the grid once, so we switch back to the select tool after placement. + // You typically only place the grid once, so we switch back to the last tool after placement. if (commit && hitObjectComposer is OsuHitObjectComposer osuHitObjectComposer) osuHitObjectComposer.SetLastTool(); } From de2f9dec39c193108c8919aefe24cb6fda5d6a29 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 8 Oct 2024 10:51:35 +0200 Subject: [PATCH 459/924] Let right-click reset grid to default values --- .../Edit/Blueprints/GridPlacementBlueprint.cs | 5 +++++ osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs | 2 +- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs index 85042e0e32..b13663cb44 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs @@ -68,6 +68,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints { if (e.Button == MouseButton.Right) { + // Reset the grid to the default values. + gridToolboxGroup.StartPosition.Value = gridToolboxGroup.StartPosition.Default; + gridToolboxGroup.Spacing.Value = gridToolboxGroup.Spacing.Default; + if (!gridToolboxGroup.GridLinesRotation.Disabled) + gridToolboxGroup.GridLinesRotation.Value = gridToolboxGroup.GridLinesRotation.Default; EndPlacement(true); return true; } diff --git a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs index 5d6bc6f525..626153a7fd 100644 --- a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs +++ b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Edit TooltipText = """ Left click to set the origin. Left click again to set the spacing and rotation. - Right click to only set the origin. + Right click to reset to default. Click and drag to set the origin, spacing and rotation. """; } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index f71243a109..2b88860cc8 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Osu.Edit /// Read-only bindable representing the grid's origin. /// Equivalent to new Vector2(StartPositionX, StartPositionY) /// - public Bindable StartPosition { get; } = new Bindable(); + public Bindable StartPosition { get; } = new Bindable(OsuPlayfield.BASE_SIZE / 2); /// /// Read-only bindable representing the grid's spacing in both the X and Y dimension. From f8e43fd8d36ad6501b1617013824d1ee87a33b89 Mon Sep 17 00:00:00 2001 From: WitherFlower <52672543+WitherFlower@users.noreply.github.com> Date: Tue, 8 Oct 2024 13:46:21 +0200 Subject: [PATCH 460/924] Add tests for ranked and submitted date filtering --- .../Filtering/FilterQueryParserTest.cs | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 9ecfa72947..852b1e1441 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -627,6 +627,88 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.AreEqual(DateTimeOffset.MinValue.AddMilliseconds(1), filterCriteria.LastPlayed.Min); } + private static DateTimeOffset dateTimeOffsetFromDateOnly(int year, int month, int day) => + new DateTimeOffset(year, month, day, 0, 0, 0, TimeSpan.Zero); + + private static readonly object[] ranked_date_valid_test_cases = + { + new object[] { "ranked<2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<2012-03", dateTimeOffsetFromDateOnly(2012, 3, 1), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<2012-03-05", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Max }, + + new object[] { "ranked<=2012", dateTimeOffsetFromDateOnly(2013, 1, 1), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<=2012-03", dateTimeOffsetFromDateOnly(2012, 4, 1), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<=2012-03-05", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<=2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Max }, + + new object[] { "ranked>2012", dateTimeOffsetFromDateOnly(2013, 1, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>2012-03", dateTimeOffsetFromDateOnly(2012, 4, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>2012-03-05", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Min }, + + new object[] { "ranked>=2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>=2012-03", dateTimeOffsetFromDateOnly(2012, 3, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>=2012-03-05", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>=2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Min }, + + new object[] { "ranked=2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked=2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked=2012-03", dateTimeOffsetFromDateOnly(2012, 3, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked=2012-03", dateTimeOffsetFromDateOnly(2012, 4, 1), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked=2012-03-05", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked=2012-03-05", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Max }, + }; + + [Test] + [TestCaseSource(nameof(ranked_date_valid_test_cases))] + public void TestValidRankedDateQueries(string query, DateTimeOffset expected, Func f) + { + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual(true, filterCriteria.DateRanked.HasFilter); + Assert.AreEqual(expected, f(filterCriteria)); + } + + private static readonly object[] ranked_date_invalid_test_cases = + { + new object[] { "ranked<0" }, + new object[] { "ranked=99999" }, + new object[] { "ranked>=2012-03-05-04" }, + new object[] { "ranked>2007.5" }, + }; + + [Test] + [TestCaseSource(nameof(ranked_date_invalid_test_cases))] + public void TestInvalidRankedDateQueries(string query) + { + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual(false, filterCriteria.DateRanked.HasFilter); + } + + private static readonly object[] submitted_date_test_cases = + { + new object[] { "submitted<2012", true }, + new object[] { "submitted<2012-03", true }, + new object[] { "submitted<2012-03-05", true }, + new object[] { "submitted<2012-3-5", true }, + + new object[] { "submitted<0", false }, + new object[] { "submitted=99999", false }, + new object[] { "submitted>=2012-03-05-04", false }, + new object[] { "submitted>2007.5", false }, + }; + + [Test] + [TestCaseSource(nameof(submitted_date_test_cases))] + public void TestInvalidRankedDateQueries(string query, bool expected) + { + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual(expected, filterCriteria.DateSubmitted.HasFilter); + } + private static readonly object[] played_query_tests = { new object[] { "0", DateTimeOffset.MinValue, true }, From 2369e98cfc34ecb9c75ac66e89403eed10807e09 Mon Sep 17 00:00:00 2001 From: WitherFlower <52672543+WitherFlower@users.noreply.github.com> Date: Tue, 8 Oct 2024 13:46:58 +0200 Subject: [PATCH 461/924] Implement ranked and submitted date filtering --- .../Select/Carousel/CarouselBeatmap.cs | 2 + osu.Game/Screens/Select/FilterCriteria.cs | 2 + osu.Game/Screens/Select/FilterQueryParser.cs | 185 ++++++++++++++++++ 3 files changed, 189 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 8f38ae710c..aa064f97c9 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -66,6 +66,8 @@ namespace osu.Game.Screens.Select.Carousel match &= !criteria.OverallDifficulty.HasFilter || criteria.OverallDifficulty.IsInRange(BeatmapInfo.Difficulty.OverallDifficulty); match &= !criteria.Length.HasFilter || criteria.Length.IsInRange(BeatmapInfo.Length); match &= !criteria.LastPlayed.HasFilter || criteria.LastPlayed.IsInRange(BeatmapInfo.LastPlayed ?? DateTimeOffset.MinValue); + match &= !criteria.DateRanked.HasFilter || criteria.DateRanked.IsInRange(BeatmapInfo.BeatmapSet?.DateRanked ?? DateTimeOffset.MinValue); + match &= !criteria.DateSubmitted.HasFilter || criteria.DateSubmitted.IsInRange(BeatmapInfo.BeatmapSet?.DateSubmitted ?? DateTimeOffset.MinValue); match &= !criteria.BPM.HasFilter || criteria.BPM.IsInRange(BeatmapInfo.BPM); match &= !criteria.BeatDivisor.HasFilter || criteria.BeatDivisor.IsInRange(BeatmapInfo.BeatDivisor); diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 190efd0fb0..77d7ff0e9f 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -37,6 +37,8 @@ namespace osu.Game.Screens.Select public OptionalRange BeatDivisor; public OptionalSet OnlineStatus = new OptionalSet(); public OptionalRange LastPlayed; + public OptionalRange DateRanked; + public OptionalRange DateSubmitted; public OptionalTextFilter Creator; public OptionalTextFilter Artist; public OptionalTextFilter Title; diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 6c9a95a250..17fa6598c5 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -65,6 +65,12 @@ namespace osu.Game.Screens.Select case "lastplayed": return tryUpdateDateAgoRange(ref criteria.LastPlayed, op, value); + case "ranked": + return tryUpdateRankedDateRange(ref criteria.DateRanked, op, value); + + case "submitted": + return tryUpdateRankedDateRange(ref criteria.DateSubmitted, op, value); + case "played": if (!tryParseBool(value, out bool played)) return false; @@ -592,5 +598,184 @@ namespace osu.Game.Screens.Select return tryUpdateCriteriaRange(ref dateRange, op, dateTimeOffset.Value); } + + /// + /// Helper function for building a UTC date from only the year, month and day. + /// UTC is used to keep consistent search results with osu!web. + /// + private static DateTimeOffset dateTimeOffsetFromDateOnly(int year, int month, int day) => + new DateTimeOffset(year, month, day, 0, 0, 0, TimeSpan.Zero); + + /// + /// Parses a string containing a ranked or submitted date filter. + /// Returns a boolean depending on whether parsing was successful or not. + /// Accepted dates are in the formats `yyyy`, `yyyy-mm` and `yyyy-mm-dd`. Leading zeros are accepted. + /// + /// The to store the parsed data into, if successful. + /// The operator of the filtering query + /// The string value to attempt parsing for. + private static bool tryUpdateRankedDateRange(ref FilterCriteria.OptionalRange dateRange, Operator op, string val) + { + GroupCollection? match = null; + + match ??= tryMatchRegex(val, @"^(?\d+)(-(?\d+)(-(?\d+))?)?$"); + + if (match == null) + return false; + + int? year = null; + int? month = null; + int? day = null; + + List keys = new List { @"year", @"month", @"day" }; + + foreach (string key in keys) + { + if (!match.TryGetValue(key, out var group) || !group.Success) + continue; + + if (group.Success) + { + if (!tryParseDoubleWithPoint(group.Value, out double value)) + return false; + + switch (key) + { + + case @"year": + year = (int)value; + break; + + case @"month": + month = (int)value; + break; + + case @"day": + day = (int)value; + break; + } + } + } + + if (year == null) + { + return false; + } + + try + { + DateTimeOffset dateTimeOffset; + + switch (op) + { + case Operator.Less: + if (month == null) + { + month = 1; + day = 1; + } + + if (day == null) + day = 1; + + dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value); + return tryUpdateCriteriaRange(ref dateRange, op, dateTimeOffset); + + case Operator.LessOrEqual: + if (month == null) + { + month = 1; + day = 1; + dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddYears(1); + return tryUpdateCriteriaRange(ref dateRange, Operator.Less, dateTimeOffset); + } + if (day == null) + { + day = 1; + dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddMonths(1); + return tryUpdateCriteriaRange(ref dateRange, Operator.Less, dateTimeOffset); + } + + dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddDays(1); + return tryUpdateCriteriaRange(ref dateRange, Operator.Less, dateTimeOffset); + + case Operator.GreaterOrEqual: + if (month == null) + { + month = 1; + day = 1; + } + + if (day == null) + day = 1; + + dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value); + return tryUpdateCriteriaRange(ref dateRange, op, dateTimeOffset); + + case Operator.Greater: + if (month == null) + { + month = 1; + day = 1; + dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddYears(1); + return tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, dateTimeOffset); + } + if (day == null) + { + day = 1; + dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddMonths(1); + return tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, dateTimeOffset); + } + + dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddDays(1); + return tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, dateTimeOffset); + + case Operator.Equal: + + DateTimeOffset minDateTimeOffset; + DateTimeOffset maxDateTimeOffset; + + if (month == null) + { + month = 1; + day = 1; + minDateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value); + maxDateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddYears(1); + return ( + tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, minDateTimeOffset) + && + tryUpdateCriteriaRange(ref dateRange, Operator.Less, maxDateTimeOffset) + ); + } + + if (day == null) + { + day = 1; + minDateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value); + maxDateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddMonths(1); + return ( + tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, minDateTimeOffset) + && + tryUpdateCriteriaRange(ref dateRange, Operator.Less, maxDateTimeOffset) + ); + } + + minDateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value); + maxDateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddDays(1); + + return ( + tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, minDateTimeOffset) + && + tryUpdateCriteriaRange(ref dateRange, Operator.Less, maxDateTimeOffset) + ); + default: + return false; + } + } + catch (ArgumentOutOfRangeException) + { + return false; + } + } } } From 3d06d67fec4339f9f01809fa318c48317ef84643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 8 Oct 2024 14:04:55 +0200 Subject: [PATCH 462/924] Add `GET /users/lookup` request type --- .../Online/API/Requests/GetUsersRequest.cs | 6 ++++ .../Online/API/Requests/LookupUsersRequest.cs | 30 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 osu.Game/Online/API/Requests/LookupUsersRequest.cs diff --git a/osu.Game/Online/API/Requests/GetUsersRequest.cs b/osu.Game/Online/API/Requests/GetUsersRequest.cs index 6f7e9c07d2..cd75ff4e31 100644 --- a/osu.Game/Online/API/Requests/GetUsersRequest.cs +++ b/osu.Game/Online/API/Requests/GetUsersRequest.cs @@ -2,9 +2,15 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests { + /// + /// Looks up users with the given . + /// In comparison to , the response here contains , + /// but in exchange is subject to more stringent rate limiting. + /// public class GetUsersRequest : APIRequest { public readonly int[] UserIds; diff --git a/osu.Game/Online/API/Requests/LookupUsersRequest.cs b/osu.Game/Online/API/Requests/LookupUsersRequest.cs new file mode 100644 index 0000000000..6e98ce064e --- /dev/null +++ b/osu.Game/Online/API/Requests/LookupUsersRequest.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Online.API.Requests +{ + /// + /// Looks up users with the given . + /// In comparison to , the response here does not contain , + /// but in exchange is subject to less stringent rate limiting, making it suitable for mass user listings. + /// + public class LookupUsersRequest : APIRequest + { + public readonly int[] UserIds; + + private const int max_ids_per_request = 50; + + public LookupUsersRequest(int[] userIds) + { + if (userIds.Length > max_ids_per_request) + throw new ArgumentException($"{nameof(LookupUsersRequest)} calls only support up to {max_ids_per_request} IDs at once"); + + UserIds = userIds; + } + + protected override string Target => @"users/lookup/?ids[]=" + string.Join(@"&ids[]=", UserIds); + } +} From 17bc5ce5a931d31cbc1bd87dd57431c2c888f13f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 8 Oct 2024 14:05:27 +0200 Subject: [PATCH 463/924] Use lookup request in user lookup cache Doing this alleviates https://github.com/ppy/osu/issues/29982, as the currently online display utilises the user lookup cache, and currently is hitting rate limits due to the amount of data retrieved from the `GET /users` endpoint. Switching to `GET /users/lookup` reduces the chance of this happening. --- osu.Game/Database/UserLookupCache.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/UserLookupCache.cs b/osu.Game/Database/UserLookupCache.cs index e581d5ce82..8c96b34666 100644 --- a/osu.Game/Database/UserLookupCache.cs +++ b/osu.Game/Database/UserLookupCache.cs @@ -10,7 +10,7 @@ using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Database { - public partial class UserLookupCache : OnlineLookupCache + public partial class UserLookupCache : OnlineLookupCache { /// /// Perform an API lookup on the specified user, populating a model. @@ -28,8 +28,8 @@ namespace osu.Game.Database /// The populated users. May include null results for failed retrievals. public Task GetUsersAsync(int[] userIds, CancellationToken token = default) => LookupAsync(userIds, token); - protected override GetUsersRequest CreateRequest(IEnumerable ids) => new GetUsersRequest(ids.ToArray()); + protected override LookupUsersRequest CreateRequest(IEnumerable ids) => new LookupUsersRequest(ids.ToArray()); - protected override IEnumerable? RetrieveResults(GetUsersRequest request) => request.Response?.Users; + protected override IEnumerable? RetrieveResults(LookupUsersRequest request) => request.Response?.Users; } } From b58576f31b05aeb18af742e6145c5a66f375ba20 Mon Sep 17 00:00:00 2001 From: WitherFlower <52672543+WitherFlower@users.noreply.github.com> Date: Tue, 8 Oct 2024 14:13:27 +0200 Subject: [PATCH 464/924] Add slash and dot as valid separators in dates. --- .../Filtering/FilterQueryParserTest.cs | 23 +++++++++---------- osu.Game/Screens/Select/FilterQueryParser.cs | 5 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 852b1e1441..ad3be17c15 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -633,23 +633,23 @@ namespace osu.Game.Tests.NonVisual.Filtering private static readonly object[] ranked_date_valid_test_cases = { new object[] { "ranked<2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Max }, - new object[] { "ranked<2012-03", dateTimeOffsetFromDateOnly(2012, 3, 1), (FilterCriteria x) => x.DateRanked.Max }, - new object[] { "ranked<2012-03-05", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<2012.03", dateTimeOffsetFromDateOnly(2012, 3, 1), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<2012/03/05", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Max }, new object[] { "ranked<2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Max }, new object[] { "ranked<=2012", dateTimeOffsetFromDateOnly(2013, 1, 1), (FilterCriteria x) => x.DateRanked.Max }, - new object[] { "ranked<=2012-03", dateTimeOffsetFromDateOnly(2012, 4, 1), (FilterCriteria x) => x.DateRanked.Max }, - new object[] { "ranked<=2012-03-05", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<=2012.03", dateTimeOffsetFromDateOnly(2012, 4, 1), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<=2012/03/05", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Max }, new object[] { "ranked<=2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Max }, new object[] { "ranked>2012", dateTimeOffsetFromDateOnly(2013, 1, 1), (FilterCriteria x) => x.DateRanked.Min }, - new object[] { "ranked>2012-03", dateTimeOffsetFromDateOnly(2012, 4, 1), (FilterCriteria x) => x.DateRanked.Min }, - new object[] { "ranked>2012-03-05", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>2012.03", dateTimeOffsetFromDateOnly(2012, 4, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>2012/03/05", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Min }, new object[] { "ranked>2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Min }, new object[] { "ranked>=2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Min }, - new object[] { "ranked>=2012-03", dateTimeOffsetFromDateOnly(2012, 3, 1), (FilterCriteria x) => x.DateRanked.Min }, - new object[] { "ranked>=2012-03-05", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>=2012.03", dateTimeOffsetFromDateOnly(2012, 3, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>=2012/03/05", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Min }, new object[] { "ranked>=2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Min }, new object[] { "ranked=2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Min }, @@ -675,7 +675,6 @@ namespace osu.Game.Tests.NonVisual.Filtering new object[] { "ranked<0" }, new object[] { "ranked=99999" }, new object[] { "ranked>=2012-03-05-04" }, - new object[] { "ranked>2007.5" }, }; [Test] @@ -690,14 +689,14 @@ namespace osu.Game.Tests.NonVisual.Filtering private static readonly object[] submitted_date_test_cases = { new object[] { "submitted<2012", true }, - new object[] { "submitted<2012-03", true }, - new object[] { "submitted<2012-03-05", true }, + new object[] { "submitted<2012.03", true }, + new object[] { "submitted<2012/03/05", true }, new object[] { "submitted<2012-3-5", true }, new object[] { "submitted<0", false }, new object[] { "submitted=99999", false }, new object[] { "submitted>=2012-03-05-04", false }, - new object[] { "submitted>2007.5", false }, + new object[] { "submitted>=2012/03.05-04", false }, }; [Test] diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 17fa6598c5..d6f46c0fbd 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -609,7 +609,8 @@ namespace osu.Game.Screens.Select /// /// Parses a string containing a ranked or submitted date filter. /// Returns a boolean depending on whether parsing was successful or not. - /// Accepted dates are in the formats `yyyy`, `yyyy-mm` and `yyyy-mm-dd`. Leading zeros are accepted. + /// Accepted dates are in the formats `yyyy`, `yyyy-mm` and `yyyy-mm-dd`. + /// Leading zeros are accepted. Numbers can be separated by `-`, `/`, or `.` /// /// The to store the parsed data into, if successful. /// The operator of the filtering query @@ -618,7 +619,7 @@ namespace osu.Game.Screens.Select { GroupCollection? match = null; - match ??= tryMatchRegex(val, @"^(?\d+)(-(?\d+)(-(?\d+))?)?$"); + match ??= tryMatchRegex(val, @"^(?\d+)((-|/|\.)(?\d+)((-|/|\.)(?\d+))?)?$"); if (match == null) return false; From 5104f3e7ac39acc03e1b876fc30a0fdb8e34a38c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 8 Oct 2024 14:08:59 +0200 Subject: [PATCH 465/924] Switch multiplayer away from using `UserLookupCache` After switching `UserLookupCache` to `GET /users/lookup` from `GET /users`, multiplayer sort of breaks, since the former endpoint does not return `ruleset_statistics`, which are used in multiplayer to show users' ranks. Therefore, switch multiplayer to use the appropriate request type directly. --- .../TestSceneMultiplayerMatchSubScreen.cs | 8 +++--- .../Online/Multiplayer/MultiplayerClient.cs | 27 +++++++++++++++---- .../OnlinePlay/TestRoomRequestsHandler.cs | 16 +++++++++++ 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index e2593e68e5..9bf29c7bf8 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -165,11 +165,9 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for room join", () => RoomJoined); - AddStep("join other user (ready)", () => - { - MultiplayerClient.AddUser(new APIUser { Id = PLAYER_1_ID }); - MultiplayerClient.ChangeUserState(PLAYER_1_ID, MultiplayerUserState.Ready); - }); + AddStep("join other user", void () => MultiplayerClient.AddUser(new APIUser { Id = PLAYER_1_ID })); + AddUntilStep("wait for user populated", () => MultiplayerClient.ClientRoom!.Users.Single(u => u.UserID == PLAYER_1_ID).User, () => Is.Not.Null); + AddStep("other user ready", () => MultiplayerClient.ChangeUserState(PLAYER_1_ID, MultiplayerUserState.Ready)); ClickButtonWhenEnabled(); diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 4aa0d92098..610ead0f9d 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics; using osu.Game.Database; using osu.Game.Localisation; using osu.Game.Online.API; +using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer.Countdown; using osu.Game.Online.Rooms; @@ -188,7 +189,7 @@ namespace osu.Game.Online.Multiplayer // Populate users. Debug.Assert(joinedRoom.Users != null); - await Task.WhenAll(joinedRoom.Users.Select(PopulateUser)).ConfigureAwait(false); + await PopulateUsers(joinedRoom.Users).ConfigureAwait(false); // Update the stored room (must be done on update thread for thread-safety). await runOnUpdateThreadAsync(() => @@ -416,7 +417,7 @@ namespace osu.Game.Online.Multiplayer async Task IMultiplayerClient.UserJoined(MultiplayerRoomUser user) { - await PopulateUser(user).ConfigureAwait(false); + await PopulateUsers([user]).ConfigureAwait(false); Scheduler.Add(() => { @@ -803,10 +804,26 @@ namespace osu.Game.Online.Multiplayer } /// - /// Populates the for a given . + /// Populates the for a given collection of s. /// - /// The to populate. - protected async Task PopulateUser(MultiplayerRoomUser multiplayerUser) => multiplayerUser.User ??= await userLookupCache.GetUserAsync(multiplayerUser.UserID).ConfigureAwait(false); + /// The s to populate. + protected async Task PopulateUsers(IEnumerable multiplayerUsers) + { + var request = new GetUsersRequest(multiplayerUsers.Select(u => u.UserID).Distinct().ToArray()); + await API.PerformAsync(request).ContinueWith(t => + { + if (request.Response == null) + return; + + var users = request.Response.Users.ToDictionary(user => user.Id); + + foreach (var multiplayerUser in multiplayerUsers) + { + if (users.TryGetValue(multiplayerUser.UserID, out var user)) + multiplayerUser.User = user; + } + }).ConfigureAwait(false); + } /// /// Updates the local room settings with the given . diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index cb05180d17..592e4c6eee 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -214,6 +214,22 @@ namespace osu.Game.Tests.Visual.OnlinePlay getBeatmapSetRequest.TriggerSuccess(OsuTestScene.CreateAPIBeatmapSet(baseBeatmap)); return true; } + + case GetUsersRequest getUsersRequest: + { + getUsersRequest.TriggerSuccess(new GetUsersResponse + { + Users = getUsersRequest.UserIds.Select(id => id == TestUserLookupCache.UNRESOLVED_USER_ID + ? null + : new APIUser + { + Id = id, + Username = $"User {id}" + }) + .Where(u => u != null).ToList(), + }); + return true; + } } List createResponseBeatmaps(params int[] beatmapIds) From 1744566def2e9b8a41fef549d3d1b97512f6a2b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 8 Oct 2024 14:10:11 +0200 Subject: [PATCH 466/924] Clarify xmldoc --- osu.Game/Online/API/Requests/Responses/APIUser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index c69e45b3fd..5d80fde515 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -261,7 +261,7 @@ namespace osu.Game.Online.API.Requests.Responses public APIUserHistoryCount[] ReplaysWatchedCounts; /// - /// All user statistics per ruleset's short name (in the case of a response). + /// All user statistics per ruleset's short name (in the case of a or response). /// Otherwise empty. Can be altered for testing purposes. /// // todo: this should likely be moved to a separate UserCompact class at some point. From 310eec69fcb30fd89d00b46d6ecb9e99c94f4314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 8 Oct 2024 23:08:18 +0200 Subject: [PATCH 467/924] Fix any and all migration attempts dying on `MusicController` I'm not sure why this was "fine" for as long as it apparently was, but what `MusicController` was doing was completely incorrect and playing with fire (accessing raw managed realm objects), which went wrong somewhere around - admittedly - https://github.com/ppy/osu/pull/29917, likely because that one started *storing* these raw managed realm objects to fields, and you know what will happen to those after you do a migration and recycle realms. To attempt to circumvent this, (ab)use `DetachedBeatmapStore` instead. Which does necessitate moving it to `OsuGameBase`, but it's the simplest way out I can see. I guess the alternative would be to faff around with `Live` but it's ugly and I'm attempting to fix this relatively quick right now. --- osu.Game/OsuGame.cs | 1 - osu.Game/OsuGameBase.cs | 4 ++++ osu.Game/Overlays/MusicController.cs | 23 ++++++++++++++--------- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index dce24c6ee7..a7f3cc8df1 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1137,7 +1137,6 @@ namespace osu.Game loadComponentSingleFile(new MedalOverlay(), topMostOverlayContent.Add); loadComponentSingleFile(new BackgroundDataStoreProcessor(), Add); - loadComponentSingleFile(new DetachedBeatmapStore(), Add, true); Add(difficultyRecommender); Add(externalLinkOpener = new ExternalLinkOpener()); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index e317fac25d..46a48a02b1 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -377,6 +377,10 @@ namespace osu.Game dependencies.Cache(previewTrackManager = new PreviewTrackManager(BeatmapManager.BeatmapTrackStore)); base.Content.Add(previewTrackManager); + var detachedBeatmapStore = new DetachedBeatmapStore(); + base.Content.Add(detachedBeatmapStore); + dependencies.CacheAs(detachedBeatmapStore); + base.Content.Add(MusicController = new MusicController()); dependencies.CacheAs(MusicController); diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 600c014a95..ffb1fcc032 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Track; @@ -65,6 +66,8 @@ namespace osu.Game.Overlays [Resolved] private RealmAccess realm { get; set; } = null!; + private IBindableList detachedBeatmaps = null!; + private BindableNumber sampleVolume = null!; private readonly BindableDouble audioDuckVolume = new BindableDouble(1); @@ -76,13 +79,15 @@ namespace osu.Game.Overlays private int randomHistoryDirection; [BackgroundDependencyLoader] - private void load(AudioManager audio, OsuConfigManager configManager) + private void load(AudioManager audio, OsuConfigManager configManager, DetachedBeatmapStore detachedBeatmapStore, CancellationToken? cancellationToken) { AddInternal(audioDuckFilter = new AudioFilter(audio.TrackMixer)); audio.Tracks.AddAdjustment(AdjustableProperty.Volume, audioDuckVolume); sampleVolume = audio.VolumeSample.GetBoundCopy(); configManager.BindWith(OsuSetting.RandomSelectAlgorithm, randomSelectAlgorithm); + + detachedBeatmaps = detachedBeatmapStore.GetDetachedBeatmaps(cancellationToken); } protected override void LoadComplete() @@ -255,8 +260,8 @@ namespace osu.Game.Overlays playableSet = getNextRandom(-1, allowProtectedTracks); else { - playableSet = getBeatmapSets().AsEnumerable().TakeWhile(i => !i.Equals(current?.BeatmapSetInfo)).LastOrDefault(s => !s.Protected || allowProtectedTracks) - ?? getBeatmapSets().AsEnumerable().LastOrDefault(s => !s.Protected || allowProtectedTracks); + playableSet = getBeatmapSets().TakeWhile(i => !i.Equals(current?.BeatmapSetInfo)).LastOrDefault(s => !s.Protected || allowProtectedTracks) + ?? getBeatmapSets().LastOrDefault(s => !s.Protected || allowProtectedTracks); } if (playableSet != null) @@ -351,10 +356,10 @@ namespace osu.Game.Overlays playableSet = getNextRandom(1, allowProtectedTracks); else { - playableSet = getBeatmapSets().AsEnumerable().SkipWhile(i => !i.Equals(current?.BeatmapSetInfo)) + playableSet = getBeatmapSets().SkipWhile(i => !i.Equals(current?.BeatmapSetInfo)) .Where(i => !i.Protected || allowProtectedTracks) .ElementAtOrDefault(1) - ?? getBeatmapSets().AsEnumerable().FirstOrDefault(i => !i.Protected || allowProtectedTracks); + ?? getBeatmapSets().FirstOrDefault(i => !i.Protected || allowProtectedTracks); } var playableBeatmap = playableSet?.Beatmaps.FirstOrDefault(); @@ -373,7 +378,7 @@ namespace osu.Game.Overlays { BeatmapSetInfo result; - var possibleSets = getBeatmapSets().AsEnumerable().Where(s => !s.Protected || allowProtectedTracks).ToArray(); + var possibleSets = getBeatmapSets().Where(s => !s.Protected || allowProtectedTracks).ToArray(); if (possibleSets.Length == 0) return null; @@ -432,7 +437,7 @@ namespace osu.Game.Overlays private TrackChangeDirection? queuedDirection; - private IQueryable getBeatmapSets() => realm.Realm.All().Where(s => !s.DeletePending); + private IEnumerable getBeatmapSets() => detachedBeatmaps.Where(s => !s.DeletePending); private void changeBeatmap(WorkingBeatmap newWorking) { @@ -459,8 +464,8 @@ namespace osu.Game.Overlays else { // figure out the best direction based on order in playlist. - int last = getBeatmapSets().AsEnumerable().TakeWhile(b => !b.Equals(current.BeatmapSetInfo)).Count(); - int next = getBeatmapSets().AsEnumerable().TakeWhile(b => !b.Equals(newWorking.BeatmapSetInfo)).Count(); + int last = getBeatmapSets().TakeWhile(b => !b.Equals(current.BeatmapSetInfo)).Count(); + int next = getBeatmapSets().TakeWhile(b => !b.Equals(newWorking.BeatmapSetInfo)).Count(); direction = last > next ? TrackChangeDirection.Prev : TrackChangeDirection.Next; } From 1633cbdb6619c78da388549555c49cf74a7138a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 8 Oct 2024 23:12:25 +0200 Subject: [PATCH 468/924] Fix `OsuGameBase.Migrate()` eating exception messages for breakfast Whomst've thought this was an ok thing to do? How did this pass review? Let's leave these as rhetorical questions right now. Huge chances are I'd implicate myself with at least one of them. --- osu.Game/OsuGameBase.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 46a48a02b1..96e5484d93 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -545,7 +545,10 @@ namespace osu.Game realmBlocker = realm.BlockAllOperations("migration"); success = true; } - catch { } + catch (Exception ex) + { + Logger.Log($"Attempting to block all operations failed: {ex}", LoggingTarget.Database); + } readyToRun.Set(); }, false); From b811b9baf653974092d0ef79952359203db59d1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 8 Oct 2024 23:13:27 +0200 Subject: [PATCH 469/924] Fix `DetachedBeatmapStore` special condition for detecting resets from blocking all operations failing on empty databases See https://discord.com/channels/188630481301012481/188630652340404224/1293309912591368244. --- osu.Game/Database/DetachedBeatmapStore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/DetachedBeatmapStore.cs b/osu.Game/Database/DetachedBeatmapStore.cs index 64aeeccd9a..f5c975d9db 100644 --- a/osu.Game/Database/DetachedBeatmapStore.cs +++ b/osu.Game/Database/DetachedBeatmapStore.cs @@ -44,7 +44,7 @@ namespace osu.Game.Database { if (changes == null) { - if (detachedBeatmapSets.Count > 0 && sender.Count == 0) + if (sender is EmptyRealmSet) { // Usually we'd reset stuff here, but doing so triggers a silly flow which ends up deadlocking realm. // Additionally, user should not be at song select when realm is blocking all operations in the first place. From 6a754222e490e50ec7cd6d1d66e7b89914942c72 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2024 01:30:23 +0000 Subject: [PATCH 470/924] Bump System.IO.Packaging from 8.0.0 to 8.0.1 in /osu.Desktop Bumps [System.IO.Packaging](https://github.com/dotnet/runtime) from 8.0.0 to 8.0.1. - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/compare/v8.0.0...v8.0.1) --- updated-dependencies: - dependency-name: System.IO.Packaging dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- osu.Desktop/osu.Desktop.csproj | 74 +++++++++++++++++----------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 342b28f5ef..8afd7ac819 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -1,37 +1,37 @@ - - - net8.0 - WinExe - true - A free-to-win rhythm game. Rhythm is just a *click* away! - osu! - osu!(lazer) - osu! - osu!(lazer) - lazer.ico - 0.0.0 - 0.0.0 - - - osu.Desktop.Program - - - - - - - - - - - - - - - - - - - - - + + + net8.0 + WinExe + true + A free-to-win rhythm game. Rhythm is just a *click* away! + osu! + osu!(lazer) + osu! + osu!(lazer) + lazer.ico + 0.0.0 + 0.0.0 + + + osu.Desktop.Program + + + + + + + + + + + + + + + + + + + + + From 68ebf64b2dc47edffef513f90bf306ea31a9b9ed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Oct 2024 12:36:39 +0900 Subject: [PATCH 471/924] Fix dependabot line endings --- osu.Desktop/osu.Desktop.csproj | 74 +++++++++++++++++----------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 8afd7ac819..904f5edf2b 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -1,37 +1,37 @@ - - - net8.0 - WinExe - true - A free-to-win rhythm game. Rhythm is just a *click* away! - osu! - osu!(lazer) - osu! - osu!(lazer) - lazer.ico - 0.0.0 - 0.0.0 - - - osu.Desktop.Program - - - - - - - - - - - - - - - - - - - - - + + + net8.0 + WinExe + true + A free-to-win rhythm game. Rhythm is just a *click* away! + osu! + osu!(lazer) + osu! + osu!(lazer) + lazer.ico + 0.0.0 + 0.0.0 + + + osu.Desktop.Program + + + + + + + + + + + + + + + + + + + + + From 7ccb7700090f875340d8aea2a96d95e012e827b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Oct 2024 12:36:51 +0900 Subject: [PATCH 472/924] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index f943ee727c..7844dcc1fe 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index 51b7bfbf91..9fc7cdf453 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + From 48dacd1c157099bec9718351a29f08968d53cdf3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Oct 2024 13:45:35 +0900 Subject: [PATCH 473/924] Remove unused property --- osu.Game/Overlays/MusicController.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index ffb1fcc032..2614774140 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -63,9 +63,6 @@ namespace osu.Game.Overlays public DrawableTrack CurrentTrack { get; private set; } = new DrawableTrack(new TrackVirtual(1000)); - [Resolved] - private RealmAccess realm { get; set; } = null!; - private IBindableList detachedBeatmaps = null!; private BindableNumber sampleVolume = null!; From d836dba98240e64014a1b0773a007328137d0706 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Oct 2024 14:18:34 +0900 Subject: [PATCH 474/924] Rename and xmldoc `RealmResetEmptySet` --- osu.Game/Database/DetachedBeatmapStore.cs | 2 +- osu.Game/Database/RealmAccess.cs | 2 +- .../Database/{EmptyRealmSet.cs => RealmResetEmptySet.cs} | 8 +++++++- 3 files changed, 9 insertions(+), 3 deletions(-) rename osu.Game/Database/{EmptyRealmSet.cs => RealmResetEmptySet.cs} (82%) diff --git a/osu.Game/Database/DetachedBeatmapStore.cs b/osu.Game/Database/DetachedBeatmapStore.cs index f5c975d9db..5b65f608b2 100644 --- a/osu.Game/Database/DetachedBeatmapStore.cs +++ b/osu.Game/Database/DetachedBeatmapStore.cs @@ -44,7 +44,7 @@ namespace osu.Game.Database { if (changes == null) { - if (sender is EmptyRealmSet) + if (sender is RealmResetEmptySet) { // Usually we'd reset stuff here, but doing so triggers a silly flow which ends up deadlocking realm. // Additionally, user should not be at song select when realm is blocking all operations in the first place. diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index cb91d6923b..ad0423191d 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -568,7 +568,7 @@ namespace osu.Game.Database lock (notificationsResetMap) { // Store an action which is used when blocking to ensure consumers don't use results of a stale changeset firing. - notificationsResetMap.Add(action, () => callback(new EmptyRealmSet(), null)); + notificationsResetMap.Add(action, () => callback(new RealmResetEmptySet(), null)); } return RegisterCustomSubscription(action); diff --git a/osu.Game/Database/EmptyRealmSet.cs b/osu.Game/Database/RealmResetEmptySet.cs similarity index 82% rename from osu.Game/Database/EmptyRealmSet.cs rename to osu.Game/Database/RealmResetEmptySet.cs index c34974cb03..9f9a1ba6d7 100644 --- a/osu.Game/Database/EmptyRealmSet.cs +++ b/osu.Game/Database/RealmResetEmptySet.cs @@ -12,7 +12,13 @@ using Realms.Schema; namespace osu.Game.Database { - public class EmptyRealmSet : IRealmCollection + /// + /// This can arrive in callbacks to imply that realm access has been reset. + /// + /// + /// Usually implies that the original database may return soon and the callback can usually be silently ignored. + /// + public class RealmResetEmptySet : IRealmCollection { private IList emptySet => Array.Empty(); From 081d87fe6df7688e9831691831e4cc55fc6d7da7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 9 Oct 2024 11:47:31 +0200 Subject: [PATCH 475/924] Revert 'Fix any and all migration attempts dying on MusicController' This reverts commit 310eec69fcb30fd89d00b46d6ecb9e99c94f4314. Way to try and "fix" stuff and screw stuff up even harder instead, me. Great job, well done. --- osu.Game/OsuGame.cs | 1 + osu.Game/OsuGameBase.cs | 4 ---- osu.Game/Overlays/MusicController.cs | 24 +++++++++++------------- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index a7f3cc8df1..dce24c6ee7 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1137,6 +1137,7 @@ namespace osu.Game loadComponentSingleFile(new MedalOverlay(), topMostOverlayContent.Add); loadComponentSingleFile(new BackgroundDataStoreProcessor(), Add); + loadComponentSingleFile(new DetachedBeatmapStore(), Add, true); Add(difficultyRecommender); Add(externalLinkOpener = new ExternalLinkOpener()); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 96e5484d93..d4704d1c72 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -377,10 +377,6 @@ namespace osu.Game dependencies.Cache(previewTrackManager = new PreviewTrackManager(BeatmapManager.BeatmapTrackStore)); base.Content.Add(previewTrackManager); - var detachedBeatmapStore = new DetachedBeatmapStore(); - base.Content.Add(detachedBeatmapStore); - dependencies.CacheAs(detachedBeatmapStore); - base.Content.Add(MusicController = new MusicController()); dependencies.CacheAs(MusicController); diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 2614774140..600c014a95 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Threading; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Track; @@ -63,7 +62,8 @@ namespace osu.Game.Overlays public DrawableTrack CurrentTrack { get; private set; } = new DrawableTrack(new TrackVirtual(1000)); - private IBindableList detachedBeatmaps = null!; + [Resolved] + private RealmAccess realm { get; set; } = null!; private BindableNumber sampleVolume = null!; @@ -76,15 +76,13 @@ namespace osu.Game.Overlays private int randomHistoryDirection; [BackgroundDependencyLoader] - private void load(AudioManager audio, OsuConfigManager configManager, DetachedBeatmapStore detachedBeatmapStore, CancellationToken? cancellationToken) + private void load(AudioManager audio, OsuConfigManager configManager) { AddInternal(audioDuckFilter = new AudioFilter(audio.TrackMixer)); audio.Tracks.AddAdjustment(AdjustableProperty.Volume, audioDuckVolume); sampleVolume = audio.VolumeSample.GetBoundCopy(); configManager.BindWith(OsuSetting.RandomSelectAlgorithm, randomSelectAlgorithm); - - detachedBeatmaps = detachedBeatmapStore.GetDetachedBeatmaps(cancellationToken); } protected override void LoadComplete() @@ -257,8 +255,8 @@ namespace osu.Game.Overlays playableSet = getNextRandom(-1, allowProtectedTracks); else { - playableSet = getBeatmapSets().TakeWhile(i => !i.Equals(current?.BeatmapSetInfo)).LastOrDefault(s => !s.Protected || allowProtectedTracks) - ?? getBeatmapSets().LastOrDefault(s => !s.Protected || allowProtectedTracks); + playableSet = getBeatmapSets().AsEnumerable().TakeWhile(i => !i.Equals(current?.BeatmapSetInfo)).LastOrDefault(s => !s.Protected || allowProtectedTracks) + ?? getBeatmapSets().AsEnumerable().LastOrDefault(s => !s.Protected || allowProtectedTracks); } if (playableSet != null) @@ -353,10 +351,10 @@ namespace osu.Game.Overlays playableSet = getNextRandom(1, allowProtectedTracks); else { - playableSet = getBeatmapSets().SkipWhile(i => !i.Equals(current?.BeatmapSetInfo)) + playableSet = getBeatmapSets().AsEnumerable().SkipWhile(i => !i.Equals(current?.BeatmapSetInfo)) .Where(i => !i.Protected || allowProtectedTracks) .ElementAtOrDefault(1) - ?? getBeatmapSets().FirstOrDefault(i => !i.Protected || allowProtectedTracks); + ?? getBeatmapSets().AsEnumerable().FirstOrDefault(i => !i.Protected || allowProtectedTracks); } var playableBeatmap = playableSet?.Beatmaps.FirstOrDefault(); @@ -375,7 +373,7 @@ namespace osu.Game.Overlays { BeatmapSetInfo result; - var possibleSets = getBeatmapSets().Where(s => !s.Protected || allowProtectedTracks).ToArray(); + var possibleSets = getBeatmapSets().AsEnumerable().Where(s => !s.Protected || allowProtectedTracks).ToArray(); if (possibleSets.Length == 0) return null; @@ -434,7 +432,7 @@ namespace osu.Game.Overlays private TrackChangeDirection? queuedDirection; - private IEnumerable getBeatmapSets() => detachedBeatmaps.Where(s => !s.DeletePending); + private IQueryable getBeatmapSets() => realm.Realm.All().Where(s => !s.DeletePending); private void changeBeatmap(WorkingBeatmap newWorking) { @@ -461,8 +459,8 @@ namespace osu.Game.Overlays else { // figure out the best direction based on order in playlist. - int last = getBeatmapSets().TakeWhile(b => !b.Equals(current.BeatmapSetInfo)).Count(); - int next = getBeatmapSets().TakeWhile(b => !b.Equals(newWorking.BeatmapSetInfo)).Count(); + int last = getBeatmapSets().AsEnumerable().TakeWhile(b => !b.Equals(current.BeatmapSetInfo)).Count(); + int next = getBeatmapSets().AsEnumerable().TakeWhile(b => !b.Equals(newWorking.BeatmapSetInfo)).Count(); direction = last > next ? TrackChangeDirection.Prev : TrackChangeDirection.Next; } From baa64d148627a09e55f0384ccb8ae0853f95b4f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 9 Oct 2024 11:52:58 +0200 Subject: [PATCH 476/924] Migrate `MusicController` to `Live` to fix broken migrations instead --- osu.Game/Overlays/MusicController.cs | 34 +++++++++++++++------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 600c014a95..971503ca8b 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -72,7 +72,7 @@ namespace osu.Game.Overlays private AudioFilter audioDuckFilter = null!; private readonly Bindable randomSelectAlgorithm = new Bindable(); - private readonly List previousRandomSets = new List(); + private readonly List> previousRandomSets = new List>(); private int randomHistoryDirection; [BackgroundDependencyLoader] @@ -249,19 +249,19 @@ namespace osu.Game.Overlays queuedDirection = TrackChangeDirection.Prev; - BeatmapSetInfo? playableSet; + Live? playableSet; if (Shuffle.Value) playableSet = getNextRandom(-1, allowProtectedTracks); else { - playableSet = getBeatmapSets().AsEnumerable().TakeWhile(i => !i.Equals(current?.BeatmapSetInfo)).LastOrDefault(s => !s.Protected || allowProtectedTracks) - ?? getBeatmapSets().AsEnumerable().LastOrDefault(s => !s.Protected || allowProtectedTracks); + playableSet = getBeatmapSets().TakeWhile(i => !i.Value.Equals(current?.BeatmapSetInfo)).LastOrDefault(s => !s.Value.Protected || allowProtectedTracks) + ?? getBeatmapSets().LastOrDefault(s => !s.Value.Protected || allowProtectedTracks); } if (playableSet != null) { - changeBeatmap(beatmaps.GetWorkingBeatmap(playableSet.Beatmaps.First())); + changeBeatmap(beatmaps.GetWorkingBeatmap(playableSet.Value.Beatmaps.First())); restartTrack(); return PreviousTrackResult.Previous; } @@ -345,19 +345,19 @@ namespace osu.Game.Overlays queuedDirection = TrackChangeDirection.Next; - BeatmapSetInfo? playableSet; + Live? playableSet; if (Shuffle.Value) playableSet = getNextRandom(1, allowProtectedTracks); else { - playableSet = getBeatmapSets().AsEnumerable().SkipWhile(i => !i.Equals(current?.BeatmapSetInfo)) - .Where(i => !i.Protected || allowProtectedTracks) + playableSet = getBeatmapSets().SkipWhile(i => !i.Value.Equals(current?.BeatmapSetInfo)) + .Where(i => !i.Value.Protected || allowProtectedTracks) .ElementAtOrDefault(1) - ?? getBeatmapSets().AsEnumerable().FirstOrDefault(i => !i.Protected || allowProtectedTracks); + ?? getBeatmapSets().FirstOrDefault(i => !i.Value.Protected || allowProtectedTracks); } - var playableBeatmap = playableSet?.Beatmaps.FirstOrDefault(); + var playableBeatmap = playableSet?.Value.Beatmaps.FirstOrDefault(); if (playableBeatmap != null) { @@ -369,11 +369,11 @@ namespace osu.Game.Overlays return false; } - private BeatmapSetInfo? getNextRandom(int direction, bool allowProtectedTracks) + private Live? getNextRandom(int direction, bool allowProtectedTracks) { - BeatmapSetInfo result; + Live result; - var possibleSets = getBeatmapSets().AsEnumerable().Where(s => !s.Protected || allowProtectedTracks).ToArray(); + var possibleSets = getBeatmapSets().Where(s => !s.Value.Protected || allowProtectedTracks).ToArray(); if (possibleSets.Length == 0) return null; @@ -432,7 +432,9 @@ namespace osu.Game.Overlays private TrackChangeDirection? queuedDirection; - private IQueryable getBeatmapSets() => realm.Realm.All().Where(s => !s.DeletePending); + private IEnumerable> getBeatmapSets() => realm.Realm.All().Where(s => !s.DeletePending) + .AsEnumerable() + .Select(s => new RealmLive(s, realm)); private void changeBeatmap(WorkingBeatmap newWorking) { @@ -459,8 +461,8 @@ namespace osu.Game.Overlays else { // figure out the best direction based on order in playlist. - int last = getBeatmapSets().AsEnumerable().TakeWhile(b => !b.Equals(current.BeatmapSetInfo)).Count(); - int next = getBeatmapSets().AsEnumerable().TakeWhile(b => !b.Equals(newWorking.BeatmapSetInfo)).Count(); + int last = getBeatmapSets().TakeWhile(b => !b.Value.Equals(current.BeatmapSetInfo)).Count(); + int next = getBeatmapSets().TakeWhile(b => !b.Value.Equals(newWorking.BeatmapSetInfo)).Count(); direction = last > next ? TrackChangeDirection.Prev : TrackChangeDirection.Next; } From 9936ec579fbac00fec0f94684d470d28e2d35da1 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 9 Oct 2024 23:31:12 +0200 Subject: [PATCH 477/924] Fix isSplittable depending on unreliable order of path control point pieces --- .../Blueprints/Sliders/Components/PathControlPointVisualiser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 239bd1100a..70ccbdfdc4 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -178,7 +178,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private bool isSplittable(PathControlPointPiece p) => // A hit object can only be split on control points which connect two different path segments. - p.ControlPoint.Type.HasValue && p != Pieces.FirstOrDefault() && p != Pieces.LastOrDefault(); + p.ControlPoint.Type.HasValue && p.ControlPoint != controlPoints.FirstOrDefault() && p.ControlPoint != controlPoints.LastOrDefault(); private void onControlPointsChanged(object sender, NotifyCollectionChangedEventArgs e) { From a7fcfd5f0d19efcc8b6dda30749dac50563a0500 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 10 Oct 2024 13:54:32 +0200 Subject: [PATCH 478/924] Fix discord RPC complaining yet again if given a single space character as activity state / details Closes https://github.com/ppy/osu/issues/30178. Really, discord? --- osu.Desktop/DiscordRichPresence.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 780d367900..5a7a01df1b 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -279,10 +279,12 @@ namespace osu.Desktop // As above, discord decides that *non-empty* strings shorter than 2 characters cannot possibly be valid input, because... reasons? // And yes, that is two *characters*, or *codepoints*, not *bytes* as further down below (as determined by empirical testing). - // That seems very questionable, and isn't even documented anywhere. So to *make it* accept such valid input, - // just tack on enough of U+200B ZERO WIDTH SPACEs at the end. - if (str.Length < 2) - return str.PadRight(2, '\u200B'); + // Also, spaces don't count. Because reasons, clearly. + // That all seems very questionable, and isn't even documented anywhere. So to *make it* accept such valid input, + // just tack on enough of U+200B ZERO WIDTH SPACEs at the end. After making sure to trim whitespace. + string trimmed = str.Trim(); + if (trimmed.Length < 2) + return trimmed.PadRight(2, '\u200B'); if (Encoding.UTF8.GetByteCount(str) <= 128) return str; From f1842d781e8ab4e371c177ae19d66a05383c798d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 10 Oct 2024 13:04:27 +0200 Subject: [PATCH 479/924] Decouple `AdvancedStats` from global mods Closes https://github.com/ppy/osu/issues/30163. If I'm to be blunt, the decoupled stuff in song select makes my head spin. I spent a solid 20 minutes thinking how I was going to fix this one but then finally realised that generally most of the cause there was the fact that `AdvancedStats` was seeing the new rulesets *before* the "ensure global selected mods are valid for current ruleset" logic, and so decided to just _delay_ that until the decoupled transfer thingamajig happens. I was honestly considering combining `BeatmapInfo`, `Ruleset`, and `Mods` into one property on `AdvancedStats`. I figured I'd rather not push my luck and try the baseline version first, but I honestly think that direction is going to be required at some point to properly corral all of the decoupled madness taking place in song select. --- .../SongSelect/TestSceneAdvancedStats.cs | 8 +++---- .../Screens/Select/Details/AdvancedStats.cs | 23 +++++++++++-------- osu.Game/Screens/Select/SongSelect.cs | 13 +++++++---- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs index 3b89c70a63..ca6c4998d1 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select EZ mod", () => { var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance().AsNonNull(); - SelectedMods.Value = new[] { ruleset.CreateMod() }; + advancedStats.Mods.Value = new[] { ruleset.CreateMod() }; }); AddAssert("circle size bar is blue", () => barIsBlue(advancedStats.FirstValue)); @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select HR mod", () => { var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance().AsNonNull(); - SelectedMods.Value = new[] { ruleset.CreateMod() }; + advancedStats.Mods.Value = new[] { ruleset.CreateMod() }; }); AddAssert("circle size bar is red", () => barIsRed(advancedStats.FirstValue)); @@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.SongSelect var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance().AsNonNull(); var difficultyAdjustMod = ruleset.CreateMod().AsNonNull(); difficultyAdjustMod.ReadFromDifficulty(advancedStats.BeatmapInfo.Difficulty); - SelectedMods.Value = new[] { difficultyAdjustMod }; + advancedStats.Mods.Value = new[] { difficultyAdjustMod }; }); AddAssert("circle size bar is white", () => barIsWhite(advancedStats.FirstValue)); @@ -143,7 +143,7 @@ namespace osu.Game.Tests.Visual.SongSelect difficultyAdjustMod.ReadFromDifficulty(originalDifficulty); difficultyAdjustMod.DrainRate.Value = originalDifficulty.DrainRate - 0.5f; difficultyAdjustMod.ApproachRate.Value = originalDifficulty.ApproachRate + 2.2f; - SelectedMods.Value = new[] { difficultyAdjustMod }; + advancedStats.Mods.Value = new[] { difficultyAdjustMod }; }); AddAssert("circle size bar is white", () => barIsWhite(advancedStats.FirstValue)); diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 1da890100e..b7086d2416 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -36,9 +37,6 @@ namespace osu.Game.Screens.Select.Details [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } - [Resolved] - private IBindable> mods { get; set; } - protected readonly StatisticRow FirstValue, HpDrain, Accuracy, ApproachRate; private readonly StatisticRow starDifficulty; @@ -69,6 +67,14 @@ namespace osu.Game.Screens.Select.Details /// public Bindable Ruleset { get; } = new Bindable(); + /// + /// Mods to be used for certain elements of display. + /// + /// + /// No checks are done as to whether the mods specified are valid for the current . + /// + public Bindable> Mods { get; } = new Bindable>(Array.Empty()); + public AdvancedStats(int columns = 1) { switch (columns) @@ -143,8 +149,7 @@ namespace osu.Game.Screens.Select.Details base.LoadComplete(); Ruleset.BindValueChanged(_ => updateStatistics()); - - mods.BindValueChanged(modsChanged, true); + Mods.BindValueChanged(modsChanged, true); } private ModSettingChangeTracker modSettingChangeTracker; @@ -173,14 +178,14 @@ namespace osu.Game.Screens.Select.Details { BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(baseDifficulty); - foreach (var mod in mods.Value.OfType()) + foreach (var mod in Mods.Value.OfType()) mod.ApplyToDifficulty(originalDifficulty); adjustedDifficulty = originalDifficulty; if (Ruleset.Value != null) { - double rate = ModUtils.CalculateRateWithMods(mods.Value); + double rate = ModUtils.CalculateRateWithMods(Mods.Value); adjustedDifficulty = Ruleset.Value.CreateInstance().GetRateAdjustedDisplayDifficulty(originalDifficulty, rate); @@ -198,7 +203,7 @@ namespace osu.Game.Screens.Select.Details // For the time being, the key count is static no matter what, because: // a) The method doesn't have knowledge of the active keymods. Doing so may require considerations for filtering. // b) Using the difficulty adjustment mod to adjust OD doesn't have an effect on conversion. - int keyCount = baseDifficulty == null ? 0 : legacyRuleset.GetKeyCount(BeatmapInfo, mods.Value); + int keyCount = baseDifficulty == null ? 0 : legacyRuleset.GetKeyCount(BeatmapInfo, Mods.Value); FirstValue.Title = BeatmapsetsStrings.ShowStatsCsMania; FirstValue.Value = (keyCount, keyCount); @@ -236,7 +241,7 @@ namespace osu.Game.Screens.Select.Details starDifficultyCancellationSource = new CancellationTokenSource(); var normalStarDifficultyTask = difficultyCache.GetDifficultyAsync(BeatmapInfo, Ruleset.Value, null, starDifficultyCancellationSource.Token); - var moddedStarDifficultyTask = difficultyCache.GetDifficultyAsync(BeatmapInfo, Ruleset.Value, mods.Value, starDifficultyCancellationSource.Token); + var moddedStarDifficultyTask = difficultyCache.GetDifficultyAsync(BeatmapInfo, Ruleset.Value, Mods.Value, starDifficultyCancellationSource.Token); Task.WhenAll(normalStarDifficultyTask, moddedStarDifficultyTask).ContinueWith(_ => Schedule(() => { diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 18608d61e9..ea5048ca49 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -610,11 +610,6 @@ namespace osu.Game.Screens.Select beatmapInfoPrevious = beatmap; } - // we can't run this in the debounced run due to the selected mods bindable not being debounced, - // since mods could be updated to the new ruleset instances while the decoupled bindable is held behind, - // therefore resulting in performing difficulty calculation with invalid states. - advancedStats.Ruleset.Value = ruleset; - void run() { // clear pending task immediately to track any potential nested debounce operation. @@ -878,6 +873,8 @@ namespace osu.Game.Screens.Select ModSelect.Beatmap.Value = beatmap; advancedStats.BeatmapInfo = beatmap.BeatmapInfo; + advancedStats.Mods.Value = selectedMods.Value; + advancedStats.Ruleset.Value = Ruleset.Value; bool beatmapSelected = beatmap is not DummyWorkingBeatmap; @@ -990,6 +987,12 @@ namespace osu.Game.Screens.Select Beatmap.BindValueChanged(updateCarouselSelection); + selectedMods.BindValueChanged(_ => + { + if (decoupledRuleset.Value.Equals(rulesetNoDebounce)) + advancedStats.Mods.Value = selectedMods.Value; + }, true); + boundLocalBindables = true; } From 687bdad389cac766f66223acea9eb5187ec496f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 10 Oct 2024 13:12:47 +0200 Subject: [PATCH 480/924] Remove no-longer-required cache-over hack This is now removable after `AdvancedStats` has been weaned off the global mods bindable. I think this is a win all things considered? --- osu.Game/Overlays/BeatmapSetOverlay.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 873336bb6e..8de21129d3 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -3,8 +3,6 @@ #nullable disable -using System; -using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -16,8 +14,6 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Overlays.Comments; -using osu.Game.Rulesets.Mods; -using osu.Game.Screens.Select.Details; using osuTK; using osuTK.Graphics; @@ -37,14 +33,6 @@ namespace osu.Game.Overlays private (BeatmapSetLookupType type, int id)? lastLookup; - /// - /// Isolates the beatmap set overlay from the game-wide selected mods bindable - /// to avoid affecting the beatmap details section (i.e. ). - /// - [Cached] - [Cached(typeof(IBindable>))] - protected readonly Bindable> SelectedMods = new Bindable>(Array.Empty()); - public BeatmapSetOverlay() : base(OverlayColourScheme.Blue) { From 938c3d78ce3fe19f6fbaa177c219c3ec1b07d0af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 10 Oct 2024 14:36:28 +0200 Subject: [PATCH 481/924] Fix argon song progress bar tooltip showing during active gameplay Closes https://github.com/ppy/osu/issues/30197. Pretty bad one, might be worth a hotfix... --- osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs index ace21fa955..9ab2366b3e 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs @@ -41,6 +41,9 @@ namespace osu.Game.Screens.Play.HUD { get { + if (!Interactive) + return default; + double progress = Math.Clamp(lastMouseX, 0, DrawWidth) / DrawWidth; TimeSpan currentSpan = TimeSpan.FromMilliseconds(Math.Round((EndTime - StartTime) * progress)); From 6b532824b1b07605f45e314900186a410649ea9a Mon Sep 17 00:00:00 2001 From: WitherFlower <52672543+WitherFlower@users.noreply.github.com> Date: Wed, 9 Oct 2024 13:29:03 +0200 Subject: [PATCH 482/924] Fix code quality and formatting issues --- .../Filtering/FilterQueryParserTest.cs | 52 +++++++++---------- osu.Game/Screens/Select/FilterQueryParser.cs | 44 +++++----------- 2 files changed, 38 insertions(+), 58 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index ad3be17c15..c8f063719d 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -632,32 +632,32 @@ namespace osu.Game.Tests.NonVisual.Filtering private static readonly object[] ranked_date_valid_test_cases = { - new object[] { "ranked<2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Max }, - new object[] { "ranked<2012.03", dateTimeOffsetFromDateOnly(2012, 3, 1), (FilterCriteria x) => x.DateRanked.Max }, - new object[] { "ranked<2012/03/05", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Max }, - new object[] { "ranked<2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<2012.03", dateTimeOffsetFromDateOnly(2012, 3, 1), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<2012/03/05", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Max }, - new object[] { "ranked<=2012", dateTimeOffsetFromDateOnly(2013, 1, 1), (FilterCriteria x) => x.DateRanked.Max }, - new object[] { "ranked<=2012.03", dateTimeOffsetFromDateOnly(2012, 4, 1), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<=2012", dateTimeOffsetFromDateOnly(2013, 1, 1), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<=2012.03", dateTimeOffsetFromDateOnly(2012, 4, 1), (FilterCriteria x) => x.DateRanked.Max }, new object[] { "ranked<=2012/03/05", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Max }, - new object[] { "ranked<=2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked<=2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Max }, - new object[] { "ranked>2012", dateTimeOffsetFromDateOnly(2013, 1, 1), (FilterCriteria x) => x.DateRanked.Min }, - new object[] { "ranked>2012.03", dateTimeOffsetFromDateOnly(2012, 4, 1), (FilterCriteria x) => x.DateRanked.Min }, - new object[] { "ranked>2012/03/05", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Min }, - new object[] { "ranked>2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>2012", dateTimeOffsetFromDateOnly(2013, 1, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>2012.03", dateTimeOffsetFromDateOnly(2012, 4, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>2012/03/05", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Min }, - new object[] { "ranked>=2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Min }, - new object[] { "ranked>=2012.03", dateTimeOffsetFromDateOnly(2012, 3, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>=2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>=2012.03", dateTimeOffsetFromDateOnly(2012, 3, 1), (FilterCriteria x) => x.DateRanked.Min }, new object[] { "ranked>=2012/03/05", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Min }, - new object[] { "ranked>=2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked>=2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Min }, - new object[] { "ranked=2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Min }, - new object[] { "ranked=2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Min }, - new object[] { "ranked=2012-03", dateTimeOffsetFromDateOnly(2012, 3, 1), (FilterCriteria x) => x.DateRanked.Min }, - new object[] { "ranked=2012-03", dateTimeOffsetFromDateOnly(2012, 4, 1), (FilterCriteria x) => x.DateRanked.Max }, - new object[] { "ranked=2012-03-05", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Min }, - new object[] { "ranked=2012-03-05", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked=2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked=2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked=2012-03", dateTimeOffsetFromDateOnly(2012, 3, 1), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked=2012-03", dateTimeOffsetFromDateOnly(2012, 4, 1), (FilterCriteria x) => x.DateRanked.Max }, + new object[] { "ranked=2012-03-05", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Min }, + new object[] { "ranked=2012-03-05", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Max }, }; [Test] @@ -688,13 +688,13 @@ namespace osu.Game.Tests.NonVisual.Filtering private static readonly object[] submitted_date_test_cases = { - new object[] { "submitted<2012", true }, - new object[] { "submitted<2012.03", true }, - new object[] { "submitted<2012/03/05", true }, - new object[] { "submitted<2012-3-5", true }, + new object[] { "submitted<2012", true }, + new object[] { "submitted<2012.03", true }, + new object[] { "submitted<2012/03/05", true }, + new object[] { "submitted<2012-3-5", true }, - new object[] { "submitted<0", false }, - new object[] { "submitted=99999", false }, + new object[] { "submitted<0", false }, + new object[] { "submitted=99999", false }, new object[] { "submitted>=2012-03-05-04", false }, new object[] { "submitted>=2012/03.05-04", false }, }; diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index d6f46c0fbd..9f29459012 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -642,7 +642,6 @@ namespace osu.Game.Screens.Select switch (key) { - case @"year": year = (int)value; break; @@ -670,14 +669,8 @@ namespace osu.Game.Screens.Select switch (op) { case Operator.Less: - if (month == null) - { - month = 1; - day = 1; - } - - if (day == null) - day = 1; + month ??= 1; + day ??= 1; dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value); return tryUpdateCriteriaRange(ref dateRange, op, dateTimeOffset); @@ -690,6 +683,7 @@ namespace osu.Game.Screens.Select dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddYears(1); return tryUpdateCriteriaRange(ref dateRange, Operator.Less, dateTimeOffset); } + if (day == null) { day = 1; @@ -701,14 +695,8 @@ namespace osu.Game.Screens.Select return tryUpdateCriteriaRange(ref dateRange, Operator.Less, dateTimeOffset); case Operator.GreaterOrEqual: - if (month == null) - { - month = 1; - day = 1; - } - - if (day == null) - day = 1; + month ??= 1; + day ??= 1; dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value); return tryUpdateCriteriaRange(ref dateRange, op, dateTimeOffset); @@ -721,6 +709,7 @@ namespace osu.Game.Screens.Select dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddYears(1); return tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, dateTimeOffset); } + if (day == null) { day = 1; @@ -742,11 +731,8 @@ namespace osu.Game.Screens.Select day = 1; minDateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value); maxDateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddYears(1); - return ( - tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, minDateTimeOffset) - && - tryUpdateCriteriaRange(ref dateRange, Operator.Less, maxDateTimeOffset) - ); + return tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, minDateTimeOffset) + && tryUpdateCriteriaRange(ref dateRange, Operator.Less, maxDateTimeOffset); } if (day == null) @@ -754,21 +740,15 @@ namespace osu.Game.Screens.Select day = 1; minDateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value); maxDateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddMonths(1); - return ( - tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, minDateTimeOffset) - && - tryUpdateCriteriaRange(ref dateRange, Operator.Less, maxDateTimeOffset) - ); + return tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, minDateTimeOffset) + && tryUpdateCriteriaRange(ref dateRange, Operator.Less, maxDateTimeOffset); } minDateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value); maxDateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddDays(1); + return tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, minDateTimeOffset) + && tryUpdateCriteriaRange(ref dateRange, Operator.Less, maxDateTimeOffset); - return ( - tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, minDateTimeOffset) - && - tryUpdateCriteriaRange(ref dateRange, Operator.Less, maxDateTimeOffset) - ); default: return false; } From eb2f1d09f87d6b6dd296ac85e2c46f576304c5af Mon Sep 17 00:00:00 2001 From: WitherFlower <52672543+WitherFlower@users.noreply.github.com> Date: Thu, 10 Oct 2024 17:42:43 +0200 Subject: [PATCH 483/924] Improve regex readability by using character set --- osu.Game/Screens/Select/FilterQueryParser.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 9f29459012..ccffd34dc2 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -617,9 +617,7 @@ namespace osu.Game.Screens.Select /// The string value to attempt parsing for. private static bool tryUpdateRankedDateRange(ref FilterCriteria.OptionalRange dateRange, Operator op, string val) { - GroupCollection? match = null; - - match ??= tryMatchRegex(val, @"^(?\d+)((-|/|\.)(?\d+)((-|/|\.)(?\d+))?)?$"); + GroupCollection? match = tryMatchRegex(val, @"^(?\d+)([-/.](?\d+)([-/.](?\d+))?)?$"); if (match == null) return false; From 9cc6ee2ebca06e9c325aeb67f97d2421debc7fc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Thu, 10 Oct 2024 20:14:11 +0200 Subject: [PATCH 484/924] Fix SelectionBox buttons freezing when button is triggered via key event --- .../Edit/Compose/Components/SelectionBox.cs | 20 +++++++++++++++---- .../Compose/Components/SelectionBoxButton.cs | 16 ++++++++++++--- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index d685fe74b0..2171ba696f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -150,13 +150,25 @@ namespace osu.Game.Screens.Edit.Compose.Components switch (e.Key) { case Key.G: - return CanReverse && reverseButton?.TriggerClick() == true; + if (!CanReverse || reverseButton == null) + return false; + + reverseButton.TriggerAction(); + return true; case Key.Comma: - return canRotate.Value && rotateCounterClockwiseButton?.TriggerClick() == true; + if (!canRotate.Value || rotateCounterClockwiseButton == null) + return false; + + rotateCounterClockwiseButton.TriggerAction(); + return true; case Key.Period: - return canRotate.Value && rotateClockwiseButton?.TriggerClick() == true; + if (!canRotate.Value || rotateClockwiseButton == null) + return false; + + rotateClockwiseButton.TriggerAction(); + return true; } return base.OnKeyDown(e); @@ -285,7 +297,7 @@ namespace osu.Game.Screens.Edit.Compose.Components Action = action }; - button.OperationStarted += freezeButtonPosition; + button.Clicked += freezeButtonPosition; button.HoverLost += unfreezeButtonPosition; button.OperationStarted += operationStarted; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs index e355add40b..1d2150dc1d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs @@ -21,6 +21,8 @@ namespace osu.Game.Screens.Edit.Compose.Components public Action? Action; + public event Action? Clicked; + public event Action? HoverLost; public SelectionBoxButton(IconUsage iconUsage, string tooltip) @@ -51,9 +53,10 @@ namespace osu.Game.Screens.Edit.Compose.Components { Circle.FlashColour(Colours.GrayF, 300); - TriggerOperationStarted(); - Action?.Invoke(); - TriggerOperationEnded(); + Clicked?.Invoke(); + + TriggerAction(); + return true; } @@ -71,5 +74,12 @@ namespace osu.Game.Screens.Edit.Compose.Components } public LocalisableString TooltipText { get; } + + internal void TriggerAction() + { + TriggerOperationStarted(); + Action?.Invoke(); + TriggerOperationEnded(); + } } } From fc7ad96fcd99176320650bd3b57479281e4c3390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Thu, 10 Oct 2024 20:20:02 +0200 Subject: [PATCH 485/924] Move circle flash to `TriggerAction` --- .../Screens/Edit/Compose/Components/SelectionBoxButton.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs index 1d2150dc1d..88272c2e5d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs @@ -51,8 +51,6 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override bool OnClick(ClickEvent e) { - Circle.FlashColour(Colours.GrayF, 300); - Clicked?.Invoke(); TriggerAction(); @@ -77,6 +75,8 @@ namespace osu.Game.Screens.Edit.Compose.Components internal void TriggerAction() { + Circle.FlashColour(Colours.GrayF, 300); + TriggerOperationStarted(); Action?.Invoke(); TriggerOperationEnded(); From af55585dc8ab2ba743224404b0cf901b8ffd2865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Thu, 10 Oct 2024 20:50:47 +0200 Subject: [PATCH 486/924] Make `TriggerAction` public --- osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs index 88272c2e5d..8f263cdf4f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs @@ -73,7 +73,7 @@ namespace osu.Game.Screens.Edit.Compose.Components public LocalisableString TooltipText { get; } - internal void TriggerAction() + public void TriggerAction() { Circle.FlashColour(Colours.GrayF, 300); From da376c534b6c5e2e7f3f8cb380f6bddd9a7496f6 Mon Sep 17 00:00:00 2001 From: WitherFlower <52672543+WitherFlower@users.noreply.github.com> Date: Fri, 11 Oct 2024 09:49:47 +0200 Subject: [PATCH 487/924] Filter out unranked/unsubmitted beatmaps when using the ranked/submitted search filters --- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index aa064f97c9..3947cefb91 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -66,8 +66,8 @@ namespace osu.Game.Screens.Select.Carousel match &= !criteria.OverallDifficulty.HasFilter || criteria.OverallDifficulty.IsInRange(BeatmapInfo.Difficulty.OverallDifficulty); match &= !criteria.Length.HasFilter || criteria.Length.IsInRange(BeatmapInfo.Length); match &= !criteria.LastPlayed.HasFilter || criteria.LastPlayed.IsInRange(BeatmapInfo.LastPlayed ?? DateTimeOffset.MinValue); - match &= !criteria.DateRanked.HasFilter || criteria.DateRanked.IsInRange(BeatmapInfo.BeatmapSet?.DateRanked ?? DateTimeOffset.MinValue); - match &= !criteria.DateSubmitted.HasFilter || criteria.DateSubmitted.IsInRange(BeatmapInfo.BeatmapSet?.DateSubmitted ?? DateTimeOffset.MinValue); + match &= !criteria.DateRanked.HasFilter || (BeatmapInfo.BeatmapSet?.DateRanked != null && criteria.DateRanked.IsInRange(BeatmapInfo.BeatmapSet.DateRanked.Value)); + match &= !criteria.DateSubmitted.HasFilter || (BeatmapInfo.BeatmapSet?.DateSubmitted != null && criteria.DateSubmitted.IsInRange(BeatmapInfo.BeatmapSet.DateSubmitted.Value)); match &= !criteria.BPM.HasFilter || criteria.BPM.IsInRange(BeatmapInfo.BPM); match &= !criteria.BeatDivisor.HasFilter || criteria.BeatDivisor.IsInRange(BeatmapInfo.BeatDivisor); From 88fa6f6bc61086e9b37c00618bb5025e2c39858a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Fri, 11 Oct 2024 11:33:53 +0200 Subject: [PATCH 488/924] Optimize `PolygonGenerationPopover` --- .../Edit/PolygonGenerationPopover.cs | 47 ++++++++++++------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs index 6325de5851..c46e2650bc 100644 --- a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs @@ -118,10 +118,10 @@ namespace osu.Game.Rulesets.Osu.Edit changeHandler?.BeginChange(); began = true; - distanceSnapInput.Current.BindValueChanged(_ => tryCreatePolygon()); - offsetAngleInput.Current.BindValueChanged(_ => tryCreatePolygon()); - repeatCountInput.Current.BindValueChanged(_ => tryCreatePolygon()); - pointInput.Current.BindValueChanged(_ => tryCreatePolygon()); + distanceSnapInput.Current.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); + offsetAngleInput.Current.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); + repeatCountInput.Current.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); + pointInput.Current.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); tryCreatePolygon(); } @@ -136,23 +136,34 @@ namespace osu.Game.Rulesets.Osu.Edit double length = distanceSnapInput.Current.Value * velocity * timeSpacing; float polygonRadius = (float)(length / (2 * Math.Sin(double.Pi / pointInput.Current.Value))); - editorBeatmap.RemoveRange(insertedCircles); - insertedCircles.Clear(); + int totalPoints = pointInput.Current.Value * repeatCountInput.Current.Value; + + if (insertedCircles.Count > totalPoints) + { + editorBeatmap.RemoveRange(insertedCircles.GetRange(totalPoints + 1, insertedCircles.Count - totalPoints - 1)); + insertedCircles.RemoveRange(totalPoints + 1, insertedCircles.Count - totalPoints - 1); + } var selectionHandler = (EditorSelectionHandler)composer.BlueprintContainer.SelectionHandler; bool first = true; - for (int i = 1; i <= pointInput.Current.Value * repeatCountInput.Current.Value; ++i) + var newlyAdded = new List(); + + for (int i = 0; i < totalPoints; ++i) { - float angle = float.DegreesToRadians(offsetAngleInput.Current.Value) + i * (2 * float.Pi / pointInput.Current.Value); + float angle = float.DegreesToRadians(offsetAngleInput.Current.Value) + (i + 1) * (2 * float.Pi / pointInput.Current.Value); var position = OsuPlayfield.BASE_SIZE / 2 + new Vector2(polygonRadius * float.Cos(angle), polygonRadius * float.Sin(angle)); - var circle = new HitCircle - { - Position = position, - StartTime = startTime, - NewCombo = first && selectionHandler.SelectionNewComboState.Value == TernaryState.True, - }; + bool alreadyAdded = i < insertedCircles.Count; + + var circle = alreadyAdded + ? insertedCircles[i] + : new HitCircle(); + + circle.Position = position; + circle.StartTime = startTime; + circle.NewCombo = first && selectionHandler.SelectionNewComboState.Value == TernaryState.True; + // TODO: probably ensure samples also follow current ternary status (not trivial) circle.Samples.Add(circle.CreateHitSampleInfo()); @@ -162,13 +173,17 @@ namespace osu.Game.Rulesets.Osu.Edit return; } - insertedCircles.Add(circle); + if (!alreadyAdded) + newlyAdded.Add(circle); + startTime = beatSnapProvider.SnapTime(startTime + timeSpacing); first = false; } - editorBeatmap.AddRange(insertedCircles); + insertedCircles.AddRange(newlyAdded); + editorBeatmap.AddRange(newlyAdded); + commitButton.Enabled.Value = true; } From 96769897bfbbf7551dbd5ee04fd89e98936819b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Fri, 11 Oct 2024 11:38:32 +0200 Subject: [PATCH 489/924] Extract method for scheduler call --- osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs index c46e2650bc..91a8d56fc1 100644 --- a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs @@ -118,13 +118,15 @@ namespace osu.Game.Rulesets.Osu.Edit changeHandler?.BeginChange(); began = true; - distanceSnapInput.Current.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); - offsetAngleInput.Current.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); - repeatCountInput.Current.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); - pointInput.Current.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); + distanceSnapInput.Current.BindValueChanged(_ => scheduleRefresh()); + offsetAngleInput.Current.BindValueChanged(_ => scheduleRefresh()); + repeatCountInput.Current.BindValueChanged(_ => scheduleRefresh()); + pointInput.Current.BindValueChanged(_ => scheduleRefresh()); tryCreatePolygon(); } + private void scheduleRefresh() => Scheduler.AddOnce(tryCreatePolygon); + private void tryCreatePolygon() { double startTime = beatSnapProvider.SnapTime(editorClock.CurrentTime); From 5c74fceff6ca7e8ea49f14bd2319a56fa8016134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Fri, 11 Oct 2024 11:46:57 +0200 Subject: [PATCH 490/924] Remove objects when polygon is invalid --- osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs index 91a8d56fc1..fb4640db82 100644 --- a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs @@ -172,6 +172,8 @@ namespace osu.Game.Rulesets.Osu.Edit if (position.X < 0 || position.Y < 0 || position.X > OsuPlayfield.BASE_SIZE.X || position.Y > OsuPlayfield.BASE_SIZE.Y) { commitButton.Enabled.Value = false; + editorBeatmap.RemoveRange(insertedCircles); + insertedCircles.Clear(); return; } From 7e439be9eca9e626a080defc0cd24da933072710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Fri, 11 Oct 2024 11:47:23 +0200 Subject: [PATCH 491/924] Fix crash from non-serializable hitobject samples --- osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs index fb4640db82..9605393f70 100644 --- a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs @@ -166,9 +166,6 @@ namespace osu.Game.Rulesets.Osu.Edit circle.StartTime = startTime; circle.NewCombo = first && selectionHandler.SelectionNewComboState.Value == TernaryState.True; - // TODO: probably ensure samples also follow current ternary status (not trivial) - circle.Samples.Add(circle.CreateHitSampleInfo()); - if (position.X < 0 || position.Y < 0 || position.X > OsuPlayfield.BASE_SIZE.X || position.Y > OsuPlayfield.BASE_SIZE.Y) { commitButton.Enabled.Value = false; @@ -178,8 +175,13 @@ namespace osu.Game.Rulesets.Osu.Edit } if (!alreadyAdded) + { newlyAdded.Add(circle); + // TODO: probably ensure samples also follow current ternary status (not trivial) + circle.Samples.Add(circle.CreateHitSampleInfo()); + } + startTime = beatSnapProvider.SnapTime(startTime + timeSpacing); first = false; From 0882f1bb70d957a09eff49b59776b9c652aece78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 11 Oct 2024 12:28:02 +0200 Subject: [PATCH 492/924] Add failing test case --- .../Menus/TestSceneMusicActionHandling.cs | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs index 4454501a96..907c84eae6 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs @@ -79,5 +79,82 @@ namespace osu.Game.Tests.Visual.Menus trackChangeQueue.Peek().changeDirection == TrackChangeDirection.Next); AddAssert("track actually changed", () => !trackChangeQueue.First().working.BeatmapInfo.Equals(trackChangeQueue.Last().working.BeatmapInfo)); } + + [Test] + public void TestShuffleBackwards() + { + Queue<(IWorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null!; + + AddStep("enable shuffle", () => Game.MusicController.Shuffle.Value = true); + + // ensure we have at least two beatmaps available to identify the direction the music controller navigated to. + AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo()), 5); + AddStep("ensure nonzero track duration", () => Game.Realm.Write(r => + { + // this was already supposed to be non-zero (see innards of `TestResources.CreateTestBeatmapSetInfo()`), + // but the non-zero value is being overwritten *to* zero by `BeatmapUpdater`. + // do a bit of a hack to change it back again - otherwise tracks are going to switch instantly and we won't be able to assert anything sane anymore. + foreach (var beatmap in r.All().Where(b => b.Length == 0)) + beatmap.Length = 60_000; + })); + + AddStep("bind to track change", () => + { + trackChangeQueue = new Queue<(IWorkingBeatmap, TrackChangeDirection)>(); + Game.MusicController.TrackChanged += (working, changeDirection) => trackChangeQueue.Enqueue((working, changeDirection)); + }); + + AddStep("seek track to 6 second", () => Game.MusicController.SeekTo(6000)); + AddUntilStep("wait for current time to update", () => Game.MusicController.CurrentTrack.CurrentTime > 5000); + + AddStep("press previous", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPrev)); + AddAssert("no track change", () => trackChangeQueue.Count == 0); + AddUntilStep("track restarted", () => Game.MusicController.CurrentTrack.CurrentTime < 5000); + + AddStep("press previous", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPrev)); + AddUntilStep("track changed", () => trackChangeQueue.Count == 1); + + AddStep("press previous", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPrev)); + AddUntilStep("track changed", () => trackChangeQueue.Count == 2); + + AddStep("press next", () => globalActionContainer.TriggerPressed(GlobalAction.MusicNext)); + AddUntilStep("track changed", () => + trackChangeQueue.Count == 3 && !trackChangeQueue.ElementAt(1).working.BeatmapInfo.Equals(trackChangeQueue.Last().working.BeatmapInfo)); + } + + [Test] + public void TestShuffleForwards() + { + Queue<(IWorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null!; + + AddStep("enable shuffle", () => Game.MusicController.Shuffle.Value = true); + + // ensure we have at least two beatmaps available to identify the direction the music controller navigated to. + AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo()), 5); + AddStep("ensure nonzero track duration", () => Game.Realm.Write(r => + { + // this was already supposed to be non-zero (see innards of `TestResources.CreateTestBeatmapSetInfo()`), + // but the non-zero value is being overwritten *to* zero by `BeatmapUpdater`. + // do a bit of a hack to change it back again - otherwise tracks are going to switch instantly and we won't be able to assert anything sane anymore. + foreach (var beatmap in r.All().Where(b => b.Length == 0)) + beatmap.Length = 60_000; + })); + + AddStep("bind to track change", () => + { + trackChangeQueue = new Queue<(IWorkingBeatmap, TrackChangeDirection)>(); + Game.MusicController.TrackChanged += (working, changeDirection) => trackChangeQueue.Enqueue((working, changeDirection)); + }); + + AddStep("press next", () => globalActionContainer.TriggerPressed(GlobalAction.MusicNext)); + AddUntilStep("track changed", () => trackChangeQueue.Count == 1); + + AddStep("press next", () => globalActionContainer.TriggerPressed(GlobalAction.MusicNext)); + AddUntilStep("track changed", () => trackChangeQueue.Count == 2); + + AddStep("press previous", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPrev)); + AddUntilStep("track changed", () => + trackChangeQueue.Count == 3 && !trackChangeQueue.ElementAt(1).working.BeatmapInfo.Equals(trackChangeQueue.Last().working.BeatmapInfo)); + } } } From 47cb696b6967999a0c48a091f1d6d0874e4fcb3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 11 Oct 2024 12:40:45 +0200 Subject: [PATCH 493/924] Fix switching direction when changing tracks with shuffle on restarting the same track Closes https://github.com/ppy/osu/issues/30190. --- osu.Game/Overlays/MusicController.cs | 94 ++++++++++++++++------------ 1 file changed, 55 insertions(+), 39 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 971503ca8b..c6c46da760 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -74,6 +74,7 @@ namespace osu.Game.Overlays private readonly Bindable randomSelectAlgorithm = new Bindable(); private readonly List> previousRandomSets = new List>(); private int randomHistoryDirection; + private int lastRandomTrackDirection; [BackgroundDependencyLoader] private void load(AudioManager audio, OsuConfigManager configManager) @@ -371,54 +372,69 @@ namespace osu.Game.Overlays private Live? getNextRandom(int direction, bool allowProtectedTracks) { - Live result; - - var possibleSets = getBeatmapSets().Where(s => !s.Value.Protected || allowProtectedTracks).ToArray(); - - if (possibleSets.Length == 0) - return null; - - // condition below checks if the signs of `randomHistoryDirection` and `direction` are opposite and not zero. - // if that is the case, it means that the user had previously chosen next track `randomHistoryDirection` times and wants to go back, - // or that the user had previously chosen previous track `randomHistoryDirection` times and wants to go forward. - // in both cases, it means that we have a history of previous random selections that we can rewind. - if (randomHistoryDirection * direction < 0) + try { - Debug.Assert(Math.Abs(randomHistoryDirection) == previousRandomSets.Count); - result = previousRandomSets[^1]; - previousRandomSets.RemoveAt(previousRandomSets.Count - 1); - randomHistoryDirection += direction; - return result; - } + Live result; - // if the early-return above didn't cover it, it means that we have no history to fall back on - // and need to actually choose something random. - switch (randomSelectAlgorithm.Value) - { - case RandomSelectAlgorithm.Random: - result = possibleSets[RNG.Next(possibleSets.Length)]; - break; + var possibleSets = getBeatmapSets().Where(s => !s.Value.Protected || allowProtectedTracks).ToArray(); - case RandomSelectAlgorithm.RandomPermutation: - var notYetPlayedSets = possibleSets.Except(previousRandomSets).ToArray(); + if (possibleSets.Length == 0) + return null; - if (notYetPlayedSets.Length == 0) + // condition below checks if the signs of `randomHistoryDirection` and `direction` are opposite and not zero. + // if that is the case, it means that the user had previously chosen next track `randomHistoryDirection` times and wants to go back, + // or that the user had previously chosen previous track `randomHistoryDirection` times and wants to go forward. + // in both cases, it means that we have a history of previous random selections that we can rewind. + if (randomHistoryDirection * direction < 0) + { + Debug.Assert(Math.Abs(randomHistoryDirection) == previousRandomSets.Count); + + // if the user has been shuffling backwards and now going forwards (or vice versa), + // the topmost item from history needs to be discarded because it's the *current* track. + if (direction * lastRandomTrackDirection < 0) { - notYetPlayedSets = possibleSets; - previousRandomSets.Clear(); - randomHistoryDirection = 0; + previousRandomSets.RemoveAt(previousRandomSets.Count - 1); + randomHistoryDirection += direction; } - result = notYetPlayedSets[RNG.Next(notYetPlayedSets.Length)]; - break; + result = previousRandomSets[^1]; + previousRandomSets.RemoveAt(previousRandomSets.Count - 1); + return result; + } - default: - throw new ArgumentOutOfRangeException(nameof(randomSelectAlgorithm), randomSelectAlgorithm.Value, "Unsupported random select algorithm"); + // if the early-return above didn't cover it, it means that we have no history to fall back on + // and need to actually choose something random. + switch (randomSelectAlgorithm.Value) + { + case RandomSelectAlgorithm.Random: + result = possibleSets[RNG.Next(possibleSets.Length)]; + break; + + case RandomSelectAlgorithm.RandomPermutation: + var notYetPlayedSets = possibleSets.Except(previousRandomSets).ToArray(); + + if (notYetPlayedSets.Length == 0) + { + notYetPlayedSets = possibleSets; + previousRandomSets.Clear(); + randomHistoryDirection = 0; + } + + result = notYetPlayedSets[RNG.Next(notYetPlayedSets.Length)]; + break; + + default: + throw new ArgumentOutOfRangeException(nameof(randomSelectAlgorithm), randomSelectAlgorithm.Value, "Unsupported random select algorithm"); + } + + previousRandomSets.Add(result); + return result; + } + finally + { + randomHistoryDirection += direction; + lastRandomTrackDirection = direction; } - - previousRandomSets.Add(result); - randomHistoryDirection += direction; - return result; } private void restartTrack() From 1a25e9d179cdf129ece99356e5998158ed0aef20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 11 Oct 2024 12:45:03 +0200 Subject: [PATCH 494/924] Add another failing test case for crash --- .../Menus/TestSceneMusicActionHandling.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs index 907c84eae6..32009dc8c2 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs @@ -156,5 +156,37 @@ namespace osu.Game.Tests.Visual.Menus AddUntilStep("track changed", () => trackChangeQueue.Count == 3 && !trackChangeQueue.ElementAt(1).working.BeatmapInfo.Equals(trackChangeQueue.Last().working.BeatmapInfo)); } + + [Test] + public void TestShuffleBackAndForth() + { + Queue<(IWorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null!; + + AddStep("enable shuffle", () => Game.MusicController.Shuffle.Value = true); + + // ensure we have at least two beatmaps available to identify the direction the music controller navigated to. + AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo()), 5); + AddStep("ensure nonzero track duration", () => Game.Realm.Write(r => + { + // this was already supposed to be non-zero (see innards of `TestResources.CreateTestBeatmapSetInfo()`), + // but the non-zero value is being overwritten *to* zero by `BeatmapUpdater`. + // do a bit of a hack to change it back again - otherwise tracks are going to switch instantly and we won't be able to assert anything sane anymore. + foreach (var beatmap in r.All().Where(b => b.Length == 0)) + beatmap.Length = 60_000; + })); + + AddStep("bind to track change", () => + { + trackChangeQueue = new Queue<(IWorkingBeatmap, TrackChangeDirection)>(); + Game.MusicController.TrackChanged += (working, changeDirection) => trackChangeQueue.Enqueue((working, changeDirection)); + }); + + AddStep("press next", () => globalActionContainer.TriggerPressed(GlobalAction.MusicNext)); + AddUntilStep("track changed", () => trackChangeQueue.Count == 1); + + AddStep("press previous", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPrev)); + AddUntilStep("track changed", () => + trackChangeQueue.Count == 2 && !trackChangeQueue.First().working.BeatmapInfo.Equals(trackChangeQueue.Last().working.BeatmapInfo)); + } } } From 4a16341a94b1f035ed85d60c7f9591228e62e929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 11 Oct 2024 12:45:14 +0200 Subject: [PATCH 495/924] Fix crash when switching tracks back and forth with shuffle on --- osu.Game/Overlays/MusicController.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index c6c46da760..a269dbdf4f 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -397,9 +397,12 @@ namespace osu.Game.Overlays randomHistoryDirection += direction; } - result = previousRandomSets[^1]; - previousRandomSets.RemoveAt(previousRandomSets.Count - 1); - return result; + if (previousRandomSets.Count > 0) + { + result = previousRandomSets[^1]; + previousRandomSets.RemoveAt(previousRandomSets.Count - 1); + return result; + } } // if the early-return above didn't cover it, it means that we have no history to fall back on From d98cc7fe66bfde99eb7f4d173a36935b5f0d106d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 02:55:47 +0100 Subject: [PATCH 496/924] use G to change grid type --- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 2b88860cc8..f66e16a236 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -25,6 +25,10 @@ namespace osu.Game.Rulesets.Osu.Edit { public partial class OsuGridToolboxGroup : EditorToolboxGroup, IKeyBindingHandler { + private static readonly PositionSnapGridType[] grid_types = Enum.GetValues(typeof(PositionSnapGridType)).Cast().ToArray(); + + private int currentGridTypeIndex; + [Resolved] private EditorBeatmap editorBeatmap { get; set; } = null!; @@ -246,6 +250,13 @@ namespace osu.Game.Rulesets.Osu.Edit Spacing.Value = Spacing.Value * 2 >= max_automatic_spacing ? Spacing.Value / 8 : Spacing.Value * 2; } + private void nextGridType() + { + currentGridTypeIndex = (currentGridTypeIndex + 1) % grid_types.Length; + GridType.Value = grid_types[currentGridTypeIndex]; + gridTypeButtons.Items[currentGridTypeIndex].Select(); + } + public bool OnPressed(KeyBindingPressEvent e) { switch (e.Action) From bfe78ff3a0ddabbc3a6f32dcabb2daaf771b6d78 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 19:53:32 +0100 Subject: [PATCH 497/924] fix grid test --- .../Editor/TestSceneOsuEditorGrids.cs | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index b70ecfbba8..135e06d965 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -168,26 +168,21 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor } [Test] - public void TestGridSizeToggling() + public void TestGridTypeToggling() { AddStep("enable rectangular grid", () => InputManager.Key(Key.T)); AddUntilStep("rectangular grid visible", () => this.ChildrenOfType().Any()); - gridSizeIs(4); + gridActive(true); - nextGridSizeIs(8); - nextGridSizeIs(16); - nextGridSizeIs(32); - nextGridSizeIs(4); + nextGridTypeIs(); + nextGridTypeIs(); + nextGridTypeIs(); } - private void nextGridSizeIs(int size) + private void nextGridTypeIs() where T : PositionSnapGrid { - AddStep("toggle to next grid size", () => InputManager.Key(Key.G)); - gridSizeIs(size); + AddStep("toggle to next grid type", () => InputManager.Key(Key.G)); + gridActive(true); } - - private void gridSizeIs(int size) - => AddAssert($"grid size is {size}", () => this.ChildrenOfType().Single().Spacing.Value == new Vector2(size) - && EditorBeatmap.BeatmapInfo.GridSize == size); } } From e56a9d2ad436ef37bcb795e9a7e116c757e4a065 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 20:25:34 +0100 Subject: [PATCH 498/924] Add TestGridFromPoints --- .../Editor/TestSceneOsuEditorGrids.cs | 69 ++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index 135e06d965..9a8e5f8cbd 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -9,6 +9,7 @@ using osu.Framework.Utils; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Tests.Visual; using osu.Game.Utils; @@ -161,7 +162,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor return grid switch { RectangularPositionSnapGrid rectangular => rectangular.StartPosition.Value + GeometryUtils.RotateVector(rectangular.Spacing.Value, -rectangular.GridLineRotation.Value), - TriangularPositionSnapGrid triangular => triangular.StartPosition.Value + GeometryUtils.RotateVector(new Vector2(triangular.Spacing.Value / 2, triangular.Spacing.Value / 2 * MathF.Sqrt(3)), -triangular.GridLineRotation.Value), + TriangularPositionSnapGrid triangular => triangular.StartPosition.Value + GeometryUtils.RotateVector( + new Vector2(triangular.Spacing.Value / 2, triangular.Spacing.Value / 2 * MathF.Sqrt(3)), -triangular.GridLineRotation.Value), CircularPositionSnapGrid circular => circular.StartPosition.Value + GeometryUtils.RotateVector(new Vector2(circular.Spacing.Value, 0), -45), _ => Vector2.Zero }; @@ -184,5 +186,70 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("toggle to next grid type", () => InputManager.Key(Key.G)); gridActive(true); } + + [Test] + public void TestGridFromPoints() + { + AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); + + AddStep("initiate grid from points", () => InputManager.Key(Key.B)); + AddStep("move cursor to slider head + (1, 1)", () => + { + var composer = Editor.ChildrenOfType().Single(); + InputManager.MoveMouseTo(composer.ToScreenSpace(((Slider)EditorBeatmap.HitObjects.First()).Position + new Vector2(1, 1))); + }); + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + AddStep("move cursor to slider tail + (1, 1)", () => + { + var composer = Editor.ChildrenOfType().Single(); + InputManager.MoveMouseTo(composer.ToScreenSpace(((Slider)EditorBeatmap.HitObjects.First()).EndPosition + new Vector2(1, 1))); + }); + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + + gridActive(true); + AddAssert("grid position at slider head", () => + { + var composer = Editor.ChildrenOfType().Single(); + return Precision.AlmostEquals(((Slider)EditorBeatmap.HitObjects.First()).Position, composer.StartPosition.Value); + }); + AddAssert("grid spacing is distance to slider tail", () => + { + var composer = Editor.ChildrenOfType().Single(); + return Precision.AlmostEquals(composer.Spacing.Value.X, 32.05, 0.01) + && Precision.AlmostEquals(composer.Spacing.Value.X, composer.Spacing.Value.Y); + }); + AddAssert("grid rotation points to slider tail", () => + { + var composer = Editor.ChildrenOfType().Single(); + return Precision.AlmostEquals(composer.GridLineRotation.Value, 0.09, 0.01); + }); + + AddStep("initiate grid from points", () => InputManager.Key(Key.B)); + AddStep("move cursor to slider tail + (1, 1)", () => + { + var composer = Editor.ChildrenOfType().Single(); + InputManager.MoveMouseTo(composer.ToScreenSpace(((Slider)EditorBeatmap.HitObjects.First()).EndPosition + new Vector2(1, 1))); + }); + AddStep("right click", () => InputManager.Click(MouseButton.Right)); + AddStep("move cursor to (0, 0)", () => + { + var composer = Editor.ChildrenOfType().Single(); + InputManager.MoveMouseTo(composer.ToScreenSpace(Vector2.Zero)); + }); + + gridActive(true); + AddAssert("grid position at slider tail", () => + { + var composer = Editor.ChildrenOfType().Single(); + return Precision.AlmostEquals(((Slider)EditorBeatmap.HitObjects.First()).EndPosition, composer.StartPosition.Value); + }); + AddAssert("grid spacing and rotation unchanged", () => + { + var composer = Editor.ChildrenOfType().Single(); + return Precision.AlmostEquals(composer.Spacing.Value.X, 32.05, 0.01) + && Precision.AlmostEquals(composer.Spacing.Value.X, composer.Spacing.Value.Y) + && Precision.AlmostEquals(composer.GridLineRotation.Value, 0.09, 0.01); + }); + } } } From b93bc21e45f7dce08f16579284ef50db2e3ebe6e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 21:57:11 +0100 Subject: [PATCH 499/924] Add back the old keybind for cycling grid spacing --- .../Editor/TestSceneOsuEditorGrids.cs | 25 ++++++++++++++++++- .../Edit/OsuGridToolboxGroup.cs | 4 +++ .../Input/Bindings/GlobalActionContainer.cs | 4 +++ .../GlobalActionKeyBindingStrings.cs | 5 ++++ 4 files changed, 37 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index 9a8e5f8cbd..6025e98e2d 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -169,6 +169,29 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor }; } + [Test] + public void TestGridSizeToggling() + { + AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); + AddUntilStep("rectangular grid visible", () => this.ChildrenOfType().Any()); + gridSizeIs(4); + + nextGridSizeIs(8); + nextGridSizeIs(16); + nextGridSizeIs(32); + nextGridSizeIs(4); + } + + private void nextGridSizeIs(int size) + { + AddStep("toggle to next grid size", () => InputManager.Key(Key.G)); + gridSizeIs(size); + } + + private void gridSizeIs(int size) + => AddAssert($"grid size is {size}", () => this.ChildrenOfType().Single().Spacing.Value == new Vector2(size) + && EditorBeatmap.BeatmapInfo.GridSize == size); + [Test] public void TestGridTypeToggling() { @@ -183,7 +206,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private void nextGridTypeIs() where T : PositionSnapGrid { - AddStep("toggle to next grid type", () => InputManager.Key(Key.G)); + AddStep("toggle to next grid type", () => InputManager.Key(Key.H)); gridActive(true); } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index f66e16a236..707611995f 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -264,6 +264,10 @@ namespace osu.Game.Rulesets.Osu.Edit case GlobalAction.EditorCycleGridDisplayMode: nextGridSize(); return true; + + case GlobalAction.EditorCycleGridType: + nextGridType(); + return true; } return false; diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index aca0984e0f..82fde189a1 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -135,6 +135,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.J }, GlobalAction.EditorNudgeLeft), new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight), new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridDisplayMode), + new KeyBinding(new[] { InputKey.H }, GlobalAction.EditorCycleGridType), new KeyBinding(new[] { InputKey.F5 }, GlobalAction.EditorTestGameplay), new KeyBinding(new[] { InputKey.T }, GlobalAction.EditorTapForBPM), new KeyBinding(new[] { InputKey.Control, InputKey.H }, GlobalAction.EditorFlipHorizontally), @@ -371,6 +372,9 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCycleGridDisplayMode))] EditorCycleGridDisplayMode, + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCycleGridType))] + EditorCycleGridType, + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTestGameplay))] EditorTestGameplay, diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index 206db1a166..54379ca598 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -194,6 +194,11 @@ namespace osu.Game.Localisation /// public static LocalisableString EditorCycleGridDisplayMode => new TranslatableString(getKey(@"editor_cycle_grid_display_mode"), @"Cycle grid display mode"); + /// + /// "Cycle grid type" + /// + public static LocalisableString EditorCycleGridType => new TranslatableString(getKey(@"editor_cycle_grid_type"), @"Cycle grid type"); + /// /// "Test gameplay" /// From ada20d230a1a1866ec550455e89bdb4b1668b908 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 1 Jan 2024 16:28:35 +0100 Subject: [PATCH 500/924] Fix grid type cycling not taking into account the radio button selection --- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 707611995f..0e7d0c7531 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -27,8 +27,6 @@ namespace osu.Game.Rulesets.Osu.Edit { private static readonly PositionSnapGridType[] grid_types = Enum.GetValues(typeof(PositionSnapGridType)).Cast().ToArray(); - private int currentGridTypeIndex; - [Resolved] private EditorBeatmap editorBeatmap { get; set; } = null!; @@ -252,9 +250,9 @@ namespace osu.Game.Rulesets.Osu.Edit private void nextGridType() { - currentGridTypeIndex = (currentGridTypeIndex + 1) % grid_types.Length; - GridType.Value = grid_types[currentGridTypeIndex]; - gridTypeButtons.Items[currentGridTypeIndex].Select(); + int nextGridTypeIndex = (Array.IndexOf(grid_types, GridType.Value) + 1) % grid_types.Length; + GridType.Value = grid_types[nextGridTypeIndex]; + gridTypeButtons.Items[nextGridTypeIndex].Select(); } public bool OnPressed(KeyBindingPressEvent e) From 56bfcde446f95c424718616d83f864022bca9551 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 10 Oct 2024 19:37:54 +0200 Subject: [PATCH 501/924] Update grid placement tool test I somehow missed this test when splitting up PRs so here it is now --- .../Editor/TestSceneOsuEditorGrids.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index 6025e98e2d..9d6135bbf8 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -211,11 +211,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor } [Test] - public void TestGridFromPoints() + public void TestGridPlacementTool() { - AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); + AddStep("enable rectangular grid", () => InputManager.Key(Key.T)); - AddStep("initiate grid from points", () => InputManager.Key(Key.B)); + AddStep("start grid placement", () => InputManager.Key(Key.Number5)); AddStep("move cursor to slider head + (1, 1)", () => { var composer = Editor.ChildrenOfType().Single(); @@ -247,13 +247,17 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor return Precision.AlmostEquals(composer.GridLineRotation.Value, 0.09, 0.01); }); - AddStep("initiate grid from points", () => InputManager.Key(Key.B)); + AddStep("start grid placement", () => InputManager.Key(Key.Number5)); AddStep("move cursor to slider tail + (1, 1)", () => { var composer = Editor.ChildrenOfType().Single(); InputManager.MoveMouseTo(composer.ToScreenSpace(((Slider)EditorBeatmap.HitObjects.First()).EndPosition + new Vector2(1, 1))); }); - AddStep("right click", () => InputManager.Click(MouseButton.Right)); + AddStep("double click", () => + { + InputManager.Click(MouseButton.Left); + InputManager.Click(MouseButton.Left); + }); AddStep("move cursor to (0, 0)", () => { var composer = Editor.ChildrenOfType().Single(); From 550e5752219545a921863d6552223c8eee47f734 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 10 Oct 2024 19:42:59 +0200 Subject: [PATCH 502/924] Rename "Cycle grid display mode" to "Cycle grid spacing" The "display mode" is easy to confuse with grid type, so I renamed it to literally the grid property it affects. --- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 2 +- osu.Game/Input/Bindings/GlobalActionContainer.cs | 6 +++--- osu.Game/Localisation/GlobalActionKeyBindingStrings.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 0e7d0c7531..7280bf7b6e 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -259,7 +259,7 @@ namespace osu.Game.Rulesets.Osu.Edit { switch (e.Action) { - case GlobalAction.EditorCycleGridDisplayMode: + case GlobalAction.EditorCycleGridSpacing: nextGridSize(); return true; diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 82fde189a1..2b5f41126a 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -134,7 +134,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.D }, GlobalAction.EditorCloneSelection), new KeyBinding(new[] { InputKey.J }, GlobalAction.EditorNudgeLeft), new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight), - new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridDisplayMode), + new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridSpacing), new KeyBinding(new[] { InputKey.H }, GlobalAction.EditorCycleGridType), new KeyBinding(new[] { InputKey.F5 }, GlobalAction.EditorTestGameplay), new KeyBinding(new[] { InputKey.T }, GlobalAction.EditorTapForBPM), @@ -369,8 +369,8 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleChatFocus))] ToggleChatFocus, - [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCycleGridDisplayMode))] - EditorCycleGridDisplayMode, + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCycleGridSpacing))] + EditorCycleGridSpacing, [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCycleGridType))] EditorCycleGridType, diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index 54379ca598..ed80704a0a 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -190,9 +190,9 @@ namespace osu.Game.Localisation public static LocalisableString EditorCloneSelection => new TranslatableString(getKey(@"editor_clone_selection"), @"Clone selection"); /// - /// "Cycle grid display mode" + /// "Cycle grid spacing" /// - public static LocalisableString EditorCycleGridDisplayMode => new TranslatableString(getKey(@"editor_cycle_grid_display_mode"), @"Cycle grid display mode"); + public static LocalisableString EditorCycleGridSpacing => new TranslatableString(getKey(@"editor_cycle_grid_spacing"), @"Cycle grid spacing"); /// /// "Cycle grid type" From 9d1eb842a7a532f047aab5b051aa434dde4bd547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 11 Oct 2024 14:16:24 +0200 Subject: [PATCH 503/924] Add failing test --- .../DailyChallenge/TestSceneDailyChallenge.cs | 31 +++++++++++++++++++ .../Visual/Metadata/TestMetadataClient.cs | 14 ++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs index e10b3f76e6..da97e967ae 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs @@ -82,5 +82,36 @@ namespace osu.Game.Tests.Visual.DailyChallenge AddUntilStep("wait for screen", () => screen.IsCurrentScreen()); AddStep("daily challenge ended", () => metadataClient.DailyChallengeInfo.Value = null); } + + [Test] + public void TestConclusionNotificationDoesNotFireOnDisconnect() + { + var room = new Room + { + RoomID = { Value = 1234 }, + Name = { Value = "Daily Challenge: June 4, 2024" }, + Playlist = + { + new PlaylistItem(TestResources.CreateTestBeatmapSetInfo().Beatmaps.First()) + { + RequiredMods = [new APIMod(new OsuModTraceable())], + AllowedMods = [new APIMod(new OsuModDoubleTime())] + } + }, + EndDate = { Value = DateTimeOffset.Now.AddHours(12) }, + Category = { Value = RoomCategory.DailyChallenge } + }; + + AddStep("add room", () => API.Perform(new CreateRoomRequest(room))); + AddStep("set daily challenge info", () => metadataClient.DailyChallengeInfo.Value = new DailyChallengeInfo { RoomID = 1234 }); + + Screens.OnlinePlay.DailyChallenge.DailyChallenge screen = null!; + AddStep("push screen", () => LoadScreen(screen = new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room))); + AddUntilStep("wait for screen", () => screen.IsCurrentScreen()); + AddStep("disconnect from metadata server", () => metadataClient.Disconnect()); + AddUntilStep("wait for disconnection", () => metadataClient.DailyChallengeInfo.Value, () => Is.Null); + AddAssert("no notification posted", () => notificationOverlay.AllNotifications, () => Is.Empty); + AddStep("reconnect to metadata server", () => metadataClient.Reconnect()); + } } } diff --git a/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs b/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs index c9f2b183e3..4a862750bc 100644 --- a/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs +++ b/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs @@ -13,7 +13,8 @@ namespace osu.Game.Tests.Visual.Metadata { public partial class TestMetadataClient : MetadataClient { - public override IBindable IsConnected => new BindableBool(true); + public override IBindable IsConnected => isConnected; + private readonly BindableBool isConnected = new BindableBool(true); public override IBindable IsWatchingUserPresence => isWatchingUserPresence; private readonly BindableBool isWatchingUserPresence = new BindableBool(); @@ -98,5 +99,16 @@ namespace osu.Game.Tests.Visual.Metadata } public override Task EndWatchingMultiplayerRoom(long id) => Task.CompletedTask; + + public void Disconnect() + { + isConnected.Value = false; + dailyChallengeInfo.Value = null; + } + + public void Reconnect() + { + isConnected.Value = true; + } } } From 968835bb44148648a2e33da7e5a1e98570f614ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 11 Oct 2024 14:18:19 +0200 Subject: [PATCH 504/924] Do not show daily challenge conclusion notification on disconnection Closes https://github.com/ppy/osu/issues/30194. --- osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 5b341956bb..1aaf0a4321 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -410,7 +410,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private void dailyChallengeChanged(ValueChangedEvent change) { - if (change.OldValue?.RoomID == room.RoomID.Value && change.NewValue == null) + if (change.OldValue?.RoomID == room.RoomID.Value && change.NewValue == null && metadataClient.IsConnected.Value) { notificationOverlay?.Post(new SimpleNotification { Text = DailyChallengeStrings.ChallengeEndedNotification }); } From 07d15cc35a6d2b729638f071780ac8bc7875c8d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 11 Oct 2024 14:31:30 +0200 Subject: [PATCH 505/924] Add positive assertion for conclusion notification being present too --- .../Visual/DailyChallenge/TestSceneDailyChallenge.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs index da97e967ae..2791563954 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs @@ -6,10 +6,12 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Screens; +using osu.Game.Localisation; using osu.Game.Online.API; using osu.Game.Online.Metadata; using osu.Game.Online.Rooms; using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Tests.Resources; using osu.Game.Tests.Visual.Metadata; @@ -81,6 +83,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge AddStep("push screen", () => LoadScreen(screen = new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room))); AddUntilStep("wait for screen", () => screen.IsCurrentScreen()); AddStep("daily challenge ended", () => metadataClient.DailyChallengeInfo.Value = null); + AddAssert("notification posted", () => notificationOverlay.AllNotifications.OfType().Any(n => n.Text == DailyChallengeStrings.ChallengeEndedNotification)); } [Test] From d1da1d4079af62bb8fc5433597081b7f55d1dab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 11 Oct 2024 15:03:30 +0200 Subject: [PATCH 506/924] Rename methods to fit new changes better --- osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs | 4 ++-- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 8e94112866..e3ab95c402 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -240,12 +240,12 @@ namespace osu.Game.Rulesets.Osu.Edit points = originalConvexHull!; foreach (var point in points) - scale = clampToBound(scale, point, Vector2.Zero, OsuPlayfield.BASE_SIZE); + scale = clampToBounds(scale, point, Vector2.Zero, OsuPlayfield.BASE_SIZE); return scale; // Clamps the scale vector s such that the point p scaled by s is within the rectangle defined by lowerBounds and upperBounds - Vector2 clampToBound(Vector2 s, Vector2 p, Vector2 lowerBounds, Vector2 upperBounds) + Vector2 clampToBounds(Vector2 s, Vector2 p, Vector2 lowerBounds, Vector2 upperBounds) { p -= actualOrigin; lowerBounds -= actualOrigin; diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index cf4473bd41..c6c0a4d3a8 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Osu.Edit axisBindable.Disabled = !available; } - private void updateMaxScale() + private void updateMinMaxScale() { if (!scaleHandler.OriginalSurroundingQuad.HasValue) return; @@ -205,7 +205,7 @@ namespace osu.Game.Rulesets.Osu.Edit private void setOrigin(ScaleOrigin origin) { scaleInfo.Value = scaleInfo.Value with { Origin = origin }; - updateMaxScale(); + updateMinMaxScale(); updateAxisCheckBoxesEnabled(); } @@ -237,14 +237,14 @@ namespace osu.Game.Rulesets.Osu.Edit private void setAxis(bool x, bool y) { scaleInfo.Value = scaleInfo.Value with { XAxis = x, YAxis = y }; - updateMaxScale(); + updateMinMaxScale(); } protected override void PopIn() { base.PopIn(); scaleHandler.Begin(); - updateMaxScale(); + updateMinMaxScale(); } protected override void PopOut() From 4e8e4a34bdd1a8eb43eb95aae542328ab3aee74c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 11 Oct 2024 15:18:43 +0200 Subject: [PATCH 507/924] Fix scale popover doing things when both scale axes are turned off Spotted in passing when reviewing https://github.com/ppy/osu/pull/30080. The popover would very arbitrarily revert to scaling by Y axis if both checkboxes were checked off. Not sure how this passed review. --- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index 33b0c14185..25515a90d5 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -219,7 +219,18 @@ namespace osu.Game.Rulesets.Osu.Edit } } - private Axes getAdjustAxis(PreciseScaleInfo scale) => scale.XAxis ? scale.YAxis ? Axes.Both : Axes.X : Axes.Y; + private Axes getAdjustAxis(PreciseScaleInfo scale) + { + var result = Axes.None; + + if (scale.XAxis) + result |= Axes.X; + + if (scale.YAxis) + result |= Axes.Y; + + return result; + } private float getRotation(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.GridCentre ? gridToolbox.GridLinesRotation.Value : 0; From d07a2fbb57cf7ca18054fb8a2587f659e7312886 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 12 Oct 2024 02:18:08 +0900 Subject: [PATCH 508/924] Change shortcut to `Shift`+`G` --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 2b5f41126a..f7ba099705 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -135,7 +135,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.J }, GlobalAction.EditorNudgeLeft), new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight), new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridSpacing), - new KeyBinding(new[] { InputKey.H }, GlobalAction.EditorCycleGridType), + new KeyBinding(new[] { InputKey.Shift, InputKey.G }, GlobalAction.EditorCycleGridType), new KeyBinding(new[] { InputKey.F5 }, GlobalAction.EditorTestGameplay), new KeyBinding(new[] { InputKey.T }, GlobalAction.EditorTapForBPM), new KeyBinding(new[] { InputKey.Control, InputKey.H }, GlobalAction.EditorFlipHorizontally), From 05c8b3cbceaf761a4ac5bd140c3be810ef6fdbf4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 12 Oct 2024 02:18:21 +0900 Subject: [PATCH 509/924] Simplify cycle logic --- .../Edit/OsuGridToolboxGroup.cs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 7280bf7b6e..4d1e06c857 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -215,6 +215,8 @@ namespace osu.Game.Rulesets.Osu.Edit { GridLinesRotation.Disabled = v.NewValue == PositionSnapGridType.Circle; + gridTypeButtons.Items[(int)v.NewValue].Select(); + switch (v.NewValue) { case PositionSnapGridType.Square: @@ -243,28 +245,16 @@ namespace osu.Game.Rulesets.Osu.Edit return ((rotation + 360 + period * 0.5f) % period) - period * 0.5f; } - private void nextGridSize() - { - Spacing.Value = Spacing.Value * 2 >= max_automatic_spacing ? Spacing.Value / 8 : Spacing.Value * 2; - } - - private void nextGridType() - { - int nextGridTypeIndex = (Array.IndexOf(grid_types, GridType.Value) + 1) % grid_types.Length; - GridType.Value = grid_types[nextGridTypeIndex]; - gridTypeButtons.Items[nextGridTypeIndex].Select(); - } - public bool OnPressed(KeyBindingPressEvent e) { switch (e.Action) { case GlobalAction.EditorCycleGridSpacing: - nextGridSize(); + Spacing.Value = Spacing.Value * 2 >= max_automatic_spacing ? Spacing.Value / 8 : Spacing.Value * 2; return true; case GlobalAction.EditorCycleGridType: - nextGridType(); + GridType.Value = (PositionSnapGridType)(((int)GridType.Value + 1) % Enum.GetValues().Length); return true; } From b62d9f8696206b9779c7d7286d974cd4e41fe581 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 12 Oct 2024 02:19:02 +0900 Subject: [PATCH 510/924] Fix bindings being clobbered --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index f7ba099705..77c21e3703 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -372,9 +372,6 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCycleGridSpacing))] EditorCycleGridSpacing, - [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCycleGridType))] - EditorCycleGridType, - [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTestGameplay))] EditorTestGameplay, @@ -476,6 +473,9 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSeekToNextSamplePoint))] EditorSeekToNextSamplePoint, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCycleGridType))] + EditorCycleGridType, } public enum GlobalActionCategory From 8b046f60f0f81cd333d85979b1fd7add2f09be99 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 12 Oct 2024 02:21:28 +0900 Subject: [PATCH 511/924] Update test --- .../Editor/TestSceneOsuEditorGrids.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index 9d6135bbf8..f666812082 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -206,7 +206,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private void nextGridTypeIs() where T : PositionSnapGrid { - AddStep("toggle to next grid type", () => InputManager.Key(Key.H)); + AddStep("toggle to next grid type", () => + { + InputManager.PressKey(Key.ShiftLeft); + InputManager.Key(Key.G); + InputManager.ReleaseKey(Key.ShiftLeft); + }); gridActive(true); } From 8e781c170d034d4395ae6b045f24b1887837bd4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Fri, 11 Oct 2024 19:24:46 +0200 Subject: [PATCH 512/924] Inline scheduler calls --- osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs index 9605393f70..a84494fb76 100644 --- a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs @@ -118,15 +118,13 @@ namespace osu.Game.Rulesets.Osu.Edit changeHandler?.BeginChange(); began = true; - distanceSnapInput.Current.BindValueChanged(_ => scheduleRefresh()); - offsetAngleInput.Current.BindValueChanged(_ => scheduleRefresh()); - repeatCountInput.Current.BindValueChanged(_ => scheduleRefresh()); - pointInput.Current.BindValueChanged(_ => scheduleRefresh()); + distanceSnapInput.Current.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); + offsetAngleInput.Current.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); + repeatCountInput.Current.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); + pointInput.Current.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); tryCreatePolygon(); } - private void scheduleRefresh() => Scheduler.AddOnce(tryCreatePolygon); - private void tryCreatePolygon() { double startTime = beatSnapProvider.SnapTime(editorClock.CurrentTime); From 9e97141e33f3f94064790f2db2d668d55243d287 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Fri, 11 Oct 2024 22:03:21 +0200 Subject: [PATCH 513/924] Fix off-by-one error --- osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs index a84494fb76..b244e07adf 100644 --- a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs @@ -140,8 +140,8 @@ namespace osu.Game.Rulesets.Osu.Edit if (insertedCircles.Count > totalPoints) { - editorBeatmap.RemoveRange(insertedCircles.GetRange(totalPoints + 1, insertedCircles.Count - totalPoints - 1)); - insertedCircles.RemoveRange(totalPoints + 1, insertedCircles.Count - totalPoints - 1); + editorBeatmap.RemoveRange(insertedCircles.GetRange(totalPoints, insertedCircles.Count - totalPoints)); + insertedCircles.RemoveRange(totalPoints, insertedCircles.Count - totalPoints); } var selectionHandler = (EditorSelectionHandler)composer.BlueprintContainer.SelectionHandler; From f76a2d02f61e522ae69eef5f1c3e8af0836bb42d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Fri, 11 Oct 2024 23:22:15 +0200 Subject: [PATCH 514/924] Make sure to properly update hitobjects --- .../Edit/PolygonGenerationPopover.cs | 13 +++++++++++-- .../Compose/Components/EditorSelectionHandler.cs | 3 ++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs index b244e07adf..335b0f3ea6 100644 --- a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs @@ -50,9 +50,14 @@ namespace osu.Game.Rulesets.Osu.Edit [Resolved] private HitObjectComposer composer { get; set; } = null!; + private Bindable newComboState = null!; + [BackgroundDependencyLoader] private void load() { + var selectionHandler = (EditorSelectionHandler)composer.BlueprintContainer.SelectionHandler; + newComboState = selectionHandler.SelectionNewComboState.GetBoundCopy(); + Child = new FillFlowContainer { Width = 220, @@ -122,6 +127,7 @@ namespace osu.Game.Rulesets.Osu.Edit offsetAngleInput.Current.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); repeatCountInput.Current.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); pointInput.Current.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); + newComboState.BindValueChanged(_ => Scheduler.AddOnce(tryCreatePolygon)); tryCreatePolygon(); } @@ -144,7 +150,6 @@ namespace osu.Game.Rulesets.Osu.Edit insertedCircles.RemoveRange(totalPoints, insertedCircles.Count - totalPoints); } - var selectionHandler = (EditorSelectionHandler)composer.BlueprintContainer.SelectionHandler; bool first = true; var newlyAdded = new List(); @@ -162,7 +167,7 @@ namespace osu.Game.Rulesets.Osu.Edit circle.Position = position; circle.StartTime = startTime; - circle.NewCombo = first && selectionHandler.SelectionNewComboState.Value == TernaryState.True; + circle.NewCombo = first && newComboState.Value == TernaryState.True; if (position.X < 0 || position.Y < 0 || position.X > OsuPlayfield.BASE_SIZE.X || position.Y > OsuPlayfield.BASE_SIZE.Y) { @@ -179,6 +184,10 @@ namespace osu.Game.Rulesets.Osu.Edit // TODO: probably ensure samples also follow current ternary status (not trivial) circle.Samples.Add(circle.CreateHitSampleInfo()); } + else + { + editorBeatmap.Update(circle); + } startTime = beatSnapProvider.SnapTime(startTime + timeSpacing); diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index dbbf767a7d..dfda93cedd 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -176,7 +176,8 @@ namespace osu.Game.Screens.Edit.Compose.Components /// protected virtual void UpdateTernaryStates() { - SelectionNewComboState.Value = GetStateFromSelection(SelectedItems.OfType(), h => h.NewCombo); + if (SelectedItems.Any()) + SelectionNewComboState.Value = GetStateFromSelection(SelectedItems.OfType(), h => h.NewCombo); var samplesInSelection = SelectedItems.SelectMany(enumerateAllSamples).ToArray(); From 767ded43267f23b6e2a8a0b9b5221106605112ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Fri, 11 Oct 2024 23:41:57 +0200 Subject: [PATCH 515/924] Restore previous NewCombo state when adding objects --- .../Edit/PolygonGenerationPopover.cs | 54 +++++++++++-------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs index 335b0f3ea6..00abab0bae 100644 --- a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs @@ -150,24 +150,40 @@ namespace osu.Game.Rulesets.Osu.Edit insertedCircles.RemoveRange(totalPoints, insertedCircles.Count - totalPoints); } - bool first = true; - var newlyAdded = new List(); for (int i = 0; i < totalPoints; ++i) { float angle = float.DegreesToRadians(offsetAngleInput.Current.Value) + (i + 1) * (2 * float.Pi / pointInput.Current.Value); var position = OsuPlayfield.BASE_SIZE / 2 + new Vector2(polygonRadius * float.Cos(angle), polygonRadius * float.Sin(angle)); + bool newCombo = i == 0 && newComboState.Value == TernaryState.True; - bool alreadyAdded = i < insertedCircles.Count; + HitCircle circle; - var circle = alreadyAdded - ? insertedCircles[i] - : new HitCircle(); + if (i < insertedCircles.Count) + { + circle = insertedCircles[i]; - circle.Position = position; - circle.StartTime = startTime; - circle.NewCombo = first && newComboState.Value == TernaryState.True; + circle.Position = position; + circle.StartTime = startTime; + circle.NewCombo = newCombo; + + editorBeatmap.Update(circle); + } + else + { + circle = new HitCircle + { + Position = position, + StartTime = startTime, + NewCombo = newCombo, + }; + + newlyAdded.Add(circle); + + // TODO: probably ensure samples also follow current ternary status (not trivial) + circle.Samples.Add(circle.CreateHitSampleInfo()); + } if (position.X < 0 || position.Y < 0 || position.X > OsuPlayfield.BASE_SIZE.X || position.Y > OsuPlayfield.BASE_SIZE.Y) { @@ -177,26 +193,18 @@ namespace osu.Game.Rulesets.Osu.Edit return; } - if (!alreadyAdded) - { - newlyAdded.Add(circle); - - // TODO: probably ensure samples also follow current ternary status (not trivial) - circle.Samples.Add(circle.CreateHitSampleInfo()); - } - else - { - editorBeatmap.Update(circle); - } - startTime = beatSnapProvider.SnapTime(startTime + timeSpacing); - - first = false; } + var previousNewComboState = newComboState.Value; + insertedCircles.AddRange(newlyAdded); editorBeatmap.AddRange(newlyAdded); + // When adding new hitObjects, newCombo state will get reset to false when no objects are selected. + // Since this is the case when this popover is showing, we need to restore the previous newCombo state + newComboState.Value = previousNewComboState; + commitButton.Enabled.Value = true; } From 40339b8a1713aaf1b980550b602a932c24f42ba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20Sch=C3=BCrz?= Date: Fri, 11 Oct 2024 23:49:52 +0200 Subject: [PATCH 516/924] Fix typo --- osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs index 00abab0bae..2f073c2fc4 100644 --- a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs @@ -201,7 +201,7 @@ namespace osu.Game.Rulesets.Osu.Edit insertedCircles.AddRange(newlyAdded); editorBeatmap.AddRange(newlyAdded); - // When adding new hitObjects, newCombo state will get reset to false when no objects are selected. + // When adding new hitObjects, newCombo state will get reset to false when no objects are selected. // Since this is the case when this popover is showing, we need to restore the previous newCombo state newComboState.Value = previousNewComboState; From 9f73a45580c2452238aa80d313a8158da4b0c4a5 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 11 Oct 2024 23:57:26 +0200 Subject: [PATCH 517/924] Explicitly assert specific grid type --- osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index f666812082..fb109ba6f9 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -130,6 +130,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private void gridActive(bool active) where T : PositionSnapGrid { + AddAssert($"grid type is {typeof(T).Name}", () => this.ChildrenOfType().Any()); AddStep("choose placement tool", () => InputManager.Key(Key.Number2)); AddStep("move cursor to spacing + (1, 1)", () => { From 0794fc61a0935274beed82de91ef780c4b1d34f2 Mon Sep 17 00:00:00 2001 From: jhk2601 Date: Fri, 11 Oct 2024 18:38:53 -0400 Subject: [PATCH 518/924] Initial Implementation (working, unsafe handling of cursor position) --- osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs | 106 +++++++++++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 3 +- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 12 +++ 3 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs new file mode 100644 index 0000000000..193b0142df --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs @@ -0,0 +1,106 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + + + +using System; +using System.Diagnostics; +using osu.Framework.Bindables; +using osu.Framework.Localisation; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; +using osu.Game.Configuration; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Settings; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.Play; +using osu.Game.Rulesets.Osu.UI.Cursor; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModBloom : Mod, IApplicableToScoreProcessor, IUpdatableByPlayfield, IApplicableToPlayer + { + public override string Name => "Bloom"; + public override string Acronym => "BM"; + public override ModType Type => ModType.Fun; + public override LocalisableString Description => "The cursor blooms into a.. larger cursor!"; + public override double ScoreMultiplier => 1; + protected const float MIN_SIZE = 1; + protected const float TRANSITION_DURATION = 100; + public override Type[] IncompatibleMods => new[] { typeof(OsuModFlashlight), typeof(OsuModNoScope), typeof(OsuModObjectScaleTween), typeof(OsuModTouchDevice), typeof(OsuModAutopilot) }; + + + protected readonly BindableNumber CurrentCombo = new BindableInt(); + protected readonly IBindable IsBreakTime = new Bindable(); + protected float ComboBasedSize; + + + [SettingSource( + "Max Size at Combo", + "The combo count at which the cursor reaches its maximum size", + SettingControlType = typeof(SettingsSlider) + )] + public BindableInt MaxSizeComboCount { get; } = new BindableInt(50) + { + MinValue = 0, + MaxValue = 100, + }; + [SettingSource( + "Final Size Multiplier", + "The multiplier applied to cursor size when combo reaches maximum", + SettingControlType = typeof(SettingsSlider>) + )] + public BindableFloat MaxMulti { get; } = new BindableFloat(10f) + { + MinValue = 5f, + MaxValue = 15f, + Precision = 0.5f, + }; + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; + public void ApplyToPlayer(Player player) + { + IsBreakTime.BindTo(player.IsBreakTime); + } + + public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) + { + if (MaxSizeComboCount.Value == 0) return; + + CurrentCombo.BindTo(scoreProcessor.Combo); + CurrentCombo.BindValueChanged(combo => + { + ComboBasedSize = Math.Min(MaxMulti.Value, 10 * ((float)combo.NewValue / MaxSizeComboCount.Value)); + ComboBasedSize = Math.Max(ComboBasedSize, MIN_SIZE); + }, true + ); + } + public void Update(Playfield playfield) + //terrible terrible handling on making sure cursor position stays accurate, will fix + { + bool beBaseSize = IsBreakTime.Value; + var osuPlayfield = (OsuPlayfield)playfield; + Debug.Assert(osuPlayfield.Cursor != null); + var realCursor = (OsuCursor)osuPlayfield.Cursor.ActiveCursor; + realCursor.isBloom = true; + if (beBaseSize) + { + realCursor.ComboSize = 1; + } + else + { + realCursor.ComboSize = ComboBasedSize; + } + } + + + + + } + public partial class MaxSizeSlider : RoundedSliderBar + { + public override LocalisableString TooltipText => Current.Value == 0 ? "always at max size" : base.TooltipText; + } + +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 2f928aaefa..25b1dd9b12 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -214,7 +214,8 @@ namespace osu.Game.Rulesets.Osu new OsuModFreezeFrame(), new OsuModBubbles(), new OsuModSynesthesia(), - new OsuModDepth() + new OsuModDepth(), + new OsuModBloom() }; case ModType.System: diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index 0bb316e0aa..75a38826ad 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -39,6 +39,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor public IBindable CursorScale => cursorScale; private readonly Bindable cursorScale = new BindableFloat(1); + public bool isBloom; + public float ComboSize; private Bindable userCursorScale = null!; private Bindable autoCursorScale = null!; @@ -68,6 +70,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor autoCursorScale.ValueChanged += _ => cursorScale.Value = CalculateCursorScale(); cursorScale.BindValueChanged(e => cursorScaleContainer.Scale = new Vector2(e.NewValue), true); + isBloom = false; } protected override void LoadComplete() @@ -75,6 +78,15 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor base.LoadComplete(); cursorScale.Value = CalculateCursorScale(); } + protected override void Update() + //this should not exist will implement sane fix + { + base.Update(); + if (isBloom) + { + cursorScale.Value = ComboSize; + } + } protected virtual Drawable CreateCursorContent() => cursorScaleContainer = new Container { From 1e7e2e0b1c76f5dcfd2a7ab76564910f1df0aaf6 Mon Sep 17 00:00:00 2001 From: schiavoanto Date: Sat, 12 Oct 2024 00:55:33 +0200 Subject: [PATCH 519/924] Add more localisation in skin editor --- osu.Game/Localisation/SkinEditorStrings.cs | 45 +++++++++++++++++++ osu.Game/Overlays/SkinEditor/SkinEditor.cs | 2 +- .../SkinEditor/SkinSelectionHandler.cs | 17 +++---- 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/osu.Game/Localisation/SkinEditorStrings.cs b/osu.Game/Localisation/SkinEditorStrings.cs index 3c1d1ff40d..d96ea7dd9f 100644 --- a/osu.Game/Localisation/SkinEditorStrings.cs +++ b/osu.Game/Localisation/SkinEditorStrings.cs @@ -49,6 +49,51 @@ namespace osu.Game.Localisation /// public static LocalisableString RevertToDefaultDescription => new TranslatableString(getKey(@"revert_to_default_description"), @"All layout elements for layers in the current screen will be reset to defaults."); + /// + /// "Closest" + /// + public static LocalisableString Closest => new TranslatableString(getKey(@"closest"), @"Closest"); + + /// + /// "Anchor" + /// + public static LocalisableString Anchor => new TranslatableString(getKey(@"anchor"), @"Anchor"); + + /// + /// "Origin" + /// + public static LocalisableString Origin => new TranslatableString(getKey(@"origin"), @"Origin"); + + /// + /// "Reset position" + /// + public static LocalisableString ResetPosition => new TranslatableString(getKey(@"reset_position"), @"Reset position"); + + /// + /// "Reset rotation" + /// + public static LocalisableString ResetRotation => new TranslatableString(getKey(@"reset_rotation"), @"Reset rotation"); + + /// + /// "Reset scale" + /// + public static LocalisableString ResetScale => new TranslatableString(getKey(@"reset_scale"), @"Reset scale"); + + /// + /// "Bring to front" + /// + public static LocalisableString BringToFront => new TranslatableString(getKey(@"bring_to_front"), @"Bring to front"); + + /// + /// "Send to back" + /// + public static LocalisableString SendToBack => new TranslatableString(getKey(@"send_to_back"), @"Send to back"); + + /// + /// "Current working layer" + /// + public static LocalisableString CurrentWorkingLayer => new TranslatableString(getKey(@"current_working_layer"), @"Current working layer"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 6f7781ee9c..42908f7102 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -361,7 +361,7 @@ namespace osu.Game.Overlays.SkinEditor componentsSidebar.Children = new[] { - new EditorSidebarSection("Current working layer") + new EditorSidebarSection(SkinEditorStrings.CurrentWorkingLayer) { Children = new Drawable[] { diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index 722ffd6d07..f7691d07b3 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -13,6 +13,7 @@ using osu.Game.Extensions; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Localisation; using osu.Game.Skinning; using osu.Game.Utils; using osuTK; @@ -101,19 +102,19 @@ namespace osu.Game.Overlays.SkinEditor protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) { - var closestItem = new TernaryStateRadioMenuItem("Closest", MenuItemType.Standard, _ => applyClosestAnchors()) + var closestItem = new TernaryStateRadioMenuItem(SkinEditorStrings.Closest.ToString(), MenuItemType.Standard, _ => applyClosestAnchors()) { State = { Value = GetStateFromSelection(selection, c => !c.Item.UsesFixedAnchor) } }; - yield return new OsuMenuItem("Anchor") + yield return new OsuMenuItem(SkinEditorStrings.Anchor) { Items = createAnchorItems((d, a) => d.UsesFixedAnchor && ((Drawable)d).Anchor == a, applyFixedAnchors) .Prepend(closestItem) .ToArray() }; - yield return originMenu = new OsuMenuItem("Origin"); + yield return originMenu = new OsuMenuItem(SkinEditorStrings.Origin); closestItem.State.BindValueChanged(s => { @@ -125,19 +126,19 @@ namespace osu.Game.Overlays.SkinEditor yield return new OsuMenuItemSpacer(); - yield return new OsuMenuItem("Reset position", MenuItemType.Standard, () => + yield return new OsuMenuItem(SkinEditorStrings.ResetPosition, MenuItemType.Standard, () => { foreach (var blueprint in SelectedBlueprints) ((Drawable)blueprint.Item).Position = Vector2.Zero; }); - yield return new OsuMenuItem("Reset rotation", MenuItemType.Standard, () => + yield return new OsuMenuItem(SkinEditorStrings.ResetRotation, MenuItemType.Standard, () => { foreach (var blueprint in SelectedBlueprints) ((Drawable)blueprint.Item).Rotation = 0; }); - yield return new OsuMenuItem("Reset scale", MenuItemType.Standard, () => + yield return new OsuMenuItem(SkinEditorStrings.ResetScale, MenuItemType.Standard, () => { foreach (var blueprint in SelectedBlueprints) { @@ -153,9 +154,9 @@ namespace osu.Game.Overlays.SkinEditor yield return new OsuMenuItemSpacer(); - yield return new OsuMenuItem("Bring to front", MenuItemType.Standard, () => skinEditor.BringSelectionToFront()); + yield return new OsuMenuItem(SkinEditorStrings.BringToFront, MenuItemType.Standard, () => skinEditor.BringSelectionToFront()); - yield return new OsuMenuItem("Send to back", MenuItemType.Standard, () => skinEditor.SendSelectionToBack()); + yield return new OsuMenuItem(SkinEditorStrings.SendToBack, MenuItemType.Standard, () => skinEditor.SendSelectionToBack()); yield return new OsuMenuItemSpacer(); From 9cd7f2b5d4c3a6506e4076b8cdbfecf37bbd89b5 Mon Sep 17 00:00:00 2001 From: schiavoanto Date: Sat, 12 Oct 2024 01:38:52 +0200 Subject: [PATCH 520/924] Use `LocalisableDescription` for skin editor layers dropdown --- osu.Game/Localisation/SkinEditorStrings.cs | 10 ++++++++++ osu.Game/Skinning/GlobalSkinnableContainers.cs | 9 +++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/osu.Game/Localisation/SkinEditorStrings.cs b/osu.Game/Localisation/SkinEditorStrings.cs index d96ea7dd9f..9ae9aa0747 100644 --- a/osu.Game/Localisation/SkinEditorStrings.cs +++ b/osu.Game/Localisation/SkinEditorStrings.cs @@ -34,6 +34,16 @@ namespace osu.Game.Localisation /// public static LocalisableString Gameplay => new TranslatableString(getKey(@"gameplay"), @"Gameplay"); + /// + /// "HUD" + /// + public static LocalisableString HUD => new TranslatableString(getKey(@"hud"), @"HUD"); + + /// + /// "Playfield" + /// + public static LocalisableString Playfield => new TranslatableString(getKey(@"playfield"), @"Playfield"); + /// /// "Settings ({0})" /// diff --git a/osu.Game/Skinning/GlobalSkinnableContainers.cs b/osu.Game/Skinning/GlobalSkinnableContainers.cs index 02f915895f..73cb1303a0 100644 --- a/osu.Game/Skinning/GlobalSkinnableContainers.cs +++ b/osu.Game/Skinning/GlobalSkinnableContainers.cs @@ -1,7 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.ComponentModel; +using osu.Framework.Localisation; +using osu.Game.Localisation; namespace osu.Game.Skinning { @@ -10,13 +11,13 @@ namespace osu.Game.Skinning /// public enum GlobalSkinnableContainers { - [Description("HUD")] + [LocalisableDescription(typeof(SkinEditorStrings), nameof(SkinEditorStrings.HUD))] MainHUDComponents, - [Description("Song select")] + [LocalisableDescription(typeof(SkinEditorStrings), nameof(SkinEditorStrings.SongSelect))] SongSelect, - [Description("Playfield")] + [LocalisableDescription(typeof(SkinEditorStrings), nameof(SkinEditorStrings.Playfield))] Playfield } } From fc1ebfdf64355b60c3f572ec00b1684ea2c82c31 Mon Sep 17 00:00:00 2001 From: schiavoanto Date: Sat, 12 Oct 2024 02:00:51 +0200 Subject: [PATCH 521/924] Fix layers dropdown localised entries --- osu.Game/Skinning/GlobalSkinnableContainerLookup.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/GlobalSkinnableContainerLookup.cs b/osu.Game/Skinning/GlobalSkinnableContainerLookup.cs index 6d78981f0a..33b5527917 100644 --- a/osu.Game/Skinning/GlobalSkinnableContainerLookup.cs +++ b/osu.Game/Skinning/GlobalSkinnableContainerLookup.cs @@ -31,9 +31,9 @@ namespace osu.Game.Skinning public override string ToString() { - if (Ruleset == null) return Lookup.GetDescription(); + if (Ruleset == null) return Lookup.GetLocalisableDescription().ToString(); - return $"{Lookup.GetDescription()} (\"{Ruleset.Name}\" only)"; + return $"{Lookup.GetLocalisableDescription().ToString()} (\"{Ruleset.Name}\" only)"; } public bool Equals(GlobalSkinnableContainerLookup? other) From 71b08b54c1ec31a25d6e2098919311e8c83dd79b Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 11 Oct 2024 18:48:04 -0700 Subject: [PATCH 522/924] Make `TernaryStateRadioMenuItem` localisable --- osu.Game/Graphics/UserInterface/TernaryStateMenuItem.cs | 3 ++- osu.Game/Graphics/UserInterface/TernaryStateRadioMenuItem.cs | 3 ++- osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/TernaryStateMenuItem.cs b/osu.Game/Graphics/UserInterface/TernaryStateMenuItem.cs index d2b6ff2dba..f98628a486 100644 --- a/osu.Game/Graphics/UserInterface/TernaryStateMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/TernaryStateMenuItem.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; namespace osu.Game.Graphics.UserInterface { @@ -20,7 +21,7 @@ namespace osu.Game.Graphics.UserInterface /// A function to inform what the next state should be when this item is clicked. /// The type of action which this performs. /// A delegate to be invoked when this is pressed. - protected TernaryStateMenuItem(string text, Func nextStateFunction, MenuItemType type = MenuItemType.Standard, Action action = null) + protected TernaryStateMenuItem(LocalisableString text, Func nextStateFunction, MenuItemType type = MenuItemType.Standard, Action action = null) : base(text, nextStateFunction, type, action) { } diff --git a/osu.Game/Graphics/UserInterface/TernaryStateRadioMenuItem.cs b/osu.Game/Graphics/UserInterface/TernaryStateRadioMenuItem.cs index 133362d3e6..30fea62cd7 100644 --- a/osu.Game/Graphics/UserInterface/TernaryStateRadioMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/TernaryStateRadioMenuItem.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using osu.Framework.Localisation; namespace osu.Game.Graphics.UserInterface { @@ -18,7 +19,7 @@ namespace osu.Game.Graphics.UserInterface /// The text to display. /// The type of action which this performs. /// A delegate to be invoked when this is pressed. - public TernaryStateRadioMenuItem(string text, MenuItemType type = MenuItemType.Standard, Action action = null) + public TernaryStateRadioMenuItem(LocalisableString text, MenuItemType type = MenuItemType.Standard, Action action = null) : base(text, getNextState, type, action) { } diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index f7691d07b3..bc878b9214 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -102,7 +102,7 @@ namespace osu.Game.Overlays.SkinEditor protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) { - var closestItem = new TernaryStateRadioMenuItem(SkinEditorStrings.Closest.ToString(), MenuItemType.Standard, _ => applyClosestAnchors()) + var closestItem = new TernaryStateRadioMenuItem(SkinEditorStrings.Closest, MenuItemType.Standard, _ => applyClosestAnchors()) { State = { Value = GetStateFromSelection(selection, c => !c.Item.UsesFixedAnchor) } }; From 4d367515dc2fbb8200b42ceba48cb33bb3bcf21c Mon Sep 17 00:00:00 2001 From: jhk2601 Date: Fri, 11 Oct 2024 21:52:13 -0400 Subject: [PATCH 523/924] Smoother size changing using linear interpolation --- osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs index 193b0142df..a9026e470b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs @@ -17,6 +17,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play; using osu.Game.Rulesets.Osu.UI.Cursor; +using osu.Framework.Utils; namespace osu.Game.Rulesets.Osu.Mods { @@ -84,13 +85,14 @@ namespace osu.Game.Rulesets.Osu.Mods Debug.Assert(osuPlayfield.Cursor != null); var realCursor = (OsuCursor)osuPlayfield.Cursor.ActiveCursor; realCursor.isBloom = true; + float currentCombSize = (float)Interpolation.Lerp(osuPlayfield.Cursor.Scale.X, ComboBasedSize, Math.Clamp(osuPlayfield.Time.Elapsed / TRANSITION_DURATION, 0, 1)); if (beBaseSize) { realCursor.ComboSize = 1; } else { - realCursor.ComboSize = ComboBasedSize; + realCursor.ComboSize = currentCombSize; } } From b9214244616090a16405b10a799b2ec04e61bd88 Mon Sep 17 00:00:00 2001 From: finadoggie <75299710+Finadoggie@users.noreply.github.com> Date: Sat, 12 Oct 2024 01:00:15 -0700 Subject: [PATCH 524/924] Update to use variable usingClassicSliderAccuracy --- .../Difficulty/OsuPerformanceCalculator.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 58ac87638d..0aa243b886 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -18,8 +18,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty private bool usingClassicSliderAccuracy; - private bool useClassicSlider; - private double accuracy; private int scoreMaxCombo; private int countGreat; @@ -39,7 +37,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, DifficultyAttributes attributes) { var osuAttributes = (OsuDifficultyAttributes)attributes; - useClassicSlider = score.Mods.Any(h => h is OsuModClassic cl && cl.NoSliderHeadAccuracy.Value); usingClassicSliderAccuracy = score.Mods.OfType().Any(m => m.NoSliderHeadAccuracy.Value); @@ -51,10 +48,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); countLargeTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss); - if (!useClassicSlider) + if (!usingClassicSliderAccuracy) countSliderEndsDropped = osuAttributes.SliderCount - score.Statistics.GetValueOrDefault(HitResult.SliderTailHit); - if (useClassicSlider) + if (usingClassicSliderAccuracy) effectiveMissCount = calculateEffectiveMissCount(osuAttributes); else effectiveMissCount = countMiss; @@ -138,7 +135,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (attributes.SliderCount > 0) { double estimateSliderEndsDropped; - if (useClassicSlider) + if (usingClassicSliderAccuracy) estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders); else estimateSliderEndsDropped = Math.Min(countSliderEndsDropped + countLargeTickMiss, estimateDifficultSliders); From 3b517e03aa853ff35a81f0e0e53a8d6ac7cae895 Mon Sep 17 00:00:00 2001 From: finadoggie <75299710+Finadoggie@users.noreply.github.com> Date: Sat, 12 Oct 2024 01:08:39 -0700 Subject: [PATCH 525/924] Convert estimateSliderEndsDropped assignment into '?:' expression I would just like to say that I don't know why anyone would ever want this but github told me to do it --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 0aa243b886..95babd0fb9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -135,10 +135,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (attributes.SliderCount > 0) { double estimateSliderEndsDropped; - if (usingClassicSliderAccuracy) - estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders); - else - estimateSliderEndsDropped = Math.Min(countSliderEndsDropped + countLargeTickMiss, estimateDifficultSliders); + estimateSliderEndsDropped = usingClassicSliderAccuracy ? Math.Clamp(Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders) : Math.Min(countSliderEndsDropped + countLargeTickMiss, estimateDifficultSliders); double sliderNerfFactor = 0; sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + attributes.SliderFactor; From e4f9c861bad6b192752eeb9d7feef3b3aa29d459 Mon Sep 17 00:00:00 2001 From: Artemis Rosman <73006620+rozukke@users.noreply.github.com> Date: Sun, 13 Oct 2024 00:50:45 +1100 Subject: [PATCH 526/924] Add functionality to mass reset offsets --- osu.Game/Beatmaps/BeatmapManager.cs | 16 ++++++++++++++++ .../DeleteConfirmationContentStrings.cs | 5 +++++ .../Localisation/MaintenanceSettingsStrings.cs | 5 +++++ .../Sections/Maintenance/BeatmapSettings.cs | 15 +++++++++++++++ 4 files changed, 41 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index cd818941ff..f72741af16 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -313,6 +313,22 @@ namespace osu.Game.Beatmaps }); } + public void ResetAllOffsets() + { + const string reset_complete_message = "All offsets have been reset!"; + Realm.Write(r => + { + var items = r.All(); + + foreach (var beatmap in items) + { + beatmap.UserSettings.Offset = 0.0; + } + + PostNotification?.Invoke(new ProgressCompletionNotification { Text = reset_complete_message }); + }); + } + public void Delete(Expression>? filter = null, bool silent = false) { Realm.Run(r => diff --git a/osu.Game/Localisation/DeleteConfirmationContentStrings.cs b/osu.Game/Localisation/DeleteConfirmationContentStrings.cs index d781fadbce..2b2f4dda54 100644 --- a/osu.Game/Localisation/DeleteConfirmationContentStrings.cs +++ b/osu.Game/Localisation/DeleteConfirmationContentStrings.cs @@ -19,6 +19,11 @@ namespace osu.Game.Localisation /// public static LocalisableString BeatmapVideos => new TranslatableString(getKey(@"beatmap_videos"), @"Are you sure you want to delete all beatmaps videos? This cannot be undone!"); + /// + /// "Are you sure you want to reset all local beatmap offsets? This cannot be undone!" + /// + public static LocalisableString Offsets => new TranslatableString(getKey(@"offsets"), @"Are you sure you want to reset all local beatmap offsets? This cannot be undone!"); + /// /// "Are you sure you want to delete all skins? This cannot be undone!" /// diff --git a/osu.Game/Localisation/MaintenanceSettingsStrings.cs b/osu.Game/Localisation/MaintenanceSettingsStrings.cs index 03e15e8393..6d5e0d5e0e 100644 --- a/osu.Game/Localisation/MaintenanceSettingsStrings.cs +++ b/osu.Game/Localisation/MaintenanceSettingsStrings.cs @@ -59,6 +59,11 @@ namespace osu.Game.Localisation /// public static LocalisableString DeleteAllBeatmapVideos => new TranslatableString(getKey(@"delete_all_beatmap_videos"), @"Delete ALL beatmap videos"); + /// + /// "Reset ALL beatmap offsets" + /// + public static LocalisableString ResetAllOffsets => new TranslatableString(getKey(@"reset_all_offsets"), @"Reset ALL beatmap offsets"); + /// /// "Delete ALL scores" /// diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs index d0a8fc7d2c..597e03fab2 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs @@ -16,6 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private SettingsButton deleteBeatmapsButton = null!; private SettingsButton deleteBeatmapVideosButton = null!; + private SettingsButton resetOffsetsButton = null!; private SettingsButton restoreButton = null!; private SettingsButton undeleteButton = null!; @@ -47,6 +48,20 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance }, DeleteConfirmationContentStrings.BeatmapVideos)); } }); + + Add(resetOffsetsButton = new DangerousSettingsButton + { + Text = MaintenanceSettingsStrings.ResetAllOffsets, + Action = () => + { + dialogOverlay?.Push(new MassDeleteConfirmationDialog(() => + { + resetOffsetsButton.Enabled.Value = false; + Task.Run(beatmaps.ResetAllOffsets).ContinueWith(_ => Schedule(() => resetOffsetsButton.Enabled.Value = true)); + }, DeleteConfirmationContentStrings.Offsets)); + } + }); + AddRange(new Drawable[] { restoreButton = new SettingsButton From e1e8e39a89e66dbfa7f52697cb90658bfd12f827 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 12 Oct 2024 12:12:40 -0700 Subject: [PATCH 527/924] Recommend C# Dev Kit extension on VSCode --- .vscode/extensions.json | 3 ++- README.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 5b7a98f4ba..0793dcc76c 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,5 +1,6 @@ { "recommendations": [ - "ms-dotnettools.csharp" + "editorconfig.editorconfig", + "ms-dotnettools.csdevkit" ] } diff --git a/README.md b/README.md index cb722e5df3..6043497181 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Please make sure you have the following prerequisites: - A desktop platform with the [.NET 8.0 SDK](https://dotnet.microsoft.com/download) installed. -When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as the latest version of [Visual Studio](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/), or [Visual Studio Code](https://code.visualstudio.com/) with the [EditorConfig](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig) and [C#](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp) plugin installed. +When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as the latest version of [Visual Studio](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/), or [Visual Studio Code](https://code.visualstudio.com/) with the [EditorConfig](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig) and [C# Dev Kit](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit) plugin installed. ### Downloading the source code From dcd3e5194e3ba41e69fd69c34afb0c0e6243a48f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Brandst=C3=B6tter?= Date: Sat, 12 Oct 2024 20:31:27 +0200 Subject: [PATCH 528/924] Group `HitResult`s with the same name into one column in beatmap ranking Closes #29911 --- .../Visual/Online/TestSceneScoresContainer.cs | 56 ++++++++++++++++++- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 34 ++++++----- .../BeatmapSet/Scores/ScoresContainer.cs | 12 ++-- 3 files changed, 79 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index 33f4d577bd..664eca6e23 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -8,11 +8,13 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; using osu.Framework.Utils; +using osu.Game.Graphics.Sprites; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; @@ -90,6 +92,43 @@ namespace osu.Game.Tests.Visual.Online AddAssert("no best score displayed", () => scoresContainer.ChildrenOfType().Count() == 1); } + [Test] + public void TestHitResultsWithSameNameAreGrouped() + { + AddStep("Load scores without user best", () => + { + var allScores = createScores(); + allScores.UserScore = null; + scoresContainer.Scores = allScores; + }); + + AddUntilStep("wait for scores displayed", () => scoresContainer.ChildrenOfType().Any()); + AddAssert("only one column for slider end", () => scoresContainer.ScoreTable.Columns.Count(c => c.Header.Equals("slider end")) == 1); + + AddAssert("all rows show non-zero slider ends", () => + { + int sliderEndColumnIndex = Array.FindIndex(scoresContainer.ScoreTable.Columns, c => c != null && c.Header.Equals("slider end")); + bool sliderEndFilledInEachRow = true; + + for (int i = 0; i < scoresContainer.ScoreTable.Content?.GetLength(0); i++) + { + switch (scoresContainer.ScoreTable.Content[i, sliderEndColumnIndex]) + { + case OsuSpriteText text: + if (text.Text.Equals(0.0d.ToLocalisableString(@"N0"))) + sliderEndFilledInEachRow = false; + break; + + default: + sliderEndFilledInEachRow = false; + break; + } + } + + return sliderEndFilledInEachRow; + }); + } + [Test] public void TestUserBest() { @@ -287,13 +326,17 @@ namespace osu.Game.Tests.Visual.Online const int initial_great_count = 2000; const int initial_tick_count = 100; + const int initial_slider_end_count = 500; int greatCount = initial_great_count; int tickCount = initial_tick_count; + int sliderEndCount = initial_slider_end_count; - foreach (var s in scores.Scores) + foreach (var (score, index) in scores.Scores.Select((s, i) => (s, i))) { - s.Statistics = new Dictionary + HitResult sliderEndResult = index % 2 == 0 ? HitResult.SliderTailHit : HitResult.SmallTickHit; + + score.Statistics = new Dictionary { { HitResult.Great, greatCount }, { HitResult.LargeTickHit, tickCount }, @@ -301,10 +344,19 @@ namespace osu.Game.Tests.Visual.Online { HitResult.Meh, RNG.Next(100) }, { HitResult.Miss, initial_great_count - greatCount }, { HitResult.LargeTickMiss, initial_tick_count - tickCount }, + { sliderEndResult, sliderEndCount }, + }; + + // Some hit results, including SliderTailHit and SmallTickHit, are only displayed + // when the maximum number is known + score.MaximumStatistics = new Dictionary + { + { sliderEndResult, initial_slider_end_count }, }; greatCount -= 100; tickCount -= RNG.Next(1, 5); + sliderEndCount -= 20; } return scores; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index a6868efb5d..671b5df33b 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -9,7 +9,6 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions; -using osu.Framework.Extensions.EnumExtensions; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -58,9 +57,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } /// - /// The statistics that appear in the table, in order of appearance. + /// The statistics that appear in the table, grouped by their display name. If multiple HitResults have the same + /// DisplayName (for example, "slider end" is the name for both HitResult.SliderTailTick and HitResult.SmallTickHit + /// in osu!std) the name will only be listed once. /// - private readonly List<(HitResult result, LocalisableString displayName)> statisticResultTypes = new List<(HitResult, LocalisableString)>(); + private readonly List<(LocalisableString displayName, IEnumerable results)> statisticResults = new List<(LocalisableString displayName, IEnumerable results)>(); private bool showPerformancePoints; @@ -72,7 +73,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores return; showPerformancePoints = showPerformanceColumn; - statisticResultTypes.Clear(); + statisticResults.Clear(); for (int i = 0; i < scores.Count; i++) backgroundFlow.Add(new ScoreTableRowBackground(i, scores[i], row_height)); @@ -105,20 +106,18 @@ namespace osu.Game.Overlays.BeatmapSet.Scores var ruleset = scores.First().Ruleset.CreateInstance(); - foreach (var result in EnumExtensions.GetValuesInOrder()) + foreach (var resultGroup in ruleset.GetHitResults().GroupBy(r => r.displayName)) { - if (!allScoreStatistics.Contains(result)) + if (!resultGroup.Any(r => allScoreStatistics.Contains(r.result))) continue; // for the time being ignore bonus result types. // this is not being sent from the API and will be empty in all cases. - if (result.IsBonus()) + if (resultGroup.All(r => r.result.IsBonus())) continue; - var displayName = ruleset.GetDisplayNameForHitResult(result); - - columns.Add(new TableColumn(displayName, Anchor.CentreLeft, new Dimension(minSize: 35, maxSize: 60))); - statisticResultTypes.Add((result, displayName)); + columns.Add(new TableColumn(resultGroup.Key, Anchor.CentreLeft, new Dimension(minSize: 35, maxSize: 60))); + statisticResults.Add((resultGroup.Key, resultGroup.Select(r => r.result))); } if (showPerformancePoints) @@ -167,12 +166,17 @@ namespace osu.Game.Overlays.BeatmapSet.Scores #pragma warning restore 618 }; - var availableStatistics = score.GetStatisticsForDisplay().ToDictionary(tuple => tuple.Result); + var availableStatistics = score.GetStatisticsForDisplay().ToLookup(touple => touple.DisplayName); - foreach (var result in statisticResultTypes) + foreach (var (columnName, resultTypes) in statisticResults) { - if (!availableStatistics.TryGetValue(result.result, out var stat)) - stat = new HitResultDisplayStatistic(result.result, 0, null, result.displayName); + HitResultDisplayStatistic stat = new HitResultDisplayStatistic(resultTypes.First(), 0, null, columnName); + + if (availableStatistics.Contains(columnName)) + { + foreach (var s in availableStatistics[columnName]) + stat = s; + } content.Add(new StatisticText(stat.Count, stat.MaxCount, @"N0") { Colour = stat.Count == 0 ? Color4.Gray : Color4.White }); } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index b53b7826f3..39491e338e 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -34,7 +34,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly IBindable user = new Bindable(); private readonly Box background; - private readonly ScoreTable scoreTable; + public readonly ScoreTable ScoreTable; private readonly FillFlowContainer topScoresContainer; private readonly LoadingLayer loading; private readonly LeaderboardModSelector modSelector; @@ -59,8 +59,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores loadCancellationSource = new CancellationTokenSource(); topScoresContainer.Clear(); - scoreTable.ClearScores(); - scoreTable.Hide(); + ScoreTable.ClearScores(); + ScoreTable.Hide(); loading.Hide(); loading.FinishTransforms(); @@ -85,8 +85,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores var scores = value.Scores.Select(s => s.ToScoreInfo(rulesets, beatmapInfo)).OrderByTotalScore().ToArray(); var topScore = scores.First(); - scoreTable.DisplayScores(scores, apiBeatmap.Status.GrantsPerformancePoints()); - scoreTable.Show(); + ScoreTable.DisplayScores(scores, apiBeatmap.Status.GrantsPerformancePoints()); + ScoreTable.Show(); var userScore = value.UserScore; var userScoreInfo = userScore?.Score.ToScoreInfo(rulesets, beatmapInfo); @@ -175,7 +175,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Direction = FillDirection.Vertical, Spacing = new Vector2(0, 5), }, - scoreTable = new ScoreTable + ScoreTable = new ScoreTable { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, From 3ac6a9f0aedf4381d41e728a06896609a4992d0c Mon Sep 17 00:00:00 2001 From: finadoggie <75299710+Finadoggie@users.noreply.github.com> Date: Sat, 12 Oct 2024 15:30:02 -0700 Subject: [PATCH 529/924] Join variable assignments with declarations --- .../Difficulty/OsuPerformanceCalculator.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 95babd0fb9..65e7f705ea 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -134,11 +134,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (attributes.SliderCount > 0) { - double estimateSliderEndsDropped; - estimateSliderEndsDropped = usingClassicSliderAccuracy ? Math.Clamp(Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders) : Math.Min(countSliderEndsDropped + countLargeTickMiss, estimateDifficultSliders); - - double sliderNerfFactor = 0; - sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + attributes.SliderFactor; + double estimateSliderEndsDropped = usingClassicSliderAccuracy ? Math.Clamp(Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders) : Math.Min(countSliderEndsDropped + countLargeTickMiss, estimateDifficultSliders); + double sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + attributes.SliderFactor; aimValue *= sliderNerfFactor; } From 29b1697a70f47e7feb9c0393965a0b75c290e92c Mon Sep 17 00:00:00 2001 From: finadoggie <75299710+Finadoggie@users.noreply.github.com> Date: Sat, 12 Oct 2024 15:34:04 -0700 Subject: [PATCH 530/924] consolidated if statements for getting effectiveMissCount and countSliderEndsDropped --- .../Difficulty/OsuPerformanceCalculator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 65e7f705ea..62229dd813 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -48,13 +48,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); countLargeTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss); - if (!usingClassicSliderAccuracy) - countSliderEndsDropped = osuAttributes.SliderCount - score.Statistics.GetValueOrDefault(HitResult.SliderTailHit); - if (usingClassicSliderAccuracy) effectiveMissCount = calculateEffectiveMissCount(osuAttributes); else + { + countSliderEndsDropped = osuAttributes.SliderCount - score.Statistics.GetValueOrDefault(HitResult.SliderTailHit); effectiveMissCount = countMiss; + } double multiplier = PERFORMANCE_BASE_MULTIPLIER; From 88af57818c2a565ae450afdf2161ee39b22a7beb Mon Sep 17 00:00:00 2001 From: finadoggie <75299710+Finadoggie@users.noreply.github.com> Date: Sat, 12 Oct 2024 15:36:42 -0700 Subject: [PATCH 531/924] only assign countLargeTickMiss for slider accuracy scores helps indicate it should only be used for slider acc scores --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 62229dd813..b77afc173d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -46,13 +46,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty countOk = score.Statistics.GetValueOrDefault(HitResult.Ok); countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); - countLargeTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss); if (usingClassicSliderAccuracy) effectiveMissCount = calculateEffectiveMissCount(osuAttributes); else { countSliderEndsDropped = osuAttributes.SliderCount - score.Statistics.GetValueOrDefault(HitResult.SliderTailHit); + countLargeTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss); effectiveMissCount = countMiss; } From 5192599543a17a9d47a92627d0128051c6904e56 Mon Sep 17 00:00:00 2001 From: finadoggie <75299710+Finadoggie@users.noreply.github.com> Date: Sat, 12 Oct 2024 16:45:34 -0700 Subject: [PATCH 532/924] remove score debugging code I accidentally left in --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index b77afc173d..84da54b9b8 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (attributes.SliderCount > 0) { - double estimateSliderEndsDropped = usingClassicSliderAccuracy ? Math.Clamp(Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders) : Math.Min(countSliderEndsDropped + countLargeTickMiss, estimateDifficultSliders); + double estimateSliderEndsDropped = usingClassicSliderAccuracy ? Math.Clamp(Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo), countSliderEndsDropped + countLargeTickMiss, estimateDifficultSliders) : Math.Min(countSliderEndsDropped + countLargeTickMiss, estimateDifficultSliders); double sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + attributes.SliderFactor; aimValue *= sliderNerfFactor; } From 6bcfed8963f825c66e0429ca53cd4e4f512e4a90 Mon Sep 17 00:00:00 2001 From: finadoggie <75299710+Finadoggie@users.noreply.github.com> Date: Sat, 12 Oct 2024 16:53:32 -0700 Subject: [PATCH 533/924] Revert "remove score debugging code I accidentally left in" This reverts commit 5192599543a17a9d47a92627d0128051c6904e56. --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 84da54b9b8..b77afc173d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (attributes.SliderCount > 0) { - double estimateSliderEndsDropped = usingClassicSliderAccuracy ? Math.Clamp(Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo), countSliderEndsDropped + countLargeTickMiss, estimateDifficultSliders) : Math.Min(countSliderEndsDropped + countLargeTickMiss, estimateDifficultSliders); + double estimateSliderEndsDropped = usingClassicSliderAccuracy ? Math.Clamp(Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders) : Math.Min(countSliderEndsDropped + countLargeTickMiss, estimateDifficultSliders); double sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + attributes.SliderFactor; aimValue *= sliderNerfFactor; } From 9681e3ac46d14312ca61a7f6152a02108d55a805 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 13 Oct 2024 18:36:23 +0900 Subject: [PATCH 534/924] Overwrite downloaded data packages In https://github.com/ppy/osu/actions/runs/11311858931/job/31458581002, I cancelled the run during the download from `data.ppy.sh`. In https://github.com/ppy/osu/actions/runs/11313128285/job/31461534857, `wget` skipped downloading the file due to the `-nc` option (no-clobber), i.e.: if the file exists, don't re-download. The only way I'm aware of to resolve this with wget is to either use `-c` (continue), which may lead to broken files, or to explicitly specify the output file via `-O`. Thought I'd clean up a few pieces in the process. Why not curl? Mostly historical - some distros don't come with curl. It may be okay now but there's probably no point changing this at the moment... --- .github/workflows/diffcalc.yml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml index ebf22b8a0e..3b77a463c1 100644 --- a/.github/workflows/diffcalc.yml +++ b/.github/workflows/diffcalc.yml @@ -273,22 +273,23 @@ jobs: echo "TARGET_DIR=${{ needs.directory.outputs.GENERATOR_DIR }}/sql/${ruleset}" >> "${GITHUB_OUTPUT}" echo "DATA_NAME=${performance_data_name}" >> "${GITHUB_OUTPUT}" + echo "DATA_PKG=${performance_data_name}.tar.bz2" >> "${GITHUB_OUTPUT}" - name: Restore cache id: restore-cache uses: maxnowack/local-cache@720e69c948191660a90aa1cf6a42fc4d2dacdf30 # v2 with: - path: ${{ steps.query.outputs.DATA_NAME }}.tar.bz2 + path: ${{ steps.query.outputs.DATA_PKG }} key: ${{ steps.query.outputs.DATA_NAME }} - name: Download if: steps.restore-cache.outputs.cache-hit != 'true' run: | - wget -q -nc "https://data.ppy.sh/${{ steps.query.outputs.DATA_NAME }}.tar.bz2" + wget -q -O "${{ steps.query.outputs.DATA_PKG }}" "https://data.ppy.sh/${{ steps.query.outputs.DATA_PKG }}" - name: Extract run: | - tar -I lbzip2 -xf "${{ steps.query.outputs.DATA_NAME }}.tar.bz2" + tar -I lbzip2 -xf "${{ steps.query.outputs.DATA_PKG }}" rm -r "${{ steps.query.outputs.TARGET_DIR }}" mv "${{ steps.query.outputs.DATA_NAME }}" "${{ steps.query.outputs.TARGET_DIR }}" @@ -304,22 +305,23 @@ jobs: echo "TARGET_DIR=${{ needs.directory.outputs.GENERATOR_DIR }}/beatmaps" >> "${GITHUB_OUTPUT}" echo "DATA_NAME=${beatmaps_data_name}" >> "${GITHUB_OUTPUT}" + echo "DATA_PKG=${beatmaps_data_name}.tar.bz2" >> "${GITHUB_OUTPUT}" - name: Restore cache id: restore-cache uses: maxnowack/local-cache@720e69c948191660a90aa1cf6a42fc4d2dacdf30 # v2 with: - path: ${{ steps.query.outputs.DATA_NAME }}.tar.bz2 + path: ${{ steps.query.outputs.DATA_PKG }} key: ${{ steps.query.outputs.DATA_NAME }} - name: Download if: steps.restore-cache.outputs.cache-hit != 'true' run: | - wget -q -nc "https://data.ppy.sh/${{ steps.query.outputs.DATA_NAME }}.tar.bz2" + wget -q -O "${{ steps.query.outputs.DATA_PKG }}" "https://data.ppy.sh/${{ steps.query.outputs.DATA_PKG }}" - name: Extract run: | - tar -I lbzip2 -xf "${{ steps.query.outputs.DATA_NAME }}.tar.bz2" + tar -I lbzip2 -xf "${{ steps.query.outputs.DATA_PKG }}" rm -r "${{ steps.query.outputs.TARGET_DIR }}" mv "${{ steps.query.outputs.DATA_NAME }}" "${{ steps.query.outputs.TARGET_DIR }}" From 868a7db9e9a0b0a49037d234d7f318c793bdc764 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 13 Oct 2024 22:29:00 +0900 Subject: [PATCH 535/924] Start preparing player earlier when quick retrying Should help with https://github.com/ppy/osu/issues/9039. --- osu.Game/Screens/Play/PlayerLoader.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 7682bba9a6..9b4bf7d633 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -455,7 +455,19 @@ namespace osu.Game.Screens.Play MetadataInfo.Loading = true; content.FadeInFromZero(500, Easing.OutQuint); - content.ScaleTo(1, 650, Easing.OutQuint).Then().Schedule(prepareNewPlayer); + + if (quickRestart) + { + prepareNewPlayer(); + content.ScaleTo(1, 650, Easing.OutQuint); + } + else + { + content + .ScaleTo(1, 650, Easing.OutQuint) + .Then() + .Schedule(prepareNewPlayer); + } using (BeginDelayedSequence(delayBeforeSideDisplays)) { From 86b240d1311ed65b4a20d5b2a288db6512c8b6e7 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 13 Oct 2024 10:59:52 -0400 Subject: [PATCH 536/924] Add failing test cases --- .../Editor/TestSceneOsuEditor.cs | 72 +++++++++++++++++++ .../TestSceneSliderPlacementBlueprint.cs | 26 +++++++ 2 files changed, 98 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditor.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditor.cs index 03ab7ebbf7..befaf58029 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditor.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditor.cs @@ -1,8 +1,19 @@ // 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.Input; +using osu.Framework.Testing; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Edit.Components.RadioButtons; +using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Tests.Visual; +using osuTK; +using osuTK.Input; namespace osu.Game.Rulesets.Osu.Tests.Editor { @@ -10,5 +21,66 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor public partial class TestSceneOsuEditor : EditorTestScene { protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + + [Test] + public void TestTouchInputAfterTouchingComposeArea() + { + AddStep("tap circle", () => tap(this.ChildrenOfType().Single(b => b.Button.Label == "HitCircle"))); + + // this input is just for interacting with compose area + AddStep("tap playfield", () => tap(this.ChildrenOfType().Single())); + + AddStep("move current time", () => InputManager.Key(Key.Right)); + + AddStep("tap to place circle", () => tap(this.ChildrenOfType().Single().ToScreenSpace(new Vector2(10, 10)))); + AddAssert("circle placed correctly", () => + { + var circle = (HitCircle)EditorBeatmap.HitObjects.Single(h => h.StartTime == EditorClock.CurrentTimeAccurate); + Assert.Multiple(() => + { + Assert.That(circle.Position.X, Is.EqualTo(10f).Within(0.01f)); + Assert.That(circle.Position.Y, Is.EqualTo(10f).Within(0.01f)); + }); + + return true; + }); + + AddStep("tap slider", () => tap(this.ChildrenOfType().Single(b => b.Button.Label == "Slider"))); + + // this input is just for interacting with compose area + AddStep("tap playfield", () => tap(this.ChildrenOfType().Single())); + + AddStep("move current time", () => InputManager.Key(Key.Right)); + + AddStep("hold to draw slider", () => InputManager.BeginTouch(new Touch(TouchSource.Touch1, this.ChildrenOfType().Single().ToScreenSpace(new Vector2(50, 20))))); + AddStep("drag to draw", () => InputManager.MoveTouchTo(new Touch(TouchSource.Touch1, this.ChildrenOfType().Single().ToScreenSpace(new Vector2(200, 50))))); + AddAssert("selection not initiated", () => this.ChildrenOfType().All(d => d.State == Visibility.Hidden)); + AddStep("end", () => InputManager.EndTouch(new Touch(TouchSource.Touch1, InputManager.CurrentState.Touch.GetTouchPosition(TouchSource.Touch1)!.Value))); + AddAssert("slider placed correctly", () => + { + var slider = (Slider)EditorBeatmap.HitObjects.Single(h => h.StartTime == EditorClock.CurrentTimeAccurate); + Assert.Multiple(() => + { + Assert.That(slider.Position.X, Is.EqualTo(50f).Within(0.01f)); + Assert.That(slider.Position.Y, Is.EqualTo(20f).Within(0.01f)); + Assert.That(slider.Path.ControlPoints.Count, Is.EqualTo(2)); + Assert.That(slider.Path.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); + + // the final position may be slightly off from the mouse position when drawing, account for that. + Assert.That(slider.Path.ControlPoints[1].Position.X, Is.EqualTo(150).Within(5)); + Assert.That(slider.Path.ControlPoints[1].Position.Y, Is.EqualTo(30).Within(5)); + }); + + return true; + }); + } + + private void tap(Drawable drawable) => tap(drawable.ScreenSpaceDrawQuad.Centre); + + private void tap(Vector2 position) + { + InputManager.BeginTouch(new Touch(TouchSource.Touch1, position)); + InputManager.EndTouch(new Touch(TouchSource.Touch1, position)); + } } } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index 019565ae29..5831cc0a8a 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using NUnit.Framework; +using osu.Framework.Input; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Rulesets.Edit; @@ -392,6 +393,29 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertFinalControlPointType(3, null); } + [Test] + public void TestSliderDrawingViaTouch() + { + Vector2 startPoint = new Vector2(200); + + AddStep("move mouse to a random point", () => InputManager.MoveMouseTo(InputManager.ToScreenSpace(Vector2.Zero))); + AddStep("begin touch at start point", () => InputManager.BeginTouch(new Touch(TouchSource.Touch1, InputManager.ToScreenSpace(startPoint)))); + + for (int i = 1; i < 20; i++) + addTouchMovementStep(startPoint + new Vector2(i * 40, MathF.Sin(i * MathF.PI / 5) * 50)); + + AddStep("release touch at end point", () => InputManager.EndTouch(new Touch(TouchSource.Touch1, InputManager.CurrentState.Touch.GetTouchPosition(TouchSource.Touch1)!.Value))); + + assertPlaced(true); + assertLength(808, tolerance: 10); + assertControlPointCount(5); + assertFinalControlPointType(0, PathType.BSpline(4)); + assertFinalControlPointType(1, null); + assertFinalControlPointType(2, null); + assertFinalControlPointType(3, null); + assertFinalControlPointType(4, null); + } + [Test] public void TestPlacePerfectCurveSegmentAlmostLinearlyExterior() { @@ -492,6 +516,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private void addMovementStep(Vector2 position) => AddStep($"move mouse to {position}", () => InputManager.MoveMouseTo(InputManager.ToScreenSpace(position))); + private void addTouchMovementStep(Vector2 position) => AddStep($"move touch1 to {position}", () => InputManager.MoveTouchTo(new Touch(TouchSource.Touch1, InputManager.ToScreenSpace(position)))); + private void addClickStep(MouseButton button) { AddStep($"click {button}", () => InputManager.Click(button)); From f8c8184c5cbd81f473a6cbbe9f80e0e5df73b762 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 13 Oct 2024 11:00:29 -0400 Subject: [PATCH 537/924] Fix placement blueprints not receiving latest mouse position with touch input --- .../Components/ComposeBlueprintContainer.cs | 18 +++++++++--- .../Visual/PlacementBlueprintTestScene.cs | 28 +++++++++++++++++-- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index cbec8fc7a3..10deaa9669 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -295,8 +295,11 @@ namespace osu.Game.Screens.Edit.Compose.Components ensurePlacementCreated(); } - private void updatePlacementPosition() + private void updatePlacementTimeAndPosition() { + if (CurrentPlacement == null) + return; + var snapResult = Composer.FindSnappedPositionAndTime(InputManager.CurrentState.Mouse.Position, CurrentPlacement.SnapType); // if no time was found from positional snapping, we should still quantize to the beat. @@ -329,8 +332,15 @@ namespace osu.Game.Screens.Edit.Compose.Components if (Composer.CursorInPlacementArea) ensurePlacementCreated(); - if (CurrentPlacement != null) - updatePlacementPosition(); + // updates the placement with the latest editor clock time. + updatePlacementTimeAndPosition(); + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + // updates the placement with the latest mouse position. + updatePlacementTimeAndPosition(); + return base.OnMouseMove(e); } protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject item) @@ -367,7 +377,7 @@ namespace osu.Game.Screens.Edit.Compose.Components placementBlueprintContainer.Child = CurrentPlacement = blueprint; // Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame - updatePlacementPosition(); + updatePlacementTimeAndPosition(); updatePlacementSamples(); diff --git a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs index c8d9ef8fc8..a52325dea2 100644 --- a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs @@ -3,9 +3,11 @@ #nullable disable +using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; @@ -24,6 +26,10 @@ namespace osu.Game.Tests.Visual protected PlacementBlueprintTestScene() { base.Content.Add(HitObjectContainer = CreateHitObjectContainer().With(c => c.Clock = new FramedClock(new StopwatchClock()))); + base.Content.Add(new MouseMovementInterceptor + { + MouseMoved = updatePlacementTimeAndPosition, + }); } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) @@ -83,10 +89,11 @@ namespace osu.Game.Tests.Visual protected override void Update() { base.Update(); - - CurrentBlueprint.UpdateTimeAndPosition(SnapForBlueprint(CurrentBlueprint)); + updatePlacementTimeAndPosition(); } + private void updatePlacementTimeAndPosition() => CurrentBlueprint.UpdateTimeAndPosition(SnapForBlueprint(CurrentBlueprint)); + protected virtual SnapResult SnapForBlueprint(HitObjectPlacementBlueprint blueprint) => new SnapResult(InputManager.CurrentState.Mouse.Position, null); @@ -107,5 +114,22 @@ namespace osu.Game.Tests.Visual protected abstract DrawableHitObject CreateHitObject(HitObject hitObject); protected abstract HitObjectPlacementBlueprint CreateBlueprint(); + + private partial class MouseMovementInterceptor : Drawable + { + public Action MouseMoved; + + public MouseMovementInterceptor() + { + RelativeSizeAxes = Axes.Both; + Depth = float.MinValue; + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + MouseMoved?.Invoke(); + return base.OnMouseMove(e); + } + } } } From 53671ad11eed29e89ad6bff674cce505f09a43ed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Oct 2024 00:35:18 +0900 Subject: [PATCH 538/924] Only update beatmaps which actually had offsets Without this every beatmap gets a write and it reloads the whole of song select, basically. --- osu.Game/Beatmaps/BeatmapManager.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index f72741af16..4191771116 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -285,7 +285,8 @@ namespace osu.Game.Beatmaps /// /// The query. /// The first result for the provided query, or null if no results were found. - public BeatmapInfo? QueryBeatmap(Expression> query) => Realm.Run(r => r.All().Filter($"{nameof(BeatmapInfo.BeatmapSet)}.{nameof(BeatmapSetInfo.DeletePending)} == false").FirstOrDefault(query)?.Detach()); + public BeatmapInfo? QueryBeatmap(Expression> query) => Realm.Run(r => + r.All().Filter($"{nameof(BeatmapInfo.BeatmapSet)}.{nameof(BeatmapSetInfo.DeletePending)} == false").FirstOrDefault(query)?.Detach()); /// /// A default representation of a WorkingBeatmap to use when no beatmap is available. @@ -322,7 +323,8 @@ namespace osu.Game.Beatmaps foreach (var beatmap in items) { - beatmap.UserSettings.Offset = 0.0; + if (beatmap.UserSettings.Offset != 0) + beatmap.UserSettings.Offset = 0; } PostNotification?.Invoke(new ProgressCompletionNotification { Text = reset_complete_message }); From 8f1fbb44c4e40b57c91d7b4a045e3c3a786dc9e7 Mon Sep 17 00:00:00 2001 From: Leander Furumo Date: Sun, 13 Oct 2024 17:36:06 +0200 Subject: [PATCH 539/924] change fps toggle keybind --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index aca0984e0f..3279eecddc 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -101,7 +101,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Alt, InputKey.Home }, GlobalAction.Home), - new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.F }, GlobalAction.ToggleFPSDisplay), + new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.C }, GlobalAction.ToggleFPSDisplay), new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar), new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.S }, GlobalAction.ToggleSkinEditor), From 0e768cc5175000f689ca303e7bbeac1bae767634 Mon Sep 17 00:00:00 2001 From: Leander Furumo Date: Sun, 13 Oct 2024 22:50:58 +0200 Subject: [PATCH 540/924] remove default keybind for toggling fps counter --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 3279eecddc..0878813982 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -101,7 +101,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Alt, InputKey.Home }, GlobalAction.Home), - new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.C }, GlobalAction.ToggleFPSDisplay), + new KeyBinding(new[] { InputKey.None }, GlobalAction.ToggleFPSDisplay), new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar), new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.S }, GlobalAction.ToggleSkinEditor), From 379794c46288189ea0dbb7782b72eaf232adc1ba Mon Sep 17 00:00:00 2001 From: Leander Furumo Date: Mon, 14 Oct 2024 00:00:45 +0200 Subject: [PATCH 541/924] replace empty keybinding array with input key type of None --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 0878813982..3f5fc0ed58 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -101,7 +101,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Alt, InputKey.Home }, GlobalAction.Home), - new KeyBinding(new[] { InputKey.None }, GlobalAction.ToggleFPSDisplay), + new KeyBinding(InputKey.None, GlobalAction.ToggleFPSDisplay), new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar), new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.S }, GlobalAction.ToggleSkinEditor), From 90a08b8a6872585ecf5303fcb5e3e2defc5c1ee9 Mon Sep 17 00:00:00 2001 From: Shin Morisawa Date: Mon, 14 Oct 2024 09:55:28 +0900 Subject: [PATCH 542/924] Fix Beatmap Delete Dialog --- osu.Game/Screens/Select/BeatmapDeleteDialog.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapDeleteDialog.cs b/osu.Game/Screens/Select/BeatmapDeleteDialog.cs index 8bc40dbd9e..16f0cbe65e 100644 --- a/osu.Game/Screens/Select/BeatmapDeleteDialog.cs +++ b/osu.Game/Screens/Select/BeatmapDeleteDialog.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Select public BeatmapDeleteDialog(BeatmapSetInfo beatmapSet) { this.beatmapSet = beatmapSet; - BodyText = $@"{beatmapSet.Metadata.Artist} - {beatmapSet.Metadata.Title}"; + BodyText = beatmapSet.Metadata.GetDisplayTitleRomanisable(false); } [BackgroundDependencyLoader] From 1a142ff94448dc93c7f88ad89493c15fc6159248 Mon Sep 17 00:00:00 2001 From: jhk2601 Date: Mon, 14 Oct 2024 01:54:02 -0400 Subject: [PATCH 543/924] minor changes --- osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs | 3 ++- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs index a9026e470b..8ed0e60445 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs @@ -85,7 +85,8 @@ namespace osu.Game.Rulesets.Osu.Mods Debug.Assert(osuPlayfield.Cursor != null); var realCursor = (OsuCursor)osuPlayfield.Cursor.ActiveCursor; realCursor.isBloom = true; - float currentCombSize = (float)Interpolation.Lerp(osuPlayfield.Cursor.Scale.X, ComboBasedSize, Math.Clamp(osuPlayfield.Time.Elapsed / TRANSITION_DURATION, 0, 1)); + float currentCombSize = (float)Interpolation.Lerp(realCursor.ComboSize, ComboBasedSize, Math.Clamp(osuPlayfield.Time.Elapsed / TRANSITION_DURATION, 0, 1)); + Console.WriteLine(ComboBasedSize + " " + currentCombSize); if (beBaseSize) { realCursor.ComboSize = 1; diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index 75a38826ad..f3a6b0da3d 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -17,6 +17,7 @@ using osu.Game.Screens.Play; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; +using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Osu.UI.Cursor { @@ -70,13 +71,13 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor autoCursorScale.ValueChanged += _ => cursorScale.Value = CalculateCursorScale(); cursorScale.BindValueChanged(e => cursorScaleContainer.Scale = new Vector2(e.NewValue), true); - isBloom = false; } protected override void LoadComplete() { base.LoadComplete(); cursorScale.Value = CalculateCursorScale(); + isBloom = false; } protected override void Update() //this should not exist will implement sane fix From 17aed26f854ee5c45de5e84bb2ea5639446e708a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 14 Oct 2024 13:03:58 +0200 Subject: [PATCH 544/924] Fix shuffle not actually changing the track sometimes See https://github.com/ppy/osu/pull/30215#issuecomment-2407775408 for context. Turns out the test failures were more correct than I'd thought. The long-and-short of it is that both in "pure random" mode and in "permutation" mode, when running out of track history to fall back on, it was possible for the random algorithm to pick the same song twice in a row - which is probably not desired, and which this explicit exclude should make impossible. --- osu.Game/Overlays/MusicController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index a269dbdf4f..0f88765593 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -376,7 +376,7 @@ namespace osu.Game.Overlays { Live result; - var possibleSets = getBeatmapSets().Where(s => !s.Value.Protected || allowProtectedTracks).ToArray(); + var possibleSets = getBeatmapSets().Where(s => !s.Value.Equals(current?.BeatmapSetInfo) && (!s.Value.Protected || allowProtectedTracks)).ToArray(); if (possibleSets.Length == 0) return null; From 945d907a3d493c9b399a1b2855a5c67059b0f9f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 14 Oct 2024 14:05:13 +0200 Subject: [PATCH 545/924] Add test covering correct operation of mirror mod --- .../Mods/TestSceneOsuModMirror.cs | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMirror.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMirror.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMirror.cs new file mode 100644 index 0000000000..0b3496ba68 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMirror.cs @@ -0,0 +1,64 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Framework.Utils; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.UI; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests.Mods +{ + public partial class TestSceneOsuModMirror : OsuModTestScene + { + [Test] + public void TestCorrectReflections([Values] OsuModMirror.MirrorType type, [Values] bool withStrictTracking) => CreateModTest(new ModTestData + { + Autoplay = true, + Beatmap = new OsuBeatmap + { + HitObjects = + { + new Slider + { + Position = new Vector2(0), + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint(), + new PathControlPoint(new Vector2(100, 0)) + } + }, + TickDistanceMultiplier = 0.5, + RepeatCount = 1, + } + } + }, + Mods = withStrictTracking + ? [new OsuModMirror { Reflection = { Value = type } }, new OsuModStrictTracking()] + : [new OsuModMirror { Reflection = { Value = type } }], + PassCondition = () => + { + var slider = this.ChildrenOfType().SingleOrDefault(); + var playfield = this.ChildrenOfType().Single(); + + if (slider == null) + return false; + + return Precision.AlmostEquals(playfield.ToLocalSpace(slider.HeadCircle.ScreenSpaceDrawQuad.Centre), slider.HitObject.Position) + && Precision.AlmostEquals(playfield.ToLocalSpace(slider.TailCircle.ScreenSpaceDrawQuad.Centre), slider.HitObject.Position) + && Precision.AlmostEquals(playfield.ToLocalSpace(slider.NestedHitObjects.OfType().Single().ScreenSpaceDrawQuad.Centre), + slider.HitObject.Position + slider.HitObject.Path.PositionAt(1)) + && Precision.AlmostEquals(playfield.ToLocalSpace(slider.NestedHitObjects.OfType().First().ScreenSpaceDrawQuad.Centre), + slider.HitObject.Position + slider.HitObject.Path.PositionAt(0.7f)); + } + }); + } +} From 275b86cd3c92e2d4c0ac9efbd935cb3092cc5ae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 14 Oct 2024 14:22:26 +0200 Subject: [PATCH 546/924] Fix strict tracking mod not populating path progress for ticks/repeats Closes https://github.com/ppy/osu/issues/30237. This is the root failure in the issue, and one that *only* presents when another conversion mod that repositions the objects is also active. That makes the `PathProgress` of the nesteds to be zero, therefore making them occupy the position of the slider head after any mutation to the path. --- osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs index 2c9292c58b..7d2fd628f6 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs @@ -120,6 +120,7 @@ namespace osu.Game.Rulesets.Osu.Mods Position = Position + Path.PositionAt(e.PathProgress), StackHeight = StackHeight, Scale = Scale, + PathProgress = e.PathProgress, }); break; @@ -150,6 +151,7 @@ namespace osu.Game.Rulesets.Osu.Mods Position = Position + Path.PositionAt(e.PathProgress), StackHeight = StackHeight, Scale = Scale, + PathProgress = e.PathProgress, }); break; } From 1f1a174c5068f61f2084577e0c7fd31ae739d2fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 14 Oct 2024 14:06:09 +0200 Subject: [PATCH 547/924] Remove no longer required nested object reposition hacks As touched on in https://github.com/ppy/osu/issues/30237#issuecomment-2408557766, these types of maneouvers are no longer required after https://github.com/ppy/osu/pull/30021 - although as it turns out on closer inspection, these things being there still *did not actually break anything*, because the `slider.Path` mutation at the end of `modifySlider()` causes `updateNestedPositions()` to be called eventually anyway. So this is at mostly a code quality upgrade. --- .../Utils/OsuHitObjectGenerationUtils.cs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index e936c24c08..f27624a633 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -3,7 +3,6 @@ using System; using System.Linq; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Beatmaps; @@ -117,10 +116,9 @@ namespace osu.Game.Rulesets.Osu.Utils if (osuObject is not Slider slider) return; - void reflectNestedObject(OsuHitObject nested) => nested.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - nested.Position.X, nested.Position.Y); static void reflectControlPoint(PathControlPoint point) => point.Position = new Vector2(-point.Position.X, point.Position.Y); - modifySlider(slider, reflectNestedObject, reflectControlPoint); + modifySlider(slider, reflectControlPoint); } /// @@ -134,10 +132,9 @@ namespace osu.Game.Rulesets.Osu.Utils if (osuObject is not Slider slider) return; - void reflectNestedObject(OsuHitObject nested) => nested.Position = new Vector2(nested.Position.X, OsuPlayfield.BASE_SIZE.Y - nested.Position.Y); static void reflectControlPoint(PathControlPoint point) => point.Position = new Vector2(point.Position.X, -point.Position.Y); - modifySlider(slider, reflectNestedObject, reflectControlPoint); + modifySlider(slider, reflectControlPoint); } /// @@ -146,10 +143,9 @@ namespace osu.Game.Rulesets.Osu.Utils /// The slider to be flipped. public static void FlipSliderInPlaceHorizontally(Slider slider) { - void flipNestedObject(OsuHitObject nested) => nested.Position = new Vector2(slider.X - (nested.X - slider.X), nested.Y); static void flipControlPoint(PathControlPoint point) => point.Position = new Vector2(-point.Position.X, point.Position.Y); - modifySlider(slider, flipNestedObject, flipControlPoint); + modifySlider(slider, flipControlPoint); } /// @@ -159,18 +155,13 @@ namespace osu.Game.Rulesets.Osu.Utils /// The angle, measured in radians, to rotate the slider by. public static void RotateSlider(Slider slider, float rotation) { - void rotateNestedObject(OsuHitObject nested) => nested.Position = rotateVector(nested.Position - slider.Position, rotation) + slider.Position; void rotateControlPoint(PathControlPoint point) => point.Position = rotateVector(point.Position, rotation); - modifySlider(slider, rotateNestedObject, rotateControlPoint); + modifySlider(slider, rotateControlPoint); } - private static void modifySlider(Slider slider, Action modifyNestedObject, Action modifyControlPoint) + private static void modifySlider(Slider slider, Action modifyControlPoint) { - // No need to update the head and tail circles, since slider handles that when the new slider path is set - slider.NestedHitObjects.OfType().ForEach(modifyNestedObject); - slider.NestedHitObjects.OfType().ForEach(modifyNestedObject); - var controlPoints = slider.Path.ControlPoints.Select(p => new PathControlPoint(p.Position, p.Type)).ToArray(); foreach (var point in controlPoints) modifyControlPoint(point); From 98141430b0cb6d4b45397eadbf8b537020330af1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 14 Oct 2024 14:48:07 +0200 Subject: [PATCH 548/924] Add failing tests --- .../Editor/TestSceneManiaSelectionHandler.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaSelectionHandler.cs index 4285ef2029..a70b90789d 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaSelectionHandler.cs @@ -118,5 +118,45 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor AddAssert("all objects in last column", () => EditorBeatmap.HitObjects.All(ho => ((ManiaHitObject)ho).Column == 3)); AddAssert("all objects remain selected", () => EditorBeatmap.SelectedHitObjects.SequenceEqual(EditorBeatmap.HitObjects)); } + + [Test] + public void TestOffScreenObjectsRemainSelectedOnHorizontalFlip() + { + AddStep("create objects", () => + { + for (int i = 0; i < 20; ++i) + EditorBeatmap.Add(new Note { StartTime = 1000 * i, Column = i % 4 }); + }); + + AddStep("select everything", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); + AddStep("flip", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.H); + InputManager.ReleaseKey(Key.ControlLeft); + }); + + AddAssert("all objects remain selected", () => EditorBeatmap.SelectedHitObjects.SequenceEqual(EditorBeatmap.HitObjects)); + } + + [Test] + public void TestOffScreenObjectsRemainSelectedOnVerticalFlip() + { + AddStep("create objects", () => + { + for (int i = 0; i < 20; ++i) + EditorBeatmap.Add(new Note { StartTime = 1000 * i, Column = i % 4 }); + }); + + AddStep("select everything", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); + AddStep("flip", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.J); + InputManager.ReleaseKey(Key.ControlLeft); + }); + + AddAssert("all objects remain selected", () => EditorBeatmap.SelectedHitObjects.SequenceEqual(EditorBeatmap.HitObjects.Reverse())); + } } } From 59655a18307d018968710e0abc9584ef88ab90dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 14 Oct 2024 14:48:09 +0200 Subject: [PATCH 549/924] Fix flip operations sometimes not preserving selection Closes https://github.com/ppy/osu/issues/30250. --- .../Edit/ManiaSelectionHandler.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 7e0991a4d4..74e616ac3f 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -54,9 +54,8 @@ namespace osu.Game.Rulesets.Mania.Edit int firstColumn = flipOverOrigin ? 0 : selectedObjects.Min(ho => ho.Column); int lastColumn = flipOverOrigin ? (int)EditorBeatmap.BeatmapInfo.Difficulty.CircleSize - 1 : selectedObjects.Max(ho => ho.Column); - EditorBeatmap.PerformOnSelection(hitObject => + performOnSelection(maniaObject => { - var maniaObject = (ManiaHitObject)hitObject; maniaPlayfield.Remove(maniaObject); maniaObject.Column = firstColumn + (lastColumn - maniaObject.Column); maniaPlayfield.Add(maniaObject); @@ -71,7 +70,7 @@ namespace osu.Game.Rulesets.Mania.Edit double selectionStartTime = selectedObjects.Min(ho => ho.StartTime); double selectionEndTime = selectedObjects.Max(ho => ho.GetEndTime()); - EditorBeatmap.PerformOnSelection(hitObject => + performOnSelection(hitObject => { hitObject.StartTime = selectionStartTime + (selectionEndTime - hitObject.GetEndTime()); }); @@ -117,14 +116,21 @@ namespace osu.Game.Rulesets.Mania.Edit columnDelta = Math.Clamp(columnDelta, -minColumn, maniaPlayfield.TotalColumns - 1 - maxColumn); - EditorBeatmap.PerformOnSelection(h => + performOnSelection(h => { maniaPlayfield.Remove(h); - ((ManiaHitObject)h).Column += columnDelta; + h.Column += columnDelta; maniaPlayfield.Add(h); }); + } - // `HitObjectUsageEventBuffer`'s usage transferal flows and the playfield's `SetKeepAlive()` functionality do not combine well with this operation's usage pattern, + private void performOnSelection(Action action) + { + var selectedObjects = EditorBeatmap.SelectedHitObjects.OfType().ToArray(); + + EditorBeatmap.PerformOnSelection(h => action.Invoke((ManiaHitObject)h)); + + // `HitObjectUsageEventBuffer`'s usage transferal flows and the playfield's `SetKeepAlive()` functionality do not combine well with mania's usage patterns, // leading to selections being sometimes partially dropped if some of the objects being moved are off screen // (check blame for detailed explanation). // thus, ensure that selection is preserved manually. From 8cec318c1fc2ec9d6eafd0b4a00d8320ca2c5123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 14 Oct 2024 15:28:32 +0200 Subject: [PATCH 550/924] Loop track even if shuffling if there is only one available Co-authored-by: Dean Herbert --- osu.Game/Overlays/MusicController.cs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 0f88765593..87920fdf55 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -376,11 +376,19 @@ namespace osu.Game.Overlays { Live result; - var possibleSets = getBeatmapSets().Where(s => !s.Value.Equals(current?.BeatmapSetInfo) && (!s.Value.Protected || allowProtectedTracks)).ToArray(); + var possibleSets = getBeatmapSets().Where(s => !s.Value.Protected || allowProtectedTracks).ToList(); - if (possibleSets.Length == 0) + if (possibleSets.Count == 0) return null; + // if there is only one possible set left, play it, even if it is the same as the current track. + // looping is preferable over playing nothing. + if (possibleSets.Count == 1) + return possibleSets.Single(); + + // now that we actually know there is a choice, do not allow the current track to be played again. + possibleSets.RemoveAll(s => s.Value.Equals(current?.BeatmapSetInfo)); + // condition below checks if the signs of `randomHistoryDirection` and `direction` are opposite and not zero. // if that is the case, it means that the user had previously chosen next track `randomHistoryDirection` times and wants to go back, // or that the user had previously chosen previous track `randomHistoryDirection` times and wants to go forward. @@ -410,20 +418,20 @@ namespace osu.Game.Overlays switch (randomSelectAlgorithm.Value) { case RandomSelectAlgorithm.Random: - result = possibleSets[RNG.Next(possibleSets.Length)]; + result = possibleSets[RNG.Next(possibleSets.Count)]; break; case RandomSelectAlgorithm.RandomPermutation: - var notYetPlayedSets = possibleSets.Except(previousRandomSets).ToArray(); + var notYetPlayedSets = possibleSets.Except(previousRandomSets).ToList(); - if (notYetPlayedSets.Length == 0) + if (notYetPlayedSets.Count == 0) { notYetPlayedSets = possibleSets; previousRandomSets.Clear(); randomHistoryDirection = 0; } - result = notYetPlayedSets[RNG.Next(notYetPlayedSets.Length)]; + result = notYetPlayedSets[RNG.Next(notYetPlayedSets.Count)]; break; default: From 40b98d48632b1a051faa19b51ddf666aa1d219e6 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Mon, 14 Oct 2024 09:55:24 -0400 Subject: [PATCH 551/924] Move conditional --- .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 10deaa9669..b94240e000 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -297,9 +297,6 @@ namespace osu.Game.Screens.Edit.Compose.Components private void updatePlacementTimeAndPosition() { - if (CurrentPlacement == null) - return; - var snapResult = Composer.FindSnappedPositionAndTime(InputManager.CurrentState.Mouse.Position, CurrentPlacement.SnapType); // if no time was found from positional snapping, we should still quantize to the beat. @@ -339,7 +336,9 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override bool OnMouseMove(MouseMoveEvent e) { // updates the placement with the latest mouse position. - updatePlacementTimeAndPosition(); + if (CurrentPlacement != null) + updatePlacementTimeAndPosition(); + return base.OnMouseMove(e); } From 035e5a961380dc992472d0e245807bad3f955ac8 Mon Sep 17 00:00:00 2001 From: Leander Furumo Date: Mon, 14 Oct 2024 16:03:29 +0200 Subject: [PATCH 552/924] migrate clearance of conflicting ToggleFPSDisplay keybind --- osu.Game/Database/RealmAccess.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index ad0423191d..2f25791964 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -93,8 +93,9 @@ namespace osu.Game.Database /// 40 2023-12-21 Add ScoreInfo.Version to keep track of which build scores were set on. /// 41 2024-04-17 Add ScoreInfo.TotalScoreWithoutMods for future mod multiplier rebalances. /// 42 2024-08-07 Update mania key bindings to reflect changes to ManiaAction + /// 43 2024-10-14 Reset keybind for toggling FPS display to avoid conflict with "convert to stream" in the editor, if not already changed by user. /// - private const int schema_version = 42; + private const int schema_version = 43; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. @@ -1192,6 +1193,21 @@ namespace osu.Game.Database } break; + + case 43: + { + // Clear default bindings for "Toggle FPS Display", + // as it conflicts with "Convert to Stream" in the editor. + // Only apply change if set to the conflicting bind + // i.e. has been manually rebound by the user. + var keyBindings = migration.NewRealm.All(); + + var toggleFpsBind = keyBindings.FirstOrDefault(bind => bind.ActionInt == (int)GlobalAction.ToggleFPSDisplay); + if (toggleFpsBind != null && toggleFpsBind.KeyCombination.Keys.SequenceEqual(new[] { InputKey.Control, InputKey.Shift, InputKey.F })) + migration.NewRealm.Remove(toggleFpsBind); + + break; + } } Logger.Log($"Migration completed in {stopwatch.ElapsedMilliseconds}ms"); From 750e0b29cae455f89c46f9f0c033d9861dc84a2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Brandst=C3=B6tter?= Date: Mon, 14 Oct 2024 16:11:58 +0200 Subject: [PATCH 553/924] Use `ChildrenOfType<>` to get ScoreTable to test --- .../Visual/Online/TestSceneScoresContainer.cs | 13 +++++++++---- .../Overlays/BeatmapSet/Scores/ScoresContainer.cs | 12 ++++++------ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index 664eca6e23..c17d746232 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -103,16 +103,21 @@ namespace osu.Game.Tests.Visual.Online }); AddUntilStep("wait for scores displayed", () => scoresContainer.ChildrenOfType().Any()); - AddAssert("only one column for slider end", () => scoresContainer.ScoreTable.Columns.Count(c => c.Header.Equals("slider end")) == 1); + AddAssert("only one column for slider end", () => + { + ScoreTable scoreTable = scoresContainer.ChildrenOfType().First(); + return scoreTable.Columns.Count(c => c.Header.Equals("slider end")) == 1; + }); AddAssert("all rows show non-zero slider ends", () => { - int sliderEndColumnIndex = Array.FindIndex(scoresContainer.ScoreTable.Columns, c => c != null && c.Header.Equals("slider end")); + ScoreTable scoreTable = scoresContainer.ChildrenOfType().First(); + int sliderEndColumnIndex = Array.FindIndex(scoreTable.Columns, c => c != null && c.Header.Equals("slider end")); bool sliderEndFilledInEachRow = true; - for (int i = 0; i < scoresContainer.ScoreTable.Content?.GetLength(0); i++) + for (int i = 0; i < scoreTable.Content?.GetLength(0); i++) { - switch (scoresContainer.ScoreTable.Content[i, sliderEndColumnIndex]) + switch (scoreTable.Content[i, sliderEndColumnIndex]) { case OsuSpriteText text: if (text.Text.Equals(0.0d.ToLocalisableString(@"N0"))) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 39491e338e..b53b7826f3 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -34,7 +34,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly IBindable user = new Bindable(); private readonly Box background; - public readonly ScoreTable ScoreTable; + private readonly ScoreTable scoreTable; private readonly FillFlowContainer topScoresContainer; private readonly LoadingLayer loading; private readonly LeaderboardModSelector modSelector; @@ -59,8 +59,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores loadCancellationSource = new CancellationTokenSource(); topScoresContainer.Clear(); - ScoreTable.ClearScores(); - ScoreTable.Hide(); + scoreTable.ClearScores(); + scoreTable.Hide(); loading.Hide(); loading.FinishTransforms(); @@ -85,8 +85,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores var scores = value.Scores.Select(s => s.ToScoreInfo(rulesets, beatmapInfo)).OrderByTotalScore().ToArray(); var topScore = scores.First(); - ScoreTable.DisplayScores(scores, apiBeatmap.Status.GrantsPerformancePoints()); - ScoreTable.Show(); + scoreTable.DisplayScores(scores, apiBeatmap.Status.GrantsPerformancePoints()); + scoreTable.Show(); var userScore = value.UserScore; var userScoreInfo = userScore?.Score.ToScoreInfo(rulesets, beatmapInfo); @@ -175,7 +175,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Direction = FillDirection.Vertical, Spacing = new Vector2(0, 5), }, - ScoreTable = new ScoreTable + scoreTable = new ScoreTable { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, From d7021f989b61e98a0acdf5cbf2c3085bf9c47d11 Mon Sep 17 00:00:00 2001 From: schiavoanto Date: Mon, 14 Oct 2024 16:14:23 +0200 Subject: [PATCH 554/924] Revert 9cd7f2b and fc1ebfd --- osu.Game/Localisation/SkinEditorStrings.cs | 10 ---------- osu.Game/Skinning/GlobalSkinnableContainerLookup.cs | 4 ++-- osu.Game/Skinning/GlobalSkinnableContainers.cs | 9 ++++----- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/osu.Game/Localisation/SkinEditorStrings.cs b/osu.Game/Localisation/SkinEditorStrings.cs index 9ae9aa0747..d96ea7dd9f 100644 --- a/osu.Game/Localisation/SkinEditorStrings.cs +++ b/osu.Game/Localisation/SkinEditorStrings.cs @@ -34,16 +34,6 @@ namespace osu.Game.Localisation /// public static LocalisableString Gameplay => new TranslatableString(getKey(@"gameplay"), @"Gameplay"); - /// - /// "HUD" - /// - public static LocalisableString HUD => new TranslatableString(getKey(@"hud"), @"HUD"); - - /// - /// "Playfield" - /// - public static LocalisableString Playfield => new TranslatableString(getKey(@"playfield"), @"Playfield"); - /// /// "Settings ({0})" /// diff --git a/osu.Game/Skinning/GlobalSkinnableContainerLookup.cs b/osu.Game/Skinning/GlobalSkinnableContainerLookup.cs index 33b5527917..6d78981f0a 100644 --- a/osu.Game/Skinning/GlobalSkinnableContainerLookup.cs +++ b/osu.Game/Skinning/GlobalSkinnableContainerLookup.cs @@ -31,9 +31,9 @@ namespace osu.Game.Skinning public override string ToString() { - if (Ruleset == null) return Lookup.GetLocalisableDescription().ToString(); + if (Ruleset == null) return Lookup.GetDescription(); - return $"{Lookup.GetLocalisableDescription().ToString()} (\"{Ruleset.Name}\" only)"; + return $"{Lookup.GetDescription()} (\"{Ruleset.Name}\" only)"; } public bool Equals(GlobalSkinnableContainerLookup? other) diff --git a/osu.Game/Skinning/GlobalSkinnableContainers.cs b/osu.Game/Skinning/GlobalSkinnableContainers.cs index 73cb1303a0..02f915895f 100644 --- a/osu.Game/Skinning/GlobalSkinnableContainers.cs +++ b/osu.Game/Skinning/GlobalSkinnableContainers.cs @@ -1,8 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Localisation; -using osu.Game.Localisation; +using System.ComponentModel; namespace osu.Game.Skinning { @@ -11,13 +10,13 @@ namespace osu.Game.Skinning /// public enum GlobalSkinnableContainers { - [LocalisableDescription(typeof(SkinEditorStrings), nameof(SkinEditorStrings.HUD))] + [Description("HUD")] MainHUDComponents, - [LocalisableDescription(typeof(SkinEditorStrings), nameof(SkinEditorStrings.SongSelect))] + [Description("Song select")] SongSelect, - [LocalisableDescription(typeof(SkinEditorStrings), nameof(SkinEditorStrings.Playfield))] + [Description("Playfield")] Playfield } } From 25c0ff4168a6e116faa3e7814a5d75959f9442ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Brandst=C3=B6tter?= Date: Mon, 14 Oct 2024 16:14:29 +0200 Subject: [PATCH 555/924] Correct reference to hit result and link to them --- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 671b5df33b..e6052b0e3b 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -58,8 +58,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores /// /// The statistics that appear in the table, grouped by their display name. If multiple HitResults have the same - /// DisplayName (for example, "slider end" is the name for both HitResult.SliderTailTick and HitResult.SmallTickHit - /// in osu!std) the name will only be listed once. + /// DisplayName (for example, "slider end" is the name for both and + /// in osu!) the name will only be listed once. /// private readonly List<(LocalisableString displayName, IEnumerable results)> statisticResults = new List<(LocalisableString displayName, IEnumerable results)>(); From 511f0e99b3933459e28c53456566c0c5639f845c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Brandst=C3=B6tter?= Date: Mon, 14 Oct 2024 16:16:00 +0200 Subject: [PATCH 556/924] Correct typo --- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index e6052b0e3b..4150316f4b 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -166,7 +166,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores #pragma warning restore 618 }; - var availableStatistics = score.GetStatisticsForDisplay().ToLookup(touple => touple.DisplayName); + var availableStatistics = score.GetStatisticsForDisplay().ToLookup(tuple => tuple.DisplayName); foreach (var (columnName, resultTypes) in statisticResults) { From 285756802cd17710078f9e7576658255c7cced47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Brandst=C3=B6tter?= Date: Mon, 14 Oct 2024 16:33:30 +0200 Subject: [PATCH 557/924] Sum up totals for hit results with the same name --- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 4150316f4b..40055bbda8 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -170,15 +170,20 @@ namespace osu.Game.Overlays.BeatmapSet.Scores foreach (var (columnName, resultTypes) in statisticResults) { - HitResultDisplayStatistic stat = new HitResultDisplayStatistic(resultTypes.First(), 0, null, columnName); + int count = 0; + int? maxCount = null; if (availableStatistics.Contains(columnName)) { + maxCount = 0; foreach (var s in availableStatistics[columnName]) - stat = s; + { + count += s.Count; + maxCount += s.MaxCount; + } } - content.Add(new StatisticText(stat.Count, stat.MaxCount, @"N0") { Colour = stat.Count == 0 ? Color4.Gray : Color4.White }); + content.Add(new StatisticText(count, maxCount, @"N0") { Colour = count == 0 ? Color4.Gray : Color4.White }); } if (showPerformancePoints) From a007a81fe8518215ee5fd40dbfab56a3696cd2eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Brandst=C3=B6tter?= Date: Mon, 14 Oct 2024 16:55:07 +0200 Subject: [PATCH 558/924] Only keep track of the names of hit results to display in a `ScoreTable` --- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 40055bbda8..c70c41feed 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -57,11 +57,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } /// - /// The statistics that appear in the table, grouped by their display name. If multiple HitResults have the same + /// The names of the statistics that appear in the table. If multiple HitResults have the same /// DisplayName (for example, "slider end" is the name for both and /// in osu!) the name will only be listed once. /// - private readonly List<(LocalisableString displayName, IEnumerable results)> statisticResults = new List<(LocalisableString displayName, IEnumerable results)>(); + private readonly List statisticResultNames = new List(); private bool showPerformancePoints; @@ -73,7 +73,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores return; showPerformancePoints = showPerformanceColumn; - statisticResults.Clear(); + statisticResultNames.Clear(); for (int i = 0; i < scores.Count; i++) backgroundFlow.Add(new ScoreTableRowBackground(i, scores[i], row_height)); @@ -117,7 +117,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores continue; columns.Add(new TableColumn(resultGroup.Key, Anchor.CentreLeft, new Dimension(minSize: 35, maxSize: 60))); - statisticResults.Add((resultGroup.Key, resultGroup.Select(r => r.result))); + statisticResultNames.Add(resultGroup.Key); } if (showPerformancePoints) @@ -168,7 +168,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores var availableStatistics = score.GetStatisticsForDisplay().ToLookup(tuple => tuple.DisplayName); - foreach (var (columnName, resultTypes) in statisticResults) + foreach (var columnName in statisticResultNames) { int count = 0; int? maxCount = null; @@ -176,6 +176,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (availableStatistics.Contains(columnName)) { maxCount = 0; + foreach (var s in availableStatistics[columnName]) { count += s.Count; From e151c0fab15426d5c1cbab388616f2608e61f370 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Mon, 14 Oct 2024 15:11:35 -0400 Subject: [PATCH 559/924] Fix coding mistake --- .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index b94240e000..aa7072007d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -330,7 +330,8 @@ namespace osu.Game.Screens.Edit.Compose.Components ensurePlacementCreated(); // updates the placement with the latest editor clock time. - updatePlacementTimeAndPosition(); + if (CurrentPlacement != null) + updatePlacementTimeAndPosition(); } protected override bool OnMouseMove(MouseMoveEvent e) From 14c6cfc3651173ff95eabef33f241a0a35e983ff Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 13 Oct 2024 11:03:09 -0400 Subject: [PATCH 560/924] Extend test cases to cover direct interaction (i.e. no touch-compose-area-first) --- .../Editor/TestSceneOsuEditor.cs | 91 ++++++++++++++++--- 1 file changed, 77 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditor.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditor.cs index befaf58029..6ad4330921 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditor.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditor.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Testing; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Components.RadioButtons; @@ -23,38 +24,57 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); [Test] - public void TestTouchInputAfterTouchingComposeArea() + public void TestTouchInputPlaceHitCircleDirectly() { AddStep("tap circle", () => tap(this.ChildrenOfType().Single(b => b.Button.Label == "HitCircle"))); - // this input is just for interacting with compose area - AddStep("tap playfield", () => tap(this.ChildrenOfType().Single())); - - AddStep("move current time", () => InputManager.Key(Key.Right)); - - AddStep("tap to place circle", () => tap(this.ChildrenOfType().Single().ToScreenSpace(new Vector2(10, 10)))); + AddStep("tap to place circle", () => tap(this.ChildrenOfType().Single())); AddAssert("circle placed correctly", () => { var circle = (HitCircle)EditorBeatmap.HitObjects.Single(h => h.StartTime == EditorClock.CurrentTimeAccurate); Assert.Multiple(() => { - Assert.That(circle.Position.X, Is.EqualTo(10f).Within(0.01f)); - Assert.That(circle.Position.Y, Is.EqualTo(10f).Within(0.01f)); + Assert.That(circle.Position.X, Is.EqualTo(256f).Within(0.01f)); + Assert.That(circle.Position.Y, Is.EqualTo(192f).Within(0.01f)); }); return true; }); + } - AddStep("tap slider", () => tap(this.ChildrenOfType().Single(b => b.Button.Label == "Slider"))); + [Test] + public void TestTouchInputPlaceCircleAfterTouchingComposeArea() + { + AddStep("tap circle", () => tap(this.ChildrenOfType().Single(b => b.Button.Label == "HitCircle"))); - // this input is just for interacting with compose area AddStep("tap playfield", () => tap(this.ChildrenOfType().Single())); + AddAssert("circle placed", () => EditorBeatmap.HitObjects.Single(h => h.StartTime == EditorClock.CurrentTimeAccurate) is HitCircle); - AddStep("move current time", () => InputManager.Key(Key.Right)); + AddStep("move forward", () => InputManager.Key(Key.Right)); + + AddStep("tap to place circle", () => tap(this.ChildrenOfType().Single())); + AddAssert("circle placed correctly", () => + { + var circle = (HitCircle)EditorBeatmap.HitObjects.Single(h => h.StartTime == EditorClock.CurrentTimeAccurate); + Assert.Multiple(() => + { + Assert.That(circle.Position.X, Is.EqualTo(256f).Within(0.01f)); + Assert.That(circle.Position.Y, Is.EqualTo(192f).Within(0.01f)); + }); + + return true; + }); + } + + [Test] + public void TestTouchInputPlaceSliderDirectly() + { + AddStep("tap slider", () => tap(this.ChildrenOfType().Single(b => b.Button.Label == "Slider"))); AddStep("hold to draw slider", () => InputManager.BeginTouch(new Touch(TouchSource.Touch1, this.ChildrenOfType().Single().ToScreenSpace(new Vector2(50, 20))))); AddStep("drag to draw", () => InputManager.MoveTouchTo(new Touch(TouchSource.Touch1, this.ChildrenOfType().Single().ToScreenSpace(new Vector2(200, 50))))); AddAssert("selection not initiated", () => this.ChildrenOfType().All(d => d.State == Visibility.Hidden)); + AddAssert("blueprint visible", () => this.ChildrenOfType().Single().Alpha > 0); AddStep("end", () => InputManager.EndTouch(new Touch(TouchSource.Touch1, InputManager.CurrentState.Touch.GetTouchPosition(TouchSource.Touch1)!.Value))); AddAssert("slider placed correctly", () => { @@ -75,12 +95,55 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor }); } - private void tap(Drawable drawable) => tap(drawable.ScreenSpaceDrawQuad.Centre); + [Test] + public void TestTouchInputPlaceSliderAfterTouchingComposeArea() + { + AddStep("tap slider", () => tap(this.ChildrenOfType().Single(b => b.Button.Label == "Slider"))); + + AddStep("tap playfield", () => tap(this.ChildrenOfType().Single())); + AddStep("tap and hold another spot", () => hold(this.ChildrenOfType().Single(), new Vector2(50, 0))); + AddUntilStep("wait for slider placement", () => EditorBeatmap.HitObjects.SingleOrDefault(h => h.StartTime == EditorClock.CurrentTimeAccurate) is Slider); + AddStep("end", () => InputManager.EndTouch(new Touch(TouchSource.Touch1, InputManager.CurrentState.Touch.GetTouchPosition(TouchSource.Touch1)!.Value))); + + AddStep("move forward", () => InputManager.Key(Key.Right)); + + AddStep("hold to draw slider", () => InputManager.BeginTouch(new Touch(TouchSource.Touch1, this.ChildrenOfType().Single().ToScreenSpace(new Vector2(50, 20))))); + AddStep("drag to draw", () => InputManager.MoveTouchTo(new Touch(TouchSource.Touch1, this.ChildrenOfType().Single().ToScreenSpace(new Vector2(200, 50))))); + AddAssert("selection not initiated", () => this.ChildrenOfType().All(d => d.State == Visibility.Hidden)); + AddAssert("blueprint visible", () => this.ChildrenOfType().Single().IsPresent); + AddStep("end", () => InputManager.EndTouch(new Touch(TouchSource.Touch1, InputManager.CurrentState.Touch.GetTouchPosition(TouchSource.Touch1)!.Value))); + AddAssert("slider placed correctly", () => + { + var slider = (Slider)EditorBeatmap.HitObjects.Single(h => h.StartTime == EditorClock.CurrentTimeAccurate); + Assert.Multiple(() => + { + Assert.That(slider.Position.X, Is.EqualTo(50f).Within(0.01f)); + Assert.That(slider.Position.Y, Is.EqualTo(20f).Within(0.01f)); + Assert.That(slider.Path.ControlPoints.Count, Is.EqualTo(2)); + Assert.That(slider.Path.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); + + // the final position may be slightly off from the mouse position when drawing, account for that. + Assert.That(slider.Path.ControlPoints[1].Position.X, Is.EqualTo(150).Within(5)); + Assert.That(slider.Path.ControlPoints[1].Position.Y, Is.EqualTo(30).Within(5)); + }); + + return true; + }); + } + + private void tap(Drawable drawable, Vector2 offset = default) => tap(drawable.ToScreenSpace(drawable.LayoutRectangle.Centre + offset)); private void tap(Vector2 position) { - InputManager.BeginTouch(new Touch(TouchSource.Touch1, position)); + hold(position); InputManager.EndTouch(new Touch(TouchSource.Touch1, position)); } + + private void hold(Drawable drawable, Vector2 offset = default) => hold(drawable.ToScreenSpace(drawable.LayoutRectangle.Centre + offset)); + + private void hold(Vector2 position) + { + InputManager.BeginTouch(new Touch(TouchSource.Touch1, position)); + } } } From 0fa1d22210899b2571315911e5827b1a19337002 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 13 Oct 2024 11:04:22 -0400 Subject: [PATCH 561/924] Add tests covering expected behaviour when going on/off compose area --- .../Editing/TestSceneHitObjectComposer.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs index f392841ac7..d7c92a64b1 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs @@ -19,6 +19,7 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.RadioButtons; using osu.Game.Screens.Edit.Components.TernaryButtons; @@ -82,6 +83,45 @@ namespace osu.Game.Tests.Visual.Editing }); } + [Test] + public void TestPlacementOutsideComposeScreen() + { + AddStep("clear all control points and hitobjects", () => + { + editorBeatmap.ControlPointInfo.Clear(); + editorBeatmap.Clear(); + }); + + AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint())); + AddStep("select circle", () => hitObjectComposer.ChildrenOfType().First(d => d.Button.Label == "HitCircle").TriggerClick()); + AddStep("move mouse to compose", () => InputManager.MoveMouseTo(hitObjectComposer.ChildrenOfType().Single())); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddAssert("circle placed", () => editorBeatmap.HitObjects.Count == 1); + + AddStep("move mouse outside compose", () => InputManager.MoveMouseTo(hitObjectComposer.ChildrenOfType().Single().ScreenSpaceDrawQuad.TopLeft - new Vector2(0f, 20f))); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddAssert("no circle placed", () => editorBeatmap.HitObjects.Count == 1); + } + + [Test] + public void TestDragSliderOutsideComposeScreen() + { + AddStep("clear all control points and hitobjects", () => + { + editorBeatmap.ControlPointInfo.Clear(); + editorBeatmap.Clear(); + }); + + AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint())); + AddStep("select slider", () => hitObjectComposer.ChildrenOfType().First(d => d.Button.Label == "Slider").TriggerClick()); + + AddStep("move mouse to compose", () => InputManager.MoveMouseTo(hitObjectComposer.ChildrenOfType().Single())); + AddStep("hold", () => InputManager.PressButton(MouseButton.Left)); + AddStep("move mouse outside compose", () => InputManager.MoveMouseTo(hitObjectComposer.ChildrenOfType().Single().ScreenSpaceDrawQuad.TopLeft - new Vector2(0f, 80f))); + AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); + AddAssert("slider placed", () => editorBeatmap.HitObjects.Count == 1); + } + [Test] public void TestPlacementOnlyWorksWithTiming() { From bd71da012ad82cf828997e0cf3899789599315ea Mon Sep 17 00:00:00 2001 From: jhk2601 Date: Tue, 15 Oct 2024 02:14:03 -0400 Subject: [PATCH 562/924] Refactors handling of scale to a function --- osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs | 13 +++++-------- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 17 ++++------------- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs index 8ed0e60445..a249aec35c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs @@ -78,22 +78,19 @@ namespace osu.Game.Rulesets.Osu.Mods ); } public void Update(Playfield playfield) - //terrible terrible handling on making sure cursor position stays accurate, will fix { bool beBaseSize = IsBreakTime.Value; - var osuPlayfield = (OsuPlayfield)playfield; + OsuPlayfield osuPlayfield = (OsuPlayfield)playfield; Debug.Assert(osuPlayfield.Cursor != null); - var realCursor = (OsuCursor)osuPlayfield.Cursor.ActiveCursor; - realCursor.isBloom = true; - float currentCombSize = (float)Interpolation.Lerp(realCursor.ComboSize, ComboBasedSize, Math.Clamp(osuPlayfield.Time.Elapsed / TRANSITION_DURATION, 0, 1)); - Console.WriteLine(ComboBasedSize + " " + currentCombSize); + OsuCursor realCursor = (OsuCursor)osuPlayfield.Cursor.ActiveCursor; + float currentCombSize = (float)Interpolation.Lerp(realCursor.CursorScale.Value, ComboBasedSize, Math.Clamp(osuPlayfield.Time.Elapsed / TRANSITION_DURATION, 0, 1)); if (beBaseSize) { - realCursor.ComboSize = 1; + realCursor.UpdateSize(1); } else { - realCursor.ComboSize = currentCombSize; + realCursor.UpdateSize(currentCombSize); } } diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index f3a6b0da3d..a3a4f8a547 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -17,7 +17,6 @@ using osu.Game.Screens.Play; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; -using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Osu.UI.Cursor { @@ -40,8 +39,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor public IBindable CursorScale => cursorScale; private readonly Bindable cursorScale = new BindableFloat(1); - public bool isBloom; - public float ComboSize; private Bindable userCursorScale = null!; private Bindable autoCursorScale = null!; @@ -77,16 +74,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { base.LoadComplete(); cursorScale.Value = CalculateCursorScale(); - isBloom = false; - } - protected override void Update() - //this should not exist will implement sane fix - { - base.Update(); - if (isBloom) - { - cursorScale.Value = ComboSize; - } } protected virtual Drawable CreateCursorContent() => cursorScaleContainer = new Container @@ -113,6 +100,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor return scale; } + public void UpdateSize(float size) + { + cursorScale.Value = size; + } protected override void SkinChanged(ISkinSource skin) { From 86a5760ef490bbb548e9539224814cd2eaebdcd3 Mon Sep 17 00:00:00 2001 From: jhk2601 Date: Tue, 15 Oct 2024 02:23:41 -0400 Subject: [PATCH 563/924] Remove handling for zero as MaxSizeComboCount, set lower limit as 5 --- osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs index a249aec35c..a3cd91b49e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs @@ -41,11 +41,11 @@ namespace osu.Game.Rulesets.Osu.Mods [SettingSource( "Max Size at Combo", "The combo count at which the cursor reaches its maximum size", - SettingControlType = typeof(SettingsSlider) + SettingControlType = typeof(SettingsSlider>) )] public BindableInt MaxSizeComboCount { get; } = new BindableInt(50) { - MinValue = 0, + MinValue = 5, MaxValue = 100, }; [SettingSource( @@ -93,14 +93,5 @@ namespace osu.Game.Rulesets.Osu.Mods realCursor.UpdateSize(currentCombSize); } } - - - - } - public partial class MaxSizeSlider : RoundedSliderBar - { - public override LocalisableString TooltipText => Current.Value == 0 ? "always at max size" : base.TooltipText; - } - } From 0cd352cdcbbf918c0199cb9991df5b7ac2550f09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 15 Oct 2024 12:39:08 +0200 Subject: [PATCH 564/924] Add failing test case for first tool switch failure scenario --- .../Editor/TestSceneToolSwitching.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/TestSceneToolSwitching.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneToolSwitching.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneToolSwitching.cs new file mode 100644 index 0000000000..7bb77e3078 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneToolSwitching.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 System.Linq; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Visual; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Tests.Editor +{ + public partial class TestSceneToolSwitching : EditorTestScene + { + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + + [Test] + public void TestSliderAnchorMoveOperationEndsOnSwitchingTool() + { + var initialPosition = Vector2.Zero; + + AddStep("store original anchor position", () => initialPosition = EditorBeatmap.HitObjects.OfType().First().Path.ControlPoints.ElementAt(1).Position); + AddStep("select first slider", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.OfType().First())); + AddStep("move to second anchor", () => InputManager.MoveMouseTo(this.ChildrenOfType>().ElementAt(1))); + AddStep("start dragging", () => InputManager.PressButton(MouseButton.Left)); + AddStep("drag away", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(0, -200))); + AddStep("switch tool", () => InputManager.PressButton(MouseButton.Button1)); + AddStep("undo", () => Editor.Undo()); + AddAssert("anchor back at original position", + () => EditorBeatmap.HitObjects.OfType().First().Path.ControlPoints.ElementAt(1).Position, + () => Is.EqualTo(initialPosition)); + } + } +} From 2d0e98c951cc514451a759c0263882eab185cbe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 15 Oct 2024 12:45:02 +0200 Subject: [PATCH 565/924] Add failing test case for second tool switching failure case Not exact (doesn't reproduce the crash), but will do. --- .../Editor/TestSceneToolSwitching.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneToolSwitching.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneToolSwitching.cs index 7bb77e3078..d5ab349a16 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneToolSwitching.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneToolSwitching.cs @@ -32,5 +32,24 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor () => EditorBeatmap.HitObjects.OfType().First().Path.ControlPoints.ElementAt(1).Position, () => Is.EqualTo(initialPosition)); } + + [Test] + public void TestSliderAnchorCreationOperationEndsOnSwitchingTool() + { + AddStep("select first slider", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.OfType().First())); + AddStep("move to second anchor", () => InputManager.MoveMouseTo(this.ChildrenOfType>().ElementAt(1), new Vector2(-50, 0))); + AddStep("quick-create anchor", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.PressButton(MouseButton.Left); + InputManager.ReleaseKey(Key.ControlLeft); + }); + AddStep("drag away", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(0, -200))); + AddStep("switch tool", () => InputManager.PressKey(Key.Number3)); + AddStep("drag away further", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(0, -200))); + AddStep("select first slider", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.OfType().First())); + AddStep("undo", () => Editor.Undo()); + AddAssert("slider has three anchors again", () => EditorBeatmap.HitObjects.OfType().First().Path.ControlPoints, () => Has.Count.EqualTo(3)); + } } } From 1a7acbac33967b2eebb056d8c1189a0c5b777997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 15 Oct 2024 12:49:24 +0200 Subject: [PATCH 566/924] Terminate existing anchor drag operations when object is deselected --- .../Sliders/Components/PathControlPointVisualiser.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 70ccbdfdc4..f114516300 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -333,6 +333,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components base.Dispose(isDisposing); foreach (var p in Pieces) p.ControlPoint.Changed -= controlPointChanged; + + if (draggedControlPointIndex >= 0) + DragEnded(); } private void selectionRequested(PathControlPointPiece piece, MouseButtonEvent e) @@ -392,7 +395,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private Vector2[] dragStartPositions; private PathType?[] dragPathTypes; - private int draggedControlPointIndex; + private int draggedControlPointIndex = -1; private HashSet selectedControlPoints; private List curveTypeItems; @@ -473,7 +476,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components EnsureValidPathTypes(); } - public void DragEnded() => changeHandler?.EndChange(); + public void DragEnded() + { + changeHandler?.EndChange(); + draggedControlPointIndex = -1; + } #endregion From 232301f27d35b2de96eabfab0f74ed1902694978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 15 Oct 2024 12:54:08 +0200 Subject: [PATCH 567/924] Terminate existing anchor create operation when object is deselected --- .../Sliders/SliderSelectionBlueprint.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index c72f547565..34de81f1ba 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -178,6 +178,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { base.OnDeselected(); + if (placementControlPoint != null) + endControlPointPlacement(); + updateVisualDefinition(); BodyPiece.RecyclePath(); } @@ -377,13 +380,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected override void OnMouseUp(MouseUpEvent e) { if (placementControlPoint != null) - { - if (IsDragged) - ControlPointVisualiser?.DragEnded(); + endControlPointPlacement(); + } - placementControlPoint = null; - changeHandler?.EndChange(); - } + private void endControlPointPlacement() + { + if (IsDragged) + ControlPointVisualiser?.DragEnded(); + + placementControlPoint = null; + changeHandler?.EndChange(); } protected override bool OnKeyDown(KeyDownEvent e) From 634f20e8de5577622d6fe9946ad737d2ec0401ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 15 Oct 2024 14:11:33 +0200 Subject: [PATCH 568/924] Ensure at least scale popover axis toggle is active at any time As in, toggling off an axis if it is the only one enabled will enable the other one in turn. Co-authored-by: Dean Herbert --- .../Edit/PreciseScalePopover.cs | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index 25515a90d5..71a7793fc9 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -136,8 +136,26 @@ namespace osu.Game.Rulesets.Osu.Edit }); scaleInput.Current.BindValueChanged(scale => scaleInfo.Value = scaleInfo.Value with { Scale = scale.NewValue }); - xCheckBox.Current.BindValueChanged(x => setAxis(x.NewValue, yCheckBox.Current.Value)); - yCheckBox.Current.BindValueChanged(y => setAxis(xCheckBox.Current.Value, y.NewValue)); + xCheckBox.Current.BindValueChanged(_ => + { + if (!xCheckBox.Current.Value && !yCheckBox.Current.Value) + { + yCheckBox.Current.Value = true; + return; + } + + updateAxes(); + }); + yCheckBox.Current.BindValueChanged(_ => + { + if (!xCheckBox.Current.Value && !yCheckBox.Current.Value) + { + xCheckBox.Current.Value = true; + return; + } + + updateAxes(); + }); selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleX.Value || scaleHandler.CanScaleY.Value); playfieldCentreButton.Selected.Disabled = scaleHandler.IsScalingSlider.Value && !selectionCentreButton.Selected.Disabled; @@ -152,6 +170,12 @@ namespace osu.Game.Rulesets.Osu.Edit }); } + private void updateAxes() + { + scaleInfo.Value = scaleInfo.Value with { XAxis = xCheckBox.Current.Value, YAxis = yCheckBox.Current.Value }; + updateMaxScale(); + } + private void updateAxisCheckBoxesEnabled() { if (scaleInfo.Value.Origin != ScaleOrigin.SelectionCentre) @@ -234,12 +258,6 @@ namespace osu.Game.Rulesets.Osu.Edit private float getRotation(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.GridCentre ? gridToolbox.GridLinesRotation.Value : 0; - private void setAxis(bool x, bool y) - { - scaleInfo.Value = scaleInfo.Value with { XAxis = x, YAxis = y }; - updateMaxScale(); - } - protected override void PopIn() { base.PopIn(); From f6566f91571addd8e52ea7d8b265859822eea96b Mon Sep 17 00:00:00 2001 From: jhk2601 Date: Tue, 15 Oct 2024 15:37:05 -0400 Subject: [PATCH 569/924] Code quality touch-ups --- osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs | 16 +++++++++------- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 1 + 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs index a3cd91b49e..eef9f0590d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. - - using System; using System.Diagnostics; using osu.Framework.Bindables; @@ -26,18 +24,16 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Name => "Bloom"; public override string Acronym => "BM"; public override ModType Type => ModType.Fun; - public override LocalisableString Description => "The cursor blooms into a.. larger cursor!"; + public override LocalisableString Description => "The cursor blooms into.. a larger cursor!"; public override double ScoreMultiplier => 1; protected const float MIN_SIZE = 1; protected const float TRANSITION_DURATION = 100; public override Type[] IncompatibleMods => new[] { typeof(OsuModFlashlight), typeof(OsuModNoScope), typeof(OsuModObjectScaleTween), typeof(OsuModTouchDevice), typeof(OsuModAutopilot) }; - protected readonly BindableNumber CurrentCombo = new BindableInt(); protected readonly IBindable IsBreakTime = new Bindable(); protected float ComboBasedSize; - [SettingSource( "Max Size at Combo", "The combo count at which the cursor reaches its maximum size", @@ -48,6 +44,7 @@ namespace osu.Game.Rulesets.Osu.Mods MinValue = 5, MaxValue = 100, }; + [SettingSource( "Final Size Multiplier", "The multiplier applied to cursor size when combo reaches maximum", @@ -59,7 +56,9 @@ namespace osu.Game.Rulesets.Osu.Mods MaxValue = 15f, Precision = 0.5f, }; + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; + public void ApplyToPlayer(Player player) { IsBreakTime.BindTo(player.IsBreakTime); @@ -74,20 +73,23 @@ namespace osu.Game.Rulesets.Osu.Mods { ComboBasedSize = Math.Min(MaxMulti.Value, 10 * ((float)combo.NewValue / MaxSizeComboCount.Value)); ComboBasedSize = Math.Max(ComboBasedSize, MIN_SIZE); - }, true - ); + }, true); } + public void Update(Playfield playfield) { bool beBaseSize = IsBreakTime.Value; OsuPlayfield osuPlayfield = (OsuPlayfield)playfield; Debug.Assert(osuPlayfield.Cursor != null); + OsuCursor realCursor = (OsuCursor)osuPlayfield.Cursor.ActiveCursor; float currentCombSize = (float)Interpolation.Lerp(realCursor.CursorScale.Value, ComboBasedSize, Math.Clamp(osuPlayfield.Time.Elapsed / TRANSITION_DURATION, 0, 1)); + if (beBaseSize) { realCursor.UpdateSize(1); } + else { realCursor.UpdateSize(currentCombSize); diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index a3a4f8a547..545cd15d32 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -100,6 +100,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor return scale; } + public void UpdateSize(float size) { cursorScale.Value = size; From b75437ee1303dfef70cd548b15a9fcc20eea8032 Mon Sep 17 00:00:00 2001 From: Darius Wattimena Date: Tue, 15 Oct 2024 21:13:04 +0200 Subject: [PATCH 570/924] Fix an issue where changing the CircleSize wouldn't adjust the catcher size and represent hyperdashes incorrectly --- .../Edit/CatchEditorPlayfield.cs | 6 ++++- .../Edit/DrawableCatchEditorRuleset.cs | 23 +++++++++++++++++++ osu.Game.Rulesets.Catch/UI/Catcher.cs | 18 +++++++++++---- 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfield.cs b/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfield.cs index c9481c2757..78f5c3e9ed 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfield.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfield.cs @@ -8,7 +8,6 @@ namespace osu.Game.Rulesets.Catch.Edit { public partial class CatchEditorPlayfield : CatchPlayfield { - // TODO fixme: the size of the catcher is not changed when circle size is changed in setup screen. public CatchEditorPlayfield(IBeatmapDifficultyInfo difficulty) : base(difficulty) { @@ -23,5 +22,10 @@ namespace osu.Game.Rulesets.Catch.Edit // TODO: disable hit lighting as well } + + public void ApplyCircleSizeToCatcher(IBeatmapDifficultyInfo difficulty) + { + Catcher.SetScaleAndCatchWidth(difficulty); + } } } diff --git a/osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs b/osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs index 7ad2106ab9..cd6d490a7f 100644 --- a/osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs +++ b/osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs @@ -2,16 +2,22 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Edit; namespace osu.Game.Rulesets.Catch.Edit { public partial class DrawableCatchEditorRuleset : DrawableCatchRuleset { + [Resolved] + private EditorBeatmap editorBeatmap { get; set; } = null!; + public readonly BindableDouble TimeRangeMultiplier = new BindableDouble(1); public DrawableCatchEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList? mods = null) @@ -28,6 +34,23 @@ namespace osu.Game.Rulesets.Catch.Edit TimeRange.Value = gamePlayTimeRange * TimeRangeMultiplier.Value * playfieldStretch; } + protected override void LoadComplete() + { + base.LoadComplete(); + + editorBeatmap.BeatmapReprocessed += onBeatmapReprocessed; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (editorBeatmap.IsNotNull()) + editorBeatmap.BeatmapReprocessed -= onBeatmapReprocessed; + } + + private void onBeatmapReprocessed() => (Playfield as CatchEditorPlayfield)?.ApplyCircleSizeToCatcher(editorBeatmap.Difficulty); + protected override Playfield CreatePlayfield() => new CatchEditorPlayfield(Beatmap.Difficulty); public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new CatchEditorPlayfieldAdjustmentContainer(); diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 6a1b251d60..3a0863473a 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Catch.UI /// /// Width of the area that can be used to attempt catches during gameplay. /// - public readonly float CatchWidth; + public float CatchWidth; private readonly SkinnableCatcher body; @@ -142,10 +142,7 @@ namespace osu.Game.Rulesets.Catch.UI Size = new Vector2(BASE_SIZE); - if (difficulty != null) - Scale = calculateScale(difficulty); - - CatchWidth = CalculateCatchWidth(Scale); + SetScaleAndCatchWidth(difficulty); InternalChildren = new Drawable[] { @@ -312,6 +309,17 @@ namespace osu.Game.Rulesets.Catch.UI } } + /// + /// Set the scale and catch width. + /// + public void SetScaleAndCatchWidth(IBeatmapDifficultyInfo? difficulty) + { + if (difficulty != null) + Scale = calculateScale(difficulty); + + CatchWidth = CalculateCatchWidth(Scale); + } + /// /// Drop any fruit off the plate. /// From bf970afddb92d45c30749630e52d6f42dd1ba2b7 Mon Sep 17 00:00:00 2001 From: jhk2601 Date: Tue, 15 Oct 2024 16:27:05 -0400 Subject: [PATCH 571/924] Formatting --- osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs index eef9f0590d..a132e1d4c8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs @@ -89,7 +89,6 @@ namespace osu.Game.Rulesets.Osu.Mods { realCursor.UpdateSize(1); } - else { realCursor.UpdateSize(currentCombSize); From f8a13b0beb6c47a4e354cd2732e51b7171a60c33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 16 Oct 2024 08:17:22 +0200 Subject: [PATCH 572/924] Fix migration not checking combination properly --- osu.Game/Database/RealmAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 2f25791964..a437b3313e 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -1203,7 +1203,7 @@ namespace osu.Game.Database var keyBindings = migration.NewRealm.All(); var toggleFpsBind = keyBindings.FirstOrDefault(bind => bind.ActionInt == (int)GlobalAction.ToggleFPSDisplay); - if (toggleFpsBind != null && toggleFpsBind.KeyCombination.Keys.SequenceEqual(new[] { InputKey.Control, InputKey.Shift, InputKey.F })) + if (toggleFpsBind != null && toggleFpsBind.KeyCombination.Keys.SequenceEqual(new[] { InputKey.Shift, InputKey.Control, InputKey.F })) migration.NewRealm.Remove(toggleFpsBind); break; From 882df6b828810d3551f4432816754412e1c71153 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 16 Oct 2024 09:59:05 +0200 Subject: [PATCH 573/924] Remove unused field Not sure why inspectcode is quiet about this? --- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 4d1e06c857..768a764ad1 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -25,8 +25,6 @@ namespace osu.Game.Rulesets.Osu.Edit { public partial class OsuGridToolboxGroup : EditorToolboxGroup, IKeyBindingHandler { - private static readonly PositionSnapGridType[] grid_types = Enum.GetValues(typeof(PositionSnapGridType)).Cast().ToArray(); - [Resolved] private EditorBeatmap editorBeatmap { get; set; } = null!; From b29a17364dce4409c4d2293b8f06e379f43b054b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Oct 2024 20:16:10 +0900 Subject: [PATCH 574/924] Remove hold-to-confirm delay when pausing using keyboard shortcuts --- osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 89d083eca9..806985e19d 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -299,7 +299,7 @@ namespace osu.Game.Screens.Play.HUD { case GlobalAction.Back: if (!pendingAnimation) - BeginConfirm(); + Confirm(); return true; case GlobalAction.PauseGameplay: @@ -307,7 +307,7 @@ namespace osu.Game.Screens.Play.HUD if (ReplayLoaded.Value) return false; if (!pendingAnimation) - BeginConfirm(); + Confirm(); return true; } From c5e406a007651762df9aba228f7e857bb4b9a528 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 16 Oct 2024 21:18:48 +0200 Subject: [PATCH 575/924] add keyboard step matching osu stable --- osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs | 1 + osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs index 352debf500..a87f0286cf 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs @@ -55,6 +55,7 @@ namespace osu.Game.Rulesets.Osu.Edit MaxValue = 360, Precision = 1 }, + KeyboardStep = 1f, Instantaneous = true }, rotationOrigin = new EditorRadioButtonCollection diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index b2c48ae326..eafee9db14 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -70,6 +70,7 @@ namespace osu.Game.Rulesets.Osu.Edit Value = 1, Default = 1, }, + KeyboardStep = 0.01f, Instantaneous = true }, scaleOrigin = new EditorRadioButtonCollection From 66bf6ed6b4d10ed497dbd0abb2bdf0552b2abd74 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 16 Oct 2024 21:53:56 +0200 Subject: [PATCH 576/924] Close scale/rotate popover on Enter keypress This matches the expectation from stable where the popover also closes when you press enter. The side-effect is that spacebar also causes it to close, but luckily you don't need spacebar in the popover. --- .../Edit/PreciseRotationPopover.cs | 14 ++++++++++++++ osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs index a87f0286cf..b18452768c 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs @@ -5,10 +5,13 @@ using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Input.Bindings; using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit.Components.RadioButtons; using osu.Game.Screens.Edit.Compose.Components; @@ -127,6 +130,17 @@ namespace osu.Game.Rulesets.Osu.Edit if (IsLoaded) rotationHandler.Commit(); } + + public override bool OnPressed(KeyBindingPressEvent e) + { + if (e.Action == GlobalAction.Select && !e.Repeat) + { + this.HidePopover(); + return true; + } + + return base.OnPressed(e); + } } public enum RotationOrigin diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index eafee9db14..68f5b268f8 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -5,11 +5,14 @@ using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Input.Bindings; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; @@ -283,6 +286,17 @@ namespace osu.Game.Rulesets.Osu.Edit if (IsLoaded) scaleHandler.Commit(); } + + public override bool OnPressed(KeyBindingPressEvent e) + { + if (e.Action == GlobalAction.Select && !e.Repeat) + { + this.HidePopover(); + return true; + } + + return base.OnPressed(e); + } } public enum ScaleOrigin From 9e1af18ac6aa7bec1021f91cb8e3954c2e304df5 Mon Sep 17 00:00:00 2001 From: jhk2601 Date: Wed, 16 Oct 2024 20:37:59 -0400 Subject: [PATCH 577/924] Fixed improper handling of size calculation when the max multiplier was changed in mod settings --- osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs index a132e1d4c8..e1b05fa84b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs @@ -66,13 +66,10 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { - if (MaxSizeComboCount.Value == 0) return; - CurrentCombo.BindTo(scoreProcessor.Combo); CurrentCombo.BindValueChanged(combo => { - ComboBasedSize = Math.Min(MaxMulti.Value, 10 * ((float)combo.NewValue / MaxSizeComboCount.Value)); - ComboBasedSize = Math.Max(ComboBasedSize, MIN_SIZE); + ComboBasedSize = Math.Clamp(MaxMulti.Value * ((float)combo.NewValue / MaxSizeComboCount.Value), MIN_SIZE, MaxMulti.Value); }, true); } From 135b85a55a21d08bd713ffa291ebd723459771c9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Oct 2024 13:43:53 +0900 Subject: [PATCH 578/924] Improve diffcalc workflow with explicit wait + logs --- .github/workflows/diffcalc.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml index 3b77a463c1..805523de8b 100644 --- a/.github/workflows/diffcalc.yml +++ b/.github/workflows/diffcalc.yml @@ -341,9 +341,12 @@ jobs: sed -i 's/^GH_TOKEN=.*$/GH_TOKEN=${{ github.token }}/' "${{ needs.directory.outputs.GENERATOR_ENV }}" cd "${{ needs.directory.outputs.GENERATOR_DIR }}" - docker-compose up --build generator - link=$(docker-compose logs generator -n 10 | grep 'http' | sed -E 's/^.*(http.*)$/\1/') + docker compose up --build --detach + docker compose logs --follow & + docker compose wait generator + + link=$(docker compose logs generator --tail 10 | grep 'http' | sed -E 's/^.*(http.*)$/\1/') target=$(cat "${{ needs.directory.outputs.GENERATOR_ENV }}" | grep -E '^OSU_B=' | cut -d '=' -f2-) echo "TARGET=${target}" >> "${GITHUB_OUTPUT}" @@ -353,7 +356,7 @@ jobs: if: ${{ always() }} run: | cd "${{ needs.directory.outputs.GENERATOR_DIR }}" - docker-compose down -v + docker compose down --volumes output-cli: name: Output info From 102f55a2133373cfdb0eb5dacb20eb0bcf710ca8 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Oct 2024 16:01:44 +0900 Subject: [PATCH 579/924] Refactor BeatmapAttributeText to compute values on the fly --- .../Components/BeatmapAttributeText.cs | 165 ++++++++++++------ 1 file changed, 111 insertions(+), 54 deletions(-) diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index 6e1d655cef..63ba6d1581 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -3,8 +3,6 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -35,25 +33,6 @@ namespace osu.Game.Skinning.Components [Resolved] private IBindable beatmap { get; set; } = null!; - private readonly Dictionary valueDictionary = new Dictionary(); - - private static readonly ImmutableDictionary label_dictionary = new Dictionary - { - [BeatmapAttribute.CircleSize] = BeatmapsetsStrings.ShowStatsCs, - [BeatmapAttribute.Accuracy] = BeatmapsetsStrings.ShowStatsAccuracy, - [BeatmapAttribute.HPDrain] = BeatmapsetsStrings.ShowStatsDrain, - [BeatmapAttribute.ApproachRate] = BeatmapsetsStrings.ShowStatsAr, - [BeatmapAttribute.StarRating] = BeatmapsetsStrings.ShowStatsStars, - [BeatmapAttribute.Title] = EditorSetupStrings.Title, - [BeatmapAttribute.Artist] = EditorSetupStrings.Artist, - [BeatmapAttribute.DifficultyName] = EditorSetupStrings.DifficultyHeader, - [BeatmapAttribute.Creator] = EditorSetupStrings.Creator, - [BeatmapAttribute.Source] = EditorSetupStrings.Source, - [BeatmapAttribute.Length] = ArtistStrings.TracklistLength.ToTitle(), - [BeatmapAttribute.RankedStatus] = BeatmapDiscussionsStrings.IndexFormBeatmapsetStatusDefault, - [BeatmapAttribute.BPM] = BeatmapsetsStrings.ShowStatsBpm, - }.ToImmutableDictionary(); - private readonly OsuSpriteText text; public BeatmapAttributeText() @@ -74,52 +53,130 @@ namespace osu.Game.Skinning.Components { base.LoadComplete(); - Attribute.BindValueChanged(_ => updateLabel()); - Template.BindValueChanged(_ => updateLabel()); - beatmap.BindValueChanged(b => - { - updateBeatmapContent(b.NewValue); - updateLabel(); - }, true); + Attribute.BindValueChanged(_ => updateText()); + Template.BindValueChanged(_ => updateText()); + beatmap.BindValueChanged(b => updateText()); + + updateText(); } - private void updateBeatmapContent(WorkingBeatmap workingBeatmap) - { - valueDictionary[BeatmapAttribute.Title] = new RomanisableString(workingBeatmap.BeatmapInfo.Metadata.TitleUnicode, workingBeatmap.BeatmapInfo.Metadata.Title); - valueDictionary[BeatmapAttribute.Artist] = new RomanisableString(workingBeatmap.BeatmapInfo.Metadata.ArtistUnicode, workingBeatmap.BeatmapInfo.Metadata.Artist); - valueDictionary[BeatmapAttribute.DifficultyName] = workingBeatmap.BeatmapInfo.DifficultyName; - valueDictionary[BeatmapAttribute.Creator] = workingBeatmap.BeatmapInfo.Metadata.Author.Username; - valueDictionary[BeatmapAttribute.Source] = workingBeatmap.BeatmapInfo.Metadata.Source; - valueDictionary[BeatmapAttribute.Length] = TimeSpan.FromMilliseconds(workingBeatmap.BeatmapInfo.Length).ToFormattedDuration(); - valueDictionary[BeatmapAttribute.RankedStatus] = workingBeatmap.BeatmapInfo.Status.GetLocalisableDescription(); - valueDictionary[BeatmapAttribute.BPM] = workingBeatmap.BeatmapInfo.BPM.ToLocalisableString(@"F2"); - valueDictionary[BeatmapAttribute.CircleSize] = ((double)workingBeatmap.BeatmapInfo.Difficulty.CircleSize).ToLocalisableString(@"F2"); - valueDictionary[BeatmapAttribute.HPDrain] = ((double)workingBeatmap.BeatmapInfo.Difficulty.DrainRate).ToLocalisableString(@"F2"); - valueDictionary[BeatmapAttribute.Accuracy] = ((double)workingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty).ToLocalisableString(@"F2"); - valueDictionary[BeatmapAttribute.ApproachRate] = ((double)workingBeatmap.BeatmapInfo.Difficulty.ApproachRate).ToLocalisableString(@"F2"); - valueDictionary[BeatmapAttribute.StarRating] = workingBeatmap.BeatmapInfo.StarRating.ToLocalisableString(@"F2"); - } - - private void updateLabel() + private void updateText() { string numberedTemplate = Template.Value .Replace("{", "{{") .Replace("}", "}}") .Replace(@"{{Label}}", "{0}") - .Replace(@"{{Value}}", $"{{{1 + (int)Attribute.Value}}}"); + .Replace(@"{{Value}}", "{1}"); - object?[] args = valueDictionary.OrderBy(pair => pair.Key) - .Select(pair => pair.Value) - .Prepend(label_dictionary[Attribute.Value]) - .Cast() - .ToArray(); + List values = new List + { + getLabelString(Attribute.Value), + getValueString(Attribute.Value) + }; foreach (var type in Enum.GetValues()) { - numberedTemplate = numberedTemplate.Replace($"{{{{{type}}}}}", $"{{{1 + (int)type}}}"); + numberedTemplate = numberedTemplate.Replace($"{{{{{type}}}}}", $"{{{values.Count}}}"); + values.Add(getValueString(type)); } - text.Text = LocalisableString.Format(numberedTemplate, args); + text.Text = LocalisableString.Format(numberedTemplate, values.ToArray()); + } + + private LocalisableString getLabelString(BeatmapAttribute attribute) + { + switch (attribute) + { + case BeatmapAttribute.CircleSize: + return BeatmapsetsStrings.ShowStatsCs; + + case BeatmapAttribute.Accuracy: + return BeatmapsetsStrings.ShowStatsAccuracy; + + case BeatmapAttribute.HPDrain: + return BeatmapsetsStrings.ShowStatsDrain; + + case BeatmapAttribute.ApproachRate: + return BeatmapsetsStrings.ShowStatsAr; + + case BeatmapAttribute.StarRating: + return BeatmapsetsStrings.ShowStatsStars; + + case BeatmapAttribute.Title: + return EditorSetupStrings.Title; + + case BeatmapAttribute.Artist: + return EditorSetupStrings.Artist; + + case BeatmapAttribute.DifficultyName: + return EditorSetupStrings.DifficultyHeader; + + case BeatmapAttribute.Creator: + return EditorSetupStrings.Creator; + + case BeatmapAttribute.Source: + return EditorSetupStrings.Source; + + case BeatmapAttribute.Length: + return ArtistStrings.TracklistLength.ToTitle(); + + case BeatmapAttribute.RankedStatus: + return BeatmapDiscussionsStrings.IndexFormBeatmapsetStatusDefault; + + case BeatmapAttribute.BPM: + return BeatmapsetsStrings.ShowStatsBpm; + + default: + throw new ArgumentOutOfRangeException(); + } + } + + private LocalisableString getValueString(BeatmapAttribute attribute) + { + switch (attribute) + { + case BeatmapAttribute.Title: + return new RomanisableString(beatmap.Value.BeatmapInfo.Metadata.TitleUnicode, beatmap.Value.BeatmapInfo.Metadata.Title); + + case BeatmapAttribute.Artist: + return new RomanisableString(beatmap.Value.BeatmapInfo.Metadata.ArtistUnicode, beatmap.Value.BeatmapInfo.Metadata.Artist); + + case BeatmapAttribute.DifficultyName: + return beatmap.Value.BeatmapInfo.DifficultyName; + + case BeatmapAttribute.Creator: + return beatmap.Value.BeatmapInfo.Metadata.Author.Username; + + case BeatmapAttribute.Source: + return beatmap.Value.BeatmapInfo.Metadata.Source; + + case BeatmapAttribute.Length: + return TimeSpan.FromMilliseconds(beatmap.Value.BeatmapInfo.Length).ToFormattedDuration(); + + case BeatmapAttribute.RankedStatus: + return beatmap.Value.BeatmapInfo.Status.GetLocalisableDescription(); + + case BeatmapAttribute.BPM: + return beatmap.Value.BeatmapInfo.BPM.ToLocalisableString(@"F2"); + + case BeatmapAttribute.CircleSize: + return ((double)beatmap.Value.BeatmapInfo.Difficulty.CircleSize).ToLocalisableString(@"F2"); + + case BeatmapAttribute.HPDrain: + return ((double)beatmap.Value.BeatmapInfo.Difficulty.DrainRate).ToLocalisableString(@"F2"); + + case BeatmapAttribute.Accuracy: + return ((double)beatmap.Value.BeatmapInfo.Difficulty.OverallDifficulty).ToLocalisableString(@"F2"); + + case BeatmapAttribute.ApproachRate: + return ((double)beatmap.Value.BeatmapInfo.Difficulty.ApproachRate).ToLocalisableString(@"F2"); + + case BeatmapAttribute.StarRating: + return beatmap.Value.BeatmapInfo.StarRating.ToLocalisableString(@"F2"); + + default: + throw new ArgumentOutOfRangeException(); + } } protected override void SetFont(FontUsage font) => text.Font = font.With(size: 40); From 1536a9886c18ec25f974d9d237b1887cd763cbaf Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Oct 2024 16:57:00 +0900 Subject: [PATCH 580/924] Fix argument order in diffcalc workflow --- .github/workflows/diffcalc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml index 805523de8b..c2eeff20df 100644 --- a/.github/workflows/diffcalc.yml +++ b/.github/workflows/diffcalc.yml @@ -346,7 +346,7 @@ jobs: docker compose logs --follow & docker compose wait generator - link=$(docker compose logs generator --tail 10 | grep 'http' | sed -E 's/^.*(http.*)$/\1/') + link=$(docker compose logs --tail 10 generator | grep 'http' | sed -E 's/^.*(http.*)$/\1/') target=$(cat "${{ needs.directory.outputs.GENERATOR_ENV }}" | grep -E '^OSU_B=' | cut -d '=' -f2-) echo "TARGET=${target}" >> "${GITHUB_OUTPUT}" From 1e2c1323ff861386a3272f7db3ee1fefc45da1fb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Oct 2024 17:05:49 +0900 Subject: [PATCH 581/924] Show star difficulty attribute inclusive of mods --- .../Components/BeatmapAttributeText.cs | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index 63ba6d1581..d6246b4bcf 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Threading; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -33,7 +34,12 @@ namespace osu.Game.Skinning.Components [Resolved] private IBindable beatmap { get; set; } = null!; + [Resolved] + private BeatmapDifficultyCache difficultyCache { get; set; } = null!; + private readonly OsuSpriteText text; + private IBindable? difficultyBindable; + private CancellationTokenSource? difficultyCancellationSource; public BeatmapAttributeText() { @@ -55,7 +61,18 @@ namespace osu.Game.Skinning.Components Attribute.BindValueChanged(_ => updateText()); Template.BindValueChanged(_ => updateText()); - beatmap.BindValueChanged(b => updateText()); + + beatmap.BindValueChanged(b => + { + difficultyCancellationSource?.Cancel(); + difficultyCancellationSource = new CancellationTokenSource(); + + difficultyBindable?.UnbindAll(); + difficultyBindable = difficultyCache.GetBindableDifficulty(b.NewValue.BeatmapInfo, difficultyCancellationSource.Token); + difficultyBindable.BindValueChanged(_ => updateText()); + + updateText(); + }, true); updateText(); } @@ -172,7 +189,9 @@ namespace osu.Game.Skinning.Components return ((double)beatmap.Value.BeatmapInfo.Difficulty.ApproachRate).ToLocalisableString(@"F2"); case BeatmapAttribute.StarRating: - return beatmap.Value.BeatmapInfo.StarRating.ToLocalisableString(@"F2"); + return difficultyBindable?.Value is StarDifficulty starDifficulty + ? starDifficulty.Stars.ToLocalisableString(@"F2") + : @"..."; default: throw new ArgumentOutOfRangeException(); @@ -182,6 +201,15 @@ namespace osu.Game.Skinning.Components protected override void SetFont(FontUsage font) => text.Font = font.With(size: 40); protected override void SetTextColour(Colour4 textColour) => text.Colour = textColour; + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + difficultyCancellationSource?.Cancel(); + difficultyCancellationSource?.Dispose(); + difficultyCancellationSource = null; + } } // WARNING: DO NOT ADD ANY VALUES TO THIS ENUM ANYWHERE ELSE THAN AT THE END. From 400142545df498fa57f375a3b67e5150ee9f6c07 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Oct 2024 17:26:02 +0900 Subject: [PATCH 582/924] Show difficulty values inclusive of mods --- .../Components/BeatmapAttributeText.cs | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index d6246b4bcf..ed44d4b44c 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -19,6 +20,9 @@ using osu.Game.Graphics.Sprites; using osu.Game.Localisation; using osu.Game.Localisation.SkinComponents; using osu.Game.Resources.Localisation.Web; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Utils; namespace osu.Game.Skinning.Components { @@ -34,6 +38,12 @@ namespace osu.Game.Skinning.Components [Resolved] private IBindable beatmap { get; set; } = null!; + [Resolved] + private IBindable> mods { get; set; } = null!; + + [Resolved] + private IBindable ruleset { get; set; } = null!; + [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } = null!; @@ -74,6 +84,9 @@ namespace osu.Game.Skinning.Components updateText(); }, true); + mods.BindValueChanged(_ => updateText()); + ruleset.BindValueChanged(_ => updateText()); + updateText(); } @@ -177,16 +190,16 @@ namespace osu.Game.Skinning.Components return beatmap.Value.BeatmapInfo.BPM.ToLocalisableString(@"F2"); case BeatmapAttribute.CircleSize: - return ((double)beatmap.Value.BeatmapInfo.Difficulty.CircleSize).ToLocalisableString(@"F2"); + return computeDifficulty().CircleSize.ToLocalisableString(@"F2"); case BeatmapAttribute.HPDrain: - return ((double)beatmap.Value.BeatmapInfo.Difficulty.DrainRate).ToLocalisableString(@"F2"); + return computeDifficulty().DrainRate.ToLocalisableString(@"F2"); case BeatmapAttribute.Accuracy: - return ((double)beatmap.Value.BeatmapInfo.Difficulty.OverallDifficulty).ToLocalisableString(@"F2"); + return computeDifficulty().OverallDifficulty.ToLocalisableString(@"F2"); case BeatmapAttribute.ApproachRate: - return ((double)beatmap.Value.BeatmapInfo.Difficulty.ApproachRate).ToLocalisableString(@"F2"); + return computeDifficulty().ApproachRate.ToLocalisableString(@"F2"); case BeatmapAttribute.StarRating: return difficultyBindable?.Value is StarDifficulty starDifficulty @@ -196,6 +209,22 @@ namespace osu.Game.Skinning.Components default: throw new ArgumentOutOfRangeException(); } + + BeatmapDifficulty computeDifficulty() + { + BeatmapDifficulty difficulty = new BeatmapDifficulty(beatmap.Value.BeatmapInfo.Difficulty); + + foreach (var mod in mods.Value.OfType()) + mod.ApplyToDifficulty(difficulty); + + if (ruleset.Value is RulesetInfo rulesetInfo) + { + double rate = ModUtils.CalculateRateWithMods(mods.Value); + difficulty = rulesetInfo.CreateInstance().GetRateAdjustedDisplayDifficulty(difficulty, rate); + } + + return difficulty; + } } protected override void SetFont(FontUsage font) => text.Font = font.With(size: 40); From 9ea15a39619068deed002b50d794b9eacaef8b64 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Oct 2024 17:32:32 +0900 Subject: [PATCH 583/924] Show bpm and length inclusive of mods --- osu.Game/Skinning/Components/BeatmapAttributeText.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index ed44d4b44c..6cfed8b945 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -181,13 +181,13 @@ namespace osu.Game.Skinning.Components return beatmap.Value.BeatmapInfo.Metadata.Source; case BeatmapAttribute.Length: - return TimeSpan.FromMilliseconds(beatmap.Value.BeatmapInfo.Length).ToFormattedDuration(); + return Math.Round(beatmap.Value.BeatmapInfo.Length / ModUtils.CalculateRateWithMods(mods.Value)).ToFormattedDuration(); case BeatmapAttribute.RankedStatus: return beatmap.Value.BeatmapInfo.Status.GetLocalisableDescription(); case BeatmapAttribute.BPM: - return beatmap.Value.BeatmapInfo.BPM.ToLocalisableString(@"F2"); + return FormatUtils.RoundBPM(beatmap.Value.BeatmapInfo.BPM, ModUtils.CalculateRateWithMods(mods.Value)).ToLocalisableString(@"F2"); case BeatmapAttribute.CircleSize: return computeDifficulty().CircleSize.ToLocalisableString(@"F2"); From f9a9ceb41c76fd67b90a129cc97d0b4018afa2d9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Oct 2024 17:42:40 +0900 Subject: [PATCH 584/924] Also bind to mod setting changes --- .../Skinning/Components/BeatmapAttributeText.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index 6cfed8b945..ccee90410e 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -50,6 +50,7 @@ namespace osu.Game.Skinning.Components private readonly OsuSpriteText text; private IBindable? difficultyBindable; private CancellationTokenSource? difficultyCancellationSource; + private ModSettingChangeTracker? modSettingTracker; public BeatmapAttributeText() { @@ -84,7 +85,17 @@ namespace osu.Game.Skinning.Components updateText(); }, true); - mods.BindValueChanged(_ => updateText()); + mods.BindValueChanged(m => + { + modSettingTracker?.Dispose(); + modSettingTracker = new ModSettingChangeTracker(m.NewValue) + { + SettingChanged = _ => updateText() + }; + + updateText(); + }, true); + ruleset.BindValueChanged(_ => updateText()); updateText(); From d195a694479d8086a81bed39a33776138b104084 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Oct 2024 19:26:19 +0900 Subject: [PATCH 585/924] Compute maximum performance along with difficulty --- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 109 +++++++++++++++- osu.Game/Beatmaps/StarDifficulty.cs | 25 ++-- .../PerformanceBreakdownCalculator.cs | 121 ------------------ .../Statistics/PerformanceStatistic.cs | 4 +- .../Statistics/PerformanceBreakdownChart.cs | 34 ++++- 5 files changed, 151 insertions(+), 142 deletions(-) delete mode 100644 osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index 871faf5906..c4506c9f7c 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -4,12 +4,15 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Extensions; +using osu.Framework.Graphics.Textures; using osu.Framework.Lists; using osu.Framework.Logging; using osu.Framework.Threading; @@ -18,7 +21,12 @@ using osu.Game.Database; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; +using osu.Game.Skinning; +using osu.Game.Storyboards; namespace osu.Game.Beatmaps { @@ -237,10 +245,13 @@ namespace osu.Game.Beatmaps var ruleset = rulesetInfo.CreateInstance(); Debug.Assert(ruleset != null); - var calculator = ruleset.CreateDifficultyCalculator(beatmapManager.GetWorkingBeatmap(key.BeatmapInfo)); - var attributes = calculator.Calculate(key.OrderedMods, cancellationToken); + PlayableCachedWorkingBeatmap working = new PlayableCachedWorkingBeatmap(beatmapManager.GetWorkingBeatmap(key.BeatmapInfo)); - return new StarDifficulty(attributes); + var difficulty = ruleset.CreateDifficultyCalculator(working).Calculate(key.OrderedMods, cancellationToken); + cancellationToken.ThrowIfCancellationRequested(); + var performance = computeMaxPerformance(working, key.BeatmapInfo, ruleset, key.OrderedMods, difficulty); + + return new StarDifficulty(difficulty, performance); } catch (OperationCanceledException) { @@ -262,6 +273,60 @@ namespace osu.Game.Beatmaps } } + private static PerformanceAttributes computeMaxPerformance(IWorkingBeatmap working, BeatmapInfo beatmap, Ruleset ruleset, Mod[] mods, DifficultyAttributes attributes) + { + var performanceCalculator = ruleset.CreatePerformanceCalculator(); + if (performanceCalculator == null) + return new PerformanceAttributes(); + + IBeatmap playableBeatmap = working.GetPlayableBeatmap(ruleset.RulesetInfo, mods); + + // create statistics assuming all hit objects have perfect hit result + var statistics = playableBeatmap.HitObjects + .SelectMany(getPerfectHitResults) + .GroupBy(hr => hr, (hr, list) => (hitResult: hr, count: list.Count())) + .ToDictionary(pair => pair.hitResult, pair => pair.count); + + // compute maximum total score + ScoreProcessor scoreProcessor = ruleset.CreateScoreProcessor(); + scoreProcessor.Mods.Value = mods; + scoreProcessor.ApplyBeatmap(playableBeatmap); + long maxScore = scoreProcessor.MaximumTotalScore; + + // todo: Get max combo from difficulty calculator instead when diffcalc properly supports lazer-first scores + int maxCombo = calculateMaxCombo(playableBeatmap); + + // compute maximum rank - default to SS, then adjust the rank with mods + ScoreRank maxRank = ScoreRank.X; + foreach (IApplicableToScoreProcessor mod in mods.OfType()) + maxRank = mod.AdjustRank(maxRank, 1); + + ScoreInfo perfectScore = new ScoreInfo(beatmap, ruleset.RulesetInfo) + { + Accuracy = 1, + Passed = true, + MaxCombo = maxCombo, + Combo = maxCombo, + Mods = mods, + TotalScore = maxScore, + Statistics = statistics, + MaximumStatistics = statistics + }; + + return performanceCalculator.Calculate(perfectScore, attributes); + + static int calculateMaxCombo(IBeatmap beatmap) + => beatmap.HitObjects.SelectMany(getPerfectHitResults).Count(r => r.AffectsCombo()); + + static IEnumerable getPerfectHitResults(HitObject hitObject) + { + foreach (HitObject nested in hitObject.NestedHitObjects) + yield return nested.Judgement.MaxResult; + + yield return hitObject.Judgement.MaxResult; + } + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -276,7 +341,6 @@ namespace osu.Game.Beatmaps { public readonly BeatmapInfo BeatmapInfo; public readonly RulesetInfo Ruleset; - public readonly Mod[] OrderedMods; public DifficultyCacheLookup(BeatmapInfo beatmapInfo, RulesetInfo? ruleset, IEnumerable? mods) @@ -317,5 +381,42 @@ namespace osu.Game.Beatmaps CancellationToken = cancellationToken; } } + + /// + /// A working beatmap that caches its playable representation. + /// This is intended as single-use for when it is guaranteed that the playable beatmap can be reused. + /// + private class PlayableCachedWorkingBeatmap : IWorkingBeatmap + { + private readonly IWorkingBeatmap working; + private IBeatmap? playable; + + public PlayableCachedWorkingBeatmap(IWorkingBeatmap working) + { + this.working = working; + } + + public IBeatmap GetPlayableBeatmap(IRulesetInfo ruleset, IReadOnlyList mods) + => playable ??= working.GetPlayableBeatmap(ruleset, mods); + + public IBeatmap GetPlayableBeatmap(IRulesetInfo ruleset, IReadOnlyList mods, CancellationToken cancellationToken) + => playable ??= working.GetPlayableBeatmap(ruleset, mods, cancellationToken); + + IBeatmapInfo IWorkingBeatmap.BeatmapInfo => working.BeatmapInfo; + bool IWorkingBeatmap.BeatmapLoaded => working.BeatmapLoaded; + bool IWorkingBeatmap.TrackLoaded => working.TrackLoaded; + IBeatmap IWorkingBeatmap.Beatmap => working.Beatmap; + Texture IWorkingBeatmap.GetBackground() => working.GetBackground(); + Texture IWorkingBeatmap.GetPanelBackground() => working.GetPanelBackground(); + Waveform IWorkingBeatmap.Waveform => working.Waveform; + Storyboard IWorkingBeatmap.Storyboard => working.Storyboard; + ISkin IWorkingBeatmap.Skin => working.Skin; + Track IWorkingBeatmap.Track => working.Track; + Track IWorkingBeatmap.LoadTrack() => working.LoadTrack(); + Stream IWorkingBeatmap.GetStream(string storagePath) => working.GetStream(storagePath); + void IWorkingBeatmap.BeginAsyncLoad() => working.BeginAsyncLoad(); + void IWorkingBeatmap.CancelAsyncLoad() => working.CancelAsyncLoad(); + void IWorkingBeatmap.PrepareTrackForPreview(bool looping, double offsetFromPreviewPoint) => working.PrepareTrackForPreview(looping, offsetFromPreviewPoint); + } } } diff --git a/osu.Game/Beatmaps/StarDifficulty.cs b/osu.Game/Beatmaps/StarDifficulty.cs index 6aac275a6a..51358fcd7f 100644 --- a/osu.Game/Beatmaps/StarDifficulty.cs +++ b/osu.Game/Beatmaps/StarDifficulty.cs @@ -26,29 +26,36 @@ namespace osu.Game.Beatmaps /// Might not be available if the star difficulty is associated with a beatmap that's not locally available. /// [CanBeNull] - public readonly DifficultyAttributes Attributes; + public readonly DifficultyAttributes DifficultyAttributes; /// - /// Creates a structure based on computed - /// by a . + /// The performance attributes computed for a perfect score on the given beatmap. + /// Might not be available if the star difficulty is associated with a beatmap that's not locally available. /// - public StarDifficulty([NotNull] DifficultyAttributes attributes) + [CanBeNull] + public readonly PerformanceAttributes PerformanceAttributes; + + /// + /// Creates a structure. + /// + public StarDifficulty([NotNull] DifficultyAttributes difficulty, [NotNull] PerformanceAttributes performance) { - Stars = double.IsFinite(attributes.StarRating) ? attributes.StarRating : 0; - MaxCombo = attributes.MaxCombo; - Attributes = attributes; + Stars = double.IsFinite(difficulty.StarRating) ? difficulty.StarRating : 0; + MaxCombo = difficulty.MaxCombo; + DifficultyAttributes = difficulty; + PerformanceAttributes = performance; // Todo: Add more members (BeatmapInfo.DifficultyRating? Attributes? Etc...) } /// /// Creates a structure with a pre-populated star difficulty and max combo - /// in scenarios where computing is not feasible (i.e. when working with online sources). + /// in scenarios where computing is not feasible (i.e. when working with online sources). /// public StarDifficulty(double starDifficulty, int maxCombo) { Stars = double.IsFinite(starDifficulty) ? starDifficulty : 0; MaxCombo = maxCombo; - Attributes = null; + DifficultyAttributes = null; } public DifficultyRating DifficultyRating => GetDifficultyRating(Stars); diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs deleted file mode 100644 index 946d83b14b..0000000000 --- a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using JetBrains.Annotations; -using osu.Framework.Extensions.ObjectExtensions; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; - -namespace osu.Game.Rulesets.Difficulty -{ - public class PerformanceBreakdownCalculator - { - private readonly IBeatmap playableBeatmap; - private readonly BeatmapDifficultyCache difficultyCache; - - public PerformanceBreakdownCalculator(IBeatmap playableBeatmap, BeatmapDifficultyCache difficultyCache) - { - this.playableBeatmap = playableBeatmap; - this.difficultyCache = difficultyCache; - } - - [ItemCanBeNull] - public async Task CalculateAsync(ScoreInfo score, CancellationToken cancellationToken = default) - { - var attributes = await difficultyCache.GetDifficultyAsync(score.BeatmapInfo!, score.Ruleset, score.Mods, cancellationToken).ConfigureAwait(false); - - var performanceCalculator = score.Ruleset.CreateInstance().CreatePerformanceCalculator(); - - // Performance calculation requires the beatmap and ruleset to be locally available. If not, return a default value. - if (attributes?.Attributes == null || performanceCalculator == null) - return null; - - cancellationToken.ThrowIfCancellationRequested(); - - PerformanceAttributes[] performanceArray = await Task.WhenAll( - // compute actual performance - performanceCalculator.CalculateAsync(score, attributes.Value.Attributes, cancellationToken), - // compute performance for perfect play - getPerfectPerformance(score, cancellationToken) - ).ConfigureAwait(false); - - return new PerformanceBreakdown(performanceArray[0] ?? new PerformanceAttributes(), performanceArray[1] ?? new PerformanceAttributes()); - } - - [ItemCanBeNull] - private Task getPerfectPerformance(ScoreInfo score, CancellationToken cancellationToken = default) - { - return Task.Run(async () => - { - Ruleset ruleset = score.Ruleset.CreateInstance(); - ScoreInfo perfectPlay = score.DeepClone(); - perfectPlay.Accuracy = 1; - perfectPlay.Passed = true; - - // calculate max combo - // todo: Get max combo from difficulty calculator instead when diffcalc properly supports lazer-first scores - perfectPlay.MaxCombo = calculateMaxCombo(playableBeatmap); - - // create statistics assuming all hit objects have perfect hit result - var statistics = playableBeatmap.HitObjects - .SelectMany(getPerfectHitResults) - .GroupBy(hr => hr, (hr, list) => (hitResult: hr, count: list.Count())) - .ToDictionary(pair => pair.hitResult, pair => pair.count); - perfectPlay.Statistics = statistics; - perfectPlay.MaximumStatistics = statistics; - - // calculate total score - ScoreProcessor scoreProcessor = ruleset.CreateScoreProcessor(); - scoreProcessor.Mods.Value = perfectPlay.Mods; - scoreProcessor.ApplyBeatmap(playableBeatmap); - perfectPlay.TotalScore = scoreProcessor.MaximumTotalScore; - - // compute rank achieved - // default to SS, then adjust the rank with mods - perfectPlay.Rank = ScoreRank.X; - - foreach (IApplicableToScoreProcessor mod in perfectPlay.Mods.OfType()) - { - perfectPlay.Rank = mod.AdjustRank(perfectPlay.Rank, 1); - } - - // calculate performance for this perfect score - var difficulty = await difficultyCache.GetDifficultyAsync( - playableBeatmap.BeatmapInfo, - score.Ruleset, - score.Mods, - cancellationToken - ).ConfigureAwait(false); - - var performanceCalculator = ruleset.CreatePerformanceCalculator(); - - if (performanceCalculator == null || difficulty == null) - return null; - - return await performanceCalculator.CalculateAsync(perfectPlay, difficulty.Value.Attributes.AsNonNull(), cancellationToken).ConfigureAwait(false); - }, cancellationToken); - } - - private int calculateMaxCombo(IBeatmap beatmap) - { - return beatmap.HitObjects.SelectMany(getPerfectHitResults).Count(r => r.AffectsCombo()); - } - - private IEnumerable getPerfectHitResults(HitObject hitObject) - { - foreach (HitObject nested in hitObject.NestedHitObjects) - yield return nested.Judgement.MaxResult; - - yield return hitObject.Judgement.MaxResult; - } - } -} diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs index 7ea3cbe917..7d155e32b0 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs @@ -53,10 +53,10 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics var performanceCalculator = score.Ruleset.CreateInstance().CreatePerformanceCalculator(); // Performance calculation requires the beatmap and ruleset to be locally available. If not, return a default value. - if (attributes?.Attributes == null || performanceCalculator == null) + if (attributes?.DifficultyAttributes == null || performanceCalculator == null) return; - var result = await performanceCalculator.CalculateAsync(score, attributes.Value.Attributes, cancellationToken ?? default).ConfigureAwait(false); + var result = await performanceCalculator.CalculateAsync(score, attributes.Value.DifficultyAttributes, cancellationToken ?? default).ConfigureAwait(false); Schedule(() => setPerformanceValue(score, result.Total)); }, cancellationToken ?? default); diff --git a/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs b/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs index b5eed2d12a..f9c8c93dec 100644 --- a/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs +++ b/osu.Game/Screens/Ranking/Statistics/PerformanceBreakdownChart.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; @@ -26,7 +27,6 @@ namespace osu.Game.Screens.Ranking.Statistics public partial class PerformanceBreakdownChart : Container { private readonly ScoreInfo score; - private readonly IBeatmap playableBeatmap; private Drawable spinner = null!; private Drawable content = null!; @@ -42,7 +42,6 @@ namespace osu.Game.Screens.Ranking.Statistics public PerformanceBreakdownChart(ScoreInfo score, IBeatmap playableBeatmap) { this.score = score; - this.playableBeatmap = playableBeatmap; } [BackgroundDependencyLoader] @@ -142,12 +141,33 @@ namespace osu.Game.Screens.Ranking.Statistics spinner.Show(); - new PerformanceBreakdownCalculator(playableBeatmap, difficultyCache) - .CalculateAsync(score, cancellationTokenSource.Token) - .ContinueWith(t => Schedule(() => setPerformanceValue(t.GetResultSafely()!))); + computePerformance(cancellationTokenSource.Token) + .ContinueWith(t => Schedule(() => + { + if (t.GetResultSafely() is PerformanceBreakdown breakdown) + setPerformance(breakdown); + }), TaskContinuationOptions.OnlyOnRanToCompletion); } - private void setPerformanceValue(PerformanceBreakdown breakdown) + private async Task computePerformance(CancellationToken token) + { + var performanceCalculator = score.Ruleset.CreateInstance().CreatePerformanceCalculator(); + if (performanceCalculator == null) + return null; + + var starsTask = difficultyCache.GetDifficultyAsync(score.BeatmapInfo!, score.Ruleset, score.Mods, token).ConfigureAwait(false); + if (await starsTask is not StarDifficulty stars) + return null; + + if (stars.DifficultyAttributes == null || stars.PerformanceAttributes == null) + return null; + + return new PerformanceBreakdown( + await performanceCalculator.CalculateAsync(score, stars.DifficultyAttributes, token).ConfigureAwait(false), + stars.PerformanceAttributes); + } + + private void setPerformance(PerformanceBreakdown breakdown) { spinner.Hide(); content.FadeIn(200); @@ -236,6 +256,8 @@ namespace osu.Game.Screens.Ranking.Statistics protected override void Dispose(bool isDisposing) { cancellationTokenSource.Cancel(); + cancellationTokenSource.Dispose(); + base.Dispose(isDisposing); } } From bfcf6693cac611de1deb8909a76a15ef7f8517f2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Oct 2024 19:39:03 +0900 Subject: [PATCH 586/924] Simplify implementation --- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 85 +++++++-------------- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 6 ++ 2 files changed, 33 insertions(+), 58 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index c4506c9f7c..fc4175415c 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -21,7 +21,6 @@ using osu.Game.Database; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; @@ -245,11 +244,35 @@ namespace osu.Game.Beatmaps var ruleset = rulesetInfo.CreateInstance(); Debug.Assert(ruleset != null); - PlayableCachedWorkingBeatmap working = new PlayableCachedWorkingBeatmap(beatmapManager.GetWorkingBeatmap(key.BeatmapInfo)); + PlayableCachedWorkingBeatmap workingBeatmap = new PlayableCachedWorkingBeatmap(beatmapManager.GetWorkingBeatmap(key.BeatmapInfo)); + IBeatmap playableBeatmap = workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo, key.OrderedMods, cancellationToken); - var difficulty = ruleset.CreateDifficultyCalculator(working).Calculate(key.OrderedMods, cancellationToken); + var difficulty = ruleset.CreateDifficultyCalculator(workingBeatmap).Calculate(key.OrderedMods, cancellationToken); + cancellationToken.ThrowIfCancellationRequested(); + + var performanceCalculator = ruleset.CreatePerformanceCalculator(); + if (performanceCalculator == null) + return new StarDifficulty(difficulty, new PerformanceAttributes()); + + ScoreProcessor scoreProcessor = ruleset.CreateScoreProcessor(); + scoreProcessor.Mods.Value = key.OrderedMods; + scoreProcessor.ApplyBeatmap(playableBeatmap); + cancellationToken.ThrowIfCancellationRequested(); + + ScoreInfo perfectScore = new ScoreInfo(key.BeatmapInfo, ruleset.RulesetInfo) + { + Passed = true, + Accuracy = 1, + Mods = key.OrderedMods, + MaxCombo = scoreProcessor.MaximumCombo, + Combo = scoreProcessor.MaximumCombo, + TotalScore = scoreProcessor.MaximumTotalScore, + Statistics = scoreProcessor.MaximumStatistics, + MaximumStatistics = scoreProcessor.MaximumStatistics + }; + + var performance = performanceCalculator.Calculate(perfectScore, difficulty); cancellationToken.ThrowIfCancellationRequested(); - var performance = computeMaxPerformance(working, key.BeatmapInfo, ruleset, key.OrderedMods, difficulty); return new StarDifficulty(difficulty, performance); } @@ -273,60 +296,6 @@ namespace osu.Game.Beatmaps } } - private static PerformanceAttributes computeMaxPerformance(IWorkingBeatmap working, BeatmapInfo beatmap, Ruleset ruleset, Mod[] mods, DifficultyAttributes attributes) - { - var performanceCalculator = ruleset.CreatePerformanceCalculator(); - if (performanceCalculator == null) - return new PerformanceAttributes(); - - IBeatmap playableBeatmap = working.GetPlayableBeatmap(ruleset.RulesetInfo, mods); - - // create statistics assuming all hit objects have perfect hit result - var statistics = playableBeatmap.HitObjects - .SelectMany(getPerfectHitResults) - .GroupBy(hr => hr, (hr, list) => (hitResult: hr, count: list.Count())) - .ToDictionary(pair => pair.hitResult, pair => pair.count); - - // compute maximum total score - ScoreProcessor scoreProcessor = ruleset.CreateScoreProcessor(); - scoreProcessor.Mods.Value = mods; - scoreProcessor.ApplyBeatmap(playableBeatmap); - long maxScore = scoreProcessor.MaximumTotalScore; - - // todo: Get max combo from difficulty calculator instead when diffcalc properly supports lazer-first scores - int maxCombo = calculateMaxCombo(playableBeatmap); - - // compute maximum rank - default to SS, then adjust the rank with mods - ScoreRank maxRank = ScoreRank.X; - foreach (IApplicableToScoreProcessor mod in mods.OfType()) - maxRank = mod.AdjustRank(maxRank, 1); - - ScoreInfo perfectScore = new ScoreInfo(beatmap, ruleset.RulesetInfo) - { - Accuracy = 1, - Passed = true, - MaxCombo = maxCombo, - Combo = maxCombo, - Mods = mods, - TotalScore = maxScore, - Statistics = statistics, - MaximumStatistics = statistics - }; - - return performanceCalculator.Calculate(perfectScore, attributes); - - static int calculateMaxCombo(IBeatmap beatmap) - => beatmap.HitObjects.SelectMany(getPerfectHitResults).Count(r => r.AffectsCombo()); - - static IEnumerable getPerfectHitResults(HitObject hitObject) - { - foreach (HitObject nested in hitObject.NestedHitObjects) - yield return nested.Judgement.MaxResult; - - yield return hitObject.Judgement.MaxResult; - } - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 9752918dfb..7b5af9beda 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -119,6 +119,11 @@ namespace osu.Game.Rulesets.Scoring /// public long MaximumTotalScore { get; private set; } + /// + /// The maximum achievable combo. + /// + public int MaximumCombo { get; private set; } + /// /// The maximum sum of accuracy-affecting judgements at the current point in time. /// @@ -423,6 +428,7 @@ namespace osu.Game.Rulesets.Scoring MaximumResultCounts.AddRange(ScoreResultCounts); MaximumTotalScore = TotalScore.Value; + MaximumCombo = HighestCombo.Value; } ScoreResultCounts.Clear(); From 6749768b9e1a1b4d095581ade5359e7927f1e34a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Oct 2024 19:43:34 +0900 Subject: [PATCH 587/924] Add max performance beatmap attribute text --- .../SkinComponents/BeatmapAttributeTextStrings.cs | 13 +++++++++---- .../Skinning/Components/BeatmapAttributeText.cs | 10 ++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/osu.Game/Localisation/SkinComponents/BeatmapAttributeTextStrings.cs b/osu.Game/Localisation/SkinComponents/BeatmapAttributeTextStrings.cs index b2e2285faf..390a6f9ca4 100644 --- a/osu.Game/Localisation/SkinComponents/BeatmapAttributeTextStrings.cs +++ b/osu.Game/Localisation/SkinComponents/BeatmapAttributeTextStrings.cs @@ -12,23 +12,28 @@ namespace osu.Game.Localisation.SkinComponents /// /// "Attribute" /// - public static LocalisableString Attribute => new TranslatableString(getKey(@"attribute"), "Attribute"); + public static LocalisableString Attribute => new TranslatableString(getKey(@"attribute"), @"Attribute"); /// /// "The attribute to be displayed." /// - public static LocalisableString AttributeDescription => new TranslatableString(getKey(@"attribute_description"), "The attribute to be displayed."); + public static LocalisableString AttributeDescription => new TranslatableString(getKey(@"attribute_description"), @"The attribute to be displayed."); /// /// "Template" /// - public static LocalisableString Template => new TranslatableString(getKey(@"template"), "Template"); + public static LocalisableString Template => new TranslatableString(getKey(@"template"), @"Template"); /// /// "Supports {{Label}} and {{Value}}, but also including arbitrary attributes like {{StarRating}} (see attribute list for supported values)." /// public static LocalisableString TemplateDescription => new TranslatableString(getKey(@"template_description"), @"Supports {{Label}} and {{Value}}, but also including arbitrary attributes like {{StarRating}} (see attribute list for supported values)."); - private static string getKey(string key) => $"{prefix}:{key}"; + /// + /// "Max PP" + /// + public static LocalisableString MaxPP => new TranslatableString(getKey(@"max_pp"), @"Max PP"); + + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index ccee90410e..28352b533f 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -21,6 +21,7 @@ using osu.Game.Localisation; using osu.Game.Localisation.SkinComponents; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Utils; @@ -167,6 +168,9 @@ namespace osu.Game.Skinning.Components case BeatmapAttribute.BPM: return BeatmapsetsStrings.ShowStatsBpm; + case BeatmapAttribute.MaxPP: + return BeatmapAttributeTextStrings.MaxPP; + default: throw new ArgumentOutOfRangeException(); } @@ -217,6 +221,11 @@ namespace osu.Game.Skinning.Components ? starDifficulty.Stars.ToLocalisableString(@"F2") : @"..."; + case BeatmapAttribute.MaxPP: + return difficultyBindable?.Value?.PerformanceAttributes is PerformanceAttributes attributes + ? attributes.Total.ToLocalisableString(@"F2") + : @"..."; + default: throw new ArgumentOutOfRangeException(); } @@ -269,5 +278,6 @@ namespace osu.Game.Skinning.Components RankedStatus, BPM, Source, + MaxPP } } From b40c31af3adc922d2c47ecd0f810e179aece085f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Oct 2024 20:36:15 +0900 Subject: [PATCH 588/924] Store difficulty to reduce flickering The computation usually finishes in a few milliseconds anyway. --- osu.Game/Skinning/Components/BeatmapAttributeText.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index ccee90410e..316b20035c 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -51,6 +51,7 @@ namespace osu.Game.Skinning.Components private IBindable? difficultyBindable; private CancellationTokenSource? difficultyCancellationSource; private ModSettingChangeTracker? modSettingTracker; + private StarDifficulty? starDifficulty; public BeatmapAttributeText() { @@ -80,7 +81,11 @@ namespace osu.Game.Skinning.Components difficultyBindable?.UnbindAll(); difficultyBindable = difficultyCache.GetBindableDifficulty(b.NewValue.BeatmapInfo, difficultyCancellationSource.Token); - difficultyBindable.BindValueChanged(_ => updateText()); + difficultyBindable.BindValueChanged(d => + { + starDifficulty = d.NewValue; + updateText(); + }); updateText(); }, true); @@ -213,9 +218,7 @@ namespace osu.Game.Skinning.Components return computeDifficulty().ApproachRate.ToLocalisableString(@"F2"); case BeatmapAttribute.StarRating: - return difficultyBindable?.Value is StarDifficulty starDifficulty - ? starDifficulty.Stars.ToLocalisableString(@"F2") - : @"..."; + return (starDifficulty?.Stars ?? 0).ToLocalisableString(@"F2"); default: throw new ArgumentOutOfRangeException(); From 0473f0b60a58a0b0b733f9125ed121022c1fc6d1 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Oct 2024 20:39:24 +0900 Subject: [PATCH 589/924] Use stored difficulty to reduce flickering --- osu.Game/Skinning/Components/BeatmapAttributeText.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index bfdb7655fc..1b4f853d72 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -21,7 +21,6 @@ using osu.Game.Localisation; using osu.Game.Localisation.SkinComponents; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; -using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Utils; @@ -225,9 +224,7 @@ namespace osu.Game.Skinning.Components return (starDifficulty?.Stars ?? 0).ToLocalisableString(@"F2"); case BeatmapAttribute.MaxPP: - return difficultyBindable?.Value?.PerformanceAttributes is PerformanceAttributes attributes - ? attributes.Total.ToLocalisableString(@"F2") - : @"..."; + return (starDifficulty?.PerformanceAttributes?.Total ?? 0).ToLocalisableString(@"F2"); default: throw new ArgumentOutOfRangeException(); From f3178b1fef38ae606b8b632735556fa9dff50b61 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Oct 2024 21:16:45 +0900 Subject: [PATCH 590/924] Add test scene --- .../TestSceneBeatmapAttributeText.cs | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs new file mode 100644 index 0000000000..bf959d9862 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs @@ -0,0 +1,98 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Models; +using osu.Game.Rulesets.Osu; +using osu.Game.Skinning.Components; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public partial class TestSceneBeatmapAttributeText : OsuTestScene + { + private readonly BeatmapAttributeText text; + + public TestSceneBeatmapAttributeText() + { + Child = text = new BeatmapAttributeText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }; + } + + [SetUp] + public void Setup() => Schedule(() => + { + Beatmap.Value = CreateWorkingBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo) + { + BeatmapInfo = + { + BPM = 100, + DifficultyName = "_Difficulty", + Status = BeatmapOnlineStatus.Loved, + Metadata = + { + Title = "_Title", + TitleUnicode = "_Title", + Artist = "_Artist", + ArtistUnicode = "_Artist", + Author = new RealmUser { Username = "_Creator" }, + Source = "_Source", + }, + Difficulty = + { + CircleSize = 1, + DrainRate = 2, + OverallDifficulty = 3, + ApproachRate = 4, + } + } + }); + }); + + [TestCase(BeatmapAttribute.CircleSize, "Circle Size: 1.00")] + [TestCase(BeatmapAttribute.HPDrain, "HP Drain: 2.00")] + [TestCase(BeatmapAttribute.Accuracy, "Accuracy: 3.00")] + [TestCase(BeatmapAttribute.ApproachRate, "Approach Rate: 4.00")] + [TestCase(BeatmapAttribute.Title, "Title: _Title")] + [TestCase(BeatmapAttribute.Artist, "Artist: _Artist")] + [TestCase(BeatmapAttribute.Creator, "Creator: _Creator")] + [TestCase(BeatmapAttribute.DifficultyName, "Difficulty: _Difficulty")] + [TestCase(BeatmapAttribute.Source, "Source: _Source")] + [TestCase(BeatmapAttribute.RankedStatus, "Beatmap Status: Loved")] + public void TestAttributeDisplay(BeatmapAttribute attribute, string expectedText) + { + AddStep($"set attribute: {attribute}", () => text.Attribute.Value = attribute); + AddAssert("check correct text", getText, () => Is.EqualTo(expectedText)); + } + + [Test] + public void TestChangeBeatmap() + { + AddStep("set title attribute", () => text.Attribute.Value = BeatmapAttribute.Title); + AddAssert("check initial title", getText, () => Is.EqualTo("Title: _Title")); + + AddStep("change to beatmap with another title", () => Beatmap.Value = CreateWorkingBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo) + { + BeatmapInfo = + { + Metadata = + { + Title = "Another" + } + } + })); + + AddAssert("check new title", getText, () => Is.EqualTo("Title: Another")); + } + + private string getText() => text.ChildrenOfType().Single().Text.ToString(); + } +} From c0eda3606cb6be03938f3df155e3648e8f46f7bc Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Oct 2024 22:01:55 +0900 Subject: [PATCH 591/924] Add mod-related tests --- .../TestSceneBeatmapAttributeText.cs | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs index bf959d9862..01659a2654 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs @@ -1,14 +1,28 @@ // 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 Newtonsoft.Json; using NUnit.Framework; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Models; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osu.Game.Skinning.Components; using osu.Game.Tests.Beatmaps; @@ -30,6 +44,8 @@ namespace osu.Game.Tests.Visual.UserInterface [SetUp] public void Setup() => Schedule(() => { + SelectedMods.SetDefault(); + Ruleset.SetDefault(); Beatmap.Value = CreateWorkingBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo) { BeatmapInfo = @@ -93,6 +109,126 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("check new title", getText, () => Is.EqualTo("Title: Another")); } + [Test] + public void TestWithMods() + { + AddStep("set beatmap", () => Beatmap.Value = CreateWorkingBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo) + { + BeatmapInfo = + { + BPM = 100, + Length = 30000, + Difficulty = + { + ApproachRate = 10, + CircleSize = 9 + } + } + })); + + test(BeatmapAttribute.BPM, new OsuModDoubleTime(), "BPM: 100.00", "BPM: 150.00"); + test(BeatmapAttribute.Length, new OsuModDoubleTime(), "Length: 00:30", "Length: 00:20"); + test(BeatmapAttribute.ApproachRate, new OsuModDoubleTime(), "Approach Rate: 10.00", "Approach Rate: 11.00"); + test(BeatmapAttribute.CircleSize, new OsuModHardRock(), "Circle Size: 9.00", "Circle Size: 10.00"); + + void test(BeatmapAttribute attribute, Mod mod, string before, string after) + { + AddStep($"set attribute: {attribute}", () => text.Attribute.Value = attribute); + AddAssert("check text is correct", getText, () => Is.EqualTo(before)); + + AddStep("add DT mod", () => SelectedMods.Value = new[] { mod }); + AddAssert("check text is correct", getText, () => Is.EqualTo(after)); + AddStep("clear mods", () => SelectedMods.SetDefault()); + } + } + + [Test] + public void TestStarRating() + { + AddStep("set test ruleset", () => Ruleset.Value = new TestRuleset().RulesetInfo); + AddStep("set star rating attribute", () => text.Attribute.Value = BeatmapAttribute.StarRating); + AddAssert("check star rating is 0", getText, () => Is.EqualTo("Star Rating: 0.00")); + + // Adding mod + TestMod mod = null!; + AddStep("add mod with difficulty 1", () => SelectedMods.Value = new[] { mod = new TestMod { Difficulty = { Value = 1 } } }); + AddUntilStep("check star rating is 1", getText, () => Is.EqualTo("Star Rating: 1.00")); + + // Changing mod setting + AddStep("change mod difficulty to 2", () => mod.Difficulty.Value = 2); + AddUntilStep("check star rating is 2", getText, () => Is.EqualTo("Star Rating: 2.00")); + } + private string getText() => text.ChildrenOfType().Single().Text.ToString(); + + private class TestRuleset : Ruleset + { + public override IEnumerable GetModsFor(ModType type) => new[] + { + new TestMod() + }; + + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) + => new OsuRuleset().CreateBeatmapConverter(beatmap); + + public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) + => new TestDifficultyCalculator(new TestRuleset().RulesetInfo, beatmap); + + public override PerformanceCalculator CreatePerformanceCalculator() + => new TestPerformanceCalculator(new TestRuleset()); + + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList? mods = null) + => null!; + + public override string Description => string.Empty; + public override string ShortName => string.Empty; + } + + private class TestDifficultyCalculator : DifficultyCalculator + { + public TestDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) + : base(ruleset, beatmap) + { + } + + protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) + => new DifficultyAttributes(mods, mods.OfType().SingleOrDefault()?.Difficulty.Value ?? 0); + + protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) + => Array.Empty(); + + protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) + => Array.Empty(); + } + + private class TestPerformanceCalculator : PerformanceCalculator + { + public TestPerformanceCalculator(Ruleset ruleset) + : base(ruleset) + { + } + + protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, DifficultyAttributes attributes) + => new PerformanceAttributes { Total = score.Mods.OfType().SingleOrDefault()?.Performance.Value ?? 0 }; + } + + private class TestMod : Mod + { + [SettingSource("difficulty")] + public BindableDouble Difficulty { get; } = new BindableDouble(0); + + [SettingSource("performance")] + public BindableDouble Performance { get; } = new BindableDouble(0); + + [JsonConstructor] + public TestMod() + { + } + + public override string Name => string.Empty; + public override LocalisableString Description => string.Empty; + public override double ScoreMultiplier => 1.0; + public override string Acronym => "Test"; + } } } From 03094533b481cc2365b59d2b9c5e9b51a77f08f4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Oct 2024 22:03:53 +0900 Subject: [PATCH 592/924] Add test --- .../TestSceneBeatmapAttributeText.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs index 01659a2654..5c929a4d62 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs @@ -159,6 +159,23 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("check star rating is 2", getText, () => Is.EqualTo("Star Rating: 2.00")); } + [Test] + public void TestMaxPp() + { + AddStep("set test ruleset", () => Ruleset.Value = new TestRuleset().RulesetInfo); + AddStep("set max pp attribute", () => text.Attribute.Value = BeatmapAttribute.MaxPP); + AddAssert("check max pp is 0", getText, () => Is.EqualTo("Max PP: 0.00")); + + // Adding mod + TestMod mod = null!; + AddStep("add mod with pp 1", () => SelectedMods.Value = new[] { mod = new TestMod { Performance = { Value = 1 } } }); + AddUntilStep("check max pp is 1", getText, () => Is.EqualTo("Max PP: 1.00")); + + // Changing mod setting + AddStep("change mod pp to 2", () => mod.Performance.Value = 2); + AddUntilStep("check max pp is 2", getText, () => Is.EqualTo("Max PP: 2.00")); + } + private string getText() => text.ChildrenOfType().Single().Text.ToString(); private class TestRuleset : Ruleset From 7416106321d6c8bf80bc971b088c1f6344d80ef5 Mon Sep 17 00:00:00 2001 From: jhk2601 Date: Thu, 17 Oct 2024 14:38:13 -0400 Subject: [PATCH 593/924] Fixes cursor rotating along with playfield when using Barrel Roll in standard --- osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs | 12 ++++++++++++ osu.Game/Rulesets/Mods/ModBarrelRoll.cs | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs index 2394cf92fc..8898faf7b8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs @@ -2,10 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.Mods { @@ -25,5 +28,14 @@ namespace osu.Game.Rulesets.Osu.Mods } }; } + + public override void Update(Playfield playfield) + { + base.Update(playfield); + OsuPlayfield osuPlayfield = (OsuPlayfield)playfield; + Debug.Assert(osuPlayfield.Cursor != null); + + osuPlayfield.Cursor.ActiveCursor.Rotation = -CurrentRotation; + } } } diff --git a/osu.Game/Rulesets/Mods/ModBarrelRoll.cs b/osu.Game/Rulesets/Mods/ModBarrelRoll.cs index 4f90496308..67f9da37be 100644 --- a/osu.Game/Rulesets/Mods/ModBarrelRoll.cs +++ b/osu.Game/Rulesets/Mods/ModBarrelRoll.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mods private PlayfieldAdjustmentContainer playfieldAdjustmentContainer = null!; - public void Update(Playfield playfield) + public virtual void Update(Playfield playfield) { playfieldAdjustmentContainer.Rotation = CurrentRotation = (Direction.Value == RotationDirection.Counterclockwise ? -1 : 1) * 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value); } From 3fe0791298103ab0006372ef2c98e9657155ada1 Mon Sep 17 00:00:00 2001 From: TaterToes Date: Thu, 17 Oct 2024 16:26:13 -0400 Subject: [PATCH 594/924] fix seeking back on control points --- osu.Game/Screens/Edit/Editor.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index e9bcd3050b..984caf5486 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1098,6 +1098,13 @@ namespace osu.Game.Screens.Edit private void seekControlPoint(int direction) { + // Gives 350 ms margin to seek back after last control point + double seekMargin = 0; + if (clock.IsRunning) + { + seekMargin = 350; + } + var found = direction < 1 ? editorBeatmap.ControlPointInfo.AllControlPoints.LastOrDefault(p => p.Time < clock.CurrentTime) : editorBeatmap.ControlPointInfo.AllControlPoints.FirstOrDefault(p => p.Time > clock.CurrentTime); From 45dd9b116710a3f58995c2bab0c10a8015284bf6 Mon Sep 17 00:00:00 2001 From: TaterToes Date: Thu, 17 Oct 2024 17:01:42 -0400 Subject: [PATCH 595/924] Forgot subtraction --- osu.Game/Screens/Edit/Editor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 984caf5486..ed8e6b28ca 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1106,7 +1106,7 @@ namespace osu.Game.Screens.Edit } var found = direction < 1 - ? editorBeatmap.ControlPointInfo.AllControlPoints.LastOrDefault(p => p.Time < clock.CurrentTime) + ? editorBeatmap.ControlPointInfo.AllControlPoints.LastOrDefault(p => p.Time < clock.CurrentTime - seekMargin) : editorBeatmap.ControlPointInfo.AllControlPoints.FirstOrDefault(p => p.Time > clock.CurrentTime); if (found != null) From 2ef87205900b687df9e30926db1ce02d87075f43 Mon Sep 17 00:00:00 2001 From: jhk2601 Date: Thu, 17 Oct 2024 17:04:13 -0400 Subject: [PATCH 596/924] Adds logic to prioritize selecting exact mod acronyms when they are searched --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index cdc0fbbd96..30f50e7065 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -590,9 +590,16 @@ namespace osu.Game.Overlays.Mods return true; } + //matchingMod ensures that if an exact mod acronym is typed, it will be selected when Select is pressed (as opposed to being prioritized in arbitrary column order) ModState? firstMod = columnFlow.Columns.OfType().FirstOrDefault(m => m.IsPresent)?.AvailableMods.FirstOrDefault(x => x.Visible); + ModState? matchingMod = columnFlow.Columns.OfType().SelectMany(m => m.AvailableMods).FirstOrDefault(x => x.Mod.Acronym.Equals(SearchTerm, StringComparison.OrdinalIgnoreCase)); - if (firstMod is not null) + if (matchingMod is not null) + { + matchingMod.Active.Value = !matchingMod.Active.Value; + SearchTextBox.SelectAll(); + } + else if (firstMod is not null) { firstMod.Active.Value = !firstMod.Active.Value; SearchTextBox.SelectAll(); From 9a0356919c31a1298690fe115285b6cf8483f5e5 Mon Sep 17 00:00:00 2001 From: jhk2601 Date: Thu, 17 Oct 2024 17:11:01 -0400 Subject: [PATCH 597/924] Also handle full mod name (likely irrelevant but might as well) --- 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 30f50e7065..b89a602a6e 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -592,7 +592,7 @@ namespace osu.Game.Overlays.Mods //matchingMod ensures that if an exact mod acronym is typed, it will be selected when Select is pressed (as opposed to being prioritized in arbitrary column order) ModState? firstMod = columnFlow.Columns.OfType().FirstOrDefault(m => m.IsPresent)?.AvailableMods.FirstOrDefault(x => x.Visible); - ModState? matchingMod = columnFlow.Columns.OfType().SelectMany(m => m.AvailableMods).FirstOrDefault(x => x.Mod.Acronym.Equals(SearchTerm, StringComparison.OrdinalIgnoreCase)); + ModState? matchingMod = columnFlow.Columns.OfType().SelectMany(m => m.AvailableMods).FirstOrDefault(x => x.Mod.Acronym.Equals(SearchTerm, StringComparison.OrdinalIgnoreCase) | x.Mod.Name.Equals(SearchTerm, StringComparison.OrdinalIgnoreCase)); if (matchingMod is not null) { From e10293531b4dc60ff8dda3d179a786d6d29958c8 Mon Sep 17 00:00:00 2001 From: TaterToes Date: Thu, 17 Oct 2024 18:10:48 -0400 Subject: [PATCH 598/924] adjust margin time and apply rate adjust --- osu.Game/Screens/Edit/Editor.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index ed8e6b28ca..9dd7d9da3f 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1102,11 +1102,13 @@ namespace osu.Game.Screens.Edit double seekMargin = 0; if (clock.IsRunning) { - seekMargin = 350; + seekMargin = 450; } + IAdjustableClock adjustableClock = clock; + var found = direction < 1 - ? editorBeatmap.ControlPointInfo.AllControlPoints.LastOrDefault(p => p.Time < clock.CurrentTime - seekMargin) + ? editorBeatmap.ControlPointInfo.AllControlPoints.LastOrDefault(p => p.Time < clock.CurrentTime - (seekMargin * adjustableClock.Rate)) : editorBeatmap.ControlPointInfo.AllControlPoints.FirstOrDefault(p => p.Time > clock.CurrentTime); if (found != null) From c8b0220934562e05abeb33eadb9d8f180f95ec4f Mon Sep 17 00:00:00 2001 From: TaterToes Date: Thu, 17 Oct 2024 18:12:05 -0400 Subject: [PATCH 599/924] fix comment --- osu.Game/Screens/Edit/Editor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 9dd7d9da3f..4df9362726 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1098,7 +1098,7 @@ namespace osu.Game.Screens.Edit private void seekControlPoint(int direction) { - // Gives 350 ms margin to seek back after last control point + // Gives margin to seek back after last control point double seekMargin = 0; if (clock.IsRunning) { From 0a8ba4bfb575c7f68fe110a72e858aea3a5bfe02 Mon Sep 17 00:00:00 2001 From: TaterToes Date: Thu, 17 Oct 2024 18:41:00 -0400 Subject: [PATCH 600/924] cleanup --- osu.Game/Screens/Edit/Editor.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 4df9362726..b99d24dbe4 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1102,13 +1102,12 @@ namespace osu.Game.Screens.Edit double seekMargin = 0; if (clock.IsRunning) { - seekMargin = 450; + IAdjustableClock adjustableClock = clock; + seekMargin = 450 * adjustableClock.Rate; } - - IAdjustableClock adjustableClock = clock; var found = direction < 1 - ? editorBeatmap.ControlPointInfo.AllControlPoints.LastOrDefault(p => p.Time < clock.CurrentTime - (seekMargin * adjustableClock.Rate)) + ? editorBeatmap.ControlPointInfo.AllControlPoints.LastOrDefault(p => p.Time < clock.CurrentTime - seekMargin) : editorBeatmap.ControlPointInfo.AllControlPoints.FirstOrDefault(p => p.Time > clock.CurrentTime); if (found != null) From 1337b7eb41d8a3e225bf7e8e1d0b9138dca88927 Mon Sep 17 00:00:00 2001 From: finadoggie <75299710+Finadoggie@users.noreply.github.com> Date: Thu, 17 Oct 2024 15:58:42 -0700 Subject: [PATCH 601/924] use LargeTickHit instead of LargeTickMiss LargeTickMiss appears to not be stored --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index b77afc173d..c5c3dbf418 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -52,7 +52,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty else { countSliderEndsDropped = osuAttributes.SliderCount - score.Statistics.GetValueOrDefault(HitResult.SliderTailHit); - countLargeTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss); + // Note: HitResult.LargeTickMiss is not stored within the API or in score dumps. Do not use it! + countLargeTickMiss = score.MaximumStatistics.GetValueOrDefault(HitResult.LargeTickHit) - score.Statistics.GetValueOrDefault(HitResult.LargeTickHit); effectiveMissCount = countMiss; } From 7d0da79db78346424ca71d387cc9bad63d29c2f4 Mon Sep 17 00:00:00 2001 From: SupDos <6813986+SupDos@users.noreply.github.com> Date: Fri, 18 Oct 2024 01:47:54 +0200 Subject: [PATCH 602/924] Add Use relative size setting to ArgonSongProgress --- osu.Game/Screens/Play/HUD/ArgonSongProgress.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs index 92ac863e98..4f8ee8b374 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs @@ -30,6 +30,9 @@ namespace osu.Game.Screens.Play.HUD [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowTime), nameof(SongProgressStrings.ShowTimeDescription))] public Bindable ShowTime { get; } = new BindableBool(true); + [SettingSource("Use relative size")] + public BindableBool UseRelativeSize { get; } = new BindableBool(true); + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour), nameof(SkinnableComponentStrings.ColourDescription))] public BindableColour4 AccentColour { get; } = new BindableColour4(Colour4.White); @@ -99,6 +102,11 @@ namespace osu.Game.Screens.Play.HUD ShowGraph.BindValueChanged(_ => updateGraphVisibility(), true); ShowTime.BindValueChanged(_ => info.FadeTo(ShowTime.Value ? 1 : 0, 200, Easing.In), true); AccentColour.BindValueChanged(_ => Colour = AccentColour.Value, true); + + // see comment in ArgonHealthDisplay.cs regarding RelativeSizeAxes + float previousWidth = Width; + UseRelativeSize.BindValueChanged(v => RelativeSizeAxes = v.NewValue ? Axes.X : Axes.None, true); + Width = previousWidth; } protected override void UpdateObjects(IEnumerable objects) From 8193038986d2dc205d83568ff2146220bc203dc5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Oct 2024 13:30:22 +0900 Subject: [PATCH 603/924] Expose no-op constructors as `protected` --- osu.Game/Beatmaps/BeatmapInfo.cs | 2 +- osu.Game/Scoring/ScoreInfo.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 425fd98d27..f1463eb632 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -62,7 +62,7 @@ namespace osu.Game.Beatmaps } [UsedImplicitly] - private BeatmapInfo() + protected BeatmapInfo() { } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 92c18c9c1e..a3dabc7945 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -165,7 +165,7 @@ namespace osu.Game.Scoring } [UsedImplicitly] // Realm - private ScoreInfo() + protected ScoreInfo() { } From b455b9ad09a74284ab0d68b1e1088a8beb763ab6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 18 Oct 2024 08:39:23 +0200 Subject: [PATCH 604/924] Discard unused argument --- osu.Game/Skinning/Components/BeatmapAttributeText.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index 63ba6d1581..77e415b995 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -55,7 +55,7 @@ namespace osu.Game.Skinning.Components Attribute.BindValueChanged(_ => updateText()); Template.BindValueChanged(_ => updateText()); - beatmap.BindValueChanged(b => updateText()); + beatmap.BindValueChanged(_ => updateText()); updateText(); } From 8804769da1b44de957c09bdd911447a332969fe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 18 Oct 2024 08:51:01 +0200 Subject: [PATCH 605/924] Use better exception messaging --- osu.Game/Skinning/Components/BeatmapAttributeText.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index 77e415b995..91ca7b8903 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -127,7 +127,7 @@ namespace osu.Game.Skinning.Components return BeatmapsetsStrings.ShowStatsBpm; default: - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(attribute), attribute, $@"Unrecognised {nameof(BeatmapAttribute)}"); } } @@ -175,7 +175,7 @@ namespace osu.Game.Skinning.Components return beatmap.Value.BeatmapInfo.StarRating.ToLocalisableString(@"F2"); default: - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(attribute), attribute, $@"Unrecognised {nameof(BeatmapAttribute)}"); } } From ca2bd640b4600944f28c550ab53b5ea087d824fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Oct 2024 15:23:59 +0900 Subject: [PATCH 606/924] Update all dependencies (except realm, nunit, moq and deepclone) --- ...u.Game.Rulesets.EmptyFreeform.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- ....Game.Rulesets.EmptyScrolling.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- .../osu.Game.Benchmarks.csproj | 2 +- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- .../osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- .../osu.Game.Tournament.Tests.csproj | 2 +- osu.Game/osu.Game.csproj | 22 +++++++++---------- 12 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj index 7d43eb2b05..f77cda1533 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj @@ -9,7 +9,7 @@ false - + diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 7dc8a1336b..47cabaddb1 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -9,7 +9,7 @@ false - + diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj index 9c4c8217f0..a7d62291d0 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj @@ -9,7 +9,7 @@ false - + diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 7dc8a1336b..47cabaddb1 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -9,7 +9,7 @@ false - + diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj index af84ee47f1..9764c71493 100644 --- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj +++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj @@ -7,7 +7,7 @@ - + diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 619081c754..b434d6aaf9 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -1,7 +1,7 @@  - + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index eee06acdb8..e7abd47881 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -1,7 +1,7 @@  - + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index ea54c8d313..5ea231e606 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -1,7 +1,7 @@  - + diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index a2420fc679..2170009ae8 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -1,7 +1,7 @@  - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index c0bbdfb4ed..28a1d4d021 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -1,8 +1,8 @@  + - diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index 8f1d7114b1..04683cd83b 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -4,7 +4,7 @@ osu.Game.Tournament.Tests.TournamentTestRunner - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index f982b11ad5..10c72ee2f1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -18,16 +18,16 @@ - + - + - - - - - - + + + + + + @@ -37,11 +37,11 @@ - + - + - + From 7ca5f91c15ea6845ca658697261ea95227359dd4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Oct 2024 15:26:17 +0900 Subject: [PATCH 607/924] Update signalr exceptions in line with deprecated `ctor` --- osu.Game/Online/Multiplayer/InvalidPasswordException.cs | 9 --------- .../Online/Multiplayer/InvalidStateChangeException.cs | 6 ------ osu.Game/Online/Multiplayer/InvalidStateException.cs | 6 ------ osu.Game/Online/Multiplayer/NotHostException.cs | 6 ------ osu.Game/Online/Multiplayer/NotJoinedRoomException.cs | 6 ------ osu.Game/Online/Multiplayer/UserBlockedException.cs | 6 ------ osu.Game/Online/Multiplayer/UserBlocksPMsException.cs | 6 ------ 7 files changed, 45 deletions(-) diff --git a/osu.Game/Online/Multiplayer/InvalidPasswordException.cs b/osu.Game/Online/Multiplayer/InvalidPasswordException.cs index d3da8f491b..b76a1cc05d 100644 --- a/osu.Game/Online/Multiplayer/InvalidPasswordException.cs +++ b/osu.Game/Online/Multiplayer/InvalidPasswordException.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Runtime.Serialization; using Microsoft.AspNetCore.SignalR; namespace osu.Game.Online.Multiplayer @@ -10,13 +9,5 @@ namespace osu.Game.Online.Multiplayer [Serializable] public class InvalidPasswordException : HubException { - public InvalidPasswordException() - { - } - - protected InvalidPasswordException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } } } diff --git a/osu.Game/Online/Multiplayer/InvalidStateChangeException.cs b/osu.Game/Online/Multiplayer/InvalidStateChangeException.cs index 4c793dba68..2bae31196a 100644 --- a/osu.Game/Online/Multiplayer/InvalidStateChangeException.cs +++ b/osu.Game/Online/Multiplayer/InvalidStateChangeException.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Runtime.Serialization; using Microsoft.AspNetCore.SignalR; namespace osu.Game.Online.Multiplayer @@ -14,10 +13,5 @@ namespace osu.Game.Online.Multiplayer : base($"Cannot change from {oldState} to {newState}") { } - - protected InvalidStateChangeException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } } } diff --git a/osu.Game/Online/Multiplayer/InvalidStateException.cs b/osu.Game/Online/Multiplayer/InvalidStateException.cs index 27b111a781..c9705e9e53 100644 --- a/osu.Game/Online/Multiplayer/InvalidStateException.cs +++ b/osu.Game/Online/Multiplayer/InvalidStateException.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Runtime.Serialization; using Microsoft.AspNetCore.SignalR; namespace osu.Game.Online.Multiplayer @@ -14,10 +13,5 @@ namespace osu.Game.Online.Multiplayer : base(message) { } - - protected InvalidStateException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } } } diff --git a/osu.Game/Online/Multiplayer/NotHostException.cs b/osu.Game/Online/Multiplayer/NotHostException.cs index cd43b13e52..f4fd217c87 100644 --- a/osu.Game/Online/Multiplayer/NotHostException.cs +++ b/osu.Game/Online/Multiplayer/NotHostException.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Runtime.Serialization; using Microsoft.AspNetCore.SignalR; namespace osu.Game.Online.Multiplayer @@ -14,10 +13,5 @@ namespace osu.Game.Online.Multiplayer : base("User is attempting to perform a host level operation while not the host") { } - - protected NotHostException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } } } diff --git a/osu.Game/Online/Multiplayer/NotJoinedRoomException.cs b/osu.Game/Online/Multiplayer/NotJoinedRoomException.cs index 0a96406c16..72773e28db 100644 --- a/osu.Game/Online/Multiplayer/NotJoinedRoomException.cs +++ b/osu.Game/Online/Multiplayer/NotJoinedRoomException.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Runtime.Serialization; using Microsoft.AspNetCore.SignalR; namespace osu.Game.Online.Multiplayer @@ -14,10 +13,5 @@ namespace osu.Game.Online.Multiplayer : base("This user has not yet joined a multiplayer room.") { } - - protected NotJoinedRoomException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } } } diff --git a/osu.Game/Online/Multiplayer/UserBlockedException.cs b/osu.Game/Online/Multiplayer/UserBlockedException.cs index e964b13c75..58e86d9f32 100644 --- a/osu.Game/Online/Multiplayer/UserBlockedException.cs +++ b/osu.Game/Online/Multiplayer/UserBlockedException.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Runtime.Serialization; using Microsoft.AspNetCore.SignalR; namespace osu.Game.Online.Multiplayer @@ -16,10 +15,5 @@ namespace osu.Game.Online.Multiplayer : base(MESSAGE) { } - - protected UserBlockedException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } } } diff --git a/osu.Game/Online/Multiplayer/UserBlocksPMsException.cs b/osu.Game/Online/Multiplayer/UserBlocksPMsException.cs index 14ed6fc212..0ea583ae2c 100644 --- a/osu.Game/Online/Multiplayer/UserBlocksPMsException.cs +++ b/osu.Game/Online/Multiplayer/UserBlocksPMsException.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Runtime.Serialization; using Microsoft.AspNetCore.SignalR; namespace osu.Game.Online.Multiplayer @@ -16,10 +15,5 @@ namespace osu.Game.Online.Multiplayer : base(MESSAGE) { } - - protected UserBlocksPMsException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } } } From 8b4565b3d904389f02d86b328e7160af59b41248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 18 Oct 2024 09:42:08 +0200 Subject: [PATCH 608/924] Silence nullref inspection in test --- osu.Game.Tests/Database/BeatmapImporterTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 0eac70f9c8..38746f2567 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -716,7 +716,7 @@ namespace osu.Game.Tests.Database { foreach (var entry in zip.Entries.ToArray()) { - if (entry.Key.EndsWith(".osu", StringComparison.InvariantCulture)) + if (entry.Key!.EndsWith(".osu", StringComparison.InvariantCulture)) zip.RemoveEntry(entry); } From 2de5e3392ef8928ca57d4416069a453ade1673ae Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 18 Oct 2024 17:16:42 +0900 Subject: [PATCH 609/924] Only add as many values as are replaced --- osu.Game/Skinning/Components/BeatmapAttributeText.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index 91ca7b8903..d8c864c8d8 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -76,8 +76,13 @@ namespace osu.Game.Skinning.Components foreach (var type in Enum.GetValues()) { - numberedTemplate = numberedTemplate.Replace($"{{{{{type}}}}}", $"{{{values.Count}}}"); - values.Add(getValueString(type)); + string replaced = numberedTemplate.Replace($@"{{{{{type}}}}}", $@"{{{values.Count}}}"); + + if (numberedTemplate != replaced) + { + numberedTemplate = replaced; + values.Add(getValueString(type)); + } } text.Text = LocalisableString.Format(numberedTemplate, values.ToArray()); From e27aa0c761408cc16f2a0fe1c70e74bfecd85a89 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 18 Oct 2024 17:17:57 +0900 Subject: [PATCH 610/924] Return empty strings rather than erroring Preventing malicious actors from permanently destroying games via skins. --- osu.Game/Skinning/Components/BeatmapAttributeText.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index d8c864c8d8..4ecf5acea7 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -132,7 +132,7 @@ namespace osu.Game.Skinning.Components return BeatmapsetsStrings.ShowStatsBpm; default: - throw new ArgumentOutOfRangeException(nameof(attribute), attribute, $@"Unrecognised {nameof(BeatmapAttribute)}"); + return string.Empty; } } @@ -180,7 +180,7 @@ namespace osu.Game.Skinning.Components return beatmap.Value.BeatmapInfo.StarRating.ToLocalisableString(@"F2"); default: - throw new ArgumentOutOfRangeException(nameof(attribute), attribute, $@"Unrecognised {nameof(BeatmapAttribute)}"); + return string.Empty; } } From bb4f3c71e033fc71d9e769af7eaba6015c4e25df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 18 Oct 2024 11:39:52 +0200 Subject: [PATCH 611/924] Add localisation support for "use relative size" setting label --- .../Localisation/SkinComponents/SkinnableComponentStrings.cs | 5 +++++ osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs | 3 ++- osu.Game/Screens/Play/HUD/ArgonSongProgress.cs | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/SkinComponents/SkinnableComponentStrings.cs b/osu.Game/Localisation/SkinComponents/SkinnableComponentStrings.cs index 33fda23cb0..b21446e18a 100644 --- a/osu.Game/Localisation/SkinComponents/SkinnableComponentStrings.cs +++ b/osu.Game/Localisation/SkinComponents/SkinnableComponentStrings.cs @@ -79,6 +79,11 @@ namespace osu.Game.Localisation.SkinComponents /// public static LocalisableString TextColourDescription => new TranslatableString(getKey(@"text_colour_description"), @"The colour of the text."); + /// + /// "Use relative size" + /// + public static LocalisableString UseRelativeSize => new TranslatableString(getKey(@"use_relative_size"), @"Use relative size"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 71996718d9..44f021f93e 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -13,6 +13,7 @@ using osu.Framework.Layout; using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Configuration; +using osu.Game.Localisation.SkinComponents; using osu.Game.Rulesets.Judgements; using osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts; using osu.Game.Skinning; @@ -33,7 +34,7 @@ namespace osu.Game.Screens.Play.HUD Precision = 1 }; - [SettingSource("Use relative size")] + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.UseRelativeSize))] public BindableBool UseRelativeSize { get; } = new BindableBool(true); private ArgonHealthDisplayBar mainBar = null!; diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs index 4f8ee8b374..8dc5d60352 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs @@ -30,7 +30,7 @@ namespace osu.Game.Screens.Play.HUD [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowTime), nameof(SongProgressStrings.ShowTimeDescription))] public Bindable ShowTime { get; } = new BindableBool(true); - [SettingSource("Use relative size")] + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.UseRelativeSize))] public BindableBool UseRelativeSize { get; } = new BindableBool(true); [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour), nameof(SkinnableComponentStrings.ColourDescription))] From 083644b7139eb661c7a1464b8f2cf06fe4824502 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 18 Oct 2024 18:40:25 +0900 Subject: [PATCH 612/924] Refactor a bit --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index b89a602a6e..a9b7867126 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -590,20 +590,18 @@ namespace osu.Game.Overlays.Mods return true; } - //matchingMod ensures that if an exact mod acronym is typed, it will be selected when Select is pressed (as opposed to being prioritized in arbitrary column order) - ModState? firstMod = columnFlow.Columns.OfType().FirstOrDefault(m => m.IsPresent)?.AvailableMods.FirstOrDefault(x => x.Visible); - ModState? matchingMod = columnFlow.Columns.OfType().SelectMany(m => m.AvailableMods).FirstOrDefault(x => x.Mod.Acronym.Equals(SearchTerm, StringComparison.OrdinalIgnoreCase) | x.Mod.Name.Equals(SearchTerm, StringComparison.OrdinalIgnoreCase)); + IEnumerable visibleMods = columnFlow.Columns.OfType().Where(c => c.IsPresent).SelectMany(c => c.AvailableMods.Where(m => m.Visible)); + + // Search for an exact acronym or name match, or otherwise default to the first visible mod. + ModState? matchingMod = + visibleMods.FirstOrDefault(m => m.Mod.Acronym.Equals(SearchTerm, StringComparison.OrdinalIgnoreCase) || m.Mod.Name.Equals(SearchTerm, StringComparison.OrdinalIgnoreCase)) + ?? visibleMods.FirstOrDefault(); if (matchingMod is not null) { matchingMod.Active.Value = !matchingMod.Active.Value; SearchTextBox.SelectAll(); } - else if (firstMod is not null) - { - firstMod.Active.Value = !firstMod.Active.Value; - SearchTextBox.SelectAll(); - } return true; } From 47f10693c49ed47c21912ff6172ce6d092052b2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 18 Oct 2024 11:43:03 +0200 Subject: [PATCH 613/924] Add relative size toggle to `DefaultSongProgress` too --- osu.Game/Screens/Play/HUD/DefaultSongProgress.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index 4e41901ee3..672017750d 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -37,6 +37,9 @@ namespace osu.Game.Screens.Play.HUD [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowTime), nameof(SongProgressStrings.ShowTimeDescription))] public Bindable ShowTime { get; } = new BindableBool(true); + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.UseRelativeSize))] + public BindableBool UseRelativeSize { get; } = new BindableBool(true); + [SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour), nameof(SkinnableComponentStrings.ColourDescription))] public BindableColour4 AccentColour { get; } = new BindableColour4(Colour4.White); @@ -83,6 +86,11 @@ namespace osu.Game.Screens.Play.HUD private void load(OsuColour colours) { graph.FillColour = bar.FillColour = colours.BlueLighter; + + // see comment in ArgonHealthDisplay.cs regarding RelativeSizeAxes + float previousWidth = Width; + UseRelativeSize.BindValueChanged(v => RelativeSizeAxes = v.NewValue ? Axes.X : Axes.None, true); + Width = previousWidth; } protected override void LoadComplete() From 1cc63096564b24175e162f51665e53e5e1c0147e Mon Sep 17 00:00:00 2001 From: TaterToes Date: Fri, 18 Oct 2024 07:21:05 -0400 Subject: [PATCH 614/924] attempt to fix formatting --- osu.Game/Screens/Edit/Editor.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b99d24dbe4..f40c357f9c 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1100,6 +1100,7 @@ namespace osu.Game.Screens.Edit { // Gives margin to seek back after last control point double seekMargin = 0; + if (clock.IsRunning) { IAdjustableClock adjustableClock = clock; From 05e2f6db8ef6a947898068a2aa064acd7167fe40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 18 Oct 2024 13:23:37 +0200 Subject: [PATCH 615/924] Add preselection indicator for better visibility what will be selected --- osu.Game/Overlays/Mods/ModPanel.cs | 16 +++++++++ osu.Game/Overlays/Mods/ModSelectOverlay.cs | 38 ++++++++++++++++++---- osu.Game/Overlays/Mods/ModState.cs | 2 ++ 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index 9f87a704c0..489e609639 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -4,8 +4,10 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; @@ -58,6 +60,20 @@ namespace osu.Game.Overlays.Mods modState.ValidForSelection.BindValueChanged(_ => updateFilterState()); modState.MatchingTextFilter.BindValueChanged(_ => updateFilterState(), true); + modState.Preselected.BindValueChanged(b => + { + if (b.NewValue) + { + Content.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = AccentColour.Opacity(0.5f), + Radius = 10, + }; + } + else + Content.EdgeEffect = default; + }, true); } protected override void Select() diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index a9b7867126..ed73340eeb 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -243,6 +243,9 @@ namespace osu.Game.Overlays.Mods { foreach (var column in columnFlow.Columns) column.SearchTerm = query.NewValue; + + if (SearchTextBox.HasFocus) + preselectMod(); }, true); // Start scrolling from the end, to give the user a sense that @@ -254,6 +257,26 @@ namespace osu.Game.Overlays.Mods }); } + private void preselectMod() + { + var visibleMods = columnFlow.Columns.OfType().Where(c => c.IsPresent).SelectMany(c => c.AvailableMods.Where(m => m.Visible)); + + // Search for an exact acronym or name match, or otherwise default to the first visible mod. + ModState? matchingMod = + visibleMods.FirstOrDefault(m => m.Mod.Acronym.Equals(SearchTerm, StringComparison.OrdinalIgnoreCase) || m.Mod.Name.Equals(SearchTerm, StringComparison.OrdinalIgnoreCase)) + ?? visibleMods.FirstOrDefault(); + var preselectedMod = matchingMod; + + foreach (var mod in AllAvailableMods) + mod.Preselected.Value = mod == preselectedMod && SearchTextBox.Current.Value.Length > 0; + } + + private void clearPreselection() + { + foreach (var mod in AllAvailableMods) + mod.Preselected.Value = false; + } + public new ModSelectFooterContent? DisplayedFooterContent => base.DisplayedFooterContent as ModSelectFooterContent; public override VisibilityContainer CreateFooterContent() => new ModSelectFooterContent(this) @@ -383,7 +406,7 @@ namespace osu.Game.Overlays.Mods { columnScroll.FadeColour(OsuColour.Gray(0.5f), 400, Easing.OutQuint); SearchTextBox.FadeColour(OsuColour.Gray(0.5f), 400, Easing.OutQuint); - SearchTextBox.KillFocus(); + setTextBoxFocus(false); } else { @@ -590,12 +613,7 @@ namespace osu.Game.Overlays.Mods return true; } - IEnumerable visibleMods = columnFlow.Columns.OfType().Where(c => c.IsPresent).SelectMany(c => c.AvailableMods.Where(m => m.Visible)); - - // Search for an exact acronym or name match, or otherwise default to the first visible mod. - ModState? matchingMod = - visibleMods.FirstOrDefault(m => m.Mod.Acronym.Equals(SearchTerm, StringComparison.OrdinalIgnoreCase) || m.Mod.Name.Equals(SearchTerm, StringComparison.OrdinalIgnoreCase)) - ?? visibleMods.FirstOrDefault(); + var matchingMod = AllAvailableMods.SingleOrDefault(m => m.Preselected.Value); if (matchingMod is not null) { @@ -653,9 +671,15 @@ namespace osu.Game.Overlays.Mods private void setTextBoxFocus(bool focus) { if (focus) + { SearchTextBox.TakeFocus(); + preselectMod(); + } else + { SearchTextBox.KillFocus(); + clearPreselection(); + } } #endregion diff --git a/osu.Game/Overlays/Mods/ModState.cs b/osu.Game/Overlays/Mods/ModState.cs index 7a5bc0f3ae..48fde2fc44 100644 --- a/osu.Game/Overlays/Mods/ModState.cs +++ b/osu.Game/Overlays/Mods/ModState.cs @@ -22,6 +22,8 @@ namespace osu.Game.Overlays.Mods /// public BindableBool Active { get; } = new BindableBool(); + public BindableBool Preselected { get; } = new BindableBool(); + /// /// Whether the mod requires further customisation. /// This flag is read by the to determine if the customisation panel should be opened after a mod change From df90b726b9005f7fb645c93f310f47d9417a2601 Mon Sep 17 00:00:00 2001 From: Wezwery Date: Sat, 19 Oct 2024 03:08:08 +0300 Subject: [PATCH 616/924] Add highlight to combo and accuracy when max. --- .../BeatmapSet/Scores/TopScoreStatisticsSection.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 17704f63ee..1ecc3d53fa 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; @@ -96,10 +97,17 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } [BackgroundDependencyLoader] - private void load() + private void load(OsuColour colours) { if (score != null) + { totalScoreColumn.Current = scoreManager.GetBindableTotalScoreString(score); + + if (score.Accuracy == 1.0) accuracyColumn.TextColour = colours.GreenLight; +#pragma warning disable CS0618 + if (score.MaxCombo == score.BeatmapInfo!.MaxCombo) maxComboColumn.TextColour = colours.GreenLight; +#pragma warning restore CS0618 + } } private ScoreInfo score; @@ -227,6 +235,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { set => text.Text = value; } + public ColourInfo TextColour + { + set => text.Colour = value; + } public Drawable Drawable { From 47936c7b02e784d97951418a585b9a4dade751a5 Mon Sep 17 00:00:00 2001 From: Wezwery Date: Sat, 19 Oct 2024 13:41:36 +0300 Subject: [PATCH 617/924] Add blank line to TopScoreStatisticsSection.cs:238 --- osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 1ecc3d53fa..f1eed81e56 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -235,6 +235,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { set => text.Text = value; } + public ColourInfo TextColour { set => text.Colour = value; From 6d4cb608ab7fc2350a2a6a1a2a31267646243279 Mon Sep 17 00:00:00 2001 From: finadoggie <75299710+Finadoggie@users.noreply.github.com> Date: Sat, 19 Oct 2024 05:43:10 -0700 Subject: [PATCH 618/924] Revert "use LargeTickHit instead of LargeTickMiss" This reverts commit 1337b7eb41d8a3e225bf7e8e1d0b9138dca88927. --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index c5c3dbf418..b77afc173d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -52,8 +52,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty else { countSliderEndsDropped = osuAttributes.SliderCount - score.Statistics.GetValueOrDefault(HitResult.SliderTailHit); - // Note: HitResult.LargeTickMiss is not stored within the API or in score dumps. Do not use it! - countLargeTickMiss = score.MaximumStatistics.GetValueOrDefault(HitResult.LargeTickHit) - score.Statistics.GetValueOrDefault(HitResult.LargeTickHit); + countLargeTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss); effectiveMissCount = countMiss; } From c03017438e3c78fb0e13cdf28fb2fb426b13f363 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 19 Oct 2024 23:57:05 +0200 Subject: [PATCH 619/924] Use FinishTransforms to init visual state --- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index ded7c66058..48178b2e19 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -78,9 +78,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { base.LoadComplete(); - samplesVisible.BindValueChanged(visible => this.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint)); - this.FadeTo(samplesVisible.Value ? 1 : 0); - if (timelineBlueprintContainer != null) contracted.BindTo(timelineBlueprintContainer.SamplePointContracted); @@ -99,6 +96,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline LabelContainer.CornerRadius = 8; } }, true); + + samplesVisible.BindValueChanged(visible => this.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint), true); + FinishTransforms(); } protected override void Dispose(bool isDisposing) From f137fed04e16b6a1829073b673571e6d6e475376 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 19 Oct 2024 23:57:41 +0200 Subject: [PATCH 620/924] Update sample point contracted state each frame --- .../Timeline/TimelineBlueprintContainer.cs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index b50739c80f..fec15517a4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -15,7 +15,6 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; -using osu.Framework.Layout; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; @@ -38,8 +37,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private bool hitObjectDragged; - private readonly LayoutValue samplePointContractedStateCache = new LayoutValue(Invalidation.DrawSize); - /// /// Positional input must be received outside the container's bounds, /// in order to handle timeline blueprints which are stacked offscreen. @@ -54,8 +51,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Origin = Anchor.Centre; Height = 0.6f; - - AddLayout(samplePointContractedStateCache); } [BackgroundDependencyLoader] @@ -123,10 +118,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Composer.Playfield.FutureLifetimeExtension = timeline.VisibleRange / 2; } - updateSamplePointContractedState(); - base.Update(); + updateSamplePointContractedState(); updateStacking(); } @@ -134,8 +128,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateSamplePointContractedState() { - if (samplePointContractedStateCache.IsValid) - return; + // because only blueprints of objects which are alive (via pooling) are displayed in the timeline, it's feasible to do this every-update. const double minimum_gap = 28; @@ -157,7 +150,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline double smallestAbsoluteGap = ((TimelineSelectionBlueprintContainer)SelectionBlueprints).ContentRelativeToAbsoluteFactor.X * smallestTimeGap; SamplePointContracted.Value = smallestAbsoluteGap < minimum_gap; - samplePointContractedStateCache.Validate(); } private readonly Stack currentConcurrentObjects = new Stack(); From 22aef90cb7b1edaff97c86ec640ce49b750c6b1f Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 20 Oct 2024 00:07:40 +0200 Subject: [PATCH 621/924] dont contract samples for stacked objects --- .../Components/Timeline/TimelineBlueprintContainer.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index fec15517a4..c8448b745e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -144,7 +144,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (hitObject is IHasRepeats hasRepeats) smallestTimeGap = Math.Min(smallestTimeGap, hasRepeats.Duration / hasRepeats.SpanCount() / 2); - smallestTimeGap = Math.Min(smallestTimeGap, lastTime - hitObject.GetEndTime()); + double gap = lastTime - hitObject.GetEndTime(); + + // If the gap is less than 1ms, we can assume that the objects are stacked on top of each other + // Contracting doesn't make sense in this case + if (gap > 1 && gap < smallestTimeGap) + smallestTimeGap = gap; + lastTime = hitObject.StartTime; } From 31e08536413b37a547cd5f7d098c4b53eee16ebd Mon Sep 17 00:00:00 2001 From: finadoggie <75299710+Finadoggie@users.noreply.github.com> Date: Sun, 20 Oct 2024 21:39:15 -0700 Subject: [PATCH 622/924] add large tick misses back into effectivemisscount --- .../Difficulty/OsuPerformanceCalculator.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index b77afc173d..778e80c5c4 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty { countSliderEndsDropped = osuAttributes.SliderCount - score.Statistics.GetValueOrDefault(HitResult.SliderTailHit); countLargeTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss); - effectiveMissCount = countMiss; + effectiveMissCount = calculateEffectiveLazerMissCount(osuAttributes); } double multiplier = PERFORMANCE_BASE_MULTIPLIER; @@ -274,6 +274,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty return Math.Max(countMiss, comboBasedMissCount); } + private double calculateEffectiveLazerMissCount(OsuDifficultyAttributes attributes) + { + // Cap LargeTickMisses to avoid buzz sliders giving high miss counts + // Uses very similar formula to calculateEffectiveMissCount(), but utilizes osu!lazer's extra data + double comboBasedMissCount = 0.0; + + if (attributes.SliderCount > 0) + { + comboBasedMissCount = (attributes.MaxCombo - countSliderEndsDropped) / Math.Max(1.0, scoreMaxCombo); + } + + // Clamp miss count to maximum amount of possible breaks + comboBasedMissCount = Math.Min(comboBasedMissCount, countOk + countMeh + countMiss + countLargeTickMiss); + + return Math.Max(countMiss, comboBasedMissCount); + } // Miss penalty assumes that a player will miss on the hardest parts of a map, // so we use the amount of relatively difficult sections to adjust miss penalty From e31e10d616e02fd0570c0a230f29e2fa30ee5089 Mon Sep 17 00:00:00 2001 From: finadoggie <75299710+Finadoggie@users.noreply.github.com> Date: Sun, 20 Oct 2024 21:46:12 -0700 Subject: [PATCH 623/924] merge effectivemisscount functions having two functions was unnecessary --- .../Difficulty/OsuPerformanceCalculator.cs | 57 ++++++++++--------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 778e80c5c4..2c82df8bec 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty { countSliderEndsDropped = osuAttributes.SliderCount - score.Statistics.GetValueOrDefault(HitResult.SliderTailHit); countLargeTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss); - effectiveMissCount = calculateEffectiveLazerMissCount(osuAttributes); + effectiveMissCount = calculateEffectiveMissCount(osuAttributes); } double multiplier = PERFORMANCE_BASE_MULTIPLIER; @@ -259,36 +259,39 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double calculateEffectiveMissCount(OsuDifficultyAttributes attributes) { - // Guess the number of misses + slider breaks from combo - double comboBasedMissCount = 0.0; - - if (attributes.SliderCount > 0) + if (usingClassicSliderAccuracy) { - double fullComboThreshold = attributes.MaxCombo - 0.1 * attributes.SliderCount; - if (scoreMaxCombo < fullComboThreshold) - comboBasedMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo); + // Guess the number of misses + slider breaks from combo + double comboBasedMissCount = 0.0; + + if (attributes.SliderCount > 0) + { + double fullComboThreshold = attributes.MaxCombo - 0.1 * attributes.SliderCount; + if (scoreMaxCombo < fullComboThreshold) + comboBasedMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo); + } + + // Clamp miss count to maximum amount of possible breaks + comboBasedMissCount = Math.Min(comboBasedMissCount, countOk + countMeh + countMiss); + + return Math.Max(countMiss, comboBasedMissCount); } - - // Clamp miss count to maximum amount of possible breaks - comboBasedMissCount = Math.Min(comboBasedMissCount, countOk + countMeh + countMiss); - - return Math.Max(countMiss, comboBasedMissCount); - } - private double calculateEffectiveLazerMissCount(OsuDifficultyAttributes attributes) - { - // Cap LargeTickMisses to avoid buzz sliders giving high miss counts - // Uses very similar formula to calculateEffectiveMissCount(), but utilizes osu!lazer's extra data - double comboBasedMissCount = 0.0; - - if (attributes.SliderCount > 0) + else { - comboBasedMissCount = (attributes.MaxCombo - countSliderEndsDropped) / Math.Max(1.0, scoreMaxCombo); + // Cap LargeTickMisses to avoid buzz sliders giving high miss counts + // Uses very similar formula to calculateEffectiveMissCount(), but utilizes osu!lazer's extra data + double comboBasedMissCount = 0.0; + + if (attributes.SliderCount > 0) + { + comboBasedMissCount = (attributes.MaxCombo - countSliderEndsDropped) / Math.Max(1.0, scoreMaxCombo); + } + + // Clamp miss count to maximum amount of possible breaks + comboBasedMissCount = Math.Min(comboBasedMissCount, countOk + countMeh + countMiss + countLargeTickMiss); + + return Math.Max(countMiss, comboBasedMissCount); } - - // Clamp miss count to maximum amount of possible breaks - comboBasedMissCount = Math.Min(comboBasedMissCount, countOk + countMeh + countMiss + countLargeTickMiss); - - return Math.Max(countMiss, comboBasedMissCount); } // Miss penalty assumes that a player will miss on the hardest parts of a map, From 3778246a55d1a8e58af78127b1cf99960c0bf11c Mon Sep 17 00:00:00 2001 From: finadoggie <75299710+Finadoggie@users.noreply.github.com> Date: Sun, 20 Oct 2024 22:15:15 -0700 Subject: [PATCH 624/924] Addressed code quality concerns --- .../Difficulty/OsuPerformanceCalculator.cs | 48 ++++++------------- 1 file changed, 15 insertions(+), 33 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 2c82df8bec..3ca6f63256 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -46,14 +46,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty countOk = score.Statistics.GetValueOrDefault(HitResult.Ok); countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); + effectiveMissCount = calculateEffectiveMissCount(osuAttributes); - if (usingClassicSliderAccuracy) - effectiveMissCount = calculateEffectiveMissCount(osuAttributes); - else + if (!usingClassicSliderAccuracy) { countSliderEndsDropped = osuAttributes.SliderCount - score.Statistics.GetValueOrDefault(HitResult.SliderTailHit); countLargeTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss); - effectiveMissCount = calculateEffectiveMissCount(osuAttributes); } double multiplier = PERFORMANCE_BASE_MULTIPLIER; @@ -134,7 +132,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (attributes.SliderCount > 0) { - double estimateSliderEndsDropped = usingClassicSliderAccuracy ? Math.Clamp(Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders) : Math.Min(countSliderEndsDropped + countLargeTickMiss, estimateDifficultSliders); + double estimateSliderEndsDropped = usingClassicSliderAccuracy + ? Math.Clamp(Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders) + : Math.Min(countSliderEndsDropped + countLargeTickMiss, estimateDifficultSliders); + double sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + attributes.SliderFactor; aimValue *= sliderNerfFactor; } @@ -259,39 +260,20 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double calculateEffectiveMissCount(OsuDifficultyAttributes attributes) { - if (usingClassicSliderAccuracy) + double comboBasedMissCount = 0.0; + + if (attributes.SliderCount > 0) { // Guess the number of misses + slider breaks from combo - double comboBasedMissCount = 0.0; - - if (attributes.SliderCount > 0) - { - double fullComboThreshold = attributes.MaxCombo - 0.1 * attributes.SliderCount; - if (scoreMaxCombo < fullComboThreshold) - comboBasedMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo); - } - - // Clamp miss count to maximum amount of possible breaks - comboBasedMissCount = Math.Min(comboBasedMissCount, countOk + countMeh + countMiss); - - return Math.Max(countMiss, comboBasedMissCount); + double fullComboThreshold = usingClassicSliderAccuracy ? attributes.MaxCombo - 0.1 * attributes.SliderCount : attributes.MaxCombo - countSliderEndsDropped; + if (scoreMaxCombo < fullComboThreshold) + comboBasedMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo); } - else - { - // Cap LargeTickMisses to avoid buzz sliders giving high miss counts - // Uses very similar formula to calculateEffectiveMissCount(), but utilizes osu!lazer's extra data - double comboBasedMissCount = 0.0; - if (attributes.SliderCount > 0) - { - comboBasedMissCount = (attributes.MaxCombo - countSliderEndsDropped) / Math.Max(1.0, scoreMaxCombo); - } + // Clamp miss count to maximum amount of possible breaks + comboBasedMissCount = Math.Min(comboBasedMissCount, countOk + countMeh + countMiss + countLargeTickMiss); - // Clamp miss count to maximum amount of possible breaks - comboBasedMissCount = Math.Min(comboBasedMissCount, countOk + countMeh + countMiss + countLargeTickMiss); - - return Math.Max(countMiss, comboBasedMissCount); - } + return Math.Max(countMiss, comboBasedMissCount); } // Miss penalty assumes that a player will miss on the hardest parts of a map, From 5907c2a1c497d6d2740dccb78355205b2d01f759 Mon Sep 17 00:00:00 2001 From: finadoggie <75299710+Finadoggie@users.noreply.github.com> Date: Sun, 20 Oct 2024 22:47:02 -0700 Subject: [PATCH 625/924] Only clamp estimated miss count with relevant statistics --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 3ca6f63256..a8114e65a9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -271,7 +271,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty } // Clamp miss count to maximum amount of possible breaks - comboBasedMissCount = Math.Min(comboBasedMissCount, countOk + countMeh + countMiss + countLargeTickMiss); + comboBasedMissCount = usingClassicSliderAccuracy ? Math.Min(comboBasedMissCount, countOk + countMeh + countMiss) : Math.Min(comboBasedMissCount, countLargeTickMiss + countMiss); return Math.Max(countMiss, comboBasedMissCount); } From 98800fea71e35552cb0fa8080c282e78bb4723a3 Mon Sep 17 00:00:00 2001 From: finadoggie <75299710+Finadoggie@users.noreply.github.com> Date: Mon, 21 Oct 2024 00:34:26 -0700 Subject: [PATCH 626/924] Fix variables being used before being assigned slightly miffed by the lack of build errors but oh well --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index a8114e65a9..8ccd9db513 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -46,13 +46,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty countOk = score.Statistics.GetValueOrDefault(HitResult.Ok); countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); - effectiveMissCount = calculateEffectiveMissCount(osuAttributes); - if (!usingClassicSliderAccuracy) { countSliderEndsDropped = osuAttributes.SliderCount - score.Statistics.GetValueOrDefault(HitResult.SliderTailHit); countLargeTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss); } + effectiveMissCount = calculateEffectiveMissCount(osuAttributes); double multiplier = PERFORMANCE_BASE_MULTIPLIER; From fc0ec20db98fdf54683cc3b3f25d179662a530e7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 21 Oct 2024 18:26:38 +0900 Subject: [PATCH 627/924] Change convert-to-ternary warning to hint --- osu.sln.DotSettings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 4a2ef97520..ccd6db354b 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -69,7 +69,7 @@ DO_NOT_SHOW HINT WARNING - WARNING + HINT WARNING WARNING DO_NOT_SHOW From bcb997028e38630e89b66b7693f177ece5f5d00b Mon Sep 17 00:00:00 2001 From: StanR Date: Mon, 21 Oct 2024 14:38:03 +0500 Subject: [PATCH 628/924] Refactor and add comments --- .../Difficulty/OsuPerformanceCalculator.cs | 79 +++++++++++++------ 1 file changed, 54 insertions(+), 25 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 8ccd9db513..af17789b59 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -24,9 +24,20 @@ namespace osu.Game.Rulesets.Osu.Difficulty private int countOk; private int countMeh; private int countMiss; - private int countLargeTickMiss; + + /// + /// Missed slider ticks that includes missed reverse arrows + /// + private int countSliderTickMiss; + + /// + /// Amount of missed slider tails that don't break combo + /// private int countSliderEndsDropped; + /// + /// Estimated total amount of combo breaks + /// private double effectiveMissCount; public OsuPerformanceCalculator() @@ -46,12 +57,36 @@ namespace osu.Game.Rulesets.Osu.Difficulty countOk = score.Statistics.GetValueOrDefault(HitResult.Ok); countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); + + if (osuAttributes.SliderCount > 0) + { + // Consider that full combo is maximum combo minus dropped sliders since missed tails don't contribute to score combo but also don't break it + // In classic scores we can't know the amount of dropped sliders so we use 10% of all sliders on the map + double droppedSliderTailsAmount = usingClassicSliderAccuracy + ? 0.1 * osuAttributes.SliderCount + : countSliderEndsDropped; + + double fullComboThreshold = attributes.MaxCombo - droppedSliderTailsAmount; + + if (scoreMaxCombo < fullComboThreshold) + effectiveMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo); + } + if (!usingClassicSliderAccuracy) { countSliderEndsDropped = osuAttributes.SliderCount - score.Statistics.GetValueOrDefault(HitResult.SliderTailHit); - countLargeTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss); + countSliderTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss); + + // Combine regular misses with tick misses since tick misses break combo as well + effectiveMissCount = Math.Min(effectiveMissCount, countSliderTickMiss + countMiss); } - effectiveMissCount = calculateEffectiveMissCount(osuAttributes); + else + { + // In classic scores there can't be more misses than a sum of all non-perfect judgements + effectiveMissCount = Math.Min(effectiveMissCount, totalImperfectHits); + } + + effectiveMissCount = Math.Max(countMiss, effectiveMissCount); double multiplier = PERFORMANCE_BASE_MULTIPLIER; @@ -131,11 +166,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (attributes.SliderCount > 0) { - double estimateSliderEndsDropped = usingClassicSliderAccuracy - ? Math.Clamp(Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders) - : Math.Min(countSliderEndsDropped + countLargeTickMiss, estimateDifficultSliders); + double estimateImproperlyFollowedDifficultSliders; - double sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + attributes.SliderFactor; + if (usingClassicSliderAccuracy) + { + // When the score is considered classic (regardless if it was made on old client or not) we consider all missing combo to be dropped difficult sliders + int maximumPossibleDroppedSliders = totalImperfectHits; + estimateImproperlyFollowedDifficultSliders = Math.Clamp(Math.Min(maximumPossibleDroppedSliders, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders); + } + else + { + // We add tick misses here since they too mean that the player didn't follow the slider properly + // We however aren't adding misses here because missing slider heads has a harsh penalty by itself and doesn't mean that the rest of the slider wasn't followed properly + estimateImproperlyFollowedDifficultSliders = Math.Min(countSliderEndsDropped + countSliderTickMiss, estimateDifficultSliders); + } + + double sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - estimateImproperlyFollowedDifficultSliders / estimateDifficultSliders, 3) + attributes.SliderFactor; aimValue *= sliderNerfFactor; } @@ -257,29 +303,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty return flashlightValue; } - private double calculateEffectiveMissCount(OsuDifficultyAttributes attributes) - { - double comboBasedMissCount = 0.0; - - if (attributes.SliderCount > 0) - { - // Guess the number of misses + slider breaks from combo - double fullComboThreshold = usingClassicSliderAccuracy ? attributes.MaxCombo - 0.1 * attributes.SliderCount : attributes.MaxCombo - countSliderEndsDropped; - if (scoreMaxCombo < fullComboThreshold) - comboBasedMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo); - } - - // Clamp miss count to maximum amount of possible breaks - comboBasedMissCount = usingClassicSliderAccuracy ? Math.Min(comboBasedMissCount, countOk + countMeh + countMiss) : Math.Min(comboBasedMissCount, countLargeTickMiss + countMiss); - - return Math.Max(countMiss, comboBasedMissCount); - } - // Miss penalty assumes that a player will miss on the hardest parts of a map, // so we use the amount of relatively difficult sections to adjust miss penalty // to make it more punishing on maps with lower amount of hard sections. private double calculateMissPenalty(double missCount, double difficultStrainCount) => 0.96 / ((missCount / (4 * Math.Pow(Math.Log(difficultStrainCount), 0.94))) + 1); private double getComboScalingFactor(OsuDifficultyAttributes attributes) => attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(attributes.MaxCombo, 0.8), 1.0); private int totalHits => countGreat + countOk + countMeh + countMiss; + private int totalImperfectHits => countOk + countMeh + countMiss; } } From acf282dddd33ebd342826ac95596c05610966794 Mon Sep 17 00:00:00 2001 From: StanR Date: Mon, 21 Oct 2024 15:06:34 +0500 Subject: [PATCH 629/924] Fix effectiveMissCount being calculated wrong --- .../Difficulty/OsuPerformanceCalculator.cs | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index af17789b59..ddabf866ff 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -26,12 +26,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty private int countMiss; /// - /// Missed slider ticks that includes missed reverse arrows + /// Missed slider ticks that includes missed reverse arrows. Will only be correct on non-classic scores /// private int countSliderTickMiss; /// - /// Amount of missed slider tails that don't break combo + /// Amount of missed slider tails that don't break combo. Will only be correct on non-classic scores /// private int countSliderEndsDropped; @@ -57,33 +57,33 @@ namespace osu.Game.Rulesets.Osu.Difficulty countOk = score.Statistics.GetValueOrDefault(HitResult.Ok); countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); + countSliderEndsDropped = osuAttributes.SliderCount - score.Statistics.GetValueOrDefault(HitResult.SliderTailHit); + countSliderTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss); if (osuAttributes.SliderCount > 0) { - // Consider that full combo is maximum combo minus dropped sliders since missed tails don't contribute to score combo but also don't break it - // In classic scores we can't know the amount of dropped sliders so we use 10% of all sliders on the map - double droppedSliderTailsAmount = usingClassicSliderAccuracy - ? 0.1 * osuAttributes.SliderCount - : countSliderEndsDropped; + if (usingClassicSliderAccuracy) + { + // Consider that full combo is maximum combo minus dropped slider tails since they don't contribute to combo but also don't break it + // In classic scores we can't know the amount of dropped sliders so we estimate to 10% of all sliders on the map + double fullComboThreshold = attributes.MaxCombo - 0.1 * osuAttributes.SliderCount; - double fullComboThreshold = attributes.MaxCombo - droppedSliderTailsAmount; + if (scoreMaxCombo < fullComboThreshold) + effectiveMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo); - if (scoreMaxCombo < fullComboThreshold) - effectiveMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo); - } + // In classic scores there can't be more misses than a sum of all non-perfect judgements + effectiveMissCount = Math.Min(effectiveMissCount, totalImperfectHits); + } + else + { + double fullComboThreshold = attributes.MaxCombo - countSliderEndsDropped; - if (!usingClassicSliderAccuracy) - { - countSliderEndsDropped = osuAttributes.SliderCount - score.Statistics.GetValueOrDefault(HitResult.SliderTailHit); - countSliderTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss); + if (scoreMaxCombo < fullComboThreshold) + effectiveMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo); - // Combine regular misses with tick misses since tick misses break combo as well - effectiveMissCount = Math.Min(effectiveMissCount, countSliderTickMiss + countMiss); - } - else - { - // In classic scores there can't be more misses than a sum of all non-perfect judgements - effectiveMissCount = Math.Min(effectiveMissCount, totalImperfectHits); + // Combine regular misses with tick misses since tick misses break combo as well + effectiveMissCount = Math.Min(effectiveMissCount, countSliderTickMiss + countMiss); + } } effectiveMissCount = Math.Max(countMiss, effectiveMissCount); From 59e9ed7bac4ae111e71f2fff4c601ffaefe9a7cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 21 Oct 2024 12:44:01 +0200 Subject: [PATCH 630/924] Add test coverage --- .../Visual/Online/TestSceneScoresContainer.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index c17d746232..ad0c5f9247 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -147,6 +147,18 @@ namespace osu.Game.Tests.Visual.Online AddUntilStep("wait for scores displayed", () => scoresContainer.ChildrenOfType().Any()); AddAssert("best score displayed", () => scoresContainer.ChildrenOfType().Count() == 2); + AddStep("Load scores with personal best FC", () => + { + var allScores = createScores(); + allScores.UserScore = createUserBest(); + allScores.UserScore.Score.Accuracy = 1; + scoresContainer.Beatmap.Value.MaxCombo = allScores.UserScore.Score.MaxCombo = 1337; + scoresContainer.Scores = allScores; + }); + + AddUntilStep("wait for scores displayed", () => scoresContainer.ChildrenOfType().Any()); + AddAssert("best score displayed", () => scoresContainer.ChildrenOfType().Count() == 2); + AddStep("Load scores with personal best (null position)", () => { var allScores = createScores(); From a90ad6349358a4eecabaf42721f6340f60b6dd81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 21 Oct 2024 12:47:41 +0200 Subject: [PATCH 631/924] Change property type Nobody is supposed to be using `ColourInfo` directly, really. --- .../Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index f1eed81e56..9250a82902 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -236,7 +236,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores set => text.Text = value; } - public ColourInfo TextColour + public Colour4 TextColour { set => text.Colour = value; } From e89a4561ab85aa2c1753f24d0526f7ae19e773bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 21 Oct 2024 13:40:35 +0200 Subject: [PATCH 632/924] Fix playfield skinning layer no longer correctly rotating with the playfield Closes https://github.com/ppy/osu/issues/30353. Regressed in https://github.com/ppy/osu/commit/4a39873e2aac3a6fa71a3be06407d9afb7df8922. --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 18 +++++++++++------- osu.Game/Screens/Play/HUDOverlay.cs | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index a28b2716cb..3ad173893b 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -65,18 +65,16 @@ namespace osu.Game.Rulesets.UI /// public override Playfield Playfield => playfield.Value; + private PlayfieldAdjustmentContainer playfieldAdjustmentContainer; + + public override PlayfieldAdjustmentContainer PlayfieldAdjustmentContainer => playfieldAdjustmentContainer; + public override Container Overlays { get; } = new Container { RelativeSizeAxes = Axes.Both }; public override IAdjustableAudioComponent Audio => audioContainer; private readonly AudioContainer audioContainer = new AudioContainer { RelativeSizeAxes = Axes.Both }; - /// - /// A container which encapsulates the and provides any adjustments to - /// ensure correct scale and position. - /// - public virtual PlayfieldAdjustmentContainer PlayfieldAdjustmentContainer { get; private set; } - public override Container FrameStableComponents { get; } = new Container { RelativeSizeAxes = Axes.Both }; public override IFrameStableClock FrameStableClock => frameStabilityContainer; @@ -197,7 +195,7 @@ namespace osu.Game.Rulesets.UI audioContainer.WithChild(KeyBindingInputManager .WithChildren(new Drawable[] { - PlayfieldAdjustmentContainer = CreatePlayfieldAdjustmentContainer() + playfieldAdjustmentContainer = CreatePlayfieldAdjustmentContainer() .WithChild(Playfield), Overlays })), @@ -456,6 +454,12 @@ namespace osu.Game.Rulesets.UI /// public abstract Playfield Playfield { get; } + /// + /// A container which encapsulates the and provides any adjustments to + /// ensure correct scale and position. + /// + public abstract PlayfieldAdjustmentContainer PlayfieldAdjustmentContainer { get; } + /// /// Content to be placed above hitobjects. Will be affected by frame stability and adjustments applied to . /// diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index ac1b9ce34f..292f554483 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -252,7 +252,7 @@ namespace osu.Game.Screens.Play PlayfieldSkinLayer.Position = ToLocalSpace(playfieldScreenSpaceDrawQuad.TopLeft); PlayfieldSkinLayer.Width = (ToLocalSpace(playfieldScreenSpaceDrawQuad.TopRight) - ToLocalSpace(playfieldScreenSpaceDrawQuad.TopLeft)).Length; PlayfieldSkinLayer.Height = (ToLocalSpace(playfieldScreenSpaceDrawQuad.BottomLeft) - ToLocalSpace(playfieldScreenSpaceDrawQuad.TopLeft)).Length; - PlayfieldSkinLayer.Rotation = drawableRuleset.Playfield.Rotation; + PlayfieldSkinLayer.Rotation = drawableRuleset.PlayfieldAdjustmentContainer.Rotation; } float? lowestTopScreenSpaceLeft = null; From 0a15eec7de9a9d435f200453af736859f0cda658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 21 Oct 2024 13:52:17 +0200 Subject: [PATCH 633/924] Remove unused using directive --- osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 9250a82902..e8833fa0a3 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -9,7 +9,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; From 1e03bd11a350e4d4af98eb5c12b1c3a1ad412e9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 21 Oct 2024 13:57:34 +0200 Subject: [PATCH 634/924] Fix test compile failures --- osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs | 1 + osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs index d4b69c1be2..07d6d68e82 100644 --- a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs +++ b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs @@ -96,6 +96,7 @@ namespace osu.Game.Tests.NonVisual public override IAdjustableAudioComponent Audio { get; } public override Playfield Playfield { get; } + public override PlayfieldAdjustmentContainer PlayfieldAdjustmentContainer { get; } public override Container Overlays { get; } public override Container FrameStableComponents { get; } public override IFrameStableClock FrameStableClock { get; } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs index e57177498d..2e646f2850 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs @@ -284,6 +284,7 @@ namespace osu.Game.Tests.Visual.Gameplay public override IAdjustableAudioComponent Audio { get; } public override Playfield Playfield { get; } + public override PlayfieldAdjustmentContainer PlayfieldAdjustmentContainer { get; } public override Container Overlays { get; } public override Container FrameStableComponents { get; } public override IFrameStableClock FrameStableClock { get; } From d6497640d9f18c0392e393fc34a37464714a27dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 21 Oct 2024 14:44:09 +0200 Subject: [PATCH 635/924] Add failing test case --- .../Editor/TestSceneEditorPlacement.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorPlacement.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorPlacement.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorPlacement.cs new file mode 100644 index 0000000000..c523652ae1 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorPlacement.cs @@ -0,0 +1,37 @@ +// 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.UserInterface; +using osu.Framework.Testing; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Objects.Drawables; +using osu.Game.Tests.Visual; +using osuTK.Input; + +namespace osu.Game.Rulesets.Taiko.Tests.Editor +{ + public partial class TestSceneEditorPlacement : EditorTestScene + { + protected override Ruleset CreateEditorRuleset() => new TaikoRuleset(); + + [Test] + public void TestPlacementBlueprintDoesNotCauseCrashes() + { + AddStep("clear objects", () => EditorBeatmap.Clear()); + AddStep("add two objects", () => + { + EditorBeatmap.Add(new Hit { StartTime = 1818 }); + EditorBeatmap.Add(new Hit { StartTime = 1584 }); + }); + AddStep("seek back", () => EditorClock.Seek(1584)); + AddStep("choose hit placement tool", () => InputManager.Key(Key.Number2)); + AddStep("hover over first hit", () => InputManager.MoveMouseTo(Editor.ChildrenOfType().ElementAt(1))); + AddStep("hover over second hit", () => InputManager.MoveMouseTo(Editor.ChildrenOfType().ElementAt(0))); + AddStep("right click", () => InputManager.Click(MouseButton.Right)); + AddUntilStep("context menu open", () => Editor.ChildrenOfType().Any(menu => menu.State == MenuState.Open)); + } + } +} From dbc2e78dd94781444ef7ac24c8038b3c5a21ee4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 21 Oct 2024 14:44:53 +0200 Subject: [PATCH 636/924] Fix timeline blueprints sometimes causing crashes due to current placement blueprint becoming unsorted Closes https://github.com/ppy/osu/issues/30324. --- .../Compose/Components/BlueprintContainer.cs | 9 +++++++-- .../Components/EditorBlueprintContainer.cs | 3 +-- .../HitObjectOrderedSelectionContainer.cs | 9 +++++---- .../Timeline/TimelineBlueprintContainer.cs | 19 ++++++++++++++++--- 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 30c1258f93..e12574f7ee 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -32,7 +32,12 @@ namespace osu.Game.Screens.Edit.Compose.Components { protected DragBox DragBox { get; private set; } - public Container> SelectionBlueprints { get; private set; } + public SelectionBlueprintContainer SelectionBlueprints { get; private set; } + + public partial class SelectionBlueprintContainer : Container> + { + public new virtual void ChangeChildDepth(SelectionBlueprint child, float newDepth) => base.ChangeChildDepth(child, newDepth); + } public SelectionHandler SelectionHandler { get; private set; } @@ -95,7 +100,7 @@ namespace osu.Game.Screens.Edit.Compose.Components }); } - protected virtual Container> CreateSelectionBlueprintContainer() => new Container> { RelativeSizeAxes = Axes.Both }; + protected virtual SelectionBlueprintContainer CreateSelectionBlueprintContainer() => new SelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; /// /// Creates a which outlines items and handles movement of selections. diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index 378d378be3..7b046251e0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -9,7 +9,6 @@ using System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Rulesets.Edit; @@ -136,7 +135,7 @@ namespace osu.Game.Screens.Edit.Compose.Components base.ApplySelectionOrder(blueprints) .OrderBy(b => Math.Min(Math.Abs(EditorClock.CurrentTime - b.Item.GetEndTime()), Math.Abs(EditorClock.CurrentTime - b.Item.StartTime))); - protected override Container> CreateSelectionBlueprintContainer() => new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both }; + protected override SelectionBlueprintContainer CreateSelectionBlueprintContainer() => new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both }; protected override SelectionHandler CreateSelectionHandler() => new EditorSelectionHandler(); diff --git a/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs b/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs index 8f54d55d5d..a7f8fd0d4c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -14,7 +13,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// A container for ordered by their start times. /// - public sealed partial class HitObjectOrderedSelectionContainer : Container> + public sealed partial class HitObjectOrderedSelectionContainer : BlueprintContainer.SelectionBlueprintContainer { [Resolved] private EditorBeatmap editorBeatmap { get; set; } = null!; @@ -28,16 +27,18 @@ namespace osu.Game.Screens.Edit.Compose.Components public override void Add(SelectionBlueprint drawable) { - SortInternal(); + Sort(); base.Add(drawable); } public override bool Remove(SelectionBlueprint drawable, bool disposeImmediately) { - SortInternal(); + Sort(); return base.Remove(drawable, disposeImmediately); } + internal void Sort() => SortInternal(); + protected override int Compare(Drawable x, Drawable y) { var xObj = ((SelectionBlueprint)x).Item; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index a6af83d268..a5d58215e8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -91,7 +91,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - protected override Container> CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; + protected override SelectionBlueprintContainer CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; protected override bool OnDragStart(DragStartEvent e) { @@ -287,14 +287,27 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - protected partial class TimelineSelectionBlueprintContainer : Container> + protected partial class TimelineSelectionBlueprintContainer : SelectionBlueprintContainer { - protected override Container> Content { get; } + protected override HitObjectOrderedSelectionContainer Content { get; } public TimelineSelectionBlueprintContainer() { AddInternal(new TimelinePart>(Content = new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both }) { RelativeSizeAxes = Axes.Both }); } + + public override void ChangeChildDepth(SelectionBlueprint child, float newDepth) + { + // timeline blueprint container also contains a blueprint for current placement, if present + // (see `placementChanged()` callback above). + // because the current placement hitobject is generally going to be mutated during the placement, + // it is possible for `Content`'s children to become unsorted when the user moves the placement around, + // which can culminate in a critical failure when attempting to binary-search children here + // using `HitObjectOrderedSelectionContainer`'s custom comparer. + // thus, always force a re-sort of objects before attempting to change child depth to avoid this scenario. + Content.Sort(); + base.ChangeChildDepth(child, newDepth); + } } } } From 9940be818d9c560aebbf526b1d936990d3c86e4b Mon Sep 17 00:00:00 2001 From: CloneWith Date: Mon, 21 Oct 2024 21:09:26 +0800 Subject: [PATCH 637/924] Add hover color back to ClickablePlaceholder --- .../Online/Placeholders/ClickablePlaceholder.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Placeholders/ClickablePlaceholder.cs b/osu.Game/Online/Placeholders/ClickablePlaceholder.cs index 9bef1d4b7a..ee8762fca3 100644 --- a/osu.Game/Online/Placeholders/ClickablePlaceholder.cs +++ b/osu.Game/Online/Placeholders/ClickablePlaceholder.cs @@ -17,19 +17,21 @@ namespace osu.Game.Online.Placeholders public ClickablePlaceholder(LocalisableString actionMessage, IconUsage icon) { + OsuAnimatedButton button; OsuTextFlowContainer textFlow; - AddArbitraryDrawable(new OsuAnimatedButton + AddArbitraryDrawable(button = new OsuAnimatedButton { AutoSizeAxes = Framework.Graphics.Axes.Both, - Child = textFlow = new OsuTextFlowContainer(cp => cp.Font = cp.Font.With(size: TEXT_SIZE)) - { - AutoSizeAxes = Framework.Graphics.Axes.Both, - Margin = new Framework.Graphics.MarginPadding(5) - }, Action = () => Action?.Invoke() }); + button.Add(textFlow = new OsuTextFlowContainer(cp => cp.Font = cp.Font.With(size: TEXT_SIZE)) + { + AutoSizeAxes = Framework.Graphics.Axes.Both, + Margin = new Framework.Graphics.MarginPadding(5) + }); + textFlow.AddIcon(icon, i => { i.Padding = new Framework.Graphics.MarginPadding { Right = 10 }; From cbaee986742fd5dfa011df2b33e2793d3e42bc94 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Oct 2024 01:39:05 +0900 Subject: [PATCH 638/924] Don't delete scores when deleting beatmaps The score model's spec allows for null `BeatmapInfo` so the reasoning of the inline comment is no longer valid. We match based on hash these days. --- osu.Game/Database/RealmAccess.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index a437b3313e..eb7182820b 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -376,10 +376,6 @@ namespace osu.Game.Database { foreach (var beatmap in beatmapSet.Beatmaps) { - // Cascade delete related scores, else they will have a null beatmap against the model's spec. - foreach (var score in beatmap.Scores) - realm.Remove(score); - realm.Remove(beatmap.Metadata); realm.Remove(beatmap); } From 17cd41156798fbee77ce82248e5714c00aed6e49 Mon Sep 17 00:00:00 2001 From: EvT Date: Tue, 22 Oct 2024 01:33:53 +0300 Subject: [PATCH 639/924] Added search box to ChannelGroup Private Message --- .../Overlays/Chat/ChannelList/ChannelList.cs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index e6fe97f3c6..5a143ffa41 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -9,10 +9,12 @@ using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Online.Chat; using osu.Game.Overlays.Chat.Listing; using osu.Game.Resources.Localisation.Web; @@ -39,6 +41,7 @@ namespace osu.Game.Overlays.Chat.ChannelList private ChannelGroup publicChannelGroup = null!; private ChannelGroup privateChannelGroup = null!; private ChannelListItem selector = null!; + private TextBox searchTextBox = null!; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) @@ -65,12 +68,40 @@ namespace osu.Game.Overlays.Chat.ChannelList announceChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitleANNOUNCE.ToUpper()), publicChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePUBLIC.ToUpper()), selector = new ChannelListItem(ChannelListingChannel), + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = 18, Top = 8 }, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = "SEARCH", + Margin = new MarginPadding { Bottom = 5 }, + Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold), + }, + searchTextBox = new OsuTextBox + { + Height = 28, + RelativeSizeAxes = Axes.X, + PlaceholderText = "Search by name...", + FontSize = 14, + } + } + }, privateChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePM.ToUpper()), }, }, }, }; + searchTextBox.OnUpdate += (newText) => + { + privateChannelGroup.FilterChannels(searchTextBox.Text); + }; + selector.OnRequestSelect += chan => OnRequestSelect?.Invoke(chan); } @@ -167,6 +198,23 @@ namespace osu.Game.Overlays.Chat.ChannelList }, }; } + + public void FilterChannels(string searchTerm) + { + foreach (var item in ItemFlow.Children) + { + if (string.IsNullOrEmpty(searchTerm)) + { + item.Show(); + continue; + } + + if (item.Channel.Name.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) + item.Show(); + else + item.Hide(); + } + } } } } From 17702ead0bcde62988b2146cfd8db671b64da09d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 22 Oct 2024 14:20:10 +0900 Subject: [PATCH 640/924] Fix ruleset not being reset correctly in tests --- .../Visual/UserInterface/TestSceneBeatmapAttributeText.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs index 01659a2654..91525e02c1 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.UserInterface public void Setup() => Schedule(() => { SelectedMods.SetDefault(); - Ruleset.SetDefault(); + Ruleset.Value = new OsuRuleset().RulesetInfo; Beatmap.Value = CreateWorkingBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo) { BeatmapInfo = From 13fba9f92e8e7e8129efda00c5f4311b71b6af7b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Oct 2024 17:09:46 +0900 Subject: [PATCH 641/924] Adjust glow slightly --- osu.Game/Overlays/Mods/ModPanel.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index 489e609639..b85904f22b 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; @@ -67,8 +66,9 @@ namespace osu.Game.Overlays.Mods Content.EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, - Colour = AccentColour.Opacity(0.5f), - Radius = 10, + Colour = AccentColour, + Hollow = true, + Radius = 2, }; } else From e1a950e2d393068300874216ce41ff9fb0e72195 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Oct 2024 17:53:34 +0900 Subject: [PATCH 642/924] Fix beatmap selection being lost during update process Broke due to something changing in the way we handle realm things in the carousel. The deselection happens in `updateBeatmapSet` so we need to store / check the original selection before this occurs. Doesn't seem this had test coverage? Probably implies that the overhead of adding a test was very large, so maybe best to leave it that way. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index d9359cfec3..44f91b4df1 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -322,6 +322,11 @@ namespace osu.Game.Screens.Select { try { + // To handle the beatmap update flow, attempt to track selection changes across delete-insert transactions. + // When an update occurs, the previous beatmap set is either soft or hard deleted. + // Check if the current selection was potentially deleted by re-querying its validity. + bool selectedSetMarkedDeleted = SelectedBeatmapSet != null && fetchFromID(SelectedBeatmapSet.ID)?.DeletePending != false; + foreach (var set in setsRequiringRemoval) removeBeatmapSet(set.ID); foreach (var set in setsRequiringUpdate) updateBeatmapSet(set); @@ -331,11 +336,6 @@ namespace osu.Game.Screens.Select // If SelectedBeatmapInfo is non-null, the set should also be non-null. Debug.Assert(SelectedBeatmapSet != null); - // To handle the beatmap update flow, attempt to track selection changes across delete-insert transactions. - // When an update occurs, the previous beatmap set is either soft or hard deleted. - // Check if the current selection was potentially deleted by re-querying its validity. - bool selectedSetMarkedDeleted = fetchFromID(SelectedBeatmapSet.ID)?.DeletePending != false; - if (selectedSetMarkedDeleted && setsRequiringUpdate.Any()) { // If it is no longer valid, make the bold assumption that an updated version will be available in the modified/inserted indices. From 16bc188ba7def10716452e3f8dceb902dca6b0d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Oct 2024 18:08:17 +0900 Subject: [PATCH 643/924] Refactor code to read better (and adjust lenience to match stable) --- osu.Game/Screens/Edit/Editor.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index f40c357f9c..6dd7b9726f 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1098,17 +1098,12 @@ namespace osu.Game.Screens.Edit private void seekControlPoint(int direction) { - // Gives margin to seek back after last control point - double seekMargin = 0; - - if (clock.IsRunning) - { - IAdjustableClock adjustableClock = clock; - seekMargin = 450 * adjustableClock.Rate; - } + // In the case of a backwards seek while playing, it can be hard to jump before a timing point. + // Adding some lenience here makes it more user-friendly. + double seekLenience = clock.IsRunning ? 1000 * ((IAdjustableClock)clock).Rate : 0; - var found = direction < 1 - ? editorBeatmap.ControlPointInfo.AllControlPoints.LastOrDefault(p => p.Time < clock.CurrentTime - seekMargin) + ControlPoint found = direction < 1 + ? editorBeatmap.ControlPointInfo.AllControlPoints.LastOrDefault(p => p.Time < clock.CurrentTime - seekLenience) : editorBeatmap.ControlPointInfo.AllControlPoints.FirstOrDefault(p => p.Time > clock.CurrentTime); if (found != null) From 256d8c6559fb620f66cd9eff1b2563b71c4db3a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Oct 2024 11:27:45 +0200 Subject: [PATCH 644/924] Move search box to the top, remove redundant heading, and use existing search box --- .../Overlays/Chat/ChannelList/ChannelList.cs | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index 5a143ffa41..c674263bd1 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -18,6 +18,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online.Chat; using osu.Game.Overlays.Chat.Listing; using osu.Game.Resources.Localisation.Web; +using osuTK; namespace osu.Game.Overlays.Chat.ChannelList { @@ -65,32 +66,21 @@ namespace osu.Game.Overlays.Chat.ChannelList AutoSizeAxes = Axes.Y, Children = new Drawable[] { - announceChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitleANNOUNCE.ToUpper()), - publicChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePUBLIC.ToUpper()), - selector = new ChannelListItem(ChannelListingChannel), - new FillFlowContainer + new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = 18, Top = 8 }, - Direction = FillDirection.Vertical, - Children = new Drawable[] + Padding = new MarginPadding { Horizontal = 10, Top = 8 }, + Child = searchTextBox = new BasicSearchTextBox { - new OsuSpriteText - { - Text = "SEARCH", - Margin = new MarginPadding { Bottom = 5 }, - Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold), - }, - searchTextBox = new OsuTextBox - { - Height = 28, - RelativeSizeAxes = Axes.X, - PlaceholderText = "Search by name...", - FontSize = 14, - } + RelativeSizeAxes = Axes.X, + Scale = new Vector2(0.8f), + Width = 1 / 0.8f, } }, + announceChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitleANNOUNCE.ToUpper()), + publicChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePUBLIC.ToUpper()), + selector = new ChannelListItem(ChannelListingChannel), privateChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePM.ToUpper()), }, }, From 187fa5eccd7309b7cde3645fb35896ed6fa81340 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Oct 2024 18:47:04 +0900 Subject: [PATCH 645/924] Use full `async` flow rather than `ContinueWith` --- .../Online/Multiplayer/MultiplayerClient.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 610ead0f9d..5fd8b8b337 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -810,19 +810,19 @@ namespace osu.Game.Online.Multiplayer protected async Task PopulateUsers(IEnumerable multiplayerUsers) { var request = new GetUsersRequest(multiplayerUsers.Select(u => u.UserID).Distinct().ToArray()); - await API.PerformAsync(request).ContinueWith(t => + + await API.PerformAsync(request).ConfigureAwait(false); + + if (request.Response == null) + return; + + Dictionary users = request.Response.Users.ToDictionary(user => user.Id); + + foreach (var multiplayerUser in multiplayerUsers) { - if (request.Response == null) - return; - - var users = request.Response.Users.ToDictionary(user => user.Id); - - foreach (var multiplayerUser in multiplayerUsers) - { - if (users.TryGetValue(multiplayerUser.UserID, out var user)) - multiplayerUser.User = user; - } - }).ConfigureAwait(false); + if (users.TryGetValue(multiplayerUser.UserID, out var user)) + multiplayerUser.User = user; + } } /// From 826b35e031d7620d8dbd0e3aba800f7fef6458bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Oct 2024 11:36:00 +0200 Subject: [PATCH 646/924] Use `SearchContainer` instead of manual search implementation --- .../Overlays/Chat/ChannelList/ChannelList.cs | 26 +++--------------- .../Chat/ChannelList/ChannelListItem.cs | 27 ++++++++++++++++++- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index c674263bd1..b0db87cc64 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -37,7 +37,7 @@ namespace osu.Game.Overlays.Chat.ChannelList private readonly Dictionary channelMap = new Dictionary(); private OsuScrollContainer scroll = null!; - private FillFlowContainer groupFlow = null!; + private SearchContainer groupFlow = null!; private ChannelGroup announceChannelGroup = null!; private ChannelGroup publicChannelGroup = null!; private ChannelGroup privateChannelGroup = null!; @@ -59,7 +59,7 @@ namespace osu.Game.Overlays.Chat.ChannelList RelativeSizeAxes = Axes.Both, ScrollbarAnchor = Anchor.TopRight, ScrollDistance = 35f, - Child = groupFlow = new FillFlowContainer + Child = groupFlow = new SearchContainer { Direction = FillDirection.Vertical, RelativeSizeAxes = Axes.X, @@ -87,10 +87,7 @@ namespace osu.Game.Overlays.Chat.ChannelList }, }; - searchTextBox.OnUpdate += (newText) => - { - privateChannelGroup.FilterChannels(searchTextBox.Text); - }; + searchTextBox.Current.BindValueChanged(_ => groupFlow.SearchTerm = searchTextBox.Current.Value, true); selector.OnRequestSelect += chan => OnRequestSelect?.Invoke(chan); } @@ -188,23 +185,6 @@ namespace osu.Game.Overlays.Chat.ChannelList }, }; } - - public void FilterChannels(string searchTerm) - { - foreach (var item in ItemFlow.Children) - { - if (string.IsNullOrEmpty(searchTerm)) - { - item.Show(); - continue; - } - - if (item.Channel.Name.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)) - item.Show(); - else - item.Hide(); - } - } } } } diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs index e8c251e7fd..b197fe199d 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -9,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -19,7 +21,7 @@ using osuTK; namespace osu.Game.Overlays.Chat.ChannelList { - public partial class ChannelListItem : OsuClickableContainer + public partial class ChannelListItem : OsuClickableContainer, IFilterable { public event Action? OnRequestSelect; public event Action? OnRequestLeave; @@ -186,5 +188,28 @@ namespace osu.Game.Overlays.Chat.ChannelList } private bool isSelector => Channel is ChannelListing.ChannelListingChannel; + + #region Filtering support + + public IEnumerable FilterTerms => isSelector ? Enumerable.Empty() : [Channel.Name]; + + private bool matchingFilter = true; + + public bool MatchingFilter + { + get => matchingFilter; + set + { + if (matchingFilter == value) + return; + + matchingFilter = value; + Alpha = matchingFilter ? 1 : 0; + } + } + + public bool FilteringActive { get; set; } + + #endregion } } From 2ab68c6ab9fe5996e4696c21366af1b2bd18d5a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Oct 2024 11:48:24 +0200 Subject: [PATCH 647/924] Select first filtered channel on search box commit --- .../Overlays/Chat/ChannelList/ChannelList.cs | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index b0db87cc64..fc0060d86a 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; +using osu.Framework.Testing; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -71,11 +72,9 @@ namespace osu.Game.Overlays.Chat.ChannelList RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Padding = new MarginPadding { Horizontal = 10, Top = 8 }, - Child = searchTextBox = new BasicSearchTextBox + Child = searchTextBox = new ChannelSearchTextBox { RelativeSizeAxes = Axes.X, - Scale = new Vector2(0.8f), - Width = 1 / 0.8f, } }, announceChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitleANNOUNCE.ToUpper()), @@ -88,6 +87,17 @@ namespace osu.Game.Overlays.Chat.ChannelList }; searchTextBox.Current.BindValueChanged(_ => groupFlow.SearchTerm = searchTextBox.Current.Value, true); + searchTextBox.OnCommit += (_, _) => + { + if (string.IsNullOrEmpty(searchTextBox.Current.Value)) + return; + + var firstMatchingItem = this.ChildrenOfType().FirstOrDefault(item => item.MatchingFilter); + if (firstMatchingItem == null) + return; + + OnRequestSelect?.Invoke(firstMatchingItem.Channel); + }; selector.OnRequestSelect += chan => OnRequestSelect?.Invoke(chan); } @@ -186,5 +196,17 @@ namespace osu.Game.Overlays.Chat.ChannelList }; } } + + private partial class ChannelSearchTextBox : BasicSearchTextBox + { + protected override bool AllowCommit => true; + + public ChannelSearchTextBox() + { + const float scale_factor = 0.8f; + Scale = new Vector2(scale_factor); + Width = 1 / scale_factor; + } + } } } From 54aeeaa529ec327217c88e502f0780e8a0208649 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Oct 2024 12:17:28 +0200 Subject: [PATCH 648/924] Add test coverage --- .../Visual/Online/TestSceneChatOverlay.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index b6445dec6b..3d6fe50d34 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -648,6 +648,34 @@ namespace osu.Game.Tests.Visual.Online AddUntilStep("Info message displayed", () => channelManager.CurrentChannel.Value.Messages.Last(), () => Is.InstanceOf(typeof(InfoMessage))); } + [Test] + public void TestFiltering() + { + AddStep("Show overlay", () => chatOverlay.Show()); + joinTestChannel(1); + joinTestChannel(3); + joinTestChannel(5); + joinChannel(new Channel(new APIUser { Id = 2001, Username = "alice" })); + joinChannel(new Channel(new APIUser { Id = 2002, Username = "bob" })); + joinChannel(new Channel(new APIUser { Id = 2003, Username = "charley the plant" })); + + AddStep("filter to \"c\"", () => chatOverlay.ChildrenOfType().Single().Text = "c"); + AddUntilStep("bob filtered out", () => chatOverlay.ChildrenOfType().Count(i => i.Alpha > 0), () => Is.EqualTo(5)); + + AddStep("filter to \"channel\"", () => chatOverlay.ChildrenOfType().Single().Text = "channel"); + AddUntilStep("only public channels left", () => chatOverlay.ChildrenOfType().Count(i => i.Alpha > 0), () => Is.EqualTo(3)); + + AddStep("commit textbox", () => + { + chatOverlay.ChildrenOfType().Single().TakeFocus(); + Schedule(() => InputManager.PressKey(Key.Enter)); + }); + AddUntilStep("#channel-2 active", () => channelManager.CurrentChannel.Value.Name, () => Is.EqualTo("#channel-2")); + + AddStep("filter to \"channel-3\"", () => chatOverlay.ChildrenOfType().Single().Text = "channel-3"); + AddUntilStep("no channels left", () => chatOverlay.ChildrenOfType().Count(i => i.Alpha > 0), () => Is.EqualTo(0)); + } + private void joinTestChannel(int i) { AddStep($"Join test channel {i}", () => channelManager.JoinChannel(testChannels[i])); From e37d415c6faad29ac54131c181c836783512b815 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Oct 2024 20:08:05 +0900 Subject: [PATCH 649/924] Keep editor sidebars expanded by default They will not only contract if the user chooses to have them contract (new setting in the `View` menu) or if the game isn't wide enough to allow full interaction with the playfield while they are expanded. Addressess https://github.com/ppy/osu/discussions/28970. --- osu.Game/Configuration/OsuConfigManager.cs | 5 ++- osu.Game/Localisation/EditorStrings.cs | 5 +++ .../Edit/ExpandingToolboxContainer.cs | 34 +++++++++++++++++++ osu.Game/Screens/Edit/Editor.cs | 8 ++++- 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 8d6c244b35..a16abe5c55 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -206,6 +206,8 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.EditorTimelineShowTimingChanges, true); SetDefault(OsuSetting.EditorTimelineShowTicks, true); + SetDefault(OsuSetting.EditorContractSidebars, false); + SetDefault(OsuSetting.AlwaysShowHoldForMenuButton, false); } @@ -431,6 +433,7 @@ namespace osu.Game.Configuration HideCountryFlags, EditorTimelineShowTimingChanges, EditorTimelineShowTicks, - AlwaysShowHoldForMenuButton + AlwaysShowHoldForMenuButton, + EditorContractSidebars } } diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs index bcffc18d4d..f6cce554e9 100644 --- a/osu.Game/Localisation/EditorStrings.cs +++ b/osu.Game/Localisation/EditorStrings.cs @@ -114,6 +114,11 @@ namespace osu.Game.Localisation /// public static LocalisableString LimitedDistanceSnap => new TranslatableString(getKey(@"limited_distance_snap_grid"), @"Limit distance snap placement to current time"); + /// + /// "Contract sidebars when not hovered" + /// + public static LocalisableString ContractSidebars => new TranslatableString(getKey(@"contract_sidebars"), @"Contract sidebars when not hovered"); + /// /// "Must be in edit mode to handle editor links" /// diff --git a/osu.Game/Rulesets/Edit/ExpandingToolboxContainer.cs b/osu.Game/Rulesets/Edit/ExpandingToolboxContainer.cs index c2ab5a6eb9..8af795f880 100644 --- a/osu.Game/Rulesets/Edit/ExpandingToolboxContainer.cs +++ b/osu.Game/Rulesets/Edit/ExpandingToolboxContainer.cs @@ -1,9 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Events; +using osu.Game.Configuration; using osu.Game.Graphics.Containers; +using osu.Game.Screens.Edit; using osuTK; namespace osu.Game.Rulesets.Edit @@ -12,6 +16,15 @@ namespace osu.Game.Rulesets.Edit { protected override double HoverExpansionDelay => 250; + protected override bool ExpandOnHover => expandOnHover; + + private readonly Bindable contractSidebars = new Bindable(); + + private bool expandOnHover; + + [Resolved] + private Editor? editor { get; set; } + public ExpandingToolboxContainer(float contractedWidth, float expandedWidth) : base(contractedWidth, expandedWidth) { @@ -19,6 +32,27 @@ namespace osu.Game.Rulesets.Edit FillFlow.Spacing = new Vector2(5); FillFlow.Padding = new MarginPadding { Vertical = 5 }; + + Expanded.Value = true; + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + config.BindWith(OsuSetting.EditorContractSidebars, contractSidebars); + } + + protected override void Update() + { + base.Update(); + + bool requireContracting = contractSidebars.Value || editor?.DrawSize.X / editor?.DrawSize.Y < 1.7f; + + if (expandOnHover != requireContracting) + { + expandOnHover = requireContracting; + Expanded.Value = !expandOnHover; + } } protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && anyToolboxHovered(screenSpacePos); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index e9bcd3050b..e2dfbbcaf1 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -215,6 +215,7 @@ namespace osu.Game.Screens.Edit private Bindable editorLimitedDistanceSnap; private Bindable editorTimelineShowTimingChanges; private Bindable editorTimelineShowTicks; + private Bindable editorContractSidebars; /// /// This controls the opacity of components like the timelines, sidebars, etc. @@ -323,6 +324,7 @@ namespace osu.Game.Screens.Edit editorLimitedDistanceSnap = config.GetBindable(OsuSetting.EditorLimitedDistanceSnap); editorTimelineShowTimingChanges = config.GetBindable(OsuSetting.EditorTimelineShowTimingChanges); editorTimelineShowTicks = config.GetBindable(OsuSetting.EditorTimelineShowTicks); + editorContractSidebars = config.GetBindable(OsuSetting.EditorContractSidebars); AddInternal(new OsuContextMenuContainer { @@ -402,7 +404,11 @@ namespace osu.Game.Screens.Edit new ToggleMenuItem(EditorStrings.LimitedDistanceSnap) { State = { BindTarget = editorLimitedDistanceSnap }, - } + }, + new ToggleMenuItem(EditorStrings.ContractSidebars) + { + State = { BindTarget = editorContractSidebars } + }, } }, new MenuItem(EditorStrings.Timing) From 1008d32ddb61266989877461096ae3a0b13e7d2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Oct 2024 14:30:29 +0200 Subject: [PATCH 650/924] Fix old looping samples not stopping when replacing a `SkinnableSound`'s `Samples` Closes https://github.com/ppy/osu/issues/30365. --- osu.Game/Skinning/SkinnableSound.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index f153f4f8d3..be9212da9e 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -154,6 +154,9 @@ namespace osu.Game.Skinning { bool wasPlaying = IsPlaying; + if (wasPlaying && Looping) + Stop(); + // Remove all pooled samples (return them to the pool), and dispose the rest. samplesContainer.RemoveAll(s => s.IsInPool, false); samplesContainer.Clear(); From 21351b1be45d700abf216b7eca14f5bf58aec16f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Oct 2024 15:41:42 +0900 Subject: [PATCH 651/924] Quote source text when searching for it via click Addresses https://github.com/ppy/osu/discussions/30181. --- osu.Game/Overlays/BeatmapSet/MetadataSectionSource.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/MetadataSectionSource.cs b/osu.Game/Overlays/BeatmapSet/MetadataSectionSource.cs index 544dc0dfe4..c8d4ef5355 100644 --- a/osu.Game/Overlays/BeatmapSet/MetadataSectionSource.cs +++ b/osu.Game/Overlays/BeatmapSet/MetadataSectionSource.cs @@ -17,9 +17,9 @@ namespace osu.Game.Overlays.BeatmapSet protected override void AddMetadata(string metadata, LinkFlowContainer loaded) { if (SearchAction != null) - loaded.AddLink(metadata, () => SearchAction(metadata)); + loaded.AddLink(metadata, () => SearchAction($"\"{metadata}\"")); else - loaded.AddLink(metadata, LinkAction.SearchBeatmapSet, metadata); + loaded.AddLink(metadata, LinkAction.SearchBeatmapSet, $"\"{metadata}\""); } } } From af7d35bfbf50bd3b5891ec1e5e6a1c508e7c2d77 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 23 Oct 2024 16:15:40 +0900 Subject: [PATCH 652/924] Doubly quote strings Note the external-action case (currently used for tags) doesn't match osu!web but it doesn't matter because tags are single words anyway. --- osu.Game/Overlays/BeatmapSet/MetadataSectionSource.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/MetadataSectionSource.cs b/osu.Game/Overlays/BeatmapSet/MetadataSectionSource.cs index c8d4ef5355..92511df498 100644 --- a/osu.Game/Overlays/BeatmapSet/MetadataSectionSource.cs +++ b/osu.Game/Overlays/BeatmapSet/MetadataSectionSource.cs @@ -17,9 +17,9 @@ namespace osu.Game.Overlays.BeatmapSet protected override void AddMetadata(string metadata, LinkFlowContainer loaded) { if (SearchAction != null) - loaded.AddLink(metadata, () => SearchAction($"\"{metadata}\"")); + loaded.AddLink(metadata, () => SearchAction($@"""""{metadata}""""")); else - loaded.AddLink(metadata, LinkAction.SearchBeatmapSet, $"\"{metadata}\""); + loaded.AddLink(metadata, LinkAction.SearchBeatmapSet, $@"""""{metadata}"""""); } } } From 2bea1fe4a6cfbcc313e12dabb98c314164ae5b4d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 23 Oct 2024 16:21:28 +0900 Subject: [PATCH 653/924] Also add source prefix --- osu.Game/Overlays/BeatmapSet/MetadataSectionSource.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/MetadataSectionSource.cs b/osu.Game/Overlays/BeatmapSet/MetadataSectionSource.cs index 92511df498..4d55359e63 100644 --- a/osu.Game/Overlays/BeatmapSet/MetadataSectionSource.cs +++ b/osu.Game/Overlays/BeatmapSet/MetadataSectionSource.cs @@ -17,9 +17,9 @@ namespace osu.Game.Overlays.BeatmapSet protected override void AddMetadata(string metadata, LinkFlowContainer loaded) { if (SearchAction != null) - loaded.AddLink(metadata, () => SearchAction($@"""""{metadata}""""")); + loaded.AddLink(metadata, () => SearchAction($@"source=""""{metadata}""""")); else - loaded.AddLink(metadata, LinkAction.SearchBeatmapSet, $@"""""{metadata}"""""); + loaded.AddLink(metadata, LinkAction.SearchBeatmapSet, $@"source=""""{metadata}"""""); } } } From be5cb209e502c1df833ad7dc542c6fc4a1c8a846 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Oct 2024 17:51:29 +0900 Subject: [PATCH 654/924] Update notification text when import is paused due to gameplay Addresses https://github.com/ppy/osu/discussions/30388. --- osu.Game/Database/RealmArchiveModelImporter.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index cf0625c51c..a27e35fc4e 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -105,7 +105,6 @@ namespace osu.Game.Database } notification.Progress = 0; - notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import is initialising..."; int current = 0; @@ -113,6 +112,18 @@ namespace osu.Game.Database parameters.Batch |= tasks.Length >= minimum_items_considered_batch_import; + // A paused state could obviously be entered mid-import (during the `Task.WhenAll` below), + // but in order to keep things simple let's focus on the most common scenario. + notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import is paused due to gameplay..."; + + try + { + pauseIfNecessary(parameters, notification.CancellationToken); + } + catch { } + + notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import is initialising..."; + await Task.WhenAll(tasks.Select(async task => { if (notification.CancellationToken.IsCancellationRequested) From 064aaeb60e07e51d80582c2ecde491008166d1fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Oct 2024 18:46:20 +0900 Subject: [PATCH 655/924] Initialise container earlier to avoid null reference failures --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 3ad173893b..ebd84fd91b 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -65,8 +65,6 @@ namespace osu.Game.Rulesets.UI /// public override Playfield Playfield => playfield.Value; - private PlayfieldAdjustmentContainer playfieldAdjustmentContainer; - public override PlayfieldAdjustmentContainer PlayfieldAdjustmentContainer => playfieldAdjustmentContainer; public override Container Overlays { get; } = new Container { RelativeSizeAxes = Axes.Both }; @@ -79,6 +77,8 @@ namespace osu.Game.Rulesets.UI public override IFrameStableClock FrameStableClock => frameStabilityContainer; + private readonly PlayfieldAdjustmentContainer playfieldAdjustmentContainer; + private bool allowBackwardsSeeks; public override bool AllowBackwardsSeeks @@ -144,6 +144,7 @@ namespace osu.Game.Rulesets.UI RelativeSizeAxes = Axes.Both; KeyBindingInputManager = CreateInputManager(); + playfieldAdjustmentContainer = CreatePlayfieldAdjustmentContainer(); playfield = new Lazy(() => CreatePlayfield().With(p => { p.NewResult += (_, r) => NewResult?.Invoke(r); @@ -195,8 +196,7 @@ namespace osu.Game.Rulesets.UI audioContainer.WithChild(KeyBindingInputManager .WithChildren(new Drawable[] { - playfieldAdjustmentContainer = CreatePlayfieldAdjustmentContainer() - .WithChild(Playfield), + playfieldAdjustmentContainer.WithChild(Playfield), Overlays })), } From bf88219dfb5c987c3be49ec6b804370eeb41750b Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 23 Oct 2024 20:21:38 +0200 Subject: [PATCH 656/924] Move TestTouchInputAfterTouchingComposeArea to separate test scene --- .../Editor/TestSceneOsuEditor.cs | 72 --------------- .../Editor/TestSceneSliderDrawing.cs | 87 +++++++++++++++++++ 2 files changed, 87 insertions(+), 72 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderDrawing.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditor.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditor.cs index befaf58029..03ab7ebbf7 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditor.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditor.cs @@ -1,19 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using osu.Framework.Testing; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.UI; -using osu.Game.Screens.Edit.Components.RadioButtons; -using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Tests.Visual; -using osuTK; -using osuTK.Input; namespace osu.Game.Rulesets.Osu.Tests.Editor { @@ -21,66 +10,5 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor public partial class TestSceneOsuEditor : EditorTestScene { protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); - - [Test] - public void TestTouchInputAfterTouchingComposeArea() - { - AddStep("tap circle", () => tap(this.ChildrenOfType().Single(b => b.Button.Label == "HitCircle"))); - - // this input is just for interacting with compose area - AddStep("tap playfield", () => tap(this.ChildrenOfType().Single())); - - AddStep("move current time", () => InputManager.Key(Key.Right)); - - AddStep("tap to place circle", () => tap(this.ChildrenOfType().Single().ToScreenSpace(new Vector2(10, 10)))); - AddAssert("circle placed correctly", () => - { - var circle = (HitCircle)EditorBeatmap.HitObjects.Single(h => h.StartTime == EditorClock.CurrentTimeAccurate); - Assert.Multiple(() => - { - Assert.That(circle.Position.X, Is.EqualTo(10f).Within(0.01f)); - Assert.That(circle.Position.Y, Is.EqualTo(10f).Within(0.01f)); - }); - - return true; - }); - - AddStep("tap slider", () => tap(this.ChildrenOfType().Single(b => b.Button.Label == "Slider"))); - - // this input is just for interacting with compose area - AddStep("tap playfield", () => tap(this.ChildrenOfType().Single())); - - AddStep("move current time", () => InputManager.Key(Key.Right)); - - AddStep("hold to draw slider", () => InputManager.BeginTouch(new Touch(TouchSource.Touch1, this.ChildrenOfType().Single().ToScreenSpace(new Vector2(50, 20))))); - AddStep("drag to draw", () => InputManager.MoveTouchTo(new Touch(TouchSource.Touch1, this.ChildrenOfType().Single().ToScreenSpace(new Vector2(200, 50))))); - AddAssert("selection not initiated", () => this.ChildrenOfType().All(d => d.State == Visibility.Hidden)); - AddStep("end", () => InputManager.EndTouch(new Touch(TouchSource.Touch1, InputManager.CurrentState.Touch.GetTouchPosition(TouchSource.Touch1)!.Value))); - AddAssert("slider placed correctly", () => - { - var slider = (Slider)EditorBeatmap.HitObjects.Single(h => h.StartTime == EditorClock.CurrentTimeAccurate); - Assert.Multiple(() => - { - Assert.That(slider.Position.X, Is.EqualTo(50f).Within(0.01f)); - Assert.That(slider.Position.Y, Is.EqualTo(20f).Within(0.01f)); - Assert.That(slider.Path.ControlPoints.Count, Is.EqualTo(2)); - Assert.That(slider.Path.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); - - // the final position may be slightly off from the mouse position when drawing, account for that. - Assert.That(slider.Path.ControlPoints[1].Position.X, Is.EqualTo(150).Within(5)); - Assert.That(slider.Path.ControlPoints[1].Position.Y, Is.EqualTo(30).Within(5)); - }); - - return true; - }); - } - - private void tap(Drawable drawable) => tap(drawable.ScreenSpaceDrawQuad.Centre); - - private void tap(Vector2 position) - { - InputManager.BeginTouch(new Touch(TouchSource.Touch1, position)); - InputManager.EndTouch(new Touch(TouchSource.Touch1, position)); - } } } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderDrawing.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderDrawing.cs new file mode 100644 index 0000000000..3c8358b4cd --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderDrawing.cs @@ -0,0 +1,87 @@ +// 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.Input; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Edit.Components.RadioButtons; +using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Tests.Beatmaps; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Tests.Editor +{ + [TestFixture] + public partial class TestSceneSliderDrawing : TestSceneOsuEditor + { + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); + + [Test] + public void TestTouchInputAfterTouchingComposeArea() + { + AddStep("tap circle", () => tap(this.ChildrenOfType().Single(b => b.Button.Label == "HitCircle"))); + + // this input is just for interacting with compose area + AddStep("tap playfield", () => tap(this.ChildrenOfType().Single())); + + AddStep("move current time", () => InputManager.Key(Key.Right)); + + AddStep("tap to place circle", () => tap(this.ChildrenOfType().Single().ToScreenSpace(new Vector2(10, 10)))); + AddAssert("circle placed correctly", () => + { + var circle = (HitCircle)EditorBeatmap.HitObjects.Single(h => h.StartTime == EditorClock.CurrentTimeAccurate); + Assert.Multiple(() => + { + Assert.That(circle.Position.X, Is.EqualTo(10f).Within(0.01f)); + Assert.That(circle.Position.Y, Is.EqualTo(10f).Within(0.01f)); + }); + + return true; + }); + + AddStep("tap slider", () => tap(this.ChildrenOfType().Single(b => b.Button.Label == "Slider"))); + + // this input is just for interacting with compose area + AddStep("tap playfield", () => tap(this.ChildrenOfType().Single())); + + AddStep("move current time", () => InputManager.Key(Key.Right)); + + AddStep("hold to draw slider", () => InputManager.BeginTouch(new Touch(TouchSource.Touch1, this.ChildrenOfType().Single().ToScreenSpace(new Vector2(50, 20))))); + AddStep("drag to draw", () => InputManager.MoveTouchTo(new Touch(TouchSource.Touch1, this.ChildrenOfType().Single().ToScreenSpace(new Vector2(200, 50))))); + AddAssert("selection not initiated", () => this.ChildrenOfType().All(d => d.State == Visibility.Hidden)); + AddStep("end", () => InputManager.EndTouch(new Touch(TouchSource.Touch1, InputManager.CurrentState.Touch.GetTouchPosition(TouchSource.Touch1)!.Value))); + AddAssert("slider placed correctly", () => + { + var slider = (Slider)EditorBeatmap.HitObjects.Single(h => h.StartTime == EditorClock.CurrentTimeAccurate); + Assert.Multiple(() => + { + Assert.That(slider.Position.X, Is.EqualTo(50f).Within(0.01f)); + Assert.That(slider.Position.Y, Is.EqualTo(20f).Within(0.01f)); + Assert.That(slider.Path.ControlPoints.Count, Is.EqualTo(2)); + Assert.That(slider.Path.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); + + // the final position may be slightly off from the mouse position when drawing, account for that. + Assert.That(slider.Path.ControlPoints[1].Position.X, Is.EqualTo(150).Within(5)); + Assert.That(slider.Path.ControlPoints[1].Position.Y, Is.EqualTo(30).Within(5)); + }); + + return true; + }); + } + + private void tap(Drawable drawable) => tap(drawable.ScreenSpaceDrawQuad.Centre); + + private void tap(Vector2 position) + { + InputManager.BeginTouch(new Touch(TouchSource.Touch1, position)); + InputManager.EndTouch(new Touch(TouchSource.Touch1, position)); + } + } +} From ddbeb56f0f92544ee5463c94e4e6b3f69fe8d406 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 23 Oct 2024 21:25:37 +0200 Subject: [PATCH 657/924] Show tooltip on auto normal bank when not usable --- .../Compose/Components/ComposeBlueprintContainer.cs | 11 +++++++++-- .../Edit/Compose/Components/EditorSelectionHandler.cs | 7 +++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index fe9a6e6443..4331199a47 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -68,7 +68,8 @@ namespace osu.Game.Screens.Edit.Compose.Components SampleBankTernaryStates = createSampleBankTernaryButtons(SelectionHandler.SelectionBankStates).ToArray(); SampleAdditionBankTernaryStates = createSampleBankTernaryButtons(SelectionHandler.SelectionAdditionBankStates).ToArray(); - SelectionHandler.SelectionAdditionBanksEnabled.BindValueChanged(_ => updateTernaryButtonTooltips()); + SelectionHandler.AutoSelectionBankEnabled.BindValueChanged(_ => updateAutoBankTernaryButtonTooltip(), true); + SelectionHandler.SelectionAdditionBanksEnabled.BindValueChanged(_ => updateAdditionBankTernaryButtonTooltips(), true); AddInternal(new DrawableRulesetDependenciesProvidingContainer(Composer.Ruleset) { @@ -290,7 +291,13 @@ namespace osu.Game.Screens.Edit.Compose.Components return null; } - private void updateTernaryButtonTooltips() + private void updateAutoBankTernaryButtonTooltip() + { + var autoBankButton = SampleBankTernaryStates.Single(t => t.Bindable == SelectionHandler.SelectionBankStates[EditorSelectionHandler.HIT_BANK_AUTO]); + autoBankButton.Tooltip = !SelectionHandler.AutoSelectionBankEnabled.Value ? "Auto normal bank can only be used during hit object placement" : string.Empty; + } + + private void updateAdditionBankTernaryButtonTooltips() { foreach (var ternaryButton in SampleAdditionBankTernaryStates) ternaryButton.Tooltip = !SelectionHandler.SelectionAdditionBanksEnabled.Value ? "Add an addition sample first to be able to set a bank" : string.Empty; diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index beead572e6..2d5341e85e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -64,6 +64,11 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public readonly Dictionary> SelectionAdditionBankStates = new Dictionary>(); + /// + /// Whether there is no selection and the auto can be used. + /// + public readonly Bindable AutoSelectionBankEnabled = new Bindable(); + /// /// Whether the selection contains any addition samples and the can be used. /// @@ -253,6 +258,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void resetTernaryStates() { + AutoSelectionBankEnabled.Value = true; SelectionBankStates[HIT_BANK_AUTO].Value = TernaryState.True; SelectionAdditionBankStates[HIT_BANK_AUTO].Value = TernaryState.True; } @@ -263,6 +269,7 @@ namespace osu.Game.Screens.Edit.Compose.Components protected virtual void UpdateTernaryStates() { SelectionNewComboState.Value = GetStateFromSelection(SelectedItems.OfType(), h => h.NewCombo); + AutoSelectionBankEnabled.Value = SelectedItems.Count == 0; var samplesInSelection = SelectedItems.SelectMany(enumerateAllSamples).ToArray(); From 3a4d5af83efc812f41ba6a95d6890fe7b768225b Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 13 Oct 2024 11:07:49 -0400 Subject: [PATCH 658/924] Fix placement blueprints sometimes not behaving correctly with touch input More specifically, this fixes placement blueprints not beginning placement when using touch input while the cursor was previously outside compose area, due to the placement blueprint not existing (removed from the scene by `ComposeBlueprintContainer`). --- .../Edit/Blueprints/GridPlacementBlueprint.cs | 25 ++++++++++--- .../Sliders/SliderPlacementBlueprint.cs | 7 ++++ osu.Game/Rulesets/Edit/HitObjectComposer.cs | 17 +++++---- .../Edit/HitObjectPlacementBlueprint.cs | 27 +++++++++++++- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 24 +++++++++--- .../Components/ComposeBlueprintContainer.cs | 37 +++++++++---------- .../Screens/Edit/Compose/IPlacementHandler.cs | 16 +++++--- .../Visual/PlacementBlueprintTestScene.cs | 16 +++++--- 8 files changed, 116 insertions(+), 53 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs index b13663cb44..163b42bcfd 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Rulesets.Edit; using osuTK; @@ -31,12 +32,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints public override void EndPlacement(bool commit) { if (!commit && PlacementActive != PlacementState.Finished) - { - gridToolboxGroup.StartPosition.Value = originalOrigin; - gridToolboxGroup.Spacing.Value = originalSpacing; - if (!gridToolboxGroup.GridLinesRotation.Disabled) - gridToolboxGroup.GridLinesRotation.Value = originalRotation; - } + resetGridState(); base.EndPlacement(commit); @@ -103,6 +99,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints public override void UpdateTimeAndPosition(SnapResult result) { + if (State.Value == Visibility.Hidden) + return; + var pos = ToLocalSpace(result.ScreenSpacePosition); if (PlacementActive != PlacementState.Active) @@ -122,5 +121,19 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints } } } + + protected override void PopOut() + { + base.PopOut(); + resetGridState(); + } + + private void resetGridState() + { + gridToolboxGroup.StartPosition.Value = originalOrigin; + gridToolboxGroup.Spacing.Value = originalSpacing; + if (!gridToolboxGroup.GridLinesRotation.Disabled) + gridToolboxGroup.GridLinesRotation.Value = originalRotation; + } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index cb57c8e6e0..4f2f6516a8 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -156,6 +156,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders return true; } + // this allows sliders to be drawn outside compose area (after starting from a point within the compose area). + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || PlacementActive == PlacementState.Active; + + // ReceivePositionalInputAtSubTree generally always returns true when masking is disabled, but we don't want that, + // otherwise a slider path tooltip will be displayed anywhere in the editor (outside compose area). + protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => ReceivePositionalInputAt(screenSpacePos); + private void beginNewSegment(PathControlPoint lastPoint) { segmentStart = lastPoint; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 0499e10607..a33d258602 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -484,22 +484,23 @@ namespace osu.Game.Rulesets.Edit #region IPlacementHandler - public void BeginPlacement(HitObject hitObject) + public void ShowPlacement(HitObject hitObject) { EditorBeatmap.PlacementObject.Value = hitObject; } - public void EndPlacement(HitObject hitObject, bool commit) + public void HidePlacement() { EditorBeatmap.PlacementObject.Value = null; + } - if (commit) - { - EditorBeatmap.Add(hitObject); + public void CommitPlacement(HitObject hitObject) + { + EditorBeatmap.PlacementObject.Value = null; + EditorBeatmap.Add(hitObject); - if (autoSeekOnPlacement.Value && EditorClock.CurrentTime < hitObject.StartTime) - EditorClock.SeekSmoothlyTo(hitObject.StartTime); - } + if (autoSeekOnPlacement.Value && EditorClock.CurrentTime < hitObject.StartTime) + EditorClock.SeekSmoothlyTo(hitObject.StartTime); } public void Delete(HitObject hitObject) => EditorBeatmap.Remove(hitObject); diff --git a/osu.Game/Rulesets/Edit/HitObjectPlacementBlueprint.cs b/osu.Game/Rulesets/Edit/HitObjectPlacementBlueprint.cs index 74025b4260..f5b5648d5e 100644 --- a/osu.Game/Rulesets/Edit/HitObjectPlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/HitObjectPlacementBlueprint.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -58,18 +59,26 @@ namespace osu.Game.Rulesets.Edit startTimeBindable.BindValueChanged(_ => ApplyDefaultsToHitObject(), true); } + private bool placementBegun; + protected override void BeginPlacement(bool commitStart = false) { base.BeginPlacement(commitStart); - placementHandler.BeginPlacement(HitObject); + if (State.Value == Visibility.Visible) + placementHandler.ShowPlacement(HitObject); + + placementBegun = true; } public override void EndPlacement(bool commit) { base.EndPlacement(commit); - placementHandler.EndPlacement(HitObject, IsValidForPlacement && commit); + if (IsValidForPlacement && commit) + placementHandler.CommitPlacement(HitObject); + else + placementHandler.HidePlacement(); } /// @@ -122,5 +131,19 @@ namespace osu.Game.Rulesets.Edit /// refreshing and parameters for the . /// protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); + + protected override void PopIn() + { + base.PopIn(); + + if (placementBegun) + placementHandler.ShowPlacement(HitObject); + } + + protected override void PopOut() + { + base.PopOut(); + placementHandler.HidePlacement(); + } } } diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index a36de02433..a4a35fcd5c 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -7,7 +7,6 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Objects; -using osuTK; using osuTK.Input; namespace osu.Game.Rulesets.Edit @@ -15,7 +14,7 @@ namespace osu.Game.Rulesets.Edit /// /// A blueprint which governs the placement of something. /// - public abstract partial class PlacementBlueprint : CompositeDrawable, IKeyBindingHandler + public abstract partial class PlacementBlueprint : VisibilityContainer, IKeyBindingHandler { /// /// Whether the is currently mid-placement, but has not necessarily finished being placed. @@ -31,12 +30,17 @@ namespace osu.Game.Rulesets.Edit /// protected virtual bool IsValidForPlacement => true; + // the blueprint should still be considered for input even if it is hidden, + // especially when such input is the reason for making the blueprint become visible. + public override bool PropagatePositionalInputSubTree => true; + public override bool PropagateNonPositionalInputSubTree => true; + protected PlacementBlueprint() { RelativeSizeAxes = Axes.Both; - // This is required to allow the blueprint's position to be updated via OnMouseMove/Handle - // on the same frame it is made visible via a PlacementState change. + // the blueprint should still be considered for input even if it is hidden, + // especially when such input is the reason for making the blueprint become visible. AlwaysPresent = true; } @@ -104,8 +108,6 @@ namespace osu.Game.Rulesets.Edit { } - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; - protected override bool Handle(UIEvent e) { base.Handle(e); @@ -127,6 +129,16 @@ namespace osu.Game.Rulesets.Edit } } + protected override void PopIn() + { + this.FadeIn(200, Easing.OutQuint); + } + + protected override void PopOut() + { + this.FadeOut(200, Easing.OutQuint); + } + public enum PlacementState { Waiting, diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index aa7072007d..fde4783661 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -289,9 +289,12 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Refreshes the current placement tool. /// - private void refreshTool() + private void refreshPlacement() { - removePlacement(); + CurrentPlacement?.EndPlacement(false); + CurrentPlacement?.Expire(); + CurrentPlacement = null; + ensurePlacementCreated(); } @@ -317,21 +320,24 @@ namespace osu.Game.Screens.Edit.Compose.Components { case PlacementBlueprint.PlacementState.Waiting: if (!Composer.CursorInPlacementArea) - removePlacement(); + CurrentPlacement.Hide(); + else if (Composer.CursorInPlacementArea) + CurrentPlacement.Show(); + + break; + + case PlacementBlueprint.PlacementState.Active: + CurrentPlacement.Show(); break; case PlacementBlueprint.PlacementState.Finished: - removePlacement(); + refreshPlacement(); break; } - } - if (Composer.CursorInPlacementArea) - ensurePlacementCreated(); - - // updates the placement with the latest editor clock time. - if (CurrentPlacement != null) + // updates the placement with the latest editor clock time. updatePlacementTimeAndPosition(); + } } protected override bool OnMouseMove(MouseMoveEvent e) @@ -359,7 +365,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void hitObjectAdded(HitObject obj) { // refresh the tool to handle the case of placement completing. - refreshTool(); + refreshPlacement(); // on successful placement, the new combo button should be reset as this is the most common user interaction. if (Beatmap.SelectedHitObjects.Count == 0) @@ -388,14 +394,7 @@ namespace osu.Game.Screens.Edit.Compose.Components public void CommitIfPlacementActive() { CurrentPlacement?.EndPlacement(CurrentPlacement.PlacementActive == PlacementBlueprint.PlacementState.Active); - removePlacement(); - } - - private void removePlacement() - { - CurrentPlacement?.EndPlacement(false); - CurrentPlacement?.Expire(); - CurrentPlacement = null; + refreshPlacement(); } private CompositionTool currentTool; diff --git a/osu.Game/Screens/Edit/Compose/IPlacementHandler.cs b/osu.Game/Screens/Edit/Compose/IPlacementHandler.cs index 57960a76a1..e2046cd532 100644 --- a/osu.Game/Screens/Edit/Compose/IPlacementHandler.cs +++ b/osu.Game/Screens/Edit/Compose/IPlacementHandler.cs @@ -10,17 +10,21 @@ namespace osu.Game.Screens.Edit.Compose public interface IPlacementHandler { /// - /// Notifies that a placement has begun. + /// Notifies that a placement blueprint became visible on the screen. /// - /// The being placed. - void BeginPlacement(HitObject hitObject); + /// The representing the placement. + void ShowPlacement(HitObject hitObject); /// - /// Notifies that a placement has finished. + /// Notifies that a visible placement blueprint has been hidden. + /// + void HidePlacement(); + + /// + /// Notifies that a placement has been committed. /// /// The that has been placed. - /// Whether the object should be committed. - void EndPlacement(HitObject hitObject, bool commit); + void CommitPlacement(HitObject hitObject); /// /// Deletes a . diff --git a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs index a52325dea2..aa8aff3adc 100644 --- a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs @@ -59,20 +59,20 @@ namespace osu.Game.Tests.Visual protected override void LoadComplete() { base.LoadComplete(); - ResetPlacement(); } - public void BeginPlacement(HitObject hitObject) + public void ShowPlacement(HitObject hitObject) { } - public void EndPlacement(HitObject hitObject, bool commit) + public void HidePlacement() { - if (commit) - AddHitObject(CreateHitObject(hitObject)); + } - ResetPlacement(); + public void CommitPlacement(HitObject hitObject) + { + AddHitObject(CreateHitObject(hitObject)); } protected void ResetPlacement() @@ -89,6 +89,10 @@ namespace osu.Game.Tests.Visual protected override void Update() { base.Update(); + + if (CurrentBlueprint.PlacementActive == PlacementBlueprint.PlacementState.Finished) + ResetPlacement(); + updatePlacementTimeAndPosition(); } From 61f0cfd688f45d8bc02ca24a71566ed662aa8f56 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Oct 2024 17:56:51 +0900 Subject: [PATCH 659/924] Make flow more `async` to avoid any chance of deadlocks --- .../Database/RealmArchiveModelImporter.cs | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index a27e35fc4e..200c2051fc 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -112,26 +112,20 @@ namespace osu.Game.Database parameters.Batch |= tasks.Length >= minimum_items_considered_batch_import; - // A paused state could obviously be entered mid-import (during the `Task.WhenAll` below), - // but in order to keep things simple let's focus on the most common scenario. - notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import is paused due to gameplay..."; - - try - { - pauseIfNecessary(parameters, notification.CancellationToken); - } - catch { } - notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import is initialising..."; + notification.State = ProgressNotificationState.Active; - await Task.WhenAll(tasks.Select(async task => + await pauseIfNecessaryAsync(parameters, notification, notification.CancellationToken).ConfigureAwait(false); + + await Parallel.ForEachAsync(tasks, notification.CancellationToken, async (task, cancellation) => { - if (notification.CancellationToken.IsCancellationRequested) - return; + cancellation.ThrowIfCancellationRequested(); try { - var model = await Import(task, parameters, notification.CancellationToken).ConfigureAwait(false); + await pauseIfNecessaryAsync(parameters, notification, cancellation).ConfigureAwait(false); + + var model = await Import(task, parameters, cancellation).ConfigureAwait(false); lock (imported) { @@ -150,7 +144,7 @@ namespace osu.Game.Database { Logger.Error(e, $@"Could not import ({task})", LoggingTarget.Database); } - })).ConfigureAwait(false); + }).ConfigureAwait(false); if (imported.Count == 0) { @@ -297,8 +291,6 @@ namespace osu.Game.Database /// An optional cancellation token. public virtual Live? ImportModel(TModel item, ArchiveReader? archive = null, ImportParameters parameters = default, CancellationToken cancellationToken = default) => Realm.Run(realm => { - pauseIfNecessary(parameters, cancellationToken); - TModel? existing; if (parameters.Batch && archive != null) @@ -586,21 +578,29 @@ namespace osu.Game.Database /// Whether to perform deletion. protected virtual bool ShouldDeleteArchive(string path) => false; - private void pauseIfNecessary(ImportParameters importParameters, CancellationToken cancellationToken) + private async Task pauseIfNecessaryAsync(ImportParameters importParameters, ProgressNotification notification, CancellationToken cancellationToken) { if (!PauseImports || importParameters.ImportImmediately) return; Logger.Log($@"{GetType().Name} is being paused."); + // A paused state could obviously be entered mid-import (during the `Task.WhenAll` below), + // but in order to keep things simple let's focus on the most common scenario. + notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import is paused due to gameplay..."; + notification.State = ProgressNotificationState.Queued; + while (PauseImports) { cancellationToken.ThrowIfCancellationRequested(); - Thread.Sleep(500); + await Task.Delay(500, cancellationToken).ConfigureAwait(false); } cancellationToken.ThrowIfCancellationRequested(); Logger.Log($@"{GetType().Name} is being resumed."); + + notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import is resuming..."; + notification.State = ProgressNotificationState.Active; } private IEnumerable getIDs(IEnumerable files) From 77bd0e8d7004f0b9c237a5377c24882b36c09bab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Oct 2024 18:36:34 +0900 Subject: [PATCH 660/924] Add visual disabled state to ternary buttons --- .../TernaryButtons/DrawableTernaryButton.cs | 3 +++ .../Edit/Components/TernaryButtons/TernaryButton.cs | 2 ++ .../Compose/Components/ComposeBlueprintContainer.cs | 12 ++++++++++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs b/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs index 7a9bdfc41f..aedae9fea1 100644 --- a/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs +++ b/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs @@ -66,6 +66,9 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons private void onAction() { + if (!Button.Enabled.Value) + return; + Button.Toggle(); } diff --git a/osu.Game/Screens/Edit/Components/TernaryButtons/TernaryButton.cs b/osu.Game/Screens/Edit/Components/TernaryButtons/TernaryButton.cs index b025e4fbf5..8d1bc8d9fc 100644 --- a/osu.Game/Screens/Edit/Components/TernaryButtons/TernaryButton.cs +++ b/osu.Game/Screens/Edit/Components/TernaryButtons/TernaryButton.cs @@ -12,6 +12,8 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons { public readonly Bindable Bindable; + public readonly Bindable Enabled = new Bindable(); + public readonly string Description; /// diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 4331199a47..69e676667d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -293,14 +293,22 @@ namespace osu.Game.Screens.Edit.Compose.Components private void updateAutoBankTernaryButtonTooltip() { + bool enabled = SelectionHandler.AutoSelectionBankEnabled.Value; + var autoBankButton = SampleBankTernaryStates.Single(t => t.Bindable == SelectionHandler.SelectionBankStates[EditorSelectionHandler.HIT_BANK_AUTO]); - autoBankButton.Tooltip = !SelectionHandler.AutoSelectionBankEnabled.Value ? "Auto normal bank can only be used during hit object placement" : string.Empty; + autoBankButton.Enabled.Value = enabled; + autoBankButton.Tooltip = !enabled ? "Auto normal bank can only be used during hit object placement" : string.Empty; } private void updateAdditionBankTernaryButtonTooltips() { + bool enabled = SelectionHandler.SelectionAdditionBanksEnabled.Value; + foreach (var ternaryButton in SampleAdditionBankTernaryStates) - ternaryButton.Tooltip = !SelectionHandler.SelectionAdditionBanksEnabled.Value ? "Add an addition sample first to be able to set a bank" : string.Empty; + { + ternaryButton.Enabled.Value = enabled; + ternaryButton.Tooltip = !enabled ? "Add an addition sample first to be able to set a bank" : string.Empty; + } } #region Placement From 6dd1efa0132e9ab9f08eea02439d794e191f73bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Oct 2024 19:46:16 +0900 Subject: [PATCH 661/924] Adjust slider bar implementations to show focused state --- .../UserInterface/RoundedSliderBar.cs | 25 ++++++++++++- .../UserInterface/ShearedSliderBar.cs | 27 +++++++++++++- .../Graphics/UserInterfaceV2/FormSliderBar.cs | 36 ++++++++++++++----- 3 files changed, 77 insertions(+), 11 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/RoundedSliderBar.cs b/osu.Game/Graphics/UserInterface/RoundedSliderBar.cs index 56047173bb..aeab7c34b2 100644 --- a/osu.Game/Graphics/UserInterface/RoundedSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/RoundedSliderBar.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Overlays; @@ -25,6 +26,8 @@ namespace osu.Game.Graphics.UserInterface private readonly HoverClickSounds hoverClickSounds; + private readonly Container mainContent; + private Color4 accentColour; public Color4 AccentColour @@ -62,7 +65,7 @@ namespace osu.Game.Graphics.UserInterface Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Padding = new MarginPadding { Horizontal = 2 }, - Child = new CircularContainer + Child = mainContent = new CircularContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -135,6 +138,26 @@ namespace osu.Game.Graphics.UserInterface }, true); } + protected override void OnFocus(FocusEvent e) + { + base.OnFocus(e); + + mainContent.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = AccentColour.Darken(1), + Hollow = true, + Radius = 2, + }; + } + + protected override void OnFocusLost(FocusLostEvent e) + { + base.OnFocusLost(e); + + mainContent.EdgeEffect = default; + } + protected override bool OnHover(HoverEvent e) { updateGlow(); diff --git a/osu.Game/Graphics/UserInterface/ShearedSliderBar.cs b/osu.Game/Graphics/UserInterface/ShearedSliderBar.cs index 0df1c1d204..f4c6f07262 100644 --- a/osu.Game/Graphics/UserInterface/ShearedSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/ShearedSliderBar.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Overlays; @@ -26,6 +27,8 @@ namespace osu.Game.Graphics.UserInterface private readonly HoverClickSounds hoverClickSounds; + private readonly Container mainContent; + private Color4 accentColour; public Color4 AccentColour @@ -60,9 +63,11 @@ namespace osu.Game.Graphics.UserInterface RangePadding = EXPANDED_SIZE / 2; Children = new Drawable[] { - new Container + mainContent = new Container { RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 5, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Padding = new MarginPadding { Horizontal = 2 }, @@ -138,6 +143,26 @@ namespace osu.Game.Graphics.UserInterface }, true); } + protected override void OnFocus(FocusEvent e) + { + base.OnFocus(e); + + mainContent.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = AccentColour.Darken(1), + Hollow = true, + Radius = 2, + }; + } + + protected override void OnFocusLost(FocusLostEvent e) + { + base.OnFocusLost(e); + + mainContent.EdgeEffect = default; + } + protected override bool OnHover(HoverEvent e) { updateGlow(); diff --git a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs index da28437eee..532423876e 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs @@ -71,7 +71,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 private Box background = null!; private Box flashLayer = null!; private FormTextBox.InnerTextBox textBox = null!; - private Slider slider = null!; + private InnerSlider slider = null!; private FormFieldCaption caption = null!; private IFocusManager focusManager = null!; @@ -135,7 +135,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 }, TabbableContentContainer = tabbableContentContainer, }, - slider = new Slider + slider = new InnerSlider { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, @@ -163,6 +163,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 textBox.Current.BindValueChanged(textChanged); slider.IsDragging.BindValueChanged(_ => updateState()); + slider.Focused.BindValueChanged(_ => updateState()); current.ValueChanged += e => currentNumberInstantaneous.Value = e.NewValue; current.MinValueChanged += v => currentNumberInstantaneous.MinValue = v; @@ -259,16 +260,18 @@ namespace osu.Game.Graphics.UserInterfaceV2 private void updateState() { + bool childHasFocus = slider.Focused.Value || textBox.Focused.Value; + textBox.Alpha = 1; background.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Background4 : colourProvider.Background5; caption.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Foreground1 : colourProvider.Content2; textBox.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Foreground1 : colourProvider.Content1; - BorderThickness = IsHovered || textBox.Focused.Value || slider.IsDragging.Value ? 2 : 0; - BorderColour = textBox.Focused.Value ? colourProvider.Highlight1 : colourProvider.Light4; + BorderThickness = childHasFocus || IsHovered || slider.IsDragging.Value ? 2 : 0; + BorderColour = childHasFocus ? colourProvider.Highlight1 : colourProvider.Light4; - if (textBox.Focused.Value) + if (childHasFocus) background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark3); else if (IsHovered || slider.IsDragging.Value) background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark4); @@ -283,8 +286,10 @@ namespace osu.Game.Graphics.UserInterfaceV2 textBox.Text = slider.GetDisplayableValue(currentNumberInstantaneous.Value).ToString(); } - private partial class Slider : OsuSliderBar + private partial class InnerSlider : OsuSliderBar { + public BindableBool Focused { get; } = new BindableBool(); + public BindableBool IsDragging { get; set; } = new BindableBool(); public Action? OnCommit { get; set; } @@ -344,7 +349,6 @@ namespace osu.Game.Graphics.UserInterfaceV2 protected override void LoadComplete() { base.LoadComplete(); - updateState(); } @@ -382,11 +386,25 @@ namespace osu.Game.Graphics.UserInterfaceV2 base.OnHoverLost(e); } + protected override void OnFocus(FocusEvent e) + { + updateState(); + Focused.Value = true; + base.OnFocus(e); + } + + protected override void OnFocusLost(FocusLostEvent e) + { + updateState(); + Focused.Value = false; + base.OnFocusLost(e); + } + private void updateState() { rightBox.Colour = colourProvider.Background6; - leftBox.Colour = IsHovered || IsDragged ? colourProvider.Highlight1.Opacity(0.5f) : colourProvider.Dark2; - nub.Colour = IsHovered || IsDragged ? colourProvider.Highlight1 : colourProvider.Light4; + leftBox.Colour = HasFocus || IsHovered || IsDragged ? colourProvider.Highlight1.Opacity(0.5f) : colourProvider.Dark2; + nub.Colour = HasFocus || IsHovered || IsDragged ? colourProvider.Highlight1 : colourProvider.Light4; } protected override void UpdateValue(float value) From 940220b64942cb32e541649d90697625814385d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Oct 2024 19:57:39 +0900 Subject: [PATCH 662/924] Fix big oops --- .../Screens/Edit/Components/TernaryButtons/TernaryButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/TernaryButtons/TernaryButton.cs b/osu.Game/Screens/Edit/Components/TernaryButtons/TernaryButton.cs index 8d1bc8d9fc..b7aaf517f5 100644 --- a/osu.Game/Screens/Edit/Components/TernaryButtons/TernaryButton.cs +++ b/osu.Game/Screens/Edit/Components/TernaryButtons/TernaryButton.cs @@ -12,7 +12,7 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons { public readonly Bindable Bindable; - public readonly Bindable Enabled = new Bindable(); + public readonly Bindable Enabled = new Bindable(true); public readonly string Description; From 5b92a9ff595cbbc5c42f1f3d95d94c1c1c05f7ed Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 24 Oct 2024 13:15:09 +0200 Subject: [PATCH 663/924] Fix enabled state not updating drawable --- .../Edit/Components/TernaryButtons/DrawableTernaryButton.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs b/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs index aedae9fea1..fcbc719f46 100644 --- a/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs +++ b/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs @@ -60,6 +60,7 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons base.LoadComplete(); Button.Bindable.BindValueChanged(_ => updateSelectionState(), true); + Button.Enabled.BindTo(Enabled); Action = onAction; } From 88e88bdc4fcffe920c1f95f8592eaa9da2fe1624 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 24 Oct 2024 13:17:49 +0200 Subject: [PATCH 664/924] Fix addition banks disabled on reset --- .../Screens/Edit/Compose/Components/EditorSelectionHandler.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index 2d5341e85e..ca774c34e7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -259,6 +259,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void resetTernaryStates() { AutoSelectionBankEnabled.Value = true; + SelectionAdditionBanksEnabled.Value = true; SelectionBankStates[HIT_BANK_AUTO].Value = TernaryState.True; SelectionAdditionBankStates[HIT_BANK_AUTO].Value = TernaryState.True; } From 6e8400a95885f2e33588f909034f0a5474b1c52b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Oct 2024 20:37:38 +0900 Subject: [PATCH 665/924] Fix gap in `ShearedSliderBar` when focused --- osu.Game/Graphics/UserInterface/ShearedSliderBar.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ShearedSliderBar.cs b/osu.Game/Graphics/UserInterface/ShearedSliderBar.cs index f4c6f07262..a36b9c7a4c 100644 --- a/osu.Game/Graphics/UserInterface/ShearedSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/ShearedSliderBar.cs @@ -70,7 +70,6 @@ namespace osu.Game.Graphics.UserInterface CornerRadius = 5, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Padding = new MarginPadding { Horizontal = 2 }, Child = new Container { RelativeSizeAxes = Axes.Both, @@ -192,8 +191,8 @@ namespace osu.Game.Graphics.UserInterface protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); - LeftBox.Scale = new Vector2(Math.Clamp(RangePadding + Nub.DrawPosition.X - Nub.DrawWidth / 2.15f, 0, Math.Max(0, DrawWidth)), 1); - RightBox.Scale = new Vector2(Math.Clamp(DrawWidth - Nub.DrawPosition.X - RangePadding - Nub.DrawWidth / 2.15f, 0, Math.Max(0, DrawWidth)), 1); + LeftBox.Scale = new Vector2(Math.Clamp(RangePadding + Nub.DrawPosition.X - Nub.DrawWidth / 2.3f, 0, Math.Max(0, DrawWidth)), 1); + RightBox.Scale = new Vector2(Math.Clamp(DrawWidth - Nub.DrawPosition.X - RangePadding - Nub.DrawWidth / 2.3f, 0, Math.Max(0, DrawWidth)), 1); } protected override void UpdateValue(float value) From eb61da423299e50de211f10cd1e093f1ac444207 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 24 Oct 2024 21:22:09 +0200 Subject: [PATCH 666/924] only consider hit objects which are visible in timeline for contracted state --- .../Timeline/TimelineBlueprintContainer.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index c8448b745e..d4b9101747 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -32,6 +32,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved(CanBeNull = true)] private Timeline timeline { get; set; } + [Resolved(CanBeNull = true)] + private EditorClock editorClock { get; set; } + private Bindable placement; private SelectionBlueprint placementBlueprint; @@ -128,10 +131,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateSamplePointContractedState() { - // because only blueprints of objects which are alive (via pooling) are displayed in the timeline, it's feasible to do this every-update. - const double minimum_gap = 28; + if (timeline == null || editorClock == null) + return; + // Find the smallest time gap between any two sample point pieces double smallestTimeGap = double.PositiveInfinity; double lastTime = double.PositiveInfinity; @@ -141,6 +145,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { var hitObject = selectionBlueprint.Item; + // Only check the hit objects which are visible in the timeline + // SelectionBlueprints can contain hit objects which are not visible in the timeline due to selection keeping them alive + if (hitObject.StartTime > editorClock.CurrentTime + timeline.VisibleRange / 2) + continue; + + if (hitObject.GetEndTime() < editorClock.CurrentTime - timeline.VisibleRange / 2) + break; + if (hitObject is IHasRepeats hasRepeats) smallestTimeGap = Math.Min(smallestTimeGap, hasRepeats.Duration / hasRepeats.SpanCount() / 2); From 757416e6476ca8168b95c129e3d5ed15b49581a2 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 24 Oct 2024 16:19:00 -0400 Subject: [PATCH 667/924] Move added test coverage to `TestSceneSliderDrawing` --- .../Editor/TestSceneSliderDrawing.cs | 91 ++++++++++++++++--- 1 file changed, 77 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderDrawing.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderDrawing.cs index 3c8358b4cd..0e36c1dc45 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderDrawing.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderDrawing.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Components.RadioButtons; @@ -24,38 +25,57 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); [Test] - public void TestTouchInputAfterTouchingComposeArea() + public void TestTouchInputPlaceHitCircleDirectly() { AddStep("tap circle", () => tap(this.ChildrenOfType().Single(b => b.Button.Label == "HitCircle"))); - // this input is just for interacting with compose area - AddStep("tap playfield", () => tap(this.ChildrenOfType().Single())); - - AddStep("move current time", () => InputManager.Key(Key.Right)); - - AddStep("tap to place circle", () => tap(this.ChildrenOfType().Single().ToScreenSpace(new Vector2(10, 10)))); + AddStep("tap to place circle", () => tap(this.ChildrenOfType().Single())); AddAssert("circle placed correctly", () => { var circle = (HitCircle)EditorBeatmap.HitObjects.Single(h => h.StartTime == EditorClock.CurrentTimeAccurate); Assert.Multiple(() => { - Assert.That(circle.Position.X, Is.EqualTo(10f).Within(0.01f)); - Assert.That(circle.Position.Y, Is.EqualTo(10f).Within(0.01f)); + Assert.That(circle.Position.X, Is.EqualTo(256f).Within(0.01f)); + Assert.That(circle.Position.Y, Is.EqualTo(192f).Within(0.01f)); }); return true; }); + } - AddStep("tap slider", () => tap(this.ChildrenOfType().Single(b => b.Button.Label == "Slider"))); + [Test] + public void TestTouchInputPlaceCircleAfterTouchingComposeArea() + { + AddStep("tap circle", () => tap(this.ChildrenOfType().Single(b => b.Button.Label == "HitCircle"))); - // this input is just for interacting with compose area AddStep("tap playfield", () => tap(this.ChildrenOfType().Single())); + AddAssert("circle placed", () => EditorBeatmap.HitObjects.Single(h => h.StartTime == EditorClock.CurrentTimeAccurate) is HitCircle); - AddStep("move current time", () => InputManager.Key(Key.Right)); + AddStep("move forward", () => InputManager.Key(Key.Right)); + + AddStep("tap to place circle", () => tap(this.ChildrenOfType().Single())); + AddAssert("circle placed correctly", () => + { + var circle = (HitCircle)EditorBeatmap.HitObjects.Single(h => h.StartTime == EditorClock.CurrentTimeAccurate); + Assert.Multiple(() => + { + Assert.That(circle.Position.X, Is.EqualTo(256f).Within(0.01f)); + Assert.That(circle.Position.Y, Is.EqualTo(192f).Within(0.01f)); + }); + + return true; + }); + } + + [Test] + public void TestTouchInputPlaceSliderDirectly() + { + AddStep("tap slider", () => tap(this.ChildrenOfType().Single(b => b.Button.Label == "Slider"))); AddStep("hold to draw slider", () => InputManager.BeginTouch(new Touch(TouchSource.Touch1, this.ChildrenOfType().Single().ToScreenSpace(new Vector2(50, 20))))); AddStep("drag to draw", () => InputManager.MoveTouchTo(new Touch(TouchSource.Touch1, this.ChildrenOfType().Single().ToScreenSpace(new Vector2(200, 50))))); AddAssert("selection not initiated", () => this.ChildrenOfType().All(d => d.State == Visibility.Hidden)); + AddAssert("blueprint visible", () => this.ChildrenOfType().Single().Alpha > 0); AddStep("end", () => InputManager.EndTouch(new Touch(TouchSource.Touch1, InputManager.CurrentState.Touch.GetTouchPosition(TouchSource.Touch1)!.Value))); AddAssert("slider placed correctly", () => { @@ -76,12 +96,55 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor }); } - private void tap(Drawable drawable) => tap(drawable.ScreenSpaceDrawQuad.Centre); + [Test] + public void TestTouchInputPlaceSliderAfterTouchingComposeArea() + { + AddStep("tap slider", () => tap(this.ChildrenOfType().Single(b => b.Button.Label == "Slider"))); + + AddStep("tap playfield", () => tap(this.ChildrenOfType().Single())); + AddStep("tap and hold another spot", () => hold(this.ChildrenOfType().Single(), new Vector2(50, 0))); + AddUntilStep("wait for slider placement", () => EditorBeatmap.HitObjects.SingleOrDefault(h => h.StartTime == EditorClock.CurrentTimeAccurate) is Slider); + AddStep("end", () => InputManager.EndTouch(new Touch(TouchSource.Touch1, InputManager.CurrentState.Touch.GetTouchPosition(TouchSource.Touch1)!.Value))); + + AddStep("move forward", () => InputManager.Key(Key.Right)); + + AddStep("hold to draw slider", () => InputManager.BeginTouch(new Touch(TouchSource.Touch1, this.ChildrenOfType().Single().ToScreenSpace(new Vector2(50, 20))))); + AddStep("drag to draw", () => InputManager.MoveTouchTo(new Touch(TouchSource.Touch1, this.ChildrenOfType().Single().ToScreenSpace(new Vector2(200, 50))))); + AddAssert("selection not initiated", () => this.ChildrenOfType().All(d => d.State == Visibility.Hidden)); + AddAssert("blueprint visible", () => this.ChildrenOfType().Single().IsPresent); + AddStep("end", () => InputManager.EndTouch(new Touch(TouchSource.Touch1, InputManager.CurrentState.Touch.GetTouchPosition(TouchSource.Touch1)!.Value))); + AddAssert("slider placed correctly", () => + { + var slider = (Slider)EditorBeatmap.HitObjects.Single(h => h.StartTime == EditorClock.CurrentTimeAccurate); + Assert.Multiple(() => + { + Assert.That(slider.Position.X, Is.EqualTo(50f).Within(0.01f)); + Assert.That(slider.Position.Y, Is.EqualTo(20f).Within(0.01f)); + Assert.That(slider.Path.ControlPoints.Count, Is.EqualTo(2)); + Assert.That(slider.Path.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); + + // the final position may be slightly off from the mouse position when drawing, account for that. + Assert.That(slider.Path.ControlPoints[1].Position.X, Is.EqualTo(150).Within(5)); + Assert.That(slider.Path.ControlPoints[1].Position.Y, Is.EqualTo(30).Within(5)); + }); + + return true; + }); + } + + private void tap(Drawable drawable, Vector2 offset = default) => tap(drawable.ToScreenSpace(drawable.LayoutRectangle.Centre + offset)); private void tap(Vector2 position) { - InputManager.BeginTouch(new Touch(TouchSource.Touch1, position)); + hold(position); InputManager.EndTouch(new Touch(TouchSource.Touch1, position)); } + + private void hold(Drawable drawable, Vector2 offset = default) => hold(drawable.ToScreenSpace(drawable.LayoutRectangle.Centre + offset)); + + private void hold(Vector2 position) + { + InputManager.BeginTouch(new Touch(TouchSource.Touch1, position)); + } } } From 1d559b2cade00341422b7efbf27f67dda0040489 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 24 Oct 2024 16:20:49 -0400 Subject: [PATCH 668/924] Remove fade transitions --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index a4a35fcd5c..52b8a5c796 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -129,15 +129,8 @@ namespace osu.Game.Rulesets.Edit } } - protected override void PopIn() - { - this.FadeIn(200, Easing.OutQuint); - } - - protected override void PopOut() - { - this.FadeOut(200, Easing.OutQuint); - } + protected override void PopIn() => this.FadeIn(); + protected override void PopOut() => this.FadeOut(); public enum PlacementState { From 5e642cbce72862b29b117829e7ec90d6d6b0ce1e Mon Sep 17 00:00:00 2001 From: Darius Wattimena Date: Thu, 24 Oct 2024 23:17:47 +0200 Subject: [PATCH 669/924] Apply code feedback and also resize catcher trails when any is shown --- .../Edit/CatchEditorPlayfield.cs | 5 ----- .../Edit/DrawableCatchEditorRuleset.cs | 9 ++++++++- osu.Game.Rulesets.Catch/UI/Catcher.cs | 6 +++--- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 8 ++++---- .../UI/CatcherTrailDisplay.cs | 20 +++++++++++++++++++ 5 files changed, 35 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfield.cs b/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfield.cs index 78f5c3e9ed..3967c54f4b 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfield.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfield.cs @@ -22,10 +22,5 @@ namespace osu.Game.Rulesets.Catch.Edit // TODO: disable hit lighting as well } - - public void ApplyCircleSizeToCatcher(IBeatmapDifficultyInfo difficulty) - { - Catcher.SetScaleAndCatchWidth(difficulty); - } } } diff --git a/osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs b/osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs index cd6d490a7f..86cdc2df5c 100644 --- a/osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs +++ b/osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs @@ -49,7 +49,14 @@ namespace osu.Game.Rulesets.Catch.Edit editorBeatmap.BeatmapReprocessed -= onBeatmapReprocessed; } - private void onBeatmapReprocessed() => (Playfield as CatchEditorPlayfield)?.ApplyCircleSizeToCatcher(editorBeatmap.Difficulty); + private void onBeatmapReprocessed() + { + if (Playfield is CatchEditorPlayfield catchPlayfield) + { + catchPlayfield.Catcher.ApplyDifficulty(editorBeatmap.Difficulty); + catchPlayfield.CatcherArea.CatcherTrails.UpdateCatcherTrailsScale(catchPlayfield.Catcher.BodyScale); + } + } protected override Playfield CreatePlayfield() => new CatchEditorPlayfield(Beatmap.Difficulty); diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 3a0863473a..ebf3e47fd7 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Catch.UI /// /// Width of the area that can be used to attempt catches during gameplay. /// - public float CatchWidth; + public float CatchWidth { get; private set; } private readonly SkinnableCatcher body; @@ -142,7 +142,7 @@ namespace osu.Game.Rulesets.Catch.UI Size = new Vector2(BASE_SIZE); - SetScaleAndCatchWidth(difficulty); + ApplyDifficulty(difficulty); InternalChildren = new Drawable[] { @@ -312,7 +312,7 @@ namespace osu.Game.Rulesets.Catch.UI /// /// Set the scale and catch width. /// - public void SetScaleAndCatchWidth(IBeatmapDifficultyInfo? difficulty) + public void ApplyDifficulty(IBeatmapDifficultyInfo? difficulty) { if (difficulty != null) Scale = calculateScale(difficulty); diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 338e1364a9..7f0a79ef49 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Catch.UI private readonly CatchComboDisplay comboDisplay; - private readonly CatcherTrailDisplay catcherTrails; + public readonly CatcherTrailDisplay CatcherTrails; private Catcher catcher = null!; @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Catch.UI Children = new Drawable[] { catcherContainer = new Container { RelativeSizeAxes = Axes.Both }, - catcherTrails = new CatcherTrailDisplay(), + CatcherTrails = new CatcherTrailDisplay(), comboDisplay = new CatchComboDisplay { RelativeSizeAxes = Axes.None, @@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Catch.UI { const double trail_generation_interval = 16; - if (Time.Current - catcherTrails.LastDashTrailTime >= trail_generation_interval) + if (Time.Current - CatcherTrails.LastDashTrailTime >= trail_generation_interval) displayCatcherTrail(Catcher.HyperDashing ? CatcherTrailAnimation.HyperDashing : CatcherTrailAnimation.Dashing); } @@ -170,6 +170,6 @@ namespace osu.Game.Rulesets.Catch.UI } } - private void displayCatcherTrail(CatcherTrailAnimation animation) => catcherTrails.Add(new CatcherTrailEntry(Time.Current, Catcher.CurrentState, Catcher.X, Catcher.BodyScale, animation)); + private void displayCatcherTrail(CatcherTrailAnimation animation) => CatcherTrails.Add(new CatcherTrailEntry(Time.Current, Catcher.CurrentState, Catcher.X, Catcher.BodyScale, animation)); } } diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs index e3e01c1b39..6a9162da91 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Pooling; using osu.Game.Rulesets.Catch.Skinning; using osu.Game.Rulesets.Objects.Pooling; using osu.Game.Skinning; +using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.UI @@ -55,6 +56,25 @@ namespace osu.Game.Rulesets.Catch.UI }; } + /// + /// Update the scale of all trails. + /// + /// The new body scale of the Catcher + public void UpdateCatcherTrailsScale(Vector2 scale) + { + applyScaleChange(scale, dashTrails); + applyScaleChange(scale, hyperDashTrails); + applyScaleChange(scale, hyperDashAfterImages); + } + + private void applyScaleChange(Vector2 scale, Container trails) + { + foreach (var trail in trails) + { + trail.Scale = scale; + } + } + protected override void LoadComplete() { base.LoadComplete(); From 2b14d78f04083afb3f8e067ce00554fdfdc5e41f Mon Sep 17 00:00:00 2001 From: Darius Wattimena Date: Thu, 24 Oct 2024 23:40:23 +0200 Subject: [PATCH 670/924] Hide the after image instead as the effect can't be resized easily --- osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs index 6a9162da91..ad2ae53f48 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs @@ -64,7 +64,12 @@ namespace osu.Game.Rulesets.Catch.UI { applyScaleChange(scale, dashTrails); applyScaleChange(scale, hyperDashTrails); - applyScaleChange(scale, hyperDashAfterImages); + + foreach (var afterImage in hyperDashAfterImages) + { + afterImage.Hide(); + afterImage.Expire(); + } } private void applyScaleChange(Vector2 scale, Container trails) From c666fa7472f9dc8a0fb529ba85816151198d8973 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Oct 2024 16:09:08 +0900 Subject: [PATCH 671/924] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 7844dcc1fe..f271bdfaaf 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index 9fc7cdf453..ecb9ae2c8d 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + From 1fc221bb396c0c0fa626c49cc80e154654ea73e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Oct 2024 16:13:02 +0900 Subject: [PATCH 672/924] Fix focus glow appearing above range slider's nubs --- osu.Game/Graphics/UserInterface/RangeSlider.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/RangeSlider.cs b/osu.Game/Graphics/UserInterface/RangeSlider.cs index f83dff6295..45c7f713ee 100644 --- a/osu.Game/Graphics/UserInterface/RangeSlider.cs +++ b/osu.Game/Graphics/UserInterface/RangeSlider.cs @@ -115,7 +115,9 @@ namespace osu.Game.Graphics.UserInterface KeyboardStep = 0.1f, RelativeSizeAxes = Axes.X, Y = vertical_offset, - } + }, + upperBound.Nub.CreateProxy(), + lowerBound.Nub.CreateProxy(), }; } @@ -160,6 +162,8 @@ namespace osu.Game.Graphics.UserInterface protected partial class BoundSlider : RoundedSliderBar { + public new Nub Nub => base.Nub; + public string? DefaultString; public LocalisableString? DefaultTooltip; public string? TooltipSuffix; From 68e8819f3bd8eec0162301074e04a1583d81afff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Oct 2024 17:17:19 +0900 Subject: [PATCH 673/924] Fix taiko playfield looking weird with new editor toolbox displays --- .../Edit/DrawableTaikoEditorRuleset.cs | 3 +++ .../Edit/TaikoEditorPlayfield.cs | 25 +++++++++++++++++++ osu.Game/Rulesets/Edit/HitObjectComposer.cs | 4 +-- 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/Edit/TaikoEditorPlayfield.cs diff --git a/osu.Game.Rulesets.Taiko/Edit/DrawableTaikoEditorRuleset.cs b/osu.Game.Rulesets.Taiko/Edit/DrawableTaikoEditorRuleset.cs index 217bb8139c..147ceb3ba1 100644 --- a/osu.Game.Rulesets.Taiko/Edit/DrawableTaikoEditorRuleset.cs +++ b/osu.Game.Rulesets.Taiko/Edit/DrawableTaikoEditorRuleset.cs @@ -7,6 +7,7 @@ using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.UI; +using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Taiko.Edit @@ -20,6 +21,8 @@ namespace osu.Game.Rulesets.Taiko.Edit { } + protected override Playfield CreatePlayfield() => new TaikoEditorPlayfield(); + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoEditorPlayfield.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoEditorPlayfield.cs new file mode 100644 index 0000000000..760ed71662 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoEditorPlayfield.cs @@ -0,0 +1,25 @@ +// 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.Game.Rulesets.Taiko.UI; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Taiko.Edit +{ + public partial class TaikoEditorPlayfield : TaikoPlayfield + { + [BackgroundDependencyLoader] + private void load() + { + // This is the simplest way to extend the taiko playfield beyond the left of the drum area. + // Required in the editor to not look weird underneath left toolbox area. + AddInternal(new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.PlayfieldBackgroundRight), _ => new PlayfieldBackgroundRight()) + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopRight, + }); + } + } +} diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 0499e10607..b24e09accf 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -303,8 +303,8 @@ namespace osu.Game.Rulesets.Edit PlayfieldContentContainer.Anchor = Anchor.CentreLeft; PlayfieldContentContainer.Origin = Anchor.CentreLeft; - PlayfieldContentContainer.Width = Math.Max(1024, DrawWidth) - (TOOLBOX_CONTRACTED_SIZE_LEFT + TOOLBOX_CONTRACTED_SIZE_RIGHT); - PlayfieldContentContainer.X = TOOLBOX_CONTRACTED_SIZE_LEFT; + PlayfieldContentContainer.Width = Math.Max(1024, DrawWidth); + PlayfieldContentContainer.X = LeftToolbox.DrawWidth; } composerFocusMode.Value = PlayfieldContentContainer.Contains(InputManager.CurrentState.Mouse.Position) From 9902c22f5c91afb3a804f632dc89d13e3ab44025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 25 Oct 2024 13:16:24 +0200 Subject: [PATCH 674/924] Do not fall back to beatmap's original ruleset if conversion fails I don't know why this was ever a good idea, and would say that we want this to fail *hard* not soft. If things ever get in this state, things have gone *seriously* wrong elsewhere, and need to be fixed there. --- osu.Game/Screens/Play/Player.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 536050c9bd..398e8df5c9 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -562,11 +562,8 @@ namespace osu.Game.Screens.Play } catch (BeatmapInvalidForRulesetException) { - // A playable beatmap may not be creatable with the user's preferred ruleset, so try using the beatmap's default ruleset - rulesetInfo = Beatmap.Value.BeatmapInfo.Ruleset; - ruleset = rulesetInfo.CreateInstance(); - - playable = Beatmap.Value.GetPlayableBeatmap(rulesetInfo, gameplayMods, cancellationToken); + Logger.Log($"The current beatmap is not playable in {ruleset.RulesetInfo.Name}!", level: LogLevel.Important); + return null; } if (playable.HitObjects.Count == 0) From f5071c205f62cefdc0c64abfd212c77f1c4b9837 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Sat, 26 Oct 2024 01:45:51 +0900 Subject: [PATCH 675/924] Increase ducking duration when selecting Mania ruleset --- osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index 05ab505417..a979575a0b 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs @@ -122,7 +122,10 @@ namespace osu.Game.Overlays.Toolbar rulesetSelectionChannel[r.NewValue] = channel; channel.Play(); - musicController?.DuckMomentarily(500, new DuckParameters { DuckDuration = 0 }); + + // Longer unduck delay for Mania sample + int unduckDelay = r.NewValue.OnlineID == 3 ? 750 : 500; + musicController?.DuckMomentarily(unduckDelay, new DuckParameters { DuckDuration = 0 }); } public override bool HandleNonPositionalInput => !Current.Disabled && base.HandleNonPositionalInput; From 0b3d906e318f40095789c80eceec2cb6a67c0201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 25 Oct 2024 19:18:09 +0200 Subject: [PATCH 676/924] Fix test failures --- osu.Game.Rulesets.Taiko.Tests/Judgements/JudgementTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Taiko.Tests/Judgements/JudgementTest.cs b/osu.Game.Rulesets.Taiko.Tests/Judgements/JudgementTest.cs index f3e37736b2..30ecec2366 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Judgements/JudgementTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Judgements/JudgementTest.cs @@ -43,6 +43,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Judgements AddStep("load player", () => { Beatmap.Value = CreateWorkingBeatmap(beatmap); + Ruleset.Value = new TaikoRuleset().RulesetInfo; SelectedMods.Value = mods ?? Array.Empty(); var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }); From e96d593b1fa4030ff8da1cedd2b86836735a4144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 25 Oct 2024 19:58:31 +0200 Subject: [PATCH 677/924] Fix redundant array type specification --- osu.Game/Graphics/UserInterface/RangeSlider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/RangeSlider.cs b/osu.Game/Graphics/UserInterface/RangeSlider.cs index 45c7f713ee..422c2ca4a3 100644 --- a/osu.Game/Graphics/UserInterface/RangeSlider.cs +++ b/osu.Game/Graphics/UserInterface/RangeSlider.cs @@ -98,7 +98,7 @@ namespace osu.Game.Graphics.UserInterface { const float vertical_offset = 13; - InternalChildren = new Drawable[] + InternalChildren = new[] { label = new OsuSpriteText { From 36bcc5896ced539dae14a8e78093ba5f0ab81248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 25 Oct 2024 14:40:37 +0200 Subject: [PATCH 678/924] Add failing test case --- .../TestSceneMultiplayerMatchSongSelect.cs | 43 ++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 88cc7eb9b3..bd635b1669 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -15,6 +16,7 @@ using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Online.Rooms; using osu.Game.Overlays.Mods; @@ -42,6 +44,9 @@ namespace osu.Game.Tests.Visual.Multiplayer private Live importedBeatmapSet; + [Resolved] + private OsuConfigManager configManager { get; set; } + [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { @@ -57,10 +62,8 @@ namespace osu.Game.Tests.Visual.Multiplayer Add(detachedBeatmapStore); } - public override void SetUpSteps() + private void setUp() { - base.SetUpSteps(); - AddStep("reset", () => { Ruleset.Value = new OsuRuleset().RulesetInfo; @@ -75,6 +78,8 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestSelectFreeMods() { + setUp(); + AddStep("set some freemods", () => songSelect.FreeMods.Value = new OsuRuleset().GetModsFor(ModType.Fun).ToArray()); AddStep("set all freemods", () => songSelect.FreeMods.Value = new OsuRuleset().CreateAllMods().ToArray()); AddStep("set no freemods", () => songSelect.FreeMods.Value = Array.Empty()); @@ -85,6 +90,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { BeatmapInfo selectedBeatmap = null; + setUp(); + AddStep("change ruleset", () => Ruleset.Value = new TaikoRuleset().RulesetInfo); AddStep("select beatmap", () => songSelect.Carousel.SelectBeatmap(selectedBeatmap = beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == new TaikoRuleset().LegacyID))); @@ -107,6 +114,8 @@ namespace osu.Game.Tests.Visual.Multiplayer [TestCase(typeof(OsuModHidden), typeof(OsuModTraceable))] // Incompatible. public void TestAllowedModDeselectedWhenRequired(Type allowedMod, Type requiredMod) { + setUp(); + AddStep("change ruleset", () => Ruleset.Value = new OsuRuleset().RulesetInfo); AddStep($"select {allowedMod.ReadableName()} as allowed", () => songSelect.FreeMods.Value = new[] { (Mod)Activator.CreateInstance(allowedMod) }); AddStep($"select {requiredMod.ReadableName()} as required", () => songSelect.Mods.Value = new[] { (Mod)Activator.CreateInstance(requiredMod) }); @@ -120,6 +129,30 @@ namespace osu.Game.Tests.Visual.Multiplayer assertFreeModNotShown(requiredMod); } + [Test] + public void TestChangeRulesetImmediatelyAfterLoadComplete() + { + AddStep("reset", () => + { + configManager.SetValue(OsuSetting.ShowConvertedBeatmaps, false); + Beatmap.SetDefault(); + SelectedMods.SetDefault(); + }); + + AddStep("create song select", () => + { + SelectedRoom.Value.Playlist.Single().RulesetID = 2; + songSelect = new TestMultiplayerMatchSongSelect(SelectedRoom.Value, SelectedRoom.Value.Playlist.Single()); + songSelect.OnLoadComplete += _ => Ruleset.Value = new TaikoRuleset().RulesetInfo; + LoadScreen(songSelect); + }); + AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && songSelect.BeatmapSetsLoaded); + + AddStep("confirm selection", () => songSelect.FinaliseSelection()); + AddAssert("beatmap is taiko", () => Beatmap.Value.BeatmapInfo.Ruleset.OnlineID, () => Is.EqualTo(1)); + AddAssert("ruleset is taiko", () => Ruleset.Value.OnlineID, () => Is.EqualTo(1)); + } + private void assertFreeModNotShown(Type type) { AddAssert($"{type.ReadableName()} not displayed in freemod overlay", @@ -138,8 +171,8 @@ namespace osu.Game.Tests.Visual.Multiplayer public new BeatmapCarousel Carousel => base.Carousel; - public TestMultiplayerMatchSongSelect(Room room) - : base(room) + public TestMultiplayerMatchSongSelect(Room room, [CanBeNull] PlaylistItem itemToEdit = null) + : base(room, itemToEdit) { } } From b78e7d5d9ae987c383c5c65689e9a62efb6c4b01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 25 Oct 2024 15:51:26 +0200 Subject: [PATCH 679/924] Fix multiplayer song select not correctly applying filter sometimes Fixes the root client-side failure causing https://github.com/ppy/osu/issues/30415. Thread of breakage is as follows: 1. `SongSelect` loads the carousel. At this point, the ruleset is what the ambient ruleset would have been at the time of pushing song select, so most likely it will match the current ruleset. Notably, the carousel is loaded with `AllowSelection == false`. 2. `OnlinePlaySongSelect` sets the ruleset to the one taken from the relevant playlist item in `LoadComplete()`. 3. At any point between the previous and the next step, the user changes the ruleset manually. 4. `SongSelect.carouselBeatmapsLoaded()` is ran, which calls `transferRulesetValue()`, which calls `FilterControl.FilterChanged`. But at this stage `Carousel.AllowSelection` is still false, so the filter is not executed, but `pendingFilterApplication` is set instead. Unfortunately, the pending filter never gets applied after that. The only place that checks that flag is `OnEntering()`, which at this point has already ran. To fix, move the `pendingFilterApplication` check to `Update()`, which seems like the most obvious and safe solution. --- osu.Game/Screens/Select/SongSelect.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index ea5048ca49..9f7a2c02ff 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -711,12 +711,6 @@ namespace osu.Game.Screens.Select Carousel.AllowSelection = true; - if (pendingFilterApplication) - { - Carousel.Filter(FilterControl.CreateCriteria()); - pendingFilterApplication = false; - } - BeatmapDetails.Refresh(); beginLooping(); @@ -749,6 +743,17 @@ namespace osu.Game.Screens.Select FilterControl.Activate(); } + protected override void Update() + { + base.Update(); + + if (Carousel.AllowSelection && pendingFilterApplication) + { + Carousel.Filter(FilterControl.CreateCriteria()); + pendingFilterApplication = false; + } + } + public override void OnSuspending(ScreenTransitionEvent e) { // Handle the case where FinaliseSelection is never called (ie. when a screen is pushed externally). From 0e65655feffcbe38f6e9b9573e16227c7225bbc3 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Fri, 25 Oct 2024 23:15:16 -0400 Subject: [PATCH 680/924] Remove redundant condition in else branch Yep. --- .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index d74152e78e..ef4f134766 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -325,7 +325,7 @@ namespace osu.Game.Screens.Edit.Compose.Components case PlacementBlueprint.PlacementState.Waiting: if (!Composer.CursorInPlacementArea) CurrentPlacement.Hide(); - else if (Composer.CursorInPlacementArea) + else CurrentPlacement.Show(); break; From 86989aa5ce8f835f03f3c8bb923346bf2456a6b6 Mon Sep 17 00:00:00 2001 From: StanR Date: Sun, 27 Oct 2024 13:40:29 +0500 Subject: [PATCH 681/924] Fix `UserRankPanel` rank overflowing on 6+ digits --- .../Visual/Online/TestSceneUserPanel.cs | 20 +++++++++++++------ osu.Game/Users/UserRankPanel.cs | 2 +- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index 91942c391a..ab3d5907ef 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -55,21 +55,21 @@ namespace osu.Game.Tests.Visual.Online { Username = @"flyte", Id = 3103765, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" + CoverUrl = @"https://assets.ppy.sh/user-cover-presets/1/df28696b58541a9e67f6755918951d542d93bdf1da41720fcca2fd2c1ea8cf51.jpeg", }), new UserBrickPanel(new APIUser { Username = @"peppy", Id = 2, Colour = "99EB47", - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg", }), new UserGridPanel(new APIUser { Username = @"flyte", Id = 3103765, CountryCode = CountryCode.JP, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg", + CoverUrl = @"https://assets.ppy.sh/user-cover-presets/1/df28696b58541a9e67f6755918951d542d93bdf1da41720fcca2fd2c1ea8cf51.jpeg", Status = { Value = UserStatus.Online } }) { Width = 300 }, boundPanel1 = new UserGridPanel(new APIUser @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Online Username = @"peppy", Id = 2, CountryCode = CountryCode.AU, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg", IsSupporter = true, SupportLevel = 3, }) { Width = 300 }, @@ -95,7 +95,7 @@ namespace osu.Game.Tests.Visual.Online Username = @"flyte", Id = 3103765, CountryCode = CountryCode.JP, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg", + CoverUrl = @"https://assets.ppy.sh/user-cover-presets/1/df28696b58541a9e67f6755918951d542d93bdf1da41720fcca2fd2c1ea8cf51.jpeg", Statistics = new UserStatistics { GlobalRank = 12345, CountryRank = 1234 } }) { Width = 300 }, new UserRankPanel(new APIUser @@ -104,7 +104,7 @@ namespace osu.Game.Tests.Visual.Online Id = 2, Colour = "99EB47", CountryCode = CountryCode.AU, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg", Statistics = new UserStatistics { GlobalRank = null, CountryRank = null } }) { Width = 300 } } @@ -168,6 +168,14 @@ namespace osu.Game.Tests.Visual.Online CountryRank = RNG.Next(100000) }); }); + AddStep("set statistics to something big", () => + { + API.UpdateStatistics(new UserStatistics + { + GlobalRank = RNG.Next(1_000_000, 100_000_000), + CountryRank = RNG.Next(1_000_000, 100_000_000) + }); + }); AddStep("set statistics to empty", () => { API.UpdateStatistics(new UserStatistics()); diff --git a/osu.Game/Users/UserRankPanel.cs b/osu.Game/Users/UserRankPanel.cs index 0d57b7bb7d..6d8d7dd143 100644 --- a/osu.Game/Users/UserRankPanel.cs +++ b/osu.Game/Users/UserRankPanel.cs @@ -152,7 +152,7 @@ namespace osu.Game.Users Margin = new MarginPadding { Top = main_content_height }, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Left = 80, Vertical = padding }, + Padding = new MarginPadding(padding), ColumnDimensions = new[] { new Dimension(), From 74dc0dc4806c22fdf353f8297f3bbd874247c7a9 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 29 Oct 2024 20:21:26 -0700 Subject: [PATCH 682/924] Fix editor sidebar resizing on hover repeatedly when polygon popover is opened --- osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs index 6325de5851..a2ee4a888d 100644 --- a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs @@ -53,6 +53,8 @@ namespace osu.Game.Rulesets.Osu.Edit [BackgroundDependencyLoader] private void load() { + AllowableAnchors = new[] { Anchor.CentreLeft, Anchor.CentreRight }; + Child = new FillFlowContainer { Width = 220, From 40c2d4e942453b583ff3dc41368992b347a474dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 30 Oct 2024 07:35:00 +0100 Subject: [PATCH 683/924] Adjust test to match desired reality --- osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs b/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs index 11c4c54ea6..33d1e5c91e 100644 --- a/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs +++ b/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs @@ -241,8 +241,8 @@ namespace osu.Game.Tests.Beatmaps metadataLookup.Update(beatmapSet, preferOnlineFetch); - Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.None)); - Assert.That(beatmap.OnlineID, Is.EqualTo(-1)); + Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked)); + Assert.That(beatmap.OnlineID, Is.EqualTo(654321)); } [Test] From 1a2e323c11158aab0433f144d1595e06f7a1fe20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 30 Oct 2024 07:40:08 +0100 Subject: [PATCH 684/924] Remove problematic online ID check --- osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs index 034ec31ee4..42d8e07432 100644 --- a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs +++ b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs @@ -85,11 +85,11 @@ namespace osu.Game.Beatmaps private bool shouldDiscardLookupResult(OnlineBeatmapMetadata result, BeatmapInfo beatmapInfo) { - if (beatmapInfo.OnlineID > 0 && result.BeatmapID != beatmapInfo.OnlineID) - { - Logger.Log($"Discarding metadata lookup result due to mismatching online ID (expected: {beatmapInfo.OnlineID} actual: {result.BeatmapID})", LoggingTarget.Database); - return true; - } + // previously this used to check whether the `OnlineID` of the looked-up beatmap matches the local `OnlineID`. + // unfortunately it appears that historically stable mappers would apply crude hacks to fix unspecified "issues" with submission + // which would amount to reusing online IDs of other beatmaps. + // this means that the online ID in the `.osu` file is not reliable, and this cannot be fixed server-side + // because updating the maps retroactively would break stable (by losing all of users' local scores). if (beatmapInfo.OnlineID == -1 && result.MD5Hash != beatmapInfo.MD5Hash) { From b42fa23e42bfd449f534a73d3aefe9551827ff80 Mon Sep 17 00:00:00 2001 From: Luke Knight Date: Wed, 30 Oct 2024 02:04:03 -0500 Subject: [PATCH 685/924] Prevent key bind conflict on reversion --- osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs index ddf831c23e..97ebde4e2c 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs @@ -222,7 +222,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input var button = buttons[i++]; button.UpdateKeyCombination(d); - tryPersistKeyBinding(button.KeyBinding.Value, advanceToNextBinding: false); + tryPersistKeyBinding(button.KeyBinding.Value, advanceToNextBinding: false, restoringBinding: true); } isDefault.Value = true; @@ -489,13 +489,12 @@ namespace osu.Game.Overlays.Settings.Sections.Input base.OnFocusLost(e); } - private void tryPersistKeyBinding(RealmKeyBinding keyBinding, bool advanceToNextBinding) + private void tryPersistKeyBinding(RealmKeyBinding keyBinding, bool advanceToNextBinding, bool restoringBinding = false) { List bindings = GetAllSectionBindings(); RealmKeyBinding? existingBinding = keyBinding.KeyCombination.Equals(new KeyCombination(InputKey.None)) ? null - : bindings.FirstOrDefault(other => other.ID != keyBinding.ID && other.KeyCombination.Equals(keyBinding.KeyCombination)); - + : bindings.FirstOrDefault(other => other.ID != keyBinding.ID && other.KeyCombination.Equals(keyBinding.KeyCombination) && (!restoringBinding || other.ActionInt != keyBinding.ActionInt)); if (existingBinding == null) { realm.Write(r => r.Find(keyBinding.ID)!.KeyCombinationString = keyBinding.KeyCombination.ToString()); From 776fabd77c5a6225e69a0b14e79b9e097692320c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 30 Oct 2024 08:00:57 +0100 Subject: [PATCH 686/924] Only use MD5 when performing metadata lookups Both online and offline using the cache. The rationale behind this change is that in the current state of affairs, `TestPartiallyMaliciousSet()` fails in a way that cannot be reconciled without this sort of change. The test exercises a scenario where the beatmap being imported has an online ID in the `.osu` file, but its hash does not match the online hash of the beatmap. This turns out to be a more frequent scenario than envisioned because of users doing stupid things with manual file editing rather than reporting issues properly. The scenario is realistic only because the behaviour of the endpoint responsible for looking up beatmaps is such that if multiple parameters are given (e.g. all three of beatmap MD5, online ID, and filename), it will try the three in succession: https://github.com/ppy/osu-web/blob/f6b341813be270de59ec8f9698428aa6422bc8ae/app/Http/Controllers/BeatmapsController.php#L260-L266 and the local metadata cache implementation reflected this implementation. Because online ID and filename are inherently unreliable in this scenario due to being directly manipulable by clueless or malicious users, neither should not be used as a fallback. --- osu.Game/Beatmaps/APIBeatmapMetadataSource.cs | 2 +- .../LocalCachedBeatmapMetadataSource.cs | 12 +++------- .../Online/API/Requests/GetBeatmapRequest.cs | 22 +++++++++++++------ .../OnlinePlay/TestRoomRequestsHandler.cs | 2 +- 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/osu.Game/Beatmaps/APIBeatmapMetadataSource.cs b/osu.Game/Beatmaps/APIBeatmapMetadataSource.cs index a2eebe6161..9c292a4cfb 100644 --- a/osu.Game/Beatmaps/APIBeatmapMetadataSource.cs +++ b/osu.Game/Beatmaps/APIBeatmapMetadataSource.cs @@ -33,7 +33,7 @@ namespace osu.Game.Beatmaps Debug.Assert(beatmapInfo.BeatmapSet != null); - var req = new GetBeatmapRequest(beatmapInfo); + var req = new GetBeatmapRequest(beatmapInfo.MD5Hash); try { diff --git a/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs b/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs index eaa4d8ebfb..39afe5f519 100644 --- a/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs +++ b/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs @@ -89,9 +89,7 @@ namespace osu.Game.Beatmaps return false; } - if (string.IsNullOrEmpty(beatmapInfo.MD5Hash) - && string.IsNullOrEmpty(beatmapInfo.Path) - && beatmapInfo.OnlineID <= 0) + if (string.IsNullOrEmpty(beatmapInfo.MD5Hash)) { onlineMetadata = null; return false; @@ -240,11 +238,9 @@ namespace osu.Game.Beatmaps using var cmd = db.CreateCommand(); cmd.CommandText = - @"SELECT beatmapset_id, beatmap_id, approved, user_id, checksum, last_update FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineID OR filename = @Path"; + @"SELECT beatmapset_id, beatmap_id, approved, user_id, checksum, last_update FROM osu_beatmaps WHERE checksum = @MD5Hash"; cmd.Parameters.Add(new SqliteParameter(@"@MD5Hash", beatmapInfo.MD5Hash)); - cmd.Parameters.Add(new SqliteParameter(@"@OnlineID", beatmapInfo.OnlineID)); - cmd.Parameters.Add(new SqliteParameter(@"@Path", beatmapInfo.Path)); using var reader = cmd.ExecuteReader(); @@ -281,12 +277,10 @@ namespace osu.Game.Beatmaps SELECT `b`.`beatmapset_id`, `b`.`beatmap_id`, `b`.`approved`, `b`.`user_id`, `b`.`checksum`, `b`.`last_update`, `s`.`submit_date`, `s`.`approved_date` FROM `osu_beatmaps` AS `b` JOIN `osu_beatmapsets` AS `s` ON `s`.`beatmapset_id` = `b`.`beatmapset_id` - WHERE `b`.`checksum` = @MD5Hash OR `b`.`beatmap_id` = @OnlineID OR `b`.`filename` = @Path + WHERE `b`.`checksum` = @MD5Hash """; cmd.Parameters.Add(new SqliteParameter(@"@MD5Hash", beatmapInfo.MD5Hash)); - cmd.Parameters.Add(new SqliteParameter(@"@OnlineID", beatmapInfo.OnlineID)); - cmd.Parameters.Add(new SqliteParameter(@"@Path", beatmapInfo.Path)); using var reader = cmd.ExecuteReader(); diff --git a/osu.Game/Online/API/Requests/GetBeatmapRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs index 3383d21dfc..091f336b71 100644 --- a/osu.Game/Online/API/Requests/GetBeatmapRequest.cs +++ b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Globalization; using osu.Framework.IO.Network; using osu.Game.Beatmaps; using osu.Game.Online.API.Requests.Responses; @@ -9,23 +10,30 @@ namespace osu.Game.Online.API.Requests { public class GetBeatmapRequest : APIRequest { - public readonly IBeatmapInfo BeatmapInfo; - public readonly string Filename; + public readonly int OnlineID = -1; + public readonly string? MD5Hash; + public readonly string? Filename; public GetBeatmapRequest(IBeatmapInfo beatmapInfo) { - BeatmapInfo = beatmapInfo; + OnlineID = beatmapInfo.OnlineID; + MD5Hash = beatmapInfo.MD5Hash; Filename = (beatmapInfo as BeatmapInfo)?.Path ?? string.Empty; } + public GetBeatmapRequest(string md5Hash) + { + MD5Hash = md5Hash; + } + protected override WebRequest CreateWebRequest() { var request = base.CreateWebRequest(); - if (BeatmapInfo.OnlineID > 0) - request.AddParameter(@"id", BeatmapInfo.OnlineID.ToString()); - if (!string.IsNullOrEmpty(BeatmapInfo.MD5Hash)) - request.AddParameter(@"checksum", BeatmapInfo.MD5Hash); + if (OnlineID > 0) + request.AddParameter(@"id", OnlineID.ToString(CultureInfo.InvariantCulture)); + if (!string.IsNullOrEmpty(MD5Hash)) + request.AddParameter(@"checksum", MD5Hash); if (!string.IsNullOrEmpty(Filename)) request.AddParameter(@"filename", Filename); diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index 592e4c6eee..ec89f81597 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -188,7 +188,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay case GetBeatmapRequest getBeatmapRequest: { - getBeatmapRequest.TriggerSuccess(createResponseBeatmaps(getBeatmapRequest.BeatmapInfo.OnlineID).Single()); + getBeatmapRequest.TriggerSuccess(createResponseBeatmaps(getBeatmapRequest.OnlineID).Single()); return true; } From 2c2f307a63c1c4eee8ccbc0bc5d339ccaaf308af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 30 Oct 2024 08:01:57 +0100 Subject: [PATCH 687/924] Remove no longer applicable test After dd06dd0e699311494412e36bc3f37bb055a01477 the behaviour set up on the mock in the test in question is no longer realistic. Online metadata lookups will no longer fall back to online ID or filename. --- .../BeatmapUpdaterMetadataLookupTest.cs | 53 ------------------- 1 file changed, 53 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs b/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs index 33d1e5c91e..2e1bb3a1c6 100644 --- a/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs +++ b/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs @@ -383,58 +383,5 @@ namespace osu.Game.Tests.Beatmaps Assert.That(beatmapSet.Status, Is.EqualTo(BeatmapOnlineStatus.None)); } - - [Test] - public void TestPartiallyMaliciousSet([Values] bool preferOnlineFetch) - { - var firstResult = new OnlineBeatmapMetadata - { - BeatmapID = 654321, - BeatmapStatus = BeatmapOnlineStatus.Ranked, - BeatmapSetStatus = BeatmapOnlineStatus.Ranked, - MD5Hash = @"cafebabe" - }; - var secondResult = new OnlineBeatmapMetadata - { - BeatmapStatus = BeatmapOnlineStatus.Ranked, - BeatmapSetStatus = BeatmapOnlineStatus.Ranked, - MD5Hash = @"dededede" - }; - - var targetMock = preferOnlineFetch ? apiMetadataSourceMock : localCachedMetadataSourceMock; - targetMock.Setup(src => src.Available).Returns(true); - targetMock.Setup(src => src.TryLookup(It.Is(bi => bi.OnlineID == 654321), out firstResult)) - .Returns(true); - targetMock.Setup(src => src.TryLookup(It.Is(bi => bi.OnlineID == 666666), out secondResult)) - .Returns(true); - - var firstBeatmap = new BeatmapInfo - { - OnlineID = 654321, - MD5Hash = @"cafebabe", - }; - var secondBeatmap = new BeatmapInfo - { - OnlineID = 666666, - MD5Hash = @"deadbeef" - }; - var beatmapSet = new BeatmapSetInfo(new[] - { - firstBeatmap, - secondBeatmap - }); - firstBeatmap.BeatmapSet = beatmapSet; - secondBeatmap.BeatmapSet = beatmapSet; - - metadataLookup.Update(beatmapSet, preferOnlineFetch); - - Assert.That(firstBeatmap.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked)); - Assert.That(firstBeatmap.OnlineID, Is.EqualTo(654321)); - - Assert.That(secondBeatmap.Status, Is.EqualTo(BeatmapOnlineStatus.None)); - Assert.That(secondBeatmap.OnlineID, Is.EqualTo(-1)); - - Assert.That(beatmapSet.Status, Is.EqualTo(BeatmapOnlineStatus.None)); - } } } From 0e52797f2948aa60c6b375f80239c091d1a3fb7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 30 Oct 2024 08:07:19 +0100 Subject: [PATCH 688/924] Prefer not deleted models when picking model instances for reuse when importing This fell out while investigating why the issue with online IDs mismatching in the `.osu` could be worked around by importing the map three times in total when starting from it not being available locally. Here follows an explanation of why that "helped". Import 1: - The beatmap set is imported normally. - Online metadata population sees the online ID mismatch and resets it on the problematic beatmap. Import 2: - The existing beatmap set is found, but deemed not reusable because of the single beatmap having its ID reset to -1. - The existing beatmap set is marked deleted, and all the IDs of its beatmaps are reset to -1. - The beatmap set is reimported afresh. - Online metadata population still sees the online ID mismatch and resets it on the problematic beatmap. Note that at this point the first import *is still physically present in the database* but marked deleted. Import 3: - When trying to find the existing beatmap set to see if it can be reused, *the one pending deletion and with its IDs reset - - the remnant from import 1 - is returned*. - Because of this, `validateOnlineIds()` resets online IDs *on the model representing the current reimport*. - The beatmap set is reimported yet again. - With the online ID reset, the online metadata population check for online ID mismatch does not run because *the IDs were reset to -1* earlier. Preferring undeleted models when picking the model instance for reuse prevents this scenario. --- osu.Game/Database/RealmArchiveModelImporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index cf0625c51c..75462797f8 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -528,7 +528,7 @@ namespace osu.Game.Database /// The new model proposed for import. /// The current realm context. /// An existing model which matches the criteria to skip importing, else null. - protected TModel? CheckForExisting(TModel model, Realm realm) => string.IsNullOrEmpty(model.Hash) ? null : realm.All().FirstOrDefault(b => b.Hash == model.Hash); + protected TModel? CheckForExisting(TModel model, Realm realm) => string.IsNullOrEmpty(model.Hash) ? null : realm.All().OrderBy(b => b.DeletePending).FirstOrDefault(b => b.Hash == model.Hash); /// /// Whether import can be skipped after finding an existing import early in the process. From 2b0fd3558f2f7f1c6a79dbba62ab80f40d656ca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 30 Oct 2024 08:44:23 +0100 Subject: [PATCH 689/924] Remove more no-longer-required checks The scenario that remaining guard was trying to protect against is obviated by and no longer possible after 776fabd77c5a6225e69a0b14e79b9e097692320c. --- .../BeatmapUpdaterMetadataLookupTest.cs | 28 ------------------- .../Beatmaps/BeatmapUpdaterMetadataLookup.cs | 24 ++++------------ 2 files changed, 5 insertions(+), 47 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs b/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs index 2e1bb3a1c6..82e54875ef 100644 --- a/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs +++ b/osu.Game.Tests/Beatmaps/BeatmapUpdaterMetadataLookupTest.cs @@ -273,34 +273,6 @@ namespace osu.Game.Tests.Beatmaps Assert.That(beatmap.OnlineID, Is.EqualTo(654321)); } - [Test] - public void TestMetadataLookupForBeatmapWithoutPopulatedIDAndIncorrectHash([Values] bool preferOnlineFetch) - { - var lookupResult = new OnlineBeatmapMetadata - { - BeatmapID = 654321, - BeatmapStatus = BeatmapOnlineStatus.Ranked, - MD5Hash = @"cafebabe", - }; - - var targetMock = preferOnlineFetch ? apiMetadataSourceMock : localCachedMetadataSourceMock; - targetMock.Setup(src => src.Available).Returns(true); - targetMock.Setup(src => src.TryLookup(It.IsAny(), out lookupResult)) - .Returns(true); - - var beatmap = new BeatmapInfo - { - MD5Hash = @"deadbeef" - }; - var beatmapSet = new BeatmapSetInfo(beatmap.Yield()); - beatmap.BeatmapSet = beatmapSet; - - metadataLookup.Update(beatmapSet, preferOnlineFetch); - - Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.None)); - Assert.That(beatmap.OnlineID, Is.EqualTo(-1)); - } - [Test] public void TestReturnedMetadataHasDifferentHash([Values] bool preferOnlineFetch) { diff --git a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs index 42d8e07432..dd17ee784b 100644 --- a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs +++ b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Online.API; @@ -44,10 +43,14 @@ namespace osu.Game.Beatmaps foreach (var beatmapInfo in beatmapSet.Beatmaps) { + // note that it is KEY that the lookup here only uses MD5 inside + // (see implementations of underlying `IOnlineBeatmapMetadataSource`s). + // anything else like online ID or filename can be manipulated, thus being inherently unreliable, + // thus being unusable here for any purpose. if (!tryLookup(beatmapInfo, preferOnlineFetch, out var res)) continue; - if (res == null || shouldDiscardLookupResult(res, beatmapInfo)) + if (res == null) { beatmapInfo.ResetOnlineInfo(); lookupResults.Add(null); // mark lookup failure @@ -83,23 +86,6 @@ namespace osu.Game.Beatmaps } } - private bool shouldDiscardLookupResult(OnlineBeatmapMetadata result, BeatmapInfo beatmapInfo) - { - // previously this used to check whether the `OnlineID` of the looked-up beatmap matches the local `OnlineID`. - // unfortunately it appears that historically stable mappers would apply crude hacks to fix unspecified "issues" with submission - // which would amount to reusing online IDs of other beatmaps. - // this means that the online ID in the `.osu` file is not reliable, and this cannot be fixed server-side - // because updating the maps retroactively would break stable (by losing all of users' local scores). - - if (beatmapInfo.OnlineID == -1 && result.MD5Hash != beatmapInfo.MD5Hash) - { - Logger.Log($"Discarding metadata lookup result due to mismatching hash (expected: {beatmapInfo.MD5Hash} actual: {result.MD5Hash})", LoggingTarget.Database); - return true; - } - - return false; - } - /// /// Attempts to retrieve the for the given . /// From 0f61e19857238a4491a07aeb84140811f8626fe8 Mon Sep 17 00:00:00 2001 From: Luke Knight Date: Wed, 30 Oct 2024 03:02:51 -0500 Subject: [PATCH 690/924] Fixed formatting for InspectCode --- osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs index 97ebde4e2c..2003c7fef6 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs @@ -495,6 +495,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input RealmKeyBinding? existingBinding = keyBinding.KeyCombination.Equals(new KeyCombination(InputKey.None)) ? null : bindings.FirstOrDefault(other => other.ID != keyBinding.ID && other.KeyCombination.Equals(keyBinding.KeyCombination) && (!restoringBinding || other.ActionInt != keyBinding.ActionInt)); + if (existingBinding == null) { realm.Write(r => r.Find(keyBinding.ID)!.KeyCombinationString = keyBinding.KeyCombination.ToString()); From 7e3564cb4aa92c6206e7840dcd13f6d2e73e8b93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 30 Oct 2024 10:23:57 +0100 Subject: [PATCH 691/924] Bring back matching by filename when performing online metadata lookups --- osu.Game/Beatmaps/APIBeatmapMetadataSource.cs | 2 +- osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs | 13 +++++++++---- .../Beatmaps/LocalCachedBeatmapMetadataSource.cs | 9 ++++++--- osu.Game/Online/API/Requests/GetBeatmapRequest.cs | 10 +++++----- 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/osu.Game/Beatmaps/APIBeatmapMetadataSource.cs b/osu.Game/Beatmaps/APIBeatmapMetadataSource.cs index 9c292a4cfb..34eedfb474 100644 --- a/osu.Game/Beatmaps/APIBeatmapMetadataSource.cs +++ b/osu.Game/Beatmaps/APIBeatmapMetadataSource.cs @@ -33,7 +33,7 @@ namespace osu.Game.Beatmaps Debug.Assert(beatmapInfo.BeatmapSet != null); - var req = new GetBeatmapRequest(beatmapInfo.MD5Hash); + var req = new GetBeatmapRequest(md5Hash: beatmapInfo.MD5Hash, filename: beatmapInfo.Path); try { diff --git a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs index dd17ee784b..364a0f9b4b 100644 --- a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs +++ b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs @@ -43,10 +43,15 @@ namespace osu.Game.Beatmaps foreach (var beatmapInfo in beatmapSet.Beatmaps) { - // note that it is KEY that the lookup here only uses MD5 inside - // (see implementations of underlying `IOnlineBeatmapMetadataSource`s). - // anything else like online ID or filename can be manipulated, thus being inherently unreliable, - // thus being unusable here for any purpose. + // note that these lookups DO NOT ACTUALLY FULLY GUARANTEE that the beatmap is what it claims it is, + // i.e. the correctness of this lookup should be treated as APPROXIMATE AT WORST. + // this is because the beatmap filename is used as a fallback in some scenarios where the MD5 of the beatmap may mismatch. + // this is considered to be an acceptable casualty so that things can continue to work as expected for users in some rare scenarios + // (stale beatmap files in beatmap packs, beatmap mirror desyncs). + // however, all this means that other places such as score submission ARE EXPECTED TO VERIFY THE MD5 OF THE BEATMAP AGAINST THE ONLINE ONE EXPLICITLY AGAIN. + // + // additionally note that the online ID stored to the map is EXPLICITLY NOT USED because some users in a silly attempt to "fix" things for themselves on stable + // would reuse online IDs of already submitted beatmaps, which means that information is pretty much expected to be bogus in a nonzero number of beatmapsets. if (!tryLookup(beatmapInfo, preferOnlineFetch, out var res)) continue; diff --git a/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs b/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs index 39afe5f519..66fad6c8d8 100644 --- a/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs +++ b/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs @@ -89,7 +89,8 @@ namespace osu.Game.Beatmaps return false; } - if (string.IsNullOrEmpty(beatmapInfo.MD5Hash)) + if (string.IsNullOrEmpty(beatmapInfo.MD5Hash) + && string.IsNullOrEmpty(beatmapInfo.Path)) { onlineMetadata = null; return false; @@ -238,9 +239,10 @@ namespace osu.Game.Beatmaps using var cmd = db.CreateCommand(); cmd.CommandText = - @"SELECT beatmapset_id, beatmap_id, approved, user_id, checksum, last_update FROM osu_beatmaps WHERE checksum = @MD5Hash"; + @"SELECT beatmapset_id, beatmap_id, approved, user_id, checksum, last_update FROM osu_beatmaps WHERE checksum = @MD5Hash OR filename = @Path"; cmd.Parameters.Add(new SqliteParameter(@"@MD5Hash", beatmapInfo.MD5Hash)); + cmd.Parameters.Add(new SqliteParameter(@"@Path", beatmapInfo.Path)); using var reader = cmd.ExecuteReader(); @@ -277,10 +279,11 @@ namespace osu.Game.Beatmaps SELECT `b`.`beatmapset_id`, `b`.`beatmap_id`, `b`.`approved`, `b`.`user_id`, `b`.`checksum`, `b`.`last_update`, `s`.`submit_date`, `s`.`approved_date` FROM `osu_beatmaps` AS `b` JOIN `osu_beatmapsets` AS `s` ON `s`.`beatmapset_id` = `b`.`beatmapset_id` - WHERE `b`.`checksum` = @MD5Hash + WHERE `b`.`checksum` = @MD5Hash OR `b`.`filename` = @Path """; cmd.Parameters.Add(new SqliteParameter(@"@MD5Hash", beatmapInfo.MD5Hash)); + cmd.Parameters.Add(new SqliteParameter(@"@Path", beatmapInfo.Path)); using var reader = cmd.ExecuteReader(); diff --git a/osu.Game/Online/API/Requests/GetBeatmapRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs index 091f336b71..14bb0d3122 100644 --- a/osu.Game/Online/API/Requests/GetBeatmapRequest.cs +++ b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs @@ -10,20 +10,20 @@ namespace osu.Game.Online.API.Requests { public class GetBeatmapRequest : APIRequest { - public readonly int OnlineID = -1; + public readonly int OnlineID; public readonly string? MD5Hash; public readonly string? Filename; public GetBeatmapRequest(IBeatmapInfo beatmapInfo) + : this(onlineId: beatmapInfo.OnlineID, md5Hash: beatmapInfo.MD5Hash, filename: (beatmapInfo as BeatmapInfo)?.Path) { - OnlineID = beatmapInfo.OnlineID; - MD5Hash = beatmapInfo.MD5Hash; - Filename = (beatmapInfo as BeatmapInfo)?.Path ?? string.Empty; } - public GetBeatmapRequest(string md5Hash) + public GetBeatmapRequest(int onlineId = -1, string? md5Hash = null, string? filename = null) { + OnlineID = onlineId; MD5Hash = md5Hash; + Filename = filename; } protected override WebRequest CreateWebRequest() From c1a40388ff3cff1aef4d8ea054cc88a94144b8a0 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 30 Oct 2024 23:47:56 +0900 Subject: [PATCH 692/924] Cap effective miss count to total hits --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index ddabf866ff..43ae95c75e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -87,6 +87,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty } effectiveMissCount = Math.Max(countMiss, effectiveMissCount); + effectiveMissCount = Math.Min(totalHits, effectiveMissCount); double multiplier = PERFORMANCE_BASE_MULTIPLIER; From e8540a3e7b542e468453a98a502a8ad663a21f10 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 31 Oct 2024 02:15:00 +0900 Subject: [PATCH 693/924] Bring back convert nerf to fix overweighted taiko difficulty --- .../Difficulty/TaikoDifficultyCalculator.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 18223e74fa..7dacc164d4 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -87,6 +87,15 @@ namespace osu.Game.Rulesets.Taiko.Difficulty double combinedRating = combinedDifficultyValue(rhythm, colour, stamina); double starRating = rescale(combinedRating * 1.4); + // TODO: This is temporary measure as we don't detect abuse of multiple-input playstyles of converts within the current system. + if (beatmap.BeatmapInfo.Ruleset.OnlineID == 0) + { + starRating *= 0.925; + // For maps with low colour variance and high stamina requirement, multiple inputs are more likely to be abused. + if (colourRating < 2 && staminaRating > 8) + starRating *= 0.80; + } + HitWindows hitWindows = new TaikoHitWindows(); hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty); From 101a4028fa7c6af627be59bf1704cf72a8f95543 Mon Sep 17 00:00:00 2001 From: Nathen Date: Wed, 30 Oct 2024 18:57:47 -0400 Subject: [PATCH 694/924] LTCA save me --- .../Difficulty/Skills/Stamina.cs | 30 +++++++++++++++---- .../Difficulty/TaikoDifficultyAttributes.cs | 6 ++++ .../Difficulty/TaikoDifficultyCalculator.cs | 8 ++++- .../Difficulty/TaikoPerformanceCalculator.cs | 6 +++- 4 files changed, 42 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs index e528c70699..38ae7bd2ca 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs @@ -1,33 +1,51 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Difficulty.Evaluators; +using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; namespace osu.Game.Rulesets.Taiko.Difficulty.Skills { /// /// Calculates the stamina coefficient of taiko difficulty. /// - public class Stamina : StrainDecaySkill + public class Stamina : StrainSkill { - protected override double SkillMultiplier => 1.1; - protected override double StrainDecayBase => 0.4; + private double skillMultiplier => 1.1; + private double strainDecayBase => 0.4; + + private bool onlyMono; + + private double currentStrain; /// /// Creates a skill. /// /// Mods for use in skill calculations. - public Stamina(Mod[] mods) + /// I hate strangeprogram + public Stamina(Mod[] mods, bool onlyMono) : base(mods) { + this.onlyMono = onlyMono; } - protected override double StrainValueOf(DifficultyHitObject current) + private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); + + protected override double StrainValueAt(DifficultyHitObject current) { - return StaminaEvaluator.EvaluateDifficultyOf(current); + currentStrain *= strainDecay(current.DeltaTime); + currentStrain += StaminaEvaluator.EvaluateDifficultyOf(current) * skillMultiplier; + + if (onlyMono) + return ((TaikoDifficultyHitObject)current).Colour.MonoStreak?.RunLength >= 16 ? currentStrain : 0; + + return currentStrain; } + + protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => onlyMono ? 0 : currentStrain * strainDecay(time - current.Previous(0).StartTime); } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs index 451aed183d..c62ea75abd 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs @@ -16,6 +16,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty [JsonProperty("stamina_difficulty")] public double StaminaDifficulty { get; set; } + /// + /// The ratio of stamina difficulty from mono-color streams to total stamina difficulty. + /// + [JsonProperty("mono_stamina_factor")] + public double MonoStaminaFactor { get; set; } + /// /// The difficulty corresponding to the rhythm skill. /// diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 18223e74fa..5ff8f2f31a 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -38,7 +38,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { new Rhythm(mods), new Colour(mods), - new Stamina(mods) + new Stamina(mods, false), + new Stamina(mods, true) }; } @@ -79,10 +80,14 @@ namespace osu.Game.Rulesets.Taiko.Difficulty Colour colour = (Colour)skills.First(x => x is Colour); Rhythm rhythm = (Rhythm)skills.First(x => x is Rhythm); Stamina stamina = (Stamina)skills.First(x => x is Stamina); + Stamina staminaMonos = (Stamina)skills.Last(x => x is Stamina); double colourRating = colour.DifficultyValue() * colour_skill_multiplier; double rhythmRating = rhythm.DifficultyValue() * rhythm_skill_multiplier; double staminaRating = stamina.DifficultyValue() * stamina_skill_multiplier; + double monoStaminaRating = staminaMonos.DifficultyValue() * stamina_skill_multiplier; + + double monoStaminaFactor = Math.Pow(monoStaminaRating / staminaRating, 5); double combinedRating = combinedDifficultyValue(rhythm, colour, stamina); double starRating = rescale(combinedRating * 1.4); @@ -95,6 +100,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty StarRating = starRating, Mods = mods, StaminaDifficulty = staminaRating, + MonoStaminaFactor = monoStaminaFactor, RhythmDifficulty = rhythmRating, ColourDifficulty = colourRating, PeakDifficulty = combinedRating, diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index e42b015176..330df7b090 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -95,7 +95,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (estimatedUnstableRate == null) return 0; - return difficultyValue * Math.Pow(SpecialFunctions.Erf(400 / (Math.Sqrt(2) * estimatedUnstableRate.Value)), 2.0); + // Scale accuracy more harshly on nearly-completely mono speed maps. + double accScalingExponent = 2 + attributes.MonoStaminaFactor; + double accScalingShift = 300 - 100 * attributes.MonoStaminaFactor; + + return difficultyValue * Math.Pow(SpecialFunctions.Erf(accScalingShift / (Math.Sqrt(2) * estimatedUnstableRate.Value)), accScalingExponent); } private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes, bool isConvert) From 372162de5dbdaea57697bfb5cfdff039efcb5c71 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 31 Oct 2024 08:55:40 +0900 Subject: [PATCH 695/924] Ignore casing when matching mods acronyms --- osu.Game/Rulesets/Ruleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index d4989642c2..bd1f273b49 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -101,7 +101,7 @@ namespace osu.Game.Rulesets /// The acronym to query for . public Mod? CreateModFromAcronym(string acronym) { - return AllMods.FirstOrDefault(m => m.Acronym == acronym)?.CreateInstance(); + return AllMods.FirstOrDefault(m => string.Equals(m.Acronym, acronym, StringComparison.OrdinalIgnoreCase))?.CreateInstance(); } /// From 85aa2ea8afe9dbe672b480e6f3a4de2d52a35aee Mon Sep 17 00:00:00 2001 From: Jay Lawton Date: Thu, 31 Oct 2024 10:15:29 +1000 Subject: [PATCH 696/924] Change Convert Bonuses to Performance --- .../Difficulty/TaikoPerformanceCalculator.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 330df7b090..65b8f080cd 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -47,11 +47,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty double multiplier = 1.13; - if (score.Mods.Any(m => m is ModHidden)) + if (score.Mods.Any(m => m is ModHidden) && !isConvert) multiplier *= 1.075; if (score.Mods.Any(m => m is ModEasy)) - multiplier *= 0.975; + multiplier *= 0.950; double difficultyValue = computeDifficultyValue(score, taikoAttributes, isConvert); double accuracyValue = computeAccuracyValue(score, taikoAttributes, isConvert); @@ -81,16 +81,16 @@ namespace osu.Game.Rulesets.Taiko.Difficulty difficultyValue *= Math.Pow(0.986, effectiveMissCount); if (score.Mods.Any(m => m is ModEasy)) - difficultyValue *= 0.985; + difficultyValue *= 0.90; - if (score.Mods.Any(m => m is ModHidden) && !isConvert) + if (score.Mods.Any(m => m is ModHidden)) difficultyValue *= 1.025; if (score.Mods.Any(m => m is ModHardRock)) difficultyValue *= 1.10; if (score.Mods.Any(m => m is ModFlashlight)) - difficultyValue *= 1.050 * lengthBonus; + difficultyValue *= Math.Max(1, 1.050 - Math.Min(attributes.MonoStaminaFactor / 50, 1) * lengthBonus); if (estimatedUnstableRate == null) return 0; From 4a26989084cfdb0634c2029225f116878d6e8074 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 30 Oct 2024 17:43:39 -0700 Subject: [PATCH 697/924] Fix android screen orientation locking in portrait mode during gameplay when exiting/re-entering app --- osu.Android/GameplayScreenRotationLocker.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Android/GameplayScreenRotationLocker.cs b/osu.Android/GameplayScreenRotationLocker.cs index 26234545ef..42583b5dc2 100644 --- a/osu.Android/GameplayScreenRotationLocker.cs +++ b/osu.Android/GameplayScreenRotationLocker.cs @@ -5,7 +5,6 @@ using Android.Content.PM; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game; using osu.Game.Screens.Play; namespace osu.Android @@ -28,7 +27,7 @@ namespace osu.Android { gameActivity.RunOnUiThread(() => { - gameActivity.RequestedOrientation = userPlaying.NewValue != LocalUserPlayingState.NotPlaying ? ScreenOrientation.Locked : gameActivity.DefaultOrientation; + gameActivity.RequestedOrientation = userPlaying.NewValue == LocalUserPlayingState.Playing ? ScreenOrientation.Locked : gameActivity.DefaultOrientation; }); } } From 21b458d268e749d576b37e22efd053d4f5ed7ddd Mon Sep 17 00:00:00 2001 From: Jay Lawton Date: Thu, 31 Oct 2024 12:08:12 +1000 Subject: [PATCH 698/924] change convert specific omissions --- .../Difficulty/TaikoDifficultyAttributes.cs | 2 +- .../Difficulty/TaikoPerformanceCalculator.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs index c62ea75abd..c1d704b0ba 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty public double StaminaDifficulty { get; set; } /// - /// The ratio of stamina difficulty from mono-color streams to total stamina difficulty. + /// The ratio of stamina difficulty from mono-color (single colour) streams to total stamina difficulty. /// [JsonProperty("mono_stamina_factor")] public double MonoStaminaFactor { get; set; } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 65b8f080cd..9e89c0c110 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (totalSuccessfulHits > 0) effectiveMissCount = Math.Max(1.0, 1000.0 / totalSuccessfulHits) * countMiss; - // TODO: The detection of rulesets is temporary until the leftover old skills have been reworked. + // Converts are detected and omitted from mod-specific bonuses due to the scope of current difficulty calcuation. bool isConvert = score.BeatmapInfo!.Ruleset.OnlineID != 1; double multiplier = 1.13; @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (score.Mods.Any(m => m is ModEasy)) multiplier *= 0.950; - double difficultyValue = computeDifficultyValue(score, taikoAttributes, isConvert); + double difficultyValue = computeDifficultyValue(score, taikoAttributes); double accuracyValue = computeAccuracyValue(score, taikoAttributes, isConvert); double totalValue = Math.Pow( @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty }; } - private double computeDifficultyValue(ScoreInfo score, TaikoDifficultyAttributes attributes, bool isConvert) + private double computeDifficultyValue(ScoreInfo score, TaikoDifficultyAttributes attributes) { double difficultyValue = Math.Pow(5 * Math.Max(1.0, attributes.StarRating / 0.115) - 4.0, 2.25) / 1150.0; @@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (estimatedUnstableRate == null) return 0; - // Scale accuracy more harshly on nearly-completely mono speed maps. + // Scale accuracy more harshly on nearly-completely mono (single coloured) speed maps. double accScalingExponent = 2 + attributes.MonoStaminaFactor; double accScalingShift = 300 - 100 * attributes.MonoStaminaFactor; From abe2ee90e0b8836a0339e2d55d3d1f2d1b60a421 Mon Sep 17 00:00:00 2001 From: Jay Lawton Date: Thu, 31 Oct 2024 12:12:14 +1000 Subject: [PATCH 699/924] Change naming of onlyMono to SingleColourStamina --- osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs | 12 ++++++------ .../Difficulty/TaikoDifficultyCalculator.cs | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs index 38ae7bd2ca..7d259becb1 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills private double skillMultiplier => 1.1; private double strainDecayBase => 0.4; - private bool onlyMono; + private bool singleColourStamina; private double currentStrain; @@ -26,11 +26,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills /// Creates a skill. /// /// Mods for use in skill calculations. - /// I hate strangeprogram - public Stamina(Mod[] mods, bool onlyMono) + /// Reads when Stamina is from a single coloured pattern. + public Stamina(Mod[] mods, bool singleColourStamina) : base(mods) { - this.onlyMono = onlyMono; + this.singleColourStamina = singleColourStamina; } private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); @@ -40,12 +40,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills currentStrain *= strainDecay(current.DeltaTime); currentStrain += StaminaEvaluator.EvaluateDifficultyOf(current) * skillMultiplier; - if (onlyMono) + if (singleColourStamina) return ((TaikoDifficultyHitObject)current).Colour.MonoStreak?.RunLength >= 16 ? currentStrain : 0; return currentStrain; } - protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => onlyMono ? 0 : currentStrain * strainDecay(time - current.Previous(0).StartTime); + protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => singleColourStamina ? 0 : currentStrain * strainDecay(time - current.Previous(0).StartTime); } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 5ff8f2f31a..2dca44fb76 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -80,12 +80,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty Colour colour = (Colour)skills.First(x => x is Colour); Rhythm rhythm = (Rhythm)skills.First(x => x is Rhythm); Stamina stamina = (Stamina)skills.First(x => x is Stamina); - Stamina staminaMonos = (Stamina)skills.Last(x => x is Stamina); + Stamina singleColourStamina = (Stamina)skills.Last(x => x is Stamina); double colourRating = colour.DifficultyValue() * colour_skill_multiplier; double rhythmRating = rhythm.DifficultyValue() * rhythm_skill_multiplier; double staminaRating = stamina.DifficultyValue() * stamina_skill_multiplier; - double monoStaminaRating = staminaMonos.DifficultyValue() * stamina_skill_multiplier; + double monoStaminaRating = singleColourStamina.DifficultyValue() * stamina_skill_multiplier; double monoStaminaFactor = Math.Pow(monoStaminaRating / staminaRating, 5); From ff05bbd63fdd2ba148d1c3debbcbae38b5df3ba6 Mon Sep 17 00:00:00 2001 From: Jay Lawton Date: Thu, 31 Oct 2024 15:25:25 +1000 Subject: [PATCH 700/924] Add mono streak index calculation to strain values --- osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs index 7d259becb1..f6914039f0 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills private double skillMultiplier => 1.1; private double strainDecayBase => 0.4; - private bool singleColourStamina; + private readonly bool singleColourStamina; private double currentStrain; @@ -40,8 +40,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills currentStrain *= strainDecay(current.DeltaTime); currentStrain += StaminaEvaluator.EvaluateDifficultyOf(current) * skillMultiplier; + // Safely prevents previous strains from shifting as new notes are added. + var currentObject = current as TaikoDifficultyHitObject; + int index = currentObject?.Colour.MonoStreak?.HitObjects.IndexOf(currentObject) ?? 0; + if (singleColourStamina) - return ((TaikoDifficultyHitObject)current).Colour.MonoStreak?.RunLength >= 16 ? currentStrain : 0; + return currentStrain / (1 + Math.Exp(-(index - 10) / 2.0)); return currentStrain; } From bf53833b7bcf6636211d3b3a483a1ab96b1a811e Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 1 Nov 2024 12:52:37 +0800 Subject: [PATCH 701/924] add API model and request --- .../Online/API/Requests/FriendAddRequest.cs | 31 +++++++++++++++++++ .../API/Requests/FriendDeleteRequest.cs | 27 ++++++++++++++++ .../Online/API/Requests/GetFriendsRequest.cs | 2 +- .../API/Requests/Responses/APIRelation.cs | 28 +++++++++++++++++ 4 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Online/API/Requests/FriendAddRequest.cs create mode 100644 osu.Game/Online/API/Requests/FriendDeleteRequest.cs create mode 100644 osu.Game/Online/API/Requests/Responses/APIRelation.cs diff --git a/osu.Game/Online/API/Requests/FriendAddRequest.cs b/osu.Game/Online/API/Requests/FriendAddRequest.cs new file mode 100644 index 0000000000..3efba4a740 --- /dev/null +++ b/osu.Game/Online/API/Requests/FriendAddRequest.cs @@ -0,0 +1,31 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Net.Http; +using osu.Framework.IO.Network; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Online.API.Requests +{ + public class FriendAddRequest : APIRequest + { + private readonly int targetId; + + public FriendAddRequest(int targetId) + { + this.targetId = targetId; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + + req.Method = HttpMethod.Post; + req.AddParameter("target", targetId.ToString(), RequestParameterType.Query); + + return req; + } + + protected override string Target => @"friends"; + } +} diff --git a/osu.Game/Online/API/Requests/FriendDeleteRequest.cs b/osu.Game/Online/API/Requests/FriendDeleteRequest.cs new file mode 100644 index 0000000000..d365031c8e --- /dev/null +++ b/osu.Game/Online/API/Requests/FriendDeleteRequest.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Net.Http; +using osu.Framework.IO.Network; + +namespace osu.Game.Online.API.Requests +{ + public class FriendDeleteRequest : APIRequest + { + private readonly int targetId; + + public FriendDeleteRequest(int targetId) + { + this.targetId = targetId; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + req.Method = HttpMethod.Delete; + return req; + } + + protected override string Target => $@"friends/{targetId}"; + } +} diff --git a/osu.Game/Online/API/Requests/GetFriendsRequest.cs b/osu.Game/Online/API/Requests/GetFriendsRequest.cs index 63a221d91a..77b37e87d0 100644 --- a/osu.Game/Online/API/Requests/GetFriendsRequest.cs +++ b/osu.Game/Online/API/Requests/GetFriendsRequest.cs @@ -6,7 +6,7 @@ using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests { - public class GetFriendsRequest : APIRequest> + public class GetFriendsRequest : APIRequest> { protected override string Target => @"friends"; } diff --git a/osu.Game/Online/API/Requests/Responses/APIRelation.cs b/osu.Game/Online/API/Requests/Responses/APIRelation.cs new file mode 100644 index 0000000000..75b9a97ffc --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIRelation.cs @@ -0,0 +1,28 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIRelation + { + [JsonProperty("target_id")] + public int TargetID { get; set; } + + [JsonProperty("relation_type")] + public RelationType RelationType { get; set; } + + [JsonProperty("mutual")] + public bool Mutual { get; set; } + + [JsonProperty("target")] + public APIUser? TargetUser { get; set; } + } + + public enum RelationType + { + Friend, + Block, + } +} From 69b5bd3b50e3a7520620ff4318187544e7d9bece Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 1 Nov 2024 12:53:13 +0800 Subject: [PATCH 702/924] Fix existing friend logic --- osu.Game/Online/API/APIAccess.cs | 34 +++++++++++-------- osu.Game/Online/API/DummyAPIAccess.cs | 8 +++-- osu.Game/Online/API/IAPIProvider.cs | 7 +++- .../Dashboard/Friends/FriendDisplay.cs | 4 +-- .../Play/HUD/GameplayLeaderboardScore.cs | 2 +- 5 files changed, 34 insertions(+), 21 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index a9ccbf9b18..c8992c108e 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -57,7 +57,7 @@ namespace osu.Game.Online.API private string password; public IBindable LocalUser => localUser; - public IBindableList Friends => friends; + public IBindableList Friends => friends; public IBindable Activity => activity; public IBindable Statistics => statistics; @@ -67,7 +67,7 @@ namespace osu.Game.Online.API private Bindable localUser { get; } = new Bindable(createGuestUser()); - private BindableList friends { get; } = new BindableList(); + private BindableList friends { get; } = new BindableList(); private Bindable activity { get; } = new Bindable(); @@ -360,19 +360,7 @@ namespace osu.Game.Online.API } } - var friendsReq = new GetFriendsRequest(); - friendsReq.Failure += _ => state.Value = APIState.Failing; - friendsReq.Success += res => - { - friends.Clear(); - friends.AddRange(res); - }; - - if (!handleRequest(friendsReq)) - { - state.Value = APIState.Failing; - return; - } + UpdateLocalFriends(); // The Success callback event is fired on the main thread, so we should wait for that to run before proceeding. // Without this, we will end up circulating this Connecting loop multiple times and queueing up many web requests @@ -624,6 +612,22 @@ namespace osu.Game.Online.API localUser.Value.Statistics = newStatistics; } + public void UpdateLocalFriends() + { + if (!IsLoggedIn) + return; + + var friendsReq = new GetFriendsRequest(); + friendsReq.Failure += _ => state.Value = APIState.Failing; + friendsReq.Success += res => + { + friends.Clear(); + friends.AddRange(res); + }; + + Queue(friendsReq); + } + private static APIUser createGuestUser() => new GuestUser(); private void setLocalUser(APIUser user) => Scheduler.Add(() => diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 7ac5c45fad..f0da0c25da 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -26,7 +26,7 @@ namespace osu.Game.Online.API Id = DUMMY_USER_ID, }); - public BindableList Friends { get; } = new BindableList(); + public BindableList Friends { get; } = new BindableList(); public Bindable Activity { get; } = new Bindable(); @@ -201,6 +201,10 @@ namespace osu.Game.Online.API LocalUser.Value.Statistics = newStatistics; } + public void UpdateLocalFriends() + { + } + public IHubClientConnector? GetHubConnector(string clientName, string endpoint, bool preferMessagePack) => null; public IChatClient GetChatClient() => new TestChatClientConnector(this); @@ -214,7 +218,7 @@ namespace osu.Game.Online.API public void SetState(APIState newState) => state.Value = newState; IBindable IAPIProvider.LocalUser => LocalUser; - IBindableList IAPIProvider.Friends => Friends; + IBindableList IAPIProvider.Friends => Friends; IBindable IAPIProvider.Activity => Activity; IBindable IAPIProvider.Statistics => Statistics; diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index eccfb36546..4b1aed236d 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -22,7 +22,7 @@ namespace osu.Game.Online.API /// /// The user's friends. /// - IBindableList Friends { get; } + IBindableList Friends { get; } /// /// The current user's activity. @@ -134,6 +134,11 @@ namespace osu.Game.Online.API /// void UpdateStatistics(UserStatistics newStatistics); + /// + /// Update the friends status of the current user. + /// + void UpdateLocalFriends(); + /// /// Schedule a callback to run on the update thread. /// diff --git a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs index e3accfd2ad..483537e02a 100644 --- a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs +++ b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs @@ -44,7 +44,7 @@ namespace osu.Game.Overlays.Dashboard.Friends private Container itemsPlaceholder; private LoadingLayer loading; - private readonly IBindableList apiFriends = new BindableList(); + private readonly IBindableList apiFriends = new BindableList(); public FriendDisplay() { @@ -145,7 +145,7 @@ namespace osu.Game.Overlays.Dashboard.Friends controlBackground.Colour = colourProvider.Background5; apiFriends.BindTo(api.Friends); - apiFriends.BindCollectionChanged((_, _) => Schedule(() => Users = apiFriends.ToList()), true); + apiFriends.BindCollectionChanged((_, _) => Schedule(() => Users = apiFriends.Select(f => f.TargetUser).ToList()), true); } protected override void LoadComplete() diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs index 7471955493..3d46517a68 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs @@ -316,7 +316,7 @@ namespace osu.Game.Screens.Play.HUD HasQuit.BindValueChanged(_ => updateState()); - isFriend = User != null && api.Friends.Any(u => User.OnlineID == u.Id); + isFriend = User != null && api.Friends.Any(u => User.OnlineID == u.TargetID); } protected override void LoadComplete() From 350e1d6332256bd4d4dd3c6620372c49dbbd290f Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 1 Nov 2024 12:53:57 +0800 Subject: [PATCH 703/924] add ability to shou loading layer and set icon for followersButton --- .../Header/Components/ProfileHeaderButton.cs | 21 ++++++++++++++++++- .../ProfileHeaderStatisticsButton.cs | 21 +++++++++++++++---- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs index 414ca4d077..eb951ef026 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs @@ -4,9 +4,11 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Profile.Header.Components { @@ -14,6 +16,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { private readonly Box background; private readonly Container content; + private readonly LoadingLayer loading; protected override Container Content => content; @@ -40,11 +43,27 @@ namespace osu.Game.Overlays.Profile.Header.Components AutoSizeAxes = Axes.X, RelativeSizeAxes = Axes.Y, Padding = new MarginPadding { Horizontal = 10 }, - } + }, + loading = new LoadingLayer(true, false) } }); } + protected void SetBackGroundColour(ColourInfo colorInfo, double duration = 0) + { + background.FadeColour(colorInfo, duration); + } + + protected void ShowLodingLayer() + { + loading.Show(); + } + + protected void HideLodingLayer() + { + loading.Hide(); + } + [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { diff --git a/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderStatisticsButton.cs b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderStatisticsButton.cs index 32c5ebee2c..3c2e603da8 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderStatisticsButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderStatisticsButton.cs @@ -14,6 +14,7 @@ namespace osu.Game.Overlays.Profile.Header.Components public abstract partial class ProfileHeaderStatisticsButton : ProfileHeaderButton { private readonly OsuSpriteText drawableText; + private readonly Container iconContainer; protected ProfileHeaderStatisticsButton() { @@ -26,13 +27,11 @@ namespace osu.Game.Overlays.Profile.Header.Components Direction = FillDirection.Horizontal, Children = new Drawable[] { - new SpriteIcon + iconContainer = new Container { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Icon = Icon, - FillMode = FillMode.Fit, - Size = new Vector2(50, 14) + AutoSizeAxes = Axes.Both, }, drawableText = new OsuSpriteText { @@ -43,10 +42,24 @@ namespace osu.Game.Overlays.Profile.Header.Components } } }; + + SetIcon(Icon); } protected abstract IconUsage Icon { get; } + protected void SetIcon(IconUsage icon) + { + iconContainer.Child = new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = icon, + FillMode = FillMode.Fit, + Size = new Vector2(50, 14) + }; + } + protected void SetValue(int value) => drawableText.Text = value.ToLocalisableString("#,##0"); } } From 45cc830aee196836fd6009306beee8019a80413f Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 1 Nov 2024 12:54:13 +0800 Subject: [PATCH 704/924] logic in FollowersButton --- .../Header/Components/FollowersButton.cs | 161 +++++++++++++++++- 1 file changed, 158 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs index 844efa5cf0..305724ae07 100644 --- a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs @@ -1,10 +1,18 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays.Notifications; using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.Profile.Header.Components @@ -17,11 +25,158 @@ namespace osu.Game.Overlays.Profile.Header.Components protected override IconUsage Icon => FontAwesome.Solid.User; + private readonly IBindableList apiFriends = new BindableList(); + private readonly IBindable localUser = new Bindable(); + + private readonly Bindable status = new Bindable(); + + [Resolved] + private OsuColour colour { get; set; } = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + [BackgroundDependencyLoader] - private void load() + private void load(IAPIProvider api, INotificationOverlay notifications) { - // todo: when friending/unfriending is implemented, the APIAccess.Friends list should be updated accordingly. - User.BindValueChanged(user => SetValue(user.NewValue?.User.FollowerCount ?? 0), true); + localUser.BindTo(api.LocalUser); + + status.BindValueChanged(_ => + { + updateIcon(); + updateColor(); + }); + + User.BindValueChanged(_ => updateStatus(), true); + + apiFriends.BindTo(api.Friends); + apiFriends.BindCollectionChanged((_, _) => Schedule(updateStatus)); + + Action += () => + { + if (User.Value == null) + return; + + if (status.Value == FriendStatus.Self) + return; + + ShowLodingLayer(); + + APIRequest req = status.Value == FriendStatus.None ? new FriendAddRequest(User.Value.User.OnlineID) : new FriendDeleteRequest(User.Value.User.OnlineID); + + req.Success += () => + { + api.UpdateLocalFriends(); + HideLodingLayer(); + }; + + req.Failure += e => + { + notifications?.Post(new SimpleNotification + { + Text = e.Message, + Icon = FontAwesome.Solid.Times, + }); + + HideLodingLayer(); + }; + + api.Queue(req); + }; + } + + protected override bool OnHover(HoverEvent e) + { + if (status.Value > FriendStatus.None) + { + SetIcon(FontAwesome.Solid.UserTimes); + } + + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + + updateIcon(); + } + + private void updateStatus() + { + SetValue(User.Value?.User.FollowerCount ?? 0); + + if (localUser.Value.OnlineID == User.Value?.User.OnlineID) + { + status.Value = FriendStatus.Self; + return; + } + + var friend = apiFriends.FirstOrDefault(u => User.Value?.User.OnlineID == u.TargetID); + + if (friend != null) + { + status.Value = friend.Mutual ? FriendStatus.Mutual : FriendStatus.NotMutual; + } + else + { + status.Value = FriendStatus.None; + } + } + + private void updateIcon() + { + switch (status.Value) + { + case FriendStatus.Self: + SetIcon(FontAwesome.Solid.User); + break; + + case FriendStatus.None: + SetIcon(FontAwesome.Solid.UserPlus); + break; + + case FriendStatus.NotMutual: + SetIcon(FontAwesome.Solid.User); + break; + + case FriendStatus.Mutual: + SetIcon(FontAwesome.Solid.UserFriends); + break; + } + } + + private void updateColor() + { + switch (status.Value) + { + case FriendStatus.Self: + case FriendStatus.None: + IdleColour = colourProvider.Background6; + HoverColour = colourProvider.Background5; + SetBackGroundColour(colourProvider.Background6, 200); + break; + + case FriendStatus.NotMutual: + IdleColour = colour.Green; + HoverColour = colour.Green.Lighten(0.1f); + SetBackGroundColour(colour.Green, 200); + break; + + case FriendStatus.Mutual: + IdleColour = colour.Pink; + HoverColour = colour.Pink1.Lighten(0.1f); + SetBackGroundColour(colour.Pink, 200); + break; + } + } + + private enum FriendStatus + { + Self, + None, + NotMutual, + Mutual, } } } From 0b2f4facace29a1fa6eedaf2726abb7af7d92fcf Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 1 Nov 2024 13:58:08 +0800 Subject: [PATCH 705/924] add test --- .../Gameplay/TestSceneGameplayLeaderboard.cs | 2 +- .../Online/TestSceneDashboardOverlay.cs | 4 +- .../Online/TestSceneUserProfileHeader.cs | 80 +++++++++++++++++++ .../Online/API/Requests/FriendAddRequest.cs | 6 +- .../API/Requests/FriendDeleteRequest.cs | 6 +- .../Header/Components/FollowersButton.cs | 2 +- osu.Game/Tests/Visual/OsuTestScene.cs | 9 +++ 7 files changed, 99 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs index 193e8b2571..135c1fd50c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs @@ -155,7 +155,7 @@ namespace osu.Game.Tests.Visual.Gameplay var api = (DummyAPIAccess)API; api.Friends.Clear(); - api.Friends.Add(friend); + api.Friends.Add(CreateAPIRelationFromAPIUser(friend)); }); int playerNumber = 1; diff --git a/osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs index b6a300322f..f2ea084f40 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.Online if (supportLevel > 3) supportLevel = 0; - ((DummyAPIAccess)API).Friends.Add(new APIUser + ((DummyAPIAccess)API).Friends.Add(CreateAPIRelationFromAPIUser(new APIUser { Username = @"peppy", Id = 2, @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Online CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", IsSupporter = supportLevel > 0, SupportLevel = supportLevel - }); + })); } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs index c9e5a3315c..a8ef11e20c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs @@ -3,17 +3,24 @@ using System; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Graphics.Containers; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.Profile; +using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Rulesets.Osu; using osu.Game.Users; +using osuTK.Input; namespace osu.Game.Tests.Visual.Online { @@ -22,6 +29,10 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; + + private readonly ManualResetEventSlim requestLock = new ManualResetEventSlim(); + [Resolved] private OsuConfigManager configManager { get; set; } = null!; @@ -400,5 +411,74 @@ namespace osu.Game.Tests.Visual.Online } }, new OsuRuleset().RulesetInfo)); } + + [Test] + public void TestAddFriend() + { + AddStep("clear friend list", () => dummyAPI.Friends.Clear()); + AddStep("Show non-friend user", () => header.User.Value = new UserProfileData(TestSceneUserProfileOverlay.TEST_USER, new OsuRuleset().RulesetInfo)); + AddStep("Setup request", () => + { + requestLock.Reset(); + + dummyAPI.HandleRequest += request => + { + if (request is not FriendAddRequest req) + return false; + + if (req.TargetId != 1) + return false; + + var apiRelation = CreateAPIRelationFromAPIUser(TestSceneUserProfileOverlay.TEST_USER); + + Task.Run(() => + { + requestLock.Wait(3000); + req.TriggerSuccess(apiRelation); + }); + + dummyAPI.Friends.Add(apiRelation); + return true; + }; + }); + AddStep("Click followers button", () => this.ChildrenOfType().First().TriggerClick()); + AddStep("Complete request", () => requestLock.Set()); + AddUntilStep("Friend added", () => API.Friends.Any(f => f.TargetID == TestSceneUserProfileOverlay.TEST_USER.OnlineID)); + } + + [Test] + public void TestAddFriendNonMutual() + { + AddStep("clear friend list", () => dummyAPI.Friends.Clear()); + AddStep("Show non-friend user", () => header.User.Value = new UserProfileData(TestSceneUserProfileOverlay.TEST_USER, new OsuRuleset().RulesetInfo)); + AddStep("Setup request", () => + { + requestLock.Reset(); + + dummyAPI.HandleRequest += request => + { + if (request is not FriendAddRequest req) + return false; + + if (req.TargetId != 1) + return false; + + var apiRelation = CreateAPIRelationFromAPIUser(TestSceneUserProfileOverlay.TEST_USER); + apiRelation.Mutual = false; + + Task.Run(() => + { + requestLock.Wait(3000); + req.TriggerSuccess(apiRelation); + }); + + dummyAPI.Friends.Add(apiRelation); + return true; + }; + }); + AddStep("Click followers button", () => this.ChildrenOfType().First().TriggerClick()); + AddStep("Complete request", () => requestLock.Set()); + AddUntilStep("Friend added", () => API.Friends.Any(f => f.TargetID == TestSceneUserProfileOverlay.TEST_USER.OnlineID)); + } } } diff --git a/osu.Game/Online/API/Requests/FriendAddRequest.cs b/osu.Game/Online/API/Requests/FriendAddRequest.cs index 3efba4a740..80aa7cb995 100644 --- a/osu.Game/Online/API/Requests/FriendAddRequest.cs +++ b/osu.Game/Online/API/Requests/FriendAddRequest.cs @@ -9,11 +9,11 @@ namespace osu.Game.Online.API.Requests { public class FriendAddRequest : APIRequest { - private readonly int targetId; + public readonly int TargetId; public FriendAddRequest(int targetId) { - this.targetId = targetId; + TargetId = targetId; } protected override WebRequest CreateWebRequest() @@ -21,7 +21,7 @@ namespace osu.Game.Online.API.Requests var req = base.CreateWebRequest(); req.Method = HttpMethod.Post; - req.AddParameter("target", targetId.ToString(), RequestParameterType.Query); + req.AddParameter("target", TargetId.ToString(), RequestParameterType.Query); return req; } diff --git a/osu.Game/Online/API/Requests/FriendDeleteRequest.cs b/osu.Game/Online/API/Requests/FriendDeleteRequest.cs index d365031c8e..9b6c4081da 100644 --- a/osu.Game/Online/API/Requests/FriendDeleteRequest.cs +++ b/osu.Game/Online/API/Requests/FriendDeleteRequest.cs @@ -8,11 +8,11 @@ namespace osu.Game.Online.API.Requests { public class FriendDeleteRequest : APIRequest { - private readonly int targetId; + public readonly int TargetId; public FriendDeleteRequest(int targetId) { - this.targetId = targetId; + TargetId = targetId; } protected override WebRequest CreateWebRequest() @@ -22,6 +22,6 @@ namespace osu.Game.Online.API.Requests return req; } - protected override string Target => $@"friends/{targetId}"; + protected override string Target => $@"friends/{TargetId}"; } } diff --git a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs index 305724ae07..d76b979033 100644 --- a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs @@ -37,7 +37,7 @@ namespace osu.Game.Overlays.Profile.Header.Components private OverlayColourProvider colourProvider { get; set; } = null!; [BackgroundDependencyLoader] - private void load(IAPIProvider api, INotificationOverlay notifications) + private void load(IAPIProvider api, INotificationOverlay? notifications) { localUser.BindTo(api.LocalUser); diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 09cfe5ecad..853c5d5f5c 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -317,6 +317,15 @@ namespace osu.Game.Tests.Visual return result; } + public static APIRelation CreateAPIRelationFromAPIUser(APIUser user) => + new APIRelation + { + Mutual = true, + RelationType = RelationType.Friend, + TargetID = user.OnlineID, + TargetUser = user + }; + protected WorkingBeatmap CreateWorkingBeatmap(RulesetInfo ruleset) => CreateWorkingBeatmap(CreateBeatmap(ruleset)); From 29ba13fe77cf0c0e7e6d1391421917c0fe4ed232 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 1 Nov 2024 14:06:23 +0800 Subject: [PATCH 706/924] store follower count locally --- .../Profile/Header/Components/FollowersButton.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs index d76b979033..38108a1544 100644 --- a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs @@ -21,6 +21,10 @@ namespace osu.Game.Overlays.Profile.Header.Components { public readonly Bindable User = new Bindable(); + // Because it is impossible to update the number of friends after the operation, + // the number of friends obtained is stored and modified locally. + private int followerCount; + public override LocalisableString TooltipText => FriendsStrings.ButtonsDisabled; protected override IconUsage Icon => FontAwesome.Solid.User; @@ -47,7 +51,11 @@ namespace osu.Game.Overlays.Profile.Header.Components updateColor(); }); - User.BindValueChanged(_ => updateStatus(), true); + User.BindValueChanged(u => + { + followerCount = u.NewValue?.User.FollowerCount ?? 0; + updateStatus(); + }, true); apiFriends.BindTo(api.Friends); apiFriends.BindCollectionChanged((_, _) => Schedule(updateStatus)); @@ -66,6 +74,8 @@ namespace osu.Game.Overlays.Profile.Header.Components req.Success += () => { + followerCount += status.Value == FriendStatus.None ? 1 : -1; + api.UpdateLocalFriends(); HideLodingLayer(); }; @@ -104,7 +114,7 @@ namespace osu.Game.Overlays.Profile.Header.Components private void updateStatus() { - SetValue(User.Value?.User.FollowerCount ?? 0); + SetValue(followerCount); if (localUser.Value.OnlineID == User.Value?.User.OnlineID) { From b682285f5310425030e0fffdff93cacdc7666b62 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 1 Nov 2024 14:24:54 +0800 Subject: [PATCH 707/924] simpily test --- .../Online/TestSceneUserProfileHeader.cs | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs index a8ef11e20c..864baa8439 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs @@ -7,7 +7,6 @@ using System.Threading; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Configuration; @@ -20,7 +19,6 @@ using osu.Game.Overlays.Profile; using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Rulesets.Osu; using osu.Game.Users; -using osuTK.Input; namespace osu.Game.Tests.Visual.Online { @@ -412,73 +410,79 @@ namespace osu.Game.Tests.Visual.Online }, new OsuRuleset().RulesetInfo)); } + private APIUser nonFriend => new APIUser + { + Id = 727, + Username = "Whatever", + }; + [Test] public void TestAddFriend() { - AddStep("clear friend list", () => dummyAPI.Friends.Clear()); - AddStep("Show non-friend user", () => header.User.Value = new UserProfileData(TestSceneUserProfileOverlay.TEST_USER, new OsuRuleset().RulesetInfo)); AddStep("Setup request", () => { requestLock.Reset(); - dummyAPI.HandleRequest += request => + dummyAPI.HandleRequest = request => { if (request is not FriendAddRequest req) return false; - if (req.TargetId != 1) + if (req.TargetId != nonFriend.OnlineID) return false; - var apiRelation = CreateAPIRelationFromAPIUser(TestSceneUserProfileOverlay.TEST_USER); + var apiRelation = CreateAPIRelationFromAPIUser(nonFriend); Task.Run(() => { requestLock.Wait(3000); + dummyAPI.Friends.Add(apiRelation); req.TriggerSuccess(apiRelation); }); - dummyAPI.Friends.Add(apiRelation); return true; }; }); + AddStep("clear friend list", () => dummyAPI.Friends.Clear()); + AddStep("Show non-friend user", () => header.User.Value = new UserProfileData(nonFriend, new OsuRuleset().RulesetInfo)); AddStep("Click followers button", () => this.ChildrenOfType().First().TriggerClick()); AddStep("Complete request", () => requestLock.Set()); - AddUntilStep("Friend added", () => API.Friends.Any(f => f.TargetID == TestSceneUserProfileOverlay.TEST_USER.OnlineID)); + AddUntilStep("Friend added", () => API.Friends.Any(f => f.TargetID == nonFriend.OnlineID)); } [Test] public void TestAddFriendNonMutual() { - AddStep("clear friend list", () => dummyAPI.Friends.Clear()); - AddStep("Show non-friend user", () => header.User.Value = new UserProfileData(TestSceneUserProfileOverlay.TEST_USER, new OsuRuleset().RulesetInfo)); AddStep("Setup request", () => { requestLock.Reset(); - dummyAPI.HandleRequest += request => + dummyAPI.HandleRequest = request => { if (request is not FriendAddRequest req) return false; - if (req.TargetId != 1) + if (req.TargetId != nonFriend.OnlineID) return false; - var apiRelation = CreateAPIRelationFromAPIUser(TestSceneUserProfileOverlay.TEST_USER); + var apiRelation = CreateAPIRelationFromAPIUser(nonFriend); apiRelation.Mutual = false; Task.Run(() => { requestLock.Wait(3000); + dummyAPI.Friends.Add(apiRelation); req.TriggerSuccess(apiRelation); }); - dummyAPI.Friends.Add(apiRelation); return true; }; }); + AddStep("clear friend list", () => dummyAPI.Friends.Clear()); + AddStep("Show non-friend user", () => header.User.Value = new UserProfileData(nonFriend, new OsuRuleset().RulesetInfo)); AddStep("Click followers button", () => this.ChildrenOfType().First().TriggerClick()); AddStep("Complete request", () => requestLock.Set()); - AddUntilStep("Friend added", () => API.Friends.Any(f => f.TargetID == TestSceneUserProfileOverlay.TEST_USER.OnlineID)); + AddUntilStep("Friend added", () => API.Friends.Any(f => f.TargetID == nonFriend.OnlineID)); } } } From 3bd116cd658b8b612ac16160f72cea396921d92f Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 1 Nov 2024 14:42:50 +0800 Subject: [PATCH 708/924] typo --- .../Profile/Header/Components/FollowersButton.cs | 13 +++++++------ .../Header/Components/ProfileHeaderButton.cs | 6 +++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs index 38108a1544..337a8e4545 100644 --- a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs @@ -68,7 +68,7 @@ namespace osu.Game.Overlays.Profile.Header.Components if (status.Value == FriendStatus.Self) return; - ShowLodingLayer(); + ShowLoadingLayer(); APIRequest req = status.Value == FriendStatus.None ? new FriendAddRequest(User.Value.User.OnlineID) : new FriendDeleteRequest(User.Value.User.OnlineID); @@ -77,7 +77,8 @@ namespace osu.Game.Overlays.Profile.Header.Components followerCount += status.Value == FriendStatus.None ? 1 : -1; api.UpdateLocalFriends(); - HideLodingLayer(); + updateStatus(); + HideLoadingLayer(); }; req.Failure += e => @@ -88,7 +89,7 @@ namespace osu.Game.Overlays.Profile.Header.Components Icon = FontAwesome.Solid.Times, }); - HideLodingLayer(); + HideLoadingLayer(); }; api.Queue(req); @@ -164,19 +165,19 @@ namespace osu.Game.Overlays.Profile.Header.Components case FriendStatus.None: IdleColour = colourProvider.Background6; HoverColour = colourProvider.Background5; - SetBackGroundColour(colourProvider.Background6, 200); + SetBackgroundColour(colourProvider.Background6, 200); break; case FriendStatus.NotMutual: IdleColour = colour.Green; HoverColour = colour.Green.Lighten(0.1f); - SetBackGroundColour(colour.Green, 200); + SetBackgroundColour(colour.Green, 200); break; case FriendStatus.Mutual: IdleColour = colour.Pink; HoverColour = colour.Pink1.Lighten(0.1f); - SetBackGroundColour(colour.Pink, 200); + SetBackgroundColour(colour.Pink, 200); break; } } diff --git a/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs index eb951ef026..2c30d999e5 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs @@ -49,17 +49,17 @@ namespace osu.Game.Overlays.Profile.Header.Components }); } - protected void SetBackGroundColour(ColourInfo colorInfo, double duration = 0) + protected void SetBackgroundColour(ColourInfo colorInfo, double duration = 0) { background.FadeColour(colorInfo, duration); } - protected void ShowLodingLayer() + protected void ShowLoadingLayer() { loading.Show(); } - protected void HideLodingLayer() + protected void HideLoadingLayer() { loading.Hide(); } From 9e4c382a61ae5cc13484b4354aac03c6fde2fe05 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 1 Nov 2024 14:43:00 +0800 Subject: [PATCH 709/924] add tooltips --- .../Header/Components/FollowersButton.cs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs index 337a8e4545..9721c45914 100644 --- a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs @@ -25,7 +25,26 @@ namespace osu.Game.Overlays.Profile.Header.Components // the number of friends obtained is stored and modified locally. private int followerCount; - public override LocalisableString TooltipText => FriendsStrings.ButtonsDisabled; + public override LocalisableString TooltipText + { + get + { + switch (status.Value) + { + case FriendStatus.Self: + return FriendsStrings.ButtonsDisabled; + + case FriendStatus.None: + return FriendsStrings.ButtonsAdd; + + case FriendStatus.NotMutual: + case FriendStatus.Mutual: + return FriendsStrings.ButtonsRemove; + } + + return FriendsStrings.TitleCompact; + } + } protected override IconUsage Icon => FontAwesome.Solid.User; From 9766d51559217f1d76cbf9184a6a280571bb7fbf Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Nov 2024 16:02:02 +0900 Subject: [PATCH 710/924] Store attribute to the database --- osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs | 2 ++ osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs index c1d704b0ba..c8f0448767 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs @@ -66,6 +66,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty yield return (ATTRIB_ID_DIFFICULTY, StarRating); yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow); yield return (ATTRIB_ID_OK_HIT_WINDOW, OkHitWindow); + yield return (ATTRIB_ID_MONO_STAMINA_FACTOR, MonoStaminaFactor); } public override void FromDatabaseAttributes(IReadOnlyDictionary values, IBeatmapOnlineInfo onlineInfo) @@ -75,6 +76,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty StarRating = values[ATTRIB_ID_DIFFICULTY]; GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW]; OkHitWindow = values[ATTRIB_ID_OK_HIT_WINDOW]; + MonoStaminaFactor = values[ATTRIB_ID_MONO_STAMINA_FACTOR]; } } } diff --git a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs index ae4239c148..7b6bc37a61 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs @@ -29,6 +29,7 @@ namespace osu.Game.Rulesets.Difficulty protected const int ATTRIB_ID_SPEED_DIFFICULT_STRAIN_COUNT = 23; protected const int ATTRIB_ID_AIM_DIFFICULT_STRAIN_COUNT = 25; protected const int ATTRIB_ID_OK_HIT_WINDOW = 27; + protected const int ATTRIB_ID_MONO_STAMINA_FACTOR = 29; /// /// The mods which were applied to the beatmap. From 5f6395059807f2c0eca9c354dd30e03952bce876 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 1 Nov 2024 11:26:59 +0100 Subject: [PATCH 711/924] Add missing disposal --- osu.Game/Skinning/Components/BeatmapAttributeText.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index 9176c42da8..4e35c90ba6 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -257,6 +257,8 @@ namespace osu.Game.Skinning.Components difficultyCancellationSource?.Cancel(); difficultyCancellationSource?.Dispose(); difficultyCancellationSource = null; + + modSettingTracker?.Dispose(); } } From 729c7f11a94e7378e4dd64177b3b21c2021ef67c Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 1 Nov 2024 19:15:20 +0800 Subject: [PATCH 712/924] add `StringEnumConverter` for `RelationType` --- osu.Game/Online/API/Requests/Responses/APIRelation.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Online/API/Requests/Responses/APIRelation.cs b/osu.Game/Online/API/Requests/Responses/APIRelation.cs index 75b9a97ffc..c7315db8b9 100644 --- a/osu.Game/Online/API/Requests/Responses/APIRelation.cs +++ b/osu.Game/Online/API/Requests/Responses/APIRelation.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace osu.Game.Online.API.Requests.Responses { @@ -20,6 +21,7 @@ namespace osu.Game.Online.API.Requests.Responses public APIUser? TargetUser { get; set; } } + [JsonConverter(typeof(StringEnumConverter))] public enum RelationType { Friend, From 21b1c799f3898eafa22ec763eefc8726d1c41cea Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 1 Nov 2024 19:16:56 +0800 Subject: [PATCH 713/924] rename `FriendAddRequest` to `AddFriendRequest` --- osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs | 4 ++-- .../API/Requests/{FriendAddRequest.cs => AddFriendRequest.cs} | 4 ++-- .../Overlays/Profile/Header/Components/FollowersButton.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) rename osu.Game/Online/API/Requests/{FriendAddRequest.cs => AddFriendRequest.cs} (87%) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs index 864baa8439..725da655c3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs @@ -425,7 +425,7 @@ namespace osu.Game.Tests.Visual.Online dummyAPI.HandleRequest = request => { - if (request is not FriendAddRequest req) + if (request is not AddFriendRequest req) return false; if (req.TargetId != nonFriend.OnlineID) @@ -459,7 +459,7 @@ namespace osu.Game.Tests.Visual.Online dummyAPI.HandleRequest = request => { - if (request is not FriendAddRequest req) + if (request is not AddFriendRequest req) return false; if (req.TargetId != nonFriend.OnlineID) diff --git a/osu.Game/Online/API/Requests/FriendAddRequest.cs b/osu.Game/Online/API/Requests/AddFriendRequest.cs similarity index 87% rename from osu.Game/Online/API/Requests/FriendAddRequest.cs rename to osu.Game/Online/API/Requests/AddFriendRequest.cs index 80aa7cb995..892ef0c7db 100644 --- a/osu.Game/Online/API/Requests/FriendAddRequest.cs +++ b/osu.Game/Online/API/Requests/AddFriendRequest.cs @@ -7,11 +7,11 @@ using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests { - public class FriendAddRequest : APIRequest + public class AddFriendRequest : APIRequest { public readonly int TargetId; - public FriendAddRequest(int targetId) + public AddFriendRequest(int targetId) { TargetId = targetId; } diff --git a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs index 9721c45914..2e5438d101 100644 --- a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs @@ -89,7 +89,7 @@ namespace osu.Game.Overlays.Profile.Header.Components ShowLoadingLayer(); - APIRequest req = status.Value == FriendStatus.None ? new FriendAddRequest(User.Value.User.OnlineID) : new FriendDeleteRequest(User.Value.User.OnlineID); + APIRequest req = status.Value == FriendStatus.None ? new AddFriendRequest(User.Value.User.OnlineID) : new FriendDeleteRequest(User.Value.User.OnlineID); req.Success += () => { From fbe6077ec22fb1ef28882a2f7a0e326e045fb32f Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 1 Nov 2024 19:17:25 +0800 Subject: [PATCH 714/924] rename `FriendDeleteRequest` to `DeleteFriendRequest` --- .../{FriendDeleteRequest.cs => DeleteFriendRequest.cs} | 4 ++-- .../Overlays/Profile/Header/Components/FollowersButton.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename osu.Game/Online/API/Requests/{FriendDeleteRequest.cs => DeleteFriendRequest.cs} (86%) diff --git a/osu.Game/Online/API/Requests/FriendDeleteRequest.cs b/osu.Game/Online/API/Requests/DeleteFriendRequest.cs similarity index 86% rename from osu.Game/Online/API/Requests/FriendDeleteRequest.cs rename to osu.Game/Online/API/Requests/DeleteFriendRequest.cs index 9b6c4081da..42ceb2c55a 100644 --- a/osu.Game/Online/API/Requests/FriendDeleteRequest.cs +++ b/osu.Game/Online/API/Requests/DeleteFriendRequest.cs @@ -6,11 +6,11 @@ using osu.Framework.IO.Network; namespace osu.Game.Online.API.Requests { - public class FriendDeleteRequest : APIRequest + public class DeleteFriendRequest : APIRequest { public readonly int TargetId; - public FriendDeleteRequest(int targetId) + public DeleteFriendRequest(int targetId) { TargetId = targetId; } diff --git a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs index 2e5438d101..1a1dbfa3e5 100644 --- a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs @@ -89,7 +89,7 @@ namespace osu.Game.Overlays.Profile.Header.Components ShowLoadingLayer(); - APIRequest req = status.Value == FriendStatus.None ? new AddFriendRequest(User.Value.User.OnlineID) : new FriendDeleteRequest(User.Value.User.OnlineID); + APIRequest req = status.Value == FriendStatus.None ? new AddFriendRequest(User.Value.User.OnlineID) : new DeleteFriendRequest(User.Value.User.OnlineID); req.Success += () => { From 1a92e5ad1960d4962433e0225b436908610bf9d5 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 1 Nov 2024 19:24:58 +0800 Subject: [PATCH 715/924] remove CreateAPIRelationFromAPIUser --- .../Gameplay/TestSceneGameplayLeaderboard.cs | 8 ++++++- .../Online/TestSceneDashboardOverlay.cs | 22 ++++++++++++------- .../Online/TestSceneUserProfileHeader.cs | 17 +++++++++++--- osu.Game/Tests/Visual/OsuTestScene.cs | 9 -------- 4 files changed, 35 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs index 135c1fd50c..1787230117 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs @@ -155,7 +155,13 @@ namespace osu.Game.Tests.Visual.Gameplay var api = (DummyAPIAccess)API; api.Friends.Clear(); - api.Friends.Add(CreateAPIRelationFromAPIUser(friend)); + api.Friends.Add(new APIRelation + { + Mutual = true, + RelationType = RelationType.Friend, + TargetID = friend.OnlineID, + TargetUser = friend + }); }); int playerNumber = 1; diff --git a/osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs index f2ea084f40..fb54e936bc 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs @@ -30,15 +30,21 @@ namespace osu.Game.Tests.Visual.Online if (supportLevel > 3) supportLevel = 0; - ((DummyAPIAccess)API).Friends.Add(CreateAPIRelationFromAPIUser(new APIUser + ((DummyAPIAccess)API).Friends.Add(new APIRelation { - Username = @"peppy", - Id = 2, - Colour = "99EB47", - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", - IsSupporter = supportLevel > 0, - SupportLevel = supportLevel - })); + TargetID = 2, + RelationType = RelationType.Friend, + Mutual = true, + TargetUser = new APIUser + { + Username = @"peppy", + Id = 2, + Colour = "99EB47", + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + IsSupporter = supportLevel > 0, + SupportLevel = supportLevel + } + }); } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs index 725da655c3..9c368f6d73 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs @@ -431,7 +431,13 @@ namespace osu.Game.Tests.Visual.Online if (req.TargetId != nonFriend.OnlineID) return false; - var apiRelation = CreateAPIRelationFromAPIUser(nonFriend); + var apiRelation = new APIRelation + { + TargetID = nonFriend.OnlineID, + Mutual = true, + RelationType = RelationType.Friend, + TargetUser = nonFriend + }; Task.Run(() => { @@ -465,8 +471,13 @@ namespace osu.Game.Tests.Visual.Online if (req.TargetId != nonFriend.OnlineID) return false; - var apiRelation = CreateAPIRelationFromAPIUser(nonFriend); - apiRelation.Mutual = false; + var apiRelation = new APIRelation + { + TargetID = nonFriend.OnlineID, + Mutual = false, + RelationType = RelationType.Friend, + TargetUser = nonFriend + }; Task.Run(() => { diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 853c5d5f5c..09cfe5ecad 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -317,15 +317,6 @@ namespace osu.Game.Tests.Visual return result; } - public static APIRelation CreateAPIRelationFromAPIUser(APIUser user) => - new APIRelation - { - Mutual = true, - RelationType = RelationType.Friend, - TargetID = user.OnlineID, - TargetUser = user - }; - protected WorkingBeatmap CreateWorkingBeatmap(RulesetInfo ruleset) => CreateWorkingBeatmap(CreateBeatmap(ruleset)); From 488fabcd5479ac4cdd43889ed71202eb00b75278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 1 Nov 2024 13:07:32 +0100 Subject: [PATCH 716/924] Use alternative method of resizing the catcher trails This one preserves the catcher afterimage, which I'd count as a win. --- .../UI/CatcherTrailDisplay.cs | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs index ad2ae53f48..6b888be961 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; @@ -62,21 +63,16 @@ namespace osu.Game.Rulesets.Catch.UI /// The new body scale of the Catcher public void UpdateCatcherTrailsScale(Vector2 scale) { - applyScaleChange(scale, dashTrails); - applyScaleChange(scale, hyperDashTrails); + var oldEntries = Entries.ToList(); - foreach (var afterImage in hyperDashAfterImages) - { - afterImage.Hide(); - afterImage.Expire(); - } - } + Clear(); - private void applyScaleChange(Vector2 scale, Container trails) - { - foreach (var trail in trails) + foreach (var oldEntry in oldEntries) { - trail.Scale = scale; + // use magnitude of the new scale while preserving the sign of the old one in the X direction. + // the end effect is preserving the direction in which the trail sprites face, which is important. + var targetScale = new Vector2(Math.Abs(scale.X) * Math.Sign(oldEntry.Scale.X), Math.Abs(scale.Y)); + Add(new CatcherTrailEntry(oldEntry.LifetimeStart, oldEntry.CatcherState, oldEntry.Position, targetScale, oldEntry.Animation)); } } From ef22b6b1a86afa8998ec6cb7c482ba28bbf097d0 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Nov 2024 22:40:00 +0900 Subject: [PATCH 717/924] Round to integral units The rounding matches the implementation of `PerformancePointsCounter`. --- osu.Game/Skinning/Components/BeatmapAttributeText.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index c858e75032..79a1ed4d7c 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -229,7 +229,7 @@ namespace osu.Game.Skinning.Components return (starDifficulty?.Stars ?? 0).ToLocalisableString(@"F2"); case BeatmapAttribute.MaxPP: - return (starDifficulty?.PerformanceAttributes?.Total ?? 0).ToLocalisableString(@"F2"); + return Math.Round(starDifficulty?.PerformanceAttributes?.Total ?? 0, MidpointRounding.AwayFromZero).ToLocalisableString(); default: return string.Empty; From 3e7fcc3c2410ff959d18173f1ac3ac2559292e4b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Nov 2024 22:52:51 +0900 Subject: [PATCH 718/924] Fix NaN values when stamina difficulty is 0 --- .../Difficulty/TaikoDifficultyCalculator.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 2dca44fb76..bf9e320e57 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -86,8 +86,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty double rhythmRating = rhythm.DifficultyValue() * rhythm_skill_multiplier; double staminaRating = stamina.DifficultyValue() * stamina_skill_multiplier; double monoStaminaRating = singleColourStamina.DifficultyValue() * stamina_skill_multiplier; - - double monoStaminaFactor = Math.Pow(monoStaminaRating / staminaRating, 5); + double monoStaminaFactor = staminaRating == 0 ? 1 : Math.Pow(monoStaminaRating / staminaRating, 5); double combinedRating = combinedDifficultyValue(rhythm, colour, stamina); double starRating = rescale(combinedRating * 1.4); From 03fc744e927e2cab99f2d43f9e289c75a70dba61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 1 Nov 2024 18:26:49 +0100 Subject: [PATCH 719/924] Fix test --- .../Visual/UserInterface/TestSceneBeatmapAttributeText.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs index df128ed89e..e3a6fca319 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs @@ -164,16 +164,16 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("set test ruleset", () => Ruleset.Value = new TestRuleset().RulesetInfo); AddStep("set max pp attribute", () => text.Attribute.Value = BeatmapAttribute.MaxPP); - AddAssert("check max pp is 0", getText, () => Is.EqualTo("Max PP: 0.00")); + AddAssert("check max pp is 0", getText, () => Is.EqualTo("Max PP: 0")); // Adding mod TestMod mod = null!; AddStep("add mod with pp 1", () => SelectedMods.Value = new[] { mod = new TestMod { Performance = { Value = 1 } } }); - AddUntilStep("check max pp is 1", getText, () => Is.EqualTo("Max PP: 1.00")); + AddUntilStep("check max pp is 1", getText, () => Is.EqualTo("Max PP: 1")); // Changing mod setting AddStep("change mod pp to 2", () => mod.Performance.Value = 2); - AddUntilStep("check max pp is 2", getText, () => Is.EqualTo("Max PP: 2.00")); + AddUntilStep("check max pp is 2", getText, () => Is.EqualTo("Max PP: 2")); } private string getText() => text.ChildrenOfType().Single().Text.ToString(); From 8f48682d0a8a8385d2ade6db7ce0f03eac1668b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 1 Nov 2024 18:36:01 +0100 Subject: [PATCH 720/924] Remove hide sample toggle In line with feedback from https://github.com/ppy/osu/pull/29896#issuecomment-2378676851. --- osu.Game/Configuration/OsuConfigManager.cs | 2 -- osu.Game/Localisation/EditorStrings.cs | 5 ----- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 5 ----- osu.Game/Screens/Edit/Editor.cs | 6 ------ 4 files changed, 18 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index ae2025fb50..8705253b7d 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -205,7 +205,6 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.EditorTimelineShowTimingChanges, true); SetDefault(OsuSetting.EditorTimelineShowTicks, true); - SetDefault(OsuSetting.EditorTimelineShowSamples, true); SetDefault(OsuSetting.AlwaysShowHoldForMenuButton, false); } @@ -433,6 +432,5 @@ namespace osu.Game.Configuration EditorTimelineShowTimingChanges, EditorTimelineShowTicks, AlwaysShowHoldForMenuButton, - EditorTimelineShowSamples } } diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs index f72a0f4a26..bcffc18d4d 100644 --- a/osu.Game/Localisation/EditorStrings.cs +++ b/osu.Game/Localisation/EditorStrings.cs @@ -139,11 +139,6 @@ namespace osu.Game.Localisation /// public static LocalisableString TimelineShowTicks => new TranslatableString(getKey(@"timeline_show_ticks"), @"Show ticks"); - /// - /// "Show samples" - /// - public static LocalisableString TimelineShowSamples => new TranslatableString(getKey(@"timeline_show_samples"), @"Show samples"); - private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 48178b2e19..0cfaeb8c38 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -44,8 +44,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private TimelineBlueprintContainer? timelineBlueprintContainer { get; set; } - private Bindable samplesVisible = null!; - public SamplePointPiece(HitObject hitObject) { HitObject = hitObject; @@ -68,8 +66,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (editor != null) editor.ShowSampleEditPopoverRequested += onShowSampleEditPopoverRequested; - - samplesVisible = config.GetBindable(OsuSetting.EditorTimelineShowSamples); } private readonly Bindable contracted = new Bindable(); @@ -97,7 +93,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } }, true); - samplesVisible.BindValueChanged(visible => this.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint), true); FinishTransforms(); } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b059557b28..9bb91af806 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -215,7 +215,6 @@ namespace osu.Game.Screens.Edit private Bindable editorLimitedDistanceSnap; private Bindable editorTimelineShowTimingChanges; private Bindable editorTimelineShowTicks; - private Bindable editorTimelineShowSamples; /// /// This controls the opacity of components like the timelines, sidebars, etc. @@ -324,7 +323,6 @@ namespace osu.Game.Screens.Edit editorLimitedDistanceSnap = config.GetBindable(OsuSetting.EditorLimitedDistanceSnap); editorTimelineShowTimingChanges = config.GetBindable(OsuSetting.EditorTimelineShowTimingChanges); editorTimelineShowTicks = config.GetBindable(OsuSetting.EditorTimelineShowTicks); - editorTimelineShowSamples = config.GetBindable(OsuSetting.EditorTimelineShowSamples); AddInternal(new OsuContextMenuContainer { @@ -390,10 +388,6 @@ namespace osu.Game.Screens.Edit { State = { BindTarget = editorTimelineShowTicks } }, - new ToggleMenuItem(EditorStrings.TimelineShowSamples) - { - State = { BindTarget = editorTimelineShowSamples } - } ] }, new BackgroundDimMenuItem(editorBackgroundDim), From 0a33d71671895398743a2ea8b853b588b2ff1794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 1 Nov 2024 19:26:56 +0100 Subject: [PATCH 721/924] Add test coverage --- .../Settings/TestSceneKeyBindingPanel.cs | 55 +++++++++++++++---- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs index 86008a56a4..4cad283833 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs @@ -414,11 +414,7 @@ namespace osu.Game.Tests.Visual.Settings }); AddStep("move mouse to centre", () => InputManager.MoveMouseTo(panel.ScreenSpaceDrawQuad.Centre)); scrollToAndStartBinding("Left (centre)"); - AddStep("clear binding", () => - { - var row = panel.ChildrenOfType().First(r => r.ChildrenOfType().Any(s => s.Text.ToString() == "Left (centre)")); - row.ChildrenOfType().Single().TriggerClick(); - }); + clearBinding(); scrollToAndStartBinding("Left (rim)"); AddStep("bind M1", () => InputManager.Click(MouseButton.Left)); @@ -431,6 +427,45 @@ namespace osu.Game.Tests.Visual.Settings AddUntilStep("conflict popover not shown", () => panel.ChildrenOfType().SingleOrDefault(), () => Is.Null); } + [Test] + public void TestResettingRowCannotConflictWithItself() + { + AddStep("reset taiko section to default", () => + { + var section = panel.ChildrenOfType().First(section => new TaikoRuleset().RulesetInfo.Equals(section.Ruleset)); + section.ChildrenOfType().Single().TriggerClick(); + }); + AddStep("move mouse to centre", () => InputManager.MoveMouseTo(panel.ScreenSpaceDrawQuad.Centre)); + + scrollToAndStartBinding("Left (centre)"); + clearBinding(); + scrollToAndStartBinding("Left (centre)", 1); + clearBinding(); + + scrollToAndStartBinding("Left (centre)"); + AddStep("bind F", () => InputManager.Key(Key.F)); + scrollToAndStartBinding("Left (centre)", 1); + AddStep("bind M1", () => InputManager.Click(MouseButton.Left)); + + AddStep("revert row to default", () => + { + var row = panel.ChildrenOfType().First(r => r.ChildrenOfType().Any(s => s.Text.ToString() == "Left (centre)")); + InputManager.MoveMouseTo(row.ChildrenOfType>().Single()); + InputManager.Click(MouseButton.Left); + }); + AddWaitStep("wait a bit", 3); + AddUntilStep("conflict popover not shown", () => panel.ChildrenOfType().SingleOrDefault(), () => Is.Null); + } + + private void clearBinding() + { + AddStep("clear binding", () => + { + var row = panel.ChildrenOfType().First(r => r.ChildrenOfType().Any(s => s.Text.ToString() == "Left (centre)")); + row.ChildrenOfType().Single().TriggerClick(); + }); + } + private void checkBinding(string name, string keyName) { AddAssert($"Check {name} is bound to {keyName}", () => @@ -442,23 +477,23 @@ namespace osu.Game.Tests.Visual.Settings }, () => Is.EqualTo(keyName)); } - private void scrollToAndStartBinding(string name) + private void scrollToAndStartBinding(string name, int bindingIndex = 0) { - KeyBindingRow.KeyButton firstButton = null; + KeyBindingRow.KeyButton targetButton = null; AddStep($"Scroll to {name}", () => { var firstRow = panel.ChildrenOfType().First(r => r.ChildrenOfType().Any(s => s.Text.ToString() == name)); - firstButton = firstRow.ChildrenOfType().First(); + targetButton = firstRow.ChildrenOfType().ElementAt(bindingIndex); - panel.ChildrenOfType().First().ScrollTo(firstButton); + panel.ChildrenOfType().First().ScrollTo(targetButton); }); AddWaitStep("wait for scroll", 5); AddStep("click to bind", () => { - InputManager.MoveMouseTo(firstButton); + InputManager.MoveMouseTo(targetButton); InputManager.Click(MouseButton.Left); }); } From f5a2674f6696cce7ba483bd25bf57c897af1bb30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 1 Nov 2024 19:26:59 +0100 Subject: [PATCH 722/924] Rewrite fix in a more legible way - Use better param name ("restoring" what bindings? the key information there is that the *defaults* are being restored) - Split ugly and not easily understandable (but probably best-that-can-be-done) conditional out to a method and comment it appropriately --- .../Settings/Sections/Input/KeyBindingRow.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs index 2003c7fef6..083c678176 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs @@ -222,7 +222,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input var button = buttons[i++]; button.UpdateKeyCombination(d); - tryPersistKeyBinding(button.KeyBinding.Value, advanceToNextBinding: false, restoringBinding: true); + tryPersistKeyBinding(button.KeyBinding.Value, advanceToNextBinding: false, restoringDefaults: true); } isDefault.Value = true; @@ -489,12 +489,25 @@ namespace osu.Game.Overlays.Settings.Sections.Input base.OnFocusLost(e); } - private void tryPersistKeyBinding(RealmKeyBinding keyBinding, bool advanceToNextBinding, bool restoringBinding = false) + private bool isConflictingBinding(RealmKeyBinding first, RealmKeyBinding second, bool restoringDefaults) + { + if (first.ID == second.ID) + return false; + + // ignore conflicts with same action bindings during revert. the assumption is that the other binding will be reverted subsequently in the same higher-level operation. + // this happens if the bindings for an action are rebound to the same keys, but the ordering of the bindings itself is different. + if (restoringDefaults && first.ActionInt == second.ActionInt) + return false; + + return first.KeyCombination.Equals(second.KeyCombination); + } + + private void tryPersistKeyBinding(RealmKeyBinding keyBinding, bool advanceToNextBinding, bool restoringDefaults = false) { List bindings = GetAllSectionBindings(); RealmKeyBinding? existingBinding = keyBinding.KeyCombination.Equals(new KeyCombination(InputKey.None)) ? null - : bindings.FirstOrDefault(other => other.ID != keyBinding.ID && other.KeyCombination.Equals(keyBinding.KeyCombination) && (!restoringBinding || other.ActionInt != keyBinding.ActionInt)); + : bindings.FirstOrDefault(other => isConflictingBinding(keyBinding, other, restoringDefaults)); if (existingBinding == null) { From 4eee1f429b7fec220299512b43c193aabdf77b5c Mon Sep 17 00:00:00 2001 From: Jay Lawton Date: Sun, 3 Nov 2024 00:47:53 +1000 Subject: [PATCH 723/924] fix spelling error --- .../Difficulty/TaikoPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 9e89c0c110..c672b7a1d9 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (totalSuccessfulHits > 0) effectiveMissCount = Math.Max(1.0, 1000.0 / totalSuccessfulHits) * countMiss; - // Converts are detected and omitted from mod-specific bonuses due to the scope of current difficulty calcuation. + // Converts are detected and omitted from mod-specific bonuses due to the scope of current difficulty calculation. bool isConvert = score.BeatmapInfo!.Ruleset.OnlineID != 1; double multiplier = 1.13; From 57227b5aab967fdb174c27a3cf28b3a81447f03f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 3 Nov 2024 14:19:57 +0900 Subject: [PATCH 724/924] Allow scaling down to 5% in popover scale dialog Request from mapper IRL. --- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index 68f5b268f8..4fd7c297a0 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Edit { Current = scaleInputBindable = new BindableNumber { - MinValue = 0.5f, + MinValue = 0.05f, MaxValue = 2, Precision = 0.001f, Value = 1, From b03963ac84fe7e34a09ea01d9dc6734b3c42332f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 3 Nov 2024 15:20:45 +0900 Subject: [PATCH 725/924] Remember origin for editor scale popover --- .../Edit/PreciseRotationPopover.cs | 23 ++--- .../Edit/PreciseScalePopover.cs | 88 ++++++++++++++----- osu.Game/Configuration/OsuConfigManager.cs | 6 +- .../Edit/Compose/Components/EditorOrigin.cs | 12 +++ 4 files changed, 93 insertions(+), 36 deletions(-) create mode 100644 osu.Game/Screens/Edit/Compose/Components/EditorOrigin.cs diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs index b18452768c..18d3a4f627 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Edit private readonly OsuGridToolboxGroup gridToolbox; - private readonly Bindable rotationInfo = new Bindable(new PreciseRotationInfo(0, RotationOrigin.GridCentre)); + private readonly Bindable rotationInfo = new Bindable(new PreciseRotationInfo(0, EditorOrigin.GridCentre)); private SliderWithTextBoxInput angleInput = null!; private EditorRadioButtonCollection rotationOrigin = null!; @@ -67,13 +67,13 @@ namespace osu.Game.Rulesets.Osu.Edit Items = new[] { new RadioButton("Grid centre", - () => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.GridCentre }, + () => rotationInfo.Value = rotationInfo.Value with { Origin = EditorOrigin.GridCentre }, () => new SpriteIcon { Icon = FontAwesome.Regular.PlusSquare }), new RadioButton("Playfield centre", - () => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.PlayfieldCentre }, + () => rotationInfo.Value = rotationInfo.Value with { Origin = EditorOrigin.PlayfieldCentre }, () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), selectionCentreButton = new RadioButton("Selection centre", - () => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.SelectionCentre }, + () => rotationInfo.Value = rotationInfo.Value with { Origin = EditorOrigin.SelectionCentre }, () => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare }) } } @@ -111,9 +111,9 @@ namespace osu.Game.Rulesets.Osu.Edit private Vector2? getOriginPosition(PreciseRotationInfo rotation) => rotation.Origin switch { - RotationOrigin.GridCentre => gridToolbox.StartPosition.Value, - RotationOrigin.PlayfieldCentre => OsuPlayfield.BASE_SIZE / 2, - RotationOrigin.SelectionCentre => null, + EditorOrigin.GridCentre => gridToolbox.StartPosition.Value, + EditorOrigin.PlayfieldCentre => OsuPlayfield.BASE_SIZE / 2, + EditorOrigin.SelectionCentre => null, _ => throw new ArgumentOutOfRangeException(nameof(rotation)) }; @@ -143,12 +143,5 @@ namespace osu.Game.Rulesets.Osu.Edit } } - public enum RotationOrigin - { - GridCentre, - PlayfieldCentre, - SelectionCentre - } - - public record PreciseRotationInfo(float Degrees, RotationOrigin Origin); + public record PreciseRotationInfo(float Degrees, EditorOrigin Origin); } diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index 68f5b268f8..915659f47f 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Input.Bindings; @@ -18,6 +19,7 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.RadioButtons; +using osu.Game.Screens.Edit.Compose.Components; using osuTK; namespace osu.Game.Rulesets.Osu.Edit @@ -28,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Edit private readonly OsuGridToolboxGroup gridToolbox; - private readonly Bindable scaleInfo = new Bindable(new PreciseScaleInfo(1, ScaleOrigin.GridCentre, true, true)); + private readonly Bindable scaleInfo = new Bindable(new PreciseScaleInfo(1, EditorOrigin.GridCentre, true, true)); private SliderWithTextBoxInput scaleInput = null!; private BindableNumber scaleInputBindable = null!; @@ -41,6 +43,8 @@ namespace osu.Game.Rulesets.Osu.Edit private OsuCheckbox xCheckBox = null!; private OsuCheckbox yCheckBox = null!; + private Bindable configScaleOrigin = null!; + private BindableList selectedItems { get; } = new BindableList(); public PreciseScalePopover(OsuSelectionScaleHandler scaleHandler, OsuGridToolboxGroup gridToolbox) @@ -52,10 +56,12 @@ namespace osu.Game.Rulesets.Osu.Edit } [BackgroundDependencyLoader] - private void load(EditorBeatmap editorBeatmap) + private void load(EditorBeatmap editorBeatmap, OsuConfigManager config) { selectedItems.BindTo(editorBeatmap.SelectedHitObjects); + configScaleOrigin = config.GetBindable(OsuSetting.EditorScaleOrigin); + Child = new FillFlowContainer { Width = 220, @@ -82,13 +88,13 @@ namespace osu.Game.Rulesets.Osu.Edit Items = new[] { gridCentreButton = new RadioButton("Grid centre", - () => setOrigin(ScaleOrigin.GridCentre), + () => setOrigin(EditorOrigin.GridCentre), () => new SpriteIcon { Icon = FontAwesome.Regular.PlusSquare }), playfieldCentreButton = new RadioButton("Playfield centre", - () => setOrigin(ScaleOrigin.PlayfieldCentre), + () => setOrigin(EditorOrigin.PlayfieldCentre), () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), selectionCentreButton = new RadioButton("Selection centre", - () => setOrigin(ScaleOrigin.SelectionCentre), + () => setOrigin(EditorOrigin.SelectionCentre), () => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare }) } }, @@ -165,7 +171,56 @@ namespace osu.Game.Rulesets.Osu.Edit playfieldCentreButton.Selected.Disabled = scaleHandler.IsScalingSlider.Value && !selectionCentreButton.Selected.Disabled; gridCentreButton.Selected.Disabled = playfieldCentreButton.Selected.Disabled; - scaleOrigin.Items.First(b => !b.Selected.Disabled).Select(); + bool didSelect = false; + + configScaleOrigin.BindValueChanged(val => + { + switch (configScaleOrigin.Value) + { + case EditorOrigin.GridCentre: + if (!gridCentreButton.Selected.Disabled) + { + gridCentreButton.Select(); + didSelect = true; + } + + break; + + case EditorOrigin.PlayfieldCentre: + if (!playfieldCentreButton.Selected.Disabled) + { + playfieldCentreButton.Select(); + didSelect = true; + } + + break; + + case EditorOrigin.SelectionCentre: + if (!selectionCentreButton.Selected.Disabled) + { + selectionCentreButton.Select(); + didSelect = true; + } + + break; + } + }, true); + + if (!didSelect) + scaleOrigin.Items.First(b => !b.Selected.Disabled).Select(); + + gridCentreButton.Selected.BindValueChanged(b => + { + if (b.NewValue) configScaleOrigin.Value = EditorOrigin.GridCentre; + }); + playfieldCentreButton.Selected.BindValueChanged(b => + { + if (b.NewValue) configScaleOrigin.Value = EditorOrigin.PlayfieldCentre; + }); + selectionCentreButton.Selected.BindValueChanged(b => + { + if (b.NewValue) configScaleOrigin.Value = EditorOrigin.SelectionCentre; + }); scaleInfo.BindValueChanged(scale => { @@ -182,7 +237,7 @@ namespace osu.Game.Rulesets.Osu.Edit private void updateAxisCheckBoxesEnabled() { - if (scaleInfo.Value.Origin != ScaleOrigin.SelectionCentre) + if (scaleInfo.Value.Origin != EditorOrigin.SelectionCentre) { toggleAxisAvailable(xCheckBox.Current, true); toggleAxisAvailable(yCheckBox.Current, true); @@ -230,7 +285,7 @@ namespace osu.Game.Rulesets.Osu.Edit scaleInputBindable.MinValue = MathF.Min(1, MathF.Max(scale.X, scale.Y)); } - private void setOrigin(ScaleOrigin origin) + private void setOrigin(EditorOrigin origin) { scaleInfo.Value = scaleInfo.Value with { Origin = origin }; updateMinMaxScale(); @@ -241,13 +296,13 @@ namespace osu.Game.Rulesets.Osu.Edit { switch (scale.Origin) { - case ScaleOrigin.GridCentre: + case EditorOrigin.GridCentre: return gridToolbox.StartPosition.Value; - case ScaleOrigin.PlayfieldCentre: + case EditorOrigin.PlayfieldCentre: return OsuPlayfield.BASE_SIZE / 2; - case ScaleOrigin.SelectionCentre: + case EditorOrigin.SelectionCentre: if (selectedItems.Count == 1 && selectedItems.First() is Slider slider) return slider.Position; @@ -271,7 +326,7 @@ namespace osu.Game.Rulesets.Osu.Edit return result; } - private float getRotation(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.GridCentre ? gridToolbox.GridLinesRotation.Value : 0; + private float getRotation(PreciseScaleInfo scale) => scale.Origin == EditorOrigin.GridCentre ? gridToolbox.GridLinesRotation.Value : 0; protected override void PopIn() { @@ -299,12 +354,5 @@ namespace osu.Game.Rulesets.Osu.Edit } } - public enum ScaleOrigin - { - GridCentre, - PlayfieldCentre, - SelectionCentre - } - - public record PreciseScaleInfo(float Scale, ScaleOrigin Origin, bool XAxis, bool YAxis); + public record PreciseScaleInfo(float Scale, EditorOrigin Origin, bool XAxis, bool YAxis); } diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index a16abe5c55..54c0af00c3 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -17,6 +17,7 @@ using osu.Game.Localisation; using osu.Game.Overlays; using osu.Game.Overlays.Mods.Input; using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Filter; @@ -193,6 +194,8 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.EditorAutoSeekOnPlacement, true); SetDefault(OsuSetting.EditorLimitedDistanceSnap, false); SetDefault(OsuSetting.EditorShowSpeedChanges, false); + SetDefault(OsuSetting.EditorScaleOrigin, EditorOrigin.GridCentre); + SetDefault(OsuSetting.EditorRotateOrigin, EditorOrigin.GridCentre); SetDefault(OsuSetting.HideCountryFlags, false); @@ -434,6 +437,7 @@ namespace osu.Game.Configuration EditorTimelineShowTimingChanges, EditorTimelineShowTicks, AlwaysShowHoldForMenuButton, - EditorContractSidebars + EditorContractSidebars, + EditorScaleOrigin } } diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorOrigin.cs b/osu.Game/Screens/Edit/Compose/Components/EditorOrigin.cs new file mode 100644 index 0000000000..4aa7bf68d7 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/EditorOrigin.cs @@ -0,0 +1,12 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public enum EditorOrigin + { + GridCentre, + PlayfieldCentre, + SelectionCentre + } +} From 3a3471c202d28d6c1e429fd1a48bc546173a778d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 3 Nov 2024 15:23:55 +0900 Subject: [PATCH 726/924] Remember origin for editor rotation popover --- .../Edit/PreciseRotationPopover.cs | 65 +++++++++++++++++-- osu.Game/Configuration/OsuConfigManager.cs | 5 +- 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs index 18d3a4f627..678d98250a 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Game.Configuration; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Osu.UI; @@ -30,8 +31,12 @@ namespace osu.Game.Rulesets.Osu.Edit private SliderWithTextBoxInput angleInput = null!; private EditorRadioButtonCollection rotationOrigin = null!; + private RadioButton gridCentreButton = null!; + private RadioButton playfieldCentreButton = null!; private RadioButton selectionCentreButton = null!; + private Bindable configRotationOrigin = null!; + public PreciseRotationPopover(SelectionRotationHandler rotationHandler, OsuGridToolboxGroup gridToolbox) { this.rotationHandler = rotationHandler; @@ -41,8 +46,10 @@ namespace osu.Game.Rulesets.Osu.Edit } [BackgroundDependencyLoader] - private void load() + private void load(OsuConfigManager config) { + configRotationOrigin = config.GetBindable(OsuSetting.EditorRotationOrigin); + Child = new FillFlowContainer { Width = 220, @@ -66,10 +73,10 @@ namespace osu.Game.Rulesets.Osu.Edit RelativeSizeAxes = Axes.X, Items = new[] { - new RadioButton("Grid centre", + gridCentreButton = new RadioButton("Grid centre", () => rotationInfo.Value = rotationInfo.Value with { Origin = EditorOrigin.GridCentre }, () => new SpriteIcon { Icon = FontAwesome.Regular.PlusSquare }), - new RadioButton("Playfield centre", + playfieldCentreButton = new RadioButton("Playfield centre", () => rotationInfo.Value = rotationInfo.Value with { Origin = EditorOrigin.PlayfieldCentre }, () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), selectionCentreButton = new RadioButton("Selection centre", @@ -95,7 +102,57 @@ namespace osu.Game.Rulesets.Osu.Edit angleInput.SelectAll(); }); angleInput.Current.BindValueChanged(angle => rotationInfo.Value = rotationInfo.Value with { Degrees = angle.NewValue }); - rotationOrigin.Items.First().Select(); + + bool didSelect = false; + + configRotationOrigin.BindValueChanged(val => + { + switch (configRotationOrigin.Value) + { + case EditorOrigin.GridCentre: + if (!gridCentreButton.Selected.Disabled) + { + gridCentreButton.Select(); + didSelect = true; + } + + break; + + case EditorOrigin.PlayfieldCentre: + if (!playfieldCentreButton.Selected.Disabled) + { + playfieldCentreButton.Select(); + didSelect = true; + } + + break; + + case EditorOrigin.SelectionCentre: + if (!selectionCentreButton.Selected.Disabled) + { + selectionCentreButton.Select(); + didSelect = true; + } + + break; + } + }, true); + + if (!didSelect) + rotationOrigin.Items.First(b => !b.Selected.Disabled).Select(); + + gridCentreButton.Selected.BindValueChanged(b => + { + if (b.NewValue) configRotationOrigin.Value = EditorOrigin.GridCentre; + }); + playfieldCentreButton.Selected.BindValueChanged(b => + { + if (b.NewValue) configRotationOrigin.Value = EditorOrigin.PlayfieldCentre; + }); + selectionCentreButton.Selected.BindValueChanged(b => + { + if (b.NewValue) configRotationOrigin.Value = EditorOrigin.SelectionCentre; + }); rotationHandler.CanRotateAroundSelectionOrigin.BindValueChanged(e => { diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 54c0af00c3..1c6479e6f5 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -195,7 +195,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.EditorLimitedDistanceSnap, false); SetDefault(OsuSetting.EditorShowSpeedChanges, false); SetDefault(OsuSetting.EditorScaleOrigin, EditorOrigin.GridCentre); - SetDefault(OsuSetting.EditorRotateOrigin, EditorOrigin.GridCentre); + SetDefault(OsuSetting.EditorRotationOrigin, EditorOrigin.GridCentre); SetDefault(OsuSetting.HideCountryFlags, false); @@ -438,6 +438,7 @@ namespace osu.Game.Configuration EditorTimelineShowTicks, AlwaysShowHoldForMenuButton, EditorContractSidebars, - EditorScaleOrigin + EditorScaleOrigin, + EditorRotationOrigin } } From a49b2eaa3b6df06c64b3646b29d83c97a8453853 Mon Sep 17 00:00:00 2001 From: iSlodinx <112963750+iSlodinxOsu@users.noreply.github.com> Date: Sun, 3 Nov 2024 18:52:40 +0100 Subject: [PATCH 727/924] Update PlayerLoaderStrings.cs --- osu.Game/Localisation/PlayerLoaderStrings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/PlayerLoaderStrings.cs b/osu.Game/Localisation/PlayerLoaderStrings.cs index eba98c7aa7..f9d6f80676 100644 --- a/osu.Game/Localisation/PlayerLoaderStrings.cs +++ b/osu.Game/Localisation/PlayerLoaderStrings.cs @@ -26,10 +26,10 @@ namespace osu.Game.Localisation /// /// "No performance points will be awarded. - /// Leaderboards may be reset by the beatmap creator." + /// Leaderboards may be reset." /// public static LocalisableString LovedBeatmapDisclaimerContent => new TranslatableString(getKey(@"loved_beatmap_disclaimer_content"), @"No performance points will be awarded. -Leaderboards may be reset by the beatmap creator."); +Leaderboards may be reset."); /// /// "This beatmap is qualified" From f616c7b752bb224107be796b11311f0b0aea77aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Nov 2024 10:36:49 +0100 Subject: [PATCH 728/924] Fix scale clamps undoing the intended 5% scaling minimum --- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index 4fd7c297a0..3f5a449cee 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -208,7 +208,7 @@ namespace osu.Game.Rulesets.Osu.Edit if (!scaleHandler.OriginalSurroundingQuad.HasValue) return; - const float min_scale = 0.5f; + const float min_scale = 0.05f; const float max_scale = 10; var scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(max_scale), getOriginPosition(scaleInfo.Value), getAdjustAxis(scaleInfo.Value), getRotation(scaleInfo.Value)); From e2a4a9b30024f82f9b40885cecf135867b749412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Nov 2024 10:58:48 +0100 Subject: [PATCH 729/924] Fix rotation popover potentially crashing due to activating selection origin just before disabling it --- osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs index 678d98250a..477d3b4e57 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs @@ -103,6 +103,11 @@ namespace osu.Game.Rulesets.Osu.Edit }); angleInput.Current.BindValueChanged(angle => rotationInfo.Value = rotationInfo.Value with { Degrees = angle.NewValue }); + rotationHandler.CanRotateAroundSelectionOrigin.BindValueChanged(e => + { + selectionCentreButton.Selected.Disabled = !e.NewValue; + }, true); + bool didSelect = false; configRotationOrigin.BindValueChanged(val => @@ -154,11 +159,6 @@ namespace osu.Game.Rulesets.Osu.Edit if (b.NewValue) configRotationOrigin.Value = EditorOrigin.SelectionCentre; }); - rotationHandler.CanRotateAroundSelectionOrigin.BindValueChanged(e => - { - selectionCentreButton.Selected.Disabled = !e.NewValue; - }, true); - rotationInfo.BindValueChanged(rotation => { rotationHandler.Update(rotation.NewValue.Degrees, getOriginPosition(rotation.NewValue)); From cb833007e0d776652bd038a3fdb59b543c7d940c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Nov 2024 12:02:20 +0100 Subject: [PATCH 730/924] Adjust colours to actually match web --- .../Profile/Header/Components/FollowersButton.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs index 1a1dbfa3e5..8198058bd4 100644 --- a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs @@ -178,6 +178,8 @@ namespace osu.Game.Overlays.Profile.Header.Components private void updateColor() { + // https://github.com/ppy/osu-web/blob/0a5367a4a68a6cdf450eb483251b3cf03b3ac7d2/resources/css/bem/user-action-button.less + switch (status.Value) { case FriendStatus.Self: @@ -188,15 +190,15 @@ namespace osu.Game.Overlays.Profile.Header.Components break; case FriendStatus.NotMutual: - IdleColour = colour.Green; - HoverColour = colour.Green.Lighten(0.1f); - SetBackgroundColour(colour.Green, 200); + IdleColour = colour.Green.Opacity(0.7f); + HoverColour = IdleColour.Lighten(0.05f); + SetBackgroundColour(IdleColour, 200); break; case FriendStatus.Mutual: - IdleColour = colour.Pink; - HoverColour = colour.Pink1.Lighten(0.1f); - SetBackgroundColour(colour.Pink, 200); + IdleColour = colour.Pink.Opacity(0.7f); + HoverColour = IdleColour.Lighten(0.05f); + SetBackgroundColour(colour.Pink.Opacity(0.7f), 200); break; } } From e064965281dbc846fdbf1e25d121f17067b66fc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Nov 2024 12:18:03 +0100 Subject: [PATCH 731/924] Remove weird method --- .../Profile/Header/Components/FollowersButton.cs | 11 ++++++----- .../Profile/Header/Components/ProfileHeaderButton.cs | 6 ------ 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs index 8198058bd4..39e853d2c8 100644 --- a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs @@ -5,6 +5,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Localisation; @@ -14,6 +15,7 @@ using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Notifications; using osu.Game.Resources.Localisation.Web; +using SharpCompress; namespace osu.Game.Overlays.Profile.Header.Components { @@ -186,21 +188,20 @@ namespace osu.Game.Overlays.Profile.Header.Components case FriendStatus.None: IdleColour = colourProvider.Background6; HoverColour = colourProvider.Background5; - SetBackgroundColour(colourProvider.Background6, 200); break; case FriendStatus.NotMutual: IdleColour = colour.Green.Opacity(0.7f); - HoverColour = IdleColour.Lighten(0.05f); - SetBackgroundColour(IdleColour, 200); + HoverColour = IdleColour.Lighten(0.1f); break; case FriendStatus.Mutual: IdleColour = colour.Pink.Opacity(0.7f); - HoverColour = IdleColour.Lighten(0.05f); - SetBackgroundColour(colour.Pink.Opacity(0.7f), 200); + HoverColour = IdleColour.Lighten(0.1f); break; } + + EffectTargets.ForEach(d => d.FadeColour(IsHovered ? HoverColour : IdleColour, FADE_DURATION, Easing.OutQuint)); } private enum FriendStatus diff --git a/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs index 2c30d999e5..4fa72de5cc 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers; @@ -49,11 +48,6 @@ namespace osu.Game.Overlays.Profile.Header.Components }); } - protected void SetBackgroundColour(ColourInfo colorInfo, double duration = 0) - { - background.FadeColour(colorInfo, duration); - } - protected void ShowLoadingLayer() { loading.Show(); From 51f26993fa254c3169a981bc2544c3ed5e0a2778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Nov 2024 13:44:25 +0100 Subject: [PATCH 732/924] Extract "copy link" text to common localisation --- osu.Game/Graphics/UserInterface/ExternalLinkButton.cs | 3 ++- osu.Game/Localisation/CommonStrings.cs | 7 ++++++- osu.Game/Online/Chat/ExternalLinkOpener.cs | 6 +++--- .../Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 6 ++++-- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 3 ++- 5 files changed, 17 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index 806b7a10b8..b3ffd15816 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Localisation; +using osu.Game.Localisation; using osuTK; using osuTK.Graphics; @@ -77,7 +78,7 @@ namespace osu.Game.Graphics.UserInterface if (Link != null) { items.Add(new OsuMenuItem("Open", MenuItemType.Highlighted, () => game?.OpenUrlExternally(Link))); - items.Add(new OsuMenuItem("Copy link", MenuItemType.Standard, copyUrl)); + items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, copyUrl)); } return items.ToArray(); diff --git a/osu.Game/Localisation/CommonStrings.cs b/osu.Game/Localisation/CommonStrings.cs index 2c377a81d9..243a100029 100644 --- a/osu.Game/Localisation/CommonStrings.cs +++ b/osu.Game/Localisation/CommonStrings.cs @@ -174,6 +174,11 @@ namespace osu.Game.Localisation /// public static LocalisableString General => new TranslatableString(getKey(@"general"), @"General"); + /// + /// "Copy link" + /// + public static LocalisableString CopyLink => new TranslatableString(getKey(@"copy_link"), @"Copy link"); + private static string getKey(string key) => $@"{prefix}:{key}"; } -} +} \ No newline at end of file diff --git a/osu.Game/Online/Chat/ExternalLinkOpener.cs b/osu.Game/Online/Chat/ExternalLinkOpener.cs index 1c48a4fe6d..75b161d57b 100644 --- a/osu.Game/Online/Chat/ExternalLinkOpener.cs +++ b/osu.Game/Online/Chat/ExternalLinkOpener.cs @@ -11,7 +11,7 @@ using osu.Game.Configuration; using osu.Game.Localisation; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; -using CommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings; +using WebCommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings; namespace osu.Game.Online.Chat { @@ -60,12 +60,12 @@ namespace osu.Game.Online.Chat }, new PopupDialogCancelButton { - Text = @"Copy link", + Text = CommonStrings.CopyLink, Action = copyExternalLinkAction }, new PopupDialogCancelButton { - Text = CommonStrings.ButtonsCancel, + Text = WebCommonStrings.ButtonsCancel, }, }; } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 359e0f6c78..75c13c1be6 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -32,6 +32,8 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osuTK; using osuTK.Graphics; +using CommonStrings = osu.Game.Localisation.CommonStrings; +using WebCommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings; namespace osu.Game.Screens.Select.Carousel { @@ -296,10 +298,10 @@ namespace osu.Game.Screens.Select.Carousel items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); if (beatmapInfo.GetOnlineURL(api, ruleset.Value) is string url) - items.Add(new OsuMenuItem("Copy link", MenuItemType.Standard, () => game?.CopyUrlToClipboard(url))); + items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => game?.CopyUrlToClipboard(url))); if (hideRequested != null) - items.Add(new OsuMenuItem(CommonStrings.ButtonsHide.ToSentence(), MenuItemType.Destructive, () => hideRequested(beatmapInfo))); + items.Add(new OsuMenuItem(WebCommonStrings.ButtonsHide.ToSentence(), MenuItemType.Destructive, () => hideRequested(beatmapInfo))); return items.ToArray(); } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index eba40994e2..996d9ea0ab 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -20,6 +20,7 @@ using osu.Game.Beatmaps; using osu.Game.Collections; using osu.Game.Database; using osu.Game.Graphics.UserInterface; +using osu.Game.Localisation; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Rulesets; @@ -300,7 +301,7 @@ namespace osu.Game.Screens.Select.Carousel items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => restoreHiddenRequested(beatmapSet))); if (beatmapSet.GetOnlineURL(api, ruleset.Value) is string url) - items.Add(new OsuMenuItem("Copy link", MenuItemType.Standard, () => game?.CopyUrlToClipboard(url))); + items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => game?.CopyUrlToClipboard(url))); if (dialogOverlay != null) items.Add(new OsuMenuItem("Delete...", MenuItemType.Destructive, () => dialogOverlay.Push(new BeatmapDeleteDialog(beatmapSet)))); From 6a1893ff3f948afcade4459e7579a883c5ad6d06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Nov 2024 13:59:06 +0100 Subject: [PATCH 733/924] Add context menu option to copy link to an online score I feel like this may become useful soon enough to help diagnose weird issues. --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 16 ++++++++++++++-- .../SelectV2/Leaderboards/LeaderboardScoreV2.cs | 11 +++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 964f065813..5651f01645 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -17,6 +17,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Localisation; +using osu.Framework.Platform; using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -33,6 +34,8 @@ using osu.Game.Online.API; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets.Mods; using osu.Game.Utils; +using CommonStrings = osu.Game.Localisation.CommonStrings; +using WebCommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings; namespace osu.Game.Online.Leaderboards { @@ -71,6 +74,12 @@ namespace osu.Game.Online.Leaderboards [Resolved(canBeNull: true)] private SongSelect songSelect { get; set; } + [Resolved(canBeNull: true)] + private Clipboard clipboard { get; set; } + + [Resolved] + private IAPIProvider api { get; set; } + public ITooltip GetCustomTooltip() => new LeaderboardScoreTooltip(); public virtual ScoreInfo TooltipContent => Score; @@ -423,10 +432,13 @@ namespace osu.Game.Online.Leaderboards if (Score.Mods.Length > 0 && songSelect != null) items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => songSelect.Mods.Value = Score.Mods)); + if (Score.OnlineID > 0) + items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => clipboard?.SetText($@"{api.WebsiteRootUrl}/scores/{Score.OnlineID}"))); + if (Score.Files.Count > 0) { - items.Add(new OsuMenuItem(Localisation.CommonStrings.Export, MenuItemType.Standard, () => scoreManager.Export(Score))); - items.Add(new OsuMenuItem(CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(Score)))); + items.Add(new OsuMenuItem(CommonStrings.Export, MenuItemType.Standard, () => scoreManager.Export(Score))); + items.Add(new OsuMenuItem(WebCommonStrings.ButtonsDelete, MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(Score)))); } return items.ToArray(); diff --git a/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs b/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs index b6508e177a..732fb2cd8c 100644 --- a/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs +++ b/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs @@ -16,6 +16,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Localisation; +using osu.Framework.Platform; using osu.Game.Configuration; using osu.Game.Extensions; using osu.Game.Graphics; @@ -23,6 +24,7 @@ using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; using osu.Game.Overlays; @@ -82,6 +84,12 @@ namespace osu.Game.Screens.SelectV2.Leaderboards [Resolved] private ScoreManager scoreManager { get; set; } = null!; + [Resolved] + private Clipboard? clipboard { get; set; } + + [Resolved] + private IAPIProvider api { get; set; } = null!; + private Container content = null!; private Box background = null!; private Box foreground = null!; @@ -769,6 +777,9 @@ namespace osu.Game.Screens.SelectV2.Leaderboards if (score.Mods.Length > 0) items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => SelectedMods.Value = score.Mods.Where(m => IsValidMod.Invoke(m)).ToArray())); + if (score.OnlineID > 0) + items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => clipboard?.SetText($@"{api.WebsiteRootUrl}/scores/{score.OnlineID}"))); + if (score.Files.Count <= 0) return items.ToArray(); items.Add(new OsuMenuItem(CommonStrings.Export, MenuItemType.Standard, () => scoreManager.Export(score))); From 1dee04144843d7fad1391e577768662534eea1e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Nov 2024 14:40:30 +0100 Subject: [PATCH 734/924] Add search textbox in friends display Random user feature request that made sense to me because the same thing is in currently online display. Yes web doesn't have this, but web is in a browser where you can Ctrl-F. You can't do that in the client. Design taken out of posterior because I can't be bothered waiting for design cycles for this. --- .../Dashboard/Friends/FriendDisplay.cs | 58 +++++++++++++++---- osu.Game/Users/UserPanel.cs | 19 +++++- 2 files changed, 66 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs index e3accfd2ad..d0db3060f6 100644 --- a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs +++ b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs @@ -6,14 +6,17 @@ using System.Collections.Generic; using System.Linq; using System.Threading; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Resources.Localisation.Web; using osu.Game.Users; using osuTK; @@ -35,7 +38,8 @@ namespace osu.Game.Overlays.Dashboard.Friends private CancellationTokenSource cancellationToken; - private Drawable currentContent; + [CanBeNull] + private SearchContainer currentContent = null; private FriendOnlineStreamControl onlineStreamControl; private Box background; @@ -43,6 +47,7 @@ namespace osu.Game.Overlays.Dashboard.Friends private UserListToolbar userListToolbar; private Container itemsPlaceholder; private LoadingLayer loading; + private BasicSearchTextBox searchTextBox; private readonly IBindableList apiFriends = new BindableList(); @@ -104,7 +109,7 @@ namespace osu.Game.Overlays.Dashboard.Friends Margin = new MarginPadding { Bottom = 20 }, Children = new Drawable[] { - new Container + new GridContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -113,11 +118,38 @@ namespace osu.Game.Overlays.Dashboard.Friends Horizontal = 40, Vertical = 20 }, - Child = userListToolbar = new UserListToolbar + ColumnDimensions = new[] { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - } + new Dimension(), + new Dimension(GridSizeMode.Absolute, 50), + new Dimension(GridSizeMode.AutoSize), + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new[] + { + searchTextBox = new BasicSearchTextBox + { + RelativeSizeAxes = Axes.X, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Height = 40, + ReleaseFocusOnCommit = false, + HoldFocus = true, + PlaceholderText = HomeStrings.SearchPlaceholder, + }, + Empty(), + userListToolbar = new UserListToolbar + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + }, + }, + }, }, new Container { @@ -155,6 +187,11 @@ namespace osu.Game.Overlays.Dashboard.Friends onlineStreamControl.Current.BindValueChanged(_ => recreatePanels()); userListToolbar.DisplayStyle.BindValueChanged(_ => recreatePanels()); userListToolbar.SortCriteria.BindValueChanged(_ => recreatePanels()); + searchTextBox.Current.BindValueChanged(_ => + { + if (currentContent.IsNotNull()) + currentContent.SearchTerm = searchTextBox.Current.Value; + }); } private void recreatePanels() @@ -188,7 +225,7 @@ namespace osu.Game.Overlays.Dashboard.Friends } } - private void addContentToPlaceholder(Drawable content) + private void addContentToPlaceholder(SearchContainer content) { loading.Hide(); @@ -204,16 +241,17 @@ namespace osu.Game.Overlays.Dashboard.Friends currentContent.FadeIn(200, Easing.OutQuint); } - private FillFlowContainer createTable(List users) + private SearchContainer createTable(List users) { var style = userListToolbar.DisplayStyle.Value; - return new FillFlowContainer + return new SearchContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Spacing = new Vector2(style == OverlayPanelDisplayStyle.Card ? 10 : 2), - Children = users.Select(u => createUserPanel(u, style)).ToList() + Children = users.Select(u => createUserPanel(u, style)).ToList(), + SearchTerm = searchTextBox.Current.Value, }; } diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index b88619c8b7..0d3ea52611 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -13,6 +14,7 @@ using osu.Game.Overlays; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; using osu.Framework.Screens; using osu.Game.Graphics.Containers; using osu.Game.Online.API; @@ -28,7 +30,7 @@ using osuTK; namespace osu.Game.Users { - public abstract partial class UserPanel : OsuClickableContainer, IHasContextMenu + public abstract partial class UserPanel : OsuClickableContainer, IHasContextMenu, IFilterable { public readonly APIUser User; @@ -162,5 +164,20 @@ namespace osu.Game.Users return items.ToArray(); } } + + public IEnumerable FilterTerms => [User.Username]; + + public bool MatchingFilter + { + set + { + if (value) + Show(); + else + Hide(); + } + } + + public bool FilteringActive { get; set; } } } From 74d5de2d130943072c0f3c8814ee24b03a984cec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Nov 2024 14:49:11 +0100 Subject: [PATCH 735/924] Remove redundant initialiser --- osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs index d0db3060f6..186c5e87e7 100644 --- a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs +++ b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs @@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Dashboard.Friends private CancellationTokenSource cancellationToken; [CanBeNull] - private SearchContainer currentContent = null; + private SearchContainer currentContent; private FriendOnlineStreamControl onlineStreamControl; private Box background; From 9d65d394d3bf6c00502101ca818f470803452d90 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Nov 2024 12:51:26 +0900 Subject: [PATCH 736/924] Add ability to hide breaks from timeline This was another IRL request from a mapper / team member. The rationale here is that it can be very annoying to map with break time enabled if you have a large gap in the beatmap you are trying to fill with hitobjects, as you are placing objects on top of a very gray area. --- osu.Game/Configuration/OsuConfigManager.cs | 4 +++- osu.Game/Localisation/EditorStrings.cs | 5 +++++ .../Components/Timeline/TimelineBreakDisplay.cs | 13 +++++++++++++ osu.Game/Screens/Edit/Editor.cs | 6 ++++++ 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 1c6479e6f5..f642d23bb0 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -207,6 +207,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.UserOnlineStatus, null); SetDefault(OsuSetting.EditorTimelineShowTimingChanges, true); + SetDefault(OsuSetting.EditorTimelineShowBreaks, true); SetDefault(OsuSetting.EditorTimelineShowTicks, true); SetDefault(OsuSetting.EditorContractSidebars, false); @@ -439,6 +440,7 @@ namespace osu.Game.Configuration AlwaysShowHoldForMenuButton, EditorContractSidebars, EditorScaleOrigin, - EditorRotationOrigin + EditorRotationOrigin, + EditorTimelineShowBreaks, } } diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs index f6cce554e9..1ba03b3fde 100644 --- a/osu.Game/Localisation/EditorStrings.cs +++ b/osu.Game/Localisation/EditorStrings.cs @@ -139,6 +139,11 @@ namespace osu.Game.Localisation /// public static LocalisableString TimelineShowTimingChanges => new TranslatableString(getKey(@"timeline_show_timing_changes"), @"Show timing changes"); + /// + /// "Show breaks" + /// + public static LocalisableString TimelineShowBreaks => new TranslatableString(getKey(@"timeline_show_breaks"), @"Show breaks"); + /// /// "Show ticks" /// diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreakDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreakDisplay.cs index eca44672f6..381816c546 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreakDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreakDisplay.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Game.Beatmaps.Timing; +using osu.Game.Configuration; using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; namespace osu.Game.Screens.Edit.Compose.Components.Timeline @@ -27,6 +28,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly BindableList breaks = new BindableList(); + private readonly BindableBool showBreaks = new BindableBool(true); + + [BackgroundDependencyLoader] + private void load(OsuConfigManager configManager) + { + configManager.BindWith(OsuSetting.EditorTimelineShowBreaks, showBreaks); + showBreaks.BindValueChanged(_ => breakCache.Invalidate()); + } + protected override void LoadBeatmap(EditorBeatmap beatmap) { base.LoadBeatmap(beatmap); @@ -67,6 +77,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { Clear(); + if (!showBreaks.Value) + return; + for (int i = 0; i < breaks.Count; i++) { var breakPeriod = breaks[i]; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index be49343933..6262bb7aba 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -214,6 +214,7 @@ namespace osu.Game.Screens.Edit private Bindable editorAutoSeekOnPlacement; private Bindable editorLimitedDistanceSnap; private Bindable editorTimelineShowTimingChanges; + private Bindable editorTimelineShowBreaks; private Bindable editorTimelineShowTicks; private Bindable editorContractSidebars; @@ -323,6 +324,7 @@ namespace osu.Game.Screens.Edit editorAutoSeekOnPlacement = config.GetBindable(OsuSetting.EditorAutoSeekOnPlacement); editorLimitedDistanceSnap = config.GetBindable(OsuSetting.EditorLimitedDistanceSnap); editorTimelineShowTimingChanges = config.GetBindable(OsuSetting.EditorTimelineShowTimingChanges); + editorTimelineShowBreaks = config.GetBindable(OsuSetting.EditorTimelineShowBreaks); editorTimelineShowTicks = config.GetBindable(OsuSetting.EditorTimelineShowTicks); editorContractSidebars = config.GetBindable(OsuSetting.EditorContractSidebars); @@ -390,6 +392,10 @@ namespace osu.Game.Screens.Edit { State = { BindTarget = editorTimelineShowTicks } }, + new ToggleMenuItem(EditorStrings.TimelineShowBreaks) + { + State = { BindTarget = editorTimelineShowBreaks } + }, ] }, new BackgroundDimMenuItem(editorBackgroundDim), From 0087270b7e8502ed72edbfe110241f22f1b283b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Nov 2024 14:53:54 +0900 Subject: [PATCH 737/924] Update status and count immediately after friend request completes --- .../Overlays/Profile/Header/Components/FollowersButton.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs index 39e853d2c8..69cb5684fa 100644 --- a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs @@ -95,10 +95,12 @@ namespace osu.Game.Overlays.Profile.Header.Components req.Success += () => { - followerCount += status.Value == FriendStatus.None ? 1 : -1; + bool becameFriend = status.Value == FriendStatus.None; + + SetValue(followerCount += becameFriend ? 1 : -1); + status.Value = becameFriend ? FriendStatus.NotMutual : FriendStatus.None; api.UpdateLocalFriends(); - updateStatus(); HideLoadingLayer(); }; From 1fcdf6780607fd3fce3478d891dea0ebb51707b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Nov 2024 16:20:55 +0900 Subject: [PATCH 738/924] Handle response to get accurate mutual state immediately --- .../Profile/Header/Components/FollowersButton.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs index 69cb5684fa..c717a627ba 100644 --- a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs @@ -95,10 +95,16 @@ namespace osu.Game.Overlays.Profile.Header.Components req.Success += () => { - bool becameFriend = status.Value == FriendStatus.None; - - SetValue(followerCount += becameFriend ? 1 : -1); - status.Value = becameFriend ? FriendStatus.NotMutual : FriendStatus.None; + if (req is AddFriendRequest addedRequest) + { + SetValue(++followerCount); + status.Value = addedRequest.Response?.Mutual == true ? FriendStatus.Mutual : FriendStatus.NotMutual; + } + else + { + SetValue(--followerCount); + status.Value = FriendStatus.None; + } api.UpdateLocalFriends(); HideLoadingLayer(); From 32190fdb580821dde55310fcd759a1b696657603 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 5 Nov 2024 16:39:13 +0900 Subject: [PATCH 739/924] Hide cursor trail from draw visualiser --- osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index a4bccb0aff..5132dc2859 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.Rendering.Vertices; using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Shaders.Types; using osu.Framework.Graphics.Textures; +using osu.Framework.Graphics.Visualisation; using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Framework.Timing; @@ -23,6 +24,7 @@ using osuTK.Graphics.ES30; namespace osu.Game.Rulesets.Osu.UI.Cursor { + [DrawVisualiserHidden] public partial class CursorTrail : Drawable, IRequireHighFrequencyMousePosition { private const int max_sprites = 2048; From c576fd84482e5c0f5b146f639614cb8966867612 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Tue, 5 Nov 2024 15:47:25 +0800 Subject: [PATCH 740/924] add AddFriendResponse --- .../Visual/Online/TestSceneUserProfileHeader.cs | 10 ++++++++-- osu.Game/Online/API/Requests/AddFriendRequest.cs | 3 +-- osu.Game/Online/API/Requests/AddFriendResponse.cs | 14 ++++++++++++++ .../Profile/Header/Components/FollowersButton.cs | 2 +- 4 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Online/API/Requests/AddFriendResponse.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs index 9c368f6d73..6167d1f760 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs @@ -443,7 +443,10 @@ namespace osu.Game.Tests.Visual.Online { requestLock.Wait(3000); dummyAPI.Friends.Add(apiRelation); - req.TriggerSuccess(apiRelation); + req.TriggerSuccess(new AddFriendResponse + { + UserRelation = apiRelation + }); }); return true; @@ -483,7 +486,10 @@ namespace osu.Game.Tests.Visual.Online { requestLock.Wait(3000); dummyAPI.Friends.Add(apiRelation); - req.TriggerSuccess(apiRelation); + req.TriggerSuccess(new AddFriendResponse + { + UserRelation = apiRelation + }); }); return true; diff --git a/osu.Game/Online/API/Requests/AddFriendRequest.cs b/osu.Game/Online/API/Requests/AddFriendRequest.cs index 892ef0c7db..11045cedbe 100644 --- a/osu.Game/Online/API/Requests/AddFriendRequest.cs +++ b/osu.Game/Online/API/Requests/AddFriendRequest.cs @@ -3,11 +3,10 @@ using System.Net.Http; using osu.Framework.IO.Network; -using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests { - public class AddFriendRequest : APIRequest + public class AddFriendRequest : APIRequest { public readonly int TargetId; diff --git a/osu.Game/Online/API/Requests/AddFriendResponse.cs b/osu.Game/Online/API/Requests/AddFriendResponse.cs new file mode 100644 index 0000000000..af9d037e47 --- /dev/null +++ b/osu.Game/Online/API/Requests/AddFriendResponse.cs @@ -0,0 +1,14 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Online.API.Requests +{ + public class AddFriendResponse + { + [JsonProperty("user_relation")] + public APIRelation UserRelation = null!; + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs index c717a627ba..af78d62789 100644 --- a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs @@ -98,7 +98,7 @@ namespace osu.Game.Overlays.Profile.Header.Components if (req is AddFriendRequest addedRequest) { SetValue(++followerCount); - status.Value = addedRequest.Response?.Mutual == true ? FriendStatus.Mutual : FriendStatus.NotMutual; + status.Value = addedRequest.Response?.UserRelation.Mutual == true ? FriendStatus.Mutual : FriendStatus.NotMutual; } else { From 5f1b6963877cff5f2c496056701b5bccf69d793d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Nov 2024 15:21:29 +0100 Subject: [PATCH 741/924] Fix strong context menu operation not being written properly --- osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs index b706e96bdb..be2a5ac144 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs @@ -54,17 +54,17 @@ namespace osu.Game.Rulesets.Taiko.Edit public void SetStrongState(bool state) { - if (SelectedItems.OfType().All(h => h.IsStrong == state)) + if (SelectedItems.OfType().All(h => h.IsStrong == state)) return; EditorBeatmap.PerformOnSelection(h => { - if (!(h is Hit taikoHit)) return; + if (h is not TaikoStrongableHitObject strongable) return; - if (taikoHit.IsStrong != state) + if (strongable.IsStrong != state) { - taikoHit.IsStrong = state; - EditorBeatmap.Update(taikoHit); + strongable.IsStrong = state; + EditorBeatmap.Update(strongable); } }); } From 387fbc2214a37f70affd934c4e0f71761de78460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 5 Nov 2024 10:59:22 +0100 Subject: [PATCH 742/924] Fix drum rolls losing width on strong state toggle in editor Fixes https://github.com/ppy/osu/issues/30480. --- .../Objects/Drawables/DrawableDrumRoll.cs | 1 + .../Objects/Drawables/DrawableDrumRollTick.cs | 7 +++++++ .../Objects/Drawables/DrawableHit.cs | 2 ++ .../Objects/Drawables/DrawableSwell.cs | 11 ++++++++++- .../Objects/Drawables/DrawableTaikoHitObject.cs | 3 --- .../Drawables/DrawableTaikoStrongableHitObject.cs | 8 -------- 6 files changed, 20 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 1af4719b02..35c8a02af5 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -78,6 +78,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { base.RecreatePieces(); updateColour(); + Height = HitObject.IsStrong ? TaikoStrongableHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE; } protected override void OnFree() diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index 0333fd71a9..64d2020edc 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Skinning.Default; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { @@ -44,6 +45,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables IsFirstTick.Value = HitObject.FirstTick; } + protected override void RecreatePieces() + { + base.RecreatePieces(); + Size = new Vector2(HitObject.IsStrong ? TaikoStrongableHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE); + } + protected override void CheckForResult(bool userTriggered, double timeOffset) { if (!userTriggered) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index a5e63c373f..28831a6d2c 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -14,6 +14,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Skinning.Default; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { @@ -63,6 +64,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { updateActionsFromType(); base.RecreatePieces(); + Size = new Vector2(HitObject.IsStrong ? TaikoStrongableHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE); } protected override void OnFree() diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index f2fcd185dd..28617b35f6 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -19,6 +19,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Taiko.Skinning.Default; using osu.Game.Screens.Play; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { @@ -34,6 +35,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables /// private const double ring_appear_offset = 100; + private Vector2 baseSize; + private readonly Container ticks; private readonly Container bodyContainer; private readonly CircularContainer targetRing; @@ -141,6 +144,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables Origin = Anchor.Centre, }); + protected override void RecreatePieces() + { + base.RecreatePieces(); + Size = baseSize = new Vector2(TaikoHitObject.DEFAULT_SIZE); + } + protected override void OnFree() { base.OnFree(); @@ -269,7 +278,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { base.Update(); - Size = BaseSize * Parent!.RelativeChildSize; + Size = baseSize * Parent!.RelativeChildSize; // Make the swell stop at the hit target X = Math.Max(0, X); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 3f4694d71d..0cf9651965 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -130,7 +130,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public new TObject HitObject => (TObject)base.HitObject; - protected Vector2 BaseSize; protected SkinnableDrawable MainPiece; protected DrawableTaikoHitObject([CanBeNull] TObject hitObject) @@ -152,8 +151,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected virtual void RecreatePieces() { - Size = BaseSize = new Vector2(TaikoHitObject.DEFAULT_SIZE); - if (MainPiece != null) Content.Remove(MainPiece, true); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoStrongableHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoStrongableHitObject.cs index 4d7cdf3243..7c3ff4f27e 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoStrongableHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoStrongableHitObject.cs @@ -8,7 +8,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; -using osuTK; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { @@ -44,13 +43,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables isStrong.UnbindEvents(); } - protected override void RecreatePieces() - { - base.RecreatePieces(); - if (HitObject.IsStrong) - Size = BaseSize = new Vector2(TaikoStrongableHitObject.DEFAULT_STRONG_SIZE); - } - protected override void AddNestedHitObject(DrawableHitObject hitObject) { base.AddNestedHitObject(hitObject); From 48ce4fdd169caac456aa5bb290a7d02e3af7e284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 5 Nov 2024 11:51:07 +0100 Subject: [PATCH 743/924] Add failing test case --- osu.Game.Tests/Resources/mania-0-01-sv.osu | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 osu.Game.Tests/Resources/mania-0-01-sv.osu diff --git a/osu.Game.Tests/Resources/mania-0-01-sv.osu b/osu.Game.Tests/Resources/mania-0-01-sv.osu new file mode 100644 index 0000000000..295a8a423a --- /dev/null +++ b/osu.Game.Tests/Resources/mania-0-01-sv.osu @@ -0,0 +1,39 @@ +osu file format v14 + +[General] +SampleSet: Normal +StackLeniency: 0.7 +Mode: 3 + +[Difficulty] +HPDrainRate:3 +CircleSize:5 +OverallDifficulty:8 +ApproachRate:8 +SliderMultiplier:3.59999990463257 +SliderTickRate:2 + +[TimingPoints] +24,352.941176470588,4,1,1,100,1,0 +6376,-10000,4,1,1,100,0,0 + +[HitObjects] +51,192,24,1,0,0:0:0:0: +153,192,200,1,0,0:0:0:0: +358,192,376,1,0,0:0:0:0: +460,192,553,1,0,0:0:0:0: +460,192,729,128,0,1435:0:0:0:0: +358,192,906,128,0,1612:0:0:0:0: +256,192,1082,128,0,1788:0:0:0:0: +153,192,1259,128,0,1965:0:0:0:0: +51,192,1435,128,0,2141:0:0:0:0: +51,192,2318,1,12,0:0:0:0: +153,192,2318,1,4,0:0:0:0: +256,192,2318,1,6,0:0:0:0: +358,192,2318,1,14,0:0:0:0: +460,192,2318,1,0,0:0:0:0: +51,192,2494,128,0,2582:0:0:0:0: +153,192,2494,128,14,2582:0:0:0:0: +256,192,2494,128,6,2582:0:0:0:0: +358,192,2494,128,4,2582:0:0:0:0: +460,192,2494,128,12,2582:0:0:0:0: From 0e8dce5527563c20a29c069574d480f38e051c1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 5 Nov 2024 11:51:59 +0100 Subject: [PATCH 744/924] Fix `LegacyBeatmapEncoderTest` swapping expected/actual values around Was making test output look confusing. --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index b931896898..c8a09786ec 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -120,11 +120,11 @@ namespace osu.Game.Tests.Beatmaps.Formats private void compareBeatmaps((IBeatmap beatmap, TestLegacySkin skin) expected, (IBeatmap beatmap, TestLegacySkin skin) actual) { // Check all control points that are still considered to be at a global level. - Assert.That(expected.beatmap.ControlPointInfo.TimingPoints.Serialize(), Is.EqualTo(actual.beatmap.ControlPointInfo.TimingPoints.Serialize())); - Assert.That(expected.beatmap.ControlPointInfo.EffectPoints.Serialize(), Is.EqualTo(actual.beatmap.ControlPointInfo.EffectPoints.Serialize())); + Assert.That(actual.beatmap.ControlPointInfo.TimingPoints.Serialize(), Is.EqualTo(expected.beatmap.ControlPointInfo.TimingPoints.Serialize())); + Assert.That(actual.beatmap.ControlPointInfo.EffectPoints.Serialize(), Is.EqualTo(expected.beatmap.ControlPointInfo.EffectPoints.Serialize())); // Check all hitobjects. - Assert.That(expected.beatmap.HitObjects.Serialize(), Is.EqualTo(actual.beatmap.HitObjects.Serialize())); + Assert.That(actual.beatmap.HitObjects.Serialize(), Is.EqualTo(expected.beatmap.HitObjects.Serialize())); // Check skin. Assert.IsTrue(areComboColoursEqual(expected.skin.Configuration, actual.skin.Configuration)); From 23f38902931ca1e7afffaa1dde1b26008f755090 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 5 Nov 2024 11:56:19 +0100 Subject: [PATCH 745/924] Fix effect point scroll speeds below 0.1x not being encoded properly Closes https://github.com/ppy/osu/issues/30472. Caused by mismatching bounds between https://github.com/ppy/osu/blob/2bd12e14dbc7fd6fe29c6db53923c7da1d4b7557/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs#L22-L26 and https://github.com/ppy/osu/blob/2bd12e14dbc7fd6fe29c6db53923c7da1d4b7557/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs#L21-L28 --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index c14648caf6..956d004602 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -183,7 +183,17 @@ namespace osu.Game.Beatmaps.Formats if (scrollSpeedEncodedAsSliderVelocity) { foreach (var point in legacyControlPoints.EffectPoints) - legacyControlPoints.Add(point.Time, new DifficultyControlPoint { SliderVelocity = point.ScrollSpeed }); + { + legacyControlPoints.Add(point.Time, new DifficultyControlPoint + { + SliderVelocityBindable = + { + MinValue = point.ScrollSpeedBindable.MinValue, + MaxValue = point.ScrollSpeedBindable.MaxValue, + Value = point.ScrollSpeedBindable.Value, + } + }); + } } LegacyControlPointProperties lastControlPointProperties = new LegacyControlPointProperties(); From 5668c6244696d3e1a5209f969719dbfe7bf615ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 5 Nov 2024 13:00:00 +0100 Subject: [PATCH 746/924] Fix drum roll editor blueprint size & input handling --- .../Objects/Drawables/DrawableDrumRoll.cs | 3 +++ .../Skinning/Legacy/LegacyDrumRoll.cs | 17 ++++++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 1af4719b02..3f58476571 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -31,6 +31,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public override Quad ScreenSpaceDrawQuad => MainPiece.Drawable.ScreenSpaceDrawQuad; + // done strictly for editor purposes. + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => MainPiece.Drawable.ReceivePositionalInputAt(screenSpacePos); + /// /// Rolling number of tick hits. This increases for hits and decreases for misses. /// diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyDrumRoll.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyDrumRoll.cs index 5543a31ec9..78be0ef643 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyDrumRoll.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -9,6 +10,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Graphics; using osu.Game.Skinning; +using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Taiko.Skinning.Legacy @@ -19,13 +21,22 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { get { - var headDrawQuad = headCircle.ScreenSpaceDrawQuad; - var tailDrawQuad = tailCircle.ScreenSpaceDrawQuad; + // the reason why this calculation is so involved is that the head & tail sprites have different sizes/radii. + // therefore naively taking the SSDQs of them and making a quad out of them results in a trapezoid shape and not a box. + var headCentre = headCircle.ScreenSpaceDrawQuad.Centre; + var tailCentre = (tailCircle.ScreenSpaceDrawQuad.TopLeft + tailCircle.ScreenSpaceDrawQuad.BottomLeft) / 2; - return new Quad(headDrawQuad.TopLeft, tailDrawQuad.TopRight, headDrawQuad.BottomLeft, tailDrawQuad.BottomRight); + float headRadius = headCircle.ScreenSpaceDrawQuad.Height / 2; + float tailRadius = tailCircle.ScreenSpaceDrawQuad.Height / 2; + float radius = Math.Max(headRadius, tailRadius); + + var rectangle = new RectangleF(headCentre.X, headCentre.Y, tailCentre.X - headCentre.X, 0).Inflate(radius); + return new Quad(rectangle.TopLeft, rectangle.TopRight, rectangle.BottomLeft, rectangle.BottomRight); } } + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => ScreenSpaceDrawQuad.Contains(screenSpacePos); + private LegacyCirclePiece headCircle = null!; private Sprite body = null!; From dfda5e1b66b6274f565be99532fa1b1c7162f758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 5 Nov 2024 15:23:09 +0100 Subject: [PATCH 747/924] Improve visual appearance of mania selection blueprints --- .../Components/EditHoldNoteEndPiece.cs | 13 +++++++-- .../Blueprints/Components/EditNotePiece.cs | 29 ++++++++++++++++--- .../Blueprints/HoldNoteSelectionBlueprint.cs | 5 ++++ .../Edit/Blueprints/NoteSelectionBlueprint.cs | 11 ++++++- 4 files changed, 51 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditHoldNoteEndPiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditHoldNoteEndPiece.cs index 0aa72c28b8..285226e8b4 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditHoldNoteEndPiece.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditHoldNoteEndPiece.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Graphics; @@ -29,7 +30,11 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components CornerRadius = 5; Masking = true; - InternalChild = new DefaultNotePiece(); + InternalChild = new EditNotePiece + { + RelativeSizeAxes = Axes.Both, + Height = 1, + }; } protected override void LoadComplete() @@ -60,19 +65,23 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components { base.OnDrag(e); Dragging?.Invoke(e.ScreenSpaceMousePosition); + updateState(); } protected override void OnDragEnd(DragEndEvent e) { base.OnDragEnd(e); DragEnded?.Invoke(); + updateState(); } private void updateState() { + InternalChild.Colour = Colour4.White; + var colour = colours.Yellow; - if (IsHovered) + if (IsHovered || IsDragged) colour = colour.Lighten(1); Colour = colour; diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs index 48dde29a9f..63f67d6150 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs @@ -1,10 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Skinning.Default; +using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components { @@ -12,12 +16,17 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components { public EditNotePiece() { + Masking = true; + BorderThickness = 9; // organoleptically chosen to look good enough for all default skins + BorderColour = Color4.White; Height = DefaultNotePiece.NOTE_HEIGHT; - CornerRadius = 5; - Masking = true; - - InternalChild = new DefaultNotePiece(); + InternalChild = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + }; } [BackgroundDependencyLoader] @@ -25,5 +34,17 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components { Colour = colours.Yellow; } + + protected override void Update() + { + base.Update(); + + // from anecdotal experience, there are generally two types of user skins: + // one type uses rectangles for notes, and the other uses circles / squarish sprites (various stepmania-likes). + // this is a crude heuristic that attempts to choose the best of both worlds based on aspect ratio alone. + float aspectRatio = DrawWidth / DrawHeight; + bool isSquarish = aspectRatio > 4f / 5 && aspectRatio < 5f / 4; + CornerRadius = isSquarish ? Math.Min(DrawWidth, DrawHeight) / 2 : 5; + } } } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index b8e6aa26a0..2f2ad4bbba 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -10,6 +10,7 @@ using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Screens.Edit; using osuTK; @@ -32,6 +33,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints private EditHoldNoteEndPiece head = null!; private EditHoldNoteEndPiece tail = null!; + protected new DrawableHoldNote DrawableObject => (DrawableHoldNote)base.DrawableObject; + public HoldNoteSelectionBlueprint(HoldNote hold) : base(hold) { @@ -99,7 +102,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { base.Update(); + head.Height = DrawableObject.Head.DrawHeight; head.Y = HitObjectContainer.PositionAtTime(HitObject.Head.StartTime, HitObject.StartTime); + tail.Height = DrawableObject.Tail.DrawHeight; tail.Y = HitObjectContainer.PositionAtTime(HitObject.Tail.StartTime, HitObject.StartTime); Height = HitObjectContainer.LengthAtTime(HitObject.StartTime, HitObject.EndTime) + tail.DrawHeight; } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs index 01c7bd502a..c2df09e92a 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs @@ -9,10 +9,19 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { public partial class NoteSelectionBlueprint : ManiaSelectionBlueprint { + private readonly EditNotePiece notePiece; + public NoteSelectionBlueprint(Note note) : base(note) { - AddInternal(new EditNotePiece { RelativeSizeAxes = Axes.X }); + AddInternal(notePiece = new EditNotePiece { RelativeSizeAxes = Axes.X }); + } + + protected override void Update() + { + base.Update(); + + notePiece.Height = DrawableObject.DrawHeight; } } } From 3d4f5d4d105cc14cde1730818680625365686a96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 5 Nov 2024 15:29:52 +0100 Subject: [PATCH 748/924] Fix test --- .../Editor/TestSceneManiaSelectionHandler.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaSelectionHandler.cs index a70b90789d..0f7d696c7f 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaSelectionHandler.cs @@ -5,6 +5,8 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Edit.Blueprints; +using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.UI; using osu.Game.Screens.Edit.Compose.Components; @@ -106,7 +108,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor AddStep("select everything", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); AddStep("start drag", () => { - InputManager.MoveMouseTo(this.ChildrenOfType().First()); + InputManager.MoveMouseTo(this.ChildrenOfType().Single(blueprint => blueprint.IsSelected && blueprint.HitObject.StartTime == 0)); InputManager.PressButton(MouseButton.Left); }); AddStep("end drag", () => From ec046651b2fa7ed5e255e650a1200a19d49a127a Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Tue, 5 Nov 2024 22:08:43 +0200 Subject: [PATCH 749/924] Update OsuPerformanceCalculator.cs --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 43ae95c75e..bfdab6a4d9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -59,6 +59,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); countSliderEndsDropped = osuAttributes.SliderCount - score.Statistics.GetValueOrDefault(HitResult.SliderTailHit); countSliderTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss); + effectiveMissCount = 0; if (osuAttributes.SliderCount > 0) { From f3251bfcfdd6966c7ce9183d8da11b1df2db735f Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Tue, 5 Nov 2024 22:15:18 +0200 Subject: [PATCH 750/924] reset to miss instead of 0 --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index bfdab6a4d9..b8f5849aaf 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); countSliderEndsDropped = osuAttributes.SliderCount - score.Statistics.GetValueOrDefault(HitResult.SliderTailHit); countSliderTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss); - effectiveMissCount = 0; + effectiveMissCount = countMiss; if (osuAttributes.SliderCount > 0) { From 4b9f9b9be5731d9be0fcae866c987f4b3148a4ce Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 6 Nov 2024 14:02:01 +0900 Subject: [PATCH 751/924] Update GHA --- .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 fc6e231c4b..cb45447ed5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -88,7 +88,7 @@ jobs: # Attempt to upload results even if test fails. # https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#always - name: Upload Test Results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: ${{ always() }} with: name: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}} From 788ecc1e7b8c2cd0c967e92c6db3078a46109875 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 5 Nov 2024 19:44:29 +0900 Subject: [PATCH 752/924] Replace MultiplayerRoomComposite with local bindings --- .../Online/Multiplayer/MultiplayerClient.cs | 18 +-- .../Multiplayer/MultiplayerRoomExtensions.cs | 39 ++++++ .../Lounge/Components/RankRangePill.cs | 38 ++++-- .../Multiplayer/Match/MatchStartControl.cs | 109 ++++++++------- .../Match/MultiplayerSpectateButton.cs | 45 ++++--- .../Match/Playlist/MultiplayerPlaylist.cs | 67 +++++----- .../Multiplayer/MultiplayerRoomComposite.cs | 125 ------------------ .../Multiplayer/MultiplayerRoomSounds.cs | 80 ++++++----- .../Participants/ParticipantPanel.cs | 48 +++++-- .../Participants/ParticipantsList.cs | 46 ++++--- .../Multiplayer/Participants/TeamDisplay.cs | 48 ++++--- 11 files changed, 336 insertions(+), 327 deletions(-) create mode 100644 osu.Game/Online/Multiplayer/MultiplayerRoomExtensions.cs delete mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 5fd8b8b337..ff147aba10 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -202,7 +202,7 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(joinedRoom.Playlist.Count > 0); APIRoom.Playlist.Clear(); - APIRoom.Playlist.AddRange(joinedRoom.Playlist.Select(createPlaylistItem)); + APIRoom.Playlist.AddRange(joinedRoom.Playlist.Select(item => new PlaylistItem(item))); APIRoom.CurrentPlaylistItem.Value = APIRoom.Playlist.Single(item => item.ID == joinedRoom.Settings.PlaylistItemId); // The server will null out the end date upon the host joining the room, but the null value is never communicated to the client. @@ -734,7 +734,7 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(APIRoom != null); Room.Playlist.Add(item); - APIRoom.Playlist.Add(createPlaylistItem(item)); + APIRoom.Playlist.Add(new PlaylistItem(item)); ItemAdded?.Invoke(item); RoomUpdated?.Invoke(); @@ -780,7 +780,7 @@ namespace osu.Game.Online.Multiplayer int existingIndex = APIRoom.Playlist.IndexOf(APIRoom.Playlist.Single(existing => existing.ID == item.ID)); APIRoom.Playlist.RemoveAt(existingIndex); - APIRoom.Playlist.Insert(existingIndex, createPlaylistItem(item)); + APIRoom.Playlist.Insert(existingIndex, new PlaylistItem(item)); } catch (Exception ex) { @@ -853,18 +853,6 @@ namespace osu.Game.Online.Multiplayer RoomUpdated?.Invoke(); } - private PlaylistItem createPlaylistItem(MultiplayerPlaylistItem item) => new PlaylistItem(new APIBeatmap { OnlineID = item.BeatmapID, StarRating = item.StarRating }) - { - ID = item.ID, - OwnerID = item.OwnerID, - RulesetID = item.RulesetID, - Expired = item.Expired, - PlaylistOrder = item.PlaylistOrder, - PlayedAt = item.PlayedAt, - RequiredMods = item.RequiredMods.ToArray(), - AllowedMods = item.AllowedMods.ToArray() - }; - /// /// For the provided user ID, update whether the user is included in . /// diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomExtensions.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomExtensions.cs new file mode 100644 index 0000000000..0aeb85d2d8 --- /dev/null +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomExtensions.cs @@ -0,0 +1,39 @@ +// 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.Game.Online.Rooms; + +namespace osu.Game.Online.Multiplayer +{ + public static class MultiplayerRoomExtensions + { + /// + /// Returns all historical/expired items from the , in the order in which they were played. + /// + public static IEnumerable GetHistoricalItems(this MultiplayerRoom room) + => room.Playlist.Where(item => item.Expired).OrderBy(item => item.PlayedAt); + + /// + /// Returns all non-expired items from the , in the order in which they are to be played. + /// + public static IEnumerable GetUpcomingItems(this MultiplayerRoom room) + => room.Playlist.Where(item => !item.Expired).OrderBy(item => item.PlaylistOrder); + + /// + /// Returns the first non-expired in playlist order from the supplied , + /// or the last-played if all items are expired, + /// or if was empty. + /// + public static MultiplayerPlaylistItem? GetCurrentItem(this MultiplayerRoom room) + { + if (room.Playlist.Count == 0) + return null; + + return room.Playlist.All(item => item.Expired) + ? GetHistoricalItems(room).Last() + : GetUpcomingItems(room).First(); + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RankRangePill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RankRangePill.cs index adfc44fbd4..09aafa415a 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RankRangePill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RankRangePill.cs @@ -1,23 +1,25 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Screens.OnlinePlay.Multiplayer; +using osu.Game.Online.Multiplayer; using osuTK; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { - public partial class RankRangePill : MultiplayerRoomComposite + public partial class RankRangePill : CompositeDrawable { - private OsuTextFlowContainer rankFlow; + private OsuTextFlowContainer rankFlow = null!; + + [Resolved] + private MultiplayerClient client { get; set; } = null!; public RankRangePill() { @@ -55,20 +57,28 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components }; } - protected override void OnRoomUpdated() + protected override void LoadComplete() { - base.OnRoomUpdated(); + base.LoadComplete(); + client.RoomUpdated += onRoomUpdated; + updateState(); + } + + private void onRoomUpdated() => Scheduler.AddOnce(updateState); + + private void updateState() + { rankFlow.Clear(); - if (Room == null || Room.Users.All(u => u.User == null)) + if (client.Room == null || client.Room.Users.All(u => u.User == null)) { rankFlow.AddText("-"); return; } - int minRank = Room.Users.Select(u => u.User?.Statistics.GlobalRank ?? 0).DefaultIfEmpty(0).Min(); - int maxRank = Room.Users.Select(u => u.User?.Statistics.GlobalRank ?? 0).DefaultIfEmpty(0).Max(); + int minRank = client.Room.Users.Select(u => u.User?.Statistics.GlobalRank ?? 0).DefaultIfEmpty(0).Min(); + int maxRank = client.Room.Users.Select(u => u.User?.Statistics.GlobalRank ?? 0).DefaultIfEmpty(0).Max(); rankFlow.AddText("#"); rankFlow.AddText(minRank.ToString("#,0"), s => s.Font = s.Font.With(weight: FontWeight.Bold)); @@ -78,5 +88,13 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components rankFlow.AddText("#"); rankFlow.AddText(maxRank.ToString("#,0"), s => s.Font = s.Font.With(weight: FontWeight.Bold)); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (client.IsNotNull()) + client.RoomUpdated -= onRoomUpdated; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs index ba3508b24f..a82fa6e4bc 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs @@ -1,47 +1,50 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Diagnostics; 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.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Threading; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.Countdown; +using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { - public partial class MatchStartControl : MultiplayerRoomComposite + public partial class MatchStartControl : CompositeDrawable { [Resolved] - private OngoingOperationTracker ongoingOperationTracker { get; set; } - - [CanBeNull] - private IDisposable clickOperation; + private OngoingOperationTracker ongoingOperationTracker { get; set; } = null!; [Resolved(canBeNull: true)] - private IDialogOverlay dialogOverlay { get; set; } + private IDialogOverlay? dialogOverlay { get; set; } - private Sample sampleReady; - private Sample sampleReadyAll; - private Sample sampleUnready; + [Resolved] + private MultiplayerClient client { get; set; } = null!; + + [Resolved] + private IBindable currentItem { get; set; } = null!; private readonly MultiplayerReadyButton readyButton; private readonly MultiplayerCountdownButton countdownButton; + + private IBindable operationInProgress = null!; + private ScheduledDelegate? readySampleDelegate; + private IDisposable? clickOperation; + private Sample? sampleReady; + private Sample? sampleReadyAll; + private Sample? sampleUnready; private int countReady; - private ScheduledDelegate readySampleDelegate; - private IBindable operationInProgress; public MatchStartControl() { @@ -91,34 +94,29 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { base.LoadComplete(); - CurrentPlaylistItem.BindValueChanged(_ => updateState()); - } - - protected override void OnRoomUpdated() - { - base.OnRoomUpdated(); + currentItem.BindValueChanged(_ => updateState()); + client.RoomUpdated += onRoomUpdated; + client.LoadRequested += onLoadRequested; updateState(); } - protected override void OnRoomLoadRequested() - { - base.OnRoomLoadRequested(); - endOperation(); - } + private void onRoomUpdated() => Scheduler.AddOnce(updateState); + + private void onLoadRequested() => Scheduler.AddOnce(endOperation); private void onReadyButtonClick() { - if (Room == null) + if (client.Room == null) return; Debug.Assert(clickOperation == null); clickOperation = ongoingOperationTracker.BeginOperation(); - if (Client.IsHost) + if (client.IsHost) { - if (Room.State == MultiplayerRoomState.Open) + if (client.Room.State == MultiplayerRoomState.Open) { - if (isReady() && !Room.ActiveCountdowns.Any(c => c is MatchStartCountdown)) + if (isReady() && !client.Room.ActiveCountdowns.Any(c => c is MatchStartCountdown)) startMatch(); else toggleReady(); @@ -131,16 +129,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match dialogOverlay.Push(new ConfirmAbortDialog(abortMatch, endOperation)); } } - else if (Room.State != MultiplayerRoomState.Closed) + else if (client.Room.State != MultiplayerRoomState.Closed) toggleReady(); - bool isReady() => Client.LocalUser?.State == MultiplayerUserState.Ready || Client.LocalUser?.State == MultiplayerUserState.Spectating; + bool isReady() => client.LocalUser?.State == MultiplayerUserState.Ready || client.LocalUser?.State == MultiplayerUserState.Spectating; - void toggleReady() => Client.ToggleReady().FireAndForget( + void toggleReady() => client.ToggleReady().FireAndForget( onSuccess: endOperation, onError: _ => endOperation()); - void startMatch() => Client.StartMatch().FireAndForget(onSuccess: () => + void startMatch() => client.StartMatch().FireAndForget(onSuccess: () => { // gameplay is starting, the button will be unblocked on load requested. }, onError: _ => @@ -149,7 +147,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match endOperation(); }); - void abortMatch() => Client.AbortMatch().FireAndForget(endOperation, _ => endOperation()); + void abortMatch() => client.AbortMatch().FireAndForget(endOperation, _ => endOperation()); } private void startCountdown(TimeSpan duration) @@ -157,19 +155,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match Debug.Assert(clickOperation == null); clickOperation = ongoingOperationTracker.BeginOperation(); - Client.SendMatchRequest(new StartMatchCountdownRequest { Duration = duration }).ContinueWith(_ => endOperation()); + client.SendMatchRequest(new StartMatchCountdownRequest { Duration = duration }).ContinueWith(_ => endOperation()); } private void cancelCountdown() { - if (Client.Room == null) + if (client.Room == null) return; Debug.Assert(clickOperation == null); clickOperation = ongoingOperationTracker.BeginOperation(); - MultiplayerCountdown countdown = Client.Room.ActiveCountdowns.Single(c => c is MatchStartCountdown); - Client.SendMatchRequest(new StopCountdownRequest(countdown.ID)).ContinueWith(_ => endOperation()); + MultiplayerCountdown countdown = client.Room.ActiveCountdowns.Single(c => c is MatchStartCountdown); + client.SendMatchRequest(new StopCountdownRequest(countdown.ID)).ContinueWith(_ => endOperation()); } private void endOperation() @@ -180,19 +178,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void updateState() { - if (Room == null) + if (client.Room == null) { readyButton.Enabled.Value = false; countdownButton.Enabled.Value = false; return; } - var localUser = Client.LocalUser; + var localUser = client.LocalUser; - int newCountReady = Room.Users.Count(u => u.State == MultiplayerUserState.Ready); - int newCountTotal = Room.Users.Count(u => u.State != MultiplayerUserState.Spectating); + int newCountReady = client.Room.Users.Count(u => u.State == MultiplayerUserState.Ready); + int newCountTotal = client.Room.Users.Count(u => u.State != MultiplayerUserState.Spectating); - if (!Client.IsHost || Room.Settings.AutoStartEnabled) + if (!client.IsHost || client.Room.Settings.AutoStartEnabled) countdownButton.Hide(); else { @@ -211,21 +209,21 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match } readyButton.Enabled.Value = countdownButton.Enabled.Value = - Room.State != MultiplayerRoomState.Closed - && CurrentPlaylistItem.Value?.ID == Room.Settings.PlaylistItemId - && !Room.Playlist.Single(i => i.ID == Room.Settings.PlaylistItemId).Expired + client.Room.State != MultiplayerRoomState.Closed + && currentItem.Value?.ID == client.Room.Settings.PlaylistItemId + && !client.Room.Playlist.Single(i => i.ID == client.Room.Settings.PlaylistItemId).Expired && !operationInProgress.Value; // When the local user is the host and spectating the match, the ready button should be enabled only if any users are ready. if (localUser?.State == MultiplayerUserState.Spectating) - readyButton.Enabled.Value &= Client.IsHost && newCountReady > 0 && !Room.ActiveCountdowns.Any(c => c is MatchStartCountdown); + readyButton.Enabled.Value &= client.IsHost && newCountReady > 0 && !client.Room.ActiveCountdowns.Any(c => c is MatchStartCountdown); // When the local user is not the host, the button should only be enabled when no match is in progress. - if (!Client.IsHost) - readyButton.Enabled.Value &= Room.State == MultiplayerRoomState.Open; + if (!client.IsHost) + readyButton.Enabled.Value &= client.Room.State == MultiplayerRoomState.Open; // At all times, the countdown button should only be enabled when no match is in progress. - countdownButton.Enabled.Value &= Room.State == MultiplayerRoomState.Open; + countdownButton.Enabled.Value &= client.Room.State == MultiplayerRoomState.Open; if (newCountReady == countReady) return; @@ -249,6 +247,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match }); } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (client.IsNotNull()) + { + client.RoomUpdated -= onRoomUpdated; + client.LoadRequested -= onLoadRequested; + } + } + public partial class ConfirmAbortDialog : DangerousActionDialog { public ConfirmAbortDialog(Action abortMatch, Action cancel) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs index ea7ab2dce3..92edc9b979 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs @@ -5,7 +5,9 @@ using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; @@ -17,7 +19,7 @@ using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { - public partial class MultiplayerSpectateButton : MultiplayerRoomComposite + public partial class MultiplayerSpectateButton : CompositeDrawable { [Resolved] private OngoingOperationTracker ongoingOperationTracker { get; set; } = null!; @@ -25,6 +27,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match [Resolved] private OsuColour colours { get; set; } = null!; + [Resolved] + private MultiplayerClient client { get; set; } = null!; + + [Resolved] + private IBindable currentItem { get; set; } = null!; + private IBindable operationInProgress = null!; private readonly RoundedButton button; @@ -44,7 +52,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { var clickOperation = ongoingOperationTracker.BeginOperation(); - Client.ToggleSpectate().ContinueWith(_ => endOperation()); + client.ToggleSpectate().ContinueWith(_ => endOperation()); void endOperation() => clickOperation?.Dispose(); } @@ -63,19 +71,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { base.LoadComplete(); - CurrentPlaylistItem.BindValueChanged(_ => Scheduler.AddOnce(checkForAutomaticDownload), true); - } - - protected override void OnRoomUpdated() - { - base.OnRoomUpdated(); - + currentItem.BindValueChanged(_ => Scheduler.AddOnce(checkForAutomaticDownload), true); + client.RoomUpdated += onRoomUpdated; updateState(); } + private void onRoomUpdated() => Scheduler.AddOnce(updateState); + private void updateState() { - switch (Client.LocalUser?.State) + switch (client.LocalUser?.State) { default: button.Text = "Spectate"; @@ -88,8 +93,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match break; } - button.Enabled.Value = Client.Room != null - && Client.Room.State != MultiplayerRoomState.Closed + button.Enabled.Value = client.Room != null + && client.Room.State != MultiplayerRoomState.Closed && !operationInProgress.Value; Scheduler.AddOnce(checkForAutomaticDownload); @@ -112,11 +117,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void checkForAutomaticDownload() { - PlaylistItem? currentItem = CurrentPlaylistItem.Value; + PlaylistItem? item = currentItem.Value; downloadCheckCancellation?.Cancel(); - if (currentItem == null) + if (item == null) return; if (!automaticallyDownload.Value) @@ -128,13 +133,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match // // Rather than over-complicating this flow, let's only auto-download when spectating for the time being. // A potential path forward would be to have a local auto-download checkbox above the playlist item list area. - if (Client.LocalUser?.State != MultiplayerUserState.Spectating) + if (client.LocalUser?.State != MultiplayerUserState.Spectating) return; // In a perfect world we'd use BeatmapAvailability, but there's no event-driven flow for when a selection changes. // ie. if selection changes from "not downloaded" to another "not downloaded" we wouldn't get a value changed raised. beatmapLookupCache - .GetBeatmapAsync(currentItem.Beatmap.OnlineID, (downloadCheckCancellation = new CancellationTokenSource()).Token) + .GetBeatmapAsync(item.Beatmap.OnlineID, (downloadCheckCancellation = new CancellationTokenSource()).Token) .ContinueWith(resolved => Schedule(() => { var beatmapSet = resolved.GetResultSafely()?.BeatmapSet; @@ -150,5 +155,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match } #endregion + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (client.IsNotNull()) + client.RoomUpdated -= onRoomUpdated; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs index 2d08d8ecf6..8ba85019d5 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Allocation; @@ -17,18 +15,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist /// /// The multiplayer playlist, containing lists to show the items from a in both gameplay-order and historical-order. /// - public partial class MultiplayerPlaylist : MultiplayerRoomComposite + public partial class MultiplayerPlaylist : CompositeDrawable { public readonly Bindable DisplayMode = new Bindable(); /// /// Invoked when an item requests to be edited. /// - public Action RequestEdit; + public Action? RequestEdit; - private MultiplayerPlaylistTabControl playlistTabControl; - private MultiplayerQueueList queueList; - private MultiplayerHistoryList historyList; + [Resolved] + private MultiplayerClient client { get; set; } = null!; + + [Resolved] + private IBindable currentItem { get; set; } = null!; + + private MultiplayerPlaylistTabControl playlistTabControl = null!; + private MultiplayerQueueList queueList = null!; + private MultiplayerHistoryList historyList = null!; private bool firstPopulation = true; [BackgroundDependencyLoader] @@ -54,14 +58,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist queueList = new MultiplayerQueueList { RelativeSizeAxes = Axes.Both, - SelectedItem = { BindTarget = CurrentPlaylistItem }, + SelectedItem = { BindTarget = currentItem }, RequestEdit = item => RequestEdit?.Invoke(item) }, historyList = new MultiplayerHistoryList { RelativeSizeAxes = Axes.Both, Alpha = 0, - SelectedItem = { BindTarget = CurrentPlaylistItem } + SelectedItem = { BindTarget = currentItem } } } } @@ -73,7 +77,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist protected override void LoadComplete() { base.LoadComplete(); + DisplayMode.BindValueChanged(onDisplayModeChanged, true); + client.ItemAdded += playlistItemAdded; + client.ItemRemoved += playlistItemRemoved; + client.ItemChanged += playlistItemChanged; + client.RoomUpdated += onRoomUpdated; + updateState(); } private void onDisplayModeChanged(ValueChangedEvent mode) @@ -82,11 +92,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist queueList.FadeTo(mode.NewValue == MultiplayerPlaylistDisplayMode.Queue ? 1 : 0, 100); } - protected override void OnRoomUpdated() - { - base.OnRoomUpdated(); + private void onRoomUpdated() => Scheduler.AddOnce(updateState); - if (Room == null) + private void updateState() + { + if (client.Room == null) { historyList.Items.Clear(); queueList.Items.Clear(); @@ -96,34 +106,27 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist if (firstPopulation) { - foreach (var item in Room.Playlist) + foreach (var item in client.Room.Playlist) addItemToLists(item); firstPopulation = false; } } - protected override void PlaylistItemAdded(MultiplayerPlaylistItem item) - { - base.PlaylistItemAdded(item); - addItemToLists(item); - } + private void playlistItemAdded(MultiplayerPlaylistItem item) => Schedule(() => addItemToLists(item)); - protected override void PlaylistItemRemoved(long item) - { - base.PlaylistItemRemoved(item); - removeItemFromLists(item); - } + private void playlistItemRemoved(long item) => Schedule(() => removeItemFromLists(item)); - protected override void PlaylistItemChanged(MultiplayerPlaylistItem item) + private void playlistItemChanged(MultiplayerPlaylistItem item) => Schedule(() => { - base.PlaylistItemChanged(item); + if (client.Room == null) + return; - var newApiItem = Playlist.SingleOrDefault(i => i.ID == item.ID); + var newApiItem = new PlaylistItem(item); var existingApiItemInQueue = queueList.Items.SingleOrDefault(i => i.ID == item.ID); // Test if the only change between the two playlist items is the order. - if (newApiItem != null && existingApiItemInQueue != null && existingApiItemInQueue.With(playlistOrder: newApiItem.PlaylistOrder).Equals(newApiItem)) + if (existingApiItemInQueue != null && existingApiItemInQueue.With(playlistOrder: newApiItem.PlaylistOrder).Equals(newApiItem)) { // Set the new playlist order directly without refreshing the DrawablePlaylistItem. existingApiItemInQueue.PlaylistOrder = newApiItem.PlaylistOrder; @@ -137,20 +140,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist removeItemFromLists(item.ID); addItemToLists(item); } - } + }); private void addItemToLists(MultiplayerPlaylistItem item) { - var apiItem = Playlist.SingleOrDefault(i => i.ID == item.ID); + var apiItem = client.Room?.Playlist.SingleOrDefault(i => i.ID == item.ID); // Item could have been removed from the playlist while the local player was in gameplay. if (apiItem == null) return; if (item.Expired) - historyList.Items.Add(apiItem); + historyList.Items.Add(new PlaylistItem(apiItem)); else - queueList.Items.Add(apiItem); + queueList.Items.Add(new PlaylistItem(apiItem)); } private void removeItemFromLists(long item) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs deleted file mode 100644 index ee5c84bf40..0000000000 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomComposite.cs +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using JetBrains.Annotations; -using osu.Framework.Allocation; -using osu.Game.Online.Multiplayer; -using osu.Game.Online.Rooms; - -namespace osu.Game.Screens.OnlinePlay.Multiplayer -{ - public abstract partial class MultiplayerRoomComposite : OnlinePlayComposite - { - [CanBeNull] - protected MultiplayerRoom Room => Client.Room; - - [Resolved] - protected MultiplayerClient Client { get; private set; } - - protected override void LoadComplete() - { - base.LoadComplete(); - - Client.RoomUpdated += invokeOnRoomUpdated; - Client.LoadRequested += invokeOnRoomLoadRequested; - Client.UserLeft += invokeUserLeft; - Client.UserKicked += invokeUserKicked; - Client.UserJoined += invokeUserJoined; - Client.ItemAdded += invokeItemAdded; - Client.ItemRemoved += invokeItemRemoved; - Client.ItemChanged += invokeItemChanged; - - OnRoomUpdated(); - } - - private void invokeOnRoomUpdated() => Scheduler.AddOnce(OnRoomUpdated); - private void invokeUserJoined(MultiplayerRoomUser user) => Scheduler.Add(() => UserJoined(user)); - private void invokeUserKicked(MultiplayerRoomUser user) => Scheduler.Add(() => UserKicked(user)); - private void invokeUserLeft(MultiplayerRoomUser user) => Scheduler.Add(() => UserLeft(user)); - private void invokeItemAdded(MultiplayerPlaylistItem item) => Schedule(() => PlaylistItemAdded(item)); - private void invokeItemRemoved(long item) => Schedule(() => PlaylistItemRemoved(item)); - private void invokeItemChanged(MultiplayerPlaylistItem item) => Schedule(() => PlaylistItemChanged(item)); - private void invokeOnRoomLoadRequested() => Scheduler.AddOnce(OnRoomLoadRequested); - - /// - /// Invoked when a user has joined the room. - /// - /// The user. - protected virtual void UserJoined(MultiplayerRoomUser user) - { - } - - /// - /// Invoked when a user has been kicked from the room (including the local user). - /// - /// The user. - protected virtual void UserKicked(MultiplayerRoomUser user) - { - } - - /// - /// Invoked when a user has left the room. - /// - /// The user. - protected virtual void UserLeft(MultiplayerRoomUser user) - { - } - - /// - /// Invoked when a playlist item is added to the room. - /// - /// The added playlist item. - protected virtual void PlaylistItemAdded(MultiplayerPlaylistItem item) - { - } - - /// - /// Invoked when a playlist item is removed from the room. - /// - /// The ID of the removed playlist item. - protected virtual void PlaylistItemRemoved(long item) - { - } - - /// - /// Invoked when a playlist item is changed in the room. - /// - /// The new playlist item, with an existing item's ID. - protected virtual void PlaylistItemChanged(MultiplayerPlaylistItem item) - { - } - - /// - /// Invoked when any change occurs to the multiplayer room. - /// - protected virtual void OnRoomUpdated() - { - } - - /// - /// Invoked when the room requests the local user to load into gameplay. - /// - protected virtual void OnRoomLoadRequested() - { - } - - protected override void Dispose(bool isDisposing) - { - if (Client != null) - { - Client.RoomUpdated -= invokeOnRoomUpdated; - Client.LoadRequested -= invokeOnRoomLoadRequested; - Client.UserLeft -= invokeUserLeft; - Client.UserKicked -= invokeUserKicked; - Client.UserJoined -= invokeUserJoined; - Client.ItemAdded -= invokeItemAdded; - Client.ItemRemoved -= invokeItemRemoved; - Client.ItemChanged -= invokeItemChanged; - } - - base.Dispose(isDisposing); - } - } -} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomSounds.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomSounds.cs index 90595bc33b..d53e485c86 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomSounds.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomSounds.cs @@ -1,23 +1,26 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; -using osu.Framework.Bindables; -using osu.Game.Online.API.Requests.Responses; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics.Containers; using osu.Game.Online.Multiplayer; namespace osu.Game.Screens.OnlinePlay.Multiplayer { - public partial class MultiplayerRoomSounds : MultiplayerRoomComposite + public partial class MultiplayerRoomSounds : CompositeDrawable { - private Sample hostChangedSample; - private Sample userJoinedSample; - private Sample userLeftSample; - private Sample userKickedSample; + [Resolved] + private MultiplayerClient client { get; set; } = null!; + + private Sample? hostChangedSample; + private Sample? userJoinedSample; + private Sample? userLeftSample; + private Sample? userKickedSample; + private MultiplayerRoomUser? host; [BackgroundDependencyLoader] private void load(AudioManager audio) @@ -32,36 +35,47 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { base.LoadComplete(); - Host.BindValueChanged(hostChanged); + client.RoomUpdated += onRoomUpdated; + client.UserJoined += onUserJoined; + client.UserLeft += onUserLeft; + client.UserKicked += onUserKicked; + updateState(); } - protected override void UserJoined(MultiplayerRoomUser user) + private void onRoomUpdated() => Scheduler.AddOnce(updateState); + + private void updateState() { - base.UserJoined(user); + if (EqualityComparer.Default.Equals(host, client.Room?.Host)) + return; - Scheduler.AddOnce(() => userJoinedSample?.Play()); - } - - protected override void UserLeft(MultiplayerRoomUser user) - { - base.UserLeft(user); - - Scheduler.AddOnce(() => userLeftSample?.Play()); - } - - protected override void UserKicked(MultiplayerRoomUser user) - { - base.UserKicked(user); - - Scheduler.AddOnce(() => userKickedSample?.Play()); - } - - private void hostChanged(ValueChangedEvent value) - { // only play sound when the host changes from an already-existing host. - if (value.OldValue == null) return; + if (host != null) + Scheduler.AddOnce(() => hostChangedSample?.Play()); - Scheduler.AddOnce(() => hostChangedSample?.Play()); + host = client.Room?.Host; + } + + private void onUserJoined(MultiplayerRoomUser user) + => Scheduler.AddOnce(() => userJoinedSample?.Play()); + + private void onUserLeft(MultiplayerRoomUser user) + => Scheduler.AddOnce(() => userLeftSample?.Play()); + + private void onUserKicked(MultiplayerRoomUser user) + => Scheduler.AddOnce(() => userKickedSample?.Play()); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (client.IsNotNull()) + { + client.RoomUpdated -= onRoomUpdated; + client.UserJoined -= onUserJoined; + client.UserLeft -= onUserLeft; + client.UserKicked -= onUserKicked; + } } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index c79c210e30..7e42b18240 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -30,7 +31,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants { - public partial class ParticipantPanel : MultiplayerRoomComposite, IHasContextMenu + public partial class ParticipantPanel : CompositeDrawable, IHasContextMenu { public readonly MultiplayerRoomUser User; @@ -40,6 +41,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants [Resolved] private IRulesetStore rulesets { get; set; } = null!; + [Resolved] + private MultiplayerClient client { get; set; } = null!; + private SpriteIcon crown = null!; private OsuSpriteText userRankText = null!; @@ -171,23 +175,31 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants Origin = Anchor.Centre, Alpha = 0, Margin = new MarginPadding(4), - Action = () => Client.KickUser(User.UserID).FireAndForget(), + Action = () => client.KickUser(User.UserID).FireAndForget(), }, }, } }; } - protected override void OnRoomUpdated() + protected override void LoadComplete() { - base.OnRoomUpdated(); + base.LoadComplete(); - if (Room == null || Client.LocalUser == null) + client.RoomUpdated += onRoomUpdated; + updateState(); + } + + private void onRoomUpdated() => Scheduler.AddOnce(updateState); + + private void updateState() + { + if (client.Room == null || client.LocalUser == null) return; const double fade_time = 50; - var currentItem = Playlist.GetCurrentItem(); + MultiplayerPlaylistItem? currentItem = client.Room.GetCurrentItem(); Ruleset? ruleset = currentItem != null ? rulesets.GetRuleset(currentItem.RulesetID)?.CreateInstance() : null; int? currentModeRank = ruleset != null ? User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank : null; @@ -200,8 +212,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants else userModsDisplay.FadeOut(fade_time); - kickButton.Alpha = Client.IsHost && !User.Equals(Client.LocalUser) ? 1 : 0; - crown.Alpha = Room.Host?.Equals(User) == true ? 1 : 0; + kickButton.Alpha = client.IsHost && !User.Equals(client.LocalUser) ? 1 : 0; + crown.Alpha = client.Room.Host?.Equals(User) == true ? 1 : 0; // If the mods are updated at the end of the frame, the flow container will skip a reflow cycle: https://github.com/ppy/osu-framework/issues/4187 // This looks particularly jarring here, so re-schedule the update to that start of our frame as a fix. @@ -215,7 +227,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants { get { - if (Room == null) + if (client.Room == null) return null; // If the local user is targetted. @@ -223,7 +235,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants return null; // If the local user is not the host of the room. - if (Room.Host?.UserID != api.LocalUser.Value.Id) + if (client.Room.Host?.UserID != api.LocalUser.Value.Id) return null; int targetUser = User.UserID; @@ -233,23 +245,31 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants new OsuMenuItem("Give host", MenuItemType.Standard, () => { // Ensure the local user is still host. - if (!Client.IsHost) + if (!client.IsHost) return; - Client.TransferHost(targetUser).FireAndForget(); + client.TransferHost(targetUser).FireAndForget(); }), new OsuMenuItem("Kick", MenuItemType.Destructive, () => { // Ensure the local user is still host. - if (!Client.IsHost) + if (!client.IsHost) return; - Client.KickUser(targetUser).FireAndForget(); + client.KickUser(targetUser).FireAndForget(); }) }; } } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (client.IsNotNull()) + client.RoomUpdated -= onRoomUpdated; + } + public partial class KickButton : IconButton { public KickButton() diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantsList.cs index 6a7a3758c3..a9d7f4ab52 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantsList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantsList.cs @@ -1,24 +1,24 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; +using osu.Game.Online.Multiplayer; using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants { - public partial class ParticipantsList : MultiplayerRoomComposite + public partial class ParticipantsList : CompositeDrawable { - private FillFlowContainer panels; + private FillFlowContainer panels = null!; + private ParticipantPanel? currentHostPanel; - [CanBeNull] - private ParticipantPanel currentHostPanel; + [Resolved] + private MultiplayerClient client { get; set; } = null!; [BackgroundDependencyLoader] private void load() @@ -37,11 +37,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants }; } - protected override void OnRoomUpdated() + protected override void LoadComplete() { - base.OnRoomUpdated(); + base.LoadComplete(); - if (Room == null) + client.RoomUpdated += onRoomUpdated; + updateState(); + } + + private void onRoomUpdated() => Scheduler.AddOnce(updateState); + + private void updateState() + { + if (client.Room == null) panels.Clear(); else { @@ -49,15 +57,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants foreach (var p in panels) { // Note that we *must* use reference equality here, as this call is scheduled and a user may have left and joined since it was last run. - if (Room.Users.All(u => !ReferenceEquals(p.User, u))) + if (client.Room.Users.All(u => !ReferenceEquals(p.User, u))) p.Expire(); } // Add panels for all users new to the room. - foreach (var user in Room.Users.Except(panels.Select(p => p.User))) + foreach (var user in client.Room.Users.Except(panels.Select(p => p.User))) panels.Add(new ParticipantPanel(user)); - if (currentHostPanel == null || !currentHostPanel.User.Equals(Room.Host)) + if (currentHostPanel == null || !currentHostPanel.User.Equals(client.Room.Host)) { // Reset position of previous host back to normal, if one existing. if (currentHostPanel != null && panels.Contains(currentHostPanel)) @@ -66,9 +74,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants currentHostPanel = null; // Change position of new host to display above all participants. - if (Room.Host != null) + if (client.Room.Host != null) { - currentHostPanel = panels.SingleOrDefault(u => u.User.Equals(Room.Host)); + currentHostPanel = panels.SingleOrDefault(u => u.User.Equals(client.Room.Host)); if (currentHostPanel != null) panels.SetLayoutPosition(currentHostPanel, -1); @@ -76,5 +84,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants } } } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (client.IsNotNull()) + client.RoomUpdated -= onRoomUpdated; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs index fe57ad26a5..bd9511d50d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs @@ -1,12 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -20,27 +19,26 @@ using osuTK.Graphics; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants { - internal partial class TeamDisplay : MultiplayerRoomComposite + internal partial class TeamDisplay : CompositeDrawable { private readonly MultiplayerRoomUser user; - private Drawable box; - - private Sample sampleTeamSwap; + [Resolved] + private OsuColour colours { get; set; } = null!; [Resolved] - private OsuColour colours { get; set; } + private MultiplayerClient client { get; set; } = null!; - private OsuClickableContainer clickableContent; + private OsuClickableContainer clickableContent = null!; + private Drawable box = null!; + private Sample? sampleTeamSwap; public TeamDisplay(MultiplayerRoomUser user) { this.user = user; RelativeSizeAxes = Axes.Y; - AutoSizeAxes = Axes.X; - Margin = new MarginPadding { Horizontal = 3 }; } @@ -71,7 +69,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants } }; - if (Client.LocalUser?.Equals(user) == true) + if (client.LocalUser?.Equals(user) == true) { clickableContent.Action = changeTeam; clickableContent.TooltipText = "Change team"; @@ -80,23 +78,31 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants sampleTeamSwap = audio.Samples.Get(@"Multiplayer/team-swap"); } + protected override void LoadComplete() + { + base.LoadComplete(); + + client.RoomUpdated += onRoomUpdated; + updateState(); + } + private void changeTeam() { - Client.SendMatchRequest(new ChangeTeamRequest + client.SendMatchRequest(new ChangeTeamRequest { - TeamID = ((Client.LocalUser?.MatchState as TeamVersusUserState)?.TeamID + 1) % 2 ?? 0, + TeamID = ((client.LocalUser?.MatchState as TeamVersusUserState)?.TeamID + 1) % 2 ?? 0, }).FireAndForget(); } public int? DisplayedTeam { get; private set; } - protected override void OnRoomUpdated() - { - base.OnRoomUpdated(); + private void onRoomUpdated() => Scheduler.AddOnce(updateState); + private void updateState() + { // we don't have a way of knowing when an individual user's state has updated, so just handle on RoomUpdated for now. - var userRoomState = Room?.Users.FirstOrDefault(u => u.Equals(user))?.MatchState; + var userRoomState = client.Room?.Users.FirstOrDefault(u => u.Equals(user))?.MatchState; const double duration = 400; @@ -138,5 +144,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants return colours.Blue; } } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (client.IsNotNull()) + client.RoomUpdated -= onRoomUpdated; + } } } From 9f08b377920c108b5cfd5d287d0dafaddcd534df Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 6 Nov 2024 16:35:11 +0900 Subject: [PATCH 753/924] Fix up tests --- .../Multiplayer/TestSceneMatchStartControl.cs | 23 ++++++++++--------- .../TestSceneMultiplayerMatchFooter.cs | 6 +++++ .../TestSceneMultiplayerPlaylist.cs | 16 +++++++------ .../TestSceneMultiplayerSpectateButton.cs | 21 ++++++++--------- 4 files changed, 36 insertions(+), 30 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs index 2d61c26a6b..3b10509895 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using Moq; @@ -36,15 +34,18 @@ namespace osu.Game.Tests.Visual.Multiplayer private readonly Bindable beatmapAvailability = new Bindable(); private readonly Bindable room = new Bindable(); - private MultiplayerRoom multiplayerRoom; - private MultiplayerRoomUser localUser; - private OngoingOperationTracker ongoingOperationTracker; + private MultiplayerRoom multiplayerRoom = null!; + private MultiplayerRoomUser localUser = null!; + private OngoingOperationTracker ongoingOperationTracker = null!; - private PopoverContainer content; - private MatchStartControl control; + private PopoverContainer content = null!; + private MatchStartControl control = null!; private OsuButton readyButton => control.ChildrenOfType().Single(); + [Cached(typeof(IBindable))] + private readonly Bindable currentItem = new Bindable(); + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => new CachedModelDependencyContainer(base.CreateChildDependencies(parent)) { Model = { BindTarget = room } }; @@ -112,15 +113,15 @@ namespace osu.Game.Tests.Visual.Multiplayer beatmapAvailability.Value = BeatmapAvailability.LocallyAvailable(); - var playlistItem = new PlaylistItem(Beatmap.Value.BeatmapInfo) + currentItem.Value = new PlaylistItem(Beatmap.Value.BeatmapInfo) { RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID }; room.Value = new Room { - Playlist = { playlistItem }, - CurrentPlaylistItem = { Value = playlistItem } + Playlist = { currentItem.Value }, + CurrentPlaylistItem = { BindTarget = currentItem } }; localUser = new MultiplayerRoomUser(API.LocalUser.Value.Id) { User = API.LocalUser.Value }; @@ -129,7 +130,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Playlist = { - TestMultiplayerClient.CreateMultiplayerPlaylistItem(playlistItem), + TestMultiplayerClient.CreateMultiplayerPlaylistItem(currentItem.Value), }, Users = { localUser }, Host = localUser, diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs index c2d3b17ccb..9d8ef76e75 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs @@ -1,15 +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 osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneMultiplayerMatchFooter : MultiplayerTestScene { + [Cached(typeof(IBindable))] + private readonly Bindable currentItem = new Bindable(); + public override void SetUpSteps() { base.SetUpSteps(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index 2100f82886..3baabecd84 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -1,13 +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 disable - using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Platform; @@ -29,10 +28,13 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneMultiplayerPlaylist : MultiplayerTestScene { - private MultiplayerPlaylist list; - private BeatmapManager beatmaps; - private BeatmapSetInfo importedSet; - private BeatmapInfo importedBeatmap; + [Cached(typeof(IBindable))] + private readonly Bindable currentItem = new Bindable(); + + private MultiplayerPlaylist list = null!; + private BeatmapManager beatmaps = null!; + private BeatmapSetInfo importedSet = null!; + private BeatmapInfo importedBeatmap = null!; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) @@ -198,7 +200,7 @@ namespace osu.Game.Tests.Visual.Multiplayer addItemStep(); addItemStep(); - DrawableRoomPlaylistItem[] drawableItems = null; + DrawableRoomPlaylistItem[] drawableItems = null!; AddStep("get drawable items", () => drawableItems = this.ChildrenOfType().ToArray()); // Add 1 item for another user. diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 816ba4ca32..5ae5d1e228 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -28,13 +26,14 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneMultiplayerSpectateButton : MultiplayerTestScene { - private MultiplayerSpectateButton spectateButton; - private MatchStartControl startControl; + [Cached(typeof(IBindable))] + private readonly Bindable currentItem = new Bindable(); - private readonly Bindable selectedItem = new Bindable(); + private MultiplayerSpectateButton spectateButton = null!; + private MatchStartControl startControl = null!; - private BeatmapSetInfo importedSet; - private BeatmapManager beatmaps; + private BeatmapSetInfo importedSet = null!; + private BeatmapManager beatmaps = null!; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) @@ -52,14 +51,12 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create button", () => { - AvailabilityTracker.SelectedItem.BindTo(selectedItem); + AvailabilityTracker.SelectedItem.BindTo(currentItem); importedSet = beatmaps.GetAllUsableBeatmapSets().First(); Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()); - selectedItem.Value = new PlaylistItem(Beatmap.Value.BeatmapInfo) - { - RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID, - }; + + currentItem.Value = SelectedRoom.Value.Playlist.First(); Child = new PopoverContainer { From da95a1a2f10d7ce34f60b5651415d3e28e795aa9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2024 18:54:52 +0900 Subject: [PATCH 754/924] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 73668536e0..7405a7c587 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From 35d004cdc2706420738cf3b96f63eb4147dbab5a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 6 Nov 2024 20:39:10 +0900 Subject: [PATCH 755/924] Fix intermittent beatmap recommendations test --- .../Visual/SongSelect/TestSceneBeatmapRecommendations.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index 16c8bc1a6b..82791056b6 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -191,7 +191,7 @@ namespace osu.Game.Tests.Visual.SongSelect { AddStep("present beatmap", () => Game.PresentBeatmap(getImport())); - AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect); + AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded); AddUntilStep("recommended beatmap displayed", () => Game.Beatmap.Value.BeatmapInfo.MatchesOnlineID(getImport().Beatmaps[expectedDiff - 1])); } } From fd179666057d23d28db13eacb9612cfba84431c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 7 Nov 2024 08:39:58 +0100 Subject: [PATCH 756/924] Remove unused using directive --- .../Editor/TestSceneManiaSelectionHandler.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaSelectionHandler.cs index 0f7d696c7f..a920b3470f 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaSelectionHandler.cs @@ -6,7 +6,6 @@ using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Edit.Blueprints; -using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.UI; using osu.Game.Screens.Edit.Compose.Components; From aede94a3f3edcac2c0495f05c1193c4b08578b0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 7 Nov 2024 08:41:29 +0100 Subject: [PATCH 757/924] Use box variant of selection always --- .../Edit/Blueprints/Components/EditNotePiece.cs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs index 63f67d6150..a2a5955a9d 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -17,6 +16,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components public EditNotePiece() { Masking = true; + CornerRadius = 5; BorderThickness = 9; // organoleptically chosen to look good enough for all default skins BorderColour = Color4.White; Height = DefaultNotePiece.NOTE_HEIGHT; @@ -34,17 +34,5 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components { Colour = colours.Yellow; } - - protected override void Update() - { - base.Update(); - - // from anecdotal experience, there are generally two types of user skins: - // one type uses rectangles for notes, and the other uses circles / squarish sprites (various stepmania-likes). - // this is a crude heuristic that attempts to choose the best of both worlds based on aspect ratio alone. - float aspectRatio = DrawWidth / DrawHeight; - bool isSquarish = aspectRatio > 4f / 5 && aspectRatio < 5f / 4; - CornerRadius = isSquarish ? Math.Min(DrawWidth, DrawHeight) / 2 : 5; - } } } From 98ae45d763cbf8a2ad34d7be780eba906fff34b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 7 Nov 2024 09:18:57 +0100 Subject: [PATCH 758/924] Adjust appearance further in response to feedback --- .../Blueprints/Components/EditNotePiece.cs | 20 +++++++++++++++--- .../Blueprints/HoldNoteSelectionBlueprint.cs | 21 ++++++++++++++++++- .../Blueprints/ManiaSelectionBlueprint.cs | 10 ++------- .../Edit/Blueprints/NoteSelectionBlueprint.cs | 17 ++++++++++++++- 4 files changed, 55 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs index a2a5955a9d..c626e8011e 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs @@ -3,22 +3,28 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Skinning.Default; -using osuTK.Graphics; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK; namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components { public partial class EditNotePiece : CompositeDrawable { + [Resolved] + private Column? column { get; set; } + public EditNotePiece() { Masking = true; CornerRadius = 5; - BorderThickness = 9; // organoleptically chosen to look good enough for all default skins - BorderColour = Color4.White; + BorderThickness = 6; + BorderColour = ColourInfo.GradientVertical(Colour4.White.Opacity(0.5f), Colour4.White); Height = DefaultNotePiece.NOTE_HEIGHT; InternalChild = new Box @@ -34,5 +40,13 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components { Colour = colours.Yellow; } + + protected override void Update() + { + base.Update(); + + if (column != null) + Scale = new Vector2(1, column.ScrollingInfo.Direction.Value == ScrollingDirection.Down ? 1 : -1); + } } } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index 2f2ad4bbba..66b914d320 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; @@ -11,6 +12,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit; using osuTK; @@ -32,6 +34,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints private EditHoldNoteEndPiece head = null!; private EditHoldNoteEndPiece tail = null!; + private Container body = null!; protected new DrawableHoldNote DrawableObject => (DrawableHoldNote)base.DrawableObject; @@ -48,6 +51,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints head = new EditHoldNoteEndPiece { RelativeSizeAxes = Axes.X, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, DragStarted = () => changeHandler?.BeginChange(), Dragging = pos => { @@ -67,6 +72,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints tail = new EditHoldNoteEndPiece { RelativeSizeAxes = Axes.X, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, DragStarted = () => changeHandler?.BeginChange(), Dragging = pos => { @@ -82,10 +89,12 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints }, DragEnded = () => changeHandler?.EndChange(), }, - new Container + body = new Container { RelativeSizeAxes = Axes.Both, Masking = true, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, BorderThickness = 1, BorderColour = colours.Yellow, Child = new Box @@ -109,6 +118,16 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints Height = HitObjectContainer.LengthAtTime(HitObject.StartTime, HitObject.EndTime) + tail.DrawHeight; } + protected override void OnDirectionChanged(ValueChangedEvent direction) + { + Origin = direction.NewValue == ScrollingDirection.Down ? Anchor.BottomCentre : Anchor.TopCentre; + + foreach (var child in InternalChildren) + child.Anchor = Origin; + + head.Scale = tail.Scale = new Vector2(1, direction.NewValue == ScrollingDirection.Down ? 1 : -1); + } + public override Quad SelectionQuad => ScreenSpaceDrawQuad; public override Vector2 ScreenSpaceSelectionPoint => head.ScreenSpaceDrawQuad.Centre; diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs index c645ddd98d..4bb9d5f5c1 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs @@ -37,16 +37,10 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints protected override void LoadComplete() { base.LoadComplete(); - directionBindable.BindValueChanged(onDirectionChanged, true); + directionBindable.BindValueChanged(OnDirectionChanged, true); } - private void onDirectionChanged(ValueChangedEvent direction) - { - var anchor = direction.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre; - Anchor = Origin = anchor; - foreach (var child in InternalChildren) - child.Anchor = child.Origin = anchor; - } + protected abstract void OnDirectionChanged(ValueChangedEvent direction); protected override void Update() { diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs index c2df09e92a..3476f91568 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs @@ -1,9 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK; namespace osu.Game.Rulesets.Mania.Edit.Blueprints { @@ -14,7 +17,14 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints public NoteSelectionBlueprint(Note note) : base(note) { - AddInternal(notePiece = new EditNotePiece { RelativeSizeAxes = Axes.X }); + Anchor = Anchor.BottomCentre; + Origin = Anchor.BottomCentre; + AddInternal(notePiece = new EditNotePiece + { + RelativeSizeAxes = Axes.X, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + }); } protected override void Update() @@ -23,5 +33,10 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints notePiece.Height = DrawableObject.DrawHeight; } + + protected override void OnDirectionChanged(ValueChangedEvent direction) + { + notePiece.Scale = new Vector2(1, direction.NewValue == ScrollingDirection.Down ? 1 : -1); + } } } From bd630c189e16754e630fdbcacccae455fe0239dd Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 7 Nov 2024 17:25:42 +0900 Subject: [PATCH 759/924] Fix tests not working by forgoing beatmap updates --- .../TestSceneBeatmapRecommendations.cs | 34 +++++++++++++++++++ .../Beatmaps/BeatmapOnlineChangeIngest.cs | 4 +-- osu.Game/Beatmaps/BeatmapUpdater.cs | 16 +-------- osu.Game/Beatmaps/IBeatmapUpdater.cs | 30 ++++++++++++++++ .../Database/BackgroundDataStoreProcessor.cs | 2 +- osu.Game/OsuGameBase.cs | 6 ++-- 6 files changed, 72 insertions(+), 20 deletions(-) create mode 100644 osu.Game/Beatmaps/IBeatmapUpdater.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index 82791056b6..c9ad22e8df 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -10,9 +10,12 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Extensions; +using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mania; @@ -194,5 +197,36 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded); AddUntilStep("recommended beatmap displayed", () => Game.Beatmap.Value.BeatmapInfo.MatchesOnlineID(getImport().Beatmaps[expectedDiff - 1])); } + + protected override TestOsuGame CreateTestGame() => new NoBeatmapUpdateGame(LocalStorage, API); + + private class NoBeatmapUpdateGame : TestOsuGame + { + public NoBeatmapUpdateGame(Storage storage, IAPIProvider api, string[] args = null) + : base(storage, api, args) + { + } + + protected override IBeatmapUpdater CreateBeatmapUpdater() => new TestBeatmapUpdater(); + + private class TestBeatmapUpdater : IBeatmapUpdater + { + public void Queue(Live beatmapSet, MetadataLookupScope lookupScope = MetadataLookupScope.LocalCacheFirst) + { + } + + public void Process(BeatmapSetInfo beatmapSet, MetadataLookupScope lookupScope = MetadataLookupScope.LocalCacheFirst) + { + } + + public void ProcessObjectCounts(BeatmapInfo beatmapInfo, MetadataLookupScope lookupScope = MetadataLookupScope.LocalCacheFirst) + { + } + + public void Dispose() + { + } + } + } } } diff --git a/osu.Game/Beatmaps/BeatmapOnlineChangeIngest.cs b/osu.Game/Beatmaps/BeatmapOnlineChangeIngest.cs index b160043820..965f3be0aa 100644 --- a/osu.Game/Beatmaps/BeatmapOnlineChangeIngest.cs +++ b/osu.Game/Beatmaps/BeatmapOnlineChangeIngest.cs @@ -13,11 +13,11 @@ namespace osu.Game.Beatmaps /// public partial class BeatmapOnlineChangeIngest : Component { - private readonly BeatmapUpdater beatmapUpdater; + private readonly IBeatmapUpdater beatmapUpdater; private readonly RealmAccess realm; private readonly MetadataClient metadataClient; - public BeatmapOnlineChangeIngest(BeatmapUpdater beatmapUpdater, RealmAccess realm, MetadataClient metadataClient) + public BeatmapOnlineChangeIngest(IBeatmapUpdater beatmapUpdater, RealmAccess realm, MetadataClient metadataClient) { this.beatmapUpdater = beatmapUpdater; this.realm = realm; diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index e897d28916..04ed9c2488 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; @@ -15,10 +14,7 @@ using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Beatmaps { - /// - /// Handles all processing required to ensure a local beatmap is in a consistent state with any changes. - /// - public class BeatmapUpdater : IDisposable + public class BeatmapUpdater : IBeatmapUpdater { private readonly IWorkingBeatmapCache workingBeatmapCache; @@ -38,11 +34,6 @@ namespace osu.Game.Beatmaps metadataLookup = new BeatmapUpdaterMetadataLookup(api, storage); } - /// - /// Queue a beatmap for background processing. - /// - /// The managed beatmap set to update. A transaction will be opened to apply changes. - /// The preferred scope to use for metadata lookup. public void Queue(Live beatmapSet, MetadataLookupScope lookupScope = MetadataLookupScope.LocalCacheFirst) { Logger.Log($"Queueing change for local beatmap {beatmapSet}"); @@ -50,11 +41,6 @@ namespace osu.Game.Beatmaps updateScheduler); } - /// - /// Run all processing on a beatmap immediately. - /// - /// The managed beatmap set to update. A transaction will be opened to apply changes. - /// The preferred scope to use for metadata lookup. public void Process(BeatmapSetInfo beatmapSet, MetadataLookupScope lookupScope = MetadataLookupScope.LocalCacheFirst) => beatmapSet.Realm!.Write(_ => { // Before we use below, we want to invalidate. diff --git a/osu.Game/Beatmaps/IBeatmapUpdater.cs b/osu.Game/Beatmaps/IBeatmapUpdater.cs new file mode 100644 index 0000000000..ad543e667e --- /dev/null +++ b/osu.Game/Beatmaps/IBeatmapUpdater.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Game.Database; + +namespace osu.Game.Beatmaps +{ + /// + /// Handles all processing required to ensure a local beatmap is in a consistent state with any changes. + /// + public interface IBeatmapUpdater : IDisposable + { + /// + /// Queue a beatmap for background processing. + /// + /// The managed beatmap set to update. A transaction will be opened to apply changes. + /// The preferred scope to use for metadata lookup. + void Queue(Live beatmapSet, MetadataLookupScope lookupScope = MetadataLookupScope.LocalCacheFirst); + + /// + /// Run all processing on a beatmap immediately. + /// + /// The managed beatmap set to update. A transaction will be opened to apply changes. + /// The preferred scope to use for metadata lookup. + void Process(BeatmapSetInfo beatmapSet, MetadataLookupScope lookupScope = MetadataLookupScope.LocalCacheFirst); + + void ProcessObjectCounts(BeatmapInfo beatmapInfo, MetadataLookupScope lookupScope = MetadataLookupScope.LocalCacheFirst); + } +} diff --git a/osu.Game/Database/BackgroundDataStoreProcessor.cs b/osu.Game/Database/BackgroundDataStoreProcessor.cs index 3efd4da3aa..1512b6be93 100644 --- a/osu.Game/Database/BackgroundDataStoreProcessor.cs +++ b/osu.Game/Database/BackgroundDataStoreProcessor.cs @@ -46,7 +46,7 @@ namespace osu.Game.Database private RealmAccess realmAccess { get; set; } = null!; [Resolved] - private BeatmapUpdater beatmapUpdater { get; set; } = null!; + private IBeatmapUpdater beatmapUpdater { get; set; } = null!; [Resolved] private IBindable gameBeatmap { get; set; } = null!; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index d4704d1c72..dc13924b4f 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -198,7 +198,7 @@ namespace osu.Game public readonly Bindable>> AvailableMods = new Bindable>>(new Dictionary>()); private BeatmapDifficultyCache difficultyCache; - private BeatmapUpdater beatmapUpdater; + private IBeatmapUpdater beatmapUpdater; private UserLookupCache userCache; private BeatmapLookupCache beatmapCache; @@ -324,7 +324,7 @@ namespace osu.Game base.Content.Add(difficultyCache); // TODO: OsuGame or OsuGameBase? - dependencies.CacheAs(beatmapUpdater = new BeatmapUpdater(BeatmapManager, difficultyCache, API, Storage)); + dependencies.CacheAs(beatmapUpdater = CreateBeatmapUpdater()); dependencies.CacheAs(SpectatorClient = new OnlineSpectatorClient(endpoints)); dependencies.CacheAs(MultiplayerClient = new OnlineMultiplayerClient(endpoints)); dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints)); @@ -563,6 +563,8 @@ namespace osu.Game } } + protected virtual IBeatmapUpdater CreateBeatmapUpdater() => new BeatmapUpdater(BeatmapManager, difficultyCache, API, Storage); + protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager(); protected virtual BatteryInfo CreateBatteryInfo() => null; From 4d7fd236c5dc74a8abcf1588be43cad2dab8b0c9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 7 Nov 2024 17:28:31 +0900 Subject: [PATCH 760/924] Make class partial --- .../Visual/SongSelect/TestSceneBeatmapRecommendations.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index c9ad22e8df..66862e1b78 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -200,7 +200,7 @@ namespace osu.Game.Tests.Visual.SongSelect protected override TestOsuGame CreateTestGame() => new NoBeatmapUpdateGame(LocalStorage, API); - private class NoBeatmapUpdateGame : TestOsuGame + private partial class NoBeatmapUpdateGame : TestOsuGame { public NoBeatmapUpdateGame(Storage storage, IAPIProvider api, string[] args = null) : base(storage, api, args) From e058f98bad3978234a7bbaf98edc8d061a7c4d5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 7 Nov 2024 09:36:28 +0100 Subject: [PATCH 761/924] Remove unused field --- .../Edit/Blueprints/HoldNoteSelectionBlueprint.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index 66b914d320..4e0b0cea6c 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -34,7 +34,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints private EditHoldNoteEndPiece head = null!; private EditHoldNoteEndPiece tail = null!; - private Container body = null!; protected new DrawableHoldNote DrawableObject => (DrawableHoldNote)base.DrawableObject; @@ -89,7 +88,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints }, DragEnded = () => changeHandler?.EndChange(), }, - body = new Container + new Container { RelativeSizeAxes = Axes.Both, Masking = true, From c7d0a7dde216fa7a1fe8be70c0097733aa26a837 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Nov 2024 18:31:06 +0900 Subject: [PATCH 762/924] Update xmldoc and make realm transactions more obvious --- osu.Game/Beatmaps/BeatmapUpdater.cs | 70 +++++++++++++++------------- osu.Game/Beatmaps/IBeatmapUpdater.cs | 5 ++ 2 files changed, 43 insertions(+), 32 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index 04ed9c2488..efb432b84e 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -41,50 +41,56 @@ namespace osu.Game.Beatmaps updateScheduler); } - public void Process(BeatmapSetInfo beatmapSet, MetadataLookupScope lookupScope = MetadataLookupScope.LocalCacheFirst) => beatmapSet.Realm!.Write(_ => + public void Process(BeatmapSetInfo beatmapSet, MetadataLookupScope lookupScope = MetadataLookupScope.LocalCacheFirst) { - // Before we use below, we want to invalidate. - workingBeatmapCache.Invalidate(beatmapSet); - - if (lookupScope != MetadataLookupScope.None) - metadataLookup.Update(beatmapSet, lookupScope == MetadataLookupScope.OnlineFirst); - - foreach (var beatmap in beatmapSet.Beatmaps) + beatmapSet.Realm!.Write(_ => { - difficultyCache.Invalidate(beatmap); + // Before we use below, we want to invalidate. + workingBeatmapCache.Invalidate(beatmapSet); - var working = workingBeatmapCache.GetWorkingBeatmap(beatmap); - var ruleset = working.BeatmapInfo.Ruleset.CreateInstance(); + if (lookupScope != MetadataLookupScope.None) + metadataLookup.Update(beatmapSet, lookupScope == MetadataLookupScope.OnlineFirst); - Debug.Assert(ruleset != null); + foreach (var beatmap in beatmapSet.Beatmaps) + { + difficultyCache.Invalidate(beatmap); - var calculator = ruleset.CreateDifficultyCalculator(working); + var working = workingBeatmapCache.GetWorkingBeatmap(beatmap); + var ruleset = working.BeatmapInfo.Ruleset.CreateInstance(); - beatmap.StarRating = calculator.Calculate().StarRating; - beatmap.Length = working.Beatmap.CalculatePlayableLength(); - beatmap.BPM = 60000 / working.Beatmap.GetMostCommonBeatLength(); - beatmap.EndTimeObjectCount = working.Beatmap.HitObjects.Count(h => h is IHasDuration); - beatmap.TotalObjectCount = working.Beatmap.HitObjects.Count; - } + Debug.Assert(ruleset != null); - // And invalidate again afterwards as re-fetching the most up-to-date database metadata will be required. - workingBeatmapCache.Invalidate(beatmapSet); - }); + var calculator = ruleset.CreateDifficultyCalculator(working); - public void ProcessObjectCounts(BeatmapInfo beatmapInfo, MetadataLookupScope lookupScope = MetadataLookupScope.LocalCacheFirst) => beatmapInfo.Realm!.Write(_ => + beatmap.StarRating = calculator.Calculate().StarRating; + beatmap.Length = working.Beatmap.CalculatePlayableLength(); + beatmap.BPM = 60000 / working.Beatmap.GetMostCommonBeatLength(); + beatmap.EndTimeObjectCount = working.Beatmap.HitObjects.Count(h => h is IHasDuration); + beatmap.TotalObjectCount = working.Beatmap.HitObjects.Count; + } + + // And invalidate again afterwards as re-fetching the most up-to-date database metadata will be required. + workingBeatmapCache.Invalidate(beatmapSet); + }); + } + + public void ProcessObjectCounts(BeatmapInfo beatmapInfo, MetadataLookupScope lookupScope = MetadataLookupScope.LocalCacheFirst) { - // Before we use below, we want to invalidate. - workingBeatmapCache.Invalidate(beatmapInfo); + beatmapInfo.Realm!.Write(_ => + { + // Before we use below, we want to invalidate. + workingBeatmapCache.Invalidate(beatmapInfo); - var working = workingBeatmapCache.GetWorkingBeatmap(beatmapInfo); - var beatmap = working.Beatmap; + var working = workingBeatmapCache.GetWorkingBeatmap(beatmapInfo); + var beatmap = working.Beatmap; - beatmapInfo.EndTimeObjectCount = beatmap.HitObjects.Count(h => h is IHasDuration); - beatmapInfo.TotalObjectCount = beatmap.HitObjects.Count; + beatmapInfo.EndTimeObjectCount = beatmap.HitObjects.Count(h => h is IHasDuration); + beatmapInfo.TotalObjectCount = beatmap.HitObjects.Count; - // And invalidate again afterwards as re-fetching the most up-to-date database metadata will be required. - workingBeatmapCache.Invalidate(beatmapInfo); - }); + // And invalidate again afterwards as re-fetching the most up-to-date database metadata will be required. + workingBeatmapCache.Invalidate(beatmapInfo); + }); + } #region Implementation of IDisposable diff --git a/osu.Game/Beatmaps/IBeatmapUpdater.cs b/osu.Game/Beatmaps/IBeatmapUpdater.cs index ad543e667e..062984adf0 100644 --- a/osu.Game/Beatmaps/IBeatmapUpdater.cs +++ b/osu.Game/Beatmaps/IBeatmapUpdater.cs @@ -25,6 +25,11 @@ namespace osu.Game.Beatmaps /// The preferred scope to use for metadata lookup. void Process(BeatmapSetInfo beatmapSet, MetadataLookupScope lookupScope = MetadataLookupScope.LocalCacheFirst); + /// + /// Runs a subset of processing focused on updating any cached beatmap object counts. + /// + /// The managed beatmap to update. A transaction will be opened to apply changes. + /// The preferred scope to use for metadata lookup. void ProcessObjectCounts(BeatmapInfo beatmapInfo, MetadataLookupScope lookupScope = MetadataLookupScope.LocalCacheFirst); } } From a5e6da76cb090a72932d7c610d037390266c5036 Mon Sep 17 00:00:00 2001 From: Jay Lawton Date: Thu, 7 Nov 2024 19:53:53 +1000 Subject: [PATCH 763/924] introduce difficult strains globally --- .../Rulesets/Difficulty/Skills/StrainSkill.cs | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs index b07e8399c0..c28635ef22 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs @@ -26,10 +26,10 @@ namespace osu.Game.Rulesets.Difficulty.Skills protected virtual int SectionLength => 400; private double currentSectionPeak; // We also keep track of the peak strain level in the current section. - private double currentSectionEnd; private readonly List strainPeaks = new List(); + protected List ObjectStrains = new List(); // Store individual strains protected StrainSkill(Mod[] mods) : base(mods) @@ -57,7 +57,25 @@ namespace osu.Game.Rulesets.Difficulty.Skills currentSectionEnd += SectionLength; } - currentSectionPeak = Math.Max(StrainValueAt(current), currentSectionPeak); + double strain = StrainValueAt(current); + currentSectionPeak = Math.Max(strain, currentSectionPeak); + + // Store the strain value for the object + ObjectStrains.Add(strain); + } + + /// + /// Calculates the number of strains weighted against the top strain. + /// The result is scaled by clock rate as it affects the total number of strains. + /// + public double CountDifficultStrains() + { + if (ObjectStrains.Count == 0) + return 0.0; + + double consistentTopStrain = DifficultyValue() / 10; // What would the top strain be if all strain values were identical + // Use a weighted sum of all strains. Constants are arbitrary and give nice values + return ObjectStrains.Sum(s => 1.1 / (1 + Math.Exp(-10 * (s / consistentTopStrain - 0.88)))); } /// From 177781aca547b82ec9cc3bd0c2fbde81c7d8c1a7 Mon Sep 17 00:00:00 2001 From: Jay Lawton Date: Thu, 7 Nov 2024 19:57:37 +1000 Subject: [PATCH 764/924] remove localised instance of difficultstrains --- .../Difficulty/Skills/OsuStrainSkill.cs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 96180c0aa1..c36534dcef 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -60,20 +60,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills return Difficulty; } - /// - /// Returns the number of strains weighted against the top strain. - /// The result is scaled by clock rate as it affects the total number of strains. - /// - public double CountDifficultStrains() - { - if (Difficulty == 0) - return 0.0; - - double consistentTopStrain = Difficulty / 10; // What would the top strain be if all strain values were identical - // Use a weighted sum of all strains. Constants are arbitrary and give nice values - return ObjectStrains.Sum(s => 1.1 / (1 + Math.Exp(-10 * (s / consistentTopStrain - 0.88)))); - } - public static double DifficultyToPerformance(double difficulty) => Math.Pow(5.0 * Math.Max(1.0, difficulty / 0.0675) - 4.0, 3.0) / 100000.0; } } From cb2e5ac2c4c974fe134f7bce2969acb67704b2ec Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 7 Nov 2024 19:15:58 +0900 Subject: [PATCH 765/924] Skip diffcalc job if target is not valid Same as all other jobs... Just so that it doesn't appear as "completed" in the workflow list. --- .github/workflows/diffcalc.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml index c2eeff20df..15fbcf38e4 100644 --- a/.github/workflows/diffcalc.yml +++ b/.github/workflows/diffcalc.yml @@ -107,6 +107,7 @@ jobs: master-environment: name: Save master environment runs-on: ubuntu-latest + if: ${{ github.event_name == 'workflow_dispatch' || contains(github.event.comment.body, '!diffcalc') }} outputs: HEAD: ${{ steps.get-head.outputs.HEAD }} steps: From 748055ab29d04c53542df0814689b05970110f53 Mon Sep 17 00:00:00 2001 From: Jay Lawton Date: Thu, 7 Nov 2024 20:15:59 +1000 Subject: [PATCH 766/924] remove double instance of array --- osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 1 - osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 1 - osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 1 - 3 files changed, 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index b3d530e3af..faf91e4652 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -34,7 +34,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills { currentStrain *= strainDecay(current.DeltaTime); currentStrain += AimEvaluator.EvaluateDifficultyOf(current, withSliders) * skillMultiplier; - ObjectStrains.Add(currentStrain); return currentStrain; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index c36534dcef..559a871df1 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -23,7 +23,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// protected virtual double ReducedStrainBaseline => 0.75; - protected List ObjectStrains = new List(); protected double Difficulty; protected OsuStrainSkill(Mod[] mods) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index e5aa25c1eb..d2c4bbb618 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -40,7 +40,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current); double totalStrain = currentStrain * currentRhythm; - ObjectStrains.Add(totalStrain); return totalStrain; } From 2b6a47316430bd4c8f939314ec8c4b086d63dc99 Mon Sep 17 00:00:00 2001 From: Jay Lawton Date: Thu, 7 Nov 2024 21:12:04 +1000 Subject: [PATCH 767/924] update methods --- osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs index c28635ef22..d597a0f6b2 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Difficulty.Skills private double currentSectionEnd; private readonly List strainPeaks = new List(); - protected List ObjectStrains = new List(); // Store individual strains + protected readonly List ObjectStrains = new List(); // Store individual strains protected StrainSkill(Mod[] mods) : base(mods) @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// Calculates the number of strains weighted against the top strain. /// The result is scaled by clock rate as it affects the total number of strains. /// - public double CountDifficultStrains() + public virtual double CountDifficultStrains() { if (ObjectStrains.Count == 0) return 0.0; From 7c3a3c4d1093b3ffcd238da5bf30fb6e8ed0d0a6 Mon Sep 17 00:00:00 2001 From: Jay Lawton Date: Thu, 7 Nov 2024 21:56:42 +1000 Subject: [PATCH 768/924] rename DifficultStrains for clarity --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 4 ++-- osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index acf01b2a83..575e03051c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -48,8 +48,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; - double aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountDifficultStrains(); - double speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountDifficultStrains(); + double aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountTopWeightedStrains(); + double speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountTopWeightedStrains(); if (mods.Any(m => m is OsuModTouchDevice)) { diff --git a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs index d597a0f6b2..1cb6b69f91 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// Calculates the number of strains weighted against the top strain. /// The result is scaled by clock rate as it affects the total number of strains. /// - public virtual double CountDifficultStrains() + public virtual double CountTopWeightedStrains() { if (ObjectStrains.Count == 0) return 0.0; From dba065302daa5bca92e8d26fe0ba8199bc67f874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 7 Nov 2024 13:41:41 +0100 Subject: [PATCH 769/924] Adjust appearance once more --- .../Blueprints/Components/EditNotePiece.cs | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs index c626e8011e..3f15515ba6 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs @@ -2,8 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; @@ -11,6 +11,7 @@ using osu.Game.Rulesets.Mania.Skinning.Default; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components { @@ -23,15 +24,31 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components { Masking = true; CornerRadius = 5; - BorderThickness = 6; - BorderColour = ColourInfo.GradientVertical(Colour4.White.Opacity(0.5f), Colour4.White); Height = DefaultNotePiece.NOTE_HEIGHT; - InternalChild = new Box + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true, + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 5, + BorderThickness = 5, + BorderColour = Color4.White.Opacity(0.7f), + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + }, + }, + new Box + { + RelativeSizeAxes = Axes.X, + Height = 10, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + }, }; } From b0c6042b2a418e2dcd4dcdf404f82cb1b5aff315 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 7 Nov 2024 13:58:57 +0100 Subject: [PATCH 770/924] Fix HUD elements shifting in unintended manner when partially off-screen flipped skin elements are present Closes https://github.com/ppy/osu/issues/30286. --- osu.Game/Screens/Play/HUDOverlay.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 292f554483..a6c2405eb6 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -300,7 +300,9 @@ namespace osu.Game.Screens.Play if (element is LegacyHealthDisplay) return; - float bottom = drawable.ScreenSpaceDrawQuad.BottomRight.Y; + // AABB is used here because the drawable can be flipped/rotated arbitrarily, + // so the "bottom right" corner of the raw SSDQ might not necessarily be where one expects it to be. + float bottom = drawable.ScreenSpaceDrawQuad.AABBFloat.BottomRight.Y; bool isRelativeX = drawable.RelativeSizeAxes == Axes.X; @@ -319,7 +321,7 @@ namespace osu.Game.Screens.Play // and align bottom-right components with the top-edge of the highest bottom-anchored hud element. else if (drawable.Anchor.HasFlag(Anchor.BottomRight) || (drawable.Anchor.HasFlag(Anchor.y2) && drawable.RelativeSizeAxes == Axes.X)) { - var topLeft = element.ScreenSpaceDrawQuad.TopLeft; + var topLeft = element.ScreenSpaceDrawQuad.AABBFloat.TopLeft; if (highestBottomScreenSpace == null || topLeft.Y < highestBottomScreenSpace.Value.Y) highestBottomScreenSpace = topLeft; } From 78c97d2cd7af2d7b05dfc2f5a6ba45fefa4dc72d Mon Sep 17 00:00:00 2001 From: StanR Date: Thu, 7 Nov 2024 20:36:00 +0500 Subject: [PATCH 771/924] Add `DifficultyCalculationUtils` --- .../Difficulty/Skills/Strain.cs | 3 +- .../Difficulty/Evaluators/AimEvaluator.cs | 12 +++-- .../Difficulty/Evaluators/RhythmEvaluator.cs | 5 +- .../Difficulty/Evaluators/SpeedEvaluator.cs | 9 ++-- .../Preprocessing/OsuDifficultyHitObject.cs | 1 + .../Difficulty/Evaluators/ColourEvaluator.cs | 21 ++------ .../Utils/DifficultyCalculationUtils.cs | 50 +++++++++++++++++++ 7 files changed, 72 insertions(+), 29 deletions(-) create mode 100644 osu.Game/Rulesets/Difficulty/Utils/DifficultyCalculationUtils.cs diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs index a24fcaad8d..bb4261ea13 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Utils; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Mania.Difficulty.Preprocessing; using osu.Game.Rulesets.Mods; @@ -73,7 +74,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills // 0.0 +--------+-+---------------> Release Difference / ms // release_threshold if (isOverlapping) - holdAddition = 1 / (1 + Math.Exp(0.27 * (release_threshold - closestEndTime))); + holdAddition = DifficultyCalculationUtils.Logistic(x: closestEndTime, multiplier: 0.27, midpointOffset: release_threshold); // Decay and increase individualStrains in own column individualStrains[column] = applyDecay(individualStrains[column], startTime - startTimes[column], individual_decay_base); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs index 3d1939acac..9816f6d0a4 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs @@ -3,6 +3,7 @@ using System; using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Objects; @@ -33,6 +34,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators var osuLastObj = (OsuDifficultyHitObject)current.Previous(0); var osuLastLastObj = (OsuDifficultyHitObject)current.Previous(1); + const int radius = OsuDifficultyHitObject.NORMALISED_RADIUS; + const int diameter = OsuDifficultyHitObject.NORMALISED_DIAMETER; + // Calculate the velocity to the current hitobject, which starts with a base distance / time assuming the last object is a hitcircle. double currVelocity = osuCurrObj.LazyJumpDistance / osuCurrObj.StrainTime; @@ -77,14 +81,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators wideAngleBonus = calcWideAngleBonus(currAngle); acuteAngleBonus = calcAcuteAngleBonus(currAngle); - if (osuCurrObj.StrainTime > 100) // Only buff deltaTime exceeding 300 bpm 1/2. + if (DifficultyCalculationUtils.MillisecondsToBPM(osuCurrObj.StrainTime, 2) < 300) // Only buff deltaTime exceeding 300 bpm 1/2. acuteAngleBonus = 0; else { acuteAngleBonus *= calcAcuteAngleBonus(lastAngle) // Multiply by previous angle, we don't want to buff unless this is a wiggle type pattern. - * Math.Min(angleBonus, 125 / osuCurrObj.StrainTime) // The maximum velocity we buff is equal to 125 / strainTime + * Math.Min(angleBonus, diameter * 1.25 / osuCurrObj.StrainTime) // The maximum velocity we buff is equal to 125 / strainTime * Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, (100 - osuCurrObj.StrainTime) / 25)), 2) // scale buff from 150 bpm 1/4 to 200 bpm 1/4 - * Math.Pow(Math.Sin(Math.PI / 2 * (Math.Clamp(osuCurrObj.LazyJumpDistance, 50, 100) - 50) / 50), 2); // Buff distance exceeding 50 (radius) up to 100 (diameter). + * Math.Pow(Math.Sin(Math.PI / 2 * (Math.Clamp(osuCurrObj.LazyJumpDistance, radius, diameter) - radius) / radius), 2); // Buff distance exceeding radius up to diameter. } // Penalize wide angles if they're repeated, reducing the penalty as the lastAngle gets more acute. @@ -104,7 +108,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators double distRatio = Math.Pow(Math.Sin(Math.PI / 2 * Math.Abs(prevVelocity - currVelocity) / Math.Max(prevVelocity, currVelocity)), 2); // Reward for % distance up to 125 / strainTime for overlaps where velocity is still changing. - double overlapVelocityBuff = Math.Min(125 / Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime), Math.Abs(prevVelocity - currVelocity)); + double overlapVelocityBuff = Math.Min(diameter * 1.25 / Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime), Math.Abs(prevVelocity - currVelocity)); velocityChangeBonus = overlapVelocityBuff * distRatio; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index d10d2c5c05..d503dd2bcc 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Objects; @@ -120,7 +121,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators islandCount.Count++; // repeated island (ex: triplet -> triplet) - double power = logistic(island.Delta, 2.75, 0.24, 14); + double power = DifficultyCalculationUtils.Logistic(island.Delta, maxValue: 2.75, multiplier: 0.24, midpointOffset: 58.33); effectiveRatio *= Math.Min(3.0 / islandCount.Count, Math.Pow(1.0 / islandCount.Count, power)); islandCounts[countIndex] = (islandCount.Island, islandCount.Count); @@ -172,8 +173,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators return Math.Sqrt(4 + rhythmComplexitySum * rhythm_overall_multiplier) / 2.0; // produces multiplier that can be applied to strain. range [1, infinity) (not really though) } - private static double logistic(double x, double maxValue, double multiplier, double offset) => (maxValue / (1 + Math.Pow(Math.E, offset - (multiplier * x)))); - private class Island : IEquatable { private readonly double deltaDifferenceEpsilon; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs index c220352ee0..a5f6468f17 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs @@ -3,6 +3,7 @@ using System; using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Objects; @@ -10,8 +11,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators { public static class SpeedEvaluator { - private const double single_spacing_threshold = 125; // 1.25 circles distance between centers - private const double min_speed_bonus = 75; // ~200BPM + private const double single_spacing_threshold = OsuDifficultyHitObject.NORMALISED_DIAMETER * 1.25; // 1.25 circles distance between centers + private const double min_speed_bonus = 200; // 200 BPM 1/4th private const double speed_balancing_factor = 40; private const double distance_multiplier = 0.94; @@ -43,8 +44,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators double speedBonus = 0.0; // Add additional scaling bonus for streams/bursts higher than 200bpm - if (strainTime < min_speed_bonus) - speedBonus = 0.75 * Math.Pow((min_speed_bonus - strainTime) / speed_balancing_factor, 2); + if (DifficultyCalculationUtils.MillisecondsToBPM(strainTime) > min_speed_bonus) + speedBonus = 0.75 * Math.Pow((DifficultyCalculationUtils.BPMToMilliseconds(min_speed_bonus) - strainTime) / speed_balancing_factor, 2); double travelDistance = osuPrevObj?.TravelDistance ?? 0; double distance = travelDistance + osuCurrObj.MinimumJumpDistance; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 3eaf500ad7..46d8c63751 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -19,6 +19,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing /// A distance by which all distances should be scaled in order to assume a uniform circle size. /// public const int NORMALISED_RADIUS = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths. + public const int NORMALISED_DIAMETER = NORMALISED_RADIUS * 2; public const int MIN_DELTA_TIME = 25; diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/ColourEvaluator.cs b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/ColourEvaluator.cs index 9f63e84867..25428c8b2f 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/ColourEvaluator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/ColourEvaluator.cs @@ -3,6 +3,7 @@ using System; using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour.Data; @@ -11,26 +12,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators { public class ColourEvaluator { - /// - /// A sigmoid function. It gives a value between (middle - height/2) and (middle + height/2). - /// - /// The input value. - /// The center of the sigmoid, where the largest gradient occurs and value is equal to middle. - /// The radius of the sigmoid, outside of which values are near the minimum/maximum. - /// The middle of the sigmoid output. - /// The height of the sigmoid output. This will be equal to max value - min value. - private static double sigmoid(double val, double center, double width, double middle, double height) - { - double sigmoid = Math.Tanh(Math.E * -(val - center) / width); - return sigmoid * (height / 2) + middle; - } - /// /// Evaluate the difficulty of the first note of a . /// public static double EvaluateDifficultyOf(MonoStreak monoStreak) { - return sigmoid(monoStreak.Index, 2, 2, 0.5, 1) * EvaluateDifficultyOf(monoStreak.Parent) * 0.5; + return DifficultyCalculationUtils.Logistic(exponent: Math.E * monoStreak.Index - 2 * Math.E) * EvaluateDifficultyOf(monoStreak.Parent) * 0.5; } /// @@ -38,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators /// public static double EvaluateDifficultyOf(AlternatingMonoPattern alternatingMonoPattern) { - return sigmoid(alternatingMonoPattern.Index, 2, 2, 0.5, 1) * EvaluateDifficultyOf(alternatingMonoPattern.Parent); + return DifficultyCalculationUtils.Logistic(exponent: Math.E * alternatingMonoPattern.Index - 2 * Math.E) * EvaluateDifficultyOf(alternatingMonoPattern.Parent); } /// @@ -46,7 +33,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators /// public static double EvaluateDifficultyOf(RepeatingHitPatterns repeatingHitPattern) { - return 2 * (1 - sigmoid(repeatingHitPattern.RepetitionInterval, 2, 2, 0.5, 1)); + return 2 * (1 - DifficultyCalculationUtils.Logistic(exponent: Math.E * repeatingHitPattern.RepetitionInterval - 2 * Math.E)); } public static double EvaluateDifficultyOf(DifficultyHitObject hitObject) diff --git a/osu.Game/Rulesets/Difficulty/Utils/DifficultyCalculationUtils.cs b/osu.Game/Rulesets/Difficulty/Utils/DifficultyCalculationUtils.cs new file mode 100644 index 0000000000..ac7bf7a04e --- /dev/null +++ b/osu.Game/Rulesets/Difficulty/Utils/DifficultyCalculationUtils.cs @@ -0,0 +1,50 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Rulesets.Difficulty.Utils +{ + public static class DifficultyCalculationUtils + { + /// + /// Converts BPM value into milliseconds + /// + /// Beats per minute + /// Which rhythm delimiter to use, default is 1/4 + /// BPM conveted to milliseconds + public static double BPMToMilliseconds(double bpm, int delimiter = 4) + { + return 60000.0 / delimiter / bpm; + } + + /// + /// Converts milliseconds value into a BPM value + /// + /// Milliseconds + /// Which rhythm delimiter to use, default is 1/4 + /// Milliseconds conveted to beats per minute + public static double MillisecondsToBPM(double ms, int delimiter = 4) + { + return 60000.0 / (ms * delimiter); + } + + /// + /// Calculates a S-shaped logistic function (https://en.wikipedia.org/wiki/Logistic_function) + /// + /// Value to calculate the function for + /// Maximum value returnable by the function + /// Growth rate of the function + /// How much the function midpoint is offset from zero + /// The output of logistic function of + public static double Logistic(double x, double maxValue = 1, double multiplier = 1, double midpointOffset = 0) => maxValue / (1 + Math.Exp(multiplier * (midpointOffset - x))); + + /// + /// Calculates a S-shaped logistic function (https://en.wikipedia.org/wiki/Logistic_function) + /// + /// Maximum value returnable by the function + /// Exponent + /// The output of logistic function + public static double Logistic(double exponent, double maxValue = 1) => maxValue / (1 + Math.Exp(exponent)); + } +} From c9d3b6557d37d6991971925ee7dc4358a5734495 Mon Sep 17 00:00:00 2001 From: StanR Date: Thu, 7 Nov 2024 21:23:26 +0500 Subject: [PATCH 772/924] Fix code issues --- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 1 + .../Rulesets/Difficulty/Utils/DifficultyCalculationUtils.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 46d8c63751..5e4c5c1ee9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -19,6 +19,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing /// A distance by which all distances should be scaled in order to assume a uniform circle size. /// public const int NORMALISED_RADIUS = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths. + public const int NORMALISED_DIAMETER = NORMALISED_RADIUS * 2; public const int MIN_DELTA_TIME = 25; diff --git a/osu.Game/Rulesets/Difficulty/Utils/DifficultyCalculationUtils.cs b/osu.Game/Rulesets/Difficulty/Utils/DifficultyCalculationUtils.cs index ac7bf7a04e..b9efcd683d 100644 --- a/osu.Game/Rulesets/Difficulty/Utils/DifficultyCalculationUtils.cs +++ b/osu.Game/Rulesets/Difficulty/Utils/DifficultyCalculationUtils.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Difficulty.Utils /// Growth rate of the function /// How much the function midpoint is offset from zero /// The output of logistic function of - public static double Logistic(double x, double maxValue = 1, double multiplier = 1, double midpointOffset = 0) => maxValue / (1 + Math.Exp(multiplier * (midpointOffset - x))); + public static double Logistic(double x, double midpointOffset, double multiplier, double maxValue = 1) => maxValue / (1 + Math.Exp(multiplier * (midpointOffset - x))); /// /// Calculates a S-shaped logistic function (https://en.wikipedia.org/wiki/Logistic_function) From 233560b8679ce0c69e1b32b41e631ccb7afc359a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 7 Nov 2024 18:15:54 +0900 Subject: [PATCH 773/924] Fix CI test report workflow --- .github/workflows/report-nunit.yml | 37 ++++++++++++++++++------------ 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/.github/workflows/report-nunit.yml b/.github/workflows/report-nunit.yml index c44f46d70a..14f0208fc8 100644 --- a/.github/workflows/report-nunit.yml +++ b/.github/workflows/report-nunit.yml @@ -5,33 +5,40 @@ name: Annotate CI run with test results on: workflow_run: - workflows: ["Continuous Integration"] + workflows: [ "Continuous Integration" ] types: - completed -permissions: {} + +permissions: + contents: read + actions: read + checks: write + jobs: annotate: - permissions: - checks: write # to create checks (dorny/test-reporter) - name: Annotate CI run with test results runs-on: ubuntu-latest if: ${{ github.event.workflow_run.conclusion != 'cancelled' }} - strategy: - fail-fast: false - matrix: - os: - - { prettyname: Windows } - - { prettyname: macOS } - - { prettyname: Linux } - threadingMode: ['SingleThread', 'MultiThreaded'] timeout-minutes: 5 steps: + - name: Checkout + uses: actions/checkout@v4 + with: + repository: ${{ github.event.workflow_run.repository.full_name }} + ref: ${{ github.event.workflow_run.head_sha }} + + - name: Download results + uses: actions/download-artifact@v4 + with: + pattern: osu-test-results-* + merge-multiple: true + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ github.token }} + - name: Annotate CI run with test results uses: dorny/test-reporter@v1.8.0 with: - artifact: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}} - name: Test Results (${{matrix.os.prettyname}}, ${{matrix.threadingMode}}) + name: Results path: "*.trx" reporter: dotnet-trx list-suites: 'failed' From 70be82b0488924f0a1d73c15138c95b1639882aa Mon Sep 17 00:00:00 2001 From: finadoggie <75299710+Finadoggie@users.noreply.github.com> Date: Thu, 7 Nov 2024 22:42:41 -0800 Subject: [PATCH 774/924] Clamp estimateImproperlyFollowedDifficultSliders for lazer scores --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index ddabf866ff..9687944817 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -178,7 +178,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty { // We add tick misses here since they too mean that the player didn't follow the slider properly // We however aren't adding misses here because missing slider heads has a harsh penalty by itself and doesn't mean that the rest of the slider wasn't followed properly - estimateImproperlyFollowedDifficultSliders = Math.Min(countSliderEndsDropped + countSliderTickMiss, estimateDifficultSliders); + estimateImproperlyFollowedDifficultSliders = Math.Clamp(countSliderEndsDropped + countSliderTickMiss, 0, estimateDifficultSliders); } double sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - estimateImproperlyFollowedDifficultSliders / estimateDifficultSliders, 3) + attributes.SliderFactor; From 091d02b3a8a41a11dea2feff5f384d613dc39f92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 8 Nov 2024 09:22:20 +0100 Subject: [PATCH 775/924] Fix retry button on storage unavailable dialog not reopening realm if retry succeeds Related: https://github.com/ppy/osu/issues/30539 When starting up the game with a data location that points to an unavailable external device, a new realm file is created in the default location. Eventually a popup is shown that informs the user that the external storage is unavailable, and the user has an option to try the storage again. The button that invokes said option would check said storage correctly, but would not do anything about realm, which means the previously opened empty realm that is placed in the default location would remain open, which means the retry essentially doesn't work because the user's stuff isn't there after the retry. To fix this, take out a `BlockAllOperations()`, which will flush all open realms, and re-open the realm on the external location if the custom storage restore succeeds. --- osu.Game/Screens/Menu/StorageErrorDialog.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/StorageErrorDialog.cs b/osu.Game/Screens/Menu/StorageErrorDialog.cs index b48046d190..677a3b0278 100644 --- a/osu.Game/Screens/Menu/StorageErrorDialog.cs +++ b/osu.Game/Screens/Menu/StorageErrorDialog.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; +using osu.Game.Database; using osu.Game.IO; using osu.Game.Localisation; using osu.Game.Overlays; @@ -16,6 +17,9 @@ namespace osu.Game.Screens.Menu [Resolved] private IDialogOverlay dialogOverlay { get; set; } = null!; + [Resolved] + private RealmAccess realmAccess { get; set; } = null!; + public StorageErrorDialog(OsuStorage storage, OsuStorageError error) { HeaderText = StorageErrorDialogStrings.StorageError; @@ -35,7 +39,15 @@ namespace osu.Game.Screens.Menu Text = StorageErrorDialogStrings.TryAgain, Action = () => { - if (!storage.TryChangeToCustomStorage(out var nextError)) + bool success; + OsuStorageError nextError; + + // blocking all operations has a side effect of closing & reopening the realm db, + // which is desirable here since the restoration of the old storage - if it succeeds - means the realm db has moved. + using (realmAccess.BlockAllOperations(@"restoration of previously unavailable storage")) + success = storage.TryChangeToCustomStorage(out nextError); + + if (!success) dialogOverlay.Push(new StorageErrorDialog(storage, nextError)); } }, From d8fc7b180359ce16b90029fe94efdc207fc47519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 8 Nov 2024 10:00:21 +0100 Subject: [PATCH 776/924] Add extended logging to NVAPI operations To help diagnose https://github.com/ppy/osu/issues/30546. --- osu.Desktop/NVAPI.cs | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/osu.Desktop/NVAPI.cs b/osu.Desktop/NVAPI.cs index 0b09613ba0..1fc04aff59 100644 --- a/osu.Desktop/NVAPI.cs +++ b/osu.Desktop/NVAPI.cs @@ -141,12 +141,12 @@ namespace osu.Desktop // Make sure that this is a laptop. IntPtr[] gpus = new IntPtr[64]; - if (checkError(EnumPhysicalGPUs(gpus, out int gpuCount))) + if (checkError(EnumPhysicalGPUs(gpus, out int gpuCount), nameof(EnumPhysicalGPUs))) return false; for (int i = 0; i < gpuCount; i++) { - if (checkError(GetSystemType(gpus[i], out var type))) + if (checkError(GetSystemType(gpus[i], out var type), nameof(GetSystemType))) return false; if (type == NvSystemType.LAPTOP) @@ -182,7 +182,7 @@ namespace osu.Desktop bool success = setSetting(NvSettingID.OGL_THREAD_CONTROL_ID, (uint)value); - Logger.Log(success ? $"Threaded optimizations set to \"{value}\"!" : "Threaded optimizations set failed!"); + Logger.Log(success ? $"[NVAPI] Threaded optimizations set to \"{value}\"!" : "[NVAPI] Threaded optimizations set failed!"); } } @@ -205,7 +205,7 @@ namespace osu.Desktop uint numApps = profile.NumOfApps; - if (checkError(EnumApplications(sessionHandle, profileHandle, 0, ref numApps, applications))) + if (checkError(EnumApplications(sessionHandle, profileHandle, 0, ref numApps, applications), nameof(EnumApplications))) return false; for (uint i = 0; i < numApps; i++) @@ -236,10 +236,10 @@ namespace osu.Desktop isApplicationSpecific = true; - if (checkError(FindApplicationByName(sessionHandle, osu_filename, out profileHandle, ref application))) + if (checkError(FindApplicationByName(sessionHandle, osu_filename, out profileHandle, ref application), nameof(FindApplicationByName))) { isApplicationSpecific = false; - if (checkError(GetCurrentGlobalProfile(sessionHandle, out profileHandle))) + if (checkError(GetCurrentGlobalProfile(sessionHandle, out profileHandle), nameof(GetCurrentGlobalProfile))) return false; } @@ -263,7 +263,7 @@ namespace osu.Desktop newProfile.GPUSupport[0] = 1; - if (checkError(CreateProfile(sessionHandle, ref newProfile, out profileHandle))) + if (checkError(CreateProfile(sessionHandle, ref newProfile, out profileHandle), nameof(CreateProfile))) return false; return true; @@ -284,7 +284,7 @@ namespace osu.Desktop SettingID = settingId }; - if (checkError(GetSetting(sessionHandle, profileHandle, settingId, ref setting))) + if (checkError(GetSetting(sessionHandle, profileHandle, settingId, ref setting), nameof(GetSetting))) return false; return true; @@ -313,7 +313,7 @@ namespace osu.Desktop }; // Set the thread state - if (checkError(SetSetting(sessionHandle, profileHandle, ref newSetting))) + if (checkError(SetSetting(sessionHandle, profileHandle, ref newSetting), nameof(SetSetting))) return false; // Get the profile (needed to check app count) @@ -321,7 +321,7 @@ namespace osu.Desktop { Version = NvProfile.Stride }; - if (checkError(GetProfileInfo(sessionHandle, profileHandle, ref profile))) + if (checkError(GetProfileInfo(sessionHandle, profileHandle, ref profile), nameof(GetProfileInfo))) return false; if (!containsApplication(profileHandle, profile, out application)) @@ -332,12 +332,12 @@ namespace osu.Desktop application.AppName = osu_filename; application.UserFriendlyName = APPLICATION_NAME; - if (checkError(CreateApplication(sessionHandle, profileHandle, ref application))) + if (checkError(CreateApplication(sessionHandle, profileHandle, ref application), nameof(CreateApplication))) return false; } // Save! - return !checkError(SaveSettings(sessionHandle)); + return !checkError(SaveSettings(sessionHandle), nameof(SaveSettings)); } /// @@ -346,20 +346,25 @@ namespace osu.Desktop /// If the operation succeeded. private static bool createSession() { - if (checkError(CreateSession(out sessionHandle))) + if (checkError(CreateSession(out sessionHandle), nameof(CreateSession))) return false; // Load settings into session - if (checkError(LoadSettings(sessionHandle))) + if (checkError(LoadSettings(sessionHandle), nameof(LoadSettings))) return false; return true; } - private static bool checkError(NvStatus status) + private static bool checkError(NvStatus status, string caller) { Status = status; - return status != NvStatus.OK; + + bool hasError = status != NvStatus.OK; + if (hasError) + Logger.Log($"[NVAPI] {caller} call failed with status code {status}"); + + return hasError; } static NVAPI() From 8d7b19d6cf75de724ace35f29c6a2b8406cbb3b8 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 9 Nov 2024 15:09:20 +0900 Subject: [PATCH 777/924] Fix incorrectly translated NVAPI ABI --- osu.Desktop/NVAPI.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Desktop/NVAPI.cs b/osu.Desktop/NVAPI.cs index 1fc04aff59..fd372cddc5 100644 --- a/osu.Desktop/NVAPI.cs +++ b/osu.Desktop/NVAPI.cs @@ -258,11 +258,9 @@ namespace osu.Desktop Version = NvProfile.Stride, IsPredefined = 0, ProfileName = PROFILE_NAME, - GPUSupport = new uint[32] + GpuSupport = NvDrsGpuSupport.Geforce }; - newProfile.GPUSupport[0] = 1; - if (checkError(CreateProfile(sessionHandle, ref newProfile, out profileHandle), nameof(CreateProfile))) return false; @@ -463,9 +461,7 @@ namespace osu.Desktop [MarshalAs(UnmanagedType.ByValTStr, SizeConst = NVAPI.UNICODE_STRING_MAX)] public string ProfileName; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] - public uint[] GPUSupport; - + public NvDrsGpuSupport GpuSupport; public uint IsPredefined; public uint NumOfApps; public uint NumOfSettings; @@ -611,6 +607,7 @@ namespace osu.Desktop SYNC_NOT_ACTIVE = -194, // The requested action cannot be performed without Sync being enabled. SYNC_MASTER_NOT_FOUND = -195, // The requested action cannot be performed without Sync Master being enabled. INVALID_SYNC_TOPOLOGY = -196, // Invalid displays passed in the NV_GSYNC_DISPLAY pointer. + ECID_SIGN_ALGO_UNSUPPORTED = -197, // The specified signing algorithm is not supported. Either an incorrect value was entered or the current installed driver/hardware does not support the input value. ECID_KEY_VERIFICATION_FAILED = -198, // The encrypted public key verification has failed. FIRMWARE_OUT_OF_DATE = -199, // The device's firmware is out of date. @@ -749,4 +746,12 @@ namespace osu.Desktop OGL_THREAD_CONTROL_NUM_VALUES = 2, OGL_THREAD_CONTROL_DEFAULT = 0 } + + [Flags] + internal enum NvDrsGpuSupport : uint + { + Geforce = 1 << 0, + Quadro = 1 << 1, + Nvs = 1 << 2 + } } From 6183daa95f90decd479a0fee0310ca8c55f87e8c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 9 Nov 2024 14:54:19 +0900 Subject: [PATCH 778/924] Split diffcalc workflow to add concurrency group --- .github/workflows/_diffcalc_processor.yml | 250 ++++++++++++++++++++ .github/workflows/diffcalc.yml | 271 ++-------------------- 2 files changed, 275 insertions(+), 246 deletions(-) create mode 100644 .github/workflows/_diffcalc_processor.yml diff --git a/.github/workflows/_diffcalc_processor.yml b/.github/workflows/_diffcalc_processor.yml new file mode 100644 index 0000000000..74ce226240 --- /dev/null +++ b/.github/workflows/_diffcalc_processor.yml @@ -0,0 +1,250 @@ +name: "🔒Run diffcalc runner" + +on: + workflow_call: + inputs: + id: + type: string + head-sha: + type: string + pr-url: + type: string + pr-text: + type: string + dispatch-inputs: + type: string + outputs: + target: + description: The comparison target. + value: ${{ jobs.generator.outputs.target }} + sheet: + description: The comparison spreadsheet. + value: ${{ jobs.generator.outputs.sheet }} + secrets: + DIFFCALC_GOOGLE_CREDENTIALS: + required: true + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + +env: + GENERATOR_DIR: ${{ github.workspace }}/${{ inputs.id }} + GENERATOR_ENV: ${{ github.workspace }}/${{ inputs.id }}/.env + +jobs: + environment: + name: Setup environment + runs-on: self-hosted + steps: + - name: Checkout diffcalc-sheet-generator + uses: actions/checkout@v4 + with: + path: ${{ inputs.id }} + repository: 'smoogipoo/diffcalc-sheet-generator' + + - name: Add base environment + env: + GOOGLE_CREDS_FILE: ${{ github.workspace }}/${{ inputs.id }}/google-credentials.json + VARS_JSON: ${{ (vars != null && toJSON(vars)) || '' }} + run: | + # Required by diffcalc-sheet-generator + cp '${{ env.GENERATOR_DIR }}/.env.sample' "${{ env.GENERATOR_ENV }}" + + # Add Google credentials + echo '${{ secrets.DIFFCALC_GOOGLE_CREDENTIALS }}' | base64 -d > "${{ env.GOOGLE_CREDS_FILE }}" + + # Add repository variables + echo "${VARS_JSON}" | jq -c '. | to_entries | .[]' | while read -r line; do + opt=$(jq -r '.key' <<< ${line}) + val=$(jq -r '.value' <<< ${line}) + + if [[ "${opt}" =~ ^DIFFCALC_ ]]; then + optNoPrefix=$(echo "${opt}" | cut -d '_' -f2-) + sed -i "s;^${optNoPrefix}=.*$;${optNoPrefix}=${val};" "${{ env.GENERATOR_ENV }}" + fi + done + + - name: Add HEAD environment + run: | + sed -i "s;^OSU_A=.*$;OSU_A=${{ inputs.head-sha }};" "${{ env.GENERATOR_ENV }}" + + - name: Add pull-request environment + if: ${{ inputs.pr-url != '' }} + run: | + sed -i "s;^OSU_B=.*$;OSU_B=${{ inputs.pr-url }};" "${{ env.GENERATOR_ENV }}" + + - name: Add comment environment + if: ${{ inputs.pr-text != '' }} + env: + PR_TEXT: ${{ inputs.pr-text }} + run: | + # Add comment environment + echo "${PR_TEXT}" | sed -r 's/\r$//' | grep -E '^\w+=' | while read -r line; do + opt=$(echo "${line}" | cut -d '=' -f1) + sed -i "s;^${opt}=.*$;${line};" "${{ env.GENERATOR_ENV }}" + done + + - name: Add dispatch environment + if: ${{ inputs.dispatch-inputs != '' }} + env: + DISPATCH_INPUTS_JSON: ${{ inputs.dispatch-inputs }} + run: | + function get_input() { + echo "${DISPATCH_INPUTS_JSON}" | jq -r ".\"$1\"" + } + + osu_a=$(get_input 'osu-a') + osu_b=$(get_input 'osu-b') + ruleset=$(get_input 'ruleset') + generators=$(get_input 'generators') + difficulty_calculator_a=$(get_input 'difficulty-calculator-a') + difficulty_calculator_b=$(get_input 'difficulty-calculator-b') + score_processor_a=$(get_input 'score-processor-a') + score_processor_b=$(get_input 'score-processor-b') + converts=$(get_input 'converts') + ranked_only=$(get_input 'ranked-only') + + sed -i "s;^OSU_B=.*$;OSU_B=${osu_b};" "${{ env.GENERATOR_ENV }}" + sed -i "s/^RULESET=.*$/RULESET=${ruleset}/" "${{ env.GENERATOR_ENV }}" + sed -i "s/^GENERATORS=.*$/GENERATORS=${generators}/" "${{ env.GENERATOR_ENV }}" + + if [[ "${osu_a}" != 'latest' ]]; then + sed -i "s;^OSU_A=.*$;OSU_A=${osu_a};" "${{ env.GENERATOR_ENV }}" + fi + + if [[ "${difficulty_calculator_a}" != 'latest' ]]; then + sed -i "s;^DIFFICULTY_CALCULATOR_A=.*$;DIFFICULTY_CALCULATOR_A=${difficulty_calculator_a};" "${{ env.GENERATOR_ENV }}" + fi + + if [[ "${difficulty_calculator_b}" != 'latest' ]]; then + sed -i "s;^DIFFICULTY_CALCULATOR_B=.*$;DIFFICULTY_CALCULATOR_B=${difficulty_calculator_b};" "${{ env.GENERATOR_ENV }}" + fi + + if [[ "${score_processor_a}" != 'latest' ]]; then + sed -i "s;^SCORE_PROCESSOR_A=.*$;SCORE_PROCESSOR_A=${score_processor_a};" "${{ env.GENERATOR_ENV }}" + fi + + if [[ "${score_processor_b}" != 'latest' ]]; then + sed -i "s;^SCORE_PROCESSOR_B=.*$;SCORE_PROCESSOR_B=${score_processor_b};" "${{ env.GENERATOR_ENV }}" + fi + + if [[ "${converts}" == 'true' ]]; then + sed -i 's/^NO_CONVERTS=.*$/NO_CONVERTS=0/' "${{ env.GENERATOR_ENV }}" + else + sed -i 's/^NO_CONVERTS=.*$/NO_CONVERTS=1/' "${{ env.GENERATOR_ENV }}" + fi + + if [[ "${ranked_only}" == 'true' ]]; then + sed -i 's/^RANKED_ONLY=.*$/RANKED_ONLY=1/' "${{ env.GENERATOR_ENV }}" + else + sed -i 's/^RANKED_ONLY=.*$/RANKED_ONLY=0/' "${{ env.GENERATOR_ENV }}" + fi + + scores: + name: Setup scores + needs: environment + runs-on: self-hosted + steps: + - name: Query latest data + id: query + run: | + ruleset=$(cat ${{ env.GENERATOR_ENV }} | grep -E '^RULESET=' | cut -d '=' -f2-) + performance_data_name=$(curl -s "https://data.ppy.sh/" | grep "performance_${ruleset}_top_1000\b" | tail -1 | awk -F "'" '{print $2}' | sed 's/\.tar\.bz2//g') + + echo "TARGET_DIR=${{ env.GENERATOR_DIR }}/sql/${ruleset}" >> "${GITHUB_OUTPUT}" + echo "DATA_NAME=${performance_data_name}" >> "${GITHUB_OUTPUT}" + echo "DATA_PKG=${performance_data_name}.tar.bz2" >> "${GITHUB_OUTPUT}" + + - name: Restore cache + id: restore-cache + uses: maxnowack/local-cache@720e69c948191660a90aa1cf6a42fc4d2dacdf30 # v2 + with: + path: ${{ steps.query.outputs.DATA_PKG }} + key: ${{ steps.query.outputs.DATA_NAME }} + + - name: Download + if: steps.restore-cache.outputs.cache-hit != 'true' + run: | + wget -q -O "${{ steps.query.outputs.DATA_PKG }}" "https://data.ppy.sh/${{ steps.query.outputs.DATA_PKG }}" + + - name: Extract + run: | + tar -I lbzip2 -xf "${{ steps.query.outputs.DATA_PKG }}" + rm -r "${{ steps.query.outputs.TARGET_DIR }}" + mv "${{ steps.query.outputs.DATA_NAME }}" "${{ steps.query.outputs.TARGET_DIR }}" + + beatmaps: + name: Setup beatmaps + needs: environment + runs-on: self-hosted + steps: + - name: Query latest data + id: query + run: | + beatmaps_data_name=$(curl -s "https://data.ppy.sh/" | grep "osu_files" | tail -1 | awk -F "'" '{print $2}' | sed 's/\.tar\.bz2//g') + + echo "TARGET_DIR=${{ env.GENERATOR_DIR }}/beatmaps" >> "${GITHUB_OUTPUT}" + echo "DATA_NAME=${beatmaps_data_name}" >> "${GITHUB_OUTPUT}" + echo "DATA_PKG=${beatmaps_data_name}.tar.bz2" >> "${GITHUB_OUTPUT}" + + - name: Restore cache + id: restore-cache + uses: maxnowack/local-cache@720e69c948191660a90aa1cf6a42fc4d2dacdf30 # v2 + with: + path: ${{ steps.query.outputs.DATA_PKG }} + key: ${{ steps.query.outputs.DATA_NAME }} + + - name: Download + if: steps.restore-cache.outputs.cache-hit != 'true' + run: | + wget -q -O "${{ steps.query.outputs.DATA_PKG }}" "https://data.ppy.sh/${{ steps.query.outputs.DATA_PKG }}" + + - name: Extract + run: | + tar -I lbzip2 -xf "${{ steps.query.outputs.DATA_PKG }}" + rm -r "${{ steps.query.outputs.TARGET_DIR }}" + mv "${{ steps.query.outputs.DATA_NAME }}" "${{ steps.query.outputs.TARGET_DIR }}" + + generator: + name: Run generator + needs: [ environment, scores, beatmaps ] + runs-on: self-hosted + timeout-minutes: 720 + outputs: + target: ${{ steps.run.outputs.target }} + sheet: ${{ steps.run.outputs.sheet }} + steps: + - name: Run + id: run + run: | + # Add the GitHub token. This needs to be done here because it's unique per-job. + sed -i 's/^GH_TOKEN=.*$/GH_TOKEN=${{ github.token }}/' "${{ env.GENERATOR_ENV }}" + + cd "${{ env.GENERATOR_DIR }}" + + docker compose up --build --detach + docker compose logs --follow & + docker compose wait generator + + link=$(docker compose logs --tail 10 generator | grep 'http' | sed -E 's/^.*(http.*)$/\1/') + target=$(cat "${{ env.GENERATOR_ENV }}" | grep -E '^OSU_B=' | cut -d '=' -f2-) + + echo "target=${target}" >> "${GITHUB_OUTPUT}" + echo "sheet=${link}" >> "${GITHUB_OUTPUT}" + + - name: Shutdown + if: ${{ always() }} + run: | + cd "${{ env.GENERATOR_DIR }}" + docker compose down --volumes + + cleanup: + name: Cleanup + needs: [ environment, scores, beatmaps, generator ] + runs-on: self-hosted + if: ${{ always() }} + steps: + - name: Cleanup + run: | + rm -rf "${{ needs.directory.outputs.GENERATOR_DIR }}" diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml index 15fbcf38e4..d494bbcb48 100644 --- a/.github/workflows/diffcalc.yml +++ b/.github/workflows/diffcalc.yml @@ -104,26 +104,6 @@ env: EXECUTION_ID: execution-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} jobs: - master-environment: - name: Save master environment - runs-on: ubuntu-latest - if: ${{ github.event_name == 'workflow_dispatch' || contains(github.event.comment.body, '!diffcalc') }} - outputs: - HEAD: ${{ steps.get-head.outputs.HEAD }} - steps: - - name: Checkout osu - uses: actions/checkout@v4 - with: - ref: master - sparse-checkout: | - README.md - - - name: Get HEAD ref - id: get-head - run: | - ref=$(git log -1 --format='%H') - echo "HEAD=https://github.com/${{ github.repository }}/commit/${ref}" >> "${GITHUB_OUTPUT}" - check-permissions: name: Check permissions runs-on: ubuntu-latest @@ -139,9 +119,23 @@ jobs: done exit 1 + run-diffcalc: + name: Run spreadsheet generator + needs: check-permissions + uses: ./.github/workflows/_diffcalc_processor.yml + with: + # Can't reference env... Why GitHub, WHY? + id: execution-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} + head-sha: https://github.com/${{ github.repository }}/commit/${{ github.event.pull_request.head.sha || github.sha }} + pr-url: ${{ github.event.issue.pull_request.html_url || '' }} + pr-text: ${{ github.event.comment.body || '' }} + dispatch-inputs: ${{ toJSON(inputs) }} + secrets: + DIFFCALC_GOOGLE_CREDENTIALS: ${{ secrets.DIFFCALC_GOOGLE_CREDENTIALS }} + create-comment: name: Create PR comment - needs: [ master-environment, check-permissions ] + needs: check-permissions runs-on: ubuntu-latest if: ${{ github.event_name == 'issue_comment' && github.event.issue.pull_request }} steps: @@ -154,249 +148,34 @@ jobs: *This comment will update on completion* - directory: - name: Prepare directory - needs: check-permissions - runs-on: self-hosted - outputs: - GENERATOR_DIR: ${{ steps.set-outputs.outputs.GENERATOR_DIR }} - GENERATOR_ENV: ${{ steps.set-outputs.outputs.GENERATOR_ENV }} - GOOGLE_CREDS_FILE: ${{ steps.set-outputs.outputs.GOOGLE_CREDS_FILE }} - steps: - - name: Checkout diffcalc-sheet-generator - uses: actions/checkout@v4 - with: - path: ${{ env.EXECUTION_ID }} - repository: 'smoogipoo/diffcalc-sheet-generator' - - - name: Set outputs - id: set-outputs - run: | - echo "GENERATOR_DIR=${{ github.workspace }}/${{ env.EXECUTION_ID }}" >> "${GITHUB_OUTPUT}" - echo "GENERATOR_ENV=${{ github.workspace }}/${{ env.EXECUTION_ID }}/.env" >> "${GITHUB_OUTPUT}" - echo "GOOGLE_CREDS_FILE=${{ github.workspace }}/${{ env.EXECUTION_ID }}/google-credentials.json" >> "${GITHUB_OUTPUT}" - - environment: - name: Setup environment - needs: [ master-environment, directory ] - runs-on: self-hosted - env: - VARS_JSON: ${{ toJSON(vars) }} - steps: - - name: Add base environment - run: | - # Required by diffcalc-sheet-generator - cp '${{ needs.directory.outputs.GENERATOR_DIR }}/.env.sample' "${{ needs.directory.outputs.GENERATOR_ENV }}" - - # Add Google credentials - echo '${{ secrets.DIFFCALC_GOOGLE_CREDENTIALS }}' | base64 -d > "${{ needs.directory.outputs.GOOGLE_CREDS_FILE }}" - - # Add repository variables - echo "${VARS_JSON}" | jq -c '. | to_entries | .[]' | while read -r line; do - opt=$(jq -r '.key' <<< ${line}) - val=$(jq -r '.value' <<< ${line}) - - if [[ "${opt}" =~ ^DIFFCALC_ ]]; then - optNoPrefix=$(echo "${opt}" | cut -d '_' -f2-) - sed -i "s;^${optNoPrefix}=.*$;${optNoPrefix}=${val};" "${{ needs.directory.outputs.GENERATOR_ENV }}" - fi - done - - - name: Add master environment - run: | - sed -i "s;^OSU_A=.*$;OSU_A=${{ needs.master-environment.outputs.HEAD }};" "${{ needs.directory.outputs.GENERATOR_ENV }}" - - - name: Add pull-request environment - if: ${{ github.event_name == 'issue_comment' && github.event.issue.pull_request }} - run: | - sed -i "s;^OSU_B=.*$;OSU_B=${{ github.event.issue.pull_request.html_url }};" "${{ needs.directory.outputs.GENERATOR_ENV }}" - - - name: Add comment environment - if: ${{ github.event_name == 'issue_comment' }} - env: - COMMENT_BODY: ${{ github.event.comment.body }} - run: | - # Add comment environment - echo "$COMMENT_BODY" | sed -r 's/\r$//' | grep -E '^\w+=' | while read -r line; do - opt=$(echo "${line}" | cut -d '=' -f1) - sed -i "s;^${opt}=.*$;${line};" "${{ needs.directory.outputs.GENERATOR_ENV }}" - done - - - name: Add dispatch environment - if: ${{ github.event_name == 'workflow_dispatch' }} - run: | - sed -i 's;^OSU_B=.*$;OSU_B=${{ inputs.osu-b }};' "${{ needs.directory.outputs.GENERATOR_ENV }}" - sed -i 's/^RULESET=.*$/RULESET=${{ inputs.ruleset }}/' "${{ needs.directory.outputs.GENERATOR_ENV }}" - sed -i 's/^GENERATORS=.*$/GENERATORS=${{ inputs.generators }}/' "${{ needs.directory.outputs.GENERATOR_ENV }}" - - if [[ '${{ inputs.osu-a }}' != 'latest' ]]; then - sed -i 's;^OSU_A=.*$;OSU_A=${{ inputs.osu-a }};' "${{ needs.directory.outputs.GENERATOR_ENV }}" - fi - - if [[ '${{ inputs.difficulty-calculator-a }}' != 'latest' ]]; then - sed -i 's;^DIFFICULTY_CALCULATOR_A=.*$;DIFFICULTY_CALCULATOR_A=${{ inputs.difficulty-calculator-a }};' "${{ needs.directory.outputs.GENERATOR_ENV }}" - fi - - if [[ '${{ inputs.difficulty-calculator-b }}' != 'latest' ]]; then - sed -i 's;^DIFFICULTY_CALCULATOR_B=.*$;DIFFICULTY_CALCULATOR_B=${{ inputs.difficulty-calculator-b }};' "${{ needs.directory.outputs.GENERATOR_ENV }}" - fi - - if [[ '${{ inputs.score-processor-a }}' != 'latest' ]]; then - sed -i 's;^SCORE_PROCESSOR_A=.*$;SCORE_PROCESSOR_A=${{ inputs.score-processor-a }};' "${{ needs.directory.outputs.GENERATOR_ENV }}" - fi - - if [[ '${{ inputs.score-processor-b }}' != 'latest' ]]; then - sed -i 's;^SCORE_PROCESSOR_B=.*$;SCORE_PROCESSOR_B=${{ inputs.score-processor-b }};' "${{ needs.directory.outputs.GENERATOR_ENV }}" - fi - - if [[ '${{ inputs.converts }}' == 'true' ]]; then - sed -i 's/^NO_CONVERTS=.*$/NO_CONVERTS=0/' "${{ needs.directory.outputs.GENERATOR_ENV }}" - else - sed -i 's/^NO_CONVERTS=.*$/NO_CONVERTS=1/' "${{ needs.directory.outputs.GENERATOR_ENV }}" - fi - - if [[ '${{ inputs.ranked-only }}' == 'true' ]]; then - sed -i 's/^RANKED_ONLY=.*$/RANKED_ONLY=1/' "${{ needs.directory.outputs.GENERATOR_ENV }}" - else - sed -i 's/^RANKED_ONLY=.*$/RANKED_ONLY=0/' "${{ needs.directory.outputs.GENERATOR_ENV }}" - fi - - scores: - name: Setup scores - needs: [ directory, environment ] - runs-on: self-hosted - steps: - - name: Query latest data - id: query - run: | - ruleset=$(cat ${{ needs.directory.outputs.GENERATOR_ENV }} | grep -E '^RULESET=' | cut -d '=' -f2-) - performance_data_name=$(curl -s "https://data.ppy.sh/" | grep "performance_${ruleset}_top_1000\b" | tail -1 | awk -F "'" '{print $2}' | sed 's/\.tar\.bz2//g') - - echo "TARGET_DIR=${{ needs.directory.outputs.GENERATOR_DIR }}/sql/${ruleset}" >> "${GITHUB_OUTPUT}" - echo "DATA_NAME=${performance_data_name}" >> "${GITHUB_OUTPUT}" - echo "DATA_PKG=${performance_data_name}.tar.bz2" >> "${GITHUB_OUTPUT}" - - - name: Restore cache - id: restore-cache - uses: maxnowack/local-cache@720e69c948191660a90aa1cf6a42fc4d2dacdf30 # v2 - with: - path: ${{ steps.query.outputs.DATA_PKG }} - key: ${{ steps.query.outputs.DATA_NAME }} - - - name: Download - if: steps.restore-cache.outputs.cache-hit != 'true' - run: | - wget -q -O "${{ steps.query.outputs.DATA_PKG }}" "https://data.ppy.sh/${{ steps.query.outputs.DATA_PKG }}" - - - name: Extract - run: | - tar -I lbzip2 -xf "${{ steps.query.outputs.DATA_PKG }}" - rm -r "${{ steps.query.outputs.TARGET_DIR }}" - mv "${{ steps.query.outputs.DATA_NAME }}" "${{ steps.query.outputs.TARGET_DIR }}" - - beatmaps: - name: Setup beatmaps - needs: directory - runs-on: self-hosted - steps: - - name: Query latest data - id: query - run: | - beatmaps_data_name=$(curl -s "https://data.ppy.sh/" | grep "osu_files" | tail -1 | awk -F "'" '{print $2}' | sed 's/\.tar\.bz2//g') - - echo "TARGET_DIR=${{ needs.directory.outputs.GENERATOR_DIR }}/beatmaps" >> "${GITHUB_OUTPUT}" - echo "DATA_NAME=${beatmaps_data_name}" >> "${GITHUB_OUTPUT}" - echo "DATA_PKG=${beatmaps_data_name}.tar.bz2" >> "${GITHUB_OUTPUT}" - - - name: Restore cache - id: restore-cache - uses: maxnowack/local-cache@720e69c948191660a90aa1cf6a42fc4d2dacdf30 # v2 - with: - path: ${{ steps.query.outputs.DATA_PKG }} - key: ${{ steps.query.outputs.DATA_NAME }} - - - name: Download - if: steps.restore-cache.outputs.cache-hit != 'true' - run: | - wget -q -O "${{ steps.query.outputs.DATA_PKG }}" "https://data.ppy.sh/${{ steps.query.outputs.DATA_PKG }}" - - - name: Extract - run: | - tar -I lbzip2 -xf "${{ steps.query.outputs.DATA_PKG }}" - rm -r "${{ steps.query.outputs.TARGET_DIR }}" - mv "${{ steps.query.outputs.DATA_NAME }}" "${{ steps.query.outputs.TARGET_DIR }}" - - generator: - name: Run generator - needs: [ directory, environment, scores, beatmaps ] - runs-on: self-hosted - timeout-minutes: 720 - outputs: - TARGET: ${{ steps.run.outputs.TARGET }} - SPREADSHEET_LINK: ${{ steps.run.outputs.SPREADSHEET_LINK }} - steps: - - name: Run - id: run - run: | - # Add the GitHub token. This needs to be done here because it's unique per-job. - sed -i 's/^GH_TOKEN=.*$/GH_TOKEN=${{ github.token }}/' "${{ needs.directory.outputs.GENERATOR_ENV }}" - - cd "${{ needs.directory.outputs.GENERATOR_DIR }}" - - docker compose up --build --detach - docker compose logs --follow & - docker compose wait generator - - link=$(docker compose logs --tail 10 generator | grep 'http' | sed -E 's/^.*(http.*)$/\1/') - target=$(cat "${{ needs.directory.outputs.GENERATOR_ENV }}" | grep -E '^OSU_B=' | cut -d '=' -f2-) - - echo "TARGET=${target}" >> "${GITHUB_OUTPUT}" - echo "SPREADSHEET_LINK=${link}" >> "${GITHUB_OUTPUT}" - - - name: Shutdown - if: ${{ always() }} - run: | - cd "${{ needs.directory.outputs.GENERATOR_DIR }}" - docker compose down --volumes - output-cli: - name: Output info - needs: generator + name: Info + needs: run-diffcalc runs-on: ubuntu-latest steps: - name: Output info run: | - echo "Target: ${{ needs.generator.outputs.TARGET }}" - echo "Spreadsheet: ${{ needs.generator.outputs.SPREADSHEET_LINK }}" - - cleanup: - name: Cleanup - needs: [ directory, generator ] - if: ${{ always() && needs.directory.result == 'success' }} - runs-on: self-hosted - steps: - - name: Cleanup - run: | - rm -rf "${{ needs.directory.outputs.GENERATOR_DIR }}" + echo "Target: ${{ needs.run-diffcalc.outputs.target }}" + echo "Spreadsheet: ${{ needs.run-diffcalc.outputs.sheet }}" update-comment: name: Update PR comment - needs: [ create-comment, generator ] + needs: [ create-comment, run-diffcalc ] runs-on: ubuntu-latest if: ${{ always() && needs.create-comment.result == 'success' }} steps: - name: Update comment on success - if: ${{ needs.generator.result == 'success' }} + if: ${{ needs.run-diffcalc.result == 'success' }} uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0 with: comment_tag: ${{ env.EXECUTION_ID }} mode: recreate message: | - Target: ${{ needs.generator.outputs.TARGET }} - Spreadsheet: ${{ needs.generator.outputs.SPREADSHEET_LINK }} + Target: ${{ needs.run-diffcalc.outputs.target }} + Spreadsheet: ${{ needs.run-diffcalc.outputs.sheet }} - name: Update comment on failure - if: ${{ needs.generator.result == 'failure' }} + if: ${{ needs.run-diffcalc.result == 'failure' }} uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0 with: comment_tag: ${{ env.EXECUTION_ID }} @@ -405,7 +184,7 @@ jobs: Difficulty calculation failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - name: Update comment on cancellation - if: ${{ needs.generator.result == 'cancelled' }} + if: ${{ needs.run-diffcalc.result == 'cancelled' }} uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0 with: comment_tag: ${{ env.EXECUTION_ID }} From 5e8df623d444244d6ab2603a8a66ccf234169411 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 9 Nov 2024 23:49:00 +0900 Subject: [PATCH 779/924] Rename workflow --- .github/workflows/_diffcalc_processor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_diffcalc_processor.yml b/.github/workflows/_diffcalc_processor.yml index 74ce226240..38ee0c45ea 100644 --- a/.github/workflows/_diffcalc_processor.yml +++ b/.github/workflows/_diffcalc_processor.yml @@ -1,4 +1,4 @@ -name: "🔒Run diffcalc runner" +name: "🔒diffcalc (do not use)" on: workflow_call: From 9acfb3c9008513fe094ed7b9ecdd2e06f7fcf1e6 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 9 Nov 2024 16:45:18 -0800 Subject: [PATCH 780/924] Fix break overlay grades not using localised string --- osu.Game/Screens/Play/Break/BreakInfoLine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Break/BreakInfoLine.cs b/osu.Game/Screens/Play/Break/BreakInfoLine.cs index df71767f82..79b417732a 100644 --- a/osu.Game/Screens/Play/Break/BreakInfoLine.cs +++ b/osu.Game/Screens/Play/Break/BreakInfoLine.cs @@ -63,7 +63,7 @@ namespace osu.Game.Screens.Play.Break protected virtual LocalisableString Format(T count) { if (count is Enum countEnum) - return countEnum.GetDescription(); + return countEnum.GetLocalisableDescription(); return count.ToString() ?? string.Empty; } From 27670542867a5f5a8157934f4c786d5630cee906 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 11 Nov 2024 12:47:55 +0900 Subject: [PATCH 781/924] Set `-euo pipefail` in diffcalc workflows --- .github/workflows/_diffcalc_processor.yml | 4 ++++ .github/workflows/diffcalc.yml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/_diffcalc_processor.yml b/.github/workflows/_diffcalc_processor.yml index 38ee0c45ea..e68449ea68 100644 --- a/.github/workflows/_diffcalc_processor.yml +++ b/.github/workflows/_diffcalc_processor.yml @@ -32,6 +32,10 @@ env: GENERATOR_DIR: ${{ github.workspace }}/${{ inputs.id }} GENERATOR_ENV: ${{ github.workspace }}/${{ inputs.id }}/.env +defaults: + run: + shell: bash -euo pipefail {0} + jobs: environment: name: Setup environment diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml index d494bbcb48..8b86650c71 100644 --- a/.github/workflows/diffcalc.yml +++ b/.github/workflows/diffcalc.yml @@ -103,6 +103,10 @@ permissions: env: EXECUTION_ID: execution-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} +defaults: + run: + shell: bash -euo pipefail {0} + jobs: check-permissions: name: Check permissions From a3b8c4d127abea98c7040dbe9af01f07b49d7a26 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 11 Nov 2024 12:48:39 +0900 Subject: [PATCH 782/924] Fix wrong cleaned up directory --- .github/workflows/_diffcalc_processor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_diffcalc_processor.yml b/.github/workflows/_diffcalc_processor.yml index e68449ea68..b193d82317 100644 --- a/.github/workflows/_diffcalc_processor.yml +++ b/.github/workflows/_diffcalc_processor.yml @@ -251,4 +251,4 @@ jobs: steps: - name: Cleanup run: | - rm -rf "${{ needs.directory.outputs.GENERATOR_DIR }}" + rm -rf "${{ env.GENERATOR_DIR }}" From c1686fb68718988e28ec89006132c96c5a0f83d2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 11 Nov 2024 13:02:26 +0900 Subject: [PATCH 783/924] Don't fail grep if no matches --- .github/workflows/_diffcalc_processor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_diffcalc_processor.yml b/.github/workflows/_diffcalc_processor.yml index b193d82317..e08534fbff 100644 --- a/.github/workflows/_diffcalc_processor.yml +++ b/.github/workflows/_diffcalc_processor.yml @@ -84,7 +84,7 @@ jobs: PR_TEXT: ${{ inputs.pr-text }} run: | # Add comment environment - echo "${PR_TEXT}" | sed -r 's/\r$//' | grep -E '^\w+=' | while read -r line; do + echo "${PR_TEXT}" | sed -r 's/\r$//' | { grep -E '^\w+=' || true; } | while read -r line; do opt=$(echo "${line}" | cut -d '=' -f1) sed -i "s;^${opt}=.*$;${line};" "${{ env.GENERATOR_ENV }}" done From 394ff88a629ee6b1e63da54032c8b514c7e4d5a3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 11 Nov 2024 13:11:21 +0900 Subject: [PATCH 784/924] Fix empty JSON sent on non-`workflow_dispatch` events --- .github/workflows/diffcalc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml index 8b86650c71..4297a88e89 100644 --- a/.github/workflows/diffcalc.yml +++ b/.github/workflows/diffcalc.yml @@ -133,7 +133,7 @@ jobs: head-sha: https://github.com/${{ github.repository }}/commit/${{ github.event.pull_request.head.sha || github.sha }} pr-url: ${{ github.event.issue.pull_request.html_url || '' }} pr-text: ${{ github.event.comment.body || '' }} - dispatch-inputs: ${{ toJSON(inputs) }} + dispatch-inputs: ${{ (github.event.type == 'workflow_dispatch' && toJSON(inputs)) || '' }} secrets: DIFFCALC_GOOGLE_CREDENTIALS: ${{ secrets.DIFFCALC_GOOGLE_CREDENTIALS }} From 0b570c4e151f047ac66e5ee73a5e5a48247baf29 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 11 Nov 2024 13:40:17 +0900 Subject: [PATCH 785/924] Enforce concurrency by using single job I've yet again re-confirmed by doubts about using concurrency groups. It's just not flexible enough. In this case, it cancels any _future_ jobs. --- .github/workflows/_diffcalc_processor.yml | 92 ++++++++--------------- 1 file changed, 33 insertions(+), 59 deletions(-) diff --git a/.github/workflows/_diffcalc_processor.yml b/.github/workflows/_diffcalc_processor.yml index e08534fbff..ac2c1b7e3f 100644 --- a/.github/workflows/_diffcalc_processor.yml +++ b/.github/workflows/_diffcalc_processor.yml @@ -24,10 +24,6 @@ on: DIFFCALC_GOOGLE_CREDENTIALS: required: true -concurrency: - group: ${{ github.workflow }} - cancel-in-progress: false - env: GENERATOR_DIR: ${{ github.workspace }}/${{ inputs.id }} GENERATOR_ENV: ${{ github.workspace }}/${{ inputs.id }}/.env @@ -37,9 +33,15 @@ defaults: shell: bash -euo pipefail {0} jobs: - environment: + generator: name: Setup environment runs-on: self-hosted + timeout-minutes: 720 + + outputs: + target: ${{ steps.run.outputs.target }} + sheet: ${{ steps.run.outputs.sheet }} + steps: - name: Checkout diffcalc-sheet-generator uses: actions/checkout@v4 @@ -145,13 +147,8 @@ jobs: sed -i 's/^RANKED_ONLY=.*$/RANKED_ONLY=0/' "${{ env.GENERATOR_ENV }}" fi - scores: - name: Setup scores - needs: environment - runs-on: self-hosted - steps: - - name: Query latest data - id: query + - name: Query latest scores + id: query-scores run: | ruleset=$(cat ${{ env.GENERATOR_ENV }} | grep -E '^RULESET=' | cut -d '=' -f2-) performance_data_name=$(curl -s "https://data.ppy.sh/" | grep "performance_${ruleset}_top_1000\b" | tail -1 | awk -F "'" '{print $2}' | sed 's/\.tar\.bz2//g') @@ -160,31 +157,26 @@ jobs: echo "DATA_NAME=${performance_data_name}" >> "${GITHUB_OUTPUT}" echo "DATA_PKG=${performance_data_name}.tar.bz2" >> "${GITHUB_OUTPUT}" - - name: Restore cache - id: restore-cache + - name: Restore score cache + id: restore-score-cache uses: maxnowack/local-cache@720e69c948191660a90aa1cf6a42fc4d2dacdf30 # v2 with: - path: ${{ steps.query.outputs.DATA_PKG }} - key: ${{ steps.query.outputs.DATA_NAME }} + path: ${{ steps.query-scores.outputs.DATA_PKG }} + key: ${{ steps.query-scores.outputs.DATA_NAME }} - - name: Download - if: steps.restore-cache.outputs.cache-hit != 'true' + - name: Download scores + if: steps.restore-score-cache.outputs.cache-hit != 'true' run: | - wget -q -O "${{ steps.query.outputs.DATA_PKG }}" "https://data.ppy.sh/${{ steps.query.outputs.DATA_PKG }}" + wget -q -O "${{ steps.query-scores.outputs.DATA_PKG }}" "https://data.ppy.sh/${{ steps.query-scores.outputs.DATA_PKG }}" - - name: Extract + - name: Extract scores run: | - tar -I lbzip2 -xf "${{ steps.query.outputs.DATA_PKG }}" - rm -r "${{ steps.query.outputs.TARGET_DIR }}" - mv "${{ steps.query.outputs.DATA_NAME }}" "${{ steps.query.outputs.TARGET_DIR }}" + tar -I lbzip2 -xf "${{ steps.query-scores.outputs.DATA_PKG }}" + rm -r "${{ steps.query-scores.outputs.TARGET_DIR }}" + mv "${{ steps.query-scores.outputs.DATA_NAME }}" "${{ steps.query-scores.outputs.TARGET_DIR }}" - beatmaps: - name: Setup beatmaps - needs: environment - runs-on: self-hosted - steps: - - name: Query latest data - id: query + - name: Query latest beatmaps + id: query-beatmaps run: | beatmaps_data_name=$(curl -s "https://data.ppy.sh/" | grep "osu_files" | tail -1 | awk -F "'" '{print $2}' | sed 's/\.tar\.bz2//g') @@ -192,33 +184,24 @@ jobs: echo "DATA_NAME=${beatmaps_data_name}" >> "${GITHUB_OUTPUT}" echo "DATA_PKG=${beatmaps_data_name}.tar.bz2" >> "${GITHUB_OUTPUT}" - - name: Restore cache - id: restore-cache + - name: Restore beatmap cache + id: restore-beatmap-cache uses: maxnowack/local-cache@720e69c948191660a90aa1cf6a42fc4d2dacdf30 # v2 with: - path: ${{ steps.query.outputs.DATA_PKG }} - key: ${{ steps.query.outputs.DATA_NAME }} + path: ${{ steps.query-beatmaps.outputs.DATA_PKG }} + key: ${{ steps.query-beatmaps.outputs.DATA_NAME }} - - name: Download - if: steps.restore-cache.outputs.cache-hit != 'true' + - name: Download beatmap + if: steps.restore-beatmap-cache.outputs.cache-hit != 'true' run: | - wget -q -O "${{ steps.query.outputs.DATA_PKG }}" "https://data.ppy.sh/${{ steps.query.outputs.DATA_PKG }}" + wget -q -O "${{ steps.query-beatmaps.outputs.DATA_PKG }}" "https://data.ppy.sh/${{ steps.query-beatmaps.outputs.DATA_PKG }}" - - name: Extract + - name: Extract beatmap run: | - tar -I lbzip2 -xf "${{ steps.query.outputs.DATA_PKG }}" - rm -r "${{ steps.query.outputs.TARGET_DIR }}" - mv "${{ steps.query.outputs.DATA_NAME }}" "${{ steps.query.outputs.TARGET_DIR }}" + tar -I lbzip2 -xf "${{ steps.query-beatmaps.outputs.DATA_PKG }}" + rm -r "${{ steps.query-beatmaps.outputs.TARGET_DIR }}" + mv "${{ steps.query-beatmaps.outputs.DATA_NAME }}" "${{ steps.query-beatmaps.outputs.TARGET_DIR }}" - generator: - name: Run generator - needs: [ environment, scores, beatmaps ] - runs-on: self-hosted - timeout-minutes: 720 - outputs: - target: ${{ steps.run.outputs.target }} - sheet: ${{ steps.run.outputs.sheet }} - steps: - name: Run id: run run: | @@ -242,13 +225,4 @@ jobs: run: | cd "${{ env.GENERATOR_DIR }}" docker compose down --volumes - - cleanup: - name: Cleanup - needs: [ environment, scores, beatmaps, generator ] - runs-on: self-hosted - if: ${{ always() }} - steps: - - name: Cleanup - run: | rm -rf "${{ env.GENERATOR_DIR }}" From 91d9c0a7e8a09904656daee5e7d41b9c17d9bb6f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 11 Nov 2024 13:44:11 +0900 Subject: [PATCH 786/924] Adjust job name --- .github/workflows/_diffcalc_processor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_diffcalc_processor.yml b/.github/workflows/_diffcalc_processor.yml index ac2c1b7e3f..4e221d0550 100644 --- a/.github/workflows/_diffcalc_processor.yml +++ b/.github/workflows/_diffcalc_processor.yml @@ -34,7 +34,7 @@ defaults: jobs: generator: - name: Setup environment + name: Run runs-on: self-hosted timeout-minutes: 720 From e1d93a7d9c819eb2ae792934c299fbf00139aab3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 11 Nov 2024 15:09:13 +0900 Subject: [PATCH 787/924] Merge implementations of ConvertHitObjectParser Having these be separate implementations sounded awesome at the time, but it only ever led to confusion. There's no practical difference if, for example, catch sees hitobjects with `IHasPosition` instead of `IHasXPosition`. --- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 45 ++----- .../Objects/Legacy/Catch/ConvertHit.cs | 20 --- .../Legacy/Catch/ConvertHitObjectParser.cs | 63 --------- .../Objects/Legacy/Catch/ConvertSlider.cs | 20 --- .../Objects/Legacy/Catch/ConvertSpinner.cs | 19 --- .../Objects/Legacy/ConvertHitCircle.cs | 13 ++ .../Objects/Legacy/ConvertHitObject.cs | 14 +- .../Objects/Legacy/ConvertHitObjectParser.cs | 123 ++++++++++++------ .../ConvertSpinner.cs => ConvertHold.cs} | 11 +- .../Rulesets/Objects/Legacy/ConvertSlider.cs | 12 +- .../Legacy/{Taiko => }/ConvertSpinner.cs | 7 +- .../Objects/Legacy/Mania/ConvertHit.cs | 15 --- .../Legacy/Mania/ConvertHitObjectParser.cs | 58 --------- .../Objects/Legacy/Mania/ConvertHold.cs | 16 --- .../Objects/Legacy/Mania/ConvertSlider.cs | 15 --- .../Rulesets/Objects/Legacy/Osu/ConvertHit.cs | 20 --- .../Legacy/Osu/ConvertHitObjectParser.cs | 64 --------- .../Objects/Legacy/Osu/ConvertSlider.cs | 22 ---- .../Objects/Legacy/Osu/ConvertSpinner.cs | 24 ---- .../Objects/Legacy/Taiko/ConvertHit.cs | 12 -- .../Legacy/Taiko/ConvertHitObjectParser.cs | 51 -------- .../Objects/Legacy/Taiko/ConvertSlider.cs | 12 -- 22 files changed, 134 insertions(+), 522 deletions(-) delete mode 100644 osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs delete mode 100644 osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs delete mode 100644 osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs delete mode 100644 osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs create mode 100644 osu.Game/Rulesets/Objects/Legacy/ConvertHitCircle.cs rename osu.Game/Rulesets/Objects/Legacy/{Mania/ConvertSpinner.cs => ConvertHold.cs} (54%) rename osu.Game/Rulesets/Objects/Legacy/{Taiko => }/ConvertSpinner.cs (70%) delete mode 100644 osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs delete mode 100644 osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs delete mode 100644 osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs delete mode 100644 osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs delete mode 100644 osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs delete mode 100644 osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs delete mode 100644 osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs delete mode 100644 osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs delete mode 100644 osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs delete mode 100644 osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs delete mode 100644 osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index b068c87fbb..3d8c8a6e7a 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -38,8 +38,7 @@ namespace osu.Game.Beatmaps.Formats internal static RulesetStore? RulesetStore; private Beatmap beatmap = null!; - - private ConvertHitObjectParser? parser; + private ConvertHitObjectParser parser = null!; private LegacySampleBank defaultSampleBank; private int defaultSampleVolume = 100; @@ -80,6 +79,7 @@ namespace osu.Game.Beatmaps.Formats { this.beatmap = beatmap; this.beatmap.BeatmapInfo.BeatmapVersion = FormatVersion; + parser = new ConvertHitObjectParser(getOffsetTime(), FormatVersion); applyLegacyDefaults(this.beatmap.BeatmapInfo); @@ -162,7 +162,8 @@ namespace osu.Game.Beatmaps.Formats { if (hitObject is IHasRepeats hasRepeats) { - SampleControlPoint sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.StartTime + CONTROL_POINT_LENIENCY + 1) ?? SampleControlPoint.DEFAULT; + SampleControlPoint sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.StartTime + CONTROL_POINT_LENIENCY + 1) + ?? SampleControlPoint.DEFAULT; hitObject.Samples = hitObject.Samples.Select(o => sampleControlPoint.ApplyTo(o)).ToList(); for (int i = 0; i < hasRepeats.NodeSamples.Count; i++) @@ -175,7 +176,8 @@ namespace osu.Game.Beatmaps.Formats } else { - SampleControlPoint sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.GetEndTime() + CONTROL_POINT_LENIENCY) ?? SampleControlPoint.DEFAULT; + SampleControlPoint sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.GetEndTime() + CONTROL_POINT_LENIENCY) + ?? SampleControlPoint.DEFAULT; hitObject.Samples = hitObject.Samples.Select(o => sampleControlPoint.ApplyTo(o)).ToList(); } } @@ -263,29 +265,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"Mode": - int rulesetID = Parsing.ParseInt(pair.Value); - - beatmap.BeatmapInfo.Ruleset = RulesetStore?.GetRuleset(rulesetID) ?? throw new ArgumentException("Ruleset is not available locally."); - - switch (rulesetID) - { - case 0: - parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(getOffsetTime(), FormatVersion); - break; - - case 1: - parser = new Rulesets.Objects.Legacy.Taiko.ConvertHitObjectParser(getOffsetTime(), FormatVersion); - break; - - case 2: - parser = new Rulesets.Objects.Legacy.Catch.ConvertHitObjectParser(getOffsetTime(), FormatVersion); - break; - - case 3: - parser = new Rulesets.Objects.Legacy.Mania.ConvertHitObjectParser(getOffsetTime(), FormatVersion); - break; - } - + beatmap.BeatmapInfo.Ruleset = RulesetStore?.GetRuleset(Parsing.ParseInt(pair.Value)) ?? throw new ArgumentException("Ruleset is not available locally."); break; case @"LetterboxInBreaks": @@ -617,17 +597,10 @@ namespace osu.Game.Beatmaps.Formats private void handleHitObject(string line) { - // If the ruleset wasn't specified, assume the osu!standard ruleset. - parser ??= new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(getOffsetTime(), FormatVersion); - var obj = parser.Parse(line); + obj.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); - if (obj != null) - { - obj.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); - - beatmap.HitObjects.Add(obj); - } + beatmap.HitObjects.Add(obj); } private int getOffsetTime(int time) => time + (ApplyOffsets ? offset : 0); diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs deleted file mode 100644 index 96c779e79b..0000000000 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.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 osu.Game.Rulesets.Objects.Types; -using osuTK; - -namespace osu.Game.Rulesets.Objects.Legacy.Catch -{ - /// - /// Legacy osu!catch Hit-type, used for parsing Beatmaps. - /// - internal sealed class ConvertHit : ConvertHitObject, IHasPosition - { - public float X => Position.X; - - public float Y => Position.Y; - - public Vector2 Position { get; set; } - } -} diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs deleted file mode 100644 index a5c1a73fa7..0000000000 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using osuTK; -using osu.Game.Audio; -using System.Collections.Generic; - -namespace osu.Game.Rulesets.Objects.Legacy.Catch -{ - /// - /// A HitObjectParser to parse legacy osu!catch Beatmaps. - /// - public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser - { - private ConvertHitObject lastObject; - - public ConvertHitObjectParser(double offset, int formatVersion) - : base(offset, formatVersion) - { - } - - protected override HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset) - { - return lastObject = new ConvertHit - { - Position = position, - NewCombo = FirstObject || lastObject is ConvertSpinner || newCombo, - ComboOffset = newCombo ? comboOffset : 0 - }; - } - - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, - IList> nodeSamples) - { - return lastObject = new ConvertSlider - { - Position = position, - NewCombo = FirstObject || lastObject is ConvertSpinner || newCombo, - ComboOffset = newCombo ? comboOffset : 0, - Path = new SliderPath(controlPoints, length), - NodeSamples = nodeSamples, - RepeatCount = repeatCount - }; - } - - protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) - { - return lastObject = new ConvertSpinner - { - Duration = duration, - NewCombo = newCombo - // Spinners cannot have combo offset. - }; - } - - protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration) - { - return lastObject = null; - } - } -} diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs deleted file mode 100644 index bcf1c7fae2..0000000000 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.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 osu.Game.Rulesets.Objects.Types; -using osuTK; - -namespace osu.Game.Rulesets.Objects.Legacy.Catch -{ - /// - /// Legacy osu!catch Slider-type, used for parsing Beatmaps. - /// - internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasPosition - { - public float X => Position.X; - - public float Y => Position.Y; - - public Vector2 Position { get; set; } - } -} diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs deleted file mode 100644 index 5ef3d51cb3..0000000000 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs +++ /dev/null @@ -1,19 +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.Objects.Types; - -namespace osu.Game.Rulesets.Objects.Legacy.Catch -{ - /// - /// Legacy osu!catch Spinner-type, used for parsing Beatmaps. - /// - internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasXPosition - { - public double EndTime => StartTime + Duration; - - public double Duration { get; set; } - - public float X => 256; // Required for CatchBeatmapConverter - } -} diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitCircle.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitCircle.cs new file mode 100644 index 0000000000..d1852d7032 --- /dev/null +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitCircle.cs @@ -0,0 +1,13 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Rulesets.Objects.Legacy +{ + /// + /// Legacy "HitCircle" hit object type. + /// + /// + /// Only used for parsing beatmaps and not gameplay. + /// + internal sealed class ConvertHitCircle : ConvertHitObject; +} diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs index bb36aab0b3..d1b0d3c6c2 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs @@ -4,18 +4,28 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; +using osuTK; namespace osu.Game.Rulesets.Objects.Legacy { /// - /// A hit object only used for conversion, not actual gameplay. + /// Represents a legacy hit object. /// - internal abstract class ConvertHitObject : HitObject, IHasCombo + /// + /// Only used for parsing beatmaps and not gameplay. + /// + internal abstract class ConvertHitObject : HitObject, IHasCombo, IHasPosition { public bool NewCombo { get; set; } public int ComboOffset { get; set; } + public float X => Position.X; + + public float Y => Position.Y; + + public Vector2 Position { get; set; } + public override Judgement CreateJudgement() => new IgnoreJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 89fd5f7384..646431c071 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osuTK; using osu.Game.Rulesets.Objects.Types; using System; @@ -11,7 +9,6 @@ using System.IO; using osu.Game.Beatmaps.Formats; using osu.Game.Audio; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Legacy; @@ -24,24 +21,32 @@ namespace osu.Game.Rulesets.Objects.Legacy /// /// A HitObjectParser to parse legacy Beatmaps. /// - public abstract class ConvertHitObjectParser : HitObjectParser + public class ConvertHitObjectParser : HitObjectParser { /// /// The offset to apply to all time values. /// - protected readonly double Offset; + private readonly double offset; /// /// The .osu format (beatmap) version. /// - protected readonly int FormatVersion; + private readonly int formatVersion; - protected bool FirstObject { get; private set; } = true; + /// + /// Whether the current hitobject is the first hitobject in the beatmap. + /// + private bool firstObject = true; - protected ConvertHitObjectParser(double offset, int formatVersion) + /// + /// The last parsed hitobject. + /// + private ConvertHitObject? lastObject; + + internal ConvertHitObjectParser(double offset, int formatVersion) { - Offset = offset; - FormatVersion = formatVersion; + this.offset = offset; + this.formatVersion = formatVersion; } public override HitObject Parse(string text) @@ -49,11 +54,11 @@ namespace osu.Game.Rulesets.Objects.Legacy string[] split = text.Split(','); Vector2 pos = - FormatVersion >= LegacyBeatmapEncoder.FIRST_LAZER_VERSION + formatVersion >= LegacyBeatmapEncoder.FIRST_LAZER_VERSION ? new Vector2(Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE)) : new Vector2((int)Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE)); - double startTime = Parsing.ParseDouble(split[2]) + Offset; + double startTime = Parsing.ParseDouble(split[2]) + offset; LegacyHitObjectType type = (LegacyHitObjectType)Parsing.ParseInt(split[3]); @@ -66,11 +71,11 @@ namespace osu.Game.Rulesets.Objects.Legacy var soundType = (LegacyHitSoundType)Parsing.ParseInt(split[4]); var bankInfo = new SampleBankInfo(); - HitObject result = null; + HitObject? result = null; if (type.HasFlag(LegacyHitObjectType.Circle)) { - result = CreateHit(pos, combo, comboOffset); + result = createHitCircle(pos, combo, comboOffset); if (split.Length > 5) readCustomSampleBanks(split[5], bankInfo); @@ -145,13 +150,13 @@ namespace osu.Game.Rulesets.Objects.Legacy for (int i = 0; i < nodes; i++) nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); - result = CreateSlider(pos, combo, comboOffset, convertPathString(split[5], pos), length, repeatCount, nodeSamples); + result = createSlider(pos, combo, comboOffset, convertPathString(split[5], pos), length, repeatCount, nodeSamples); } else if (type.HasFlag(LegacyHitObjectType.Spinner)) { - double duration = Math.Max(0, Parsing.ParseDouble(split[5]) + Offset - startTime); + double duration = Math.Max(0, Parsing.ParseDouble(split[5]) + offset - startTime); - result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, duration); + result = createSpinner(new Vector2(512, 384) / 2, combo, comboOffset, duration); if (split.Length > 6) readCustomSampleBanks(split[6], bankInfo); @@ -169,7 +174,7 @@ namespace osu.Game.Rulesets.Objects.Legacy readCustomSampleBanks(string.Join(':', ss.Skip(1)), bankInfo); } - result = CreateHold(pos, combo, comboOffset, endTime + Offset - startTime); + result = createHold(pos, combo, comboOffset, endTime + offset - startTime); } if (result == null) @@ -180,7 +185,7 @@ namespace osu.Game.Rulesets.Objects.Legacy if (result.Samples.Count == 0) result.Samples = convertSoundType(soundType, bankInfo); - FirstObject = false; + firstObject = false; return result; } @@ -200,10 +205,11 @@ namespace osu.Game.Rulesets.Objects.Legacy if (!Enum.IsDefined(addBank)) addBank = LegacySampleBank.Normal; - string stringBank = bank.ToString().ToLowerInvariant(); + string? stringBank = bank.ToString().ToLowerInvariant(); + string? stringAddBank = addBank.ToString().ToLowerInvariant(); + if (stringBank == @"none") stringBank = null; - string stringAddBank = addBank.ToString().ToLowerInvariant(); if (stringAddBank == @"none") { @@ -357,7 +363,7 @@ namespace osu.Game.Rulesets.Objects.Legacy { int endPointLength = endPoint == null ? 0 : 1; - if (FormatVersion < LegacyBeatmapEncoder.FIRST_LAZER_VERSION) + if (formatVersion < LegacyBeatmapEncoder.FIRST_LAZER_VERSION) { if (vertices.Length + endPointLength != 3) type = PathType.BEZIER; @@ -393,7 +399,7 @@ namespace osu.Game.Rulesets.Objects.Legacy // Legacy CATMULL sliders don't support multiple segments, so adjacent CATMULL segments should be treated as a single one. // Importantly, this is not applied to the first control point, which may duplicate the slider path's position // resulting in a duplicate (0,0) control point in the resultant list. - if (type == PathType.CATMULL && endIndex > 1 && FormatVersion < LegacyBeatmapEncoder.FIRST_LAZER_VERSION) + if (type == PathType.CATMULL && endIndex > 1 && formatVersion < LegacyBeatmapEncoder.FIRST_LAZER_VERSION) continue; // The last control point of each segment is not allowed to start a new implicit segment. @@ -442,7 +448,15 @@ namespace osu.Game.Rulesets.Objects.Legacy /// Whether the hit object creates a new combo. /// When starting a new combo, the offset of the new combo relative to the current one. /// The hit object. - protected abstract HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset); + private HitObject createHitCircle(Vector2 position, bool newCombo, int comboOffset) + { + return lastObject = new ConvertHitCircle + { + Position = position, + NewCombo = firstObject || lastObject is ConvertSpinner || newCombo, + ComboOffset = newCombo ? comboOffset : 0 + }; + } /// /// Creats a legacy Slider-type hit object. @@ -455,8 +469,19 @@ namespace osu.Game.Rulesets.Objects.Legacy /// The slider repeat count. /// The samples to be played when the slider nodes are hit. This includes the head and tail of the slider. /// The hit object. - protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, - IList> nodeSamples); + private HitObject createSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, + IList> nodeSamples) + { + return lastObject = new ConvertSlider + { + Position = position, + NewCombo = firstObject || lastObject is ConvertSpinner || newCombo, + ComboOffset = newCombo ? comboOffset : 0, + Path = new SliderPath(controlPoints, length), + NodeSamples = nodeSamples, + RepeatCount = repeatCount + }; + } /// /// Creates a legacy Spinner-type hit object. @@ -466,7 +491,16 @@ namespace osu.Game.Rulesets.Objects.Legacy /// When starting a new combo, the offset of the new combo relative to the current one. /// The spinner duration. /// The hit object. - protected abstract HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration); + private HitObject createSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) + { + return lastObject = new ConvertSpinner + { + Position = position, + Duration = duration, + NewCombo = newCombo + // Spinners cannot have combo offset. + }; + } /// /// Creates a legacy Hold-type hit object. @@ -475,7 +509,14 @@ namespace osu.Game.Rulesets.Objects.Legacy /// Whether the hit object creates a new combo. /// When starting a new combo, the offset of the new combo relative to the current one. /// The hold duration. - protected abstract HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration); + private HitObject createHold(Vector2 position, bool newCombo, int comboOffset, double duration) + { + return lastObject = new ConvertHold + { + Position = position, + Duration = duration + }; + } private List convertSoundType(LegacyHitSoundType type, SampleBankInfo bankInfo) { @@ -511,21 +552,19 @@ namespace osu.Game.Rulesets.Objects.Legacy /// /// An optional overriding filename which causes all bank/sample specifications to be ignored. /// - public string Filename; + public string? Filename; /// /// The bank identifier to use for the base ("hitnormal") sample. /// Transferred to when appropriate. /// - [CanBeNull] - public string BankForNormal; + public string? BankForNormal; /// /// The bank identifier to use for additions ("hitwhistle", "hitfinish", "hitclap"). /// Transferred to when appropriate. /// - [CanBeNull] - public string BankForAdditions; + public string? BankForAdditions; /// /// Hit sample volume (0-100). @@ -548,8 +587,6 @@ namespace osu.Game.Rulesets.Objects.Legacy public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone(); } -#nullable enable - public class LegacyHitSampleInfo : HitSampleInfo, IEquatable { public readonly int CustomSampleBank; @@ -577,13 +614,14 @@ namespace osu.Game.Rulesets.Objects.Legacy IsLayered = isLayered; } - public sealed override HitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newSuffix = default, Optional newVolume = default, Optional newEditorAutoBank = default) + public sealed override HitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newSuffix = default, Optional newVolume = default, + Optional newEditorAutoBank = default) => With(newName, newBank, newVolume, newEditorAutoBank); - public virtual LegacyHitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newVolume = default, Optional newEditorAutoBank = default, - Optional newCustomSampleBank = default, - Optional newIsLayered = default) - => new LegacyHitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newVolume.GetOr(Volume), newEditorAutoBank.GetOr(EditorAutoBank), newCustomSampleBank.GetOr(CustomSampleBank), newIsLayered.GetOr(IsLayered)); + public virtual LegacyHitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newVolume = default, + Optional newEditorAutoBank = default, Optional newCustomSampleBank = default, Optional newIsLayered = default) + => new LegacyHitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newVolume.GetOr(Volume), newEditorAutoBank.GetOr(EditorAutoBank), newCustomSampleBank.GetOr(CustomSampleBank), + newIsLayered.GetOr(IsLayered)); public bool Equals(LegacyHitSampleInfo? other) // The additions to equality checks here are *required* to ensure that pooling works correctly. @@ -615,9 +653,8 @@ namespace osu.Game.Rulesets.Objects.Legacy Path.ChangeExtension(Filename, null) }; - public sealed override LegacyHitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newVolume = default, Optional newEditorAutoBank = default, - Optional newCustomSampleBank = default, - Optional newIsLayered = default) + public sealed override LegacyHitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newVolume = default, + Optional newEditorAutoBank = default, Optional newCustomSampleBank = default, Optional newIsLayered = default) => new FileHitSampleInfo(Filename, newVolume.GetOr(Volume)); public bool Equals(FileHitSampleInfo? other) diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHold.cs similarity index 54% rename from osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs rename to osu.Game/Rulesets/Objects/Legacy/ConvertHold.cs index c05aaceb9c..d74224892b 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHold.cs @@ -3,17 +3,18 @@ using osu.Game.Rulesets.Objects.Types; -namespace osu.Game.Rulesets.Objects.Legacy.Mania +namespace osu.Game.Rulesets.Objects.Legacy { /// - /// Legacy osu!mania Spinner-type, used for parsing Beatmaps. + /// Legacy "Hold" hit object type. Generally only valid in the mania ruleset. /// - internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasXPosition + /// + /// Only used for parsing beatmaps and not gameplay. + /// + internal sealed class ConvertHold : ConvertHitObject, IHasDuration { public double Duration { get; set; } public double EndTime => StartTime + Duration; - - public float X { get; set; } } } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 683eefa8f4..fee68f2f11 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; using Newtonsoft.Json; @@ -13,7 +11,13 @@ using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Rulesets.Objects.Legacy { - internal abstract class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasSliderVelocity + /// + /// Legacy "Slider" hit object type. + /// + /// + /// Only used for parsing beatmaps and not gameplay. + /// + internal class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasSliderVelocity, IHasGenerateTicks { /// /// Scoring distance with a speed-adjusted beat length of 1 second. @@ -50,6 +54,8 @@ namespace osu.Game.Rulesets.Objects.Legacy set => SliderVelocityMultiplierBindable.Value = value; } + public bool GenerateTicks { get; set; } = true; + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSpinner.cs similarity index 70% rename from osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs rename to osu.Game/Rulesets/Objects/Legacy/ConvertSpinner.cs index 1d5ecb1ef3..59551cd37a 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSpinner.cs @@ -3,11 +3,14 @@ using osu.Game.Rulesets.Objects.Types; -namespace osu.Game.Rulesets.Objects.Legacy.Taiko +namespace osu.Game.Rulesets.Objects.Legacy { /// - /// Legacy osu!taiko Spinner-type, used for parsing Beatmaps. + /// Legacy "Spinner" hit object type. /// + /// + /// Only used for parsing beatmaps and not gameplay. + /// internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration { public double Duration { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs deleted file mode 100644 index 0b69817c13..0000000000 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs +++ /dev/null @@ -1,15 +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.Objects.Types; - -namespace osu.Game.Rulesets.Objects.Legacy.Mania -{ - /// - /// Legacy osu!mania Hit-type, used for parsing Beatmaps. - /// - internal sealed class ConvertHit : ConvertHitObject, IHasXPosition - { - public float X { get; set; } - } -} diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs deleted file mode 100644 index 386eb8d3ee..0000000000 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs +++ /dev/null @@ -1,58 +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.Game.Audio; -using System.Collections.Generic; - -namespace osu.Game.Rulesets.Objects.Legacy.Mania -{ - /// - /// A HitObjectParser to parse legacy osu!mania Beatmaps. - /// - public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser - { - public ConvertHitObjectParser(double offset, int formatVersion) - : base(offset, formatVersion) - { - } - - protected override HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset) - { - return new ConvertHit - { - X = position.X - }; - } - - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, - IList> nodeSamples) - { - return new ConvertSlider - { - X = position.X, - Path = new SliderPath(controlPoints, length), - NodeSamples = nodeSamples, - RepeatCount = repeatCount - }; - } - - protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) - { - return new ConvertSpinner - { - X = position.X, - Duration = duration - }; - } - - protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration) - { - return new ConvertHold - { - X = position.X, - Duration = duration - }; - } - } -} diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs deleted file mode 100644 index 2fa4766c1d..0000000000 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs +++ /dev/null @@ -1,16 +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.Objects.Types; - -namespace osu.Game.Rulesets.Objects.Legacy.Mania -{ - internal sealed class ConvertHold : ConvertHitObject, IHasXPosition, IHasDuration - { - public float X { get; set; } - - public double Duration { get; set; } - - public double EndTime => StartTime + Duration; - } -} diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs deleted file mode 100644 index 84cde5fa95..0000000000 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs +++ /dev/null @@ -1,15 +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.Objects.Types; - -namespace osu.Game.Rulesets.Objects.Legacy.Mania -{ - /// - /// Legacy osu!mania Slider-type, used for parsing Beatmaps. - /// - internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasXPosition - { - public float X { get; set; } - } -} diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs deleted file mode 100644 index b7cd4b0dcc..0000000000 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.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 osu.Game.Rulesets.Objects.Types; -using osuTK; - -namespace osu.Game.Rulesets.Objects.Legacy.Osu -{ - /// - /// Legacy osu! Hit-type, used for parsing Beatmaps. - /// - internal sealed class ConvertHit : ConvertHitObject, IHasPosition - { - public Vector2 Position { get; set; } - - public float X => Position.X; - - public float Y => Position.Y; - } -} diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs deleted file mode 100644 index 43c346b621..0000000000 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.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. - -#nullable disable - -using osuTK; -using System.Collections.Generic; -using osu.Game.Audio; - -namespace osu.Game.Rulesets.Objects.Legacy.Osu -{ - /// - /// A HitObjectParser to parse legacy osu! Beatmaps. - /// - public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser - { - private ConvertHitObject lastObject; - - public ConvertHitObjectParser(double offset, int formatVersion) - : base(offset, formatVersion) - { - } - - protected override HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset) - { - return lastObject = new ConvertHit - { - Position = position, - NewCombo = FirstObject || lastObject is ConvertSpinner || newCombo, - ComboOffset = newCombo ? comboOffset : 0 - }; - } - - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, - IList> nodeSamples) - { - return lastObject = new ConvertSlider - { - Position = position, - NewCombo = FirstObject || lastObject is ConvertSpinner || newCombo, - ComboOffset = newCombo ? comboOffset : 0, - Path = new SliderPath(controlPoints, length), - NodeSamples = nodeSamples, - RepeatCount = repeatCount - }; - } - - protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) - { - return lastObject = new ConvertSpinner - { - Position = position, - Duration = duration, - NewCombo = newCombo - // Spinners cannot have combo offset. - }; - } - - protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration) - { - return lastObject = null; - } - } -} diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs deleted file mode 100644 index 8c37154f95..0000000000 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs +++ /dev/null @@ -1,22 +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.Objects.Types; -using osuTK; - -namespace osu.Game.Rulesets.Objects.Legacy.Osu -{ - /// - /// Legacy osu! Slider-type, used for parsing Beatmaps. - /// - internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasPosition, IHasGenerateTicks - { - public Vector2 Position { get; set; } - - public float X => Position.X; - - public float Y => Position.Y; - - public bool GenerateTicks { get; set; } = true; - } -} diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs deleted file mode 100644 index d6e24b6bbf..0000000000 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs +++ /dev/null @@ -1,24 +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.Objects.Types; -using osuTK; - -namespace osu.Game.Rulesets.Objects.Legacy.Osu -{ - /// - /// Legacy osu! Spinner-type, used for parsing Beatmaps. - /// - internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasPosition - { - public double Duration { get; set; } - - public double EndTime => StartTime + Duration; - - public Vector2 Position { get; set; } - - public float X => Position.X; - - public float Y => Position.Y; - } -} diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs deleted file mode 100644 index cb5178ce48..0000000000 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -namespace osu.Game.Rulesets.Objects.Legacy.Taiko -{ - /// - /// Legacy osu!taiko Hit-type, used for parsing Beatmaps. - /// - internal sealed class ConvertHit : ConvertHitObject - { - } -} diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs deleted file mode 100644 index d62e8cd04c..0000000000 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using osuTK; -using System.Collections.Generic; -using osu.Game.Audio; - -namespace osu.Game.Rulesets.Objects.Legacy.Taiko -{ - /// - /// A HitObjectParser to parse legacy osu!taiko Beatmaps. - /// - public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser - { - public ConvertHitObjectParser(double offset, int formatVersion) - : base(offset, formatVersion) - { - } - - protected override HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset) - { - return new ConvertHit(); - } - - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, - IList> nodeSamples) - { - return new ConvertSlider - { - Path = new SliderPath(controlPoints, length), - NodeSamples = nodeSamples, - RepeatCount = repeatCount - }; - } - - protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) - { - return new ConvertSpinner - { - Duration = duration - }; - } - - protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration) - { - return null; - } - } -} diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs deleted file mode 100644 index 821554f7ee..0000000000 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -namespace osu.Game.Rulesets.Objects.Legacy.Taiko -{ - /// - /// Legacy osu!taiko Slider-type, used for parsing Beatmaps. - /// - internal sealed class ConvertSlider : Legacy.ConvertSlider - { - } -} From 7206e97b7beb637c411a39f0418c50db0f89c427 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 11 Nov 2024 15:23:23 +0900 Subject: [PATCH 788/924] Add `IHasLegacyHitObjectType` to ConvertHitObject --- .../Beatmaps/Legacy/LegacyHitObjectType.cs | 2 +- .../Objects/Legacy/ConvertHitObject.cs | 5 ++++- .../Objects/Legacy/ConvertHitObjectParser.cs | 13 +++++++------ .../Objects/Legacy/IHasLegacyHitObjectType.cs | 18 ++++++++++++++++++ 4 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 osu.Game/Rulesets/Objects/Legacy/IHasLegacyHitObjectType.cs diff --git a/osu.Game/Beatmaps/Legacy/LegacyHitObjectType.cs b/osu.Game/Beatmaps/Legacy/LegacyHitObjectType.cs index 07f170f996..6fab66bf70 100644 --- a/osu.Game/Beatmaps/Legacy/LegacyHitObjectType.cs +++ b/osu.Game/Beatmaps/Legacy/LegacyHitObjectType.cs @@ -6,7 +6,7 @@ using System; namespace osu.Game.Beatmaps.Legacy { [Flags] - internal enum LegacyHitObjectType + public enum LegacyHitObjectType { Circle = 1, Slider = 1 << 1, diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs index d1b0d3c6c2..28683583ee 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; @@ -14,7 +15,7 @@ namespace osu.Game.Rulesets.Objects.Legacy /// /// Only used for parsing beatmaps and not gameplay. /// - internal abstract class ConvertHitObject : HitObject, IHasCombo, IHasPosition + internal abstract class ConvertHitObject : HitObject, IHasCombo, IHasPosition, IHasLegacyHitObjectType { public bool NewCombo { get; set; } @@ -26,6 +27,8 @@ namespace osu.Game.Rulesets.Objects.Legacy public Vector2 Position { get; set; } + public LegacyHitObjectType LegacyType { get; set; } + public override Judgement CreateJudgement() => new IgnoreJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 646431c071..1c933392c0 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Objects.Legacy var soundType = (LegacyHitSoundType)Parsing.ParseInt(split[4]); var bankInfo = new SampleBankInfo(); - HitObject? result = null; + ConvertHitObject? result = null; if (type.HasFlag(LegacyHitObjectType.Circle)) { @@ -181,6 +181,7 @@ namespace osu.Game.Rulesets.Objects.Legacy throw new InvalidDataException($"Unknown hit object type: {split[3]}"); result.StartTime = startTime; + result.LegacyType = type; if (result.Samples.Count == 0) result.Samples = convertSoundType(soundType, bankInfo); @@ -448,7 +449,7 @@ namespace osu.Game.Rulesets.Objects.Legacy /// Whether the hit object creates a new combo. /// When starting a new combo, the offset of the new combo relative to the current one. /// The hit object. - private HitObject createHitCircle(Vector2 position, bool newCombo, int comboOffset) + private ConvertHitObject createHitCircle(Vector2 position, bool newCombo, int comboOffset) { return lastObject = new ConvertHitCircle { @@ -469,8 +470,8 @@ namespace osu.Game.Rulesets.Objects.Legacy /// The slider repeat count. /// The samples to be played when the slider nodes are hit. This includes the head and tail of the slider. /// The hit object. - private HitObject createSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, - IList> nodeSamples) + private ConvertHitObject createSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount, + IList> nodeSamples) { return lastObject = new ConvertSlider { @@ -491,7 +492,7 @@ namespace osu.Game.Rulesets.Objects.Legacy /// When starting a new combo, the offset of the new combo relative to the current one. /// The spinner duration. /// The hit object. - private HitObject createSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) + private ConvertHitObject createSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) { return lastObject = new ConvertSpinner { @@ -509,7 +510,7 @@ namespace osu.Game.Rulesets.Objects.Legacy /// Whether the hit object creates a new combo. /// When starting a new combo, the offset of the new combo relative to the current one. /// The hold duration. - private HitObject createHold(Vector2 position, bool newCombo, int comboOffset, double duration) + private ConvertHitObject createHold(Vector2 position, bool newCombo, int comboOffset, double duration) { return lastObject = new ConvertHold { diff --git a/osu.Game/Rulesets/Objects/Legacy/IHasLegacyHitObjectType.cs b/osu.Game/Rulesets/Objects/Legacy/IHasLegacyHitObjectType.cs new file mode 100644 index 0000000000..71af57700d --- /dev/null +++ b/osu.Game/Rulesets/Objects/Legacy/IHasLegacyHitObjectType.cs @@ -0,0 +1,18 @@ +// 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.Beatmaps.Legacy; + +namespace osu.Game.Rulesets.Objects.Legacy +{ + /// + /// A hit object from a legacy beatmap representation. + /// + public interface IHasLegacyHitObjectType + { + /// + /// The hit object type. + /// + LegacyHitObjectType LegacyType { get; } + } +} From 8c68db0a369bbfafe726accf1ff23ff902d3c6ed Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 11 Nov 2024 15:27:45 +0900 Subject: [PATCH 789/924] Remove unused params --- .../Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 1c933392c0..f8bc0ce112 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -156,7 +156,7 @@ namespace osu.Game.Rulesets.Objects.Legacy { double duration = Math.Max(0, Parsing.ParseDouble(split[5]) + offset - startTime); - result = createSpinner(new Vector2(512, 384) / 2, combo, comboOffset, duration); + result = createSpinner(new Vector2(512, 384) / 2, combo, duration); if (split.Length > 6) readCustomSampleBanks(split[6], bankInfo); @@ -174,7 +174,7 @@ namespace osu.Game.Rulesets.Objects.Legacy readCustomSampleBanks(string.Join(':', ss.Skip(1)), bankInfo); } - result = createHold(pos, combo, comboOffset, endTime + offset - startTime); + result = createHold(pos, endTime + offset - startTime); } if (result == null) @@ -489,10 +489,9 @@ namespace osu.Game.Rulesets.Objects.Legacy /// /// The position of the hit object. /// Whether the hit object creates a new combo. - /// When starting a new combo, the offset of the new combo relative to the current one. /// The spinner duration. /// The hit object. - private ConvertHitObject createSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) + private ConvertHitObject createSpinner(Vector2 position, bool newCombo, double duration) { return lastObject = new ConvertSpinner { @@ -507,10 +506,8 @@ namespace osu.Game.Rulesets.Objects.Legacy /// Creates a legacy Hold-type hit object. /// /// The position of the hit object. - /// Whether the hit object creates a new combo. - /// When starting a new combo, the offset of the new combo relative to the current one. /// The hold duration. - private ConvertHitObject createHold(Vector2 position, bool newCombo, int comboOffset, double duration) + private ConvertHitObject createHold(Vector2 position, double duration) { return lastObject = new ConvertHold { From 06380f91fca4656f96a3c32c82960d3c55aab9bc Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 11 Nov 2024 16:25:05 +0900 Subject: [PATCH 790/924] Update test --- .../SongSelect/TestSceneBeatmapInfoWedge.cs | 38 +++++++------------ 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index fd102da026..d8573b2d03 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -1,12 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -23,7 +20,6 @@ using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Legacy; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Taiko; @@ -35,15 +31,11 @@ namespace osu.Game.Tests.Visual.SongSelect [TestFixture] public partial class TestSceneBeatmapInfoWedge : OsuTestScene { - private RulesetStore rulesets; - private TestBeatmapInfoWedge infoWedge; - private readonly List beatmaps = new List(); + [Resolved] + private RulesetStore rulesets { get; set; } = null!; - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { - this.rulesets = rulesets; - } + private TestBeatmapInfoWedge infoWedge = null!; + private readonly List beatmaps = new List(); protected override void LoadComplete() { @@ -156,7 +148,7 @@ namespace osu.Game.Tests.Visual.SongSelect IBeatmap beatmap = createTestBeatmap(new OsuRuleset().RulesetInfo); beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 60 * 1000 / bpm }); - OsuModDoubleTime doubleTime = null; + OsuModDoubleTime doubleTime = null!; selectBeatmap(beatmap); checkDisplayedBPM($"{bpm}"); @@ -173,7 +165,7 @@ namespace osu.Game.Tests.Visual.SongSelect [TestCase(120, 120.4, null, "120")] [TestCase(120, 120.6, "DT", "180-182 (mostly 180)")] [TestCase(120, 120.4, "DT", "180")] - public void TestVaryingBPM(double commonBpm, double otherBpm, string mod, string expectedDisplay) + public void TestVaryingBPM(double commonBpm, double otherBpm, string? mod, string expectedDisplay) { IBeatmap beatmap = createTestBeatmap(new OsuRuleset().RulesetInfo); beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 60 * 1000 / commonBpm }); @@ -203,7 +195,7 @@ namespace osu.Game.Tests.Visual.SongSelect double drain = beatmap.CalculateDrainLength(); beatmap.BeatmapInfo.Length = drain; - OsuModDoubleTime doubleTime = null; + OsuModDoubleTime doubleTime = null!; selectBeatmap(beatmap); checkDisplayedLength(drain); @@ -221,14 +213,15 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep($"check map drain ({displayedLength})", () => { - var label = infoWedge.DisplayedContent.ChildrenOfType().Single(l => l.Statistic.Name == BeatmapsetsStrings.ShowStatsTotalLength(displayedLength)); + var label = infoWedge.DisplayedContent.ChildrenOfType() + .Single(l => l.Statistic.Name == BeatmapsetsStrings.ShowStatsTotalLength(displayedLength)); return label.Statistic.Content == displayedLength.ToString(); }); } private void setRuleset(RulesetInfo rulesetInfo) { - Container containerBefore = null; + Container? containerBefore = null; AddStep("set ruleset", () => { @@ -242,9 +235,9 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("wait for async load", () => infoWedge.DisplayedContent != containerBefore); } - private void selectBeatmap([CanBeNull] IBeatmap b) + private void selectBeatmap(IBeatmap? b) { - Container containerBefore = null; + Container? containerBefore = null; AddStep($"select {b?.Metadata.Title ?? "null"} beatmap", () => { @@ -307,11 +300,6 @@ namespace osu.Game.Tests.Visual.SongSelect public new WedgeInfoText Info => base.Info; } - private class TestHitObject : ConvertHitObject, IHasPosition - { - public float X => 0; - public float Y => 0; - public Vector2 Position { get; } = Vector2.Zero; - } + private class TestHitObject : ConvertHitObject; } } From 1c3a30a2976a19fb6aa099281919e871cf7c6ca1 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 11 Nov 2024 16:30:26 +0900 Subject: [PATCH 791/924] Fix the other test class too --- .../Visual/SongSelectV2/TestSceneBeatmapInfoWedge.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapInfoWedge.cs index 35bd4ee958..fbbab3a604 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapInfoWedge.cs @@ -14,9 +14,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Legacy; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Select; -using osuTK; namespace osu.Game.Tests.Visual.SongSelectV2 { @@ -209,11 +207,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2 public new WedgeInfoText? Info => base.Info; } - private class TestHitObject : ConvertHitObject, IHasPosition - { - public float X => 0; - public float Y => 0; - public Vector2 Position { get; } = Vector2.Zero; - } + private class TestHitObject : ConvertHitObject; } } From f8ac54d61cf5165c2e8cf3eb33d4edf32e6d16f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Nov 2024 16:59:02 +0900 Subject: [PATCH 792/924] Fix weird local variable typo --- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 44f91b4df1..412e44cbde 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -216,7 +216,7 @@ namespace osu.Game.Screens.Select private int visibleSetsCount; - public BeatmapCarousel(FilterCriteria initialCriterial) + public BeatmapCarousel(FilterCriteria initialCriteria) { root = new CarouselRoot(this); InternalChild = new Container @@ -239,7 +239,7 @@ namespace osu.Game.Screens.Select } }; - activeCriteria = initialCriterial; + activeCriteria = initialCriteria; } [BackgroundDependencyLoader] From e8b69581b727410244faeaed3d4e36079be2352c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Nov 2024 17:49:34 +0900 Subject: [PATCH 793/924] Fix top rank display not showing up on beatmaps with many difficulties --- osu.Game/Screens/Select/BeatmapCarousel.cs | 38 ++++++++++++++++++---- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 412e44cbde..ba20031509 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -222,12 +222,6 @@ namespace osu.Game.Screens.Select InternalChild = new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding - { - // Avoid clash between scrollbar and osu! logo. - Top = 10, - Bottom = 100, - }, Children = new Drawable[] { setPool, @@ -1271,6 +1265,38 @@ namespace osu.Game.Screens.Select return base.OnDragStart(e); } + + protected override ScrollbarContainer CreateScrollbar(Direction direction) + { + return new PaddedScrollbar(); + } + + protected partial class PaddedScrollbar : OsuScrollbar + { + public PaddedScrollbar() + : base(Direction.Vertical) + { + } + } + + private const float top_padding = 10; + private const float bottom_padding = 80; + + protected override float ToScrollbarPosition(float scrollPosition) + { + if (Precision.AlmostEquals(0, ScrollableExtent)) + return 0; + + return top_padding + (ScrollbarMovementExtent - bottom_padding) * (scrollPosition / ScrollableExtent); + } + + protected override float FromScrollbarPosition(float scrollbarPosition) + { + if (Precision.AlmostEquals(0, ScrollbarMovementExtent)) + return 0; + + return ScrollableExtent * ((scrollbarPosition - top_padding) / (ScrollbarMovementExtent - bottom_padding)); + } } protected override void Dispose(bool isDisposing) From 0cddb93dda4275c592436c71f07c2e514e1635da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 11 Nov 2024 09:57:17 +0100 Subject: [PATCH 794/924] Move setting to user config --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ osu.Game/Localisation/EditorStrings.cs | 4 ++-- osu.Game/Screens/Edit/Editor.cs | 4 ++-- osu.Game/Screens/Edit/EditorBeatmap.cs | 2 -- osu.Game/Screens/Edit/Timing/GroupSection.cs | 6 +++++- osu.Game/Screens/Edit/Timing/TapTimingControl.cs | 8 ++++++-- osu.Game/Screens/Edit/Timing/TimingSection.cs | 6 +++++- 7 files changed, 22 insertions(+), 10 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index f642d23bb0..af6fd61a3d 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -196,6 +196,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.EditorShowSpeedChanges, false); SetDefault(OsuSetting.EditorScaleOrigin, EditorOrigin.GridCentre); SetDefault(OsuSetting.EditorRotationOrigin, EditorOrigin.GridCentre); + SetDefault(OsuSetting.EditorAdjustExistingObjectsOnTimingChanges, true); SetDefault(OsuSetting.HideCountryFlags, false); @@ -442,5 +443,6 @@ namespace osu.Game.Configuration EditorScaleOrigin, EditorRotationOrigin, EditorTimelineShowBreaks, + EditorAdjustExistingObjectsOnTimingChanges, } } diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs index 19b783de92..127bdd8355 100644 --- a/osu.Game/Localisation/EditorStrings.cs +++ b/osu.Game/Localisation/EditorStrings.cs @@ -40,9 +40,9 @@ namespace osu.Game.Localisation public static LocalisableString SetPreviewPointToCurrent => new TranslatableString(getKey(@"set_preview_point_to_current"), @"Set preview point to current time"); /// - /// "Move already placed notes when changing the offset / BPM" + /// "Move already placed objects when changing timing" /// - public static LocalisableString AdjustNotesOnOffsetBPMChange => new TranslatableString(getKey(@"adjust_notes_on_offset_bpm_change"), @"Move already placed notes when changing the offset / BPM"); + public static LocalisableString AdjustExistingObjectsOnTimingChanges => new TranslatableString(getKey(@"adjust_existing_objects_on_timing_changes"), @"Move already placed objects when changing timing"); /// /// "For editing (.olz)" diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 178b3f57b0..8de0c7c33d 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -422,9 +422,9 @@ namespace osu.Game.Screens.Edit Items = new MenuItem[] { new EditorMenuItem(EditorStrings.SetPreviewPointToCurrent, MenuItemType.Standard, SetPreviewPointToCurrentTime), - new ToggleMenuItem(EditorStrings.AdjustNotesOnOffsetBPMChange) + new ToggleMenuItem(EditorStrings.AdjustExistingObjectsOnTimingChanges) { - State = { BindTarget = editorBeatmap.AdjustNotesOnOffsetBPMChange }, + State = { BindTarget = config.GetBindable(OsuSetting.EditorAdjustExistingObjectsOnTimingChanges) }, } } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 154ebf3c88..ad31c2ccc3 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -89,8 +89,6 @@ namespace osu.Game.Screens.Edit public BindableInt PreviewTime { get; } - public Bindable AdjustNotesOnOffsetBPMChange { get; } = new Bindable(false); - private readonly IBeatmapProcessor beatmapProcessor; private readonly Dictionary> startTimeBindables = new Dictionary>(); diff --git a/osu.Game/Screens/Edit/Timing/GroupSection.cs b/osu.Game/Screens/Edit/Timing/GroupSection.cs index abcdf7e4ff..13e802a8e4 100644 --- a/osu.Game/Screens/Edit/Timing/GroupSection.cs +++ b/osu.Game/Screens/Edit/Timing/GroupSection.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osuTK; @@ -25,6 +26,9 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] protected EditorBeatmap Beatmap { get; private set; } = null!; + [Resolved] + private OsuConfigManager configManager { get; set; } = null!; + [Resolved] private EditorClock clock { get; set; } = null!; @@ -112,7 +116,7 @@ namespace osu.Game.Screens.Edit.Timing foreach (var cp in currentGroupItems) { // Only adjust hit object offsets if the group contains a timing control point - if (Beatmap.AdjustNotesOnOffsetBPMChange.Value && cp is TimingControlPoint tp) + if (cp is TimingControlPoint tp && configManager.Get(OsuSetting.EditorAdjustExistingObjectsOnTimingChanges)) { TimingSectionAdjustments.AdjustHitObjectOffset(Beatmap, tp, time - SelectedGroup.Value.Time); Beatmap.UpdateAllHitObjects(); diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 91a0a43d62..f105c00726 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; @@ -26,6 +27,9 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] private EditorBeatmap beatmap { get; set; } = null!; + [Resolved] + private OsuConfigManager configManager { get; set; } = null!; + [Resolved] private Bindable selectedGroup { get; set; } = null!; @@ -209,7 +213,7 @@ namespace osu.Game.Screens.Edit.Timing foreach (var cp in currentGroupItems) { - if (beatmap.AdjustNotesOnOffsetBPMChange.Value && cp is TimingControlPoint tp) + if (cp is TimingControlPoint tp) { TimingSectionAdjustments.AdjustHitObjectOffset(beatmap, tp, adjust); beatmap.UpdateAllHitObjects(); @@ -236,7 +240,7 @@ namespace osu.Game.Screens.Edit.Timing double oldBeatLength = timing.BeatLength; timing.BeatLength = 60000 / (timing.BPM + adjust); - if (beatmap.AdjustNotesOnOffsetBPMChange.Value) + if (configManager.Get(OsuSetting.EditorAdjustExistingObjectsOnTimingChanges)) { beatmap.BeginChange(); TimingSectionAdjustments.SetHitObjectBPM(beatmap, timing, oldBeatLength); diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index e668120d0d..6a89dc1341 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Configuration; using osu.Game.Graphics.UserInterfaceV2; namespace osu.Game.Screens.Edit.Timing @@ -16,6 +17,9 @@ namespace osu.Game.Screens.Edit.Timing private LabelledSwitchButton omitBarLine = null!; private BPMTextBox bpmTextEntry = null!; + [Resolved] + private OsuConfigManager configManager { get; set; } = null!; + [BackgroundDependencyLoader] private void load() { @@ -46,7 +50,7 @@ namespace osu.Game.Screens.Edit.Timing bpmTextEntry.OnCommit = (oldBeatLength, _) => { - if (!Beatmap.AdjustNotesOnOffsetBPMChange.Value || ControlPoint.Value == null) + if (!configManager.Get(OsuSetting.EditorAdjustExistingObjectsOnTimingChanges) || ControlPoint.Value == null) return; Beatmap.BeginChange(); From f84ee3996fdfa962885ee162257908b9c1bdfff5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Nov 2024 17:54:43 +0900 Subject: [PATCH 795/924] Reduce semi-opaque layers at song select I made these changes while working on https://github.com/ppy/osu/pull/30579. Basically, it's hard to fix the ranks not loading while underneath the footer, and the transparency both looks bad, and is going away in the redesign. I've chosen values here that are moving *in the direction* of the new design without overhauling everything. - I know that there's still some transparency. I did this because it helps keep all current elements / colours contrasting without too much effort. - I completely removed the transparency adjustments on the beatmap panels. This always looked bad due to being applied per-layer, and I don't think it added much. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 5 ----- osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs | 2 -- osu.Game/Screens/Select/FilterControl.cs | 4 ++-- osu.Game/Screens/Select/Footer.cs | 6 ++++-- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 412e44cbde..e8fe191a24 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -1116,11 +1116,6 @@ namespace osu.Game.Screens.Select // adjusting the item's overall X position can cause it to become masked away when // child items (difficulties) are still visible. item.Header.X = offsetX(dist, visibleHalfHeight) - (parent?.X ?? 0); - - // We are applying a multiplicative alpha (which is internally done by nesting an - // additional container and setting that container's alpha) such that we can - // layer alpha transformations on top. - item.SetMultiplicativeAlpha(Math.Clamp(1.75f - 1.5f * dist, 0, 1)); } private enum PendingScrollOperation diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 755008d370..10921c331e 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -86,8 +86,6 @@ namespace osu.Game.Screens.Select.Carousel }; } - public void SetMultiplicativeAlpha(float alpha) => Header.BorderContainer.Alpha = alpha; - protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 877db75317..be91aef1a3 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -97,8 +97,8 @@ namespace osu.Game.Screens.Select { new Box { - Colour = Color4.Black, - Alpha = 0.8f, + Colour = OsuColour.Gray(0.05f), + Alpha = 0.96f, Width = 2, RelativeSizeAxes = Axes.Both, }, diff --git a/osu.Game/Screens/Select/Footer.cs b/osu.Game/Screens/Select/Footer.cs index 933df2464a..2d919c3247 100644 --- a/osu.Game/Screens/Select/Footer.cs +++ b/osu.Game/Screens/Select/Footer.cs @@ -12,6 +12,7 @@ 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.UserInterface; namespace osu.Game.Screens.Select @@ -82,14 +83,15 @@ namespace osu.Game.Screens.Select { RelativeSizeAxes = Axes.Both, Size = Vector2.One, - Colour = Color4.Black.Opacity(0.5f), + Colour = OsuColour.Gray(0.1f), + Alpha = 0.96f, }, modeLight = new Box { RelativeSizeAxes = Axes.X, Height = 3, Position = new Vector2(0, -3), - Colour = Color4.Black, + Colour = OsuColour.Gray(0.1f), }, new FillFlowContainer { From c37e4877e24ea295b788e54f3048643629975791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 11 Nov 2024 10:08:26 +0100 Subject: [PATCH 796/924] Move setting back to timing panel --- osu.Game/Screens/Edit/Editor.cs | 4 ---- osu.Game/Screens/Edit/Timing/TimingSection.cs | 7 +++++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 8de0c7c33d..644e1afb3b 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -422,10 +422,6 @@ namespace osu.Game.Screens.Edit Items = new MenuItem[] { new EditorMenuItem(EditorStrings.SetPreviewPointToCurrent, MenuItemType.Standard, SetPreviewPointToCurrentTime), - new ToggleMenuItem(EditorStrings.AdjustExistingObjectsOnTimingChanges) - { - State = { BindTarget = config.GetBindable(OsuSetting.EditorAdjustExistingObjectsOnTimingChanges) }, - } } } } diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 6a89dc1341..ae1ac02dd6 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Localisation; namespace osu.Game.Screens.Edit.Timing { @@ -25,6 +26,12 @@ namespace osu.Game.Screens.Edit.Timing { Flow.AddRange(new Drawable[] { + new LabelledSwitchButton + { + Label = EditorStrings.AdjustExistingObjectsOnTimingChanges, + FixedLabelWidth = 220, + Current = configManager.GetBindable(OsuSetting.EditorAdjustExistingObjectsOnTimingChanges), + }, new TapTimingControl(), bpmTextEntry = new BPMTextBox(), timeSignature = new LabelledTimeSignature From d29dd2c223fb71befd212fa6d0573faf9c3554dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 11 Nov 2024 11:04:02 +0100 Subject: [PATCH 797/924] Remove unused using directives --- osu.Game/Screens/Select/FilterControl.cs | 1 - osu.Game/Screens/Select/Footer.cs | 2 -- 2 files changed, 3 deletions(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index be91aef1a3..b221296ba8 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -28,7 +28,6 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Select.Filter; using osuTK; -using osuTK.Graphics; using osuTK.Input; namespace osu.Game.Screens.Select diff --git a/osu.Game/Screens/Select/Footer.cs b/osu.Game/Screens/Select/Footer.cs index 2d919c3247..1d05f644b7 100644 --- a/osu.Game/Screens/Select/Footer.cs +++ b/osu.Game/Screens/Select/Footer.cs @@ -6,8 +6,6 @@ using System.Collections.Generic; using System.Linq; using osuTK; -using osuTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; From ee4d58544d7a756fea19f0d59a64a2b0a0849674 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Nov 2024 21:57:01 +0900 Subject: [PATCH 798/924] Update framework (and other common packages) --- osu.Desktop/osu.Desktop.csproj | 2 +- osu.Game/osu.Game.csproj | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 904f5edf2b..2cdbaddf72 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 7405a7c587..855ba3374b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -20,7 +20,7 @@ - + @@ -35,9 +35,9 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + - + From 8c5785fdf63e30bb58d9ff587a3718892278c8be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Nov 2024 22:57:26 +0900 Subject: [PATCH 799/924] Make math more logical --- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index ba20031509..5716b2c918 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -1280,14 +1280,14 @@ namespace osu.Game.Screens.Select } private const float top_padding = 10; - private const float bottom_padding = 80; + private const float bottom_padding = 70; protected override float ToScrollbarPosition(float scrollPosition) { if (Precision.AlmostEquals(0, ScrollableExtent)) return 0; - return top_padding + (ScrollbarMovementExtent - bottom_padding) * (scrollPosition / ScrollableExtent); + return top_padding + (ScrollbarMovementExtent - (top_padding + bottom_padding)) * (scrollPosition / ScrollableExtent); } protected override float FromScrollbarPosition(float scrollbarPosition) @@ -1295,7 +1295,7 @@ namespace osu.Game.Screens.Select if (Precision.AlmostEquals(0, ScrollbarMovementExtent)) return 0; - return ScrollableExtent * ((scrollbarPosition - top_padding) / (ScrollbarMovementExtent - bottom_padding)); + return ScrollableExtent * ((scrollbarPosition - top_padding) / (ScrollbarMovementExtent - (top_padding + bottom_padding))); } } From 3268008d215a7bc1e32fdcc7741688bbca8eed29 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Tue, 12 Nov 2024 04:07:27 -0500 Subject: [PATCH 800/924] Fix stage line alignment in mania not matching stable --- .../Skinning/Legacy/LegacyStageBackground.cs | 5 +++-- osu.Game.Rulesets.Mania/UI/ColumnFlow.cs | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs index 758c8dd347..71618a4bc3 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs @@ -54,7 +54,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy }, columnBackgrounds = new ColumnFlow(stageDefinition) { - RelativeSizeAxes = Axes.Y + RelativeSizeAxes = Axes.Y, + Masking = false, }, new HitTargetInsetContainer { @@ -126,8 +127,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy }, new Container { + X = isLastColumn ? -0.16f : 0, Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, RelativeSizeAxes = Axes.Y, Width = rightLineWidth, Scale = new Vector2(0.740f, 1), diff --git a/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs index f444448797..5614a13a48 100644 --- a/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs +++ b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs @@ -28,6 +28,12 @@ namespace osu.Game.Rulesets.Mania.UI private readonly FillFlowContainer> columns; private readonly StageDefinition stageDefinition; + public new bool Masking + { + get => base.Masking; + set => base.Masking = value; + } + public ColumnFlow(StageDefinition stageDefinition) { this.stageDefinition = stageDefinition; From 72564b5c98c530f2ea39b98844e592528b44d439 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 12 Nov 2024 01:38:31 +0900 Subject: [PATCH 801/924] Make `CurrentPlaylistItem` not a bindable --- .../TestSceneDrawableLoungeRoom.cs | 20 ++-- .../Multiplayer/TestSceneDrawableRoom.cs | 94 +++++++++---------- .../Multiplayer/TestSceneMatchStartControl.cs | 22 ++--- .../TestSceneMultiplayerMatchFooter.cs | 9 +- .../TestSceneMultiplayerPlaylist.cs | 6 +- .../TestSceneMultiplayerSpectateButton.cs | 11 +-- .../UpdateableBeatmapBackgroundSprite.cs | 19 ++-- .../Online/Multiplayer/MultiplayerClient.cs | 4 +- osu.Game/Online/Rooms/Room.cs | 49 +++++++--- .../Components/OnlinePlayBackgroundSprite.cs | 41 -------- .../Lounge/Components/DrawableRoom.cs | 58 +++++++----- .../Lounge/Components/RoomsContainer.cs | 2 +- .../OnlinePlay/Lounge/DrawableLoungeRoom.cs | 61 ++++++++---- .../OnlinePlay/Match/DrawableMatchRoom.cs | 34 +++---- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 3 +- .../Multiplayer/Match/MatchStartControl.cs | 14 ++- .../Match/MultiplayerMatchFooter.cs | 16 +++- .../Match/MultiplayerMatchSettingsOverlay.cs | 19 ++-- .../Match/MultiplayerSpectateButton.cs | 16 ++-- .../Match/Playlist/MultiplayerPlaylist.cs | 14 ++- .../Multiplayer/MultiplayerMatchSubScreen.cs | 36 ++++--- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 74 +++++---------- .../Multiplayer/TestMultiplayerClient.cs | 2 +- .../OnlinePlay/TestRoomRequestsHandler.cs | 9 +- 24 files changed, 326 insertions(+), 307 deletions(-) delete mode 100644 osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs index 4de911b6b6..9fe7189a0a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using System.Threading; @@ -10,6 +8,7 @@ using System.Threading.Tasks; using Moq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Framework.Testing; @@ -31,8 +30,8 @@ namespace osu.Game.Tests.Visual.Multiplayer [Cached] protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Pink); - private DrawableLoungeRoom drawableRoom; - private SearchTextBox searchTextBox; + private DrawableLoungeRoom drawableRoom = null!; + private SearchTextBox searchTextBox = null!; private readonly ManualResetEventSlim allowResponseCallback = new ManualResetEventSlim(); @@ -78,6 +77,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Anchor = Anchor.Centre, Origin = Anchor.Centre, + SelectedRoom = new Bindable() } } }; @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestFocusViaKeyboardCommit() { - DrawableLoungeRoom.PasswordEntryPopover popover = null; + DrawableLoungeRoom.PasswordEntryPopover? popover = null; AddAssert("search textbox has focus", () => checkFocus(searchTextBox)); AddStep("click room twice", () => @@ -103,11 +103,11 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("enter password", () => popover.ChildrenOfType().Single().Text = "password"); AddStep("commit via enter", () => InputManager.Key(Key.Enter)); - AddAssert("popover has focus", () => checkFocus(popover)); + AddAssert("popover has focus", () => checkFocus(popover!)); AddStep("attempt another enter", () => InputManager.Key(Key.Enter)); - AddAssert("popover still has focus", () => checkFocus(popover)); + AddAssert("popover still has focus", () => checkFocus(popover!)); AddStep("unblock response", () => allowResponseCallback.Set()); @@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestFocusViaMouseCommit() { - DrawableLoungeRoom.PasswordEntryPopover popover = null; + DrawableLoungeRoom.PasswordEntryPopover? popover = null; AddAssert("search textbox has focus", () => checkFocus(searchTextBox)); AddStep("click room twice", () => @@ -144,11 +144,11 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.Click(MouseButton.Left); }); - AddAssert("popover has focus", () => checkFocus(popover)); + AddAssert("popover has focus", () => checkFocus(popover!)); AddStep("attempt another click", () => InputManager.Click(MouseButton.Left)); - AddAssert("popover still has focus", () => checkFocus(popover)); + AddAssert("popover still has focus", () => checkFocus(popover!)); AddStep("unblock response", () => allowResponseCallback.Set()); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index 98242e2d92..4c609613a3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -41,6 +41,31 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create rooms", () => { + PlaylistItem item1 = new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo) + { + BeatmapInfo = { StarRating = 2.5 } + }.BeatmapInfo); + + PlaylistItem item2 = new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo) + { + BeatmapInfo = { StarRating = 4.5 } + }.BeatmapInfo); + + PlaylistItem item3 = new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo) + { + BeatmapInfo = + { + StarRating = 2.5, + Metadata = + { + Artist = "very very very very very very very very very long artist", + ArtistUnicode = "very very very very very very very very very long artist", + Title = "very very very very very very very very very very very long title", + TitleUnicode = "very very very very very very very very very very very long title", + } + } + }.BeatmapInfo); + Child = rooms = new FillFlowContainer { Anchor = Anchor.Centre, @@ -56,16 +81,8 @@ namespace osu.Game.Tests.Visual.Multiplayer Status = { Value = new RoomStatusOpen() }, EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, Type = { Value = MatchType.HeadToHead }, - Playlist = - { - new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo) - { - BeatmapInfo = - { - StarRating = 2.5 - } - }.BeatmapInfo) - } + Playlist = { item1 }, + CurrentPlaylistItem = item1 }), createLoungeRoom(new Room { @@ -74,46 +91,16 @@ namespace osu.Game.Tests.Visual.Multiplayer HasPassword = { Value = true }, EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, Type = { Value = MatchType.HeadToHead }, - Playlist = - { - new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo) - { - BeatmapInfo = - { - StarRating = 2.5, - Metadata = - { - Artist = "very very very very very very very very very long artist", - ArtistUnicode = "very very very very very very very very very long artist", - Title = "very very very very very very very very very very very long title", - TitleUnicode = "very very very very very very very very very very very long title", - } - } - }.BeatmapInfo) - } + Playlist = { item3 }, + CurrentPlaylistItem = item3 }), createLoungeRoom(new Room { Name = { Value = "Playlist room with multiple beatmaps" }, Status = { Value = new RoomStatusPlaying() }, EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, - Playlist = - { - new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo) - { - BeatmapInfo = - { - StarRating = 2.5 - } - }.BeatmapInfo), - new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo) - { - BeatmapInfo = - { - StarRating = 4.5 - } - }.BeatmapInfo) - } + Playlist = { item1, item2 }, + CurrentPlaylistItem = item1 }), createLoungeRoom(new Room { @@ -181,20 +168,29 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = { Value = "A host-only room" }, QueueMode = { Value = QueueMode.HostOnly }, - Type = { Value = MatchType.HeadToHead } - }), + Type = { Value = MatchType.HeadToHead }, + }) + { + SelectedItem = new Bindable() + }, new DrawableMatchRoom(new Room { Name = { Value = "An all-players, team-versus room" }, QueueMode = { Value = QueueMode.AllPlayers }, Type = { Value = MatchType.TeamVersus } - }), + }) + { + SelectedItem = new Bindable() + }, new DrawableMatchRoom(new Room { Name = { Value = "A round-robin room" }, QueueMode = { Value = QueueMode.AllPlayersRoundRobin }, Type = { Value = MatchType.HeadToHead } - }), + }) + { + SelectedItem = new Bindable() + }, } }); } @@ -215,7 +211,7 @@ namespace osu.Game.Tests.Visual.Multiplayer return new DrawableLoungeRoom(room) { MatchingFilter = true, - SelectedRoom = { BindTarget = selectedRoom } + SelectedRoom = selectedRoom }; } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs index 3b10509895..4eddac8c7a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs @@ -43,9 +43,6 @@ namespace osu.Game.Tests.Visual.Multiplayer private OsuButton readyButton => control.ChildrenOfType().Single(); - [Cached(typeof(IBindable))] - private readonly Bindable currentItem = new Bindable(); - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => new CachedModelDependencyContainer(base.CreateChildDependencies(parent)) { Model = { BindTarget = room } }; @@ -107,31 +104,33 @@ namespace osu.Game.Tests.Visual.Multiplayer [SetUpSteps] public void SetUpSteps() { + PlaylistItem item = null!; + AddStep("reset state", () => { multiplayerClient.Invocations.Clear(); beatmapAvailability.Value = BeatmapAvailability.LocallyAvailable(); - currentItem.Value = new PlaylistItem(Beatmap.Value.BeatmapInfo) + item = new PlaylistItem(Beatmap.Value.BeatmapInfo) { RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID }; room.Value = new Room { - Playlist = { currentItem.Value }, - CurrentPlaylistItem = { BindTarget = currentItem } + Playlist = { item }, + CurrentPlaylistItem = item }; - localUser = new MultiplayerRoomUser(API.LocalUser.Value.Id) { User = API.LocalUser.Value }; + localUser = new MultiplayerRoomUser(API.LocalUser.Value.Id) + { + User = API.LocalUser.Value + }; multiplayerRoom = new MultiplayerRoom(0) { - Playlist = - { - TestMultiplayerClient.CreateMultiplayerPlaylistItem(currentItem.Value), - }, + Playlist = { TestMultiplayerClient.CreateMultiplayerPlaylistItem(item) }, Users = { localUser }, Host = localUser, }; @@ -144,6 +143,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(250, 50), + SelectedItem = new Bindable(item) }; }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs index 9d8ef76e75..edeb1708e0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.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 osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -13,9 +12,6 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneMultiplayerMatchFooter : MultiplayerTestScene { - [Cached(typeof(IBindable))] - private readonly Bindable currentItem = new Bindable(); - public override void SetUpSteps() { base.SetUpSteps(); @@ -33,7 +29,10 @@ namespace osu.Game.Tests.Visual.Multiplayer Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, Height = 50, - Child = new MultiplayerMatchFooter() + Child = new MultiplayerMatchFooter + { + SelectedItem = new Bindable() + } } }; }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index 3baabecd84..e12c2bee49 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -28,9 +28,6 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneMultiplayerPlaylist : MultiplayerTestScene { - [Cached(typeof(IBindable))] - private readonly Bindable currentItem = new Bindable(); - private MultiplayerPlaylist list = null!; private BeatmapManager beatmaps = null!; private BeatmapSetInfo importedSet = null!; @@ -56,7 +53,8 @@ namespace osu.Game.Tests.Visual.Multiplayer Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.4f, 0.8f) + Size = new Vector2(0.4f, 0.8f), + SelectedItem = new Bindable() }; }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 5ae5d1e228..b4f55bb6a3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -26,9 +26,6 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneMultiplayerSpectateButton : MultiplayerTestScene { - [Cached(typeof(IBindable))] - private readonly Bindable currentItem = new Bindable(); - private MultiplayerSpectateButton spectateButton = null!; private MatchStartControl startControl = null!; @@ -51,13 +48,13 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create button", () => { - AvailabilityTracker.SelectedItem.BindTo(currentItem); + PlaylistItem item = SelectedRoom.Value.Playlist.First(); + + AvailabilityTracker.SelectedItem.Value = item; importedSet = beatmaps.GetAllUsableBeatmapSets().First(); Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()); - currentItem.Value = SelectedRoom.Value.Playlist.First(); - Child = new PopoverContainer { RelativeSizeAxes = Axes.Both, @@ -72,12 +69,14 @@ namespace osu.Game.Tests.Visual.Multiplayer Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(200, 50), + SelectedItem = new Bindable(item) }, startControl = new MatchStartControl { Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(200, 50), + SelectedItem = new Bindable(item) } } } diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs index 6f71fa90b8..8ddda485b5 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs @@ -14,9 +14,17 @@ namespace osu.Game.Beatmaps.Drawables /// public partial class UpdateableBeatmapBackgroundSprite : ModelBackedDrawable { - public readonly Bindable Beatmap = new Bindable(); + public readonly Bindable Beatmap = new Bindable(); - protected override double LoadDelay => 500; + /// + /// Delay before the background is loaded while on-screen. + /// + public double BackgroundLoadDelay = 500; + + /// + /// Delay before the background is unloaded while off-screen. + /// + public double BackgroundUnloadDelay = 10000; [Resolved] private BeatmapManager beatmaps { get; set; } = null!; @@ -29,10 +37,9 @@ namespace osu.Game.Beatmaps.Drawables this.beatmapSetCoverType = beatmapSetCoverType; } - /// - /// Delay before the background is unloaded while off-screen. - /// - protected virtual double UnloadDelay => 10000; + protected override double LoadDelay => BackgroundLoadDelay; + + protected virtual double UnloadDelay => BackgroundUnloadDelay; protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Func createContentFunc, double timeBeforeLoad) => new DelayedLoadUnloadWrapper(createContentFunc, timeBeforeLoad, UnloadDelay) { RelativeSizeAxes = Axes.Both }; diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index ff147aba10..5519d58060 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -203,7 +203,7 @@ namespace osu.Game.Online.Multiplayer APIRoom.Playlist.Clear(); APIRoom.Playlist.AddRange(joinedRoom.Playlist.Select(item => new PlaylistItem(item))); - APIRoom.CurrentPlaylistItem.Value = APIRoom.Playlist.Single(item => item.ID == joinedRoom.Settings.PlaylistItemId); + APIRoom.CurrentPlaylistItem = APIRoom.Playlist.Single(item => item.ID == joinedRoom.Settings.PlaylistItemId); // The server will null out the end date upon the host joining the room, but the null value is never communicated to the client. APIRoom.EndDate.Value = null; @@ -847,7 +847,7 @@ namespace osu.Game.Online.Multiplayer APIRoom.Type.Value = Room.Settings.MatchType; APIRoom.QueueMode.Value = Room.Settings.QueueMode; APIRoom.AutoStartDuration.Value = Room.Settings.AutoStartDuration; - APIRoom.CurrentPlaylistItem.Value = APIRoom.Playlist.Single(item => item.ID == settings.PlaylistItemId); + APIRoom.CurrentPlaylistItem = APIRoom.Playlist.Single(item => item.ID == settings.PlaylistItemId); APIRoom.AutoSkip.Value = Room.Settings.AutoSkip; RoomUpdated?.Invoke(); diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index c39932c3bf..1f5fec6089 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -1,10 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; +using System.Collections.Generic; +using System.ComponentModel; using System.Linq; +using System.Runtime.CompilerServices; using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -16,8 +17,25 @@ using osu.Game.Online.Rooms.RoomStatuses; namespace osu.Game.Online.Rooms { [JsonObject(MemberSerialization.OptIn)] - public partial class Room : IDependencyInjectionCandidate + public partial class Room : IDependencyInjectionCandidate, INotifyPropertyChanged { + public event PropertyChangedEventHandler? PropertyChanged; + + [JsonProperty("current_playlist_item")] + private PlaylistItem? currentPlaylistItem; + + /// + /// Represents the current item selected within the room. + /// + /// + /// Only valid for room listing requests (i.e. in the lounge screen), and may not be valid while inside the room. + /// + public PlaylistItem? CurrentPlaylistItem + { + get => currentPlaylistItem; + set => SetField(ref currentPlaylistItem, value); + } + [Cached] [JsonProperty("id")] public readonly Bindable RoomID = new Bindable(); @@ -28,7 +46,7 @@ namespace osu.Game.Online.Rooms [Cached] [JsonProperty("host")] - public readonly Bindable Host = new Bindable(); + public readonly Bindable Host = new Bindable(); [Cached] [JsonProperty("playlist")] @@ -38,10 +56,6 @@ namespace osu.Game.Online.Rooms [JsonProperty("channel_id")] public readonly Bindable ChannelId = new Bindable(); - [JsonProperty("current_playlist_item")] - [Cached] - public readonly Bindable CurrentPlaylistItem = new Bindable(); - [JsonProperty("playlist_item_stats")] [Cached] public readonly Bindable PlaylistItemStats = new Bindable(); @@ -126,7 +140,7 @@ namespace osu.Game.Online.Rooms [Cached(Name = nameof(Password))] [JsonProperty("password")] - public readonly Bindable Password = new Bindable(); + public readonly Bindable Password = new Bindable(); [Cached] public readonly Bindable Duration = new Bindable(); @@ -203,7 +217,7 @@ namespace osu.Game.Online.Rooms AutoStartDuration.Value = other.AutoStartDuration.Value; DifficultyRange.Value = other.DifficultyRange.Value; PlaylistItemStats.Value = other.PlaylistItemStats.Value; - CurrentPlaylistItem.Value = other.CurrentPlaylistItem.Value; + CurrentPlaylistItem = other.CurrentPlaylistItem; AutoSkip.Value = other.AutoSkip.Value; other.RemoveExpiredPlaylistItems(); @@ -240,7 +254,7 @@ namespace osu.Game.Online.Rooms public int CountTotal; [JsonProperty("ruleset_ids")] - public int[] RulesetIDs; + public int[] RulesetIDs = []; } [JsonObject(MemberSerialization.OptIn)] @@ -252,5 +266,18 @@ namespace osu.Game.Online.Rooms [JsonProperty("max")] public double Max; } + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null!) + => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + + protected bool SetField(ref T field, T value, [CallerMemberName] string propertyName = null!) + { + if (EqualityComparer.Default.Equals(field, value)) + return false; + + field = value; + OnPropertyChanged(propertyName); + return true; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs deleted file mode 100644 index 0d4cd30090..0000000000 --- a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Beatmaps.Drawables; -using osu.Game.Online.Rooms; - -namespace osu.Game.Screens.OnlinePlay.Components -{ - public partial class OnlinePlayBackgroundSprite : OnlinePlayComposite - { - protected readonly BeatmapSetCoverType BeatmapSetCoverType; - private UpdateableBeatmapBackgroundSprite sprite; - - public OnlinePlayBackgroundSprite(BeatmapSetCoverType beatmapSetCoverType = BeatmapSetCoverType.Cover) - { - BeatmapSetCoverType = beatmapSetCoverType; - } - - [BackgroundDependencyLoader] - private void load() - { - InternalChild = sprite = CreateBackgroundSprite(); - - CurrentPlaylistItem.BindValueChanged(_ => updateBeatmap()); - Playlist.CollectionChanged += (_, _) => updateBeatmap(); - - updateBeatmap(); - } - - private void updateBeatmap() - { - sprite.Beatmap.Value = CurrentPlaylistItem.Value?.Beatmap ?? Playlist.GetCurrentItem()?.Beatmap; - } - - protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite(BeatmapSetCoverType) { RelativeSizeAxes = Axes.Both }; - } -} diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index ef06d21655..59c8047062 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -1,9 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; +using System.Diagnostics; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -16,6 +15,7 @@ using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -29,27 +29,28 @@ using osuTK.Graphics; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { - public partial class DrawableRoom : CompositeDrawable + public abstract partial class DrawableRoom : CompositeDrawable { protected const float CORNER_RADIUS = 10; private const float height = 100; public readonly Room Room; - protected Container ButtonsContainer { get; private set; } + protected readonly Bindable SelectedItem = new Bindable(); + protected Container ButtonsContainer { get; private set; } = null!; private readonly Bindable roomType = new Bindable(); private readonly Bindable roomCategory = new Bindable(); private readonly Bindable hasPassword = new Bindable(); - private DrawableRoomParticipantsList drawableRoomParticipantsList; - private RoomSpecialCategoryPill specialCategoryPill; - private PasswordProtectedIcon passwordIcon; - private EndDateInfo endDateInfo; + private DrawableRoomParticipantsList? drawableRoomParticipantsList; + private RoomSpecialCategoryPill? specialCategoryPill; + private PasswordProtectedIcon? passwordIcon; + private EndDateInfo? endDateInfo; + private UpdateableBeatmapBackgroundSprite background = null!; + private DelayedLoadWrapper wrapper = null!; - private DelayedLoadWrapper wrapper; - - public DrawableRoom(Room room) + protected DrawableRoom(Room room) { Room = room; @@ -77,7 +78,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components AutoSizeAxes = Axes.X }; - InternalChildren = new[] + InternalChildren = new Drawable[] { // This resolves internal 1px gaps due to applying the (parenting) corner radius and masking across multiple filling background sprites. new Box @@ -85,7 +86,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components RelativeSizeAxes = Axes.Both, Colour = colours.Background5, }, - CreateBackground().With(d => + background = CreateBackground().With(d => { d.RelativeSizeAxes = Axes.Both; }), @@ -186,7 +187,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components Font = OsuFont.GetFont(size: 28), Current = { BindTarget = Room.Name } }, - new RoomStatusText() + new RoomStatusText + { + SelectedItem = { BindTarget = SelectedItem } + } } } }, @@ -245,6 +249,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components wrapper.DelayedLoadComplete += _ => { + Debug.Assert(specialCategoryPill != null); + Debug.Assert(endDateInfo != null); + Debug.Assert(passwordIcon != null); + wrapper.FadeInFromZero(200); roomCategory.BindTo(Room.Category); @@ -265,6 +273,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components hasPassword.BindTo(Room.HasPassword); hasPassword.BindValueChanged(v => passwordIcon.Alpha = v.NewValue ? 1 : 0, true); }; + + SelectedItem.BindValueChanged(item => background.Beatmap.Value = item.NewValue?.Beatmap, true); } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) @@ -289,7 +299,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components } } - protected virtual Drawable CreateBackground() => new OnlinePlayBackgroundSprite(); + protected virtual UpdateableBeatmapBackgroundSprite CreateBackground() => new UpdateableBeatmapBackgroundSprite(); protected virtual IEnumerable CreateBottomDetails() { @@ -324,14 +334,16 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private partial class RoomStatusText : OnlinePlayComposite { - [Resolved] - private OsuColour colours { get; set; } + public readonly IBindable SelectedItem = new Bindable(); [Resolved] - private BeatmapLookupCache beatmapLookupCache { get; set; } + private OsuColour colours { get; set; } = null!; - private SpriteText statusText; - private LinkFlowContainer beatmapText; + [Resolved] + private BeatmapLookupCache beatmapLookupCache { get; set; } = null!; + + private SpriteText statusText = null!; + private LinkFlowContainer beatmapText = null!; public RoomStatusText() { @@ -383,12 +395,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components protected override void LoadComplete() { base.LoadComplete(); - CurrentPlaylistItem.BindValueChanged(onSelectedItemChanged, true); + SelectedItem.BindValueChanged(onSelectedItemChanged, true); } - private CancellationTokenSource beatmapLookupCancellation; + private CancellationTokenSource? beatmapLookupCancellation; - private void onSelectedItemChanged(ValueChangedEvent item) + private void onSelectedItemChanged(ValueChangedEvent item) { beatmapLookupCancellation?.Cancel(); beatmapText.Clear(); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index e842f8c436..9e60f43ed7 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -140,7 +140,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private void addRooms(IEnumerable rooms) { foreach (var room in rooms) - roomFlow.Add(new DrawableLoungeRoom(room) { SelectedRoom = { BindTarget = SelectedRoom } }); + roomFlow.Add(new DrawableLoungeRoom(room) { SelectedRoom = SelectedRoom }); applyFilterCriteria(Filter?.Value); } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index fed47e847a..36db1ab29b 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -1,9 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; +using System.ComponentModel; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -28,6 +27,7 @@ using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Lounge.Components; using osuTK; using osuTK.Graphics; +using Container = osu.Framework.Graphics.Containers.Container; namespace osu.Game.Screens.OnlinePlay.Lounge { @@ -39,14 +39,19 @@ namespace osu.Game.Screens.OnlinePlay.Lounge private const float transition_duration = 60; private const float selection_border_width = 4; - public readonly Bindable SelectedRoom = new Bindable(); + public required Bindable SelectedRoom + { + get => selectedRoom; + set => selectedRoom.Current = value; + } [Resolved(canBeNull: true)] - private LoungeSubScreen lounge { get; set; } + private LoungeSubScreen? lounge { get; set; } - private Sample sampleSelect; - private Sample sampleJoin; - private Drawable selectionBox; + private readonly BindableWithCurrent selectedRoom = new BindableWithCurrent(); + private Sample? sampleSelect; + private Sample? sampleJoin; + private Drawable selectionBox = null!; public DrawableLoungeRoom(Room room) : base(room) @@ -89,12 +94,24 @@ namespace osu.Game.Screens.OnlinePlay.Lounge base.LoadComplete(); Alpha = matchingFilter ? 1 : 0; - selectionBox.Alpha = SelectedRoom.Value == Room ? 1 : 0; + selectionBox.Alpha = selectedRoom.Value == Room ? 1 : 0; - SelectedRoom.BindValueChanged(updateSelectedRoom); + selectedRoom.BindValueChanged(updateSelectedRoom); + + Room.PropertyChanged += onRoomPropertyChanged; + updateSelectedItem(); } - private void updateSelectedRoom(ValueChangedEvent selected) + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.CurrentPlaylistItem)) + updateSelectedItem(); + } + + private void updateSelectedItem() + => SelectedItem.Value = Room.CurrentPlaylistItem; + + private void updateSelectedRoom(ValueChangedEvent selected) { if (selected.NewValue == Room) selectionBox.FadeIn(transition_duration); @@ -140,7 +157,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge if (e.Repeat) return false; - if (SelectedRoom.Value != Room) + if (selectedRoom.Value != Room) return false; switch (e.Action) @@ -157,14 +174,14 @@ namespace osu.Game.Screens.OnlinePlay.Lounge { } - protected override bool ShouldBeConsideredForInput(Drawable child) => SelectedRoom.Value == Room || child is HoverSounds; + protected override bool ShouldBeConsideredForInput(Drawable child) => selectedRoom.Value == Room || child is HoverSounds; protected override bool OnClick(ClickEvent e) { - if (Room != SelectedRoom.Value) + if (Room != selectedRoom.Value) { sampleSelect?.Play(); - SelectedRoom.Value = Room; + selectedRoom.Value = Room; return true; } @@ -179,12 +196,18 @@ namespace osu.Game.Screens.OnlinePlay.Lounge return true; } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + Room.PropertyChanged -= onRoomPropertyChanged; + } + public partial class PasswordEntryPopover : OsuPopover { private readonly Room room; [Resolved(canBeNull: true)] - private LoungeSubScreen lounge { get; set; } + private LoungeSubScreen? lounge { get; set; } public override bool HandleNonPositionalInput => true; @@ -195,10 +218,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge this.room = room; } - private OsuPasswordTextBox passwordTextBox; - private RoundedButton joinButton; - private OsuSpriteText errorText; - private Sample sampleJoinFail; + private OsuPasswordTextBox passwordTextBox = null!; + private RoundedButton joinButton = null!; + private OsuSpriteText errorText = null!; + private Sample? sampleJoinFail; [BackgroundDependencyLoader] private void load(OsuColour colours, AudioManager audio) diff --git a/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs b/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs index 3bda93c909..5d4b6b6404 100644 --- a/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs @@ -1,10 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -21,25 +18,22 @@ namespace osu.Game.Screens.OnlinePlay.Match { public partial class DrawableMatchRoom : DrawableRoom { - public readonly IBindable SelectedItem = new Bindable(); - public Action OnEdit; + public Action? OnEdit; [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; - private readonly IBindable host = new Bindable(); + private readonly BindableWithCurrent current = new BindableWithCurrent(); + private readonly IBindable host = new Bindable(); private readonly bool allowEdit; - - [CanBeNull] - private Drawable editButton; - - private BackgroundSprite background; + private Drawable? editButton; public DrawableMatchRoom(Room room, bool allowEdit = true) : base(room) { this.allowEdit = allowEdit; + base.SelectedItem.BindTo(SelectedItem); host.BindTo(room.Host); } @@ -58,21 +52,23 @@ namespace osu.Game.Screens.OnlinePlay.Match } } + public new required Bindable SelectedItem + { + get => current; + set => current.Current = value; + } + protected override void LoadComplete() { base.LoadComplete(); if (editButton != null) host.BindValueChanged(h => editButton.Alpha = h.NewValue?.Equals(api.LocalUser.Value) == true ? 1 : 0, true); - - SelectedItem.BindValueChanged(item => background.Beatmap.Value = item.NewValue?.Beatmap, true); } - protected override Drawable CreateBackground() => background = new BackgroundSprite(); - - private partial class BackgroundSprite : UpdateableBeatmapBackgroundSprite + protected override UpdateableBeatmapBackgroundSprite CreateBackground() => base.CreateBackground().With(d => { - protected override double LoadDelay => 0; - } + d.BackgroundLoadDelay = 0; + }); } } diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 7c8931c04e..9801405dd3 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -35,7 +35,6 @@ namespace osu.Game.Screens.OnlinePlay.Match [Cached(typeof(IPreviewTrackOwner))] public abstract partial class RoomSubScreen : OnlinePlaySubScreen, IPreviewTrackOwner { - [Cached(typeof(IBindable))] public readonly Bindable SelectedItem = new Bindable(); public override bool? ApplyModTrackAdjustments => true; @@ -164,7 +163,7 @@ namespace osu.Game.Screens.OnlinePlay.Match new DrawableMatchRoom(Room, allowEdit) { OnEdit = () => settingsOverlay.Show(), - SelectedItem = { BindTarget = SelectedItem } + SelectedItem = SelectedItem } }, null, diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs index a82fa6e4bc..0d90d44496 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs @@ -23,6 +23,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { public partial class MatchStartControl : CompositeDrawable { + public required Bindable SelectedItem + { + get => selectedItem; + set => selectedItem.Current = value; + } + [Resolved] private OngoingOperationTracker ongoingOperationTracker { get; set; } = null!; @@ -32,9 +38,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match [Resolved] private MultiplayerClient client { get; set; } = null!; - [Resolved] - private IBindable currentItem { get; set; } = null!; - + private readonly BindableWithCurrent selectedItem = new BindableWithCurrent(); private readonly MultiplayerReadyButton readyButton; private readonly MultiplayerCountdownButton countdownButton; @@ -94,7 +98,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { base.LoadComplete(); - currentItem.BindValueChanged(_ => updateState()); + SelectedItem.BindValueChanged(_ => updateState()); client.RoomUpdated += onRoomUpdated; client.LoadRequested += onLoadRequested; updateState(); @@ -210,7 +214,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match readyButton.Enabled.Value = countdownButton.Enabled.Value = client.Room.State != MultiplayerRoomState.Closed - && currentItem.Value?.ID == client.Room.Settings.PlaylistItemId + && SelectedItem.Value?.ID == client.Room.Settings.PlaylistItemId && !client.Room.Playlist.Single(i => i.ID == client.Room.Settings.PlaylistItemId).Expired && !operationInProgress.Value; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs index fcb6480b58..2b592bd8b9 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs @@ -1,10 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { @@ -13,6 +13,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private const float ready_button_width = 600; private const float spectate_button_width = 200; + public required Bindable SelectedItem + { + get => selectedItem; + set => selectedItem.Current = value; + } + + private readonly BindableWithCurrent selectedItem = new BindableWithCurrent(); + public MultiplayerMatchFooter() { RelativeSizeAxes = Axes.Both; @@ -22,17 +30,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match RelativeSizeAxes = Axes.Both, Content = new[] { - new Drawable[] + new Drawable?[] { null, new MultiplayerSpectateButton { RelativeSizeAxes = Axes.Both, + SelectedItem = selectedItem }, null, new MatchStartControl { RelativeSizeAxes = Axes.Both, + SelectedItem = selectedItem }, null } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 5446211ced..7a1868bfbb 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -28,14 +28,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { public partial class MultiplayerMatchSettingsOverlay : RoomSettingsOverlay { - private MatchSettings settings = null!; + public required Bindable SelectedItem + { + get => selectedItem; + set => selectedItem.Current = value; + } protected override OsuButton SubmitButton => settings.ApplyButton; + protected override bool IsLoading => ongoingOperationTracker.InProgress.Value; [Resolved] private OngoingOperationTracker ongoingOperationTracker { get; set; } = null!; - protected override bool IsLoading => ongoingOperationTracker.InProgress.Value; + private readonly BindableWithCurrent selectedItem = new BindableWithCurrent(); + private MatchSettings settings = null!; public MultiplayerMatchSettingsOverlay(Room room) : base(room) @@ -48,7 +54,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Y, - SettingsApplied = Hide + SettingsApplied = Hide, + SelectedItem = { BindTarget = SelectedItem } }; protected partial class MatchSettings : OnlinePlayComposite @@ -57,6 +64,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; + public readonly Bindable SelectedItem = new Bindable(); public Action? SettingsApplied; public OsuTextBox NameField = null!; @@ -66,7 +74,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match public OsuTextBox PasswordTextBox = null!; public OsuCheckbox AutoSkipCheckbox = null!; public RoundedButton ApplyButton = null!; - public OsuSpriteText ErrorText = null!; private OsuEnumDropdown startModeDropdown = null!; @@ -367,7 +374,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match base.LoadComplete(); drawablePlaylist.Items.BindTo(Playlist); - drawablePlaylist.SelectedItem.BindTo(CurrentPlaylistItem); + drawablePlaylist.SelectedItem.BindTo(SelectedItem); } protected override void Update() @@ -448,7 +455,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match if (text.StartsWith(not_found_prefix, StringComparison.Ordinal)) { ErrorText.Text = "The selected beatmap is not available online."; - CurrentPlaylistItem.Value.MarkInvalid(); + SelectedItem.Value?.MarkInvalid(); } else { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs index 92edc9b979..905a2bf31b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs @@ -21,6 +21,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { public partial class MultiplayerSpectateButton : CompositeDrawable { + public required Bindable SelectedItem + { + get => selectedItem; + set => selectedItem.Current = value; + } + [Resolved] private OngoingOperationTracker ongoingOperationTracker { get; set; } = null!; @@ -30,13 +36,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match [Resolved] private MultiplayerClient client { get; set; } = null!; - [Resolved] - private IBindable currentItem { get; set; } = null!; + private readonly BindableWithCurrent selectedItem = new BindableWithCurrent(); + private readonly RoundedButton button; private IBindable operationInProgress = null!; - private readonly RoundedButton button; - public MultiplayerSpectateButton() { InternalChild = button = new RoundedButton @@ -71,7 +75,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { base.LoadComplete(); - currentItem.BindValueChanged(_ => Scheduler.AddOnce(checkForAutomaticDownload), true); + SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(checkForAutomaticDownload), true); client.RoomUpdated += onRoomUpdated; updateState(); } @@ -117,7 +121,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void checkForAutomaticDownload() { - PlaylistItem? item = currentItem.Value; + PlaylistItem? item = SelectedItem.Value; downloadCheckCancellation?.Cancel(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs index 8ba85019d5..a44891e934 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs @@ -19,6 +19,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist { public readonly Bindable DisplayMode = new Bindable(); + public required Bindable SelectedItem + { + get => selectedItem; + set => selectedItem.Current = value; + } + /// /// Invoked when an item requests to be edited. /// @@ -27,9 +33,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist [Resolved] private MultiplayerClient client { get; set; } = null!; - [Resolved] - private IBindable currentItem { get; set; } = null!; - + private readonly BindableWithCurrent selectedItem = new BindableWithCurrent(); private MultiplayerPlaylistTabControl playlistTabControl = null!; private MultiplayerQueueList queueList = null!; private MultiplayerHistoryList historyList = null!; @@ -58,14 +62,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist queueList = new MultiplayerQueueList { RelativeSizeAxes = Axes.Both, - SelectedItem = { BindTarget = currentItem }, + SelectedItem = { BindTarget = selectedItem }, RequestEdit = item => RequestEdit?.Invoke(item) }, historyList = new MultiplayerHistoryList { RelativeSizeAxes = Axes.Both, Alpha = 0, - SelectedItem = { BindTarget = currentItem } + SelectedItem = { BindTarget = selectedItem } } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index a37314de0e..e0c8675ec5 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -1,13 +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 disable - using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; @@ -45,12 +44,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public override string ShortTitle => "room"; [Resolved] - private MultiplayerClient client { get; set; } + private MultiplayerClient client { get; set; } = null!; [Resolved(canBeNull: true)] - private OsuGame game { get; set; } + private OsuGame? game { get; set; } - private AddItemButton addItemButton; + private AddItemButton addItemButton = null!; public MultiplayerMatchSubScreen(Room room) : base(room) @@ -95,7 +94,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer }, Content = new[] { - new Drawable[] + new Drawable?[] { // Participants column new GridContainer @@ -142,7 +141,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer new MultiplayerPlaylist { RelativeSizeAxes = Axes.Both, - RequestEdit = OpenSongSelection + RequestEdit = OpenSongSelection, + SelectedItem = SelectedItem } }, new[] @@ -220,7 +220,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer /// Opens the song selection screen to add or edit an item. /// /// An optional playlist item to edit. If null, a new item will be added instead. - internal void OpenSongSelection(PlaylistItem itemToEdit = null) + internal void OpenSongSelection(PlaylistItem? itemToEdit = null) { if (!this.IsCurrentScreen()) return; @@ -228,9 +228,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer this.Push(new MultiplayerMatchSongSelect(Room, itemToEdit)); } - protected override Drawable CreateFooter() => new MultiplayerMatchFooter(); + protected override Drawable CreateFooter() => new MultiplayerMatchFooter + { + SelectedItem = SelectedItem + }; - protected override RoomSettingsOverlay CreateRoomSettingsOverlay(Room room) => new MultiplayerMatchSettingsOverlay(room); + protected override RoomSettingsOverlay CreateRoomSettingsOverlay(Room room) => new MultiplayerMatchSettingsOverlay(room) + { + SelectedItem = SelectedItem + }; protected override void UpdateMods() { @@ -245,7 +251,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } [Resolved(canBeNull: true)] - private IDialogOverlay dialogOverlay { get; set; } + private IDialogOverlay? dialogOverlay { get; set; } private bool exitConfirmed; @@ -275,8 +281,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return base.OnExiting(e); } - private ModSettingChangeTracker modSettingChangeTracker; - private ScheduledDelegate debouncedModSettingsUpdate; + private ModSettingChangeTracker? modSettingChangeTracker; + private ScheduledDelegate? debouncedModSettingsUpdate; private void onUserModsChanged(ValueChangedEvent> mods) { @@ -422,7 +428,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return; // If there's only one playlist item and we are the host, assume we want to change it. Else add a new one. - PlaylistItem itemToEdit = client.IsHost && Room.Playlist.Count == 1 ? Room.Playlist.Single() : null; + PlaylistItem? itemToEdit = client.IsHost && Room.Playlist.Count == 1 ? Room.Playlist.Single() : null; OpenSongSelection(itemToEdit); @@ -434,7 +440,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { base.Dispose(isDisposing); - if (client != null) + if (client.IsNotNull()) { client.RoomUpdated -= onRoomUpdated; client.LoadRequested -= onLoadRequested; diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 5be5c4b4f4..0d96f348ac 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -10,7 +8,6 @@ using osu.Framework.Graphics.Containers; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; -using osu.Game.Screens.OnlinePlay.Match; namespace osu.Game.Screens.OnlinePlay { @@ -20,96 +17,69 @@ namespace osu.Game.Screens.OnlinePlay public partial class OnlinePlayComposite : CompositeDrawable { [Resolved(typeof(Room))] - protected Bindable RoomID { get; private set; } + protected Bindable RoomID { get; private set; } = null!; [Resolved(typeof(Room), nameof(Room.Name))] - protected Bindable RoomName { get; private set; } + protected Bindable RoomName { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable Host { get; private set; } + protected Bindable Host { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable Status { get; private set; } + protected Bindable Status { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable Type { get; private set; } - - /// - /// The currently selected item in the , or the current item from - /// if this is not within a . - /// - [Resolved(typeof(Room))] - protected Bindable CurrentPlaylistItem { get; private set; } + protected Bindable Type { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable PlaylistItemStats { get; private set; } + protected Bindable PlaylistItemStats { get; private set; } = null!; [Resolved(typeof(Room))] - protected BindableList Playlist { get; private set; } + protected BindableList Playlist { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable DifficultyRange { get; private set; } + protected Bindable DifficultyRange { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable Category { get; private set; } + protected Bindable Category { get; private set; } = null!; [Resolved(typeof(Room))] - protected BindableList RecentParticipants { get; private set; } + protected BindableList RecentParticipants { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable ParticipantCount { get; private set; } + protected Bindable ParticipantCount { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable MaxParticipants { get; private set; } + protected Bindable MaxParticipants { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable MaxAttempts { get; private set; } + protected Bindable MaxAttempts { get; private set; } = null!; [Resolved(typeof(Room))] - public Bindable UserScore { get; private set; } + public Bindable UserScore { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable StartDate { get; private set; } + protected Bindable StartDate { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable EndDate { get; private set; } + protected Bindable EndDate { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable Availability { get; private set; } + protected Bindable Availability { get; private set; } = null!; [Resolved(typeof(Room))] - public Bindable Password { get; private set; } + public Bindable Password { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable Duration { get; private set; } + protected Bindable Duration { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable QueueMode { get; private set; } + protected Bindable QueueMode { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable AutoStartDuration { get; private set; } + protected Bindable AutoStartDuration { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable AutoSkip { get; private set; } - - [Resolved(CanBeNull = true)] - private IBindable subScreenSelectedItem { get; set; } - - protected override void LoadComplete() - { - base.LoadComplete(); - - subScreenSelectedItem?.BindValueChanged(_ => UpdateSelectedItem()); - Playlist.BindCollectionChanged((_, _) => UpdateSelectedItem(), true); - } - - protected void UpdateSelectedItem() - { - // null room ID means this is a room in the process of being created. - if (RoomID.Value == null) - CurrentPlaylistItem.Value = Playlist.GetCurrentItem(); - else if (subScreenSelectedItem != null) - CurrentPlaylistItem.Value = subScreenSelectedItem.Value; - } + protected Bindable AutoSkip { get; private set; } = null!; } } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index efa9dc4990..65a4019bb5 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -232,7 +232,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = ServerAPIRoom.Name.Value, MatchType = ServerAPIRoom.Type.Value, - Password = password, + Password = password ?? string.Empty, QueueMode = ServerAPIRoom.QueueMode.Value, AutoStartDuration = ServerAPIRoom.AutoStartDuration.Value }, diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index ec89f81597..41d288ba60 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics; @@ -296,10 +294,11 @@ namespace osu.Game.Tests.Visual.OnlinePlay Debug.Assert(result != null); // Playlist item IDs and beatmaps aren't serialised. - if (source.CurrentPlaylistItem.Value != null) + if (source.CurrentPlaylistItem != null) { - result.CurrentPlaylistItem.Value = result.CurrentPlaylistItem.Value.With(new Optional(source.CurrentPlaylistItem.Value.Beatmap)); - result.CurrentPlaylistItem.Value.ID = source.CurrentPlaylistItem.Value.ID; + Debug.Assert(result.CurrentPlaylistItem != null); + result.CurrentPlaylistItem = result.CurrentPlaylistItem.With(new Optional(source.CurrentPlaylistItem.Beatmap)); + result.CurrentPlaylistItem.ID = source.CurrentPlaylistItem.ID; } for (int i = 0; i < source.Playlist.Count; i++) From 8d5cd2b3531c86f8b0e85c769c2a610e1dd6b881 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 12 Nov 2024 20:12:50 +0900 Subject: [PATCH 802/924] Fix inspection --- .../Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 0883c626fe..d518565be9 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -65,7 +65,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("select first room", () => container.Rooms.First().TriggerClick()); AddAssert("first spotlight selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category.Value == RoomCategory.Spotlight))); - AddStep("remove last room", () => RoomManager.RemoveRoom(RoomManager.Rooms.MinBy(r => r.RoomID?.Value))); + AddStep("remove last room", () => RoomManager.RemoveRoom(RoomManager.Rooms.MinBy(r => r.RoomID.Value))); AddAssert("first spotlight still selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category.Value == RoomCategory.Spotlight))); AddStep("remove spotlight room", () => RoomManager.RemoveRoom(RoomManager.Rooms.Single(r => r.Category.Value == RoomCategory.Spotlight))); From f3fd87af7bb083dc97d054d3a0c6b731acdeca59 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 12 Nov 2024 20:32:59 +0900 Subject: [PATCH 803/924] Make `DrawableMatchRoom` structurally match other implementations --- .../Screens/OnlinePlay/Match/DrawableMatchRoom.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs b/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs index 5d4b6b6404..e5d3bd1586 100644 --- a/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs @@ -20,10 +20,16 @@ namespace osu.Game.Screens.OnlinePlay.Match { public Action? OnEdit; + public new required Bindable SelectedItem + { + get => selectedItem; + set => selectedItem.Current = value; + } + [Resolved] private IAPIProvider api { get; set; } = null!; - private readonly BindableWithCurrent current = new BindableWithCurrent(); + private readonly BindableWithCurrent selectedItem = new BindableWithCurrent(); private readonly IBindable host = new Bindable(); private readonly bool allowEdit; private Drawable? editButton; @@ -52,12 +58,6 @@ namespace osu.Game.Screens.OnlinePlay.Match } } - public new required Bindable SelectedItem - { - get => current; - set => current.Current = value; - } - protected override void LoadComplete() { base.LoadComplete(); From 48212dfaebe9a5e941f3a1eac413b6ccfe488cf6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Nov 2024 21:08:06 +0900 Subject: [PATCH 804/924] Fix test failures due to early disposal of import stream --- .../Online/TestSceneReplayMissingBeatmap.cs | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneReplayMissingBeatmap.cs b/osu.Game.Tests/Visual/Online/TestSceneReplayMissingBeatmap.cs index 60197e0eb7..b986901dcf 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneReplayMissingBeatmap.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneReplayMissingBeatmap.cs @@ -40,15 +40,13 @@ namespace osu.Game.Tests.Visual.Online AddStep("import score", () => { - using (var resourceStream = TestResources.OpenResource("Replays/mania-replay.osr")) - { - var importTask = new ImportTask(resourceStream, "replay.osr"); + var resourceStream = TestResources.OpenResource("Replays/mania-replay.osr"); + var importTask = new ImportTask(resourceStream, "replay.osr"); - Game.ScoreManager.Import(new[] { importTask }); - } + Game.ScoreManager.Import(new[] { importTask }); }); - AddUntilStep("Replay missing notification show", () => Game.Notifications.ChildrenOfType().Any()); + AddUntilStep("Replay missing notification shown", () => Game.Notifications.ChildrenOfType().Any()); } [Test] @@ -58,15 +56,13 @@ namespace osu.Game.Tests.Visual.Online AddStep("import score", () => { - using (var resourceStream = TestResources.OpenResource("Replays/mania-replay.osr")) - { - var importTask = new ImportTask(resourceStream, "replay.osr"); + var resourceStream = TestResources.OpenResource("Replays/mania-replay.osr"); + var importTask = new ImportTask(resourceStream, "replay.osr"); - Game.ScoreManager.Import(new[] { importTask }); - } + Game.ScoreManager.Import(new[] { importTask }); }); - AddUntilStep("Replay missing notification not show", () => !Game.Notifications.ChildrenOfType().Any()); + AddUntilStep("Replay missing notification not shown", () => !Game.Notifications.ChildrenOfType().Any()); } private void setupBeatmapResponse(APIBeatmap b) From 84587564021072da9ecd70259a859b6965ac5bfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 12 Nov 2024 14:26:13 +0100 Subject: [PATCH 805/924] Adjust appearance a third time --- .../Blueprints/Components/EditBodyPiece.cs | 33 +++++++++++++++---- .../Components/EditHoldNoteEndPiece.cs | 3 -- .../Blueprints/Components/EditNotePiece.cs | 22 +++++-------- .../Blueprints/HoldNoteSelectionBlueprint.cs | 21 ++++-------- 4 files changed, 42 insertions(+), 37 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs index 6a12ec5088..652baccaa6 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs @@ -3,21 +3,42 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; +using osu.Game.Graphics.Backgrounds; using osu.Game.Rulesets.Mania.Skinning.Default; +using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components { - public partial class EditBodyPiece : DefaultBodyPiece + public partial class EditBodyPiece : CompositeDrawable { + private readonly Container border; + + public EditBodyPiece() + { + InternalChildren = new Drawable[] + { + border = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = 3, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + }, + }, + }; + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { - AccentColour.Value = colours.Yellow; - - Background.Alpha = 0.5f; + border.BorderColour = colours.YellowDarker; } - - protected override Drawable CreateForeground() => base.CreateForeground().With(d => d.Alpha = 0); } } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditHoldNoteEndPiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditHoldNoteEndPiece.cs index 285226e8b4..d4b61b4661 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditHoldNoteEndPiece.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditHoldNoteEndPiece.cs @@ -27,9 +27,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components { Height = DefaultNotePiece.NOTE_HEIGHT; - CornerRadius = 5; - Masking = true; - InternalChild = new EditNotePiece { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs index 3f15515ba6..1f9f26b5e2 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs @@ -2,12 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osu.Game.Rulesets.Mania.Skinning.Default; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; using osuTK; @@ -17,24 +15,21 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components { public partial class EditNotePiece : CompositeDrawable { + private readonly Container border; + private readonly Box box; + [Resolved] private Column? column { get; set; } public EditNotePiece() { - Masking = true; - CornerRadius = 5; - Height = DefaultNotePiece.NOTE_HEIGHT; - InternalChildren = new Drawable[] { - new Container + border = new Container { RelativeSizeAxes = Axes.Both, Masking = true, - CornerRadius = 5, - BorderThickness = 5, - BorderColour = Color4.White.Opacity(0.7f), + BorderThickness = 3, Child = new Box { RelativeSizeAxes = Axes.Both, @@ -42,10 +37,10 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components AlwaysPresent = true, }, }, - new Box + box = new Box { RelativeSizeAxes = Axes.X, - Height = 10, + Height = 3, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, }, @@ -55,7 +50,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components [BackgroundDependencyLoader] private void load(OsuColour colours) { - Colour = colours.Yellow; + border.BorderColour = colours.YellowDark; + box.Colour = colours.YellowLight; } protected override void Update() diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index 4e0b0cea6c..8c8d58654e 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -47,6 +47,12 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { InternalChildren = new Drawable[] { + new EditBodyPiece + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + }, head = new EditHoldNoteEndPiece { RelativeSizeAxes = Axes.X, @@ -88,21 +94,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints }, DragEnded = () => changeHandler?.EndChange(), }, - new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - BorderThickness = 1, - BorderColour = colours.Yellow, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true, - } - } }; } From af4a3785e91725d1aae10f141e4113807d879a4b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Nov 2024 14:38:34 +0900 Subject: [PATCH 806/924] Fix up code quality and isolate scale better --- osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs | 38 ++++++++------------ osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 14 ++++---- 2 files changed, 22 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs index e1b05fa84b..425db55c6e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs @@ -2,20 +2,18 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using osu.Framework.Bindables; using osu.Framework.Localisation; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.UI; +using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Play; -using osu.Game.Rulesets.Osu.UI.Cursor; -using osu.Framework.Utils; namespace osu.Game.Rulesets.Osu.Mods { @@ -32,10 +30,11 @@ namespace osu.Game.Rulesets.Osu.Mods protected readonly BindableNumber CurrentCombo = new BindableInt(); protected readonly IBindable IsBreakTime = new Bindable(); - protected float ComboBasedSize; + + private float currentSize; [SettingSource( - "Max Size at Combo", + "Max size at combo", "The combo count at which the cursor reaches its maximum size", SettingControlType = typeof(SettingsSlider>) )] @@ -46,11 +45,11 @@ namespace osu.Game.Rulesets.Osu.Mods }; [SettingSource( - "Final Size Multiplier", + "Final size multiplier", "The multiplier applied to cursor size when combo reaches maximum", SettingControlType = typeof(SettingsSlider>) )] - public BindableFloat MaxMulti { get; } = new BindableFloat(10f) + public BindableFloat MaxCursorSize { get; } = new BindableFloat(10f) { MinValue = 5f, MaxValue = 15f, @@ -69,27 +68,18 @@ namespace osu.Game.Rulesets.Osu.Mods CurrentCombo.BindTo(scoreProcessor.Combo); CurrentCombo.BindValueChanged(combo => { - ComboBasedSize = Math.Clamp(MaxMulti.Value * ((float)combo.NewValue / MaxSizeComboCount.Value), MIN_SIZE, MaxMulti.Value); + currentSize = Math.Clamp(MaxCursorSize.Value * ((float)combo.NewValue / MaxSizeComboCount.Value), MIN_SIZE, MaxCursorSize.Value); }, true); } public void Update(Playfield playfield) { - bool beBaseSize = IsBreakTime.Value; - OsuPlayfield osuPlayfield = (OsuPlayfield)playfield; - Debug.Assert(osuPlayfield.Cursor != null); + OsuCursor cursor = (OsuCursor)(playfield.Cursor!.ActiveCursor); - OsuCursor realCursor = (OsuCursor)osuPlayfield.Cursor.ActiveCursor; - float currentCombSize = (float)Interpolation.Lerp(realCursor.CursorScale.Value, ComboBasedSize, Math.Clamp(osuPlayfield.Time.Elapsed / TRANSITION_DURATION, 0, 1)); - - if (beBaseSize) - { - realCursor.UpdateSize(1); - } + if (IsBreakTime.Value) + cursor.ModScaleAdjust.Value = 1; else - { - realCursor.UpdateSize(currentCombSize); - } + cursor.ModScaleAdjust.Value = (float)Interpolation.Lerp(cursor.ModScaleAdjust.Value, currentSize, Math.Clamp(cursor.Time.Elapsed / TRANSITION_DURATION, 0, 1)); } } } diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index 545cd15d32..c2f7d84f5e 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -38,6 +38,11 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor public IBindable CursorScale => cursorScale; + /// + /// Mods which want to adjust cursor size should do so via this bindable. + /// + public readonly Bindable ModScaleAdjust = new Bindable(1); + private readonly Bindable cursorScale = new BindableFloat(1); private Bindable userCursorScale = null!; @@ -67,6 +72,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize); autoCursorScale.ValueChanged += _ => cursorScale.Value = CalculateCursorScale(); + ModScaleAdjust.ValueChanged += _ => cursorScale.Value = CalculateCursorScale(); + cursorScale.BindValueChanged(e => cursorScaleContainer.Scale = new Vector2(e.NewValue), true); } @@ -90,7 +97,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor protected virtual float CalculateCursorScale() { - float scale = userCursorScale.Value; + float scale = userCursorScale.Value * ModScaleAdjust.Value; if (autoCursorScale.Value && state != null) { @@ -101,11 +108,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor return scale; } - public void UpdateSize(float size) - { - cursorScale.Value = size; - } - protected override void SkinChanged(ISkinSource skin) { cursorExpand = skin.GetConfig(OsuSkinConfiguration.CursorExpand)?.Value ?? true; From db025d81eea10954d12ab31f11f2ea61116c853e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 15:50:01 +0900 Subject: [PATCH 807/924] Reorder public property vs private field --- osu.Game/Online/Rooms/Room.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 1f5fec6089..6a4d14189d 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -21,9 +21,6 @@ namespace osu.Game.Online.Rooms { public event PropertyChangedEventHandler? PropertyChanged; - [JsonProperty("current_playlist_item")] - private PlaylistItem? currentPlaylistItem; - /// /// Represents the current item selected within the room. /// @@ -36,6 +33,9 @@ namespace osu.Game.Online.Rooms set => SetField(ref currentPlaylistItem, value); } + [JsonProperty("current_playlist_item")] + private PlaylistItem? currentPlaylistItem; + [Cached] [JsonProperty("id")] public readonly Bindable RoomID = new Bindable(); From 99762da7b8fd5f1439d4a3e2ff5f8dd6b88964b9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 16:28:39 +0900 Subject: [PATCH 808/924] Make RoomID non-bindable Most important changes are to `RoomSubScreen` and `PlaylistsRoomSubScreen`, because those are the only two cases that now bind to the event instead. --- .../StatefulMultiplayerClientTest.cs | 2 +- .../DailyChallenge/TestSceneDailyChallenge.cs | 6 +- .../TestSceneDailyChallengeIntro.cs | 2 +- .../TestSceneDailyChallengeLeaderboard.cs | 4 +- .../Visual/Menus/TestSceneMainMenu.cs | 2 +- .../Multiplayer/TestSceneMatchLeaderboard.cs | 2 +- .../TestScenePlaylistsParticipantsList.cs | 2 +- .../UserInterface/TestSceneMainMenuButton.cs | 4 +- .../Online/Multiplayer/MultiplayerClient.cs | 4 +- osu.Game/Online/Rooms/JoinRoomRequest.cs | 2 +- osu.Game/Online/Rooms/PartRoomRequest.cs | 2 +- osu.Game/Online/Rooms/Room.cs | 18 ++++-- osu.Game/Screens/Menu/DailyChallengeButton.cs | 4 +- .../OnlinePlay/Components/RoomManager.cs | 28 ++++----- .../Components/SelectionPollingComponent.cs | 8 +-- .../DailyChallenge/DailyChallenge.cs | 10 ++-- .../DailyChallengeLeaderboard.cs | 2 +- .../Lounge/Components/RoomsContainer.cs | 23 ++++--- .../OnlinePlay/Lounge/LoungeSubScreen.cs | 8 +-- .../Match/Components/MatchChatDisplay.cs | 2 +- .../Match/Components/RoomSettingsOverlay.cs | 4 +- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 58 ++++++++++-------- .../Match/MultiplayerMatchSettingsOverlay.cs | 2 +- .../Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- .../Multiplayer/MultiplayerPlayer.cs | 23 ++++--- .../Multiplayer/MultiplayerRoomManager.cs | 12 ++-- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 - .../OnlinePlay/Playlists/PlaylistsPlayer.cs | 10 ++-- .../Playlists/PlaylistsRoomSettingsOverlay.cs | 2 +- .../Playlists/PlaylistsRoomSubScreen.cs | 60 ++++++++++++------- osu.Game/Screens/Play/RoomSubmittingPlayer.cs | 12 ++-- .../Multiplayer/TestMultiplayerClient.cs | 2 +- .../Visual/OnlinePlay/TestRoomManager.cs | 10 ++-- .../OnlinePlay/TestRoomRequestsHandler.cs | 6 +- osu.Game/Users/UserActivity.cs | 2 +- 35 files changed, 176 insertions(+), 167 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs index b4bbe274a5..2e9170019c 100644 --- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs +++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs @@ -75,7 +75,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer var newRoom = new Room(); newRoom.CopyFrom(SelectedRoom.Value); - newRoom.RoomID.Value = null; + newRoom.RoomID = null; MultiplayerClient.RoomSetupAction = room => { room.State = MultiplayerRoomState.Playing; diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs index 2791563954..488289cb74 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge { var room = new Room { - RoomID = { Value = 1234 }, + RoomID = 1234, Name = { Value = "Daily Challenge: June 4, 2024" }, Playlist = { @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge { var room = new Room { - RoomID = { Value = 1234 }, + RoomID = 1234, Name = { Value = "Daily Challenge: June 4, 2024" }, Playlist = { @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge { var room = new Room { - RoomID = { Value = 1234 }, + RoomID = 1234, Name = { Value = "Daily Challenge: June 4, 2024" }, Playlist = { diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs index f1a2d6b5f2..c08398ac17 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs @@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge { API.Perform(new CreateRoomRequest(room = new Room { - RoomID = { Value = roomId }, + RoomID = roomId, Name = { Value = "Daily Challenge: June 4, 2024" }, Playlist = { diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeLeaderboard.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeLeaderboard.cs index 5fff6bb010..d21ca22e1b 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeLeaderboard.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeLeaderboard.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge return false; }; }); - AddStep("create leaderboard", () => Child = leaderboard = new DailyChallengeLeaderboard(new Room { RoomID = { Value = 1 } }, new PlaylistItem(Beatmap.Value.BeatmapInfo)) + AddStep("create leaderboard", () => Child = leaderboard = new DailyChallengeLeaderboard(new Room { RoomID = 1 }, new PlaylistItem(Beatmap.Value.BeatmapInfo)) { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, @@ -86,7 +86,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge return false; }; }); - AddStep("create leaderboard", () => Child = leaderboard = new DailyChallengeLeaderboard(new Room { RoomID = { Value = 1 } }, new PlaylistItem(Beatmap.Value.BeatmapInfo)) + AddStep("create leaderboard", () => Child = leaderboard = new DailyChallengeLeaderboard(new Room { RoomID = 1 }, new PlaylistItem(Beatmap.Value.BeatmapInfo)) { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs index aab3716463..8b81516a34 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Menus beatmap.OnlineID = 1001; getRoomRequest.TriggerSuccess(new Room { - RoomID = { Value = 1234 }, + RoomID = 1234, Name = { Value = "Aug 8, 2024" }, Playlist = { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs index ea8fe8873d..f96e0bdd4c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs @@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create leaderboard", () => { - SelectedRoom.Value = new Room { RoomID = { Value = 3 } }; + SelectedRoom.Value = new Room { RoomID = 3 }; Child = new MatchLeaderboard { diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs index 3b60c28dc0..b9e5875e06 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("create list", () => { - SelectedRoom.Value = new Room { RoomID = { Value = 7 } }; + SelectedRoom.Value = new Room { RoomID = 7 }; for (int i = 0; i < 50; i++) { diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs index 41543669eb..3894b6ea5b 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.UserInterface beatmap.OnlineID = 1001; getRoomRequest.TriggerSuccess(new Room { - RoomID = { Value = 1234 }, + RoomID = 1234, Playlist = { new PlaylistItem(beatmap) @@ -131,7 +131,7 @@ namespace osu.Game.Tests.Visual.UserInterface beatmap.OnlineID = 1001; getRoomRequest.TriggerSuccess(new Room { - RoomID = { Value = 1234 }, + RoomID = 1234, Playlist = { new PlaylistItem(beatmap) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 5519d58060..bd0fa0cb72 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -181,10 +181,10 @@ namespace osu.Game.Online.Multiplayer await joinOrLeaveTaskChain.Add(async () => { - Debug.Assert(room.RoomID.Value != null); + Debug.Assert(room.RoomID != null); // Join the server-side room. - var joinedRoom = await JoinRoom(room.RoomID.Value.Value, password ?? room.Password.Value).ConfigureAwait(false); + var joinedRoom = await JoinRoom(room.RoomID.Value, password ?? room.Password.Value).ConfigureAwait(false); Debug.Assert(joinedRoom != null); // Populate users. diff --git a/osu.Game/Online/Rooms/JoinRoomRequest.cs b/osu.Game/Online/Rooms/JoinRoomRequest.cs index 9a73104b60..2ceb37fc01 100644 --- a/osu.Game/Online/Rooms/JoinRoomRequest.cs +++ b/osu.Game/Online/Rooms/JoinRoomRequest.cs @@ -27,6 +27,6 @@ namespace osu.Game.Online.Rooms return req; } - protected override string Target => $@"rooms/{Room.RoomID.Value}/users/{User!.Id}"; + protected override string Target => $@"rooms/{Room.RoomID}/users/{User!.Id}"; } } diff --git a/osu.Game/Online/Rooms/PartRoomRequest.cs b/osu.Game/Online/Rooms/PartRoomRequest.cs index 2416833a1e..77b5619efb 100644 --- a/osu.Game/Online/Rooms/PartRoomRequest.cs +++ b/osu.Game/Online/Rooms/PartRoomRequest.cs @@ -23,6 +23,6 @@ namespace osu.Game.Online.Rooms return req; } - protected override string Target => $"rooms/{room.RoomID.Value}/users/{User!.Id}"; + protected override string Target => $"rooms/{room.RoomID}/users/{User!.Id}"; } } diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 6a4d14189d..de854596e9 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -21,6 +21,15 @@ namespace osu.Game.Online.Rooms { public event PropertyChangedEventHandler? PropertyChanged; + /// + /// The online room ID. Will be null while the room has not yet been created. + /// + public long? RoomID + { + get => roomId; + set => SetField(ref roomId, value); + } + /// /// Represents the current item selected within the room. /// @@ -33,13 +42,12 @@ namespace osu.Game.Online.Rooms set => SetField(ref currentPlaylistItem, value); } + [JsonProperty("id")] + private long? roomId; + [JsonProperty("current_playlist_item")] private PlaylistItem? currentPlaylistItem; - [Cached] - [JsonProperty("id")] - public readonly Bindable RoomID = new Bindable(); - [Cached] [JsonProperty("name")] public readonly Bindable Name = new Bindable(); @@ -196,7 +204,7 @@ namespace osu.Game.Online.Rooms /// The to copy values from. public void CopyFrom(Room other) { - RoomID.Value = other.RoomID.Value; + RoomID = other.RoomID; Name.Value = other.Name.Value; Category.Value = other.Category.Value; diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs index 4dbebf0ae9..0013c9e033 100644 --- a/osu.Game/Screens/Menu/DailyChallengeButton.cs +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -155,9 +155,9 @@ namespace osu.Game.Screens.Menu Room = room; cover.OnlineInfo = TooltipContent = room.Playlist.FirstOrDefault()?.Beatmap.BeatmapSet as APIBeatmapSet; - if (room.StartDate.Value != null && room.RoomID.Value != lastDailyChallengeRoomID) + if (room.StartDate.Value != null && room.RoomID != lastDailyChallengeRoomID) { - lastDailyChallengeRoomID = room.RoomID.Value; + lastDailyChallengeRoomID = room.RoomID; // new challenge is live, reset intro played static. statics.SetValue(Static.DailyChallengeIntroPlayed, false); diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs index cb27d1ee61..555a1f66bd 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs @@ -1,13 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Development; @@ -20,18 +17,17 @@ namespace osu.Game.Screens.OnlinePlay.Components { public partial class RoomManager : Component, IRoomManager { - [CanBeNull] - public event Action RoomsUpdated; + public event Action? RoomsUpdated; private readonly BindableList rooms = new BindableList(); public IBindableList Rooms => rooms; - protected IBindable JoinedRoom => joinedRoom; - private readonly Bindable joinedRoom = new Bindable(); + protected IBindable JoinedRoom => joinedRoom; + private readonly Bindable joinedRoom = new Bindable(); [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; public RoomManager() { @@ -44,7 +40,7 @@ namespace osu.Game.Screens.OnlinePlay.Components PartRoom(); } - public virtual void CreateRoom(Room room, Action onSuccess = null, Action onError = null) + public virtual void CreateRoom(Room room, Action? onSuccess = null, Action? onError = null) { room.Host.Value = api.LocalUser.Value; @@ -69,9 +65,9 @@ namespace osu.Game.Screens.OnlinePlay.Components api.Queue(req); } - private JoinRoomRequest currentJoinRoomRequest; + private JoinRoomRequest? currentJoinRoomRequest; - public virtual void JoinRoom(Room room, string password = null, Action onSuccess = null, Action onError = null) + public virtual void JoinRoom(Room room, string? password = null, Action? onSuccess = null, Action? onError = null) { currentJoinRoomRequest?.Cancel(); currentJoinRoomRequest = new JoinRoomRequest(room, password); @@ -97,7 +93,7 @@ namespace osu.Game.Screens.OnlinePlay.Components { currentJoinRoomRequest?.Cancel(); - if (JoinedRoom.Value == null) + if (joinedRoom.Value == null) return; if (api.State.Value == APIState.Online) @@ -111,14 +107,14 @@ namespace osu.Game.Screens.OnlinePlay.Components public void AddOrUpdateRoom(Room room) { Debug.Assert(ThreadSafety.IsUpdateThread); - Debug.Assert(room.RoomID.Value != null); + Debug.Assert(room.RoomID != null); - if (ignoredRooms.Contains(room.RoomID.Value.Value)) + if (ignoredRooms.Contains(room.RoomID.Value)) return; try { - var existing = rooms.FirstOrDefault(e => e.RoomID.Value == room.RoomID.Value); + var existing = rooms.FirstOrDefault(e => e.RoomID == room.RoomID); if (existing == null) rooms.Add(room); else @@ -128,7 +124,7 @@ namespace osu.Game.Screens.OnlinePlay.Components { Logger.Error(ex, $"Failed to update room: {room.Name.Value}."); - ignoredRooms.Add(room.RoomID.Value.Value); + ignoredRooms.Add(room.RoomID.Value); rooms.Remove(room); } diff --git a/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs index 780ee29e41..8b80228ae1 100644 --- a/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs +++ b/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Threading.Tasks; using osu.Game.Online.Rooms; @@ -20,21 +18,21 @@ namespace osu.Game.Screens.OnlinePlay.Components this.room = room; } - private GetRoomRequest lastPollRequest; + private GetRoomRequest? lastPollRequest; protected override Task Poll() { if (!API.IsLoggedIn) return base.Poll(); - if (room.RoomID.Value == null) + if (room.RoomID == null) return base.Poll(); var tcs = new TaskCompletionSource(); lastPollRequest?.Cancel(); - var req = new GetRoomRequest(room.RoomID.Value.Value); + var req = new GetRoomRequest(room.RoomID.Value); req.Success += result => { diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 1aaf0a4321..968345c713 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -353,12 +353,12 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private void presentScore(long id) { if (this.IsCurrentScreen()) - this.Push(new PlaylistItemScoreResultsScreen(room.RoomID.Value!.Value, playlistItem, id)); + this.Push(new PlaylistItemScoreResultsScreen(room.RoomID!.Value, playlistItem, id)); } private void onRoomScoreSet(MultiplayerRoomScoreSetEvent e) { - if (e.RoomID != room.RoomID.Value || e.PlaylistItemID != playlistItem.ID) + if (e.RoomID != room.RoomID || e.PlaylistItemID != playlistItem.ID) return; userLookupCache.GetUserAsync(e.UserID).ContinueWith(t => @@ -410,7 +410,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private void dailyChallengeChanged(ValueChangedEvent change) { - if (change.OldValue?.RoomID == room.RoomID.Value && change.NewValue == null && metadataClient.IsConnected.Value) + if (change.OldValue?.RoomID == room.RoomID && change.NewValue == null && metadataClient.IsConnected.Value) { notificationOverlay?.Post(new SimpleNotification { Text = DailyChallengeStrings.ChallengeEndedNotification }); } @@ -437,7 +437,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge roomManager.JoinRoom(room); startLoopingTrack(this, musicController); - metadataClient.BeginWatchingMultiplayerRoom(room.RoomID.Value!.Value).ContinueWith(t => + metadataClient.BeginWatchingMultiplayerRoom(room.RoomID!.Value).ContinueWith(t => { if (t.Exception != null) { @@ -489,7 +489,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge this.Delay(WaveContainer.DISAPPEAR_DURATION).FadeOut(); roomManager.PartRoom(); - metadataClient.EndWatchingMultiplayerRoom(room.RoomID.Value!.Value).FireAndForget(); + metadataClient.EndWatchingMultiplayerRoom(room.RoomID!.Value).FireAndForget(); return base.OnExiting(e); } diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs index c9152393e7..9fe2b70a5a 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs @@ -138,7 +138,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge if (request?.CompletionState == APIRequestCompletionState.Waiting) return; - request = new IndexPlaylistScoresRequest(room.RoomID.Value!.Value, playlistItem.ID); + request = new IndexPlaylistScoresRequest(room.RoomID!.Value, playlistItem.ID); request.Success += req => Schedule(() => { diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 9e60f43ed7..3d9b7a46d0 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Collections.Specialized; @@ -11,6 +9,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; @@ -24,8 +23,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { public partial class RoomsContainer : CompositeDrawable, IKeyBindingHandler { - public readonly Bindable SelectedRoom = new Bindable(); - public readonly Bindable Filter = new Bindable(); + public readonly Bindable SelectedRoom = new Bindable(); + public readonly Bindable Filter = new Bindable(); public IReadOnlyList Rooms => roomFlow.FlowingChildren.Cast().ToArray(); @@ -33,7 +32,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private readonly FillFlowContainer roomFlow; [Resolved] - private IRoomManager roomManager { get; set; } + private IRoomManager roomManager { get; set; } = null!; // handle deselection public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; @@ -67,10 +66,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components rooms.BindTo(roomManager.Rooms); - Filter?.BindValueChanged(criteria => applyFilterCriteria(criteria.NewValue), true); + Filter.BindValueChanged(criteria => applyFilterCriteria(criteria.NewValue), true); } - private void applyFilterCriteria(FilterCriteria criteria) + private void applyFilterCriteria(FilterCriteria? criteria) { roomFlow.Children.ForEach(r => { @@ -113,7 +112,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components } } - private void roomsChanged(object sender, NotifyCollectionChangedEventArgs args) + private void roomsChanged(object? sender, NotifyCollectionChangedEventArgs args) { switch (args.Action) { @@ -142,7 +141,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components foreach (var room in rooms) roomFlow.Add(new DrawableLoungeRoom(room) { SelectedRoom = SelectedRoom }); - applyFilterCriteria(Filter?.Value); + applyFilterCriteria(Filter.Value); } private void removeRooms(IEnumerable rooms) @@ -173,7 +172,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components roomFlow.SetLayoutPosition(room, room.Room.Category.Value > RoomCategory.Normal // Always show spotlight playlists at the top of the listing. ? float.MinValue - : -(room.Room.RoomID.Value ?? 0)); + : -(room.Room.RoomID ?? 0)); } } @@ -213,7 +212,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components var visibleRooms = Rooms.AsEnumerable().Where(r => r.IsPresent); - Room room; + Room? room; if (SelectedRoom.Value == null) room = visibleRooms.FirstOrDefault()?.Room; @@ -236,7 +235,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { base.Dispose(isDisposing); - if (roomManager != null) + if (roomManager.IsNotNull()) roomManager.RoomsUpdated -= updateSorting; } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index 3792a67896..a2eba3188d 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -259,7 +259,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge selectionLease.Return(); selectionLease = null; - if (SelectedRoom.Value?.RoomID.Value == null) + if (SelectedRoom.Value?.RoomID == null) SelectedRoom.Value = new Room(); music?.EnsurePlayingSomething(); @@ -326,19 +326,19 @@ namespace osu.Game.Screens.OnlinePlay.Lounge /// The room to copy. public void OpenCopy(Room room) { - Debug.Assert(room.RoomID.Value != null); + Debug.Assert(room.RoomID != null); if (joiningRoomOperation != null) return; joiningRoomOperation = ongoingOperationTracker?.BeginOperation(); - var req = new GetRoomRequest(room.RoomID.Value.Value); + var req = new GetRoomRequest(room.RoomID.Value); req.Success += r => { // ID must be unset as we use this as a marker for whether this is a client-side (not-yet-created) room or not. - r.RoomID.Value = null; + r.RoomID = null; // Null out dates because end date is not supported client-side and the settings overlay will populate a duration. r.EndDate.Value = null; diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchChatDisplay.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchChatDisplay.cs index 8dc1704fcd..ed4f0ad6d8 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchChatDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchChatDisplay.cs @@ -36,7 +36,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components private void updateChannel() { - if (room.RoomID.Value == null || channelId.Value == 0) + if (room.RoomID == null || channelId.Value == 0) return; Channel.Value = channelManager?.JoinChannel(new Channel { Id = channelId.Value, Type = ChannelType.Multiplayer, Name = $"#lazermp_{room.RoomID.Value}" }); diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs index 916b799d50..14d506a368 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -23,7 +21,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components protected const float TRANSITION_DURATION = 350; protected const float FIELD_PADDING = 25; - protected OnlinePlayComposite Settings { get; set; } + protected OnlinePlayComposite Settings { get; set; } = null!; protected override bool BlockScrollInput => false; diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 9801405dd3..1b7df9c213 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.Linq; using JetBrains.Annotations; @@ -29,6 +30,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Screens.Menu; using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.OnlinePlay.Multiplayer; +using Container = osu.Framework.Graphics.Containers.Container; namespace osu.Game.Screens.OnlinePlay.Match { @@ -59,8 +61,6 @@ namespace osu.Game.Screens.OnlinePlay.Match /// protected readonly Bindable> UserMods = new Bindable>(Array.Empty()); - protected readonly IBindable RoomId = new Bindable(); - [Resolved(CanBeNull = true)] private IOverlayManager overlayManager { get; set; } @@ -82,6 +82,9 @@ namespace osu.Game.Screens.OnlinePlay.Match [Resolved] private PreviewTrackManager previewTrackManager { get; set; } = null!; + [Resolved(canBeNull: true)] + private IDialogOverlay dialogOverlay { get; set; } + [Cached] private readonly OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); @@ -109,8 +112,6 @@ namespace osu.Game.Screens.OnlinePlay.Match this.allowEdit = allowEdit; Padding = new MarginPadding { Top = Header.HEIGHT }; - - RoomId.BindTo(room.RoomID); } [BackgroundDependencyLoader] @@ -252,22 +253,6 @@ namespace osu.Game.Screens.OnlinePlay.Match { base.LoadComplete(); - RoomId.BindValueChanged(id => - { - if (id.NewValue == null) - { - // A new room is being created. - // The main content should be hidden until the settings overlay is hidden, signaling the room is ready to be displayed. - mainContent.Hide(); - settingsOverlay.Show(); - } - else - { - mainContent.Show(); - settingsOverlay.Hide(); - } - }, true); - SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(selectedItemChanged)); UserMods.BindValueChanged(_ => Scheduler.AddOnce(UpdateMods)); @@ -275,6 +260,31 @@ namespace osu.Game.Screens.OnlinePlay.Match beatmapAvailabilityTracker.Availability.BindValueChanged(_ => updateWorkingBeatmap()); userModsSelectOverlayRegistration = overlayManager?.RegisterBlockingOverlay(UserModsSelectOverlay); + + Room.PropertyChanged += onRoomPropertyChanged; + updateSetupState(); + } + + private void onRoomPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.RoomID)) + updateSetupState(); + } + + private void updateSetupState() + { + if (Room.RoomID == null) + { + // A new room is being created. + // The main content should be hidden until the settings overlay is hidden, signaling the room is ready to be displayed. + mainContent.Hide(); + settingsOverlay.Show(); + } + else + { + mainContent.Show(); + settingsOverlay.Hide(); + } } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) @@ -285,14 +295,11 @@ namespace osu.Game.Screens.OnlinePlay.Match }; } - [Resolved(canBeNull: true)] - private IDialogOverlay dialogOverlay { get; set; } - protected virtual bool IsConnected => api.State.Value == APIState.Online; public override bool OnBackButton() { - if (Room.RoomID.Value == null) + if (Room.RoomID == null) { if (!ensureExitConfirmed()) return true; @@ -365,7 +372,7 @@ namespace osu.Game.Screens.OnlinePlay.Match if (!IsConnected) return true; - bool hasUnsavedChanges = Room.RoomID.Value == null && Room.Playlist.Count > 0; + bool hasUnsavedChanges = Room.RoomID == null && Room.Playlist.Count > 0; if (dialogOverlay == null || !hasUnsavedChanges) return true; @@ -538,6 +545,7 @@ namespace osu.Game.Screens.OnlinePlay.Match base.Dispose(isDisposing); userModsSelectOverlayRegistration?.Dispose(); + Room.PropertyChanged -= onRoomPropertyChanged; } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 7a1868bfbb..edcad9c3ac 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -353,7 +353,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match RoomName.BindValueChanged(name => NameField.Text = name.NewValue, true); Type.BindValueChanged(type => TypePicker.Current.Value = type.NewValue, true); MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); - RoomID.BindValueChanged(roomId => playlistContainer.Alpha = roomId.NewValue == null ? 1 : 0, true); Password.BindValueChanged(password => PasswordTextBox.Text = password.NewValue ?? string.Empty, true); QueueMode.BindValueChanged(mode => QueueModeDropdown.Current.Value = mode.NewValue, true); AutoStartDuration.BindValueChanged(duration => startModeDropdown.Current.Value = (StartMode)(int)duration.NewValue.TotalSeconds, true); @@ -382,6 +381,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match base.Update(); ApplyButton.Enabled.Value = Playlist.Count > 0 && NameField.Text.Length > 0 && !operationInProgress.Value; + playlistContainer.Alpha = room.RoomID == null ? 1 : 0; } private void apply() diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index e0c8675ec5..696eb58c3f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public MultiplayerMatchSubScreen(Room room) : base(room) { - Title = room.RoomID.Value == null ? "New room" : room.Name.Value; + Title = room.RoomID == null ? "New room" : room.Name.Value; Activity.Value = new UserActivity.InLobby(room); } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index e560c5ca5d..d444af2fd3 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -1,14 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Graphics.UserInterface; @@ -29,17 +28,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override UserActivity InitialActivity => new UserActivity.InMultiplayerGame(Beatmap.Value.BeatmapInfo, Ruleset.Value); [Resolved] - private MultiplayerClient client { get; set; } + private MultiplayerClient client { get; set; } = null!; - private IBindable isConnected; + private IBindable isConnected = null!; private readonly TaskCompletionSource resultsReady = new TaskCompletionSource(); - private readonly MultiplayerRoomUser[] users; - private LoadingLayer loadingDisplay; - - private MultiplayerGameplayLeaderboard multiplayerLeaderboard; + private LoadingLayer loadingDisplay = null!; + private MultiplayerGameplayLeaderboard multiplayerLeaderboard = null!; /// /// Construct a multiplayer player. @@ -153,7 +150,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer GameplayClockContainer.Reset(); } - private void failAndBail(string message = null) + private void failAndBail(string? message = null) { if (!string.IsNullOrEmpty(message)) Logger.Log(message, LoggingTarget.Runtime, LogLevel.Important); @@ -196,14 +193,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override ResultsScreen CreateResults(ScoreInfo score) { - Debug.Assert(Room.RoomID.Value != null); + Debug.Assert(Room.RoomID != null); return multiplayerLeaderboard.TeamScores.Count == 2 - ? new MultiplayerTeamResultsScreen(score, Room.RoomID.Value.Value, PlaylistItem, multiplayerLeaderboard.TeamScores) + ? new MultiplayerTeamResultsScreen(score, Room.RoomID.Value, PlaylistItem, multiplayerLeaderboard.TeamScores) { ShowUserStatistics = true, } - : new MultiplayerResultsScreen(score, Room.RoomID.Value.Value, PlaylistItem) + : new MultiplayerResultsScreen(score, Room.RoomID.Value, PlaylistItem) { ShowUserStatistics = true }; @@ -213,7 +210,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { base.Dispose(isDisposing); - if (client != null) + if (client.IsNotNull()) { client.GameplayStarted -= onGameplayStarted; client.ResultsReady -= onResultsReady; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs index 5f51ccc8d4..d7efd30435 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Diagnostics; using osu.Framework.Allocation; @@ -18,12 +16,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public partial class MultiplayerRoomManager : RoomManager { [Resolved] - private MultiplayerClient multiplayerClient { get; set; } + private MultiplayerClient multiplayerClient { get; set; } = null!; - public override void CreateRoom(Room room, Action onSuccess = null, Action onError = null) + public override void CreateRoom(Room room, Action? onSuccess = null, Action? onError = null) => base.CreateRoom(room, r => joinMultiplayerRoom(r, r.Password.Value, onSuccess, onError), onError); - public override void JoinRoom(Room room, string password = null, Action onSuccess = null, Action onError = null) + public override void JoinRoom(Room room, string? password = null, Action? onSuccess = null, Action? onError = null) { if (!multiplayerClient.IsConnected.Value) { @@ -51,9 +49,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer multiplayerClient.LeaveRoom(); } - private void joinMultiplayerRoom(Room room, string password, Action onSuccess = null, Action onError = null) + private void joinMultiplayerRoom(Room room, string? password, Action? onSuccess = null, Action? onError = null) { - Debug.Assert(room.RoomID.Value != null); + Debug.Assert(room.RoomID != null); multiplayerClient.JoinRoom(room, password).ContinueWith(t => { diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 0d96f348ac..9911a08b81 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -16,9 +16,6 @@ namespace osu.Game.Screens.OnlinePlay /// public partial class OnlinePlayComposite : CompositeDrawable { - [Resolved(typeof(Room))] - protected Bindable RoomID { get; private set; } = null!; - [Resolved(typeof(Room), nameof(Room.Name))] protected Bindable RoomName { get; private set; } = null!; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index 4a2d8f8f6b..7ca09b5563 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Diagnostics; using System.Linq; @@ -21,11 +19,11 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { public partial class PlaylistsPlayer : RoomSubmittingPlayer { - public Action Exited; + public Action? Exited; protected override UserActivity InitialActivity => new UserActivity.InPlaylistGame(Beatmap.Value.BeatmapInfo, Ruleset.Value); - public PlaylistsPlayer(Room room, PlaylistItem playlistItem, PlayerConfiguration configuration = null) + public PlaylistsPlayer(Room room, PlaylistItem playlistItem, PlayerConfiguration? configuration = null) : base(room, playlistItem, configuration) { } @@ -57,8 +55,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists protected override ResultsScreen CreateResults(ScoreInfo score) { - Debug.Assert(Room.RoomID.Value != null); - return new PlaylistItemUserResultsScreen(score, Room.RoomID.Value.Value, PlaylistItem) + Debug.Assert(Room.RoomID != null); + return new PlaylistItemUserResultsScreen(score, Room.RoomID.Value, PlaylistItem) { AllowRetry = true, ShowUserStatistics = true, diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index 9166cac9de..4d85206473 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -372,7 +372,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private void onPlaylistChanged(object? sender, NotifyCollectionChangedEventArgs e) => playlistLength.Text = $"Length: {Playlist.GetTotalDuration(rulesets)}"; - private bool hasValidSettings => RoomID.Value == null && NameField.Text.Length > 0 && Playlist.Count > 0 + private bool hasValidSettings => room.RoomID == null && NameField.Text.Length > 0 && Playlist.Count > 0 && hasValidDuration; private bool hasValidDuration => DurationField.Current.Value <= TimeSpan.FromDays(14) || localUser.Value.IsSupporter; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 3126bbf2eb..9667e210a4 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -1,11 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - +using System.ComponentModel; using System.Diagnostics; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -22,6 +20,7 @@ using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Users; using osuTK; +using Container = osu.Framework.Graphics.Containers.Container; namespace osu.Game.Screens.OnlinePlay.Playlists { @@ -33,20 +32,22 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private readonly IBindable isIdle = new BindableBool(); - private MatchLeaderboard leaderboard; - private SelectionPollingComponent selectionPollingComponent; + [Resolved(CanBeNull = true)] + private IdleTracker? idleTracker { get; set; } - private FillFlowContainer progressSection; + private MatchLeaderboard leaderboard = null!; + private SelectionPollingComponent selectionPollingComponent = null!; + private FillFlowContainer progressSection = null!; public PlaylistsRoomSubScreen(Room room) : base(room, false) // Editing is temporarily not allowed. { - Title = room.RoomID.Value == null ? "New playlist" : room.Name.Value; + Title = room.RoomID == null ? "New playlist" : room.Name.Value; Activity.Value = new UserActivity.InLobby(room); } - [BackgroundDependencyLoader(true)] - private void load([CanBeNull] IdleTracker idleTracker) + [BackgroundDependencyLoader] + private void load() { if (idleTracker != null) isIdle.BindTo(idleTracker.IsIdle); @@ -59,17 +60,26 @@ namespace osu.Game.Screens.OnlinePlay.Playlists base.LoadComplete(); isIdle.BindValueChanged(_ => updatePollingRate(), true); - RoomId.BindValueChanged(id => - { - if (id.NewValue != null) - { - // Set the first playlist item. - // This is scheduled since updating the room and playlist may happen in an arbitrary order (via Room.CopyFrom()). - Schedule(() => SelectedItem.Value = Room.Playlist.FirstOrDefault()); - } - }, true); - Room.MaxAttempts.BindValueChanged(_ => progressSection.Alpha = Room.MaxAttempts.Value != null ? 1 : 0, true); + + Room.PropertyChanged += onRoomPropertyChanged; + updateSetupState(); + } + + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.RoomID)) + updateSetupState(); + } + + private void updateSetupState() + { + if (Room.RoomID != null) + { + // Set the first playlist item. + // This is scheduled since updating the room and playlist may happen in an arbitrary order (via Room.CopyFrom()). + Schedule(() => SelectedItem.Value = Room.Playlist.FirstOrDefault()); + } } protected override Drawable CreateMainContent() => new Container @@ -92,7 +102,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists }, Content = new[] { - new Drawable[] + new Drawable?[] { // Playlist items column new GridContainer @@ -113,8 +123,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists AllowShowingResults = true, RequestResults = item => { - Debug.Assert(RoomId.Value != null); - ParentScreen?.Push(new PlaylistItemUserResultsScreen(null, RoomId.Value.Value, item)); + Debug.Assert(Room.RoomID != null); + ParentScreen?.Push(new PlaylistItemUserResultsScreen(null, Room.RoomID.Value, item)); } } }, @@ -248,5 +258,11 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { Exited = () => leaderboard.RefetchScores() }); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + Room.PropertyChanged -= onRoomPropertyChanged; + } } } diff --git a/osu.Game/Screens/Play/RoomSubmittingPlayer.cs b/osu.Game/Screens/Play/RoomSubmittingPlayer.cs index 3f74f49384..74ee7e1868 100644 --- a/osu.Game/Screens/Play/RoomSubmittingPlayer.cs +++ b/osu.Game/Screens/Play/RoomSubmittingPlayer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Diagnostics; using osu.Game.Extensions; using osu.Game.Online.API; @@ -19,16 +17,16 @@ namespace osu.Game.Screens.Play protected readonly PlaylistItem PlaylistItem; protected readonly Room Room; - protected RoomSubmittingPlayer(Room room, PlaylistItem playlistItem, PlayerConfiguration configuration = null) + protected RoomSubmittingPlayer(Room room, PlaylistItem playlistItem, PlayerConfiguration? configuration = null) : base(configuration) { Room = room; PlaylistItem = playlistItem; } - protected override APIRequest CreateTokenRequest() + protected override APIRequest? CreateTokenRequest() { - if (!(Room.RoomID.Value is long roomId)) + if (Room.RoomID is not long roomId) return null; int beatmapId = Beatmap.Value.BeatmapInfo.OnlineID; @@ -45,8 +43,8 @@ namespace osu.Game.Screens.Play protected override APIRequest CreateSubmissionRequest(Score score, long token) { - Debug.Assert(Room.RoomID.Value != null); - return new SubmitRoomScoreRequest(score.ScoreInfo, token, Room.RoomID.Value.Value, PlaylistItem.ID); + Debug.Assert(Room.RoomID != null); + return new SubmitRoomScoreRequest(score.ScoreInfo, token, Room.RoomID.Value, PlaylistItem.ID); } } } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 65a4019bb5..0c17998487 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -214,7 +214,7 @@ namespace osu.Game.Tests.Visual.Multiplayer roomId = clone(roomId); password = clone(password); - ServerAPIRoom = roomManager.ServerSideRooms.Single(r => r.RoomID.Value == roomId); + ServerAPIRoom = roomManager.ServerSideRooms.Single(r => r.RoomID == roomId); if (password != ServerAPIRoom.Password.Value) throw new InvalidOperationException("Invalid password."); diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs index e9980e822c..2ce511928f 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Game.Beatmaps; using osu.Game.Online.API.Requests.Responses; @@ -17,23 +15,23 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// public partial class TestRoomManager : RoomManager { - public Action JoinRoomRequested; + public Action? JoinRoomRequested; private int currentRoomId; - public override void JoinRoom(Room room, string password = null, Action onSuccess = null, Action onError = null) + public override void JoinRoom(Room room, string? password = null, Action? onSuccess = null, Action? onError = null) { JoinRoomRequested?.Invoke(room, password); base.JoinRoom(room, password, onSuccess, onError); } - public void AddRooms(int count, RulesetInfo ruleset = null, bool withPassword = false, bool withSpotlightRooms = false) + public void AddRooms(int count, RulesetInfo? ruleset = null, bool withPassword = false, bool withSpotlightRooms = false) { for (int i = 0; i < count; i++) { var room = new Room { - RoomID = { Value = -currentRoomId }, + RoomID = -currentRoomId, Name = { Value = $@"Room {currentRoomId}" }, Host = { Value = new APIUser { Username = @"Host" } }, EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }, diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index 41d288ba60..cc8bd27d91 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay case JoinRoomRequest joinRoomRequest: { - var room = ServerSideRooms.Single(r => r.RoomID.Value == joinRoomRequest.Room.RoomID.Value); + var room = ServerSideRooms.Single(r => r.RoomID == joinRoomRequest.Room.RoomID); if (joinRoomRequest.Password != room.Password.Value) { @@ -162,7 +162,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay return true; case GetRoomRequest getRoomRequest: - getRoomRequest.TriggerSuccess(createResponseRoom(ServerSideRooms.Single(r => r.RoomID.Value == getRoomRequest.RoomId), true)); + getRoomRequest.TriggerSuccess(createResponseRoom(ServerSideRooms.Single(r => r.RoomID == getRoomRequest.RoomId), true)); return true; case CreateRoomScoreRequest createRoomScoreRequest: @@ -261,7 +261,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// The room host. public void AddServerSideRoom(Room room, APIUser host) { - room.RoomID.Value ??= currentRoomId++; + room.RoomID ??= currentRoomId++; room.Host.Value = host; for (int i = 0; i < room.Playlist.Count; i++) diff --git a/osu.Game/Users/UserActivity.cs b/osu.Game/Users/UserActivity.cs index a431b204bc..ac0672c10f 100644 --- a/osu.Game/Users/UserActivity.cs +++ b/osu.Game/Users/UserActivity.cs @@ -248,7 +248,7 @@ namespace osu.Game.Users public InLobby(Room room) { - RoomID = room.RoomID.Value ?? -1; + RoomID = room.RoomID ?? -1; RoomName = room.Name.Value; } From 5b2568d18b973b2fda1ef56bfbaf0a78ad92c136 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 16:38:49 +0900 Subject: [PATCH 809/924] Fix cases where the bindable is resolved directly --- .../Multiplayer/TestSceneMatchLeaderboard.cs | 2 +- .../Match/Components/MatchLeaderboard.cs | 49 +++++++++++++------ .../Match/MultiplayerMatchSettingsOverlay.cs | 17 ++++--- .../Playlists/PlaylistsRoomSubScreen.cs | 2 +- 4 files changed, 47 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs index f96e0bdd4c..38522db4d4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { SelectedRoom.Value = new Room { RoomID = 3 }; - Child = new MatchLeaderboard + Child = new MatchLeaderboard(SelectedRoom.Value) { Origin = Anchor.Centre, Anchor = Anchor.Centre, diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs index 4627cd4072..a7148abcde 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs @@ -1,9 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.ComponentModel; using System.Threading; -using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; @@ -13,30 +12,44 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components { public partial class MatchLeaderboard : Leaderboard { - [Resolved(typeof(Room), nameof(Room.RoomID))] - private Bindable roomId { get; set; } = null!; + private readonly Room room; - [BackgroundDependencyLoader] - private void load() + public MatchLeaderboard(Room room) { - roomId.BindValueChanged(id => - { - if (id.NewValue == null) - return; + this.room = room; + } - SetScores(null); - RefetchScores(); - }, true); + protected override void LoadComplete() + { + base.LoadComplete(); + + room.PropertyChanged += onRoomPropertyChanged; + fetchInitialScores(); + } + + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.RoomID)) + fetchInitialScores(); + } + + private void fetchInitialScores() + { + if (room.RoomID == null) + return; + + SetScores(null); + RefetchScores(); } protected override bool IsOnlineScope => true; protected override APIRequest? FetchScores(CancellationToken cancellationToken) { - if (roomId.Value == null) + if (room.RoomID == null) return null; - var req = new GetRoomLeaderboardRequest(roomId.Value ?? 0); + var req = new GetRoomLeaderboardRequest(room.RoomID.Value); req.Success += r => Schedule(() => { @@ -52,6 +65,12 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components protected override LeaderboardScore CreateDrawableScore(APIUserScoreAggregate model, int index) => new MatchLeaderboardScore(model, index); protected override LeaderboardScore CreateDrawableTopScore(APIUserScoreAggregate model) => new MatchLeaderboardScore(model, model.Position, false); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } } public enum MatchLeaderboardScope diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index edcad9c3ac..3e80a5531a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -323,7 +323,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match Padding = new MarginPadding { Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING }, Children = new Drawable[] { - ApplyButton = new CreateOrUpdateButton + ApplyButton = new CreateOrUpdateButton(room) { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, @@ -471,13 +471,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match public partial class CreateOrUpdateButton : RoundedButton { - [Resolved(typeof(Room), nameof(Room.RoomID))] - private Bindable roomId { get; set; } = null!; + private readonly Room room; - protected override void LoadComplete() + public CreateOrUpdateButton(Room room) { - base.LoadComplete(); - roomId.BindValueChanged(id => Text = id.NewValue == null ? "Create" : "Update", true); + this.room = room; } [BackgroundDependencyLoader] @@ -485,6 +483,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { BackgroundColour = colours.YellowDark; } + + protected override void Update() + { + base.Update(); + + Text = room.RoomID == null ? "Create" : "Update"; + } } private enum StartMode diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 9667e210a4..a53a89d601 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -201,7 +201,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { new OverlinedHeader("Leaderboard") }, - new Drawable[] { leaderboard = new MatchLeaderboard { RelativeSizeAxes = Axes.Both }, }, + new Drawable[] { leaderboard = new MatchLeaderboard(Room) { RelativeSizeAxes = Axes.Both }, }, }, RowDimensions = new[] { From a2a930aa3592065d565f6716df2e3dea0d934ef5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 17:15:50 +0900 Subject: [PATCH 810/924] Fix CI issues --- .../Multiplayer/TestSceneLoungeRoomsContainer.cs | 6 +++--- .../Visual/Multiplayer/TestSceneMultiplayerPlayer.cs | 10 ++++------ .../OnlinePlay/Components/ListingPollingComponent.cs | 6 ++---- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index d518565be9..112fdfd9ab 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -58,14 +58,14 @@ namespace osu.Game.Tests.Visual.Multiplayer .SkipWhile(r => r.Room.Category.Value == RoomCategory.Spotlight) .All(r => r.Room.Category.Value == RoomCategory.Normal)); - AddStep("remove first room", () => RoomManager.RemoveRoom(RoomManager.Rooms.First(r => r.RoomID.Value == 0))); + AddStep("remove first room", () => RoomManager.RemoveRoom(RoomManager.Rooms.First(r => r.RoomID == 0))); AddAssert("has 4 rooms", () => container.Rooms.Count == 4); - AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID.Value != 0)); + AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID != 0)); AddStep("select first room", () => container.Rooms.First().TriggerClick()); AddAssert("first spotlight selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category.Value == RoomCategory.Spotlight))); - AddStep("remove last room", () => RoomManager.RemoveRoom(RoomManager.Rooms.MinBy(r => r.RoomID.Value))); + AddStep("remove last room", () => RoomManager.RemoveRoom(RoomManager.Rooms.MinBy(r => r.RoomID))); AddAssert("first spotlight still selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category.Value == RoomCategory.Spotlight))); AddStep("remove spotlight room", () => RoomManager.RemoveRoom(RoomManager.Rooms.Single(r => r.Category.Value == RoomCategory.Spotlight))); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs index aaf85dab7c..94dd114c32 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -22,7 +20,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneMultiplayerPlayer : MultiplayerTestScene { - private MultiplayerPlayer player; + private MultiplayerPlayer player = null!; [Test] public void TestGameplay() @@ -49,7 +47,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("score changed", () => player.GameplayState.ScoreProcessor.TotalScore.Value > 0); } - private void setup(Func> mods = null) + private void setup(Func>? mods = null) { AddStep("set beatmap", () => { @@ -64,10 +62,10 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("initialise gameplay", () => { - Stack.Push(player = new MultiplayerPlayer(MultiplayerClient.ServerAPIRoom, new PlaylistItem(Beatmap.Value.BeatmapInfo) + Stack.Push(player = new MultiplayerPlayer(MultiplayerClient.ServerAPIRoom!, new PlaylistItem(Beatmap.Value.BeatmapInfo) { RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID, - }, MultiplayerClient.ServerRoom?.Users.ToArray())); + }, MultiplayerClient.ServerRoom!.Users.ToArray())); }); AddUntilStep("wait for player to be current", () => player.IsCurrentScreen() && player.IsLoaded); diff --git a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs index 4b38ea68b3..0020a7dfb6 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; @@ -35,7 +33,7 @@ namespace osu.Game.Screens.OnlinePlay.Components }); } - private GetRoomsRequest lastPollRequest; + private GetRoomsRequest? lastPollRequest; protected override Task Poll() { @@ -57,7 +55,7 @@ namespace osu.Game.Screens.OnlinePlay.Components foreach (var existing in RoomManager.Rooms.ToArray()) { - if (result.All(r => r.RoomID.Value != existing.RoomID.Value)) + if (result.All(r => r.RoomID != existing.RoomID)) RoomManager.RemoveRoom(existing); } From 2ae8c36d17fb6a5067a1ccb03e0fb7cd194496e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Nov 2024 17:39:21 +0900 Subject: [PATCH 811/924] Increase idle time before gameplay loads when hovering controls --- osu.Game/Screens/Play/PlayerLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 9b4bf7d633..f5671cd934 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -224,7 +224,7 @@ namespace osu.Game.Screens.Play } }, }, - idleTracker = new IdleTracker(750), + idleTracker = new IdleTracker(1500), sampleRestart = new SkinnableSound(new SampleInfo(@"Gameplay/restart", @"pause-retry-click")) }; From 2dbc275bb8e27cab2370eff682422a1be1d1fc8c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Nov 2024 17:54:05 +0900 Subject: [PATCH 812/924] Make quick restart even faster --- osu.Game/Screens/Play/PlayerLoader.cs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 9b4bf7d633..51268e5dce 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -51,6 +51,8 @@ namespace osu.Game.Screens.Play public override bool? AllowGlobalTrackControl => false; + public override float BackgroundParallaxAmount => quickRestart ? 0 : 1; + // Here because IsHovered will not update unless we do so. public override bool HandlePositionalInput => true; @@ -387,7 +389,7 @@ namespace osu.Game.Screens.Play // We need to perform this check here rather than in OnHover as any number of children of VisualSettings // may also be handling the hover events. - if (inputManager.HoveredDrawables.Contains(VisualSettings)) + if (inputManager.HoveredDrawables.Contains(VisualSettings) || quickRestart) { // Preview user-defined background dim and blur when hovered on the visual settings panel. ApplyToBackground(b => @@ -458,8 +460,9 @@ namespace osu.Game.Screens.Play if (quickRestart) { - prepareNewPlayer(); - content.ScaleTo(1, 650, Easing.OutQuint); + // A slight delay is added here to avoid an awkward stutter during the initial animation. + Scheduler.AddDelayed(prepareNewPlayer, 100); + content.ScaleTo(1); } else { @@ -467,15 +470,15 @@ namespace osu.Game.Screens.Play .ScaleTo(1, 650, Easing.OutQuint) .Then() .Schedule(prepareNewPlayer); - } - using (BeginDelayedSequence(delayBeforeSideDisplays)) - { - settingsScroll.FadeInFromZero(500, Easing.Out) - .MoveToX(0, 500, Easing.OutQuint); + using (BeginDelayedSequence(delayBeforeSideDisplays)) + { + settingsScroll.FadeInFromZero(500, Easing.Out) + .MoveToX(0, 500, Easing.OutQuint); - disclaimers.FadeInFromZero(500, Easing.Out) - .MoveToX(0, 500, Easing.OutQuint); + disclaimers.FadeInFromZero(500, Easing.Out) + .MoveToX(0, 500, Easing.OutQuint); + } } AddRangeInternal(new[] @@ -565,7 +568,7 @@ namespace osu.Game.Screens.Play else this.Exit(); }); - }, 500); + }, quickRestart ? 0 : 500); } private void cancelLoad() From 68945daa4094455cb3361c0b1151e14cdadf82da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Nov 2024 19:27:20 +0900 Subject: [PATCH 813/924] Add confirmation when pressing 'q' to quit at the main menu Kinda a weird key. I feel like this deserves a confirmation step unlike mouse clicking the exit button. Addresses https://github.com/ppy/osu/discussions/30471. --- .../TestSceneDailyChallengeIntro.cs | 2 +- .../UserInterface/TestSceneButtonSystem.cs | 2 +- .../UserInterface/TestSceneMainMenuButton.cs | 6 ++--- osu.Game/Screens/Menu/ButtonSystem.cs | 26 +++++++++---------- osu.Game/Screens/Menu/DailyChallengeButton.cs | 3 ++- osu.Game/Screens/Menu/MainMenu.cs | 4 +-- osu.Game/Screens/Menu/MainMenuButton.cs | 12 ++++----- 7 files changed, 28 insertions(+), 27 deletions(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs index f1a2d6b5f2..7619328e68 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge Add(metadataClient); // add button to observe for daily challenge changes and perform its logic. - Add(new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), _ => { }, 0, Key.D)); + Add(new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), (_, _) => { }, 0, Key.D)); } [Test] diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneButtonSystem.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneButtonSystem.cs index 8f72be37df..d8baca6d23 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneButtonSystem.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneButtonSystem.cs @@ -110,7 +110,7 @@ namespace osu.Game.Tests.Visual.UserInterface break; case Key.Q: - buttons.OnExit = action; + buttons.OnExit = _ => action(); break; case Key.O: diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs index 41543669eb..4925facd8a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.UserInterface public void TestStandardButton() { AddStep("add button", () => Child = new MainMenuButton( - ButtonSystemStrings.Solo, @"button-default-select", OsuIcon.Player, new Color4(102, 68, 204, 255), _ => { }, 0, Key.P) + ButtonSystemStrings.Solo, @"button-default-select", OsuIcon.Player, new Color4(102, 68, 204, 255), (_, _) => { }, 0, Key.P) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.UserInterface Origin = Anchor.Centre, AutoSizeAxes = Axes.Both, CachedDependencies = [(typeof(INotificationOverlay), notificationOverlay)], - Child = new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), _ => { }, 0, Key.D) + Child = new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), (_, _) => { }, 0, Key.D) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -161,7 +161,7 @@ namespace osu.Game.Tests.Visual.UserInterface Origin = Anchor.Centre, AutoSizeAxes = Axes.Both, CachedDependencies = [(typeof(INotificationOverlay), notificationOverlay)], - Child = new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), _ => { }, 0, Key.D) + Child = new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), (_, _) => { }, 0, Key.D) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 0997ab8003..41920605b0 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Menu public Action? OnEditBeatmap; public Action? OnEditSkin; - public Action? OnExit; + public Action? OnExit; public Action? OnBeatmapListing; public Action? OnSolo; public Action? OnSettings; @@ -104,11 +104,11 @@ namespace osu.Game.Screens.Menu buttonArea.AddRange(new Drawable[] { - new MainMenuButton(ButtonSystemStrings.Settings, string.Empty, OsuIcon.Settings, new Color4(85, 85, 85, 255), _ => OnSettings?.Invoke(), Key.O, Key.S) + new MainMenuButton(ButtonSystemStrings.Settings, string.Empty, OsuIcon.Settings, new Color4(85, 85, 85, 255), (_, _) => OnSettings?.Invoke(), Key.O, Key.S) { Padding = new MarginPadding { Right = WEDGE_WIDTH }, }, - backButton = new MainMenuButton(ButtonSystemStrings.Back, @"back-to-top", OsuIcon.PrevCircle, new Color4(51, 58, 94, 255), _ => State = ButtonSystemState.TopLevel) + backButton = new MainMenuButton(ButtonSystemStrings.Back, @"back-to-top", OsuIcon.PrevCircle, new Color4(51, 58, 94, 255), (_, _) => State = ButtonSystemState.TopLevel) { Padding = new MarginPadding { Right = WEDGE_WIDTH }, VisibleStateMin = ButtonSystemState.Play, @@ -132,7 +132,7 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader] private void load(AudioManager audio, IdleTracker? idleTracker, GameHost host) { - buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Solo, @"button-default-select", OsuIcon.Player, new Color4(102, 68, 204, 255), _ => OnSolo?.Invoke(), Key.P) + buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Solo, @"button-default-select", OsuIcon.Player, new Color4(102, 68, 204, 255), (_, _) => OnSolo?.Invoke(), Key.P) { Padding = new MarginPadding { Left = WEDGE_WIDTH }, }); @@ -141,22 +141,22 @@ namespace osu.Game.Screens.Menu buttonsPlay.Add(new DailyChallengeButton(@"button-daily-select", new Color4(94, 63, 186, 255), onDailyChallenge, Key.D)); buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play); - buttonsEdit.Add(new MainMenuButton(EditorStrings.BeatmapEditor.ToLower(), @"button-default-select", OsuIcon.Beatmap, new Color4(238, 170, 0, 255), _ => OnEditBeatmap?.Invoke(), Key.B, Key.E) + buttonsEdit.Add(new MainMenuButton(EditorStrings.BeatmapEditor.ToLower(), @"button-default-select", OsuIcon.Beatmap, new Color4(238, 170, 0, 255), (_, _) => OnEditBeatmap?.Invoke(), Key.B, Key.E) { Padding = new MarginPadding { Left = WEDGE_WIDTH }, }); - buttonsEdit.Add(new MainMenuButton(SkinEditorStrings.SkinEditor.ToLower(), @"button-default-select", OsuIcon.SkinB, new Color4(220, 160, 0, 255), _ => OnEditSkin?.Invoke(), Key.S)); + buttonsEdit.Add(new MainMenuButton(SkinEditorStrings.SkinEditor.ToLower(), @"button-default-select", OsuIcon.SkinB, new Color4(220, 160, 0, 255), (_, _) => OnEditSkin?.Invoke(), Key.S)); buttonsEdit.ForEach(b => b.VisibleState = ButtonSystemState.Edit); - buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), _ => State = ButtonSystemState.Play, Key.P, Key.M, Key.L) + buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), (_, _) => State = ButtonSystemState.Play, Key.P, Key.M, Key.L) { Padding = new MarginPadding { Left = WEDGE_WIDTH }, }); - buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Edit, @"button-play-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), _ => State = ButtonSystemState.Edit, Key.E)); - buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-default-select", OsuIcon.Beatmap, new Color4(165, 204, 0, 255), _ => OnBeatmapListing?.Invoke(), Key.B, Key.D)); + buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Edit, @"button-play-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), (_, _) => State = ButtonSystemState.Edit, Key.E)); + buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-default-select", OsuIcon.Beatmap, new Color4(165, 204, 0, 255), (_, _) => OnBeatmapListing?.Invoke(), Key.B, Key.D)); if (host.CanExit) - buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Exit, string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), _ => OnExit?.Invoke(), Key.Q)); + buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Exit, string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), (_, e) => OnExit?.Invoke(e), Key.Q)); buttonArea.AddRange(buttonsPlay); buttonArea.AddRange(buttonsEdit); @@ -179,7 +179,7 @@ namespace osu.Game.Screens.Menu sampleLogoSwoosh = audio.Samples.Get(@"Menu/osu-logo-swoosh"); } - private void onMultiplayer(MainMenuButton _) + private void onMultiplayer(MainMenuButton mainMenuButton, UIEvent uiEvent) { if (api.State.Value != APIState.Online) { @@ -190,7 +190,7 @@ namespace osu.Game.Screens.Menu OnMultiplayer?.Invoke(); } - private void onPlaylists(MainMenuButton _) + private void onPlaylists(MainMenuButton mainMenuButton, UIEvent uiEvent) { if (api.State.Value != APIState.Online) { @@ -201,7 +201,7 @@ namespace osu.Game.Screens.Menu OnPlaylists?.Invoke(); } - private void onDailyChallenge(MainMenuButton button) + private void onDailyChallenge(MainMenuButton button, UIEvent uiEvent) { if (api.State.Value != APIState.Online) { diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs index 4dbebf0ae9..44a53efa7b 100644 --- a/osu.Game/Screens/Menu/DailyChallengeButton.cs +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Beatmaps.Drawables; @@ -50,7 +51,7 @@ namespace osu.Game.Screens.Menu [Resolved] private SessionStatics statics { get; set; } = null!; - public DailyChallengeButton(string sampleName, Color4 colour, Action? clickAction = null, params Key[] triggerKeys) + public DailyChallengeButton(string sampleName, Color4 colour, Action? clickAction = null, params Key[] triggerKeys) : base(ButtonSystemStrings.DailyChallenge, sampleName, OsuIcon.DailyChallenge, colour, clickAction, triggerKeys) { BaseSize = new Vector2(ButtonSystem.BUTTON_WIDTH * 1.3f, ButtonArea.BUTTON_AREA_HEIGHT); diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 346bdcb751..35c6bab81b 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -153,9 +153,9 @@ namespace osu.Game.Screens.Menu else this.Push(new DailyChallengeIntro(room)); }, - OnExit = () => + OnExit = e => { - exitConfirmedViaHoldOrClick = true; + exitConfirmedViaHoldOrClick = e is MouseEvent; this.Exit(); } } diff --git a/osu.Game/Screens/Menu/MainMenuButton.cs b/osu.Game/Screens/Menu/MainMenuButton.cs index 4df5e6d309..f8824795d8 100644 --- a/osu.Game/Screens/Menu/MainMenuButton.cs +++ b/osu.Game/Screens/Menu/MainMenuButton.cs @@ -64,7 +64,7 @@ namespace osu.Game.Screens.Menu protected Vector2 BaseSize { get; init; } = new Vector2(ButtonSystem.BUTTON_WIDTH, ButtonArea.BUTTON_AREA_HEIGHT); - private readonly Action? clickAction; + private readonly Action? clickAction; private readonly Container background; private readonly Drawable backgroundContent; @@ -84,7 +84,7 @@ namespace osu.Game.Screens.Menu public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => background.ReceivePositionalInputAt(screenSpacePos); - public MainMenuButton(LocalisableString text, string sampleName, IconUsage symbol, Color4 colour, Action? clickAction = null, params Key[] triggerKeys) + public MainMenuButton(LocalisableString text, string sampleName, IconUsage symbol, Color4 colour, Action? clickAction = null, params Key[] triggerKeys) { this.sampleName = sampleName; this.clickAction = clickAction; @@ -263,7 +263,7 @@ namespace osu.Game.Screens.Menu protected override bool OnClick(ClickEvent e) { - trigger(); + trigger(e); return true; } @@ -274,19 +274,19 @@ namespace osu.Game.Screens.Menu if (TriggerKeys.Contains(e.Key)) { - trigger(); + trigger(e); return true; } return false; } - private void trigger() + private void trigger(UIEvent e) { sampleChannel = sampleClick?.GetChannel(); sampleChannel?.Play(); - clickAction?.Invoke(this); + clickAction?.Invoke(this, e); boxHoverLayer.ClearTransforms(); boxHoverLayer.Alpha = 0.9f; From f15b6b1d71a0b9d45b66a0861fc5d3878755a4fa Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 13 Nov 2024 14:09:19 +0100 Subject: [PATCH 814/924] Create LegacyBeatmapExporterTest --- .../Beatmaps/IO/LegacyBeatmapExporterTest.cs | 64 ++++++++++++++++++ .../Archives/decimal-timing-beatmap.olz | Bin 0 -> 990 bytes 2 files changed, 64 insertions(+) create mode 100644 osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs create mode 100644 osu.Game.Tests/Resources/Archives/decimal-timing-beatmap.olz diff --git a/osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs b/osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs new file mode 100644 index 0000000000..7973867114 --- /dev/null +++ b/osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs @@ -0,0 +1,64 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.IO; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Tests.Resources; +using osu.Game.Tests.Visual; +using MemoryStream = System.IO.MemoryStream; + +namespace osu.Game.Tests.Beatmaps.IO +{ + [HeadlessTest] + public partial class LegacyBeatmapExporterTest : OsuTestScene + { + [Resolved] + private BeatmapManager beatmaps { get; set; } = null!; + + [Test] + public void TestObjectsSnappedAfterTruncatingExport() + { + IWorkingBeatmap beatmap = null!; + MemoryStream outStream = null!; + + // Ensure importer encoding is correct + AddStep("import beatmap", () => beatmap = importBeatmapFromArchives(@"decimal-timing-beatmap.olz")); + AddAssert("timing point has decimal offset", () => Math.Abs(beatmap.Beatmap.ControlPointInfo.TimingPoints[0].Time - 284.725) < 0.001); + AddAssert("kiai has decimal offset", () => Math.Abs(beatmap.Beatmap.ControlPointInfo.EffectPoints[0].Time - 28520.019) < 0.001); + AddAssert("hit object has decimal offset", () => Math.Abs(beatmap.Beatmap.HitObjects[0].StartTime - 28520.019) < 0.001); + + // Ensure exporter legacy conversion is correct + AddStep("export", () => + { + outStream = new MemoryStream(); + + new LegacyBeatmapExporter(LocalStorage) + .ExportToStream((BeatmapSetInfo)beatmap.BeatmapInfo.BeatmapSet!, outStream, null); + }); + + AddStep("import beatmap again", () => beatmap = importBeatmapFromStream(outStream)); + AddAssert("timing point has truncated offset", () => Math.Abs(beatmap.Beatmap.ControlPointInfo.TimingPoints[0].Time - 284) < 0.001); + AddAssert("kiai is snapped", () => Math.Abs(beatmap.Beatmap.ControlPointInfo.EffectPoints[0].Time - 28519) < 0.001); + AddAssert("hit object is snapped", () => Math.Abs(beatmap.Beatmap.HitObjects[0].StartTime - 28519) < 0.001); + } + + private IWorkingBeatmap importBeatmapFromStream(Stream stream) + { + var imported = beatmaps.Import(new ImportTask(stream, "filename.osz")).GetResultSafely(); + return imported.AsNonNull().PerformRead(s => beatmaps.GetWorkingBeatmap(s.Beatmaps[0])); + } + + private IWorkingBeatmap importBeatmapFromArchives(string filename) + { + var imported = beatmaps.Import(new ImportTask(TestResources.OpenResource($@"Archives/{filename}"), filename)).GetResultSafely(); + return imported.AsNonNull().PerformRead(s => beatmaps.GetWorkingBeatmap(s.Beatmaps[0])); + } + } +} diff --git a/osu.Game.Tests/Resources/Archives/decimal-timing-beatmap.olz b/osu.Game.Tests/Resources/Archives/decimal-timing-beatmap.olz new file mode 100644 index 0000000000000000000000000000000000000000..38dedc35d15f1a11baa49f00917acf93ee2b23eb GIT binary patch literal 990 zcmWIWW@Zs#U|`^2m|l<@`RKWA-YF&q1~FC!26v!nXiFO#b zw&N&j>KL znO3-0PWb%6t>+K57#VpNeE9Hmt9*dd^{Hx?cku7*jW;-d@=k8K>={M>7u^ek+@IYv zoj>3CgTO4~DH^7hy}CR}=M~dEPXG8CSux{4?Ys5|*7u$6t27v#H=L`x*!%6$qWV`w zn`f53%jw*-K8Ec~qH=#!{|i>jkF{kB8-JcNwOeBtS@cOD#_l8g{o^JJC)s%JoBKQ8 zU{1T6mHY$ep4)*hMK)!}mT{kX%IA3eUq;NuJ&xgD&i&r8yq8@A-FR8}pm; zlYYnlVAouHmh*1ASkV0=EPq|!I6cYAnl-^HB0l#sTh;x^+h=?>{4?)k_V!~p9V5#3 z-MmM?2AGz!%` zar*FwypriXi3$8=yG3F*OL}&lvF`WtH+)+@x81~N&!*$6&1PkWetyODN*{pU6T+^F&&vvb6;ZXM071Pt! z{XC79`erV<5EK^S7rR{LeA<#Vqq literal 0 HcmV?d00001 From b6eff38520b632d1fe311458ce761e27e0be1ce5 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 13 Nov 2024 14:23:28 +0100 Subject: [PATCH 815/924] Fix timing point truncation in legacy beatmap export --- osu.Game/Database/LegacyBeatmapExporter.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/LegacyBeatmapExporter.cs b/osu.Game/Database/LegacyBeatmapExporter.cs index 17c2c8c88d..eb48425588 100644 --- a/osu.Game/Database/LegacyBeatmapExporter.cs +++ b/osu.Game/Database/LegacyBeatmapExporter.cs @@ -59,7 +59,25 @@ namespace osu.Game.Database }; // Convert beatmap elements to be compatible with legacy format - // So we truncate time and position values to integers, and convert paths with multiple segments to bezier curves + // So we truncate time and position values to integers, and convert paths with multiple segments to Bézier curves + + // We must first truncate all timing points and move all objects in the timing section with it to ensure everything stays snapped + for (int i = 0; i < playableBeatmap.ControlPointInfo.TimingPoints.Count; i++) + { + var timingPoint = playableBeatmap.ControlPointInfo.TimingPoints[i]; + double offset = Math.Floor(timingPoint.Time) - timingPoint.Time; + double nextTimingPointTime = i + 1 < playableBeatmap.ControlPointInfo.TimingPoints.Count + ? playableBeatmap.ControlPointInfo.TimingPoints[i + 1].Time + : double.PositiveInfinity; + + // Offset all control points in the timing section (including the current one) + foreach (var controlPoint in playableBeatmap.ControlPointInfo.AllControlPoints.Where(o => o.Time >= timingPoint.Time && o.Time < nextTimingPointTime)) + controlPoint.Time += offset; + + // Offset all hit objects in the timing section + foreach (var hitObject in playableBeatmap.HitObjects.Where(o => o.StartTime >= timingPoint.Time && o.StartTime < nextTimingPointTime)) + hitObject.StartTime += offset; + } foreach (var controlPoint in playableBeatmap.ControlPointInfo.AllControlPoints) controlPoint.Time = Math.Floor(controlPoint.Time); From f597568476bdce8930bbdb30fa6fc937c1f2964f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 01:23:21 +0900 Subject: [PATCH 816/924] Fix test failure due to restart happening too fast --- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 1949808dfe..cf813cfd51 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -523,7 +523,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("restart completed", () => getCurrentPlayer() != null && getCurrentPlayer() != previousPlayer); AddStep("release quick retry key", () => InputManager.ReleaseKey(Key.Tilde)); - AddUntilStep("wait for player", () => getCurrentPlayer()?.LoadState == LoadState.Ready); + AddUntilStep("wait for player", () => getCurrentPlayer()?.LoadState >= LoadState.Ready); AddUntilStep("time reached zero", () => getCurrentPlayer()?.GameplayClockContainer.CurrentTime > 0); AddUntilStep("skip button not visible", () => !checkSkipButtonVisible()); From 7e96ae6da4492fc26fe7e4c0cf8f13cfb26c6739 Mon Sep 17 00:00:00 2001 From: Shin Morisawa Date: Thu, 14 Nov 2024 06:54:29 +0900 Subject: [PATCH 817/924] change stuff. (idk what exactly) --- osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index 36ddb6030e..5d84bbc8ad 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -146,7 +146,7 @@ namespace osu.Game.Beatmaps.Drawables approachRate.Text = @" AR: " + adjustedDifficulty.ApproachRate.ToString(@"0.##"); overallDifficulty.Text = @" OD: " + adjustedDifficulty.OverallDifficulty.ToString(@"0.##"); - length.Text = "Length: " + TimeSpan.FromMilliseconds(displayedContent.BeatmapInfo.Length / rate).ToString(@"mm\:ss"); + length.Text = "Length: " + TimeSpan.FromMilliseconds(displayedContent.BeatmapInfo.Length / rate).ToString(@"hh\:mm\:ss"); bpm.Text = " BPM: " + Math.Round(bpmAdjusted, 0); } From a3da10ff44979139349f8dc2a59c2ccbcbb12b77 Mon Sep 17 00:00:00 2001 From: Shin Morisawa Date: Thu, 14 Nov 2024 07:19:18 +0900 Subject: [PATCH 818/924] fixed said issue --- osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index 5d84bbc8ad..6c74ce5738 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -146,7 +146,10 @@ namespace osu.Game.Beatmaps.Drawables approachRate.Text = @" AR: " + adjustedDifficulty.ApproachRate.ToString(@"0.##"); overallDifficulty.Text = @" OD: " + adjustedDifficulty.OverallDifficulty.ToString(@"0.##"); - length.Text = "Length: " + TimeSpan.FromMilliseconds(displayedContent.BeatmapInfo.Length / rate).ToString(@"hh\:mm\:ss"); + TimeSpan lengthTimeSpan = TimeSpan.FromMilliseconds(displayedContent.BeatmapInfo.Length / rate); + length.Text = "Length: " + (lengthTimeSpan.Hours > 0 + ? lengthTimeSpan.ToString(@"hh\:mm\:ss") + : lengthTimeSpan.ToString(@"mm\:ss")); bpm.Text = " BPM: " + Math.Round(bpmAdjusted, 0); } From 46f59907319b870fae52e017239f109c64dad393 Mon Sep 17 00:00:00 2001 From: jhk2601 Date: Wed, 13 Nov 2024 21:12:29 -0500 Subject: [PATCH 819/924] Ensure mod incompatibility consistency across files --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 3 ++- osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs | 2 ++ osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs | 2 +- 6 files changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index b45b4fea13..8e9b14edeb 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -32,7 +32,8 @@ namespace osu.Game.Rulesets.Osu.Mods typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModRepel), - typeof(ModTouchDevice) + typeof(ModTouchDevice), + typeof(OsuModBloom) }; private OsuInputManager inputManager = null!; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs index 425db55c6e..442465bf29 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; protected const float MIN_SIZE = 1; protected const float TRANSITION_DURATION = 100; - public override Type[] IncompatibleMods => new[] { typeof(OsuModFlashlight), typeof(OsuModNoScope), typeof(OsuModObjectScaleTween), typeof(OsuModTouchDevice), typeof(OsuModAutopilot) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModFlashlight), typeof(OsuModNoScope), typeof(OsuModObjectScaleTween), typeof(ModTouchDevice), typeof(OsuModAutopilot) }; protected readonly BindableNumber CurrentCombo = new BindableInt(); protected readonly IBindable IsBreakTime = new Bindable(); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 5a6cc50082..3009530b50 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods public partial class OsuModFlashlight : ModFlashlight, IApplicableToDrawableHitObject { public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModBlinds)).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModBloom), typeof(OsuModBlinds) }).ToArray(); private const double default_follow_delay = 120; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs index d1bbae8e1a..57d540a7d4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs @@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Osu.Mods { public override LocalisableString Description => "Where's the cursor?"; + public override Type[] IncompatibleMods => new[] { typeof(OsuModBloom) }; + private PeriodTracker spinnerPeriods = null!; public override BindableInt HiddenComboCount { get; } = new BindableInt(10) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs index 1df344648a..a851ba0bbd 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods protected virtual float EndScale => 1; - public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModObjectScaleTween), typeof(OsuModDepth) }; + public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModObjectScaleTween), typeof(OsuModDepth), typeof(OsuModBloom) }; protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs index 917685cdad..a364190a00 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModTouchDevice : ModTouchDevice { - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModBloom) }).ToArray(); public override bool Ranked => UsesDefaultConfiguration; } } From 2afd3579015f7b88c1aaf0aa5a8e42638729e41d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 13:14:35 +0900 Subject: [PATCH 820/924] Re-throw `OperationCanceledException` for consistency? Mostly to see if it breaks anything. --- osu.Game/Database/RealmArchiveModelImporter.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index c4eb93b754..22bcc622e9 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -139,6 +139,7 @@ namespace osu.Game.Database } catch (OperationCanceledException) { + throw; } catch (Exception e) { @@ -531,7 +532,8 @@ namespace osu.Game.Database /// The new model proposed for import. /// The current realm context. /// An existing model which matches the criteria to skip importing, else null. - protected TModel? CheckForExisting(TModel model, Realm realm) => string.IsNullOrEmpty(model.Hash) ? null : realm.All().OrderBy(b => b.DeletePending).FirstOrDefault(b => b.Hash == model.Hash); + protected TModel? CheckForExisting(TModel model, Realm realm) => + string.IsNullOrEmpty(model.Hash) ? null : realm.All().OrderBy(b => b.DeletePending).FirstOrDefault(b => b.Hash == model.Hash); /// /// Whether import can be skipped after finding an existing import early in the process. From d37c1bb6d04269f32d9739dcc74fb781bb937358 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 13:37:53 +0900 Subject: [PATCH 821/924] Remove redundant null initialisation and apply nullability --- osu.Game/Beatmaps/StarDifficulty.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/Beatmaps/StarDifficulty.cs b/osu.Game/Beatmaps/StarDifficulty.cs index 51358fcd7f..9f7a92fe46 100644 --- a/osu.Game/Beatmaps/StarDifficulty.cs +++ b/osu.Game/Beatmaps/StarDifficulty.cs @@ -1,9 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - -using JetBrains.Annotations; using osu.Framework.Utils; using osu.Game.Rulesets.Difficulty; @@ -25,20 +22,18 @@ namespace osu.Game.Beatmaps /// The difficulty attributes computed for the given beatmap. /// Might not be available if the star difficulty is associated with a beatmap that's not locally available. /// - [CanBeNull] - public readonly DifficultyAttributes DifficultyAttributes; + public readonly DifficultyAttributes? DifficultyAttributes; /// /// The performance attributes computed for a perfect score on the given beatmap. /// Might not be available if the star difficulty is associated with a beatmap that's not locally available. /// - [CanBeNull] - public readonly PerformanceAttributes PerformanceAttributes; + public readonly PerformanceAttributes? PerformanceAttributes; /// /// Creates a structure. /// - public StarDifficulty([NotNull] DifficultyAttributes difficulty, [NotNull] PerformanceAttributes performance) + public StarDifficulty(DifficultyAttributes difficulty, PerformanceAttributes performance) { Stars = double.IsFinite(difficulty.StarRating) ? difficulty.StarRating : 0; MaxCombo = difficulty.MaxCombo; @@ -55,7 +50,6 @@ namespace osu.Game.Beatmaps { Stars = double.IsFinite(starDifficulty) ? starDifficulty : 0; MaxCombo = maxCombo; - DifficultyAttributes = null; } public DifficultyRating DifficultyRating => GetDifficultyRating(Stars); From 695f156f5f405c6c729c26e73572a1523f922944 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 14:12:13 +0900 Subject: [PATCH 822/924] Change collection management dialog to handle changes more correctly --- .../Collections/DrawableCollectionList.cs | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index 6fe38a3229..96013314c7 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -43,8 +43,25 @@ namespace osu.Game.Collections private void collectionsChanged(IRealmCollection collections, ChangeSet? changes) { - Items.Clear(); - Items.AddRange(collections.AsEnumerable().Select(c => c.ToLive(realm))); + if (changes == null) + { + Items.AddRange(collections.AsEnumerable().Select(c => c.ToLive(realm))); + return; + } + + foreach (int i in changes.DeletedIndices.OrderDescending()) + Items.RemoveAt(i); + + foreach (int i in changes.InsertedIndices) + Items.Insert(i, collections[i].ToLive(realm)); + + foreach (int i in changes.NewModifiedIndices) + { + var updatedItem = collections[i]; + + Items.RemoveAt(i); + Items.Insert(i, updatedItem.ToLive(realm)); + } } protected override OsuRearrangeableListItem> CreateOsuDrawable(Live item) From ebe9a9f0837cd4e039069b46371b7ff3e6339182 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 14:12:31 +0900 Subject: [PATCH 823/924] Avoid writing changes to realm when collection name wasn't actually changed --- .../Collections/DrawableCollectionList.cs | 25 ++++++++++++++++- .../Collections/DrawableCollectionListItem.cs | 27 +++++++++---------- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index 96013314c7..e87a79f2e8 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -140,12 +140,35 @@ namespace osu.Game.Collections var previous = PlaceholderItem; placeholderContainer.Clear(false); - placeholderContainer.Add(PlaceholderItem = new DrawableCollectionListItem(new BeatmapCollection().ToLiveUnmanaged(), false)); + placeholderContainer.Add(PlaceholderItem = new NewCollectionEntryItem()); return previous; } } + private class NewCollectionEntryItem : DrawableCollectionListItem + { + [Resolved] + private RealmAccess realm { get; set; } = null!; + + public NewCollectionEntryItem() + : base(new BeatmapCollection().ToLiveUnmanaged(), false) + { + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + TextBox.OnCommit += (sender, newText) => + { + if (newText && !string.IsNullOrEmpty(TextBox.Current.Value)) + realm.Write(r => r.Add(new BeatmapCollection(TextBox.Current.Value))); + TextBox.Text = string.Empty; + }; + } + } + /// /// The flow of . Disables layout easing unless a drag is in progress. /// diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index e71368c079..a17a50e232 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -28,6 +28,10 @@ namespace osu.Game.Collections private const float item_height = 35; private const float button_width = item_height * 0.75f; + protected TextBox TextBox => content.TextBox; + + private ItemContent content = null!; + /// /// Creates a new . /// @@ -48,7 +52,7 @@ namespace osu.Game.Collections CornerRadius = item_height / 2; } - protected override Drawable CreateContent() => new ItemContent(Model); + protected override Drawable CreateContent() => content = new ItemContent(Model); /// /// The main content of the . @@ -57,10 +61,7 @@ namespace osu.Game.Collections { private readonly Live collection; - private ItemTextBox textBox = null!; - - [Resolved] - private RealmAccess realm { get; set; } = null!; + public ItemTextBox TextBox { get; private set; } = null!; public ItemContent(Live collection) { @@ -80,7 +81,7 @@ namespace osu.Game.Collections { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - IsTextBoxHovered = v => textBox.ReceivePositionalInputAt(v) + IsTextBoxHovered = v => TextBox.ReceivePositionalInputAt(v) } : Empty(), new Container @@ -89,7 +90,7 @@ namespace osu.Game.Collections Padding = new MarginPadding { Right = collection.IsManaged ? button_width : 0 }, Children = new Drawable[] { - textBox = new ItemTextBox + TextBox = new ItemTextBox { RelativeSizeAxes = Axes.Both, Size = Vector2.One, @@ -107,18 +108,14 @@ namespace osu.Game.Collections base.LoadComplete(); // Bind late, as the collection name may change externally while still loading. - textBox.Current.Value = collection.PerformRead(c => c.IsValid ? c.Name : string.Empty); - textBox.OnCommit += onCommit; + TextBox.Current.Value = collection.PerformRead(c => c.IsValid ? c.Name : string.Empty); + TextBox.OnCommit += onCommit; } private void onCommit(TextBox sender, bool newText) { - if (collection.IsManaged) - collection.PerformWrite(c => c.Name = textBox.Current.Value); - else if (!string.IsNullOrEmpty(textBox.Current.Value)) - realm.Write(r => r.Add(new BeatmapCollection(textBox.Current.Value))); - - textBox.Text = string.Empty; + if (collection.IsManaged && newText) + collection.PerformWrite(c => c.Name = TextBox.Current.Value); } } From 17b1888c597434326db9db5da21f022be5b26fe3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 14:23:43 +0900 Subject: [PATCH 824/924] Avoid using `newTexst` as it doesn't work well with tests --- osu.Game/Collections/DrawableCollectionList.cs | 6 ++++-- osu.Game/Collections/DrawableCollectionListItem.cs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index e87a79f2e8..aaef41eee1 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -162,8 +162,10 @@ namespace osu.Game.Collections TextBox.OnCommit += (sender, newText) => { - if (newText && !string.IsNullOrEmpty(TextBox.Current.Value)) - realm.Write(r => r.Add(new BeatmapCollection(TextBox.Current.Value))); + if (string.IsNullOrEmpty(TextBox.Text)) + return; + + realm.Write(r => r.Add(new BeatmapCollection(TextBox.Text))); TextBox.Text = string.Empty; }; } diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index a17a50e232..f07ec87353 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -114,7 +114,7 @@ namespace osu.Game.Collections private void onCommit(TextBox sender, bool newText) { - if (collection.IsManaged && newText) + if (collection.IsManaged && collection.Value.Name != TextBox.Current.Value) collection.PerformWrite(c => c.Name = TextBox.Current.Value); } } From d66daf15a56579b1bf6ae905555df944fd846fdf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 14:36:58 +0900 Subject: [PATCH 825/924] Fix tests clicking wrong delete buttons due to internal drawable ordering --- .../Collections/TestSceneManageCollectionsDialog.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 747cf73baf..46bf6dfafd 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -205,7 +205,9 @@ namespace osu.Game.Tests.Visual.Collections AddStep("click first delete button", () => { - InputManager.MoveMouseTo(dialog.ChildrenOfType().First(), new Vector2(5, 0)); + InputManager.MoveMouseTo(dialog + .ChildrenOfType().Single(i => i.Model.Value.Name == "1") + .ChildrenOfType().Single(), new Vector2(5, 0)); InputManager.Click(MouseButton.Left); }); @@ -213,9 +215,11 @@ namespace osu.Game.Tests.Visual.Collections assertCollectionCount(1); assertCollectionName(0, "2"); - AddStep("click first delete button", () => + AddStep("click second delete button", () => { - InputManager.MoveMouseTo(dialog.ChildrenOfType().First(), new Vector2(5, 0)); + InputManager.MoveMouseTo(dialog + .ChildrenOfType().Single(i => i.Model.Value.Name == "2") + .ChildrenOfType().Single(), new Vector2(5, 0)); InputManager.Click(MouseButton.Left); }); @@ -310,7 +314,7 @@ namespace osu.Game.Tests.Visual.Collections AddStep("focus first collection", () => { - InputManager.MoveMouseTo(firstItem = dialog.ChildrenOfType().First()); + InputManager.MoveMouseTo(firstItem = dialog.ChildrenOfType().Single(i => i.Model.Value.Name == "1")); InputManager.Click(MouseButton.Left); }); From fea6a544325854877637d13e587864afeb4ca1ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 14:46:58 +0900 Subject: [PATCH 826/924] Fix more tests reading in wrong order --- .../Visual/Collections/TestSceneManageCollectionsDialog.cs | 3 ++- osu.Game/Collections/DrawableCollectionList.cs | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 46bf6dfafd..56e7b4d39f 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -265,6 +265,7 @@ namespace osu.Game.Tests.Visual.Collections } [Test] + [Solo] public void TestCollectionRenamedExternal() { BeatmapCollection first = null!; @@ -341,6 +342,6 @@ namespace osu.Game.Tests.Visual.Collections => AddUntilStep($"{count} collections shown", () => dialog.ChildrenOfType().Count() == count + 1); // +1 for placeholder private void assertCollectionName(int index, string name) - => AddUntilStep($"item {index + 1} has correct name", () => dialog.ChildrenOfType().ElementAt(index).ChildrenOfType().First().Text == name); + => AddUntilStep($"item {index + 1} has correct name", () => dialog.ChildrenOfType().Single().OrderedItems.ElementAt(index).ChildrenOfType().First().Text == name); } } diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index aaef41eee1..9f7494b556 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; @@ -29,7 +30,11 @@ namespace osu.Game.Collections private IDisposable? realmSubscription; - protected override FillFlowContainer>> CreateListFillFlowContainer() => new Flow + private Flow flow = null!; + + public IEnumerable OrderedItems => flow.FlowingChildren; + + protected override FillFlowContainer>> CreateListFillFlowContainer() => flow = new Flow { DragActive = { BindTarget = DragActive } }; From ce818f59e74b21e2ebffe8afd3690d7701f72f3f Mon Sep 17 00:00:00 2001 From: Nathen Date: Thu, 14 Nov 2024 01:42:07 -0500 Subject: [PATCH 827/924] Fix NaN PP values when SR is 0 --- osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs index 1cb6b69f91..1944b54e0c 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs @@ -70,10 +70,11 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// public virtual double CountTopWeightedStrains() { - if (ObjectStrains.Count == 0) + double consistentTopStrain = DifficultyValue() / 10; // What would the top strain be if all strain values were identical + + if (ObjectStrains.Count == 0 || consistentTopStrain == 0) return 0.0; - double consistentTopStrain = DifficultyValue() / 10; // What would the top strain be if all strain values were identical // Use a weighted sum of all strains. Constants are arbitrary and give nice values return ObjectStrains.Sum(s => 1.1 / (1 + Math.Exp(-10 * (s / consistentTopStrain - 0.88)))); } From 2ea2e5f1db40dacf02a297f571a481eec3351c6f Mon Sep 17 00:00:00 2001 From: Nathen Date: Thu, 14 Nov 2024 01:47:24 -0500 Subject: [PATCH 828/924] Be doubly careful --- osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs index 1944b54e0c..56249f19a7 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs @@ -70,9 +70,12 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// public virtual double CountTopWeightedStrains() { + if (ObjectStrains.Count == 0) + return 0.0; + double consistentTopStrain = DifficultyValue() / 10; // What would the top strain be if all strain values were identical - if (ObjectStrains.Count == 0 || consistentTopStrain == 0) + if (consistentTopStrain == 0) return 0.0; // Use a weighted sum of all strains. Constants are arbitrary and give nice values From d0e793a3b395026d2b26bc51538fd5f9a015ac7c Mon Sep 17 00:00:00 2001 From: Nathen Date: Thu, 14 Nov 2024 01:50:05 -0500 Subject: [PATCH 829/924] More correct but not too important --- osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs index 56249f19a7..3ba67793dc 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Difficulty.Skills double consistentTopStrain = DifficultyValue() / 10; // What would the top strain be if all strain values were identical if (consistentTopStrain == 0) - return 0.0; + return ObjectStrains.Count; // Use a weighted sum of all strains. Constants are arbitrary and give nice values return ObjectStrains.Sum(s => 1.1 / (1 + Math.Exp(-10 * (s / consistentTopStrain - 0.88)))); From 9fcf8342f0430c3c17a9fc9c5a2a00e3dc902cef Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 14 Nov 2024 08:59:03 +0200 Subject: [PATCH 830/924] initial commit --- .../Difficulty/Skills/OsuStrainSkill.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 559a871df1..10b5ee0133 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -23,8 +23,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// protected virtual double ReducedStrainBaseline => 0.75; - protected double Difficulty; - protected OsuStrainSkill(Mod[] mods) : base(mods) { @@ -32,7 +30,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public override double DifficultyValue() { - Difficulty = 0; double weight = 1; // Sections with 0 strain are excluded to avoid worst-case time complexity of the following sort (e.g. /b/2351871). @@ -48,15 +45,17 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills strains[i] *= Interpolation.Lerp(ReducedStrainBaseline, 1.0, scale); } + double difficulty = 0; + // Difficulty is the weighted sum of the highest strains from every section. // We're sorting from highest to lowest strain. foreach (double strain in strains.OrderDescending()) { - Difficulty += strain * weight; + difficulty += strain * weight; weight *= DecayWeight; } - return Difficulty; + return difficulty; } public static double DifficultyToPerformance(double difficulty) => Math.Pow(5.0 * Math.Max(1.0, difficulty / 0.0675) - 4.0, 3.0) / 100000.0; From 1b6952c42ab4977fa54026d8794af4a85210d677 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 16:03:00 +0900 Subject: [PATCH 831/924] Add failing test showing crash when adjusting offset with no `HitEvent`s --- .../Ranking/TestSceneHitEventTimingDistributionGraph.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs index 3e38b66029..760210c370 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs @@ -40,6 +40,13 @@ namespace osu.Game.Tests.Visual.Ranking AddSliderStep("height", 0.0f, 1000.0f, height.Value, height.Set); } + [Test] + public void TestZeroEvents() + { + createTest(new List()); + AddStep("update offset", () => graph.UpdateOffset(10)); + } + [Test] public void TestManyDistributedEventsOffset() { From b086c276ad82d4d983aa41837bdf6c2748be45cb Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 14 Nov 2024 09:03:08 +0200 Subject: [PATCH 832/924] moved back to the top --- osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 10b5ee0133..6823512cef 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -30,6 +30,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public override double DifficultyValue() { + double difficulty = 0; double weight = 1; // Sections with 0 strain are excluded to avoid worst-case time complexity of the following sort (e.g. /b/2351871). @@ -45,8 +46,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills strains[i] *= Interpolation.Lerp(ReducedStrainBaseline, 1.0, scale); } - double difficulty = 0; - // Difficulty is the weighted sum of the highest strains from every section. // We're sorting from highest to lowest strain. foreach (double strain in strains.OrderDescending()) From 88d220f4c5227467227c1ab2df6ad545121fbe79 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 16:03:12 +0900 Subject: [PATCH 833/924] Fix crash when resetting offset after a play with no hit events Closes https://github.com/ppy/osu/issues/30573. --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 207e19a716..a9b93e0ffc 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -176,7 +176,7 @@ namespace osu.Game.Screens.Ranking.Statistics for (int i = 1; i <= axis_points; i++) { double axisValue = i * axisValueStep; - float position = (float)(axisValue / maxValue); + float position = maxValue == 0 ? 0 : (float)(axisValue / maxValue); float alpha = 1f - position * 0.8f; axisFlow.Add(new OsuSpriteText @@ -348,7 +348,7 @@ namespace osu.Game.Screens.Ranking.Statistics boxAdjustment.FadeTo(!hasAdjustment ? 0 : 1, duration, Easing.OutQuint); } - private float offsetForValue(float value) => (1 - minimum_height) * value / maxValue; + private float offsetForValue(float value) => maxValue == 0 ? 0 : (1 - minimum_height) * value / maxValue; private float heightForValue(float value) => minimum_height + offsetForValue(value); } From 64e7e44f28c961c9de6bf0e0987a0f06b6cd392a Mon Sep 17 00:00:00 2001 From: Shin Morisawa Date: Thu, 14 Nov 2024 16:29:03 +0900 Subject: [PATCH 834/924] fix issue --- osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index 6c74ce5738..491882b53a 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -147,9 +147,7 @@ namespace osu.Game.Beatmaps.Drawables overallDifficulty.Text = @" OD: " + adjustedDifficulty.OverallDifficulty.ToString(@"0.##"); TimeSpan lengthTimeSpan = TimeSpan.FromMilliseconds(displayedContent.BeatmapInfo.Length / rate); - length.Text = "Length: " + (lengthTimeSpan.Hours > 0 - ? lengthTimeSpan.ToString(@"hh\:mm\:ss") - : lengthTimeSpan.ToString(@"mm\:ss")); + length.Text = "Length: " + (lengthTimeSpan.TotalHours >= 1 ? lengthTimeSpan.ToString(@"hh\:mm\:ss") : lengthTimeSpan.ToString(@"mm\:ss")); bpm.Text = " BPM: " + Math.Round(bpmAdjusted, 0); } From 4957a517aacca9975cb168155e3aa3ca04bab396 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 16:38:26 +0900 Subject: [PATCH 835/924] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index f271bdfaaf..757e5659d0 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index ecb9ae2c8d..25639f84f4 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + From 7dff243e6a48a7b6c429042075dce13cea9f793f Mon Sep 17 00:00:00 2001 From: Shin Morisawa Date: Thu, 14 Nov 2024 16:59:03 +0900 Subject: [PATCH 836/924] use .ToFormattedDuration --- osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index 491882b53a..8182fe24b2 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; +using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; @@ -147,7 +148,7 @@ namespace osu.Game.Beatmaps.Drawables overallDifficulty.Text = @" OD: " + adjustedDifficulty.OverallDifficulty.ToString(@"0.##"); TimeSpan lengthTimeSpan = TimeSpan.FromMilliseconds(displayedContent.BeatmapInfo.Length / rate); - length.Text = "Length: " + (lengthTimeSpan.TotalHours >= 1 ? lengthTimeSpan.ToString(@"hh\:mm\:ss") : lengthTimeSpan.ToString(@"mm\:ss")); + length.Text = "Length: " + lengthTimeSpan.ToFormattedDuration(); bpm.Text = " BPM: " + Math.Round(bpmAdjusted, 0); } From 88aea70429513151076b97dd8a2790aaedc9d27c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 14 Nov 2024 10:07:09 +0100 Subject: [PATCH 837/924] Do not permit new combo toggle to remain in indeterminate state on deselect --- .../Screens/Edit/Compose/Components/EditorSelectionHandler.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index a252649bb9..a9dbfc29a9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -258,6 +258,8 @@ namespace osu.Game.Screens.Edit.Compose.Components private void resetTernaryStates() { + if (SelectionNewComboState.Value == TernaryState.Indeterminate) + SelectionNewComboState.Value = TernaryState.False; AutoSelectionBankEnabled.Value = true; SelectionAdditionBanksEnabled.Value = true; SelectionBankStates[HIT_BANK_AUTO].Value = TernaryState.True; From 9849a88eefc7d9e8534eba47ac230b947b1cb0c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 18:27:26 +0900 Subject: [PATCH 838/924] Adjust transition further to avoid brief "jumpscare" display of metadata --- osu.Game/Screens/Play/PlayerLoader.cs | 92 ++++++++++++++++++--------- 1 file changed, 63 insertions(+), 29 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 51268e5dce..5843c28f3a 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -10,6 +10,7 @@ using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Transforms; using osu.Framework.Input; @@ -88,9 +89,13 @@ namespace osu.Game.Screens.Play private SkinnableSound sampleRestart = null!; + private Box? quickRestartBlackLayer; + [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + private const double quick_restart_initial_delay = 500; + protected bool BackgroundBrightnessReduction { set @@ -307,6 +312,9 @@ namespace osu.Game.Screens.Play { base.OnSuspending(e); + quickRestartBlackLayer?.FadeOut(500, Easing.OutQuint); + quickRestartBlackLayer = null; + BackgroundBrightnessReduction = false; // we're moving to player, so a period of silence is upcoming. @@ -350,7 +358,14 @@ namespace osu.Game.Screens.Play if (!resuming) logo.MoveTo(new Vector2(0.5f), duration, Easing.OutQuint); logo.ScaleTo(new Vector2(0.15f), duration, Easing.OutQuint); - logo.FadeIn(350); + + if (quickRestart) + { + logo.Delay(quick_restart_initial_delay) + .FadeIn(350); + } + else + logo.FadeIn(350); Scheduler.AddDelayed(() => { @@ -456,17 +471,33 @@ namespace osu.Game.Screens.Play { MetadataInfo.Loading = true; - content.FadeInFromZero(500, Easing.OutQuint); - if (quickRestart) { - // A slight delay is added here to avoid an awkward stutter during the initial animation. - Scheduler.AddDelayed(prepareNewPlayer, 100); - content.ScaleTo(1); + // A quick restart starts by triggering a fade to black + AddInternal(quickRestartBlackLayer = new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + Depth = float.MaxValue + }); + + quickRestartBlackLayer + .Delay(50) + .FadeOut(5000, Easing.OutQuint); + + prepareNewPlayer(); + + content + .Delay(quick_restart_initial_delay) + .ScaleTo(1) + .FadeInFromZero(500, Easing.OutQuint); } else { + content.FadeInFromZero(500, Easing.OutQuint); + content + .ScaleTo(0.7f) .ScaleTo(1, 650, Easing.OutQuint) .Then() .Schedule(prepareNewPlayer); @@ -542,33 +573,36 @@ namespace osu.Game.Screens.Play highPerformanceSession ??= highPerformanceSessionManager?.BeginSession(); scheduledPushPlayer = Scheduler.AddDelayed(() => - { - // ensure that once we have reached this "point of no return", readyForPush will be false for all future checks (until a new player instance is prepared). - var consumedPlayer = consumePlayer(); - - ContentOut(); - - TransformSequence pushSequence = this.Delay(0); - - // This goes hand-in-hand with the restoration of low pass filter in contentOut(). - this.TransformBindableTo(volumeAdjustment, 0, CONTENT_OUT_DURATION, Easing.OutCubic); - - pushSequence.Schedule(() => { - if (!this.IsCurrentScreen()) return; + // ensure that once we have reached this "point of no return", readyForPush will be false for all future checks (until a new player instance is prepared). + var consumedPlayer = consumePlayer(); - LoadTask = null; + ContentOut(); - // By default, we want to load the player and never be returned to. - // Note that this may change if the player we load requested a re-run. - ValidForResume = false; + TransformSequence pushSequence = this.Delay(0); - if (consumedPlayer.LoadedBeatmapSuccessfully) - this.Push(consumedPlayer); - else - this.Exit(); - }); - }, quickRestart ? 0 : 500); + // This goes hand-in-hand with the restoration of low pass filter in contentOut(). + this.TransformBindableTo(volumeAdjustment, 0, CONTENT_OUT_DURATION, Easing.OutCubic); + + pushSequence.Schedule(() => + { + if (!this.IsCurrentScreen()) return; + + LoadTask = null; + + // By default, we want to load the player and never be returned to. + // Note that this may change if the player we load requested a re-run. + ValidForResume = false; + + if (consumedPlayer.LoadedBeatmapSuccessfully) + this.Push(consumedPlayer); + else + this.Exit(); + }); + }, + // When a quick restart is activated, the metadata content will display some time later if it's taking too long. + // To avoid it appearing too briefly, if it begins to fade in let's induce a standard delay. + quickRestart && content.Alpha == 0 ? 0 : 500); } private void cancelLoad() From 36d48a01c841e9997fbc8a46adaa1962ed7f9c06 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 19:33:34 +0900 Subject: [PATCH 839/924] Add missing expire call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Screens/Play/PlayerLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 5843c28f3a..cf867e31f8 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -312,7 +312,7 @@ namespace osu.Game.Screens.Play { base.OnSuspending(e); - quickRestartBlackLayer?.FadeOut(500, Easing.OutQuint); + quickRestartBlackLayer?.FadeOut(500, Easing.OutQuint).Expire(); quickRestartBlackLayer = null; BackgroundBrightnessReduction = false; From f9489690cb64bfc30ed1b2c96c8cb1b011364efa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 14 Nov 2024 11:58:57 +0100 Subject: [PATCH 840/924] Fix code inspection --- osu.Game/Collections/DrawableCollectionList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index 9f7494b556..164ec558a4 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -151,7 +151,7 @@ namespace osu.Game.Collections } } - private class NewCollectionEntryItem : DrawableCollectionListItem + private partial class NewCollectionEntryItem : DrawableCollectionListItem { [Resolved] private RealmAccess realm { get; set; } = null!; From bbe2c87837d7315f6511bd3e5783e222a5ee47e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 14 Nov 2024 12:57:45 +0100 Subject: [PATCH 841/924] Add failing test case --- .../Gameplay/TestSceneBeatmapOffsetControl.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs index c7f1eabab2..0f47c3cd27 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs @@ -1,14 +1,18 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Tests.Resources; @@ -52,6 +56,39 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); } + [Test] + public void TestNotEnoughTimedHitEvents() + { + AddStep("Set short reference score", () => + { + List hitEvents = + [ + // 10 events total. one of them (head circle) being timed / having hitwindows, rest having no hitwindows + new HitEvent(30, 1, HitResult.LargeTickHit, new SliderHeadCircle { ClassicSliderBehaviour = true }, null, null), + new HitEvent(0, 1, HitResult.LargeTickHit, new SliderTick(), null, null), + new HitEvent(0, 1, HitResult.LargeTickHit, new SliderTick(), null, null), + new HitEvent(0, 1, HitResult.LargeTickHit, new SliderTick(), null, null), + new HitEvent(0, 1, HitResult.LargeTickHit, new SliderTick(), null, null), + new HitEvent(0, 1, HitResult.LargeTickHit, new SliderTick(), null, null), + new HitEvent(0, 1, HitResult.LargeTickHit, new SliderTick(), null, null), + new HitEvent(0, 1, HitResult.LargeTickHit, new SliderTick(), null, null), + new HitEvent(0, 1, HitResult.LargeTickHit, new SliderTick(), null, null), + new HitEvent(0, 1, HitResult.LargeTickHit, new SliderTick(), null, null), + ]; + + foreach (var ev in hitEvents) + ev.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + offsetControl.ReferenceScore.Value = new ScoreInfo + { + HitEvents = hitEvents, + BeatmapInfo = Beatmap.Value.BeatmapInfo, + }; + }); + + AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); + } + [Test] public void TestScoreFromDifferentBeatmap() { From bd1d3cad49188a90a81bff5803a3cf706b18603a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 14 Nov 2024 13:16:32 +0100 Subject: [PATCH 842/924] Do not show timing distribution graph in offset control if there's not enough timed hits Intended to address concerns raised in https://github.com/ppy/osu/pull/30620#issuecomment-2475744164. --- osu.Game/Rulesets/Scoring/HitEventExtensions.cs | 6 +++--- .../Screens/Play/PlayerSettings/BeatmapOffsetControl.cs | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs index 6e2852676a..fc4eef13ba 100644 --- a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs +++ b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Scoring foreach (var e in hitEvents) { - if (!affectsUnstableRate(e)) + if (!AffectsUnstableRate(e)) continue; count++; @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Scoring /// public static double? CalculateAverageHitError(this IEnumerable hitEvents) { - double[] timeOffsets = hitEvents.Where(affectsUnstableRate).Select(ev => ev.TimeOffset).ToArray(); + double[] timeOffsets = hitEvents.Where(AffectsUnstableRate).Select(ev => ev.TimeOffset).ToArray(); if (timeOffsets.Length == 0) return null; @@ -65,6 +65,6 @@ namespace osu.Game.Rulesets.Scoring return timeOffsets.Average(); } - private static bool affectsUnstableRate(HitEvent e) => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit(); + public static bool AffectsUnstableRate(HitEvent e) => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit(); } } diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index f312fb0ec5..74b887481f 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -195,7 +195,10 @@ namespace osu.Game.Screens.Play.PlayerSettings }, }; - if (hitEvents.Count < 10) + // affecting unstable rate here is used as a substitute of determining if a hit event represents a *timed* hit event, + // i.e. an user input that the user had to *time to the track*, + // i.e. one that it *makes sense to use* when doing anything with timing and offsets. + if (hitEvents.Count(HitEventExtensions.AffectsUnstableRate) < 10) { referenceScoreContainer.AddRange(new Drawable[] { From afeb138ea002767d8c9a9d6049b00be21d0edfac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 21:47:38 +0900 Subject: [PATCH 843/924] Fix occasional flash when quick exiting / retrying from player The gist of the issue is that `fadeOut` was being called *twice* in the quick exit/retry scenarios, causing weirdness with transforms. I've restructured things to ensure it's only called once. --- osu.Game/Screens/Play/Player.cs | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 398e8df5c9..91e984f5bc 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -86,6 +86,7 @@ namespace osu.Game.Screens.Play public Action RestartRequested; private bool isRestarting; + private bool noExitTransition; private Bindable mouseWheelDisabled; @@ -297,10 +298,7 @@ namespace osu.Game.Screens.Play { if (!this.IsCurrentScreen()) return; - if (PerformExit(false)) - // The hotkey overlay dims the screen. - // If the operation succeeds, we want to make sure we stay dimmed to keep continuity. - fadeOut(true); + PerformExit(false, true); }, }, }); @@ -318,10 +316,7 @@ namespace osu.Game.Screens.Play { if (!this.IsCurrentScreen()) return; - if (Restart(true)) - // The hotkey overlay dims the screen. - // If the operation succeeds, we want to make sure we stay dimmed to keep continuity. - fadeOut(true); + Restart(true); }, }, }); @@ -600,8 +595,9 @@ namespace osu.Game.Screens.Play /// Whether the pause or fail dialog should be shown before performing an exit. /// If and a dialog is not yet displayed, the exit will be blocked and the relevant dialog will display instead. /// + /// Whether the exit should perform without a transition, because the screen had faded to black already. /// Whether this call resulted in a final exit. - protected bool PerformExit(bool showDialogFirst) + protected bool PerformExit(bool showDialogFirst, bool withoutTransition = false) { bool pauseOrFailDialogVisible = PauseOverlay.State.Value == Visibility.Visible || FailOverlay.State.Value == Visibility.Visible; @@ -639,6 +635,8 @@ namespace osu.Game.Screens.Play // Screen may not be current if a restart has been performed. if (this.IsCurrentScreen()) { + noExitTransition = withoutTransition; + // The actual exit is performed if // - the pause / fail dialog was not requested // - the pause / fail dialog was requested but is already displayed (user showing intention to exit). @@ -709,7 +707,7 @@ namespace osu.Game.Screens.Play RestartRequested?.Invoke(quickRestart); - return PerformExit(false); + return PerformExit(false, quickRestart); } /// @@ -1254,10 +1252,10 @@ namespace osu.Game.Screens.Play ShowUserStatistics = true, }; - private void fadeOut(bool instant = false) + private void fadeOut() { - float fadeOutDuration = instant ? 0 : 250; - this.FadeOut(fadeOutDuration); + if (!noExitTransition) + this.FadeOut(250); if (this.IsCurrentScreen()) { From 3262b6d98931a085cee486cd5cc88142f4d133fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 22:10:45 +0900 Subject: [PATCH 844/924] Refactor to avoid dual-boolean mess --- .../Gameplay/TestSceneStoryboardWithOutro.cs | 2 +- .../Multiplayer/MultiplayerPlayer.cs | 2 +- osu.Game/Screens/Play/Player.cs | 48 ++++++++++++------- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index aff6139c08..4f1a63341a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -223,7 +223,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected partial class OutroPlayer : TestPlayer { - public void ExitViaPause() => PerformExit(true); + public void ExitViaPause() => PerformExitWithConfirmation(); public new FailOverlay FailOverlay => base.FailOverlay; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index e560c5ca5d..a50f3440f5 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -158,7 +158,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (!string.IsNullOrEmpty(message)) Logger.Log(message, LoggingTarget.Runtime, LogLevel.Important); - Schedule(() => PerformExit(false)); + Schedule(() => PerformExit()); } private void onGameplayStarted() => Scheduler.Add(() => diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 91e984f5bc..ac49b4c42d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -86,7 +86,7 @@ namespace osu.Game.Screens.Play public Action RestartRequested; private bool isRestarting; - private bool noExitTransition; + private bool skipExitTransition; private Bindable mouseWheelDisabled; @@ -290,7 +290,7 @@ namespace osu.Game.Screens.Play { SaveReplay = async () => await prepareAndImportScoreAsync(true).ConfigureAwait(false), OnRetry = Configuration.AllowUserInteraction ? () => Restart() : null, - OnQuit = () => PerformExit(true), + OnQuit = () => PerformExitWithConfirmation(), }, new HotkeyExitOverlay { @@ -298,7 +298,7 @@ namespace osu.Game.Screens.Play { if (!this.IsCurrentScreen()) return; - PerformExit(false, true); + PerformExit(true); }, }, }); @@ -443,7 +443,7 @@ namespace osu.Game.Screens.Play { HoldToQuit = { - Action = () => PerformExit(true), + Action = () => PerformExitWithConfirmation(), IsPaused = { BindTarget = GameplayClockContainer.IsPaused }, ReplayLoaded = { BindTarget = DrawableRuleset.HasReplayLoaded }, }, @@ -480,7 +480,7 @@ namespace osu.Game.Screens.Play OnResume = Resume, Retries = RestartCount, OnRetry = () => Restart(), - OnQuit = () => PerformExit(true), + OnQuit = () => PerformExitWithConfirmation(), }, }, }; @@ -583,26 +583,24 @@ namespace osu.Game.Screens.Play } /// - /// Attempts to complete a user request to exit gameplay. + /// Attempts to complete a user request to exit gameplay, with confirmation. /// /// /// /// This should only be called in response to a user interaction. Exiting is not guaranteed. /// This will interrupt any pending progression to the results screen, even if the transition has begun. /// + /// + /// This method will show the pause or fail dialog before performing an exit. + /// If a dialog is not yet displayed, the exit will be blocked and the relevant dialog will display instead. /// - /// - /// Whether the pause or fail dialog should be shown before performing an exit. - /// If and a dialog is not yet displayed, the exit will be blocked and the relevant dialog will display instead. - /// - /// Whether the exit should perform without a transition, because the screen had faded to black already. - /// Whether this call resulted in a final exit. - protected bool PerformExit(bool showDialogFirst, bool withoutTransition = false) + /// + protected bool PerformExitWithConfirmation() { bool pauseOrFailDialogVisible = PauseOverlay.State.Value == Visibility.Visible || FailOverlay.State.Value == Visibility.Visible; - if (showDialogFirst && !pauseOrFailDialogVisible) + if (!pauseOrFailDialogVisible) { // if the fail animation is currently in progress, accelerate it (it will show the pause dialog on completion). if (ValidForResume && GameplayState.HasFailed) @@ -621,6 +619,22 @@ namespace osu.Game.Screens.Play } } + return PerformExit(); + } + + /// + /// Attempts to complete a user request to exit gameplay. + /// + /// + /// + /// This should only be called in response to a user interaction. Exiting is not guaranteed. + /// This will interrupt any pending progression to the results screen, even if the transition has begun. + /// + /// + /// Whether the exit should perform without a transition, because the screen had faded to black already. + /// Whether this call resulted in a final exit. + protected bool PerformExit(bool skipTransition = false) + { // Matching osu!stable behaviour, if the results screen is pending and the user requests an exit, // show the results instead. if (GameplayState.HasPassed && !isRestarting) @@ -635,7 +649,7 @@ namespace osu.Game.Screens.Play // Screen may not be current if a restart has been performed. if (this.IsCurrentScreen()) { - noExitTransition = withoutTransition; + skipExitTransition = skipTransition; // The actual exit is performed if // - the pause / fail dialog was not requested @@ -707,7 +721,7 @@ namespace osu.Game.Screens.Play RestartRequested?.Invoke(quickRestart); - return PerformExit(false, quickRestart); + return PerformExit(quickRestart); } /// @@ -1254,7 +1268,7 @@ namespace osu.Game.Screens.Play private void fadeOut() { - if (!noExitTransition) + if (!skipExitTransition) this.FadeOut(250); if (this.IsCurrentScreen()) From d1b5d31ea66fc3c8aa777675e935fa103564a38f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 22:23:42 +0900 Subject: [PATCH 845/924] Add explicit parameter in --- osu.Game/Screens/Play/Player.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ac49b4c42d..8d7665a0d9 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -298,7 +298,7 @@ namespace osu.Game.Screens.Play { if (!this.IsCurrentScreen()) return; - PerformExit(true); + PerformExit(skipTransition: true); }, }, }); @@ -721,7 +721,7 @@ namespace osu.Game.Screens.Play RestartRequested?.Invoke(quickRestart); - return PerformExit(quickRestart); + return PerformExit(skipTransition: quickRestart); } /// From 8df7a6f2f31226dab8e1a1d300968b527e24c00b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 14 Nov 2024 14:24:11 +0100 Subject: [PATCH 846/924] Adjust test to have better assertions --- .../Beatmaps/IO/LegacyBeatmapExporterTest.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs b/osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs index 7973867114..9947def06d 100644 --- a/osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.IO; using NUnit.Framework; using osu.Framework.Allocation; @@ -30,9 +29,9 @@ namespace osu.Game.Tests.Beatmaps.IO // Ensure importer encoding is correct AddStep("import beatmap", () => beatmap = importBeatmapFromArchives(@"decimal-timing-beatmap.olz")); - AddAssert("timing point has decimal offset", () => Math.Abs(beatmap.Beatmap.ControlPointInfo.TimingPoints[0].Time - 284.725) < 0.001); - AddAssert("kiai has decimal offset", () => Math.Abs(beatmap.Beatmap.ControlPointInfo.EffectPoints[0].Time - 28520.019) < 0.001); - AddAssert("hit object has decimal offset", () => Math.Abs(beatmap.Beatmap.HitObjects[0].StartTime - 28520.019) < 0.001); + AddAssert("timing point has decimal offset", () => beatmap.Beatmap.ControlPointInfo.TimingPoints[0].Time, () => Is.EqualTo(284.725).Within(0.001)); + AddAssert("kiai has decimal offset", () => beatmap.Beatmap.ControlPointInfo.EffectPoints[0].Time, () => Is.EqualTo(28520.019).Within(0.001)); + AddAssert("hit object has decimal offset", () => beatmap.Beatmap.HitObjects[0].StartTime, () => Is.EqualTo(28520.019).Within(0.001)); // Ensure exporter legacy conversion is correct AddStep("export", () => @@ -44,9 +43,9 @@ namespace osu.Game.Tests.Beatmaps.IO }); AddStep("import beatmap again", () => beatmap = importBeatmapFromStream(outStream)); - AddAssert("timing point has truncated offset", () => Math.Abs(beatmap.Beatmap.ControlPointInfo.TimingPoints[0].Time - 284) < 0.001); - AddAssert("kiai is snapped", () => Math.Abs(beatmap.Beatmap.ControlPointInfo.EffectPoints[0].Time - 28519) < 0.001); - AddAssert("hit object is snapped", () => Math.Abs(beatmap.Beatmap.HitObjects[0].StartTime - 28519) < 0.001); + AddAssert("timing point has truncated offset", () => beatmap.Beatmap.ControlPointInfo.TimingPoints[0].Time, () => Is.EqualTo(284).Within(0.001)); + AddAssert("kiai is snapped", () => beatmap.Beatmap.ControlPointInfo.EffectPoints[0].Time, () => Is.EqualTo(28519).Within(0.001)); + AddAssert("hit object is snapped", () => beatmap.Beatmap.HitObjects[0].StartTime, () => Is.EqualTo(28519).Within(0.001)); } private IWorkingBeatmap importBeatmapFromStream(Stream stream) From 5d3f55fe4d237592e8f1aaad91ffa280e7ecf3ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 14 Nov 2024 15:05:46 +0100 Subject: [PATCH 847/924] Fill out xmldoc --- osu.Game/Screens/Play/Player.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 8d7665a0d9..6ab7d81f83 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -594,7 +594,7 @@ namespace osu.Game.Screens.Play /// This method will show the pause or fail dialog before performing an exit. /// If a dialog is not yet displayed, the exit will be blocked and the relevant dialog will display instead. /// - /// + /// Whether this call resulted in a final exit. protected bool PerformExitWithConfirmation() { bool pauseOrFailDialogVisible = From 2cbb09573d7cb8e8506d14e95f89886586ec42f8 Mon Sep 17 00:00:00 2001 From: Hiviexd Date: Thu, 14 Nov 2024 15:38:10 +0100 Subject: [PATCH 848/924] fix constant algorithm scroll speed --- osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 64ea9d88cd..4185b67f4c 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -84,8 +84,11 @@ namespace osu.Game.Rulesets.Taiko.UI protected virtual double ComputeTimeRange() { - // Adjust when we're using constant algorithm to not be sluggish. - double multiplier = VisualisationMethod == ScrollVisualisationMethod.Constant ? 4 * Beatmap.Difficulty.SliderMultiplier : 1; + // Using the constant algorithm results in a sluggish scroll speed that's equal to 60 BPM. + // We need to adjust it to the expected default scroll speed (BPM * base SV multiplier). + double multiplier = VisualisationMethod == ScrollVisualisationMethod.Constant + ? (Beatmap.BeatmapInfo.BPM * Beatmap.Difficulty.SliderMultiplier) / 60 + : 1; return PlayfieldAdjustmentContainer.ComputeTimeRange() / multiplier; } From 1a31e56d4acfecc55a73b0852c08dea06b8bf7b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 23:59:55 +0900 Subject: [PATCH 849/924] Fix double restart call still existing --- osu.Game/Screens/Play/Player.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 6ab7d81f83..e9722350bd 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -719,9 +719,14 @@ namespace osu.Game.Screens.Play // stopping here is to ensure music doesn't become audible after exiting back to PlayerLoader. musicController.Stop(); - RestartRequested?.Invoke(quickRestart); + if (RestartRequested != null) + { + skipExitTransition = quickRestart; + RestartRequested?.Invoke(quickRestart); + return true; + } - return PerformExit(skipTransition: quickRestart); + return PerformExit(quickRestart); } /// From ef9296f3ada2fe29537373795c3fd98415454193 Mon Sep 17 00:00:00 2001 From: jhk2601 Date: Thu, 14 Nov 2024 14:12:21 -0500 Subject: [PATCH 850/924] Allow Autopilot and Grow/Deflate compatibility --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 3 +-- osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 8e9b14edeb..b45b4fea13 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -32,8 +32,7 @@ namespace osu.Game.Rulesets.Osu.Mods typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModRepel), - typeof(ModTouchDevice), - typeof(OsuModBloom) + typeof(ModTouchDevice) }; private OsuInputManager inputManager = null!; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs index 442465bf29..c674074dc6 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBloom.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; protected const float MIN_SIZE = 1; protected const float TRANSITION_DURATION = 100; - public override Type[] IncompatibleMods => new[] { typeof(OsuModFlashlight), typeof(OsuModNoScope), typeof(OsuModObjectScaleTween), typeof(ModTouchDevice), typeof(OsuModAutopilot) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModFlashlight), typeof(OsuModNoScope), typeof(ModTouchDevice) }; protected readonly BindableNumber CurrentCombo = new BindableInt(); protected readonly IBindable IsBreakTime = new Bindable(); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs index a851ba0bbd..1df344648a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods protected virtual float EndScale => 1; - public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModObjectScaleTween), typeof(OsuModDepth), typeof(OsuModBloom) }; + public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModObjectScaleTween), typeof(OsuModDepth) }; protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) { From 6160df158615f16cae5e00d810ea0930132fd1e0 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 16:55:18 +0900 Subject: [PATCH 851/924] Make `Room.Name` non-bindable --- .../DailyChallenge/TestSceneDailyChallenge.cs | 6 +- .../TestSceneDailyChallengeIntro.cs | 2 +- .../Visual/Menus/TestSceneMainMenu.cs | 2 +- .../Visual/Multiplayer/QueueModeTestScene.cs | 14 ++--- .../Multiplayer/TestSceneDrawableRoom.cs | 36 ++++++------ .../TestSceneDrawableRoomParticipantsList.cs | 6 +- .../Multiplayer/TestSceneMultiplayer.cs | 54 +++++++++--------- .../TestSceneMultiplayerMatchSubScreen.cs | 2 +- .../TestSceneMultiplayerPlaylist.cs | 2 +- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 16 +++--- .../TestScenePlaylistsMatchSettingsOverlay.cs | 26 ++++----- .../TestScenePlaylistsRoomCreation.cs | 56 +++++++------------ .../Online/Multiplayer/MultiplayerClient.cs | 4 +- osu.Game/Online/Rooms/Room.cs | 18 ++++-- .../OnlinePlay/Components/RoomManager.cs | 2 +- .../DailyChallenge/DailyChallengeIntro.cs | 2 +- .../Lounge/Components/DrawableRoom.cs | 30 +++++++++- .../OnlinePlay/Lounge/DrawableLoungeRoom.cs | 2 +- .../Match/MultiplayerMatchSettingsOverlay.cs | 22 +++++++- .../Multiplayer/MultiplayerLoungeSubScreen.cs | 2 +- .../Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 - .../Playlists/PlaylistsLoungeSubScreen.cs | 2 +- .../Playlists/PlaylistsRoomSettingsOverlay.cs | 32 ++++++++++- .../Playlists/PlaylistsRoomSubScreen.cs | 2 +- .../Multiplayer/MultiplayerTestScene.cs | 2 +- .../Multiplayer/TestMultiplayerClient.cs | 2 +- .../Visual/OnlinePlay/TestRoomManager.cs | 2 +- osu.Game/Users/UserActivity.cs | 2 +- 29 files changed, 200 insertions(+), 153 deletions(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs index 488289cb74..46a951dcff 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs @@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge var room = new Room { RoomID = 1234, - Name = { Value = "Daily Challenge: June 4, 2024" }, + Name = "Daily Challenge: June 4, 2024", Playlist = { new PlaylistItem(TestResources.CreateTestBeatmapSetInfo().Beatmaps.First()) @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge var room = new Room { RoomID = 1234, - Name = { Value = "Daily Challenge: June 4, 2024" }, + Name = "Daily Challenge: June 4, 2024", Playlist = { new PlaylistItem(TestResources.CreateTestBeatmapSetInfo().Beatmaps.First()) @@ -92,7 +92,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge var room = new Room { RoomID = 1234, - Name = { Value = "Daily Challenge: June 4, 2024" }, + Name = "Daily Challenge: June 4, 2024", Playlist = { new PlaylistItem(TestResources.CreateTestBeatmapSetInfo().Beatmaps.First()) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs index c08398ac17..7b3595b064 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge API.Perform(new CreateRoomRequest(room = new Room { RoomID = roomId, - Name = { Value = "Daily Challenge: June 4, 2024" }, + Name = "Daily Challenge: June 4, 2024", Playlist = { new PlaylistItem(CreateAPIBeatmap(new OsuRuleset().RulesetInfo)) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs index 8b81516a34..9b00449e11 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Menus getRoomRequest.TriggerSuccess(new Room { RoomID = 1234, - Name = { Value = "Aug 8, 2024" }, + Name = "Aug 8, 2024", Playlist = { new PlaylistItem(beatmap) diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 8bcd5aab1c..2d9d979022 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -30,16 +28,16 @@ namespace osu.Game.Tests.Visual.Multiplayer { protected abstract QueueMode Mode { get; } - protected BeatmapInfo InitialBeatmap { get; private set; } - protected BeatmapInfo OtherBeatmap { get; private set; } + protected BeatmapInfo InitialBeatmap { get; private set; } = null!; + protected BeatmapInfo OtherBeatmap { get; private set; } = null!; protected IScreen CurrentScreen => multiplayerComponents.CurrentScreen; protected IScreen CurrentSubScreen => multiplayerComponents.MultiplayerScreen.CurrentSubScreen; - private BeatmapManager beatmaps; - private BeatmapSetInfo importedSet; + private BeatmapManager beatmaps = null!; + private BeatmapSetInfo importedSet = null!; - private TestMultiplayerComponents multiplayerComponents; + private TestMultiplayerComponents multiplayerComponents = null!; protected TestMultiplayerClient MultiplayerClient => multiplayerComponents.MultiplayerClient; @@ -75,7 +73,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for lounge", () => multiplayerComponents.ChildrenOfType().SingleOrDefault()?.IsLoaded == true); AddStep("open room", () => multiplayerComponents.ChildrenOfType().Single().Open(new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", QueueMode = { Value = Mode }, Playlist = { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index 4c609613a3..a386a26d32 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using NUnit.Framework; @@ -32,12 +30,12 @@ namespace osu.Game.Tests.Visual.Multiplayer [Cached] protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); - private readonly Bindable selectedRoom = new Bindable(); + private readonly Bindable selectedRoom = new Bindable(); [Test] public void TestMultipleStatuses() { - FillFlowContainer rooms = null; + FillFlowContainer rooms = null!; AddStep("create rooms", () => { @@ -77,7 +75,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createLoungeRoom(new Room { - Name = { Value = "Multiplayer room" }, + Name = "Multiplayer room", Status = { Value = new RoomStatusOpen() }, EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, Type = { Value = MatchType.HeadToHead }, @@ -86,7 +84,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }), createLoungeRoom(new Room { - Name = { Value = "Private room" }, + Name = "Private room", Status = { Value = new RoomStatusOpenPrivate() }, HasPassword = { Value = true }, EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, @@ -96,7 +94,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }), createLoungeRoom(new Room { - Name = { Value = "Playlist room with multiple beatmaps" }, + Name = "Playlist room with multiple beatmaps", Status = { Value = new RoomStatusPlaying() }, EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, Playlist = { item1, item2 }, @@ -104,19 +102,19 @@ namespace osu.Game.Tests.Visual.Multiplayer }), createLoungeRoom(new Room { - Name = { Value = "Finished room" }, + Name = "Finished room", Status = { Value = new RoomStatusEnded() }, EndDate = { Value = DateTimeOffset.Now }, }), createLoungeRoom(new Room { - Name = { Value = "Spotlight room" }, + Name = "Spotlight room", Status = { Value = new RoomStatusOpen() }, Category = { Value = RoomCategory.Spotlight }, }), createLoungeRoom(new Room { - Name = { Value = "Featured artist room" }, + Name = "Featured artist room", Status = { Value = new RoomStatusOpen() }, Category = { Value = RoomCategory.FeaturedArtist }, }), @@ -132,12 +130,12 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestEnableAndDisablePassword() { - DrawableRoom drawableRoom = null; - Room room = null; + DrawableRoom drawableRoom = null!; + Room room = null!; AddStep("create room", () => Child = drawableRoom = createLoungeRoom(room = new Room { - Name = { Value = "Room with password" }, + Name = "Room with password", Status = { Value = new RoomStatusOpen() }, Type = { Value = MatchType.HeadToHead }, })); @@ -166,30 +164,30 @@ namespace osu.Game.Tests.Visual.Multiplayer { new DrawableMatchRoom(new Room { - Name = { Value = "A host-only room" }, + Name = "A host-only room", QueueMode = { Value = QueueMode.HostOnly }, Type = { Value = MatchType.HeadToHead }, }) { - SelectedItem = new Bindable() + SelectedItem = new Bindable() }, new DrawableMatchRoom(new Room { - Name = { Value = "An all-players, team-versus room" }, + Name = "An all-players, team-versus room", QueueMode = { Value = QueueMode.AllPlayers }, Type = { Value = MatchType.TeamVersus } }) { - SelectedItem = new Bindable() + SelectedItem = new Bindable() }, new DrawableMatchRoom(new Room { - Name = { Value = "A round-robin room" }, + Name = "A round-robin room", QueueMode = { Value = QueueMode.AllPlayersRoundRobin }, Type = { Value = MatchType.HeadToHead } }) { - SelectedItem = new Bindable() + SelectedItem = new Bindable() }, } }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs index 98abc93994..c959c07ffb 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; @@ -17,7 +15,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneDrawableRoomParticipantsList : OnlinePlayTestScene { - private DrawableRoomParticipantsList list; + private DrawableRoomParticipantsList list = null!; public override void SetUpSteps() { @@ -27,7 +25,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { SelectedRoom.Value = new Room { - Name = { Value = "test room" }, + Name = "test room", Host = { Value = new APIUser diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index df2021dbaf..0b662083b5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -103,7 +103,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -238,7 +238,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -259,7 +259,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { roomManager.AddServerSideRoom(new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -288,7 +288,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { roomManager.AddServerSideRoom(new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -317,7 +317,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Password = { Value = "password" }, Playlist = { @@ -338,7 +338,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { roomManager.AddServerSideRoom(new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Password = { Value = "password" }, Playlist = { @@ -370,7 +370,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Password = { Value = "password" }, Playlist = { @@ -401,7 +401,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -430,7 +430,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }; return new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { item } }; }); @@ -471,7 +471,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }; return new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { item } }; }); @@ -512,7 +512,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }; return new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { item } }; }); @@ -548,7 +548,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -581,7 +581,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -620,7 +620,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -639,7 +639,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -679,7 +679,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -724,7 +724,7 @@ namespace osu.Game.Tests.Visual.Multiplayer createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -754,7 +754,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -791,7 +791,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { roomManager.AddServerSideRoom(new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", QueueMode = { Value = QueueMode.AllPlayers }, Playlist = { @@ -810,7 +810,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("disable polling", () => this.ChildrenOfType().Single().TimeBetweenPolls.Value = 0); AddStep("change server-side settings", () => { - roomManager.ServerSideRooms[0].Name.Value = "New name"; + roomManager.ServerSideRooms[0].Name = "New name"; roomManager.ServerSideRooms[0].Playlist.Add(new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { ID = 2, @@ -825,7 +825,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("local room has correct settings", () => { var localRoom = this.ChildrenOfType().Single().Room; - return localRoom.Name.Value == roomManager.ServerSideRooms[0].Name.Value + return localRoom.Name == roomManager.ServerSideRooms[0].Name && localRoom.Playlist.SequenceEqual(roomManager.ServerSideRooms[0].Playlist); }); } @@ -836,7 +836,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", QueueMode = { Value = QueueMode.AllPlayers }, Playlist = { @@ -872,7 +872,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", QueueMode = { Value = QueueMode.AllPlayers }, Playlist = { @@ -911,7 +911,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", QueueMode = { Value = QueueMode.AllPlayers }, Playlist = { @@ -942,7 +942,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", QueueMode = { Value = QueueMode.AllPlayers }, Playlist = { @@ -976,7 +976,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -1022,7 +1022,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", QueueMode = { Value = QueueMode.AllPlayers }, Playlist = { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 9bf29c7bf8..0c2a657c5a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("load match", () => { - SelectedRoom.Value = new Room { Name = { Value = "Test Room" } }; + SelectedRoom.Value = new Room { Name = "Test Room" }; LoadScreen(screen = new TestMultiplayerMatchSubScreen(SelectedRoom.Value)); }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index e12c2bee49..92b44d9502 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -164,7 +164,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { RoomManager.CreateRoom(new Room { - Name = { Value = "test name" }, + Name = "test name", Playlist = { new PlaylistItem(new TestBeatmap(Ruleset.Value).BeatmapInfo) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index 32e90153d8..7c30b72747 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using NUnit.Framework; @@ -29,10 +27,10 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneTeamVersus : ScreenTestScene { - private BeatmapManager beatmaps; - private BeatmapSetInfo importedSet; + private BeatmapManager beatmaps = null!; + private BeatmapSetInfo importedSet = null!; - private TestMultiplayerComponents multiplayerComponents; + private TestMultiplayerComponents multiplayerComponents = null!; private TestMultiplayerClient multiplayerClient => multiplayerComponents.MultiplayerClient; @@ -64,7 +62,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Type = { Value = MatchType.TeamVersus }, Playlist = { @@ -84,7 +82,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Type = { Value = MatchType.TeamVersus }, Playlist = { @@ -121,7 +119,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Type = { Value = MatchType.HeadToHead }, Playlist = { @@ -147,7 +145,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs index 9f7b20ad43..3652710e32 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using NUnit.Framework; using osu.Framework.Bindables; @@ -22,7 +20,7 @@ namespace osu.Game.Tests.Visual.Playlists { protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager; - private TestRoomSettings settings; + private TestRoomSettings settings = null!; protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new TestDependencies(); @@ -47,19 +45,19 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("clear name and beatmap", () => { - SelectedRoom.Value.Name.Value = ""; + SelectedRoom.Value.Name = ""; SelectedRoom.Value.Playlist.Clear(); }); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); - AddStep("set name", () => SelectedRoom.Value.Name.Value = "Room name"); + AddStep("set name", () => SelectedRoom.Value.Name = "Room name"); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); AddStep("set beatmap", () => SelectedRoom.Value.Playlist.Add(new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo))); AddAssert("button enabled", () => settings.ApplyButton.Enabled.Value); - AddStep("clear name", () => SelectedRoom.Value.Name.Value = ""); + AddStep("clear name", () => SelectedRoom.Value.Name = ""); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); } @@ -69,7 +67,7 @@ namespace osu.Game.Tests.Visual.Playlists const string expected_name = "expected name"; TimeSpan expectedDuration = TimeSpan.FromMinutes(15); - Room createdRoom = null; + Room createdRoom = null!; AddStep("setup", () => { @@ -85,7 +83,7 @@ namespace osu.Game.Tests.Visual.Playlists }); AddStep("create room", () => settings.ApplyButton.Action.Invoke()); - AddAssert("has correct name", () => createdRoom.Name.Value == expected_name); + AddAssert("has correct name", () => createdRoom.Name == expected_name); AddAssert("has correct duration", () => createdRoom.Duration.Value == expectedDuration); } @@ -94,13 +92,13 @@ namespace osu.Game.Tests.Visual.Playlists { const string not_found_prefix = "beatmaps not found:"; - string errorMessage = null; + string errorMessage = null!; AddStep("setup", () => { var beatmap = CreateBeatmap(Ruleset.Value).BeatmapInfo; - SelectedRoom.Value.Name.Value = "Test Room"; + SelectedRoom.Value.Name = "Test Room"; SelectedRoom.Value.Playlist.Add(new PlaylistItem(beatmap)); errorMessage = $"{not_found_prefix} {beatmap.OnlineID}"; @@ -127,7 +125,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("setup", () => { - SelectedRoom.Value.Name.Value = "Test Room"; + SelectedRoom.Value.Name = "Test Room"; SelectedRoom.Value.Playlist.Add(new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)); RoomManager.CreateRequested = _ => failText; @@ -169,7 +167,7 @@ namespace osu.Game.Tests.Visual.Playlists protected class TestRoomManager : IRoomManager { - public Func CreateRequested; + public Func? CreateRequested; public event Action RoomsUpdated { @@ -187,7 +185,7 @@ namespace osu.Game.Tests.Visual.Playlists public void ClearRooms() => throw new NotImplementedException(); - public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) + public void CreateRoom(Room room, Action? onSuccess = null, Action? onError = null) { if (CreateRequested == null) return; @@ -200,7 +198,7 @@ namespace osu.Game.Tests.Visual.Playlists onSuccess?.Invoke(room); } - public void JoinRoom(Room room, string password, Action onSuccess = null, Action onError = null) => throw new NotImplementedException(); + public void JoinRoom(Room room, string? password, Action? onSuccess = null, Action? onError = null) => throw new NotImplementedException(); public void PartRoom() => throw new NotImplementedException(); } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index 1636a3d4b8..8769ff2e27 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -1,12 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Diagnostics; using System.Linq; -using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -35,11 +32,9 @@ namespace osu.Game.Tests.Visual.Playlists { public partial class TestScenePlaylistsRoomCreation : OnlinePlayTestScene { - private BeatmapManager manager; - - private TestPlaylistsRoomSubScreen match; - - private BeatmapSetInfo importedBeatmap; + private BeatmapManager manager = null!; + private TestPlaylistsRoomSubScreen match = null!; + private BeatmapSetInfo importedBeatmap = null!; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) @@ -52,11 +47,11 @@ namespace osu.Game.Tests.Visual.Playlists [SetUpSteps] public void SetupSteps() { - AddStep("set room", () => SelectedRoom!.Value = new Room()); + AddStep("set room", () => SelectedRoom.Value = new Room()); importBeatmap(); - AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(SelectedRoom!.Value))); + AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(SelectedRoom.Value))); AddUntilStep("wait for load", () => match.IsCurrentScreen()); } @@ -65,7 +60,7 @@ namespace osu.Game.Tests.Visual.Playlists { setupAndCreateRoom(room => { - room.Name.Value = "my awesome room"; + room.Name = "my awesome room"; room.Host.Value = API.LocalUser.Value; room.RecentParticipants.Add(room.Host.Value); room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5); @@ -88,7 +83,7 @@ namespace osu.Game.Tests.Visual.Playlists { setupAndCreateRoom(room => { - room.Name.Value = "my awesome room"; + room.Name = "my awesome room"; room.MaxAttempts.Value = 5; room.Host.Value = API.LocalUser.Value; room.RecentParticipants.Add(room.Host.Value); @@ -107,7 +102,7 @@ namespace osu.Game.Tests.Visual.Playlists { setupAndCreateRoom(room => { - room.Name.Value = "my awesome room"; + room.Name = "my awesome room"; room.Host.Value = API.LocalUser.Value; room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First()) { @@ -115,13 +110,13 @@ namespace osu.Game.Tests.Visual.Playlists }); }); - AddAssert("first playlist item selected", () => match.SelectedItem.Value == SelectedRoom!.Value.Playlist[0]); + AddAssert("first playlist item selected", () => match.SelectedItem.Value == SelectedRoom.Value.Playlist[0]); } [Test] public void TestBeatmapUpdatedOnReImport() { - string realHash = null; + string realHash = null!; int realOnlineId = 0; int realOnlineSetId = 0; @@ -139,26 +134,23 @@ namespace osu.Game.Tests.Visual.Playlists BeatmapInfo = { OnlineID = realOnlineId, - Metadata = new BeatmapMetadata(), - BeatmapSet = - { - OnlineID = realOnlineSetId - } + Metadata = new BeatmapMetadata() }, }; + Debug.Assert(modifiedBeatmap.BeatmapInfo.BeatmapSet != null); + modifiedBeatmap.BeatmapInfo.BeatmapSet!.OnlineID = realOnlineSetId; + modifiedBeatmap.HitObjects.Clear(); modifiedBeatmap.HitObjects.Add(new HitCircle { StartTime = 5000 }); - Debug.Assert(modifiedBeatmap.BeatmapInfo.BeatmapSet != null); - manager.Import(modifiedBeatmap.BeatmapInfo.BeatmapSet); }); // Create the room using the real beatmap values. setupAndCreateRoom(room => { - room.Name.Value = "my awesome room"; + room.Name = "my awesome room"; room.Host.Value = API.LocalUser.Value; room.Playlist.Add(new PlaylistItem(new BeatmapInfo { @@ -181,17 +173,11 @@ namespace osu.Game.Tests.Visual.Playlists { var originalBeatmap = new TestBeatmap(new OsuRuleset().RulesetInfo) { - BeatmapInfo = - { - OnlineID = realOnlineId, - BeatmapSet = - { - OnlineID = realOnlineSetId - } - }, + BeatmapInfo = { OnlineID = realOnlineId }, }; Debug.Assert(originalBeatmap.BeatmapInfo.BeatmapSet != null); + originalBeatmap.BeatmapInfo.BeatmapSet.OnlineID = realOnlineSetId; manager.Import(originalBeatmap.BeatmapInfo.BeatmapSet); }); @@ -201,7 +187,7 @@ namespace osu.Game.Tests.Visual.Playlists private void setupAndCreateRoom(Action room) { - AddStep("setup room", () => room(SelectedRoom!.Value)); + AddStep("setup room", () => room(SelectedRoom.Value)); AddStep("click create button", () => { @@ -215,8 +201,7 @@ namespace osu.Game.Tests.Visual.Playlists var beatmap = CreateBeatmap(new OsuRuleset().RulesetInfo); Debug.Assert(beatmap.BeatmapInfo.BeatmapSet != null); - - importedBeatmap = manager.Import(beatmap.BeatmapInfo.BeatmapSet)?.Value.Detach(); + importedBeatmap = manager.Import(beatmap.BeatmapInfo.BeatmapSet)!.Value.Detach(); }); private partial class TestPlaylistsRoomSubScreen : PlaylistsRoomSubScreen @@ -226,8 +211,7 @@ namespace osu.Game.Tests.Visual.Playlists public new Bindable Beatmap => base.Beatmap; [Resolved(canBeNull: true)] - [CanBeNull] - private IDialogOverlay dialogOverlay { get; set; } + private IDialogOverlay? dialogOverlay { get; set; } public TestPlaylistsRoomSubScreen(Room room) : base(room) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index bd0fa0cb72..b6ad361c44 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -459,7 +459,7 @@ namespace osu.Game.Online.Multiplayer if (apiUser == null || apiRoom == null) return; PostNotification?.Invoke( - new UserAvatarNotification(apiUser, NotificationsStrings.InvitedYouToTheMultiplayer(apiUser.Username, apiRoom.Name.Value)) + new UserAvatarNotification(apiUser, NotificationsStrings.InvitedYouToTheMultiplayer(apiUser.Username, apiRoom.Name)) { Activated = () => { @@ -841,7 +841,7 @@ namespace osu.Game.Online.Multiplayer // Update a few properties of the room instantaneously. Room.Settings = settings; - APIRoom.Name.Value = Room.Settings.Name; + APIRoom.Name = Room.Settings.Name; APIRoom.Password.Value = Room.Settings.Password; APIRoom.Status.Value = string.IsNullOrEmpty(Room.Settings.Password) ? new RoomStatusOpen() : new RoomStatusOpenPrivate(); APIRoom.Type.Value = Room.Settings.MatchType; diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index de854596e9..d2aa8ee4fe 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -30,6 +30,15 @@ namespace osu.Game.Online.Rooms set => SetField(ref roomId, value); } + /// + /// The room name. + /// + public string Name + { + get => name; + set => SetField(ref name, value); + } + /// /// Represents the current item selected within the room. /// @@ -45,13 +54,12 @@ namespace osu.Game.Online.Rooms [JsonProperty("id")] private long? roomId; + [JsonProperty("name")] + private string name = string.Empty; + [JsonProperty("current_playlist_item")] private PlaylistItem? currentPlaylistItem; - [Cached] - [JsonProperty("name")] - public readonly Bindable Name = new Bindable(); - [Cached] [JsonProperty("host")] public readonly Bindable Host = new Bindable(); @@ -205,7 +213,7 @@ namespace osu.Game.Online.Rooms public void CopyFrom(Room other) { RoomID = other.RoomID; - Name.Value = other.Name.Value; + Name = other.Name; Category.Value = other.Category.Value; diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs index 555a1f66bd..0bb33df966 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs @@ -122,7 +122,7 @@ namespace osu.Game.Screens.OnlinePlay.Components } catch (Exception ex) { - Logger.Error(ex, $"Failed to update room: {room.Name.Value}."); + Logger.Error(ex, $"Failed to update room: {room.Name}."); ignoredRooms.Add(room.RoomID.Value); rooms.Remove(room); diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index 7f0f26097c..7fddb8d1c4 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -169,7 +169,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = room.Name.Value.Split(':', StringSplitOptions.TrimEntries).Last(), + Text = room.Name.Split(':', StringSplitOptions.TrimEntries).Last(), Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f }, Shear = new Vector2(-OsuGame.SHEAR, 0f), Font = OsuFont.GetFont(size: 32, weight: FontWeight.Light, typeface: Typeface.TorusAlternate), diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 59c8047062..e1235ab41e 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.Threading; using osu.Framework.Allocation; @@ -26,6 +27,7 @@ using osu.Game.Overlays; using osu.Game.Screens.OnlinePlay.Components; using osuTK; using osuTK.Graphics; +using Container = osu.Framework.Graphics.Containers.Container; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { @@ -47,6 +49,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private RoomSpecialCategoryPill? specialCategoryPill; private PasswordProtectedIcon? passwordIcon; private EndDateInfo? endDateInfo; + private SpriteText? roomName; private UpdateableBeatmapBackgroundSprite background = null!; private DelayedLoadWrapper wrapper = null!; @@ -181,11 +184,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components Direction = FillDirection.Vertical, Children = new Drawable[] { - new TruncatingSpriteText + roomName = new TruncatingSpriteText { RelativeSizeAxes = Axes.X, - Font = OsuFont.GetFont(size: 28), - Current = { BindTarget = Room.Name } + Font = OsuFont.GetFont(size: 28) }, new RoomStatusText { @@ -247,6 +249,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { base.LoadComplete(); + Room.PropertyChanged += onRoomPropertyChanged; + wrapper.DelayedLoadComplete += _ => { Debug.Assert(specialCategoryPill != null); @@ -272,11 +276,25 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components hasPassword.BindTo(Room.HasPassword); hasPassword.BindValueChanged(v => passwordIcon.Alpha = v.NewValue ? 1 : 0, true); + + updateRoomName(); }; SelectedItem.BindValueChanged(item => background.Beatmap.Value = item.NewValue?.Beatmap, true); } + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.Name)) + updateRoomName(); + } + + private void updateRoomName() + { + if (roomName != null) + roomName.Text = Room.Name; + } + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { return new CachedModelDependencyContainer(base.CreateChildDependencies(parent)) @@ -332,6 +350,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components return pills; } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + Room.PropertyChanged -= onRoomPropertyChanged; + } + private partial class RoomStatusText : OnlinePlayComposite { public readonly IBindable SelectedItem = new Bindable(); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 36db1ab29b..a277b3771b 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -121,7 +121,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge public bool FilteringActive { get; set; } - public IEnumerable FilterTerms => new LocalisableString[] { Room.Name.Value }; + public IEnumerable FilterTerms => new LocalisableString[] { Room.Name }; private bool matchingFilter = true; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 3e80a5531a..3dc01cee54 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -350,7 +350,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match }; TypePicker.Current.BindValueChanged(type => typeLabel.Text = type.NewValue.GetLocalisableDescription(), true); - RoomName.BindValueChanged(name => NameField.Text = name.NewValue, true); Type.BindValueChanged(type => TypePicker.Current.Value = type.NewValue, true); MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); Password.BindValueChanged(password => PasswordTextBox.Text = password.NewValue ?? string.Empty, true); @@ -374,8 +373,21 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match drawablePlaylist.Items.BindTo(Playlist); drawablePlaylist.SelectedItem.BindTo(SelectedItem); + + room.PropertyChanged += onRoomPropertyChanged; + + updateRoomName(); } + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.Name)) + updateRoomName(); + } + + private void updateRoomName() + => NameField.Text = room.Name; + protected override void Update() { base.Update(); @@ -417,7 +429,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match } else { - room.Name.Value = NameField.Text; + room.Name = NameField.Text; room.Type.Value = TypePicker.Current.Value; room.Password.Value = PasswordTextBox.Current.Value; room.QueueMode.Value = QueueModeDropdown.Current.Value; @@ -467,6 +479,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match applyingSettingsOperation.Dispose(); applyingSettingsOperation = null; }); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } } public partial class CreateOrUpdateButton : RoundedButton diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs index a3a6fd2d8e..1ccd02ba8d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs @@ -73,7 +73,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override Room CreateNewRoom() => new Room { - Name = { Value = $"{api.LocalUser}'s awesome room" }, + Name = $"{api.LocalUser}'s awesome room", Type = { Value = MatchType.HeadToHead }, }; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 696eb58c3f..60c2a586f9 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public MultiplayerMatchSubScreen(Room room) : base(room) { - Title = room.RoomID == null ? "New room" : room.Name.Value; + Title = room.RoomID == null ? "New room" : room.Name; Activity.Value = new UserActivity.InLobby(room); } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 9911a08b81..f642b9c989 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -16,9 +16,6 @@ namespace osu.Game.Screens.OnlinePlay /// public partial class OnlinePlayComposite : CompositeDrawable { - [Resolved(typeof(Room), nameof(Room.Name))] - protected Bindable RoomName { get; private set; } = null!; - [Resolved(typeof(Room))] protected Bindable Host { get; private set; } = null!; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs index e1d747c3b0..a35d114418 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { return new Room { - Name = { Value = $"{api.LocalUser}'s awesome playlist" }, + Name = $"{api.LocalUser}'s awesome playlist", Type = { Value = MatchType.Playlists } }; } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index 4d85206473..84a4657693 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Specialized; +using System.ComponentModel; using System.Linq; using Humanizer; using Humanizer.Localisation; @@ -25,6 +26,7 @@ using osu.Game.Screens.OnlinePlay.Match.Components; using osuTK; using osu.Game.Localisation; using osu.Game.Rulesets; +using Container = osu.Framework.Graphics.Containers.Container; namespace osu.Game.Screens.OnlinePlay.Playlists { @@ -142,7 +144,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { RelativeSizeAxes = Axes.X, TabbableContentContainer = this, - LengthLimit = 100 + LengthLimit = 100, + Text = room.Name }, }, new Section("Duration") @@ -313,7 +316,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists loadingLayer = new LoadingLayer(true) }; - RoomName.BindValueChanged(name => NameField.Text = name.NewValue, true); Availability.BindValueChanged(availability => AvailabilityPicker.Current.Value = availability.NewValue, true); MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); MaxAttempts.BindValueChanged(count => MaxAttemptsField.Text = count.NewValue?.ToString(), true); @@ -337,6 +339,24 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Playlist.BindCollectionChanged(onPlaylistChanged, true); } + protected override void LoadComplete() + { + base.LoadComplete(); + + room.PropertyChanged += onRoomPropertyChanged; + + updateRoomName(); + } + + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.Name)) + updateRoomName(); + } + + private void updateRoomName() + => NameField.Text = room.Name; + private void populateDurations(ValueChangedEvent user) { // roughly correct (see https://github.com/Humanizr/Humanizer/blob/18167e56c082449cc4fe805b8429e3127a7b7f93/readme.md?plain=1#L427) @@ -384,7 +404,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists hideError(); - RoomName.Value = NameField.Text; + room.Name = NameField.Text; Availability.Value = AvailabilityPicker.Current.Value; if (int.TryParse(MaxParticipantsField.Text, out int max)) @@ -436,6 +456,12 @@ namespace osu.Game.Screens.OnlinePlay.Playlists ErrorText.FadeIn(50); loadingLayer.Hide(); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } } public partial class CreateRoomButton : RoundedButton diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index a53a89d601..9780c48f1f 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -42,7 +42,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists public PlaylistsRoomSubScreen(Room room) : base(room, false) // Editing is temporarily not allowed. { - Title = room.RoomID == null ? "New playlist" : room.Name.Value; + Title = room.RoomID == null ? "New playlist" : room.Name; Activity.Value = new UserActivity.InLobby(room); } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index 80c69db8b1..e540c78b0f 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { return new Room { - Name = { Value = "test name" }, + Name = "test name", Type = { Value = MatchType.HeadToHead }, Playlist = { diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 0c17998487..7a8e553452 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -230,7 +230,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Settings = { - Name = ServerAPIRoom.Name.Value, + Name = ServerAPIRoom.Name, MatchType = ServerAPIRoom.Type.Value, Password = password ?? string.Empty, QueueMode = ServerAPIRoom.QueueMode.Value, diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs index 2ce511928f..d8c110d12a 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay var room = new Room { RoomID = -currentRoomId, - Name = { Value = $@"Room {currentRoomId}" }, + Name = $@"Room {currentRoomId}", Host = { Value = new APIUser { Username = @"Host" } }, EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }, Category = { Value = withSpotlightRooms && i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal }, diff --git a/osu.Game/Users/UserActivity.cs b/osu.Game/Users/UserActivity.cs index ac0672c10f..93812e3f6b 100644 --- a/osu.Game/Users/UserActivity.cs +++ b/osu.Game/Users/UserActivity.cs @@ -249,7 +249,7 @@ namespace osu.Game.Users public InLobby(Room room) { RoomID = room.RoomID ?? -1; - RoomName = room.Name.Value; + RoomName = room.Name; } [SerializationConstructor] From 8694f7e1cc2dca48df8458fed22f433c2a7cdfa9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 17:32:32 +0900 Subject: [PATCH 852/924] Make `Room.Host` non-bindable --- .../Multiplayer/TestSceneDrawableRoom.cs | 2 +- .../TestSceneDrawableRoomParticipantsList.cs | 11 ++-- .../TestScenePlaylistsRoomCreation.cs | 12 ++--- .../Online/Multiplayer/MultiplayerClient.cs | 2 +- osu.Game/Online/Rooms/Room.cs | 17 ++++--- .../OnlinePlay/Components/RoomManager.cs | 2 +- .../Lounge/Components/DrawableRoom.cs | 2 +- .../DrawableRoomParticipantsList.cs | 51 ++++++++++++------- .../OnlinePlay/Match/DrawableMatchRoom.cs | 24 +++++++-- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 -- .../Visual/OnlinePlay/TestRoomManager.cs | 2 +- .../OnlinePlay/TestRoomRequestsHandler.cs | 4 +- 12 files changed, 80 insertions(+), 52 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index a386a26d32..17c0a159e9 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -195,7 +195,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private DrawableRoom createLoungeRoom(Room room) { - room.Host.Value ??= new APIUser { Username = "peppy", Id = 2 }; + room.Host ??= new APIUser { Username = "peppy", Id = 2 }; if (room.RecentParticipants.Count == 0) { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs index c959c07ffb..1e7003c612 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs @@ -26,17 +26,14 @@ namespace osu.Game.Tests.Visual.Multiplayer SelectedRoom.Value = new Room { Name = "test room", - Host = + Host = new APIUser { - Value = new APIUser - { - Id = 2, - Username = "peppy", - } + Id = 2, + Username = "peppy", } }; - Child = list = new DrawableRoomParticipantsList + Child = list = new DrawableRoomParticipantsList(SelectedRoom.Value) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index 8769ff2e27..7cc86e71bf 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -61,8 +61,8 @@ namespace osu.Game.Tests.Visual.Playlists setupAndCreateRoom(room => { room.Name = "my awesome room"; - room.Host.Value = API.LocalUser.Value; - room.RecentParticipants.Add(room.Host.Value); + room.Host = API.LocalUser.Value; + room.RecentParticipants.Add(room.Host); room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5); room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First()) { @@ -85,8 +85,8 @@ namespace osu.Game.Tests.Visual.Playlists { room.Name = "my awesome room"; room.MaxAttempts.Value = 5; - room.Host.Value = API.LocalUser.Value; - room.RecentParticipants.Add(room.Host.Value); + room.Host = API.LocalUser.Value; + room.RecentParticipants.Add(room.Host); room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5); room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First()) { @@ -103,7 +103,7 @@ namespace osu.Game.Tests.Visual.Playlists setupAndCreateRoom(room => { room.Name = "my awesome room"; - room.Host.Value = API.LocalUser.Value; + room.Host = API.LocalUser.Value; room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First()) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID @@ -151,7 +151,7 @@ namespace osu.Game.Tests.Visual.Playlists setupAndCreateRoom(room => { room.Name = "my awesome room"; - room.Host.Value = API.LocalUser.Value; + room.Host = API.LocalUser.Value; room.Playlist.Add(new PlaylistItem(new BeatmapInfo { MD5Hash = realHash, diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index b6ad361c44..0d2b18a483 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -528,7 +528,7 @@ namespace osu.Game.Online.Multiplayer var user = Room.Users.FirstOrDefault(u => u.UserID == userId); Room.Host = user; - APIRoom.Host.Value = user?.User; + APIRoom.Host = user?.User; RoomUpdated?.Invoke(); }, false); diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index d2aa8ee4fe..48282ba5e9 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -39,6 +39,12 @@ namespace osu.Game.Online.Rooms set => SetField(ref name, value); } + public APIUser? Host + { + get => host; + set => SetField(ref host, value); + } + /// /// Represents the current item selected within the room. /// @@ -57,13 +63,12 @@ namespace osu.Game.Online.Rooms [JsonProperty("name")] private string name = string.Empty; + [JsonProperty("host")] + private APIUser? host; + [JsonProperty("current_playlist_item")] private PlaylistItem? currentPlaylistItem; - [Cached] - [JsonProperty("host")] - public readonly Bindable Host = new Bindable(); - [Cached] [JsonProperty("playlist")] public readonly BindableList Playlist = new BindableList(); @@ -217,8 +222,8 @@ namespace osu.Game.Online.Rooms Category.Value = other.Category.Value; - if (other.Host.Value != null && Host.Value?.Id != other.Host.Value.Id) - Host.Value = other.Host.Value; + if (other.Host != null && Host?.Id != other.Host.Id) + Host = other.Host; ChannelId.Value = other.ChannelId.Value; Status.Value = other.Status.Value; diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs index 0bb33df966..ca42e98e3c 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs @@ -42,7 +42,7 @@ namespace osu.Game.Screens.OnlinePlay.Components public virtual void CreateRoom(Room room, Action? onSuccess = null, Action? onError = null) { - room.Host.Value = api.LocalUser.Value; + room.Host = api.LocalUser.Value; var req = new CreateRoomRequest(room); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index e1235ab41e..99809a2a50 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -224,7 +224,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components Children = new Drawable[] { ButtonsContainer, - drawableRoomParticipantsList = new DrawableRoomParticipantsList + drawableRoomParticipantsList = new DrawableRoomParticipantsList(Room) { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs index 60e05285d9..2ffe740e24 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs @@ -1,13 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Specialized; +using System.ComponentModel; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -16,31 +14,33 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Users.Drawables; using osuTK; +using Container = osu.Framework.Graphics.Containers.Container; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { public partial class DrawableRoomParticipantsList : OnlinePlayComposite { public const float SHEAR_WIDTH = 12f; - private const float avatar_size = 36; - private const float height = 60f; - private static readonly Vector2 shear = new Vector2(SHEAR_WIDTH / height, 0); - private FillFlowContainer avatarFlow; + private readonly Room room; - private CircularAvatar hostAvatar; - private LinkFlowContainer hostText; - private HiddenUserCount hiddenUsers; - private OsuSpriteText totalCount; + private FillFlowContainer avatarFlow = null!; + private CircularAvatar hostAvatar = null!; + private LinkFlowContainer hostText = null!; + private HiddenUserCount hiddenUsers = null!; + private OsuSpriteText totalCount = null!; - public DrawableRoomParticipantsList() + public DrawableRoomParticipantsList(Room room) { + this.room = room; + AutoSizeAxes = Axes.X; Height = height; } @@ -172,7 +172,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components totalCount.Text = ParticipantCount.Value.ToString(); }, true); - Host.BindValueChanged(onHostChanged, true); + room.PropertyChanged += onRoomPropertyChanged; + updateRoomHost(); } private int numberOfCircles = 4; @@ -199,7 +200,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components } } - private void onParticipantsChanged(object sender, NotifyCollectionChangedEventArgs e) + private void onParticipantsChanged(object? sender, NotifyCollectionChangedEventArgs e) { switch (e.Action) { @@ -269,21 +270,33 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components } } - private void onHostChanged(ValueChangedEvent host) + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) { - hostAvatar.User = host.NewValue; + if (e.PropertyName == nameof(Room.Host)) + updateRoomHost(); + } + + private void updateRoomHost() + { + hostAvatar.User = room.Host; hostText.Clear(); - if (host.NewValue != null) + if (room.Host != null) { hostText.AddText("hosted by "); - hostText.AddUserLink(host.NewValue); + hostText.AddUserLink(room.Host); } } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } + private partial class CircularAvatar : CompositeDrawable { - public APIUser User + public APIUser? User { get => avatar.User; set => avatar.User = value; diff --git a/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs b/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs index e5d3bd1586..0c993f4abf 100644 --- a/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs @@ -2,12 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.ComponentModel; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps.Drawables; using osu.Game.Online.API; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Resources.Localisation.Web; using osu.Game.Screens.OnlinePlay.Lounge.Components; @@ -30,7 +30,6 @@ namespace osu.Game.Screens.OnlinePlay.Match private IAPIProvider api { get; set; } = null!; private readonly BindableWithCurrent selectedItem = new BindableWithCurrent(); - private readonly IBindable host = new Bindable(); private readonly bool allowEdit; private Drawable? editButton; @@ -40,7 +39,6 @@ namespace osu.Game.Screens.OnlinePlay.Match this.allowEdit = allowEdit; base.SelectedItem.BindTo(SelectedItem); - host.BindTo(room.Host); } [BackgroundDependencyLoader] @@ -62,13 +60,31 @@ namespace osu.Game.Screens.OnlinePlay.Match { base.LoadComplete(); + Room.PropertyChanged += onRoomPropertyChanged; + updateRoomHost(); + } + + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.Host)) + updateRoomHost(); + } + + private void updateRoomHost() + { if (editButton != null) - host.BindValueChanged(h => editButton.Alpha = h.NewValue?.Equals(api.LocalUser.Value) == true ? 1 : 0, true); + editButton.Alpha = Room.Host?.Equals(api.LocalUser.Value) == true ? 1 : 0; } protected override UpdateableBeatmapBackgroundSprite CreateBackground() => base.CreateBackground().With(d => { d.BackgroundLoadDelay = 0; }); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + Room.PropertyChanged -= onRoomPropertyChanged; + } } } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index f642b9c989..4c614bba8f 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -16,9 +16,6 @@ namespace osu.Game.Screens.OnlinePlay /// public partial class OnlinePlayComposite : CompositeDrawable { - [Resolved(typeof(Room))] - protected Bindable Host { get; private set; } = null!; - [Resolved(typeof(Room))] protected Bindable Status { get; private set; } = null!; diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs index d8c110d12a..b0f91ca0b9 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay { RoomID = -currentRoomId, Name = $@"Room {currentRoomId}", - Host = { Value = new APIUser { Username = @"Host" } }, + Host = new APIUser { Username = @"Host" }, EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }, Category = { Value = withSpotlightRooms && i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal }, }; diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index cc8bd27d91..271b036d8e 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -262,12 +262,12 @@ namespace osu.Game.Tests.Visual.OnlinePlay public void AddServerSideRoom(Room room, APIUser host) { room.RoomID ??= currentRoomId++; - room.Host.Value = host; + room.Host = host; for (int i = 0; i < room.Playlist.Count; i++) { room.Playlist[i].ID = currentPlaylistItemId++; - room.Playlist[i].OwnerID = room.Host.Value.OnlineID; + room.Playlist[i].OwnerID = room.Host.OnlineID; } serverSideRooms.Add(room); From bde7b8e610d9f72a33cf05a6d2ae208e4daf3f58 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 18:27:32 +0900 Subject: [PATCH 853/924] Make `Room.Category` non-bindable --- .../DailyChallenge/TestSceneDailyChallenge.cs | 6 ++-- .../TestSceneDailyChallengeIntro.cs | 2 +- .../Multiplayer/TestSceneDrawableRoom.cs | 4 +-- .../TestSceneLoungeRoomsContainer.cs | 10 +++--- osu.Game/Online/Rooms/Room.cs | 30 +++++++++------- .../Components/ListingPollingComponent.cs | 2 +- .../Components/StatusColouredContainer.cs | 12 +++---- .../Lounge/Components/DrawableRoom.cs | 32 ++++++++++------- .../Components/RoomSpecialCategoryPill.cs | 34 ++++++++++++++++--- .../Lounge/Components/RoomsContainer.cs | 2 +- .../OnlinePlay/Lounge/DrawableLoungeRoom.cs | 2 +- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 -- .../Visual/OnlinePlay/TestRoomManager.cs | 2 +- 13 files changed, 86 insertions(+), 55 deletions(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs index 46a951dcff..e05f5f13ae 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge } }, EndDate = { Value = DateTimeOffset.Now.AddHours(12) }, - Category = { Value = RoomCategory.DailyChallenge } + Category = RoomCategory.DailyChallenge }; AddStep("add room", () => API.Perform(new CreateRoomRequest(room))); @@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge } }, EndDate = { Value = DateTimeOffset.Now.AddHours(12) }, - Category = { Value = RoomCategory.DailyChallenge } + Category = RoomCategory.DailyChallenge }; AddStep("add room", () => API.Perform(new CreateRoomRequest(room))); @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge } }, EndDate = { Value = DateTimeOffset.Now.AddHours(12) }, - Category = { Value = RoomCategory.DailyChallenge } + Category = RoomCategory.DailyChallenge }; AddStep("add room", () => API.Perform(new CreateRoomRequest(room))); diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs index 7b3595b064..33be952e20 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs @@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge }, StartDate = { Value = DateTimeOffset.Now }, EndDate = { Value = DateTimeOffset.Now.AddHours(24) }, - Category = { Value = RoomCategory.DailyChallenge } + Category = RoomCategory.DailyChallenge })); }); AddStep("signal client", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo { RoomID = roomId })); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index 17c0a159e9..07e557fe36 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -110,13 +110,13 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Spotlight room", Status = { Value = new RoomStatusOpen() }, - Category = { Value = RoomCategory.Spotlight }, + Category = RoomCategory.Spotlight, }), createLoungeRoom(new Room { Name = "Featured artist room", Status = { Value = new RoomStatusOpen() }, - Category = { Value = RoomCategory.FeaturedArtist }, + Category = RoomCategory.FeaturedArtist, }), } }; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 112fdfd9ab..8654fe1f11 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -55,20 +55,20 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("has 5 rooms", () => container.Rooms.Count == 5); AddAssert("all spotlights at top", () => container.Rooms - .SkipWhile(r => r.Room.Category.Value == RoomCategory.Spotlight) - .All(r => r.Room.Category.Value == RoomCategory.Normal)); + .SkipWhile(r => r.Room.Category == RoomCategory.Spotlight) + .All(r => r.Room.Category == RoomCategory.Normal)); AddStep("remove first room", () => RoomManager.RemoveRoom(RoomManager.Rooms.First(r => r.RoomID == 0))); AddAssert("has 4 rooms", () => container.Rooms.Count == 4); AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID != 0)); AddStep("select first room", () => container.Rooms.First().TriggerClick()); - AddAssert("first spotlight selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category.Value == RoomCategory.Spotlight))); + AddAssert("first spotlight selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category == RoomCategory.Spotlight))); AddStep("remove last room", () => RoomManager.RemoveRoom(RoomManager.Rooms.MinBy(r => r.RoomID))); - AddAssert("first spotlight still selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category.Value == RoomCategory.Spotlight))); + AddAssert("first spotlight still selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category == RoomCategory.Spotlight))); - AddStep("remove spotlight room", () => RoomManager.RemoveRoom(RoomManager.Rooms.Single(r => r.Category.Value == RoomCategory.Spotlight))); + AddStep("remove spotlight room", () => RoomManager.RemoveRoom(RoomManager.Rooms.Single(r => r.Category == RoomCategory.Spotlight))); AddAssert("selection vacated", () => checkRoomSelected(null)); } diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 48282ba5e9..1a7a3f73e8 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -39,12 +39,24 @@ namespace osu.Game.Online.Rooms set => SetField(ref name, value); } + /// + /// The room host. Will be null while the room has not yet been created. + /// public APIUser? Host { get => host; set => SetField(ref host, value); } + /// + /// The room category. + /// + public RoomCategory Category + { + get => category; + set => SetField(ref category, value); + } + /// /// Represents the current item selected within the room. /// @@ -66,6 +78,10 @@ namespace osu.Game.Online.Rooms [JsonProperty("host")] private APIUser? host; + [JsonProperty("category")] + [JsonConverter(typeof(SnakeCaseStringEnumConverter))] + private RoomCategory category; + [JsonProperty("current_playlist_item")] private PlaylistItem? currentPlaylistItem; @@ -85,18 +101,6 @@ namespace osu.Game.Online.Rooms [Cached] public readonly Bindable DifficultyRange = new Bindable(); - [Cached] - public readonly Bindable Category = new Bindable(); - - // Todo: osu-framework bug (https://github.com/ppy/osu-framework/issues/4106) - [JsonProperty("category")] - [JsonConverter(typeof(SnakeCaseStringEnumConverter))] - private RoomCategory category - { - get => Category.Value; - set => Category.Value = value; - } - [Cached] public readonly Bindable MaxAttempts = new Bindable(); @@ -220,7 +224,7 @@ namespace osu.Game.Online.Rooms RoomID = other.RoomID; Name = other.Name; - Category.Value = other.Category.Value; + Category = other.Category; if (other.Host != null && Host?.Id != other.Host.Id) Host = other.Host; diff --git a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs index 0020a7dfb6..67c3586a35 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.OnlinePlay.Components req.Success += result => { - result = result.Where(r => r.Category.Value != RoomCategory.DailyChallenge).ToList(); + result = result.Where(r => r.Category != RoomCategory.DailyChallenge).ToList(); foreach (var existing in RoomManager.Rooms.ToArray()) { diff --git a/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs b/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs index ed39021a73..90127dc961 100644 --- a/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -17,13 +15,13 @@ namespace osu.Game.Screens.OnlinePlay.Components private readonly double transitionDuration; [Resolved(typeof(Room), nameof(Room.Status))] - private Bindable status { get; set; } + private Bindable status { get; set; } = null!; - [Resolved(typeof(Room), nameof(Room.Category))] - private Bindable category { get; set; } + private readonly Room room; - public StatusColouredContainer(double transitionDuration = 100) + public StatusColouredContainer(Room room, double transitionDuration = 100) { + this.room = room; this.transitionDuration = transitionDuration; } @@ -32,7 +30,7 @@ namespace osu.Game.Screens.OnlinePlay.Components { status.BindValueChanged(s => { - this.FadeColour(colours.ForRoomCategory(category.Value) ?? s.NewValue.GetAppropriateColour(colours), transitionDuration); + this.FadeColour(colours.ForRoomCategory(room.Category) ?? s.NewValue.GetAppropriateColour(colours), transitionDuration); }, true); } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 99809a2a50..8b2f2bfe4a 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -164,7 +164,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft }, - specialCategoryPill = new RoomSpecialCategoryPill + specialCategoryPill = new RoomSpecialCategoryPill(Room) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft @@ -259,15 +259,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components wrapper.FadeInFromZero(200); - roomCategory.BindTo(Room.Category); - roomCategory.BindValueChanged(c => - { - if (c.NewValue > RoomCategory.Normal) - specialCategoryPill.Show(); - else - specialCategoryPill.Hide(); - }, true); - roomType.BindTo(Room.Type); roomType.BindValueChanged(t => { @@ -278,6 +269,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components hasPassword.BindValueChanged(v => passwordIcon.Alpha = v.NewValue ? 1 : 0, true); updateRoomName(); + updateRoomCategory(); }; SelectedItem.BindValueChanged(item => background.Beatmap.Value = item.NewValue?.Beatmap, true); @@ -285,8 +277,16 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(Room.Name)) - updateRoomName(); + switch (e.PropertyName) + { + case nameof(Room.Name): + updateRoomName(); + break; + + case nameof(Room.Category): + updateRoomCategory(); + break; + } } private void updateRoomName() @@ -295,6 +295,14 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components roomName.Text = Room.Name; } + private void updateRoomCategory() + { + if (Room.Category > RoomCategory.Normal) + specialCategoryPill?.Show(); + else + specialCategoryPill?.Hide(); + } + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { return new CachedModelDependencyContainer(base.CreateChildDependencies(parent)) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs index 9b8954bb33..9bb3a59d0c 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs @@ -1,21 +1,30 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.ComponentModel; using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; +using osu.Game.Online.Rooms; using osuTK.Graphics; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { public partial class RoomSpecialCategoryPill : OnlinePlayPill { + private readonly Room room; + [Resolved] private OsuColour colours { get; set; } = null!; protected override FontUsage Font => base.Font.With(weight: FontWeight.SemiBold); + public RoomSpecialCategoryPill(Room room) + { + this.room = room; + } + protected override void LoadComplete() { base.LoadComplete(); @@ -23,11 +32,26 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components Pill.Background.Alpha = 1; TextFlow.Colour = Color4.Black; - Category.BindValueChanged(c => - { - TextFlow.Text = c.NewValue.GetLocalisableDescription(); - Pill.Background.Colour = colours.ForRoomCategory(c.NewValue) ?? colours.Pink; - }, true); + room.PropertyChanged += onRoomPropertyChanged; + updateRoomCategory(); + } + + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.Category)) + updateRoomCategory(); + } + + private void updateRoomCategory() + { + TextFlow.Text = room.Category.GetLocalisableDescription(); + Pill.Background.Colour = colours.ForRoomCategory(room.Category) ?? colours.Pink; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; } } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 3d9b7a46d0..f197a7d018 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -169,7 +169,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { foreach (var room in roomFlow) { - roomFlow.SetLayoutPosition(room, room.Room.Category.Value > RoomCategory.Normal + roomFlow.SetLayoutPosition(room, room.Room.Category > RoomCategory.Normal // Always show spotlight playlists at the top of the listing. ? float.MinValue : -(room.Room.RoomID ?? 0)); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index a277b3771b..1fc177ce0b 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -66,7 +66,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge AddRangeInternal(new Drawable[] { - new StatusColouredContainer(transition_duration) + new StatusColouredContainer(Room, transition_duration) { RelativeSizeAxes = Axes.Both, Child = selectionBox = new Container diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 4c614bba8f..0a51dcc8eb 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -31,9 +31,6 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected Bindable DifficultyRange { get; private set; } = null!; - [Resolved(typeof(Room))] - protected Bindable Category { get; private set; } = null!; - [Resolved(typeof(Room))] protected BindableList RecentParticipants { get; private set; } = null!; diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs index b0f91ca0b9..07dd57eb68 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay Name = $@"Room {currentRoomId}", Host = new APIUser { Username = @"Host" }, EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }, - Category = { Value = withSpotlightRooms && i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal }, + Category = withSpotlightRooms && i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal, }; if (withPassword) From 81e4cb348fe4f39b4ff1198857845694e99c7f55 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 18:36:47 +0900 Subject: [PATCH 854/924] Make `Room.Type` non-bindable --- .../Multiplayer/TestSceneDrawableRoom.cs | 12 ++++---- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 10 +++---- .../Online/Multiplayer/MultiplayerClient.cs | 2 +- osu.Game/Online/Rooms/Room.cs | 27 ++++++++--------- .../Lounge/Components/DrawableRoom.cs | 29 ++++++++++++------- .../Lounge/Components/MatchTypePill.cs | 28 +++++++++++++++--- .../Match/MultiplayerMatchSettingsOverlay.cs | 19 +++++++++--- .../Multiplayer/MultiplayerLoungeSubScreen.cs | 2 +- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 -- .../Playlists/PlaylistsLoungeSubScreen.cs | 8 ++--- .../Multiplayer/MultiplayerTestScene.cs | 4 +-- .../Multiplayer/TestMultiplayerClient.cs | 2 +- 12 files changed, 89 insertions(+), 57 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index 07e557fe36..4c9aa6af15 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -78,7 +78,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Multiplayer room", Status = { Value = new RoomStatusOpen() }, EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, - Type = { Value = MatchType.HeadToHead }, + Type = MatchType.HeadToHead, Playlist = { item1 }, CurrentPlaylistItem = item1 }), @@ -88,7 +88,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Status = { Value = new RoomStatusOpenPrivate() }, HasPassword = { Value = true }, EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, - Type = { Value = MatchType.HeadToHead }, + Type = MatchType.HeadToHead, Playlist = { item3 }, CurrentPlaylistItem = item3 }), @@ -137,7 +137,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Room with password", Status = { Value = new RoomStatusOpen() }, - Type = { Value = MatchType.HeadToHead }, + Type = MatchType.HeadToHead, })); AddUntilStep("wait for panel load", () => drawableRoom.ChildrenOfType().Any()); @@ -166,7 +166,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "A host-only room", QueueMode = { Value = QueueMode.HostOnly }, - Type = { Value = MatchType.HeadToHead }, + Type = MatchType.HeadToHead, }) { SelectedItem = new Bindable() @@ -175,7 +175,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "An all-players, team-versus room", QueueMode = { Value = QueueMode.AllPlayers }, - Type = { Value = MatchType.TeamVersus } + Type = MatchType.TeamVersus }) { SelectedItem = new Bindable() @@ -184,7 +184,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "A round-robin room", QueueMode = { Value = QueueMode.AllPlayersRoundRobin }, - Type = { Value = MatchType.HeadToHead } + Type = MatchType.HeadToHead }) { SelectedItem = new Bindable() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index 7c30b72747..b68089015e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Multiplayer createRoom(() => new Room { Name = "Test Room", - Type = { Value = MatchType.TeamVersus }, + Type = MatchType.TeamVersus, Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -83,7 +83,7 @@ namespace osu.Game.Tests.Visual.Multiplayer createRoom(() => new Room { Name = "Test Room", - Type = { Value = MatchType.TeamVersus }, + Type = MatchType.TeamVersus, Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Multiplayer createRoom(() => new Room { Name = "Test Room", - Type = { Value = MatchType.HeadToHead }, + Type = MatchType.HeadToHead, Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -130,14 +130,14 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddUntilStep("match type head to head", () => multiplayerClient.ClientAPIRoom?.Type.Value == MatchType.HeadToHead); + AddUntilStep("match type head to head", () => multiplayerClient.ClientAPIRoom?.Type == MatchType.HeadToHead); AddStep("change match type", () => multiplayerClient.ChangeSettings(new MultiplayerRoomSettings { MatchType = MatchType.TeamVersus }).WaitSafely()); - AddUntilStep("api room updated to team versus", () => multiplayerClient.ClientAPIRoom?.Type.Value == MatchType.TeamVersus); + AddUntilStep("api room updated to team versus", () => multiplayerClient.ClientAPIRoom?.Type == MatchType.TeamVersus); } [Test] diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 0d2b18a483..a6037e8907 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -844,7 +844,7 @@ namespace osu.Game.Online.Multiplayer APIRoom.Name = Room.Settings.Name; APIRoom.Password.Value = Room.Settings.Password; APIRoom.Status.Value = string.IsNullOrEmpty(Room.Settings.Password) ? new RoomStatusOpen() : new RoomStatusOpenPrivate(); - APIRoom.Type.Value = Room.Settings.MatchType; + APIRoom.Type = Room.Settings.MatchType; APIRoom.QueueMode.Value = Room.Settings.QueueMode; APIRoom.AutoStartDuration.Value = Room.Settings.AutoStartDuration; APIRoom.CurrentPlaylistItem = APIRoom.Playlist.Single(item => item.ID == settings.PlaylistItemId); diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 1a7a3f73e8..acc7d7b3df 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -57,6 +57,15 @@ namespace osu.Game.Online.Rooms set => SetField(ref category, value); } + /// + /// The match type. + /// + public MatchType Type + { + get => type; + set => SetField(ref type, value); + } + /// /// Represents the current item selected within the room. /// @@ -82,6 +91,10 @@ namespace osu.Game.Online.Rooms [JsonConverter(typeof(SnakeCaseStringEnumConverter))] private RoomCategory category; + [JsonConverter(typeof(SnakeCaseStringEnumConverter))] + [JsonProperty("type")] + private MatchType type; + [JsonProperty("current_playlist_item")] private PlaylistItem? currentPlaylistItem; @@ -110,18 +123,6 @@ namespace osu.Game.Online.Rooms [Cached] public readonly Bindable Availability = new Bindable(); - [Cached] - public readonly Bindable Type = new Bindable(); - - // Todo: osu-framework bug (https://github.com/ppy/osu-framework/issues/4106) - [JsonConverter(typeof(SnakeCaseStringEnumConverter))] - [JsonProperty("type")] - private MatchType type - { - get => Type.Value; - set => Type.Value = value; - } - [Cached] public readonly Bindable QueueMode = new Bindable(); @@ -233,7 +234,7 @@ namespace osu.Game.Online.Rooms Status.Value = other.Status.Value; Availability.Value = other.Availability.Value; HasPassword.Value = other.HasPassword.Value; - Type.Value = other.Type.Value; + Type = other.Type; MaxParticipants.Value = other.MaxParticipants.Value; ParticipantCount.Value = other.ParticipantCount.Value; EndDate.Value = other.EndDate.Value; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 8b2f2bfe4a..d05d887038 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -189,7 +189,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components RelativeSizeAxes = Axes.X, Font = OsuFont.GetFont(size: 28) }, - new RoomStatusText + new RoomStatusText(Room) { SelectedItem = { BindTarget = SelectedItem } } @@ -259,17 +259,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components wrapper.FadeInFromZero(200); - roomType.BindTo(Room.Type); - roomType.BindValueChanged(t => - { - endDateInfo.Alpha = t.NewValue == MatchType.Playlists ? 1 : 0; - }, true); - hasPassword.BindTo(Room.HasPassword); hasPassword.BindValueChanged(v => passwordIcon.Alpha = v.NewValue ? 1 : 0, true); updateRoomName(); updateRoomCategory(); + updateRoomType(); }; SelectedItem.BindValueChanged(item => background.Beatmap.Value = item.NewValue?.Beatmap, true); @@ -286,6 +281,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components case nameof(Room.Category): updateRoomCategory(); break; + + case nameof(Room.Type): + updateRoomType(); + break; } } @@ -303,6 +302,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components specialCategoryPill?.Hide(); } + private void updateRoomType() + { + if (endDateInfo != null) + endDateInfo.Alpha = Room.Type == MatchType.Playlists ? 1 : 0; + } + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { return new CachedModelDependencyContainer(base.CreateChildDependencies(parent)) @@ -331,11 +336,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { var pills = new List(); - if (Room.Type.Value != MatchType.Playlists) + if (Room.Type != MatchType.Playlists) { pills.AddRange(new OnlinePlayComposite[] { - new MatchTypePill(), + new MatchTypePill(Room), new QueueModePill(), }); } @@ -374,11 +379,13 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components [Resolved] private BeatmapLookupCache beatmapLookupCache { get; set; } = null!; + private readonly Room room; private SpriteText statusText = null!; private LinkFlowContainer beatmapText = null!; - public RoomStatusText() + public RoomStatusText(Room room) { + this.room = room; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; } @@ -437,7 +444,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components beatmapLookupCancellation?.Cancel(); beatmapText.Clear(); - if (Type.Value == MatchType.Playlists) + if (room.Type == MatchType.Playlists) { statusText.Text = "Ready to play"; return; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/MatchTypePill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/MatchTypePill.cs index e30d673b26..d5405c2d0e 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/MatchTypePill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/MatchTypePill.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.Bindables; +using System.ComponentModel; using osu.Framework.Extensions; using osu.Game.Online.Rooms; @@ -9,16 +9,36 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { public partial class MatchTypePill : OnlinePlayPill { + private readonly Room room; + + public MatchTypePill(Room room) + { + this.room = room; + } + protected override void LoadComplete() { base.LoadComplete(); - Type.BindValueChanged(onMatchTypeChanged, true); + room.PropertyChanged += onRoomPropertyChanged; + updateRoomType(); } - private void onMatchTypeChanged(ValueChangedEvent type) + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) { - TextFlow.Text = type.NewValue.GetLocalisableDescription(); + if (e.PropertyName == nameof(Room.Type)) + updateRoomType(); + } + + private void updateRoomType() + { + TextFlow.Text = room.Type.GetLocalisableDescription(); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 3dc01cee54..226484e9a5 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -350,7 +350,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match }; TypePicker.Current.BindValueChanged(type => typeLabel.Text = type.NewValue.GetLocalisableDescription(), true); - Type.BindValueChanged(type => TypePicker.Current.Value = type.NewValue, true); MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); Password.BindValueChanged(password => PasswordTextBox.Text = password.NewValue ?? string.Empty, true); QueueMode.BindValueChanged(mode => QueueModeDropdown.Current.Value = mode.NewValue, true); @@ -377,17 +376,29 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match room.PropertyChanged += onRoomPropertyChanged; updateRoomName(); + updateRoomType(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(Room.Name)) - updateRoomName(); + switch (e.PropertyName) + { + case nameof(Room.Name): + updateRoomName(); + break; + + case nameof(Room.Type): + updateRoomName(); + break; + } } private void updateRoomName() => NameField.Text = room.Name; + private void updateRoomType() + => TypePicker.Current.Value = room.Type; + protected override void Update() { base.Update(); @@ -430,7 +441,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match else { room.Name = NameField.Text; - room.Type.Value = TypePicker.Current.Value; + room.Type = TypePicker.Current.Value; room.Password.Value = PasswordTextBox.Current.Value; room.QueueMode.Value = QueueModeDropdown.Current.Value; room.AutoStartDuration.Value = autoStartDuration; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs index 1ccd02ba8d..a5cc267569 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs @@ -74,7 +74,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override Room CreateNewRoom() => new Room { Name = $"{api.LocalUser}'s awesome room", - Type = { Value = MatchType.HeadToHead }, + Type = MatchType.HeadToHead, }; protected override RoomSubScreen CreateRoomSubScreen(Room room) => new MultiplayerMatchSubScreen(room); diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 0a51dcc8eb..7e29c2f117 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -19,9 +19,6 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected Bindable Status { get; private set; } = null!; - [Resolved(typeof(Room))] - protected Bindable Type { get; private set; } = null!; - [Resolved(typeof(Room))] protected Bindable PlaylistItemStats { get; private set; } = null!; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs index a35d114418..d66b4f844c 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.ComponentModel; using System.Linq; @@ -22,9 +20,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists public partial class PlaylistsLoungeSubScreen : LoungeSubScreen { [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; - private Dropdown categoryDropdown; + private Dropdown categoryDropdown = null!; protected override IEnumerable CreateFilterControls() { @@ -68,7 +66,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists return new Room { Name = $"{api.LocalUser}'s awesome playlist", - Type = { Value = MatchType.Playlists } + Type = MatchType.Playlists }; } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index e540c78b0f..774b1d5931 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Online.Rooms; using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Visual.OnlinePlay; @@ -38,7 +36,7 @@ namespace osu.Game.Tests.Visual.Multiplayer return new Room { Name = "test name", - Type = { Value = MatchType.HeadToHead }, + Type = MatchType.HeadToHead, Playlist = { new PlaylistItem(new TestBeatmap(Ruleset.Value).BeatmapInfo) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 7a8e553452..6111ba0a6d 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -231,7 +231,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Settings = { Name = ServerAPIRoom.Name, - MatchType = ServerAPIRoom.Type.Value, + MatchType = ServerAPIRoom.Type, Password = password ?? string.Empty, QueueMode = ServerAPIRoom.QueueMode.Value, AutoStartDuration = ServerAPIRoom.AutoStartDuration.Value From 5d4838a08b93cd4ae5d4d963710b6e679fdada69 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 18:48:13 +0900 Subject: [PATCH 855/924] Make `Room.Status` non-bindable --- .../Multiplayer/TestSceneDrawableRoom.cs | 14 +++---- .../Online/Multiplayer/MultiplayerClient.cs | 8 ++-- osu.Game/Online/Rooms/GetRoomsRequest.cs | 6 +-- osu.Game/Online/Rooms/Room.cs | 19 ++++++--- .../Components/StatusColouredContainer.cs | 39 +++++++++++++------ .../Lounge/Components/DrawableRoom.cs | 2 +- .../Lounge/Components/RoomStatusPill.cs | 35 +++++++++++++---- .../Multiplayer/MultiplayerRoomManager.cs | 2 +- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 -- 9 files changed, 84 insertions(+), 44 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index 4c9aa6af15..a40a75c885 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Multiplayer createLoungeRoom(new Room { Name = "Multiplayer room", - Status = { Value = new RoomStatusOpen() }, + Status = new RoomStatusOpen(), EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, Type = MatchType.HeadToHead, Playlist = { item1 }, @@ -85,7 +85,7 @@ namespace osu.Game.Tests.Visual.Multiplayer createLoungeRoom(new Room { Name = "Private room", - Status = { Value = new RoomStatusOpenPrivate() }, + Status = new RoomStatusOpenPrivate(), HasPassword = { Value = true }, EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, Type = MatchType.HeadToHead, @@ -95,7 +95,7 @@ namespace osu.Game.Tests.Visual.Multiplayer createLoungeRoom(new Room { Name = "Playlist room with multiple beatmaps", - Status = { Value = new RoomStatusPlaying() }, + Status = new RoomStatusPlaying(), EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, Playlist = { item1, item2 }, CurrentPlaylistItem = item1 @@ -103,19 +103,19 @@ namespace osu.Game.Tests.Visual.Multiplayer createLoungeRoom(new Room { Name = "Finished room", - Status = { Value = new RoomStatusEnded() }, + Status = new RoomStatusEnded(), EndDate = { Value = DateTimeOffset.Now }, }), createLoungeRoom(new Room { Name = "Spotlight room", - Status = { Value = new RoomStatusOpen() }, + Status = new RoomStatusOpen(), Category = RoomCategory.Spotlight, }), createLoungeRoom(new Room { Name = "Featured artist room", - Status = { Value = new RoomStatusOpen() }, + Status = new RoomStatusOpen(), Category = RoomCategory.FeaturedArtist, }), } @@ -136,7 +136,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create room", () => Child = drawableRoom = createLoungeRoom(room = new Room { Name = "Room with password", - Status = { Value = new RoomStatusOpen() }, + Status = new RoomStatusOpen(), Type = MatchType.HeadToHead, })); diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index a6037e8907..f26dbb3239 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -397,15 +397,15 @@ namespace osu.Game.Online.Multiplayer switch (state) { case MultiplayerRoomState.Open: - APIRoom.Status.Value = APIRoom.HasPassword.Value ? new RoomStatusOpenPrivate() : new RoomStatusOpen(); + APIRoom.Status = APIRoom.HasPassword.Value ? new RoomStatusOpenPrivate() : new RoomStatusOpen(); break; case MultiplayerRoomState.Playing: - APIRoom.Status.Value = new RoomStatusPlaying(); + APIRoom.Status = new RoomStatusPlaying(); break; case MultiplayerRoomState.Closed: - APIRoom.Status.Value = new RoomStatusEnded(); + APIRoom.Status = new RoomStatusEnded(); break; } @@ -843,7 +843,7 @@ namespace osu.Game.Online.Multiplayer Room.Settings = settings; APIRoom.Name = Room.Settings.Name; APIRoom.Password.Value = Room.Settings.Password; - APIRoom.Status.Value = string.IsNullOrEmpty(Room.Settings.Password) ? new RoomStatusOpen() : new RoomStatusOpenPrivate(); + APIRoom.Status = string.IsNullOrEmpty(Room.Settings.Password) ? new RoomStatusOpen() : new RoomStatusOpenPrivate(); APIRoom.Type = Room.Settings.MatchType; APIRoom.QueueMode.Value = Room.Settings.QueueMode; APIRoom.AutoStartDuration.Value = Room.Settings.AutoStartDuration; diff --git a/osu.Game/Online/Rooms/GetRoomsRequest.cs b/osu.Game/Online/Rooms/GetRoomsRequest.cs index 1b5e08c729..980ce74718 100644 --- a/osu.Game/Online/Rooms/GetRoomsRequest.cs +++ b/osu.Game/Online/Rooms/GetRoomsRequest.cs @@ -45,11 +45,11 @@ namespace osu.Game.Online.Rooms foreach (var room in Response) { if (room.EndDate.Value != null && DateTimeOffset.Now >= room.EndDate.Value) - room.Status.Value = new RoomStatusEnded(); + room.Status = new RoomStatusEnded(); else if (room.HasPassword.Value) - room.Status.Value = new RoomStatusOpenPrivate(); + room.Status = new RoomStatusOpenPrivate(); else - room.Status.Value = new RoomStatusOpen(); + room.Status = new RoomStatusOpen(); } } } diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index acc7d7b3df..760580c003 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -78,6 +78,15 @@ namespace osu.Game.Online.Rooms set => SetField(ref currentPlaylistItem, value); } + /// + /// The current room status. + /// + public RoomStatus Status + { + get => status; + set => SetField(ref status, value); + } + [JsonProperty("id")] private long? roomId; @@ -98,6 +107,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("current_playlist_item")] private PlaylistItem? currentPlaylistItem; + // Not serialised (see: GetRoomsRequest). + private RoomStatus status = new RoomStatusOpen(); + [Cached] [JsonProperty("playlist")] public readonly BindableList Playlist = new BindableList(); @@ -117,9 +129,6 @@ namespace osu.Game.Online.Rooms [Cached] public readonly Bindable MaxAttempts = new Bindable(); - [Cached] - public readonly Bindable Status = new Bindable(new RoomStatusOpen()); - [Cached] public readonly Bindable Availability = new Bindable(); @@ -231,7 +240,7 @@ namespace osu.Game.Online.Rooms Host = other.Host; ChannelId.Value = other.ChannelId.Value; - Status.Value = other.Status.Value; + Status = other.Status; Availability.Value = other.Availability.Value; HasPassword.Value = other.HasPassword.Value; Type = other.Type; @@ -266,7 +275,7 @@ namespace osu.Game.Online.Rooms // Todo: This is not the best way/place to do this, but the intention is to display all playlist items when the room has ended, // and display only the non-expired playlist items while the room is still active. In order to achieve this, all expired items are removed from the source Room. // More refactoring is required before this can be done locally instead - DrawableRoomPlaylist is currently directly bound to the playlist to display items in the room. - if (!(Status.Value is RoomStatusEnded)) + if (Status is not RoomStatusEnded) Playlist.RemoveAll(i => i.Expired); } diff --git a/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs b/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs index 90127dc961..2b1233506f 100644 --- a/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs @@ -1,22 +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.ComponentModel; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Online.Rooms; +using Container = osu.Framework.Graphics.Containers.Container; namespace osu.Game.Screens.OnlinePlay.Components { public partial class StatusColouredContainer : Container { + [Resolved] + private OsuColour colours { get; set; } = null!; + private readonly double transitionDuration; - - [Resolved(typeof(Room), nameof(Room.Status))] - private Bindable status { get; set; } = null!; - private readonly Room room; public StatusColouredContainer(Room room, double transitionDuration = 100) @@ -25,13 +24,29 @@ namespace osu.Game.Screens.OnlinePlay.Components this.transitionDuration = transitionDuration; } - [BackgroundDependencyLoader] - private void load(OsuColour colours) + protected override void LoadComplete() { - status.BindValueChanged(s => - { - this.FadeColour(colours.ForRoomCategory(room.Category) ?? s.NewValue.GetAppropriateColour(colours), transitionDuration); - }, true); + base.LoadComplete(); + + room.PropertyChanged += onRoomPropertyChanged; + updateRoomStatus(); + } + + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.Status)) + updateRoomStatus(); + } + + private void updateRoomStatus() + { + this.FadeColour(colours.ForRoomCategory(room.Category) ?? room.Status.GetAppropriateColour(colours), transitionDuration); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; } } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index d05d887038..8fb439452c 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -159,7 +159,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components Spacing = new Vector2(5), Children = new Drawable[] { - new RoomStatusPill + new RoomStatusPill(Room) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs index 96d698a184..10d3695d14 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.ComponentModel; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; @@ -19,25 +20,43 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components protected override FontUsage Font => base.Font.With(weight: FontWeight.SemiBold); + private readonly Room room; + + public RoomStatusPill(Room room) + { + this.room = room; + } + protected override void LoadComplete() { base.LoadComplete(); - EndDate.BindValueChanged(_ => updateDisplay()); - Status.BindValueChanged(_ => updateDisplay(), true); - - FinishTransforms(true); - TextFlow.Colour = Colour4.Black; Pill.Background.Alpha = 1; + + EndDate.BindValueChanged(_ => updateDisplay()); + room.PropertyChanged += onRoomPropertyChanged; + updateDisplay(); + + FinishTransforms(true); + } + + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.Status)) + updateDisplay(); } private void updateDisplay() { - RoomStatus status = Status.Value; + Pill.Background.FadeColour(room.Status.GetAppropriateColour(colours), 100); + TextFlow.Text = room.Status.Message; + } - Pill.Background.FadeColour(status.GetAppropriateColour(colours), 100); - TextFlow.Text = status.Message; + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs index d7efd30435..b119c6d59d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer // this is done here as a pre-check to avoid clicking on already closed rooms in the lounge from triggering a server join. // should probably be done at a higher level, but due to the current structure of things this is the easiest place for now. - if (room.Status.Value is RoomStatusEnded) + if (room.Status is RoomStatusEnded) { onError?.Invoke("Cannot join an ended room."); return; diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 7e29c2f117..82774abac7 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -16,9 +16,6 @@ namespace osu.Game.Screens.OnlinePlay /// public partial class OnlinePlayComposite : CompositeDrawable { - [Resolved(typeof(Room))] - protected Bindable Status { get; private set; } = null!; - [Resolved(typeof(Room))] protected Bindable PlaylistItemStats { get; private set; } = null!; From 7e3e5208f0c719993f7bcc46809069979287509e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 18:52:04 +0900 Subject: [PATCH 856/924] Make `Room.Availability` non-bindable --- osu.Game/Online/Rooms/Room.cs | 17 +++++++++++++---- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 --- .../Playlists/PlaylistsRoomSettingsOverlay.cs | 19 +++++++++++++++---- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 760580c003..d43d40bd66 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -87,6 +87,15 @@ namespace osu.Game.Online.Rooms set => SetField(ref status, value); } + /// + /// Describes which players are able to join the room. + /// + public RoomAvailability Availability + { + get => availability; + set => SetField(ref availability, value); + } + [JsonProperty("id")] private long? roomId; @@ -110,6 +119,9 @@ namespace osu.Game.Online.Rooms // Not serialised (see: GetRoomsRequest). private RoomStatus status = new RoomStatusOpen(); + // Not yet serialised (not implemented). + private RoomAvailability availability; + [Cached] [JsonProperty("playlist")] public readonly BindableList Playlist = new BindableList(); @@ -129,9 +141,6 @@ namespace osu.Game.Online.Rooms [Cached] public readonly Bindable MaxAttempts = new Bindable(); - [Cached] - public readonly Bindable Availability = new Bindable(); - [Cached] public readonly Bindable QueueMode = new Bindable(); @@ -241,7 +250,7 @@ namespace osu.Game.Online.Rooms ChannelId.Value = other.ChannelId.Value; Status = other.Status; - Availability.Value = other.Availability.Value; + Availability = other.Availability; HasPassword.Value = other.HasPassword.Value; Type = other.Type; MaxParticipants.Value = other.MaxParticipants.Value; diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 82774abac7..d444067da6 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -46,9 +46,6 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected Bindable EndDate { get; private set; } = null!; - [Resolved(typeof(Room))] - protected Bindable Availability { get; private set; } = null!; - [Resolved(typeof(Room))] public Bindable Password { get; private set; } = null!; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index 84a4657693..aee26e544a 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -316,7 +316,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists loadingLayer = new LoadingLayer(true) }; - Availability.BindValueChanged(availability => AvailabilityPicker.Current.Value = availability.NewValue, true); MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); MaxAttempts.BindValueChanged(count => MaxAttemptsField.Text = count.NewValue?.ToString(), true); Duration.BindValueChanged(duration => DurationField.Current.Value = duration.NewValue ?? TimeSpan.FromMinutes(30), true); @@ -346,17 +345,29 @@ namespace osu.Game.Screens.OnlinePlay.Playlists room.PropertyChanged += onRoomPropertyChanged; updateRoomName(); + updateRoomAvailability(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(Room.Name)) - updateRoomName(); + switch (e.PropertyName) + { + case nameof(Room.Name): + updateRoomName(); + break; + + case nameof(Room.Availability): + updateRoomAvailability(); + break; + } } private void updateRoomName() => NameField.Text = room.Name; + private void updateRoomAvailability() + => AvailabilityPicker.Current.Value = room.Availability; + private void populateDurations(ValueChangedEvent user) { // roughly correct (see https://github.com/Humanizr/Humanizer/blob/18167e56c082449cc4fe805b8429e3127a7b7f93/readme.md?plain=1#L427) @@ -405,7 +416,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists hideError(); room.Name = NameField.Text; - Availability.Value = AvailabilityPicker.Current.Value; + room.Availability = AvailabilityPicker.Current.Value; if (int.TryParse(MaxParticipantsField.Text, out int max)) MaxParticipants.Value = max; From 198681e64495bace774d65ebec02bddeba0ffc62 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 18:57:51 +0900 Subject: [PATCH 857/924] Make `Room.QueueMode` non-bindable --- .../Visual/Multiplayer/QueueModeTestScene.cs | 4 +-- .../Multiplayer/TestSceneDrawableRoom.cs | 6 ++-- .../Multiplayer/TestSceneHostOnlyQueueMode.cs | 2 +- .../Multiplayer/TestSceneMultiplayer.cs | 12 ++++---- .../TestSceneMultiplayerQueueList.cs | 8 +++--- .../Online/Multiplayer/MultiplayerClient.cs | 2 +- osu.Game/Online/Rooms/Room.cs | 27 ++++++++++-------- .../Lounge/Components/DrawableRoom.cs | 2 +- .../Lounge/Components/QueueModePill.cs | 28 +++++++++++++++---- .../Match/MultiplayerMatchSettingsOverlay.cs | 11 ++++++-- .../Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 4 --- .../Multiplayer/TestMultiplayerClient.cs | 2 +- 13 files changed, 67 insertions(+), 43 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 2d9d979022..7dce7daf6d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -74,7 +74,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("open room", () => multiplayerComponents.ChildrenOfType().Single().Open(new Room { Name = "Test Room", - QueueMode = { Value = Mode }, + QueueMode = Mode, Playlist = { new PlaylistItem(InitialBeatmap) @@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestCreatedWithCorrectMode() { - AddUntilStep("room created with correct mode", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == Mode); + AddUntilStep("room created with correct mode", () => MultiplayerClient.ClientAPIRoom?.QueueMode == Mode); } protected void RunGameplay() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index a40a75c885..79ac0fa7b6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -165,7 +165,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new DrawableMatchRoom(new Room { Name = "A host-only room", - QueueMode = { Value = QueueMode.HostOnly }, + QueueMode = QueueMode.HostOnly, Type = MatchType.HeadToHead, }) { @@ -174,7 +174,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new DrawableMatchRoom(new Room { Name = "An all-players, team-versus room", - QueueMode = { Value = QueueMode.AllPlayers }, + QueueMode = QueueMode.AllPlayers, Type = MatchType.TeamVersus }) { @@ -183,7 +183,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new DrawableMatchRoom(new Room { Name = "A round-robin room", - QueueMode = { Value = QueueMode.AllPlayersRoundRobin }, + QueueMode = QueueMode.AllPlayersRoundRobin, Type = MatchType.HeadToHead }) { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs index 78baa4a39b..e0364fd65d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Multiplayer QueueMode = QueueMode.AllPlayers }).WaitSafely()); - AddUntilStep("api room updated", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers); + AddUntilStep("api room updated", () => MultiplayerClient.ClientAPIRoom?.QueueMode == QueueMode.AllPlayers); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 0b662083b5..2ce6e5a577 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -792,7 +792,7 @@ namespace osu.Game.Tests.Visual.Multiplayer roomManager.AddServerSideRoom(new Room { Name = "Test Room", - QueueMode = { Value = QueueMode.AllPlayers }, + QueueMode = QueueMode.AllPlayers, Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -837,7 +837,7 @@ namespace osu.Game.Tests.Visual.Multiplayer createRoom(() => new Room { Name = "Test Room", - QueueMode = { Value = QueueMode.AllPlayers }, + QueueMode = QueueMode.AllPlayers, Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -873,7 +873,7 @@ namespace osu.Game.Tests.Visual.Multiplayer createRoom(() => new Room { Name = "Test Room", - QueueMode = { Value = QueueMode.AllPlayers }, + QueueMode = QueueMode.AllPlayers, Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -912,7 +912,7 @@ namespace osu.Game.Tests.Visual.Multiplayer createRoom(() => new Room { Name = "Test Room", - QueueMode = { Value = QueueMode.AllPlayers }, + QueueMode = QueueMode.AllPlayers, Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -943,7 +943,7 @@ namespace osu.Game.Tests.Visual.Multiplayer createRoom(() => new Room { Name = "Test Room", - QueueMode = { Value = QueueMode.AllPlayers }, + QueueMode = QueueMode.AllPlayers, Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -1023,7 +1023,7 @@ namespace osu.Game.Tests.Visual.Multiplayer createRoom(() => new Room { Name = "Test Room", - QueueMode = { Value = QueueMode.AllPlayers }, + QueueMode = QueueMode.AllPlayers, Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index 47fb4e06ea..10625d57aa 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestDeleteButtonAlwaysVisibleForHost() { AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); - AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers); + AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode == QueueMode.AllPlayers); addPlaylistItem(() => API.LocalUser.Value.OnlineID); assertDeleteButtonVisibility(1, true); @@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestDeleteButtonOnlyVisibleForItemOwnerIfNotHost() { AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); - AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers); + AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode == QueueMode.AllPlayers); AddStep("join other user", () => MultiplayerClient.AddUser(new APIUser { Id = 1234 })); AddStep("set other user as host", () => MultiplayerClient.TransferHost(1234)); @@ -100,7 +100,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestSingleItemDoesNotHaveDeleteButton() { AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); - AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers); + AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode == QueueMode.AllPlayers); assertDeleteButtonVisibility(0, false); } @@ -109,7 +109,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestCurrentItemHasDeleteButtonIfNotSingle() { AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); - AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers); + AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode == QueueMode.AllPlayers); addPlaylistItem(() => API.LocalUser.Value.OnlineID); diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index f26dbb3239..f36c53204d 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -845,7 +845,7 @@ namespace osu.Game.Online.Multiplayer APIRoom.Password.Value = Room.Settings.Password; APIRoom.Status = string.IsNullOrEmpty(Room.Settings.Password) ? new RoomStatusOpen() : new RoomStatusOpenPrivate(); APIRoom.Type = Room.Settings.MatchType; - APIRoom.QueueMode.Value = Room.Settings.QueueMode; + APIRoom.QueueMode = Room.Settings.QueueMode; APIRoom.AutoStartDuration.Value = Room.Settings.AutoStartDuration; APIRoom.CurrentPlaylistItem = APIRoom.Playlist.Single(item => item.ID == settings.PlaylistItemId); APIRoom.AutoSkip.Value = Room.Settings.AutoSkip; diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index d43d40bd66..b85b59e992 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -66,6 +66,15 @@ namespace osu.Game.Online.Rooms set => SetField(ref type, value); } + /// + /// The playlist queueing mode. Only valid for multiplayer rooms. + /// + public QueueMode QueueMode + { + get => queueMode; + set => SetField(ref queueMode, value); + } + /// /// Represents the current item selected within the room. /// @@ -96,6 +105,7 @@ namespace osu.Game.Online.Rooms set => SetField(ref availability, value); } + [JsonProperty("id")] private long? roomId; @@ -113,6 +123,10 @@ namespace osu.Game.Online.Rooms [JsonProperty("type")] private MatchType type; + [JsonConverter(typeof(SnakeCaseStringEnumConverter))] + [JsonProperty("queue_mode")] + private QueueMode queueMode; + [JsonProperty("current_playlist_item")] private PlaylistItem? currentPlaylistItem; @@ -141,17 +155,6 @@ namespace osu.Game.Online.Rooms [Cached] public readonly Bindable MaxAttempts = new Bindable(); - [Cached] - public readonly Bindable QueueMode = new Bindable(); - - [JsonConverter(typeof(SnakeCaseStringEnumConverter))] - [JsonProperty("queue_mode")] - private QueueMode queueMode - { - get => QueueMode.Value; - set => QueueMode.Value = value; - } - [Cached] public readonly Bindable AutoStartDuration = new Bindable(); @@ -257,7 +260,7 @@ namespace osu.Game.Online.Rooms ParticipantCount.Value = other.ParticipantCount.Value; EndDate.Value = other.EndDate.Value; UserScore.Value = other.UserScore.Value; - QueueMode.Value = other.QueueMode.Value; + QueueMode = other.QueueMode; AutoStartDuration.Value = other.AutoStartDuration.Value; DifficultyRange.Value = other.DifficultyRange.Value; PlaylistItemStats.Value = other.PlaylistItemStats.Value; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 8fb439452c..f5e72f4b99 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -341,7 +341,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components pills.AddRange(new OnlinePlayComposite[] { new MatchTypePill(Room), - new QueueModePill(), + new QueueModePill(Room), }); } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/QueueModePill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/QueueModePill.cs index 23f4ecf8db..c7d7876644 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/QueueModePill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/QueueModePill.cs @@ -1,24 +1,42 @@ // 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 System.ComponentModel; using osu.Framework.Extensions; -using osu.Game.Online.Multiplayer; +using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { public partial class QueueModePill : OnlinePlayPill { + private readonly Room room; + + public QueueModePill(Room room) + { + this.room = room; + } + protected override void LoadComplete() { base.LoadComplete(); - QueueMode.BindValueChanged(onQueueModeChanged, true); + room.PropertyChanged += onRoomPropertyChanged; + updateRoomQueueMode(); } - private void onQueueModeChanged(ValueChangedEvent mode) + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) { - TextFlow.Text = mode.NewValue.GetLocalisableDescription(); + if (e.PropertyName == nameof(Room.QueueMode)) + updateRoomQueueMode(); + } + + private void updateRoomQueueMode() + => TextFlow.Text = room.QueueMode.GetLocalisableDescription(); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 226484e9a5..36bdb97e9e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -352,7 +352,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match TypePicker.Current.BindValueChanged(type => typeLabel.Text = type.NewValue.GetLocalisableDescription(), true); MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); Password.BindValueChanged(password => PasswordTextBox.Text = password.NewValue ?? string.Empty, true); - QueueMode.BindValueChanged(mode => QueueModeDropdown.Current.Value = mode.NewValue, true); AutoStartDuration.BindValueChanged(duration => startModeDropdown.Current.Value = (StartMode)(int)duration.NewValue.TotalSeconds, true); AutoSkip.BindValueChanged(autoSkip => AutoSkipCheckbox.Current.Value = autoSkip.NewValue, true); @@ -377,6 +376,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match updateRoomName(); updateRoomType(); + updateRoomQueueMode(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) @@ -390,6 +390,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match case nameof(Room.Type): updateRoomName(); break; + + case nameof(Room.QueueMode): + updateRoomQueueMode(); + break; } } @@ -399,6 +403,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void updateRoomType() => TypePicker.Current.Value = room.Type; + private void updateRoomQueueMode() + => QueueModeDropdown.Current.Value = room.QueueMode; + protected override void Update() { base.Update(); @@ -443,7 +450,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match room.Name = NameField.Text; room.Type = TypePicker.Current.Value; room.Password.Value = PasswordTextBox.Current.Value; - room.QueueMode.Value = QueueModeDropdown.Current.Value; + room.QueueMode = QueueModeDropdown.Current.Value; room.AutoStartDuration.Value = autoStartDuration; room.AutoSkip.Value = AutoSkipCheckbox.Current.Value; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 60c2a586f9..8f2f3aa678 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -358,7 +358,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Activity.Value = new UserActivity.InLobby(Room); } - private bool localUserCanAddItem => client.IsHost || Room.QueueMode.Value != QueueMode.HostOnly; + private bool localUserCanAddItem => client.IsHost || Room.QueueMode != QueueMode.HostOnly; private void updateCurrentItem() { diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index d444067da6..22e9e72f19 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -6,7 +6,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay @@ -52,9 +51,6 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected Bindable Duration { get; private set; } = null!; - [Resolved(typeof(Room))] - protected Bindable QueueMode { get; private set; } = null!; - [Resolved(typeof(Room))] protected Bindable AutoStartDuration { get; private set; } = null!; diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 6111ba0a6d..7f110ae6a5 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -233,7 +233,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = ServerAPIRoom.Name, MatchType = ServerAPIRoom.Type, Password = password ?? string.Empty, - QueueMode = ServerAPIRoom.QueueMode.Value, + QueueMode = ServerAPIRoom.QueueMode, AutoStartDuration = ServerAPIRoom.AutoStartDuration.Value }, Playlist = ServerAPIRoom.Playlist.Select(CreateMultiplayerPlaylistItem).ToList(), From ec5be6dbc3719bc42ff6eeb9f6ffd54aa530b776 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 19:11:05 +0900 Subject: [PATCH 858/924] Make `Room.Password` & `Room.HasPassword` non-bindable --- .../TestSceneDrawableLoungeRoom.cs | 2 +- .../Multiplayer/TestSceneDrawableRoom.cs | 6 +-- .../TestSceneLoungeRoomsContainer.cs | 4 +- .../Multiplayer/TestSceneMultiplayer.cs | 10 ++-- .../Online/Multiplayer/MultiplayerClient.cs | 6 +-- osu.Game/Online/Rooms/GetRoomsRequest.cs | 2 +- osu.Game/Online/Rooms/Room.cs | 49 ++++++++++++++----- .../Lounge/Components/DrawableRoom.cs | 14 ++++-- .../Lounge/Components/RoomsContainer.cs | 4 +- .../OnlinePlay/Lounge/DrawableLoungeRoom.cs | 2 +- .../Match/MultiplayerMatchSettingsOverlay.cs | 11 ++++- .../Multiplayer/MultiplayerRoomManager.cs | 2 +- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 -- .../Multiplayer/TestMultiplayerClient.cs | 2 +- .../Visual/OnlinePlay/TestRoomManager.cs | 2 +- .../OnlinePlay/TestRoomRequestsHandler.cs | 9 ++-- 16 files changed, 80 insertions(+), 48 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs index 9fe7189a0a..c5fb52461a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { private readonly Room room = new Room { - HasPassword = { Value = true } + Password = "*" }; [Cached] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index 79ac0fa7b6..b5cfecfa25 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -86,7 +86,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Private room", Status = new RoomStatusOpenPrivate(), - HasPassword = { Value = true }, + Password = "*", EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, Type = MatchType.HeadToHead, Playlist = { item3 }, @@ -144,10 +144,10 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType().Single().Alpha)); - AddStep("set password", () => room.Password.Value = "password"); + AddStep("set password", () => room.Password = "password"); AddAssert("password icon visible", () => Precision.AlmostEquals(1, drawableRoom.ChildrenOfType().Single().Alpha)); - AddStep("unset password", () => room.Password.Value = string.Empty); + AddStep("unset password", () => room.Password = string.Empty); AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType().Single().Alpha)); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 8654fe1f11..e28790f2ed 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -182,11 +182,11 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("filter public rooms", () => container.Filter.Value = new FilterCriteria { Permissions = RoomPermissionsFilter.Public }); - AddUntilStep("private room hidden", () => container.Rooms.All(r => !r.Room.HasPassword.Value)); + AddUntilStep("private room hidden", () => container.Rooms.All(r => !r.Room.HasPassword)); AddStep("filter private rooms", () => container.Filter.Value = new FilterCriteria { Permissions = RoomPermissionsFilter.Private }); - AddUntilStep("public room hidden", () => container.Rooms.All(r => r.Room.HasPassword.Value)); + AddUntilStep("public room hidden", () => container.Rooms.All(r => r.Room.HasPassword)); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 2ce6e5a577..6e9c18f040 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -318,7 +318,7 @@ namespace osu.Game.Tests.Visual.Multiplayer createRoom(() => new Room { Name = "Test Room", - Password = { Value = "password" }, + Password = "password", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -328,7 +328,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddUntilStep("room has password", () => multiplayerClient.ClientAPIRoom?.Password.Value == "password"); + AddUntilStep("room has password", () => multiplayerClient.ClientAPIRoom?.Password == "password"); } [Test] @@ -339,7 +339,7 @@ namespace osu.Game.Tests.Visual.Multiplayer roomManager.AddServerSideRoom(new Room { Name = "Test Room", - Password = { Value = "password" }, + Password = "password", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -371,7 +371,7 @@ namespace osu.Game.Tests.Visual.Multiplayer createRoom(() => new Room { Name = "Test Room", - Password = { Value = "password" }, + Password = "password", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -382,7 +382,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }); AddStep("change password", () => multiplayerClient.ChangeSettings(password: "password2")); - AddUntilStep("local password changed", () => multiplayerClient.ClientAPIRoom?.Password.Value == "password2"); + AddUntilStep("local password changed", () => multiplayerClient.ClientAPIRoom?.Password == "password2"); } [Test] diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index f36c53204d..db35f228fe 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -184,7 +184,7 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(room.RoomID != null); // Join the server-side room. - var joinedRoom = await JoinRoom(room.RoomID.Value, password ?? room.Password.Value).ConfigureAwait(false); + var joinedRoom = await JoinRoom(room.RoomID.Value, password ?? room.Password).ConfigureAwait(false); Debug.Assert(joinedRoom != null); // Populate users. @@ -397,7 +397,7 @@ namespace osu.Game.Online.Multiplayer switch (state) { case MultiplayerRoomState.Open: - APIRoom.Status = APIRoom.HasPassword.Value ? new RoomStatusOpenPrivate() : new RoomStatusOpen(); + APIRoom.Status = APIRoom.HasPassword ? new RoomStatusOpenPrivate() : new RoomStatusOpen(); break; case MultiplayerRoomState.Playing: @@ -842,7 +842,7 @@ namespace osu.Game.Online.Multiplayer // Update a few properties of the room instantaneously. Room.Settings = settings; APIRoom.Name = Room.Settings.Name; - APIRoom.Password.Value = Room.Settings.Password; + APIRoom.Password = Room.Settings.Password; APIRoom.Status = string.IsNullOrEmpty(Room.Settings.Password) ? new RoomStatusOpen() : new RoomStatusOpenPrivate(); APIRoom.Type = Room.Settings.MatchType; APIRoom.QueueMode = Room.Settings.QueueMode; diff --git a/osu.Game/Online/Rooms/GetRoomsRequest.cs b/osu.Game/Online/Rooms/GetRoomsRequest.cs index 980ce74718..0c8df642fa 100644 --- a/osu.Game/Online/Rooms/GetRoomsRequest.cs +++ b/osu.Game/Online/Rooms/GetRoomsRequest.cs @@ -46,7 +46,7 @@ namespace osu.Game.Online.Rooms { if (room.EndDate.Value != null && DateTimeOffset.Now >= room.EndDate.Value) room.Status = new RoomStatusEnded(); - else if (room.HasPassword.Value) + else if (room.HasPassword) room.Status = new RoomStatusOpenPrivate(); else room.Status = new RoomStatusOpen(); diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index b85b59e992..3f50611372 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -39,6 +39,35 @@ namespace osu.Game.Online.Rooms set => SetField(ref name, value); } + /// + /// Sets the room password. Will be null after the room is created. + /// + /// + /// To check if the room has a password, use . + /// + public string? Password + { + get => password; + set + { + SetField(ref password, value); + HasPassword = !string.IsNullOrEmpty(value); + } + } + + /// + /// Whether the room has a password. + /// + /// + /// To set a password, use . + /// + [JsonProperty("has_password")] + public bool HasPassword + { + get => hasPassword; + private set => SetField(ref hasPassword, value); + } + /// /// The room host. Will be null while the room has not yet been created. /// @@ -112,6 +141,12 @@ namespace osu.Game.Online.Rooms [JsonProperty("name")] private string name = string.Empty; + [JsonProperty("password")] + private string? password; + + // Not serialised (internal use only). + private bool hasPassword; + [JsonProperty("host")] private APIUser? host; @@ -172,9 +207,6 @@ namespace osu.Game.Online.Rooms [JsonProperty("current_user_score")] public readonly Bindable UserScore = new Bindable(); - [JsonProperty("has_password")] - public readonly Bindable HasPassword = new Bindable(); - [Cached] [JsonProperty("recent_participants")] public readonly BindableList RecentParticipants = new BindableList(); @@ -185,10 +217,6 @@ namespace osu.Game.Online.Rooms #region Properties only used for room creation request - [Cached(Name = nameof(Password))] - [JsonProperty("password")] - public readonly Bindable Password = new Bindable(); - [Cached] public readonly Bindable Duration = new Bindable(); @@ -229,11 +257,6 @@ namespace osu.Game.Online.Rooms [JsonProperty("auto_skip")] public readonly Bindable AutoSkip = new Bindable(); - public Room() - { - Password.BindValueChanged(p => HasPassword.Value = !string.IsNullOrEmpty(p.NewValue)); - } - /// /// Copies values from another into this one. /// @@ -254,7 +277,7 @@ namespace osu.Game.Online.Rooms ChannelId.Value = other.ChannelId.Value; Status = other.Status; Availability = other.Availability; - HasPassword.Value = other.HasPassword.Value; + HasPassword = other.HasPassword; Type = other.Type; MaxParticipants.Value = other.MaxParticipants.Value; ParticipantCount.Value = other.ParticipantCount.Value; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index f5e72f4b99..390ea06c1b 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -259,12 +259,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components wrapper.FadeInFromZero(200); - hasPassword.BindTo(Room.HasPassword); - hasPassword.BindValueChanged(v => passwordIcon.Alpha = v.NewValue ? 1 : 0, true); - updateRoomName(); updateRoomCategory(); updateRoomType(); + updateRoomHasPassword(); }; SelectedItem.BindValueChanged(item => background.Beatmap.Value = item.NewValue?.Beatmap, true); @@ -285,6 +283,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components case nameof(Room.Type): updateRoomType(); break; + + case nameof(Room.HasPassword): + updateRoomHasPassword(); + break; } } @@ -308,6 +310,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components endDateInfo.Alpha = Room.Type == MatchType.Playlists ? 1 : 0; } + private void updateRoomHasPassword() + { + if (passwordIcon != null) + passwordIcon.Alpha = Room.HasPassword ? 1 : 0; + } + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { return new CachedModelDependencyContainer(base.CreateChildDependencies(parent)) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index f197a7d018..11ad886773 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -101,10 +101,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components return true; case RoomPermissionsFilter.Public: - return !room.Room.HasPassword.Value; + return !room.Room.HasPassword; case RoomPermissionsFilter.Private: - return room.Room.HasPassword.Value; + return room.Room.HasPassword; default: throw new ArgumentOutOfRangeException(nameof(accessType), accessType, $"Unsupported {nameof(RoomPermissionsFilter)} in filter"); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 1fc177ce0b..d396d18b4f 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -185,7 +185,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge return true; } - if (Room.HasPassword.Value) + if (Room.HasPassword) { this.ShowPopover(); return true; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 36bdb97e9e..a3441fbd09 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -351,7 +351,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match TypePicker.Current.BindValueChanged(type => typeLabel.Text = type.NewValue.GetLocalisableDescription(), true); MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); - Password.BindValueChanged(password => PasswordTextBox.Text = password.NewValue ?? string.Empty, true); AutoStartDuration.BindValueChanged(duration => startModeDropdown.Current.Value = (StartMode)(int)duration.NewValue.TotalSeconds, true); AutoSkip.BindValueChanged(autoSkip => AutoSkipCheckbox.Current.Value = autoSkip.NewValue, true); @@ -377,6 +376,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match updateRoomName(); updateRoomType(); updateRoomQueueMode(); + updateRoomPassword(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) @@ -394,6 +394,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match case nameof(Room.QueueMode): updateRoomQueueMode(); break; + + case nameof(Room.Password): + updateRoomPassword(); + break; } } @@ -406,6 +410,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void updateRoomQueueMode() => QueueModeDropdown.Current.Value = room.QueueMode; + private void updateRoomPassword() + => PasswordTextBox.Text = room.Password ?? string.Empty; + protected override void Update() { base.Update(); @@ -449,7 +456,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { room.Name = NameField.Text; room.Type = TypePicker.Current.Value; - room.Password.Value = PasswordTextBox.Current.Value; + room.Password = PasswordTextBox.Current.Value; room.QueueMode = QueueModeDropdown.Current.Value; room.AutoStartDuration.Value = autoStartDuration; room.AutoSkip.Value = AutoSkipCheckbox.Current.Value; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs index b119c6d59d..e16582a6e1 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private MultiplayerClient multiplayerClient { get; set; } = null!; public override void CreateRoom(Room room, Action? onSuccess = null, Action? onError = null) - => base.CreateRoom(room, r => joinMultiplayerRoom(r, r.Password.Value, onSuccess, onError), onError); + => base.CreateRoom(room, r => joinMultiplayerRoom(r, r.Password, onSuccess, onError), onError); public override void JoinRoom(Room room, string? password = null, Action? onSuccess = null, Action? onError = null) { diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 22e9e72f19..93abc23e55 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -45,9 +45,6 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected Bindable EndDate { get; private set; } = null!; - [Resolved(typeof(Room))] - public Bindable Password { get; private set; } = null!; - [Resolved(typeof(Room))] protected Bindable Duration { get; private set; } = null!; diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 7f110ae6a5..dcb6f2503d 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -216,7 +216,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ServerAPIRoom = roomManager.ServerSideRooms.Single(r => r.RoomID == roomId); - if (password != ServerAPIRoom.Password.Value) + if (password != ServerAPIRoom.Password) throw new InvalidOperationException("Invalid password."); lastPlaylistItemId = ServerAPIRoom.Playlist.Max(item => item.ID); diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs index 07dd57eb68..3db9e12ab7 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay }; if (withPassword) - room.Password.Value = @"password"; + room.Password = @"password"; if (ruleset != null) { diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index 271b036d8e..0934c4f3dc 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -51,8 +51,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay var apiRoom = cloneRoom(createRoomRequest.Room); // Passwords are explicitly not copied between rooms. - apiRoom.HasPassword.Value = !string.IsNullOrEmpty(createRoomRequest.Room.Password.Value); - apiRoom.Password.Value = createRoomRequest.Room.Password.Value; + apiRoom.Password = createRoomRequest.Room.Password; AddServerSideRoom(apiRoom, localUser); @@ -66,7 +65,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay { var room = ServerSideRooms.Single(r => r.RoomID == joinRoomRequest.Room.RoomID); - if (joinRoomRequest.Password != room.Password.Value) + if (joinRoomRequest.Password != room.Password) { joinRoomRequest.TriggerFailure(new InvalidOperationException("Invalid password.")); return true; @@ -278,9 +277,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay var responseRoom = cloneRoom(room); // Password is hidden from the response, and is only propagated via HasPassword. - bool hadPassword = responseRoom.HasPassword.Value; - responseRoom.Password.Value = null; - responseRoom.HasPassword.Value = hadPassword; + responseRoom.Password = responseRoom.HasPassword ? Guid.NewGuid().ToString() : null; if (!withParticipants) responseRoom.RecentParticipants.Clear(); From f001cce24adcea7dfce18d8a1ce41bd8f494f660 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 19:15:29 +0900 Subject: [PATCH 859/924] Make `Room.AutoSkip` non-bindable --- .../Online/Multiplayer/MultiplayerClient.cs | 2 +- osu.Game/Online/Rooms/Room.cs | 19 +++++++++++++------ .../Match/MultiplayerMatchSettingsOverlay.cs | 11 +++++++++-- .../Multiplayer/MultiplayerPlayer.cs | 4 ++-- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 --- 5 files changed, 25 insertions(+), 14 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index db35f228fe..a9c0108661 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -848,7 +848,7 @@ namespace osu.Game.Online.Multiplayer APIRoom.QueueMode = Room.Settings.QueueMode; APIRoom.AutoStartDuration.Value = Room.Settings.AutoStartDuration; APIRoom.CurrentPlaylistItem = APIRoom.Playlist.Single(item => item.ID == settings.PlaylistItemId); - APIRoom.AutoSkip.Value = Room.Settings.AutoSkip; + APIRoom.AutoSkip = Room.Settings.AutoSkip; RoomUpdated?.Invoke(); } diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 3f50611372..b945d1b886 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -104,6 +104,15 @@ namespace osu.Game.Online.Rooms set => SetField(ref queueMode, value); } + /// + /// Whether to automatically skip map intros. Only valid for multiplayer rooms. + /// + public bool AutoSkip + { + get => autoSkip; + set => SetField(ref autoSkip, value); + } + /// /// Represents the current item selected within the room. /// @@ -134,7 +143,6 @@ namespace osu.Game.Online.Rooms set => SetField(ref availability, value); } - [JsonProperty("id")] private long? roomId; @@ -162,6 +170,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("queue_mode")] private QueueMode queueMode; + [JsonProperty("auto_skip")] + private bool autoSkip; + [JsonProperty("current_playlist_item")] private PlaylistItem? currentPlaylistItem; @@ -253,10 +264,6 @@ namespace osu.Game.Online.Rooms set => MaxAttempts.Value = value; } - [Cached] - [JsonProperty("auto_skip")] - public readonly Bindable AutoSkip = new Bindable(); - /// /// Copies values from another into this one. /// @@ -288,7 +295,7 @@ namespace osu.Game.Online.Rooms DifficultyRange.Value = other.DifficultyRange.Value; PlaylistItemStats.Value = other.PlaylistItemStats.Value; CurrentPlaylistItem = other.CurrentPlaylistItem; - AutoSkip.Value = other.AutoSkip.Value; + AutoSkip = other.AutoSkip; other.RemoveExpiredPlaylistItems(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index a3441fbd09..62d6459487 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -352,7 +352,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match TypePicker.Current.BindValueChanged(type => typeLabel.Text = type.NewValue.GetLocalisableDescription(), true); MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); AutoStartDuration.BindValueChanged(duration => startModeDropdown.Current.Value = (StartMode)(int)duration.NewValue.TotalSeconds, true); - AutoSkip.BindValueChanged(autoSkip => AutoSkipCheckbox.Current.Value = autoSkip.NewValue, true); operationInProgress.BindTo(ongoingOperationTracker.InProgress); operationInProgress.BindValueChanged(v => @@ -377,6 +376,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match updateRoomType(); updateRoomQueueMode(); updateRoomPassword(); + updateRoomAutoSkip(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) @@ -398,6 +398,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match case nameof(Room.Password): updateRoomPassword(); break; + + case nameof(Room.AutoSkip): + updateRoomAutoSkip(); + break; } } @@ -413,6 +417,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void updateRoomPassword() => PasswordTextBox.Text = room.Password ?? string.Empty; + private void updateRoomAutoSkip() + => AutoSkipCheckbox.Current.Value = room.AutoSkip; + protected override void Update() { base.Update(); @@ -459,7 +466,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match room.Password = PasswordTextBox.Current.Value; room.QueueMode = QueueModeDropdown.Current.Value; room.AutoStartDuration.Value = autoStartDuration; - room.AutoSkip.Value = AutoSkipCheckbox.Current.Value; + room.AutoSkip = AutoSkipCheckbox.Current.Value; if (int.TryParse(MaxParticipantsField.Text, out int max)) room.MaxParticipants.Value = max; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index d444af2fd3..c058699b0e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -50,8 +50,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer AllowPause = false, AllowRestart = false, AllowFailAnimation = false, - AllowSkipping = room.AutoSkip.Value, - AutomaticallySkipIntro = room.AutoSkip.Value, + AllowSkipping = room.AutoSkip, + AutomaticallySkipIntro = room.AutoSkip, AlwaysShowLeaderboard = true, }) { diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 93abc23e55..2b1c9cf00a 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -50,8 +50,5 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected Bindable AutoStartDuration { get; private set; } = null!; - - [Resolved(typeof(Room))] - protected Bindable AutoSkip { get; private set; } = null!; } } From b8bae30b66b6f171191314b13d0810e6529373f9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 19:45:03 +0900 Subject: [PATCH 860/924] Make `Room.ParticipantCount` & `Room.MaxParticipants` non-bindable --- .../TestSceneDrawableRoomParticipantsList.cs | 4 +- .../Multiplayer/TestSceneMultiplayer.cs | 4 +- .../TestScenePlaylistsParticipantsList.cs | 4 +- .../Online/Multiplayer/MultiplayerClient.cs | 4 +- osu.Game/Online/Rooms/Room.cs | 35 +++++++++--- .../Components/ParticipantCountDisplay.cs | 55 +++++++++++++++---- .../Components/ParticipantsDisplay.cs | 40 +++++++++++--- .../DrawableRoomParticipantsList.cs | 27 ++++++--- .../Match/MultiplayerMatchSettingsOverlay.cs | 13 ++++- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 6 -- .../Playlists/PlaylistsRoomSettingsOverlay.cs | 15 +++-- 11 files changed, 146 insertions(+), 61 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs index 1e7003c612..3d0c1be22c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs @@ -143,13 +143,13 @@ namespace osu.Game.Tests.Visual.Multiplayer Id = id, Username = $"User {id}" }); - SelectedRoom.Value.ParticipantCount.Value++; + SelectedRoom.Value.ParticipantCount++; } private void removeUserAt(int index) { SelectedRoom.Value.RecentParticipants.RemoveAt(index); - SelectedRoom.Value.ParticipantCount.Value--; + SelectedRoom.Value.ParticipantCount--; } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 6e9c18f040..e625d23779 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -248,7 +248,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddUntilStep("Check participant count correct", () => multiplayerClient.ClientAPIRoom?.ParticipantCount.Value == 1); + AddUntilStep("Check participant count correct", () => multiplayerClient.ClientAPIRoom?.ParticipantCount == 1); AddUntilStep("Check participant list contains user", () => multiplayerClient.ClientAPIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1); } @@ -308,7 +308,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); AddUntilStep("wait for join", () => multiplayerClient.RoomJoined); - AddUntilStep("Check participant count correct", () => multiplayerClient.ClientAPIRoom?.ParticipantCount.Value == 1); + AddUntilStep("Check participant count correct", () => multiplayerClient.ClientAPIRoom?.ParticipantCount == 1); AddUntilStep("Check participant list contains user", () => multiplayerClient.ClientAPIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1); } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs index b9e5875e06..2368a2068d 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("create component", () => { - Child = new ParticipantsDisplay(Direction.Horizontal) + Child = new ParticipantsDisplay(SelectedRoom.Value, Direction.Horizontal) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("create component", () => { - Child = new ParticipantsDisplay(Direction.Vertical) + Child = new ParticipantsDisplay(SelectedRoom.Value, Direction.Vertical) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index a9c0108661..3a9f728b41 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -492,7 +492,7 @@ namespace osu.Game.Online.Multiplayer Id = user.UserID, Username = "[Unresolved]" }); - APIRoom.ParticipantCount.Value++; + APIRoom.ParticipantCount++; } private Task handleUserLeft(MultiplayerRoomUser user, Action? callback) @@ -507,7 +507,7 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(APIRoom != null); APIRoom.RecentParticipants.RemoveAll(u => u.Id == user.UserID); - APIRoom.ParticipantCount.Value--; + APIRoom.ParticipantCount--; callback?.Invoke(user); RoomUpdated?.Invoke(); diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index b945d1b886..b63ebd107d 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -86,6 +86,24 @@ namespace osu.Game.Online.Rooms set => SetField(ref category, value); } + /// + /// The maximum number of users allowed in the room. + /// + public int? MaxParticipants + { + get => maxParticipants; + set => SetField(ref maxParticipants, value); + } + + /// + /// The current number of users in the room. + /// + public int ParticipantCount + { + get => participantCount; + set => SetField(ref participantCount, value); + } + /// /// The match type. /// @@ -162,6 +180,12 @@ namespace osu.Game.Online.Rooms [JsonConverter(typeof(SnakeCaseStringEnumConverter))] private RoomCategory category; + // Not yet serialised (not implemented). + private int? maxParticipants; + + [JsonProperty("participant_count")] + private int participantCount; + [JsonConverter(typeof(SnakeCaseStringEnumConverter))] [JsonProperty("type")] private MatchType type; @@ -211,9 +235,6 @@ namespace osu.Game.Online.Rooms set => AutoStartDuration.Value = TimeSpan.FromSeconds(value); } - [Cached] - public readonly Bindable MaxParticipants = new Bindable(); - [Cached] [JsonProperty("current_user_score")] public readonly Bindable UserScore = new Bindable(); @@ -222,10 +243,6 @@ namespace osu.Game.Online.Rooms [JsonProperty("recent_participants")] public readonly BindableList RecentParticipants = new BindableList(); - [Cached] - [JsonProperty("participant_count")] - public readonly Bindable ParticipantCount = new Bindable(); - #region Properties only used for room creation request [Cached] @@ -286,8 +303,8 @@ namespace osu.Game.Online.Rooms Availability = other.Availability; HasPassword = other.HasPassword; Type = other.Type; - MaxParticipants.Value = other.MaxParticipants.Value; - ParticipantCount.Value = other.ParticipantCount.Value; + MaxParticipants = other.MaxParticipants; + ParticipantCount = other.ParticipantCount; EndDate.Value = other.EndDate.Value; UserScore.Value = other.UserScore.Value; QueueMode = other.QueueMode; diff --git a/osu.Game/Screens/OnlinePlay/Components/ParticipantCountDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/ParticipantCountDisplay.cs index 9f7e700ab3..f9744717d5 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ParticipantCountDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ParticipantCountDisplay.cs @@ -1,13 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - +using System.ComponentModel; 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.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Components { @@ -16,18 +16,21 @@ namespace osu.Game.Screens.OnlinePlay.Components private const float text_size = 30; private const float transition_duration = 100; - private OsuSpriteText slash, maxText; + private readonly Room room; - public ParticipantCountDisplay() + private OsuSpriteText slash = null!; + private OsuSpriteText maxText = null!; + private OsuSpriteText count = null!; + + public ParticipantCountDisplay(Room room) { + this.room = room; AutoSizeAxes = Axes.Both; } [BackgroundDependencyLoader] private void load() { - OsuSpriteText count; - InternalChild = new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -50,14 +53,33 @@ namespace osu.Game.Screens.OnlinePlay.Components }, } }; - - MaxParticipants.BindValueChanged(_ => updateMax(), true); - ParticipantCount.BindValueChanged(c => count.Text = c.NewValue.ToString("#,0"), true); } - private void updateMax() + protected override void LoadComplete() { - if (MaxParticipants.Value == null) + base.LoadComplete(); + + room.PropertyChanged += onRoomPropertyChanged; + updateRoomParticipantCount(); + } + + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + switch (e.PropertyName) + { + case nameof(Room.MaxParticipants): + updateRoomMaxParticipants(); + break; + + case nameof(Room.ParticipantCount): + updateRoomParticipantCount(); + break; + } + } + + private void updateRoomMaxParticipants() + { + if (room.MaxParticipants == null) { slash.FadeOut(transition_duration); maxText.FadeOut(transition_duration); @@ -65,9 +87,18 @@ namespace osu.Game.Screens.OnlinePlay.Components else { slash.FadeIn(transition_duration); - maxText.Text = MaxParticipants.Value.ToString(); + maxText.Text = room.MaxParticipants.ToString()!; maxText.FadeIn(transition_duration); } } + + private void updateRoomParticipantCount() + => count.Text = room.ParticipantCount.ToString("#,0"); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs index 5128bc4c14..1ae2756514 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs @@ -1,19 +1,23 @@ // 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 System.ComponentModel; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Components { public partial class ParticipantsDisplay : OnlinePlayComposite { - public Bindable Details = new Bindable(); + public readonly Bindable Details = new Bindable(); - public ParticipantsDisplay(Direction direction) + private readonly Room room; + + public ParticipantsDisplay(Room room, Direction direction) { + this.room = room; OsuScrollContainer scroll; ParticipantsList list; @@ -46,14 +50,32 @@ namespace osu.Game.Screens.OnlinePlay.Components } } - [BackgroundDependencyLoader] - private void load() + protected override void LoadComplete() { - ParticipantCount.BindValueChanged(_ => setParticipantCount()); - MaxParticipants.BindValueChanged(_ => setParticipantCount(), true); + base.LoadComplete(); + + room.PropertyChanged += onRoomPropertyChanged; + updateRoomParticipantCount(); } - private void setParticipantCount() => - Details.Value = MaxParticipants.Value != null ? $"{ParticipantCount.Value}/{MaxParticipants.Value}" : ParticipantCount.Value.ToString(); + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + switch (e.PropertyName) + { + case nameof(Room.MaxParticipants): + case nameof(Room.ParticipantCount): + updateRoomParticipantCount(); + break; + } + } + + private void updateRoomParticipantCount() + => Details.Value = room.MaxParticipants != null ? $"{room.ParticipantCount}/{room.MaxParticipants}" : room.ParticipantCount.ToString(); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs index 2ffe740e24..06ae939842 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs @@ -166,14 +166,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components base.LoadComplete(); RecentParticipants.BindCollectionChanged(onParticipantsChanged, true); - ParticipantCount.BindValueChanged(_ => - { - updateHiddenUsers(); - totalCount.Text = ParticipantCount.Value.ToString(); - }, true); room.PropertyChanged += onRoomPropertyChanged; + updateRoomHost(); + updateRoomParticipantCount(); } private int numberOfCircles = 4; @@ -257,7 +254,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { int hiddenCount = 0; if (RecentParticipants.Count > NumberOfCircles) - hiddenCount = ParticipantCount.Value - NumberOfCircles + 1; + hiddenCount = room.ParticipantCount - NumberOfCircles + 1; hiddenUsers.Count = hiddenCount; @@ -272,8 +269,16 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(Room.Host)) - updateRoomHost(); + switch (e.PropertyName) + { + case nameof(Room.Host): + updateRoomHost(); + break; + + case nameof(Room.ParticipantCount): + updateRoomParticipantCount(); + break; + } } private void updateRoomHost() @@ -288,6 +293,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components } } + private void updateRoomParticipantCount() + { + updateHiddenUsers(); + totalCount.Text = room.ParticipantCount.ToString(); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 62d6459487..141bddc4d4 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -350,7 +350,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match }; TypePicker.Current.BindValueChanged(type => typeLabel.Text = type.NewValue.GetLocalisableDescription(), true); - MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); AutoStartDuration.BindValueChanged(duration => startModeDropdown.Current.Value = (StartMode)(int)duration.NewValue.TotalSeconds, true); operationInProgress.BindTo(ongoingOperationTracker.InProgress); @@ -377,6 +376,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match updateRoomQueueMode(); updateRoomPassword(); updateRoomAutoSkip(); + updateRoomMaxParticipants(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) @@ -402,6 +402,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match case nameof(Room.AutoSkip): updateRoomAutoSkip(); break; + + case nameof(Room.MaxParticipants): + updateRoomMaxParticipants(); + break; } } @@ -420,6 +424,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void updateRoomAutoSkip() => AutoSkipCheckbox.Current.Value = room.AutoSkip; + private void updateRoomMaxParticipants() + => MaxParticipantsField.Text = room.MaxParticipants?.ToString(); + protected override void Update() { base.Update(); @@ -469,9 +476,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match room.AutoSkip = AutoSkipCheckbox.Current.Value; if (int.TryParse(MaxParticipantsField.Text, out int max)) - room.MaxParticipants.Value = max; + room.MaxParticipants = max; else - room.MaxParticipants.Value = null; + room.MaxParticipants = null; manager.CreateRoom(room, onSuccess, onError); } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 2b1c9cf00a..7623cbabbd 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -27,12 +27,6 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected BindableList RecentParticipants { get; private set; } = null!; - [Resolved(typeof(Room))] - protected Bindable ParticipantCount { get; private set; } = null!; - - [Resolved(typeof(Room))] - protected Bindable MaxParticipants { get; private set; } = null!; - [Resolved(typeof(Room))] protected Bindable MaxAttempts { get; private set; } = null!; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index aee26e544a..20fbe42dd5 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -316,7 +316,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists loadingLayer = new LoadingLayer(true) }; - MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); MaxAttempts.BindValueChanged(count => MaxAttemptsField.Text = count.NewValue?.ToString(), true); Duration.BindValueChanged(duration => DurationField.Current.Value = duration.NewValue ?? TimeSpan.FromMinutes(30), true); @@ -346,6 +345,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists updateRoomName(); updateRoomAvailability(); + updateRoomMaxParticipants(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) @@ -359,6 +359,10 @@ namespace osu.Game.Screens.OnlinePlay.Playlists case nameof(Room.Availability): updateRoomAvailability(); break; + + case nameof(Room.MaxParticipants): + updateRoomMaxParticipants(); + break; } } @@ -368,6 +372,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private void updateRoomAvailability() => AvailabilityPicker.Current.Value = room.Availability; + private void updateRoomMaxParticipants() + => MaxParticipantsField.Text = room.MaxParticipants?.ToString(); + private void populateDurations(ValueChangedEvent user) { // roughly correct (see https://github.com/Humanizr/Humanizer/blob/18167e56c082449cc4fe805b8429e3127a7b7f93/readme.md?plain=1#L427) @@ -417,11 +424,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists room.Name = NameField.Text; room.Availability = AvailabilityPicker.Current.Value; - - if (int.TryParse(MaxParticipantsField.Text, out int max)) - MaxParticipants.Value = max; - else - MaxParticipants.Value = null; + room.MaxParticipants = int.TryParse(MaxParticipantsField.Text, out int max) ? max : null; if (int.TryParse(MaxAttemptsField.Text, out max)) MaxAttempts.Value = max; From 89de4f0f87a81feae6714b8bea2b85fd36bf7a35 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 19:53:21 +0900 Subject: [PATCH 861/924] Make `Room.AutoStartDuration` non-bindable --- .../Online/Multiplayer/MultiplayerClient.cs | 2 +- osu.Game/Online/Rooms/Room.cs | 24 ++++++++++--------- .../Match/MultiplayerMatchSettingsOverlay.cs | 15 ++++++++---- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 --- .../Multiplayer/TestMultiplayerClient.cs | 2 +- 5 files changed, 25 insertions(+), 21 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 3a9f728b41..6a7d460922 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -846,7 +846,7 @@ namespace osu.Game.Online.Multiplayer APIRoom.Status = string.IsNullOrEmpty(Room.Settings.Password) ? new RoomStatusOpen() : new RoomStatusOpenPrivate(); APIRoom.Type = Room.Settings.MatchType; APIRoom.QueueMode = Room.Settings.QueueMode; - APIRoom.AutoStartDuration.Value = Room.Settings.AutoStartDuration; + APIRoom.AutoStartDuration = Room.Settings.AutoStartDuration; APIRoom.CurrentPlaylistItem = APIRoom.Playlist.Single(item => item.ID == settings.PlaylistItemId); APIRoom.AutoSkip = Room.Settings.AutoSkip; diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index b63ebd107d..61f944a728 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -131,6 +131,15 @@ namespace osu.Game.Online.Rooms set => SetField(ref autoSkip, value); } + /// + /// The amount of time before the match is automatically started. Only valid for multiplayer rooms. + /// + public TimeSpan AutoStartDuration + { + get => TimeSpan.FromSeconds(autoStartDuration); + set => SetField(ref autoStartDuration, (ushort)value.TotalSeconds); + } + /// /// Represents the current item selected within the room. /// @@ -197,6 +206,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("auto_skip")] private bool autoSkip; + [JsonProperty("auto_start_duration")] + private ushort autoStartDuration; + [JsonProperty("current_playlist_item")] private PlaylistItem? currentPlaylistItem; @@ -225,16 +237,6 @@ namespace osu.Game.Online.Rooms [Cached] public readonly Bindable MaxAttempts = new Bindable(); - [Cached] - public readonly Bindable AutoStartDuration = new Bindable(); - - [JsonProperty("auto_start_duration")] - private ushort autoStartDuration - { - get => (ushort)AutoStartDuration.Value.TotalSeconds; - set => AutoStartDuration.Value = TimeSpan.FromSeconds(value); - } - [Cached] [JsonProperty("current_user_score")] public readonly Bindable UserScore = new Bindable(); @@ -308,7 +310,7 @@ namespace osu.Game.Online.Rooms EndDate.Value = other.EndDate.Value; UserScore.Value = other.UserScore.Value; QueueMode = other.QueueMode; - AutoStartDuration.Value = other.AutoStartDuration.Value; + AutoStartDuration = other.AutoStartDuration; DifficultyRange.Value = other.DifficultyRange.Value; PlaylistItemStats.Value = other.PlaylistItemStats.Value; CurrentPlaylistItem = other.CurrentPlaylistItem; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 141bddc4d4..f883fde600 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -350,7 +350,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match }; TypePicker.Current.BindValueChanged(type => typeLabel.Text = type.NewValue.GetLocalisableDescription(), true); - AutoStartDuration.BindValueChanged(duration => startModeDropdown.Current.Value = (StartMode)(int)duration.NewValue.TotalSeconds, true); operationInProgress.BindTo(ongoingOperationTracker.InProgress); operationInProgress.BindValueChanged(v => @@ -377,6 +376,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match updateRoomPassword(); updateRoomAutoSkip(); updateRoomMaxParticipants(); + updateRoomAutoStartDuration(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) @@ -406,6 +406,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match case nameof(Room.MaxParticipants): updateRoomMaxParticipants(); break; + + case nameof(Room.AutoStartDuration): + updateRoomAutoStartDuration(); + break; } } @@ -427,6 +431,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void updateRoomMaxParticipants() => MaxParticipantsField.Text = room.MaxParticipants?.ToString(); + private void updateRoomAutoStartDuration() + => typeLabel.Text = room.AutoStartDuration.GetLocalisableDescription(); + protected override void Update() { base.Update(); @@ -445,8 +452,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match Debug.Assert(applyingSettingsOperation == null); applyingSettingsOperation = ongoingOperationTracker.BeginOperation(); - TimeSpan autoStartDuration = TimeSpan.FromSeconds((int)startModeDropdown.Current.Value); - // If the client is already in a room, update via the client. // Otherwise, update the room directly in preparation for it to be submitted to the API on match creation. if (client.Room != null) @@ -456,7 +461,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match password: PasswordTextBox.Text, matchType: TypePicker.Current.Value, queueMode: QueueModeDropdown.Current.Value, - autoStartDuration: autoStartDuration, + autoStartDuration: TimeSpan.FromSeconds((int)startModeDropdown.Current.Value), autoSkip: AutoSkipCheckbox.Current.Value) .ContinueWith(t => Schedule(() => { @@ -472,7 +477,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match room.Type = TypePicker.Current.Value; room.Password = PasswordTextBox.Current.Value; room.QueueMode = QueueModeDropdown.Current.Value; - room.AutoStartDuration.Value = autoStartDuration; + room.AutoStartDuration = TimeSpan.FromSeconds((int)startModeDropdown.Current.Value); room.AutoSkip = AutoSkipCheckbox.Current.Value; if (int.TryParse(MaxParticipantsField.Text, out int max)) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 7623cbabbd..70418d1a7e 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -41,8 +41,5 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected Bindable Duration { get; private set; } = null!; - - [Resolved(typeof(Room))] - protected Bindable AutoStartDuration { get; private set; } = null!; } } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index dcb6f2503d..29e62cb590 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -234,7 +234,7 @@ namespace osu.Game.Tests.Visual.Multiplayer MatchType = ServerAPIRoom.Type, Password = password ?? string.Empty, QueueMode = ServerAPIRoom.QueueMode, - AutoStartDuration = ServerAPIRoom.AutoStartDuration.Value + AutoStartDuration = ServerAPIRoom.AutoStartDuration }, Playlist = ServerAPIRoom.Playlist.Select(CreateMultiplayerPlaylistItem).ToList(), Users = { localUser }, From 0ceaafe7318ac0b73c12dc746623b63e02de8e7e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 20:20:14 +0900 Subject: [PATCH 862/924] Make `Room.Duration` & `Room.StartDate` & `Room.EndDate` non-bindable --- .../DailyChallenge/TestSceneDailyChallenge.cs | 6 +- .../TestSceneDailyChallengeCarousel.cs | 6 +- .../TestSceneDailyChallengeIntro.cs | 4 +- ...estSceneDailyChallengeTimeRemainingRing.cs | 22 +++--- .../Visual/Menus/TestSceneMainMenu.cs | 4 +- .../Multiplayer/TestSceneDrawableRoom.cs | 8 +- .../TestScenePlaylistsMatchSettingsOverlay.cs | 2 +- .../TestScenePlaylistsRoomCreation.cs | 4 +- .../UserInterface/TestSceneMainMenuButton.cs | 8 +- .../Online/Multiplayer/MultiplayerClient.cs | 2 +- osu.Game/Online/Rooms/GetRoomsRequest.cs | 2 +- osu.Game/Online/Rooms/Room.cs | 74 +++++++++++-------- osu.Game/Screens/Menu/DailyChallengeButton.cs | 6 +- .../DailyChallenge/DailyChallenge.cs | 4 +- .../DailyChallengeTimeRemainingRing.cs | 40 ++++++++-- .../Lounge/Components/DrawableRoom.cs | 2 +- .../Lounge/Components/EndDateInfo.cs | 51 +++++++++---- .../Lounge/Components/RoomStatusPill.cs | 10 ++- .../OnlinePlay/Lounge/LoungeSubScreen.cs | 4 +- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 10 --- .../Playlists/PlaylistsReadyButton.cs | 18 ++--- .../Playlists/PlaylistsRoomFooter.cs | 9 +-- .../Playlists/PlaylistsRoomSettingsOverlay.cs | 11 ++- .../Playlists/PlaylistsRoomSubScreen.cs | 2 +- .../Visual/OnlinePlay/TestRoomManager.cs | 2 +- 25 files changed, 186 insertions(+), 125 deletions(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs index e05f5f13ae..1a19c423fc 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge AllowedMods = [new APIMod(new OsuModDoubleTime())] } }, - EndDate = { Value = DateTimeOffset.Now.AddHours(12) }, + EndDate = DateTimeOffset.Now.AddHours(12), Category = RoomCategory.DailyChallenge }; @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge AllowedMods = [new APIMod(new OsuModDoubleTime())] } }, - EndDate = { Value = DateTimeOffset.Now.AddHours(12) }, + EndDate = DateTimeOffset.Now.AddHours(12), Category = RoomCategory.DailyChallenge }; @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge AllowedMods = [new APIMod(new OsuModDoubleTime())] } }, - EndDate = { Value = DateTimeOffset.Now.AddHours(12) }, + EndDate = DateTimeOffset.Now.AddHours(12), Category = RoomCategory.DailyChallenge }; diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeCarousel.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeCarousel.cs index d53e386ad4..5854c2e793 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeCarousel.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeCarousel.cs @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge Origin = Anchor.Centre, Children = new Drawable[] { - new DailyChallengeTimeRemainingRing(), + new DailyChallengeTimeRemainingRing(room.Value), breakdown = new DailyChallengeScoreBreakdown(), } } @@ -125,8 +125,8 @@ namespace osu.Game.Tests.Visual.DailyChallenge AddSliderStep("update time remaining", 0f, 1f, 0f, progress => { var startedTimeAgo = TimeSpan.FromHours(24) * progress; - room.Value.StartDate.Value = DateTimeOffset.Now - startedTimeAgo; - room.Value.EndDate.Value = room.Value.StartDate.Value.Value.AddDays(1); + room.Value.StartDate = DateTimeOffset.Now - startedTimeAgo; + room.Value.EndDate = room.Value.StartDate.Value.AddDays(1); }); AddStep("add normal score", () => { diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs index 33be952e20..aa311766d5 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs @@ -78,8 +78,8 @@ namespace osu.Game.Tests.Visual.DailyChallenge AllowedMods = [new APIMod(new OsuModDoubleTime())] } }, - StartDate = { Value = DateTimeOffset.Now }, - EndDate = { Value = DateTimeOffset.Now.AddHours(24) }, + StartDate = DateTimeOffset.Now, + EndDate = DateTimeOffset.Now.AddHours(24), Category = RoomCategory.DailyChallenge })); }); diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTimeRemainingRing.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTimeRemainingRing.cs index baa1eb8318..2f522d4d6f 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTimeRemainingRing.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTimeRemainingRing.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background4, }, - ring = new DailyChallengeTimeRemainingRing + ring = new DailyChallengeTimeRemainingRing(room.Value) { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, @@ -59,29 +59,29 @@ namespace osu.Game.Tests.Visual.DailyChallenge AddStep("just started", () => { - room.Value.StartDate.Value = DateTimeOffset.Now.AddMinutes(-1); - room.Value.EndDate.Value = room.Value.StartDate.Value.Value.AddDays(1); + room.Value.StartDate = DateTimeOffset.Now.AddMinutes(-1); + room.Value.EndDate = room.Value.StartDate.Value.AddDays(1); }); AddStep("midway through", () => { - room.Value.StartDate.Value = DateTimeOffset.Now.AddHours(-12); - room.Value.EndDate.Value = room.Value.StartDate.Value.Value.AddDays(1); + room.Value.StartDate = DateTimeOffset.Now.AddHours(-12); + room.Value.EndDate = room.Value.StartDate.Value.AddDays(1); }); AddStep("nearing end", () => { - room.Value.StartDate.Value = DateTimeOffset.Now.AddDays(-1).AddMinutes(8); - room.Value.EndDate.Value = room.Value.StartDate.Value.Value.AddDays(1); + room.Value.StartDate = DateTimeOffset.Now.AddDays(-1).AddMinutes(8); + room.Value.EndDate = room.Value.StartDate.Value.AddDays(1); }); AddStep("already ended", () => { - room.Value.StartDate.Value = DateTimeOffset.Now.AddDays(-2); - room.Value.EndDate.Value = room.Value.StartDate.Value.Value.AddDays(1); + room.Value.StartDate = DateTimeOffset.Now.AddDays(-2); + room.Value.EndDate = room.Value.StartDate.Value.AddDays(1); }); AddSliderStep("manual progress", 0f, 1f, 0f, progress => { var startedTimeAgo = TimeSpan.FromHours(24) * progress; - room.Value.StartDate.Value = DateTimeOffset.Now - startedTimeAgo; - room.Value.EndDate.Value = room.Value.StartDate.Value.Value.AddDays(1); + room.Value.StartDate = DateTimeOffset.Now - startedTimeAgo; + room.Value.EndDate = room.Value.StartDate.Value.AddDays(1); }); } } diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs index 9b00449e11..bc6c3ed709 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs @@ -48,8 +48,8 @@ namespace osu.Game.Tests.Visual.Menus { new PlaylistItem(beatmap) }, - StartDate = { Value = DateTimeOffset.Now.AddMinutes(-30) }, - EndDate = { Value = DateTimeOffset.Now.AddSeconds(60) } + StartDate = DateTimeOffset.Now.AddMinutes(-30), + EndDate = DateTimeOffset.Now.AddSeconds(60) }); return true; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index b5cfecfa25..31923b676a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Multiplayer room", Status = new RoomStatusOpen(), - EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, + EndDate = DateTimeOffset.Now.AddDays(1), Type = MatchType.HeadToHead, Playlist = { item1 }, CurrentPlaylistItem = item1 @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Private room", Status = new RoomStatusOpenPrivate(), Password = "*", - EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, + EndDate = DateTimeOffset.Now.AddDays(1), Type = MatchType.HeadToHead, Playlist = { item3 }, CurrentPlaylistItem = item3 @@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Playlist room with multiple beatmaps", Status = new RoomStatusPlaying(), - EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, + EndDate = DateTimeOffset.Now.AddDays(1), Playlist = { item1, item2 }, CurrentPlaylistItem = item1 }), @@ -104,7 +104,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Finished room", Status = new RoomStatusEnded(), - EndDate = { Value = DateTimeOffset.Now }, + EndDate = DateTimeOffset.Now, }), createLoungeRoom(new Room { diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs index 3652710e32..b45d1e68cb 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("create room", () => settings.ApplyButton.Action.Invoke()); AddAssert("has correct name", () => createdRoom.Name == expected_name); - AddAssert("has correct duration", () => createdRoom.Duration.Value == expectedDuration); + AddAssert("has correct duration", () => createdRoom.Duration == expectedDuration); } [Test] diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index 7cc86e71bf..d0cbee9e50 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Playlists room.Name = "my awesome room"; room.Host = API.LocalUser.Value; room.RecentParticipants.Add(room.Host); - room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5); + room.EndDate = DateTimeOffset.Now.AddMinutes(5); room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First()) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.Playlists room.MaxAttempts.Value = 5; room.Host = API.LocalUser.Value; room.RecentParticipants.Add(room.Host); - room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5); + room.EndDate = DateTimeOffset.Now.AddMinutes(5); room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First()) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs index 3894b6ea5b..8b17749afc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs @@ -58,8 +58,8 @@ namespace osu.Game.Tests.Visual.UserInterface { new PlaylistItem(beatmap) }, - StartDate = { Value = DateTimeOffset.Now.AddMinutes(-5) }, - EndDate = { Value = DateTimeOffset.Now.AddSeconds(30) } + StartDate = DateTimeOffset.Now.AddMinutes(-5), + EndDate = DateTimeOffset.Now.AddSeconds(30) }); return true; @@ -136,8 +136,8 @@ namespace osu.Game.Tests.Visual.UserInterface { new PlaylistItem(beatmap) }, - StartDate = { Value = DateTimeOffset.Now.AddMinutes(-50) }, - EndDate = { Value = DateTimeOffset.Now.AddSeconds(30) } + StartDate = DateTimeOffset.Now.AddMinutes(-50), + EndDate = DateTimeOffset.Now.AddSeconds(30) }); return true; diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 6a7d460922..fe0b4c6a21 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -206,7 +206,7 @@ namespace osu.Game.Online.Multiplayer APIRoom.CurrentPlaylistItem = APIRoom.Playlist.Single(item => item.ID == joinedRoom.Settings.PlaylistItemId); // The server will null out the end date upon the host joining the room, but the null value is never communicated to the client. - APIRoom.EndDate.Value = null; + APIRoom.EndDate = null; Debug.Assert(LocalUser != null); addUserToAPIRoom(LocalUser); diff --git a/osu.Game/Online/Rooms/GetRoomsRequest.cs b/osu.Game/Online/Rooms/GetRoomsRequest.cs index 0c8df642fa..cd797a9668 100644 --- a/osu.Game/Online/Rooms/GetRoomsRequest.cs +++ b/osu.Game/Online/Rooms/GetRoomsRequest.cs @@ -44,7 +44,7 @@ namespace osu.Game.Online.Rooms // API doesn't populate status so let's do it here. foreach (var room in Response) { - if (room.EndDate.Value != null && DateTimeOffset.Now >= room.EndDate.Value) + if (room.EndDate != null && DateTimeOffset.Now >= room.EndDate) room.Status = new RoomStatusEnded(); else if (room.HasPassword) room.Status = new RoomStatusOpenPrivate(); diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 61f944a728..fd958d8725 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -86,6 +86,39 @@ namespace osu.Game.Online.Rooms set => SetField(ref category, value); } + /// + /// The duration for which the room will be open. Will be null after the room is created. + /// + /// + /// To check the room end time, use . + /// + public TimeSpan? Duration + { + get => duration == null ? null : TimeSpan.FromMinutes(duration.Value); + set => SetField(ref duration, value == null ? null : (int)value.Value.TotalMinutes); + } + + /// + /// The date at which the room was opened. Will be null while the room has not yet been created. + /// + public DateTimeOffset? StartDate + { + get => startDate; + set => SetField(ref startDate, value); + } + + /// + /// The date at which the room will be closed. + /// + /// + /// To set the room duration, use . + /// + public DateTimeOffset? EndDate + { + get => endDate; + set => SetField(ref endDate, value); + } + /// /// The maximum number of users allowed in the room. /// @@ -189,6 +222,15 @@ namespace osu.Game.Online.Rooms [JsonConverter(typeof(SnakeCaseStringEnumConverter))] private RoomCategory category; + [JsonProperty("duration")] + private int? duration; + + [JsonProperty("starts_at")] + private DateTimeOffset? startDate; + + [JsonProperty("ends_at")] + private DateTimeOffset? endDate; + // Not yet serialised (not implemented). private int? maxParticipants; @@ -245,36 +287,6 @@ namespace osu.Game.Online.Rooms [JsonProperty("recent_participants")] public readonly BindableList RecentParticipants = new BindableList(); - #region Properties only used for room creation request - - [Cached] - public readonly Bindable Duration = new Bindable(); - - [JsonProperty("duration")] - private int? duration - { - get => (int?)Duration.Value?.TotalMinutes; - set - { - if (value == null) - Duration.Value = null; - else - Duration.Value = TimeSpan.FromMinutes(value.Value); - } - } - - #endregion - - // Only supports retrieval for now - [Cached] - [JsonProperty("starts_at")] - public readonly Bindable StartDate = new Bindable(); - - // Only supports retrieval for now - [Cached] - [JsonProperty("ends_at")] - public readonly Bindable EndDate = new Bindable(); - // Todo: Find a better way to do this (https://github.com/ppy/osu-framework/issues/1930) [JsonProperty("max_attempts", DefaultValueHandling = DefaultValueHandling.Ignore)] private int? maxAttempts @@ -307,7 +319,7 @@ namespace osu.Game.Online.Rooms Type = other.Type; MaxParticipants = other.MaxParticipants; ParticipantCount = other.ParticipantCount; - EndDate.Value = other.EndDate.Value; + EndDate = other.EndDate; UserScore.Value = other.UserScore.Value; QueueMode = other.QueueMode; AutoStartDuration = other.AutoStartDuration; diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs index 0013c9e033..92db4d2d07 100644 --- a/osu.Game/Screens/Menu/DailyChallengeButton.cs +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -155,7 +155,7 @@ namespace osu.Game.Screens.Menu Room = room; cover.OnlineInfo = TooltipContent = room.Playlist.FirstOrDefault()?.Beatmap.BeatmapSet as APIBeatmapSet; - if (room.StartDate.Value != null && room.RoomID != lastDailyChallengeRoomID) + if (room.StartDate != null && room.RoomID != lastDailyChallengeRoomID) { lastDailyChallengeRoomID = room.RoomID; @@ -163,7 +163,7 @@ namespace osu.Game.Screens.Menu statics.SetValue(Static.DailyChallengeIntroPlayed, false); // we only want to notify the user if the new challenge just went live. - if (Math.Abs((DateTimeOffset.Now - room.StartDate.Value!.Value).TotalSeconds) < 1800) + if (Math.Abs((DateTimeOffset.Now - room.StartDate.Value).TotalSeconds) < 1800) notificationOverlay?.Post(new NewDailyChallengeNotification(room)); } @@ -179,7 +179,7 @@ namespace osu.Game.Screens.Menu if (Room == null) return; - var remaining = (Room.EndDate.Value - DateTimeOffset.Now) ?? TimeSpan.Zero; + var remaining = (Room.EndDate - DateTimeOffset.Now) ?? TimeSpan.Zero; if (remaining <= TimeSpan.Zero) { diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 968345c713..9559826dab 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -228,7 +228,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge Origin = Anchor.Centre, Children = new Drawable[] { - new DailyChallengeTimeRemainingRing(), + new DailyChallengeTimeRemainingRing(room), breakdown = new DailyChallengeScoreBreakdown(), totals = new DailyChallengeTotalsDisplay(), } @@ -301,7 +301,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge Spacing = new Vector2(10), Children = new Drawable[] { - new PlaylistsReadyButton + new PlaylistsReadyButton(room) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTimeRemainingRing.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTimeRemainingRing.cs index e86f26ad6b..bffbb664a3 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTimeRemainingRing.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTimeRemainingRing.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.ComponentModel; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -10,6 +11,7 @@ using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Rooms; using osu.Game.Overlays; using osuTK; @@ -17,8 +19,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { public partial class DailyChallengeTimeRemainingRing : OnlinePlayComposite { - private CircularProgress progress = null!; - private OsuSpriteText timeText = null!; + private readonly Room room; [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; @@ -26,6 +27,14 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge [Resolved] private OsuColour colours { get; set; } = null!; + private CircularProgress progress = null!; + private OsuSpriteText timeText = null!; + + public DailyChallengeTimeRemainingRing(Room room) + { + this.room = room; + } + [BackgroundDependencyLoader] private void load() { @@ -90,12 +99,23 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { base.LoadComplete(); - StartDate.BindValueChanged(_ => Scheduler.AddOnce(updateState)); - EndDate.BindValueChanged(_ => Scheduler.AddOnce(updateState)); + room.PropertyChanged += onRoomPropertyChanged; updateState(); + FinishTransforms(true); } + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + switch (e.PropertyName) + { + case nameof(Room.StartDate): + case nameof(Room.EndDate): + Scheduler.AddOnce(updateState); + break; + } + } + private ScheduledDelegate? scheduledUpdate; private void updateState() @@ -105,7 +125,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge const float transition_duration = 300; - if (StartDate.Value == null || EndDate.Value == null || EndDate.Value < DateTimeOffset.Now) + if (room.StartDate == null || room.EndDate == null || room.EndDate < DateTimeOffset.Now) { timeText.Text = TimeSpan.Zero.ToString(@"hh\:mm\:ss"); progress.Progress = 0; @@ -114,8 +134,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge return; } - var roomDuration = EndDate.Value.Value - StartDate.Value.Value; - var remaining = EndDate.Value.Value - DateTimeOffset.Now; + var roomDuration = room.EndDate.Value - room.StartDate.Value; + var remaining = room.EndDate.Value - DateTimeOffset.Now; timeText.Text = remaining.ToString(@"hh\:mm\:ss"); progress.Progress = remaining.TotalSeconds / roomDuration.TotalSeconds; @@ -138,5 +158,11 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge scheduledUpdate = Scheduler.AddDelayed(updateState, 1000); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 390ea06c1b..7a22bf1a0b 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -169,7 +169,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft }, - endDateInfo = new EndDateInfo + endDateInfo = new EndDateInfo(Room) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/EndDateInfo.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/EndDateInfo.cs index 844991095e..18c7972c06 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/EndDateInfo.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/EndDateInfo.cs @@ -2,49 +2,68 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.ComponentModel; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Graphics; +using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { public partial class EndDateInfo : OnlinePlayComposite { - public EndDateInfo() + private readonly Room room; + + public EndDateInfo(Room room) { + this.room = room; AutoSizeAxes = Axes.Both; } [BackgroundDependencyLoader] private void load() { - InternalChild = new EndDatePart + InternalChild = new EndDatePart(room) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 12), - EndDate = { BindTarget = EndDate } + Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 12) }; } private partial class EndDatePart : DrawableDate { - public readonly IBindable EndDate = new Bindable(); + private readonly Room room; - public EndDatePart() + public EndDatePart(Room room) : base(DateTimeOffset.UtcNow) { - EndDate.BindValueChanged(date => - { - // If null, set a very large future date to prevent unnecessary schedules. - Date = date.NewValue ?? DateTimeOffset.Now.AddYears(1); - }, true); + this.room = room; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + room.PropertyChanged += onRoomPropertyChanged; + updateEndDate(); + } + + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.EndDate)) + updateEndDate(); + } + + private void updateEndDate() + { + // If null, set a very large future date to prevent unnecessary schedules. + Date = room.EndDate ?? DateTimeOffset.Now.AddYears(1); } protected override string Format() { - if (EndDate.Value == null) + if (room.EndDate == null) return string.Empty; var diffToNow = Date.Subtract(DateTimeOffset.Now); @@ -60,6 +79,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components return $"Closing {base.Format()}"; } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } } } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs index 10d3695d14..b3dc617fd6 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs @@ -34,7 +34,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components TextFlow.Colour = Colour4.Black; Pill.Background.Alpha = 1; - EndDate.BindValueChanged(_ => updateDisplay()); room.PropertyChanged += onRoomPropertyChanged; updateDisplay(); @@ -43,8 +42,13 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(Room.Status)) - updateDisplay(); + switch (e.PropertyName) + { + case nameof(Room.Status): + case nameof(Room.EndDate): + updateDisplay(); + break; + } } private void updateDisplay() diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index a2eba3188d..90288a1067 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -341,8 +341,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge r.RoomID = null; // Null out dates because end date is not supported client-side and the settings overlay will populate a duration. - r.EndDate.Value = null; - r.Duration.Value = null; + r.EndDate = null; + r.Duration = null; Open(r); diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 70418d1a7e..66196d1df6 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; @@ -32,14 +31,5 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] public Bindable UserScore { get; private set; } = null!; - - [Resolved(typeof(Room))] - protected Bindable StartDate { get; private set; } = null!; - - [Resolved(typeof(Room))] - protected Bindable EndDate { get; private set; } = null!; - - [Resolved(typeof(Room))] - protected Bindable Duration { get; private set; } = null!; } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs index 4b00678b01..5f1037bff1 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Allocation; @@ -17,20 +15,20 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { public partial class PlaylistsReadyButton : ReadyButton { - [Resolved(typeof(Room), nameof(Room.EndDate))] - private Bindable endDate { get; set; } - [Resolved(typeof(Room), nameof(Room.MaxAttempts))] - private Bindable maxAttempts { get; set; } + private Bindable maxAttempts { get; set; } = null!; [Resolved(typeof(Room), nameof(Room.UserScore))] - private Bindable userScore { get; set; } + private Bindable userScore { get; set; } = null!; [Resolved] - private IBindable gameBeatmap { get; set; } + private IBindable gameBeatmap { get; set; } = null!; - public PlaylistsReadyButton() + private readonly Room room; + + public PlaylistsReadyButton(Room room) { + this.room = room; Text = "Start"; } @@ -80,6 +78,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private bool enoughTimeLeft => // This should probably consider the length of the currently selected item, rather than a constant 30 seconds. - endDate.Value != null && DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(gameBeatmap.Value.Track.Length) < endDate.Value; + room.EndDate != null && DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(gameBeatmap.Value.Track.Length) < room.EndDate; } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs index 5161de5f64..0d837423a6 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs @@ -1,26 +1,25 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Online.Rooms; using osuTK; namespace osu.Game.Screens.OnlinePlay.Playlists { public partial class PlaylistsRoomFooter : CompositeDrawable { - public Action OnStart; + public Action? OnStart; - public PlaylistsRoomFooter() + public PlaylistsRoomFooter(Room room) { RelativeSizeAxes = Axes.Both; InternalChildren = new[] { - new PlaylistsReadyButton + new PlaylistsReadyButton(room) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index 20fbe42dd5..63bfa8f45d 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -317,7 +317,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists }; MaxAttempts.BindValueChanged(count => MaxAttemptsField.Text = count.NewValue?.ToString(), true); - Duration.BindValueChanged(duration => DurationField.Current.Value = duration.NewValue ?? TimeSpan.FromMinutes(30), true); DurationField.Current.BindValueChanged(duration => { @@ -346,6 +345,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists updateRoomName(); updateRoomAvailability(); updateRoomMaxParticipants(); + updateRoomDuration(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) @@ -363,6 +363,10 @@ namespace osu.Game.Screens.OnlinePlay.Playlists case nameof(Room.MaxParticipants): updateRoomMaxParticipants(); break; + + case nameof(Room.Duration): + updateRoomDuration(); + break; } } @@ -375,6 +379,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private void updateRoomMaxParticipants() => MaxParticipantsField.Text = room.MaxParticipants?.ToString(); + private void updateRoomDuration() + => DurationField.Current.Value = room.Duration ?? TimeSpan.FromMinutes(30); + private void populateDurations(ValueChangedEvent user) { // roughly correct (see https://github.com/Humanizr/Humanizer/blob/18167e56c082449cc4fe805b8429e3127a7b7f93/readme.md?plain=1#L427) @@ -431,7 +438,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists else MaxAttempts.Value = null; - Duration.Value = DurationField.Current.Value; + room.Duration = DurationField.Current.Value; loadingLayer.Show(); manager?.CreateRoom(room, onSuccess, onError); diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 9780c48f1f..6d9bf29687 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -234,7 +234,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists } }; - protected override Drawable CreateFooter() => new PlaylistsRoomFooter + protected override Drawable CreateFooter() => new PlaylistsRoomFooter(Room) { OnStart = StartPlay }; diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs index 3db9e12ab7..de54ff57e8 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay RoomID = -currentRoomId, Name = $@"Room {currentRoomId}", Host = new APIUser { Username = @"Host" }, - EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }, + Duration = TimeSpan.FromSeconds(10), Category = withSpotlightRooms && i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal, }; From 6c84e425f8c71ac1a850c8d2e43e6be6cc1c2d81 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 20:32:34 +0900 Subject: [PATCH 863/924] Make `Room.MaxAttempts` non-bindable --- .../TestScenePlaylistsRoomCreation.cs | 2 +- osu.Game/Online/Rooms/Room.cs | 23 ++++++------ .../Components/RoomLocalUserInfo.cs | 36 +++++++++++++------ .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 -- .../Playlists/PlaylistsReadyButton.cs | 7 ++-- .../Playlists/PlaylistsRoomSettingsOverlay.cs | 19 +++++----- .../Playlists/PlaylistsRoomSubScreen.cs | 19 +++++++--- 7 files changed, 66 insertions(+), 43 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index d0cbee9e50..031f9c272f 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.Playlists setupAndCreateRoom(room => { room.Name = "my awesome room"; - room.MaxAttempts.Value = 5; + room.MaxAttempts = 5; room.Host = API.LocalUser.Value; room.RecentParticipants.Add(room.Host); room.EndDate = DateTimeOffset.Now.AddMinutes(5); diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index fd958d8725..cfb3b30ddb 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -146,6 +146,15 @@ namespace osu.Game.Online.Rooms set => SetField(ref type, value); } + /// + /// The maximum number of attempts on the playlist. Only valid for playlist rooms. + /// + public int? MaxAttempts + { + get => maxAttempts; + set => SetField(ref maxAttempts, value); + } + /// /// The playlist queueing mode. Only valid for multiplayer rooms. /// @@ -237,6 +246,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("participant_count")] private int participantCount; + [JsonProperty("max_attempts", DefaultValueHandling = DefaultValueHandling.Ignore)] + private int? maxAttempts; + [JsonConverter(typeof(SnakeCaseStringEnumConverter))] [JsonProperty("type")] private MatchType type; @@ -276,9 +288,6 @@ namespace osu.Game.Online.Rooms [Cached] public readonly Bindable DifficultyRange = new Bindable(); - [Cached] - public readonly Bindable MaxAttempts = new Bindable(); - [Cached] [JsonProperty("current_user_score")] public readonly Bindable UserScore = new Bindable(); @@ -287,14 +296,6 @@ namespace osu.Game.Online.Rooms [JsonProperty("recent_participants")] public readonly BindableList RecentParticipants = new BindableList(); - // Todo: Find a better way to do this (https://github.com/ppy/osu-framework/issues/1930) - [JsonProperty("max_attempts", DefaultValueHandling = DefaultValueHandling.Ignore)] - private int? maxAttempts - { - get => MaxAttempts.Value; - set => MaxAttempts.Value = value; - } - /// /// Copies values from another into this one. /// diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs b/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs index 0c3b53266c..974c296399 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs @@ -1,26 +1,28 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - +using System.ComponentModel; using System.Linq; 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.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Components { public partial class RoomLocalUserInfo : OnlinePlayComposite { - private OsuSpriteText attemptDisplay; + private readonly Room room; + private OsuSpriteText attemptDisplay = null!; [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; - public RoomLocalUserInfo() + public RoomLocalUserInfo(Room room) { + this.room = room; AutoSizeAxes = Axes.Both; } @@ -45,19 +47,27 @@ namespace osu.Game.Screens.OnlinePlay.Components { base.LoadComplete(); - MaxAttempts.BindValueChanged(_ => updateAttempts()); - UserScore.BindValueChanged(_ => updateAttempts(), true); + UserScore.BindValueChanged(_ => updateAttempts()); + + room.PropertyChanged += onRoomPropertyChanged; + updateAttempts(); + } + + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.MaxAttempts)) + updateAttempts(); } private void updateAttempts() { - if (MaxAttempts.Value != null) + if (room.MaxAttempts != null) { - attemptDisplay.Text = $"Maximum attempts: {MaxAttempts.Value:N0}"; + attemptDisplay.Text = $"Maximum attempts: {room.MaxAttempts:N0}"; if (UserScore.Value != null) { - int remaining = MaxAttempts.Value.Value - UserScore.Value.PlaylistItemAttempts.Sum(a => a.Attempts); + int remaining = room.MaxAttempts.Value - UserScore.Value.PlaylistItemAttempts.Sum(a => a.Attempts); attemptDisplay.Text += $" ({remaining} remaining)"; if (remaining == 0) @@ -69,5 +79,11 @@ namespace osu.Game.Screens.OnlinePlay.Components attemptDisplay.Text = string.Empty; } } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } } } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 66196d1df6..1880fdc974 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -26,9 +26,6 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected BindableList RecentParticipants { get; private set; } = null!; - [Resolved(typeof(Room))] - protected Bindable MaxAttempts { get; private set; } = null!; - [Resolved(typeof(Room))] public Bindable UserScore { get; private set; } = null!; } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs index 5f1037bff1..ca661b041a 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs @@ -15,9 +15,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { public partial class PlaylistsReadyButton : ReadyButton { - [Resolved(typeof(Room), nameof(Room.MaxAttempts))] - private Bindable maxAttempts { get; set; } = null!; - [Resolved(typeof(Room), nameof(Room.UserScore))] private Bindable userScore { get; set; } = null!; @@ -46,10 +43,10 @@ namespace osu.Game.Screens.OnlinePlay.Playlists userScore.BindValueChanged(aggregate => { - if (maxAttempts.Value == null) + if (room.MaxAttempts == null) return; - int remaining = maxAttempts.Value.Value - aggregate.NewValue.PlaylistItemAttempts.Sum(a => a.Attempts); + int remaining = room.MaxAttempts.Value - aggregate.NewValue.PlaylistItemAttempts.Sum(a => a.Attempts); hasRemainingAttempts = remaining > 0; }); diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index 63bfa8f45d..6550b4c2f1 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -316,8 +316,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists loadingLayer = new LoadingLayer(true) }; - MaxAttempts.BindValueChanged(count => MaxAttemptsField.Text = count.NewValue?.ToString(), true); - DurationField.Current.BindValueChanged(duration => { if (hasValidDuration) @@ -346,6 +344,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists updateRoomAvailability(); updateRoomMaxParticipants(); updateRoomDuration(); + updateRoomMaxAttempts(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) @@ -367,6 +366,10 @@ namespace osu.Game.Screens.OnlinePlay.Playlists case nameof(Room.Duration): updateRoomDuration(); break; + + case nameof(Room.MaxAttempts): + updateRoomMaxAttempts(); + break; } } @@ -382,6 +385,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private void updateRoomDuration() => DurationField.Current.Value = room.Duration ?? TimeSpan.FromMinutes(30); + private void updateRoomMaxAttempts() + => MaxAttemptsField.Text = room.MaxAttempts?.ToString(); + private void populateDurations(ValueChangedEvent user) { // roughly correct (see https://github.com/Humanizr/Humanizer/blob/18167e56c082449cc4fe805b8429e3127a7b7f93/readme.md?plain=1#L427) @@ -431,13 +437,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists room.Name = NameField.Text; room.Availability = AvailabilityPicker.Current.Value; - room.MaxParticipants = int.TryParse(MaxParticipantsField.Text, out int max) ? max : null; - - if (int.TryParse(MaxAttemptsField.Text, out max)) - MaxAttempts.Value = max; - else - MaxAttempts.Value = null; - + room.MaxParticipants = int.TryParse(MaxParticipantsField.Text, out int maxParticipants) ? maxParticipants : null; + room.MaxAttempts = int.TryParse(MaxAttemptsField.Text, out int maxAttempts) ? maxAttempts : null; room.Duration = DurationField.Current.Value; loadingLayer.Show(); diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 6d9bf29687..079e3b03b7 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -60,16 +60,24 @@ namespace osu.Game.Screens.OnlinePlay.Playlists base.LoadComplete(); isIdle.BindValueChanged(_ => updatePollingRate(), true); - Room.MaxAttempts.BindValueChanged(_ => progressSection.Alpha = Room.MaxAttempts.Value != null ? 1 : 0, true); Room.PropertyChanged += onRoomPropertyChanged; updateSetupState(); + updateRoomMaxAttempts(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(Room.RoomID)) - updateSetupState(); + switch (e.PropertyName) + { + case nameof(Room.RoomID): + updateSetupState(); + break; + + case nameof(Room.MaxAttempts): + updateRoomMaxAttempts(); + break; + } } private void updateSetupState() @@ -82,6 +90,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists } } + private void updateRoomMaxAttempts() + => progressSection.Alpha = Room.MaxAttempts != null ? 1 : 0; + protected override Drawable CreateMainContent() => new Container { RelativeSizeAxes = Axes.Both, @@ -193,7 +204,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Children = new Drawable[] { new OverlinedHeader("Progress"), - new RoomLocalUserInfo(), + new RoomLocalUserInfo(Room), } }, }, From 80b3e330a6daf7cfd95a5b1e69148f033826b8b3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 22:23:33 +0900 Subject: [PATCH 864/924] Make `Room.ChannelId` non-bindable --- osu.Game/Online/Rooms/Room.cs | 18 +++++++++++----- .../Match/Components/MatchChatDisplay.cs | 21 ++++++++++++------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index cfb3b30ddb..ea3e8d73fe 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -194,6 +194,15 @@ namespace osu.Game.Online.Rooms set => SetField(ref currentPlaylistItem, value); } + /// + /// The chat channel id for the room. Will be 0 while the room has not yet been created. + /// + public int ChannelId + { + get => channelId; + private set => SetField(ref channelId, value); + } + /// /// The current room status. /// @@ -266,6 +275,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("current_playlist_item")] private PlaylistItem? currentPlaylistItem; + [JsonProperty("channel_id")] + private int channelId; + // Not serialised (see: GetRoomsRequest). private RoomStatus status = new RoomStatusOpen(); @@ -276,10 +288,6 @@ namespace osu.Game.Online.Rooms [JsonProperty("playlist")] public readonly BindableList Playlist = new BindableList(); - [Cached] - [JsonProperty("channel_id")] - public readonly Bindable ChannelId = new Bindable(); - [JsonProperty("playlist_item_stats")] [Cached] public readonly Bindable PlaylistItemStats = new Bindable(); @@ -313,7 +321,7 @@ namespace osu.Game.Online.Rooms if (other.Host != null && Host?.Id != other.Host.Id) Host = other.Host; - ChannelId.Value = other.ChannelId.Value; + ChannelId = other.ChannelId; Status = other.Status; Availability = other.Availability; HasPassword = other.HasPassword; diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchChatDisplay.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchChatDisplay.cs index ed4f0ad6d8..a81425102d 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchChatDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchChatDisplay.cs @@ -1,8 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.ComponentModel; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Game.Online.Chat; using osu.Game.Online.Rooms; @@ -10,8 +10,6 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components { public partial class MatchChatDisplay : StandAloneChatDisplay { - private readonly IBindable channelId = new Bindable(); - [Resolved] private ChannelManager? channelManager { get; set; } @@ -29,23 +27,30 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components { base.LoadComplete(); - // Required for the time being since this component is created prior to the room being joined. - channelId.BindTo(room.ChannelId); - channelId.BindValueChanged(_ => updateChannel(), true); + room.PropertyChanged += onRoomPropertyChanged; + updateChannel(); + } + + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.ChannelId)) + updateChannel(); } private void updateChannel() { - if (room.RoomID == null || channelId.Value == 0) + if (room.RoomID == null || room.ChannelId == 0) return; - Channel.Value = channelManager?.JoinChannel(new Channel { Id = channelId.Value, Type = ChannelType.Multiplayer, Name = $"#lazermp_{room.RoomID.Value}" }); + Channel.Value = channelManager?.JoinChannel(new Channel { Id = room.ChannelId, Type = ChannelType.Multiplayer, Name = $"#lazermp_{room.RoomID.Value}" }); } protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + if (leaveChannelOnDispose) channelManager?.LeaveChannel(Channel.Value); } From 487a010b12eec172c1e5aa8dd2ad9f64a973508e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 22:29:45 +0900 Subject: [PATCH 865/924] Make `Room.PlaylistItemStats` non-bindable --- osu.Game/Online/Rooms/Room.cs | 18 ++++++++--- .../Lounge/Components/DrawableRoom.cs | 2 +- .../Lounge/Components/PlaylistCountPill.cs | 31 ++++++++++++++++--- .../Lounge/Components/RoomsContainer.cs | 2 +- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 -- .../Visual/OnlinePlay/TestRoomManager.cs | 2 +- 6 files changed, 43 insertions(+), 15 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index ea3e8d73fe..dd3a9b2e73 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -155,6 +155,15 @@ namespace osu.Game.Online.Rooms set => SetField(ref maxAttempts, value); } + /// + /// Describes the items in the playlist. + /// + public RoomPlaylistItemStats? PlaylistItemStats + { + get => playlistItemStats; + set => SetField(ref playlistItemStats, value); + } + /// /// The playlist queueing mode. Only valid for multiplayer rooms. /// @@ -258,6 +267,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("max_attempts", DefaultValueHandling = DefaultValueHandling.Ignore)] private int? maxAttempts; + [JsonProperty("playlist_item_stats")] + private RoomPlaylistItemStats? playlistItemStats; + [JsonConverter(typeof(SnakeCaseStringEnumConverter))] [JsonProperty("type")] private MatchType type; @@ -288,10 +300,6 @@ namespace osu.Game.Online.Rooms [JsonProperty("playlist")] public readonly BindableList Playlist = new BindableList(); - [JsonProperty("playlist_item_stats")] - [Cached] - public readonly Bindable PlaylistItemStats = new Bindable(); - [JsonProperty("difficulty_range")] [Cached] public readonly Bindable DifficultyRange = new Bindable(); @@ -333,7 +341,7 @@ namespace osu.Game.Online.Rooms QueueMode = other.QueueMode; AutoStartDuration = other.AutoStartDuration; DifficultyRange.Value = other.DifficultyRange.Value; - PlaylistItemStats.Value = other.PlaylistItemStats.Value; + PlaylistItemStats = other.PlaylistItemStats; CurrentPlaylistItem = other.CurrentPlaylistItem; AutoSkip = other.AutoSkip; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 7a22bf1a0b..ddfbe00227 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -355,7 +355,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components pills.AddRange(new Drawable[] { - new PlaylistCountPill + new PlaylistCountPill(Room) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs index fe5ccb4f09..7e530993dd 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs @@ -1,10 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.ComponentModel; using System.Linq; using Humanizer; using osu.Framework.Extensions.LocalisationExtensions; using osu.Game.Graphics; +using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { @@ -13,26 +15,47 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components /// public partial class PlaylistCountPill : OnlinePlayPill { + private readonly Room room; + + public PlaylistCountPill(Room room) + { + this.room = room; + } + protected override void LoadComplete() { base.LoadComplete(); - PlaylistItemStats.BindValueChanged(_ => updateCount()); - Playlist.BindCollectionChanged((_, _) => updateCount(), true); + Playlist.BindCollectionChanged((_, _) => updateCount()); + + room.PropertyChanged += onRoomPropertyChanged; + updateCount(); + } + + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.PlaylistItemStats)) + updateCount(); } private void updateCount() { - int activeItems = Playlist.Count > 0 || PlaylistItemStats.Value == null + int activeItems = Playlist.Count > 0 || room.PlaylistItemStats == null // For now, use the playlist as the source of truth if it has any items. // This allows the count to display correctly on the room screen (after joining a room). ? Playlist.Count(i => !i.Expired) - : PlaylistItemStats.Value.CountActive; + : room.PlaylistItemStats.CountActive; TextFlow.Clear(); TextFlow.AddText(activeItems.ToLocalisableString(), s => s.Font = s.Font.With(weight: FontWeight.Bold)); TextFlow.AddText(" "); TextFlow.AddText("Beatmap".ToQuantity(activeItems, ShowQuantityAs.None)); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 11ad886773..17aed021b2 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -79,7 +79,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { bool matchingFilter = true; - matchingFilter &= criteria.Ruleset == null || r.Room.PlaylistItemStats.Value?.RulesetIDs.Any(id => id == criteria.Ruleset.OnlineID) != false; + matchingFilter &= criteria.Ruleset == null || r.Room.PlaylistItemStats?.RulesetIDs.Any(id => id == criteria.Ruleset.OnlineID) != false; if (!string.IsNullOrEmpty(criteria.SearchString)) { diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 1880fdc974..46debb5b9d 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -14,9 +14,6 @@ namespace osu.Game.Screens.OnlinePlay /// public partial class OnlinePlayComposite : CompositeDrawable { - [Resolved(typeof(Room))] - protected Bindable PlaylistItemStats { get; private set; } = null!; - [Resolved(typeof(Room))] protected BindableList Playlist { get; private set; } = null!; diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs index de54ff57e8..a384a14441 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay if (ruleset != null) { - room.PlaylistItemStats.Value = new Room.RoomPlaylistItemStats + room.PlaylistItemStats = new Room.RoomPlaylistItemStats { RulesetIDs = new[] { ruleset.OnlineID }, }; From c4f8fd183216d537f3d2ee47d4c54c561cee3f3c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 22:33:49 +0900 Subject: [PATCH 866/924] Make `Room.DifficultyRange` non-bindable --- .../TestSceneStarRatingRangeDisplay.cs | 2 +- osu.Game/Online/Rooms/Room.cs | 18 +++++--- .../Components/StarRatingRangeDisplay.cs | 44 +++++++++++++------ .../Lounge/Components/DrawableRoom.cs | 2 +- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 -- 5 files changed, 46 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs index b53a61f881..5c11690cd6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { SelectedRoom.Value = new Room(); - Child = new StarRatingRangeDisplay + Child = new StarRatingRangeDisplay(SelectedRoom.Value) { Anchor = Anchor.Centre, Origin = Anchor.Centre diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index dd3a9b2e73..9d69e791c7 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -164,6 +164,15 @@ namespace osu.Game.Online.Rooms set => SetField(ref playlistItemStats, value); } + /// + /// Describes the range of difficulty of the room. + /// + public RoomDifficultyRange? DifficultyRange + { + get => difficultyRange; + set => SetField(ref difficultyRange, value); + } + /// /// The playlist queueing mode. Only valid for multiplayer rooms. /// @@ -270,6 +279,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("playlist_item_stats")] private RoomPlaylistItemStats? playlistItemStats; + [JsonProperty("difficulty_range")] + private RoomDifficultyRange? difficultyRange; + [JsonConverter(typeof(SnakeCaseStringEnumConverter))] [JsonProperty("type")] private MatchType type; @@ -300,10 +312,6 @@ namespace osu.Game.Online.Rooms [JsonProperty("playlist")] public readonly BindableList Playlist = new BindableList(); - [JsonProperty("difficulty_range")] - [Cached] - public readonly Bindable DifficultyRange = new Bindable(); - [Cached] [JsonProperty("current_user_score")] public readonly Bindable UserScore = new Bindable(); @@ -340,7 +348,7 @@ namespace osu.Game.Online.Rooms UserScore.Value = other.UserScore.Value; QueueMode = other.QueueMode; AutoStartDuration = other.AutoStartDuration; - DifficultyRange.Value = other.DifficultyRange.Value; + DifficultyRange = other.DifficultyRange; PlaylistItemStats = other.PlaylistItemStats; CurrentPlaylistItem = other.CurrentPlaylistItem; AutoSkip = other.AutoSkip; diff --git a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs index 2ee3bb30dd..13e3dc6d7c 100644 --- a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs @@ -1,9 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; +using System.ComponentModel; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -13,22 +12,27 @@ using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; +using osu.Game.Online.Rooms; using osuTK; +using Container = osu.Framework.Graphics.Containers.Container; namespace osu.Game.Screens.OnlinePlay.Components { public partial class StarRatingRangeDisplay : OnlinePlayComposite { + private readonly Room room; + [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; - private StarRatingDisplay minDisplay; - private Drawable minBackground; - private StarRatingDisplay maxDisplay; - private Drawable maxBackground; + private StarRatingDisplay minDisplay = null!; + private Drawable minBackground = null!; + private StarRatingDisplay maxDisplay = null!; + private Drawable maxBackground = null!; - public StarRatingRangeDisplay() + public StarRatingRangeDisplay(Room room) { + this.room = room; AutoSizeAxes = Axes.Both; } @@ -76,8 +80,16 @@ namespace osu.Game.Screens.OnlinePlay.Components { base.LoadComplete(); - DifficultyRange.BindValueChanged(_ => updateRange()); - Playlist.BindCollectionChanged((_, _) => updateRange(), true); + Playlist.BindCollectionChanged((_, _) => updateRange()); + + room.PropertyChanged += onRoomPropertyChanged; + updateRange(); + } + + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.DifficultyRange)) + updateRange(); } private void updateRange() @@ -85,11 +97,11 @@ namespace osu.Game.Screens.OnlinePlay.Components StarDifficulty minDifficulty; StarDifficulty maxDifficulty; - if (DifficultyRange.Value != null && Playlist.Count == 0) + if (room.DifficultyRange != null && Playlist.Count == 0) { // When Playlist is empty (in lounge) we take retrieved range - minDifficulty = new StarDifficulty(DifficultyRange.Value.Min, 0); - maxDifficulty = new StarDifficulty(DifficultyRange.Value.Max, 0); + minDifficulty = new StarDifficulty(room.DifficultyRange.Min, 0); + maxDifficulty = new StarDifficulty(room.DifficultyRange.Max, 0); } else { @@ -107,5 +119,11 @@ namespace osu.Game.Screens.OnlinePlay.Components minBackground.Colour = colours.ForStarDifficulty(minDifficulty.Stars); maxBackground.Colour = colours.ForStarDifficulty(maxDifficulty.Stars); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index ddfbe00227..c368666ac2 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -360,7 +360,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, - new StarRatingRangeDisplay + new StarRatingRangeDisplay(Room) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 46debb5b9d..66e43c50b7 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -17,9 +17,6 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected BindableList Playlist { get; private set; } = null!; - [Resolved(typeof(Room))] - protected Bindable DifficultyRange { get; private set; } = null!; - [Resolved(typeof(Room))] protected BindableList RecentParticipants { get; private set; } = null!; From dc5337d771ef035ae66911a0de85863661103d65 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 22:39:01 +0900 Subject: [PATCH 867/924] Make `Room.UserScore` non-bindable --- osu.Game/Online/Rooms/Room.cs | 18 +++++++--- .../Components/RoomLocalUserInfo.cs | 15 +++++---- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 -- .../Playlists/PlaylistsReadyButton.cs | 33 +++++++++++++------ 4 files changed, 45 insertions(+), 24 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 9d69e791c7..8fb3901a79 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -200,6 +200,15 @@ namespace osu.Game.Online.Rooms set => SetField(ref autoStartDuration, (ushort)value.TotalSeconds); } + /// + /// Provides some extra scoring statistics for the local user in the room. + /// + public PlaylistAggregateScore? UserScore + { + get => userScore; + set => SetField(ref userScore, value); + } + /// /// Represents the current item selected within the room. /// @@ -296,6 +305,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("auto_start_duration")] private ushort autoStartDuration; + [JsonProperty("current_user_score")] + private PlaylistAggregateScore? userScore; + [JsonProperty("current_playlist_item")] private PlaylistItem? currentPlaylistItem; @@ -312,10 +324,6 @@ namespace osu.Game.Online.Rooms [JsonProperty("playlist")] public readonly BindableList Playlist = new BindableList(); - [Cached] - [JsonProperty("current_user_score")] - public readonly Bindable UserScore = new Bindable(); - [Cached] [JsonProperty("recent_participants")] public readonly BindableList RecentParticipants = new BindableList(); @@ -345,7 +353,7 @@ namespace osu.Game.Online.Rooms MaxParticipants = other.MaxParticipants; ParticipantCount = other.ParticipantCount; EndDate = other.EndDate; - UserScore.Value = other.UserScore.Value; + UserScore = other.UserScore; QueueMode = other.QueueMode; AutoStartDuration = other.AutoStartDuration; DifficultyRange = other.DifficultyRange; diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs b/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs index 974c296399..b3e60d4318 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs @@ -47,16 +47,19 @@ namespace osu.Game.Screens.OnlinePlay.Components { base.LoadComplete(); - UserScore.BindValueChanged(_ => updateAttempts()); - room.PropertyChanged += onRoomPropertyChanged; updateAttempts(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(Room.MaxAttempts)) - updateAttempts(); + switch (e.PropertyName) + { + case nameof(Room.UserScore): + case nameof(Room.MaxAttempts): + updateAttempts(); + break; + } } private void updateAttempts() @@ -65,9 +68,9 @@ namespace osu.Game.Screens.OnlinePlay.Components { attemptDisplay.Text = $"Maximum attempts: {room.MaxAttempts:N0}"; - if (UserScore.Value != null) + if (room.UserScore != null) { - int remaining = room.MaxAttempts.Value - UserScore.Value.PlaylistItemAttempts.Sum(a => a.Attempts); + int remaining = room.MaxAttempts.Value - room.UserScore.PlaylistItemAttempts.Sum(a => a.Attempts); attemptDisplay.Text += $" ({remaining} remaining)"; if (remaining == 0) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 66e43c50b7..82fc6d535c 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -19,8 +19,5 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected BindableList RecentParticipants { get; private set; } = null!; - - [Resolved(typeof(Room))] - public Bindable UserScore { get; private set; } = null!; } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs index ca661b041a..a460779ea6 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.ComponentModel; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -15,9 +16,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { public partial class PlaylistsReadyButton : ReadyButton { - [Resolved(typeof(Room), nameof(Room.UserScore))] - private Bindable userScore { get; set; } = null!; - [Resolved] private IBindable gameBeatmap { get; set; } = null!; @@ -41,15 +39,24 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { base.LoadComplete(); - userScore.BindValueChanged(aggregate => - { - if (room.MaxAttempts == null) - return; + room.PropertyChanged += onRoomPropertyChanged; + updateRoomUserScore(); + } - int remaining = room.MaxAttempts.Value - aggregate.NewValue.PlaylistItemAttempts.Sum(a => a.Attempts); + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.UserScore)) + updateRoomUserScore(); + } - hasRemainingAttempts = remaining > 0; - }); + private void updateRoomUserScore() + { + if (room.MaxAttempts == null || room.UserScore == null) + return; + + int remaining = room.MaxAttempts.Value - room.UserScore.PlaylistItemAttempts.Sum(a => a.Attempts); + + hasRemainingAttempts = remaining > 0; } protected override void Update() @@ -76,5 +83,11 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private bool enoughTimeLeft => // This should probably consider the length of the currently selected item, rather than a constant 30 seconds. room.EndDate != null && DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(gameBeatmap.Value.Track.Length) < room.EndDate; + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } } } From b16edbbf524b8f95cb9a67f2bb17475dc2e34bad Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 14 Nov 2024 15:07:16 +0900 Subject: [PATCH 868/924] Make `Room.RecentParticipants` non-bindable --- .../Multiplayer/TestSceneDrawableRoom.cs | 4 +- .../TestSceneDrawableRoomParticipantsList.cs | 6 +- .../TestScenePlaylistsParticipantsList.cs | 12 ++-- .../TestScenePlaylistsRoomCreation.cs | 4 +- .../Online/Multiplayer/MultiplayerClient.cs | 6 +- osu.Game/Online/Rooms/Room.cs | 32 ++++++--- .../Components/ParticipantsDisplay.cs | 2 +- .../OnlinePlay/Components/ParticipantsList.cs | 41 ++++++++---- .../DrawableRoomParticipantsList.cs | 66 ++++++++----------- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 4 -- .../OnlinePlay/TestRoomRequestsHandler.cs | 2 +- 11 files changed, 100 insertions(+), 79 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index 31923b676a..d00ff72599 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -199,11 +199,11 @@ namespace osu.Game.Tests.Visual.Multiplayer if (room.RecentParticipants.Count == 0) { - room.RecentParticipants.AddRange(Enumerable.Range(0, 20).Select(i => new APIUser + room.RecentParticipants = Enumerable.Range(0, 20).Select(i => new APIUser { Id = i, Username = $"User {i}" - })); + }).ToArray(); } return new DrawableLoungeRoom(room) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs index 3d0c1be22c..b909b934b3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs @@ -138,17 +138,17 @@ namespace osu.Game.Tests.Visual.Multiplayer private void addUser(int id) { - SelectedRoom.Value.RecentParticipants.Add(new APIUser + SelectedRoom.Value.RecentParticipants = SelectedRoom.Value.RecentParticipants.Append(new APIUser { Id = id, Username = $"User {id}" - }); + }).ToArray(); SelectedRoom.Value.ParticipantCount++; } private void removeUserAt(int index) { - SelectedRoom.Value.RecentParticipants.RemoveAt(index); + SelectedRoom.Value.RecentParticipants = SelectedRoom.Value.RecentParticipants.Where(u => !u.Equals(SelectedRoom.Value.RecentParticipants[index])).ToArray(); SelectedRoom.Value.ParticipantCount--; } } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs index 2368a2068d..f72a2cd655 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Online.API.Requests.Responses; @@ -19,17 +20,16 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("create list", () => { - SelectedRoom.Value = new Room { RoomID = 7 }; - - for (int i = 0; i < 50; i++) + SelectedRoom.Value = new Room { - SelectedRoom.Value.RecentParticipants.Add(new APIUser + RoomID = 7, + RecentParticipants = Enumerable.Range(0, 50).Select(_ => new APIUser { Username = "peppy", Statistics = new UserStatistics { GlobalRank = 1234 }, Id = 2 - }); - } + }).ToArray() + }; }); } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index 031f9c272f..f75b3d7b14 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Playlists { room.Name = "my awesome room"; room.Host = API.LocalUser.Value; - room.RecentParticipants.Add(room.Host); + room.RecentParticipants = [room.Host]; room.EndDate = DateTimeOffset.Now.AddMinutes(5); room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First()) { @@ -86,7 +86,7 @@ namespace osu.Game.Tests.Visual.Playlists room.Name = "my awesome room"; room.MaxAttempts = 5; room.Host = API.LocalUser.Value; - room.RecentParticipants.Add(room.Host); + room.RecentParticipants = [room.Host]; room.EndDate = DateTimeOffset.Now.AddMinutes(5); room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First()) { diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index fe0b4c6a21..74f89c7a61 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -487,11 +487,11 @@ namespace osu.Game.Online.Multiplayer { Debug.Assert(APIRoom != null); - APIRoom.RecentParticipants.Add(user.User ?? new APIUser + APIRoom.RecentParticipants = APIRoom.RecentParticipants.Append(user.User ?? new APIUser { Id = user.UserID, Username = "[Unresolved]" - }); + }).ToArray(); APIRoom.ParticipantCount++; } @@ -506,7 +506,7 @@ namespace osu.Game.Online.Multiplayer PlayingUserIds.Remove(user.UserID); Debug.Assert(APIRoom != null); - APIRoom.RecentParticipants.RemoveAll(u => u.Id == user.UserID); + APIRoom.RecentParticipants = APIRoom.RecentParticipants.Where(u => u.Id != user.UserID).ToArray(); APIRoom.ParticipantCount--; callback?.Invoke(user); diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 8fb3901a79..f3f8e87e54 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -137,6 +137,15 @@ namespace osu.Game.Online.Rooms set => SetField(ref participantCount, value); } + /// + /// The set of most recent participants in the room. + /// + public IReadOnlyList RecentParticipants + { + get => recentParticipants; + set => SetList(ref recentParticipants, value); + } + /// /// The match type. /// @@ -282,6 +291,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("participant_count")] private int participantCount; + [JsonProperty("recent_participants")] + private IReadOnlyList recentParticipants = []; + [JsonProperty("max_attempts", DefaultValueHandling = DefaultValueHandling.Ignore)] private int? maxAttempts; @@ -324,10 +336,6 @@ namespace osu.Game.Online.Rooms [JsonProperty("playlist")] public readonly BindableList Playlist = new BindableList(); - [Cached] - [JsonProperty("recent_participants")] - public readonly BindableList RecentParticipants = new BindableList(); - /// /// Copies values from another into this one. /// @@ -369,11 +377,7 @@ namespace osu.Game.Online.Rooms Playlist.AddRange(other.Playlist); } - if (!RecentParticipants.SequenceEqual(other.RecentParticipants)) - { - RecentParticipants.Clear(); - RecentParticipants.AddRange(other.RecentParticipants); - } + RecentParticipants = other.RecentParticipants; } public void RemoveExpiredPlaylistItems() @@ -411,6 +415,16 @@ namespace osu.Game.Online.Rooms protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null!) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + protected bool SetList(ref IReadOnlyList list, IReadOnlyList value, [CallerMemberName] string propertyName = null!) + { + if (list.SequenceEqual(value)) + return false; + + list = value; + OnPropertyChanged(propertyName); + return true; + } + protected bool SetField(ref T field, T value, [CallerMemberName] string propertyName = null!) { if (EqualityComparer.Default.Equals(field, value)) diff --git a/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs index 1ae2756514..5a040dcadb 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.OnlinePlay.Components AddInternal(scroll = new OsuScrollContainer(direction) { - Child = list = new ParticipantsList() + Child = list = new ParticipantsList(room) }); switch (direction) diff --git a/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs index c4aefe4f99..1be23014bd 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs @@ -1,15 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - -using osu.Framework.Allocation; +using System.ComponentModel; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Threading; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Rooms; using osu.Game.Users.Drawables; using osuTK; @@ -57,15 +56,29 @@ namespace osu.Game.Screens.OnlinePlay.Components } } - [BackgroundDependencyLoader] - private void load() + private readonly Room room; + + public ParticipantsList(Room room) { - RecentParticipants.CollectionChanged += (_, _) => updateParticipants(); + this.room = room; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + room.PropertyChanged += onRoomPropertyChanged; updateParticipants(); } - private ScheduledDelegate scheduledUpdate; - private FillFlowContainer tiles; + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.RecentParticipants)) + updateParticipants(); + } + + private ScheduledDelegate? scheduledUpdate; + private FillFlowContainer? tiles; private void updateParticipants() { @@ -83,8 +96,8 @@ namespace osu.Game.Screens.OnlinePlay.Components Spacing = Vector2.One }; - for (int i = 0; i < RecentParticipants.Count; i++) - tiles.Add(new UserTile { User = RecentParticipants[i] }); + for (int i = 0; i < room.RecentParticipants.Count; i++) + tiles.Add(new UserTile { User = room.RecentParticipants[i] }); AddInternal(tiles); @@ -92,9 +105,15 @@ namespace osu.Game.Screens.OnlinePlay.Components }); } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } + private partial class UserTile : CompositeDrawable { - public APIUser User + public APIUser? User { get => avatar.User; set => avatar.User = value; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs index 06ae939842..d989bd6c82 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs @@ -1,9 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Specialized; +using System.Collections.Generic; using System.ComponentModel; -using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -165,12 +164,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { base.LoadComplete(); - RecentParticipants.BindCollectionChanged(onParticipantsChanged, true); - room.PropertyChanged += onRoomPropertyChanged; updateRoomHost(); updateRoomParticipantCount(); + updateRoomParticipants(); } private int numberOfCircles = 4; @@ -190,43 +188,38 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components // Reinitialising the list looks janky, but this is unlikely to be used in a setting where it's visible. clearUsers(); - foreach (var u in RecentParticipants) + foreach (var u in room.RecentParticipants) addUser(u); updateHiddenUsers(); } } - private void onParticipantsChanged(object? sender, NotifyCollectionChangedEventArgs e) + private void updateRoomParticipants() { - switch (e.Action) + HashSet newUsers = room.RecentParticipants.ToHashSet(); + + avatarFlow.RemoveAll(a => { - case NotifyCollectionChangedAction.Add: - Debug.Assert(e.NewItems != null); + // Avatar with no user. Really shouldn't ever be the case but asserting it correctly is difficult. + if (a.User == null) + return false; - foreach (var added in e.NewItems.OfType()) - addUser(added); - break; + // User was previously and still is a participant. Keep them around but remove them from the new set. + // This will be useful when we add all remaining users (now just the new participants) to the flow. + if (newUsers.Contains(a.User)) + { + newUsers.Remove(a.User); + return false; + } - case NotifyCollectionChangedAction.Remove: - Debug.Assert(e.OldItems != null); + // User is no longer a participant. Remove them from the flow. + return true; + }, true); - foreach (var removed in e.OldItems.OfType()) - removeUser(removed); - break; - - case NotifyCollectionChangedAction.Reset: - clearUsers(); - break; - - case NotifyCollectionChangedAction.Replace: - case NotifyCollectionChangedAction.Move: - // Easiest is to just reinitialise the whole list. These are unlikely to ever be use cases. - clearUsers(); - foreach (var u in RecentParticipants) - addUser(u); - break; - } + // Add all remaining users to the flow. + foreach (var u in newUsers) + addUser(u); updateHiddenUsers(); } @@ -239,11 +232,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components avatarFlow.Add(new CircularAvatar { User = user }); } - private void removeUser(APIUser user) - { - avatarFlow.RemoveAll(a => a.User == user, true); - } - private void clearUsers() { avatarFlow.Clear(); @@ -253,7 +241,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private void updateHiddenUsers() { int hiddenCount = 0; - if (RecentParticipants.Count > NumberOfCircles) + if (room.RecentParticipants.Count > NumberOfCircles) hiddenCount = room.ParticipantCount - NumberOfCircles + 1; hiddenUsers.Count = hiddenCount; @@ -262,7 +250,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components avatarFlow.Remove(avatarFlow.Last(), true); else if (displayedCircles < NumberOfCircles) { - var nextUser = RecentParticipants.FirstOrDefault(u => avatarFlow.All(a => a.User != u)); + var nextUser = room.RecentParticipants.FirstOrDefault(u => avatarFlow.All(a => a.User != u)); if (nextUser != null) addUser(nextUser); } } @@ -278,6 +266,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components case nameof(Room.ParticipantCount): updateRoomParticipantCount(); break; + + case nameof(Room.RecentParticipants): + updateRoomParticipants(); + break; } } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 82fc6d535c..ffe36d7609 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay @@ -16,8 +15,5 @@ namespace osu.Game.Screens.OnlinePlay { [Resolved(typeof(Room))] protected BindableList Playlist { get; private set; } = null!; - - [Resolved(typeof(Room))] - protected BindableList RecentParticipants { get; private set; } = null!; } } diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index 0934c4f3dc..37325c2d6b 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -280,7 +280,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay responseRoom.Password = responseRoom.HasPassword ? Guid.NewGuid().ToString() : null; if (!withParticipants) - responseRoom.RecentParticipants.Clear(); + responseRoom.RecentParticipants = []; return responseRoom; } From 34c0f72dd6801da1a45a4e3627a1888f1687cde1 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 14 Nov 2024 17:57:32 +0900 Subject: [PATCH 869/924] Make `Room.Playlist` non-bindable --- .../DailyChallenge/TestSceneDailyChallenge.cs | 12 +- .../TestSceneDailyChallengeIntro.cs | 4 +- .../Visual/Menus/TestSceneMainMenu.cs | 4 +- .../Visual/Multiplayer/QueueModeTestScene.cs | 4 +- .../Multiplayer/TestSceneDrawableRoom.cs | 6 +- .../TestSceneMatchBeatmapDetailArea.cs | 7 +- .../Multiplayer/TestSceneMatchStartControl.cs | 2 +- .../Multiplayer/TestSceneMultiplayer.cs | 110 ++++++++--------- .../TestSceneMultiplayerMatchSubScreen.cs | 114 +++++++++++------- .../TestSceneMultiplayerPlaylist.cs | 6 +- .../TestSceneMultiplayerQueueList.cs | 19 +-- .../TestScenePlaylistsSongSelect.cs | 7 +- .../TestSceneStarRatingRangeDisplay.cs | 6 +- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 16 +-- .../TestScenePlaylistsMatchSettingsOverlay.cs | 10 +- .../TestScenePlaylistsRoomCreation.cs | 58 +++++---- .../UserInterface/TestSceneMainMenuButton.cs | 8 +- .../Online/Multiplayer/MultiplayerClient.cs | 34 +----- osu.Game/Online/Rooms/PlaylistExtensions.cs | 5 +- osu.Game/Online/Rooms/PlaylistItem.cs | 25 ++-- osu.Game/Online/Rooms/Room.cs | 26 ++-- .../OnlinePlay/Components/BeatmapTitle.cs | 34 ++++-- .../Components/MatchBeatmapDetailArea.cs | 39 ++++-- .../Components/OverlinedPlaylistHeader.cs | 24 +++- .../Components/StarRatingRangeDisplay.cs | 15 ++- .../Lounge/Components/PlaylistCountPill.cs | 15 ++- .../Lounge/LoungeBackgroundScreen.cs | 32 ++++- .../Match/MultiplayerMatchSettingsOverlay.cs | 19 ++- .../Match/Playlist/MultiplayerPlaylist.cs | 8 +- .../Match/Playlist/MultiplayerQueueList.cs | 52 +++++--- .../Multiplayer/MultiplayerMatchSongSelect.cs | 5 +- .../Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 8 +- .../OnlinePlay/OnlinePlaySongSelect.cs | 3 - .../Playlists/PlaylistsRoomSettingsOverlay.cs | 19 ++- .../Playlists/PlaylistsRoomSubScreen.cs | 13 +- .../Playlists/PlaylistsSongSelect.cs | 37 ++---- .../Multiplayer/MultiplayerTestScene.cs | 4 +- .../Multiplayer/TestMultiplayerClient.cs | 6 +- .../Visual/OnlinePlay/TestRoomManager.cs | 11 +- .../OnlinePlay/TestRoomRequestsHandler.cs | 22 ++-- 41 files changed, 490 insertions(+), 361 deletions(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs index 1a19c423fc..0742ed5eb9 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs @@ -42,13 +42,13 @@ namespace osu.Game.Tests.Visual.DailyChallenge RoomID = 1234, Name = "Daily Challenge: June 4, 2024", Playlist = - { + [ new PlaylistItem(TestResources.CreateTestBeatmapSetInfo().Beatmaps.First()) { RequiredMods = [new APIMod(new OsuModTraceable())], AllowedMods = [new APIMod(new OsuModDoubleTime())] } - }, + ], EndDate = DateTimeOffset.Now.AddHours(12), Category = RoomCategory.DailyChallenge }; @@ -65,13 +65,13 @@ namespace osu.Game.Tests.Visual.DailyChallenge RoomID = 1234, Name = "Daily Challenge: June 4, 2024", Playlist = - { + [ new PlaylistItem(TestResources.CreateTestBeatmapSetInfo().Beatmaps.First()) { RequiredMods = [new APIMod(new OsuModTraceable())], AllowedMods = [new APIMod(new OsuModDoubleTime())] } - }, + ], EndDate = DateTimeOffset.Now.AddHours(12), Category = RoomCategory.DailyChallenge }; @@ -94,13 +94,13 @@ namespace osu.Game.Tests.Visual.DailyChallenge RoomID = 1234, Name = "Daily Challenge: June 4, 2024", Playlist = - { + [ new PlaylistItem(TestResources.CreateTestBeatmapSetInfo().Beatmaps.First()) { RequiredMods = [new APIMod(new OsuModTraceable())], AllowedMods = [new APIMod(new OsuModDoubleTime())] } - }, + ], EndDate = DateTimeOffset.Now.AddHours(12), Category = RoomCategory.DailyChallenge }; diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs index aa311766d5..b0d600c7d5 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs @@ -71,13 +71,13 @@ namespace osu.Game.Tests.Visual.DailyChallenge RoomID = roomId, Name = "Daily Challenge: June 4, 2024", Playlist = - { + [ new PlaylistItem(CreateAPIBeatmap(new OsuRuleset().RulesetInfo)) { RequiredMods = [new APIMod(new OsuModTraceable())], AllowedMods = [new APIMod(new OsuModDoubleTime())] } - }, + ], StartDate = DateTimeOffset.Now, EndDate = DateTimeOffset.Now.AddHours(24), Category = RoomCategory.DailyChallenge diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs index bc6c3ed709..f3ea20c1aa 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs @@ -45,9 +45,9 @@ namespace osu.Game.Tests.Visual.Menus RoomID = 1234, Name = "Aug 8, 2024", Playlist = - { + [ new PlaylistItem(beatmap) - }, + ], StartDate = DateTimeOffset.Now.AddMinutes(-30), EndDate = DateTimeOffset.Now.AddSeconds(60) }); diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 7dce7daf6d..2b738743ea 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -76,12 +76,12 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Test Room", QueueMode = Mode, Playlist = - { + [ new PlaylistItem(InitialBeatmap) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - } + ] })); AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index d00ff72599..e5938a796c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Status = new RoomStatusOpen(), EndDate = DateTimeOffset.Now.AddDays(1), Type = MatchType.HeadToHead, - Playlist = { item1 }, + Playlist = [item1], CurrentPlaylistItem = item1 }), createLoungeRoom(new Room @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Password = "*", EndDate = DateTimeOffset.Now.AddDays(1), Type = MatchType.HeadToHead, - Playlist = { item3 }, + Playlist = [item3], CurrentPlaylistItem = item3 }), createLoungeRoom(new Room @@ -97,7 +97,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Playlist room with multiple beatmaps", Status = new RoomStatusPlaying(), EndDate = DateTimeOffset.Now.AddDays(1), - Playlist = { item1, item2 }, + Playlist = [item1, item2], CurrentPlaylistItem = item1 }), createLoungeRoom(new Room diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs index 24d1b51ff8..dd39db49c5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Graphics; using osu.Game.Online.API; using osu.Game.Online.Rooms; @@ -23,7 +24,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { SelectedRoom.Value = new Room(); - Child = new MatchBeatmapDetailArea + Child = new MatchBeatmapDetailArea(SelectedRoom.Value) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -35,7 +36,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void createNewItem() { - SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) + SelectedRoom.Value.Playlist = SelectedRoom.Value.Playlist.Append(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) { ID = SelectedRoom.Value.Playlist.Count, RulesetID = new OsuRuleset().RulesetInfo.OnlineID, @@ -45,7 +46,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new APIMod(new OsuModDoubleTime()), new APIMod(new OsuModAutoplay()) } - }); + }).ToArray(); } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs index 4eddac8c7a..6bb5ca254b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs @@ -119,7 +119,7 @@ namespace osu.Game.Tests.Visual.Multiplayer room.Value = new Room { - Playlist = { item }, + Playlist = [item], CurrentPlaylistItem = item }; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index e625d23779..9213a52c0e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -105,12 +105,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Test Room", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - } + ] }); AddRepeatStep("random stuff happens", performRandomAction, 30); @@ -240,12 +240,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Test Room", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - } + ] }); AddUntilStep("Check participant count correct", () => multiplayerClient.ClientAPIRoom?.ParticipantCount == 1); @@ -261,12 +261,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Test Room", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - } + ] }, API.LocalUser.Value); }); @@ -290,12 +290,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Test Room", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - } + ] }, API.LocalUser.Value); }); @@ -320,12 +320,12 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Test Room", Password = "password", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - } + ] }); AddUntilStep("room has password", () => multiplayerClient.ClientAPIRoom?.Password == "password"); @@ -341,12 +341,12 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Test Room", Password = "password", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - } + ] }, API.LocalUser.Value); }); @@ -373,12 +373,12 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Test Room", Password = "password", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - } + ] }); AddStep("change password", () => multiplayerClient.ChangeSettings(password: "password2")); @@ -403,12 +403,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Test Room", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - } + ] }); pressReadyButton(); @@ -431,7 +431,7 @@ namespace osu.Game.Tests.Visual.Multiplayer return new Room { Name = "Test Room", - Playlist = { item } + Playlist = [item] }; }); @@ -472,7 +472,7 @@ namespace osu.Game.Tests.Visual.Multiplayer return new Room { Name = "Test Room", - Playlist = { item } + Playlist = [item] }; }); @@ -513,7 +513,7 @@ namespace osu.Game.Tests.Visual.Multiplayer return new Room { Name = "Test Room", - Playlist = { item } + Playlist = [item] }; }); @@ -550,12 +550,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Test Room", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - } + ] }); AddStep("join other user (ready, host)", () => @@ -583,12 +583,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Test Room", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - } + ] }); AddStep("delete beatmap", () => beatmaps.Delete(importedSet)); @@ -622,12 +622,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Test Room", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - } + ] }); AddStep("disconnect", () => multiplayerClient.Disconnect()); @@ -641,13 +641,13 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Test Room", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID, AllowedMods = new[] { new APIMod(new OsuModHidden()) } } - } + ] }); AddStep("open mod overlay", () => this.ChildrenOfType().Single().TriggerClick()); @@ -681,12 +681,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Test Room", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID, } - } + ] }); enterGameplay(); @@ -726,12 +726,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Test Room", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID, } - } + ] }); enterGameplay(); @@ -756,12 +756,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Test Room", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID, } - } + ] }); pressReadyButton(); @@ -794,12 +794,12 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Test Room", QueueMode = QueueMode.AllPlayers, Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID, } - } + ] }, API.LocalUser.Value); }); @@ -811,11 +811,14 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("change server-side settings", () => { roomManager.ServerSideRooms[0].Name = "New name"; - roomManager.ServerSideRooms[0].Playlist.Add(new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) - { - ID = 2, - RulesetID = new OsuRuleset().RulesetInfo.OnlineID, - }); + roomManager.ServerSideRooms[0].Playlist = + [ + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) + { + ID = 2, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + } + ]; }); AddStep("join room", () => InputManager.Key(Key.Enter)); @@ -825,8 +828,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("local room has correct settings", () => { var localRoom = this.ChildrenOfType().Single().Room; - return localRoom.Name == roomManager.ServerSideRooms[0].Name - && localRoom.Playlist.SequenceEqual(roomManager.ServerSideRooms[0].Playlist); + return localRoom.Name == roomManager.ServerSideRooms[0].Name && localRoom.Playlist.Single().ID == 2; }); } @@ -839,12 +841,12 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Test Room", QueueMode = QueueMode.AllPlayers, Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID, } - } + ] }); AddStep("set spectating state", () => multiplayerClient.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating)); @@ -875,12 +877,12 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Test Room", QueueMode = QueueMode.AllPlayers, Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID, } - } + ] }); AddStep("set spectating state", () => multiplayerClient.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating)); @@ -914,12 +916,12 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Test Room", QueueMode = QueueMode.AllPlayers, Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID, } - } + ] }); enterGameplay(); @@ -945,12 +947,12 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Test Room", QueueMode = QueueMode.AllPlayers, Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID, } - } + ] }); enterGameplay(); @@ -978,12 +980,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Test Room", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - } + ] }); AddStep("join other user and make host", () => @@ -1025,7 +1027,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Test Room", QueueMode = QueueMode.AllPlayers, Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID, @@ -1036,7 +1038,7 @@ namespace osu.Game.Tests.Visual.Multiplayer RulesetID = new TaikoRuleset().RulesetInfo.OnlineID, AllowedMods = new[] { new APIMod { Acronym = "HD" } }, }, - } + ] }); AddStep("select hidden", () => multiplayerClient.ChangeUserMods(new[] { new APIMod { Acronym = "HD" } })); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 0c2a657c5a..ef5ff25194 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -100,10 +100,13 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add playlist item", () => { - SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) - { - RulesetID = new OsuRuleset().RulesetInfo.OnlineID - }); + SelectedRoom.Value.Playlist = + [ + new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID + } + ]; }); ClickButtonWhenEnabled(); @@ -117,11 +120,14 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add playlist item", () => { - SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new TaikoRuleset().RulesetInfo).BeatmapInfo) - { - RulesetID = new TaikoRuleset().RulesetInfo.OnlineID, - AllowedMods = new[] { new APIMod(new TaikoModSwap()) } - }); + SelectedRoom.Value.Playlist = + [ + new PlaylistItem(new TestBeatmap(new TaikoRuleset().RulesetInfo).BeatmapInfo) + { + RulesetID = new TaikoRuleset().RulesetInfo.OnlineID, + AllowedMods = new[] { new APIMod(new TaikoModSwap()) } + } + ]; }); ClickButtonWhenEnabled(); @@ -140,10 +146,13 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("set playlist", () => { - SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) - { - RulesetID = new OsuRuleset().RulesetInfo.OnlineID - }); + SelectedRoom.Value.Playlist = + [ + new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID + } + ]; }); AddAssert("create button enabled", () => this.ChildrenOfType().Single().Enabled.Value); @@ -155,10 +164,13 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("set playlist", () => { - SelectedRoom.Value.Playlist.Add(new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo) - { - RulesetID = new OsuRuleset().RulesetInfo.OnlineID - }); + SelectedRoom.Value.Playlist = + [ + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID + } + ]; }); ClickButtonWhenEnabled(); @@ -184,11 +196,14 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add playlist item with allowed mod", () => { - SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) - { - RulesetID = new OsuRuleset().RulesetInfo.OnlineID, - AllowedMods = new[] { new APIMod(new OsuModDoubleTime()) } - }); + SelectedRoom.Value.Playlist = + [ + new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + AllowedMods = new[] { new APIMod(new OsuModDoubleTime()) } + } + ]; }); ClickButtonWhenEnabled(); @@ -211,11 +226,14 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add playlist item with allowed mod", () => { - SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) - { - RulesetID = new OsuRuleset().RulesetInfo.OnlineID, - AllowedMods = new[] { new APIMod(new OsuModDoubleTime()) } - }); + SelectedRoom.Value.Playlist = + [ + new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + AllowedMods = new[] { new APIMod(new OsuModDoubleTime()) } + } + ]; }); ClickButtonWhenEnabled(); @@ -233,10 +251,13 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add playlist item with no allowed mods", () => { - SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) - { - RulesetID = new OsuRuleset().RulesetInfo.OnlineID, - }); + SelectedRoom.Value.Playlist = + [ + new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + } + ]; }); ClickButtonWhenEnabled(); @@ -254,8 +275,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add two playlist items", () => { - SelectedRoom.Value.Playlist.AddRange(new[] - { + SelectedRoom.Value.Playlist = + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID @@ -264,7 +285,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - }); + ]; }); ClickButtonWhenEnabled(); @@ -291,19 +312,22 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add playlist item", () => { - SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) - { - RulesetID = new OsuRuleset().RulesetInfo.OnlineID, - RequiredMods = new[] + SelectedRoom.Value.Playlist = + [ + new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) { - new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 2.0 } }), - new APIMod(new OsuModStrictTracking()), - }, - AllowedMods = new[] - { - new APIMod(new OsuModFlashlight()), + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + RequiredMods = new[] + { + new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 2.0 } }), + new APIMod(new OsuModStrictTracking()), + }, + AllowedMods = new[] + { + new APIMod(new OsuModFlashlight()), + } } - }); + ]; }); ClickButtonWhenEnabled(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index 92b44d9502..518ea2b511 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create list", () => { - Child = list = new MultiplayerPlaylist + Child = list = new MultiplayerPlaylist(SelectedRoom.Value) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -166,7 +166,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "test name", Playlist = - { + [ new PlaylistItem(new TestBeatmap(Ruleset.Value).BeatmapInfo) { RulesetID = Ruleset.Value.OnlineID @@ -176,7 +176,7 @@ namespace osu.Game.Tests.Visual.Multiplayer RulesetID = Ruleset.Value.OnlineID, Expired = true } - } + ] }); }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index 10625d57aa..50f55ebde0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using NUnit.Framework; @@ -27,10 +25,10 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneMultiplayerQueueList : MultiplayerTestScene { - private MultiplayerQueueList playlist; - private BeatmapManager beatmaps; - private BeatmapSetInfo importedSet; - private BeatmapInfo importedBeatmap; + private MultiplayerQueueList playlist = null!; + private BeatmapManager beatmaps = null!; + private BeatmapSetInfo importedSet = null!; + private BeatmapInfo importedBeatmap = null!; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) @@ -46,12 +44,17 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create playlist", () => { - Child = playlist = new MultiplayerQueueList + Child = playlist = new MultiplayerQueueList(SelectedRoom.Value) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(500, 300), - Items = { BindTarget = MultiplayerClient.ClientAPIRoom!.Playlist } + }; + + MultiplayerClient.ClientAPIRoom!.PropertyChanged += (_, e) => + { + if (e.PropertyName == nameof(Room.Playlist)) + playlist.Items.ReplaceRange(0, playlist.Items.Count, MultiplayerClient.ClientAPIRoom.Playlist); }; }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index cc78bed5de..fea6617d3f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -99,12 +99,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); - AddStep("rearrange", () => - { - var item = SelectedRoom.Value.Playlist[0]; - SelectedRoom.Value.Playlist.RemoveAt(0); - SelectedRoom.Value.Playlist.Add(item); - }); + AddStep("rearrange", () => SelectedRoom.Value.Playlist = SelectedRoom.Value.Playlist.Skip(1).Append(SelectedRoom.Value.Playlist[0]).ToArray()); AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); AddAssert("new item has id 2", () => SelectedRoom.Value.Playlist.Last().ID == 2); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs index 5c11690cd6..195b40bd13 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs @@ -33,11 +33,11 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("set playlist", () => { - SelectedRoom.Value.Playlist.AddRange(new[] - { + SelectedRoom.Value.Playlist = + [ new PlaylistItem(new BeatmapInfo { StarRating = min }), new PlaylistItem(new BeatmapInfo { StarRating = max }), - }); + ]; }); } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index b68089015e..05136ebee1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -65,12 +65,12 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Test Room", Type = MatchType.TeamVersus, Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - } + ] }); AddUntilStep("room type is team vs", () => multiplayerClient.ClientRoom?.Settings.MatchType == MatchType.TeamVersus); @@ -85,12 +85,12 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Test Room", Type = MatchType.TeamVersus, Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - } + ] }); AddUntilStep("user on team 0", () => (multiplayerClient.ClientRoom?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0); @@ -122,12 +122,12 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Test Room", Type = MatchType.HeadToHead, Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID, } - } + ] }); AddUntilStep("match type head to head", () => multiplayerClient.ClientAPIRoom?.Type == MatchType.HeadToHead); @@ -147,12 +147,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Test Room", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID, } - } + ] }); AddUntilStep("room type is head to head", () => multiplayerClient.ClientRoom?.Settings.MatchType == MatchType.HeadToHead); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs index b45d1e68cb..321f563140 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("clear name and beatmap", () => { SelectedRoom.Value.Name = ""; - SelectedRoom.Value.Playlist.Clear(); + SelectedRoom.Value.Playlist = []; }); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("set name", () => SelectedRoom.Value.Name = "Room name"); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); - AddStep("set beatmap", () => SelectedRoom.Value.Playlist.Add(new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo))); + AddStep("set beatmap", () => SelectedRoom.Value.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)]); AddAssert("button enabled", () => settings.ApplyButton.Enabled.Value); AddStep("clear name", () => SelectedRoom.Value.Name = ""); @@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Playlists { settings.NameField.Current.Value = expected_name; settings.DurationField.Current.Value = expectedDuration; - SelectedRoom.Value.Playlist.Add(new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)); + SelectedRoom.Value.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)]; RoomManager.CreateRequested = r => { @@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual.Playlists var beatmap = CreateBeatmap(Ruleset.Value).BeatmapInfo; SelectedRoom.Value.Name = "Test Room"; - SelectedRoom.Value.Playlist.Add(new PlaylistItem(beatmap)); + SelectedRoom.Value.Playlist = [new PlaylistItem(beatmap)]; errorMessage = $"{not_found_prefix} {beatmap.OnlineID}"; @@ -126,7 +126,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("setup", () => { SelectedRoom.Value.Name = "Test Room"; - SelectedRoom.Value.Playlist.Add(new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)); + SelectedRoom.Value.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)]; RoomManager.CreateRequested = _ => failText; }); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index f75b3d7b14..6a53019b76 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -64,10 +64,13 @@ namespace osu.Game.Tests.Visual.Playlists room.Host = API.LocalUser.Value; room.RecentParticipants = [room.Host]; room.EndDate = DateTimeOffset.Now.AddMinutes(5); - room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First()) - { - RulesetID = new OsuRuleset().RulesetInfo.OnlineID - }); + room.Playlist = + [ + new PlaylistItem(importedBeatmap.Beatmaps.First()) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID + } + ]; }); AddUntilStep("Progress details are hidden", () => match.ChildrenOfType().FirstOrDefault()?.Parent!.Alpha == 0); @@ -88,10 +91,13 @@ namespace osu.Game.Tests.Visual.Playlists room.Host = API.LocalUser.Value; room.RecentParticipants = [room.Host]; room.EndDate = DateTimeOffset.Now.AddMinutes(5); - room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First()) - { - RulesetID = new OsuRuleset().RulesetInfo.OnlineID - }); + room.Playlist = + [ + new PlaylistItem(importedBeatmap.Beatmaps.First()) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID + } + ]; }); AddUntilStep("Progress details are visible", () => match.ChildrenOfType().FirstOrDefault()?.Parent!.Alpha == 1); @@ -104,10 +110,13 @@ namespace osu.Game.Tests.Visual.Playlists { room.Name = "my awesome room"; room.Host = API.LocalUser.Value; - room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First()) - { - RulesetID = new OsuRuleset().RulesetInfo.OnlineID - }); + room.Playlist = + [ + new PlaylistItem(importedBeatmap.Beatmaps.First()) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID + } + ]; }); AddAssert("first playlist item selected", () => match.SelectedItem.Value == SelectedRoom.Value.Playlist[0]); @@ -152,19 +161,22 @@ namespace osu.Game.Tests.Visual.Playlists { room.Name = "my awesome room"; room.Host = API.LocalUser.Value; - room.Playlist.Add(new PlaylistItem(new BeatmapInfo - { - MD5Hash = realHash, - OnlineID = realOnlineId, - Metadata = new BeatmapMetadata(), - BeatmapSet = new BeatmapSetInfo + room.Playlist = + [ + new PlaylistItem(new BeatmapInfo { - OnlineID = realOnlineSetId, + MD5Hash = realHash, + OnlineID = realOnlineId, + Metadata = new BeatmapMetadata(), + BeatmapSet = new BeatmapSetInfo + { + OnlineID = realOnlineSetId, + } + }) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - }) - { - RulesetID = new OsuRuleset().RulesetInfo.OnlineID - }); + ]; }); AddAssert("match has default beatmap", () => match.Beatmap.IsDefault); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs index 8b17749afc..923c197c22 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs @@ -55,9 +55,9 @@ namespace osu.Game.Tests.Visual.UserInterface { RoomID = 1234, Playlist = - { + [ new PlaylistItem(beatmap) - }, + ], StartDate = DateTimeOffset.Now.AddMinutes(-5), EndDate = DateTimeOffset.Now.AddSeconds(30) }); @@ -133,9 +133,9 @@ namespace osu.Game.Tests.Visual.UserInterface { RoomID = 1234, Playlist = - { + [ new PlaylistItem(beatmap) - }, + ], StartDate = DateTimeOffset.Now.AddMinutes(-50), EndDate = DateTimeOffset.Now.AddSeconds(30) }); diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 74f89c7a61..998a34931d 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; @@ -201,8 +200,7 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(joinedRoom.Playlist.Count > 0); - APIRoom.Playlist.Clear(); - APIRoom.Playlist.AddRange(joinedRoom.Playlist.Select(item => new PlaylistItem(item))); + APIRoom.Playlist = joinedRoom.Playlist.Select(item => new PlaylistItem(item)).ToArray(); APIRoom.CurrentPlaylistItem = APIRoom.Playlist.Single(item => item.ID == joinedRoom.Settings.PlaylistItemId); // The server will null out the end date upon the host joining the room, but the null value is never communicated to the client. @@ -734,7 +732,7 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(APIRoom != null); Room.Playlist.Add(item); - APIRoom.Playlist.Add(new PlaylistItem(item)); + APIRoom.Playlist = APIRoom.Playlist.Append(new PlaylistItem(item)).ToArray(); ItemAdded?.Invoke(item); RoomUpdated?.Invoke(); @@ -753,7 +751,7 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(APIRoom != null); Room.Playlist.Remove(Room.Playlist.Single(existing => existing.ID == playlistItemId)); - APIRoom.Playlist.RemoveAll(existing => existing.ID == playlistItemId); + APIRoom.Playlist = APIRoom.Playlist.Where(i => i.ID != playlistItemId).ToArray(); Debug.Assert(Room.Playlist.Count > 0); @@ -771,30 +769,10 @@ namespace osu.Game.Online.Multiplayer if (Room == null) return; - try - { - Debug.Assert(APIRoom != null); + Debug.Assert(APIRoom != null); - Room.Playlist[Room.Playlist.IndexOf(Room.Playlist.Single(existing => existing.ID == item.ID))] = item; - - int existingIndex = APIRoom.Playlist.IndexOf(APIRoom.Playlist.Single(existing => existing.ID == item.ID)); - - APIRoom.Playlist.RemoveAt(existingIndex); - APIRoom.Playlist.Insert(existingIndex, new PlaylistItem(item)); - } - catch (Exception ex) - { - // Temporary code to attempt to figure out long-term failing tests. - StringBuilder exceptionText = new StringBuilder(); - - exceptionText.AppendLine("MultiplayerClient test failure investigation"); - exceptionText.AppendLine($"Exception : {ex.ToString()}"); - exceptionText.AppendLine($"Lookup : {item.ID}"); - exceptionText.AppendLine($"Items in Room.Playlist : {string.Join(',', Room.Playlist.Select(i => i.ID))}"); - exceptionText.AppendLine($"Items in APIRoom.Playlist: {string.Join(',', APIRoom!.Playlist.Select(i => i.ID))}"); - - throw new AggregateException(exceptionText.ToString()); - } + Room.Playlist[Room.Playlist.IndexOf(Room.Playlist.Single(existing => existing.ID == item.ID))] = item; + APIRoom.Playlist = APIRoom.Playlist.Select((pi, i) => pi.ID == item.ID ? new PlaylistItem(item) : APIRoom.Playlist[i]).ToArray(); ItemChanged?.Invoke(item); RoomUpdated?.Invoke(); diff --git a/osu.Game/Online/Rooms/PlaylistExtensions.cs b/osu.Game/Online/Rooms/PlaylistExtensions.cs index 8591b5bb47..8afa7d90f8 100644 --- a/osu.Game/Online/Rooms/PlaylistExtensions.cs +++ b/osu.Game/Online/Rooms/PlaylistExtensions.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using Humanizer; using Humanizer.Localisation; -using osu.Framework.Bindables; using osu.Game.Rulesets; using osu.Game.Utils; @@ -30,7 +29,7 @@ namespace osu.Game.Online.Rooms /// or the last-played if all items are expired, /// or if was empty. /// - public static PlaylistItem? GetCurrentItem(this ICollection playlist) + public static PlaylistItem? GetCurrentItem(this IReadOnlyCollection playlist) { if (playlist.Count == 0) return null; @@ -43,7 +42,7 @@ namespace osu.Game.Online.Rooms /// /// Returns the total duration from the in playlist order from the supplied , /// - public static string GetTotalDuration(this BindableList playlist, RulesetStore rulesetStore) => + public static string GetTotalDuration(this IReadOnlyList playlist, RulesetStore rulesetStore) => playlist.Select(p => { double rate = 1; diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs index a900d8f3d7..47d4e163bf 100644 --- a/osu.Game/Online/Rooms/PlaylistItem.cs +++ b/osu.Game/Online/Rooms/PlaylistItem.cs @@ -120,18 +120,21 @@ namespace osu.Game.Online.Rooms #endregion - public PlaylistItem With(Optional beatmap = default, Optional playlistOrder = default) => new PlaylistItem(beatmap.GetOr(Beatmap)) + public PlaylistItem With(Optional id = default, Optional beatmap = default, Optional playlistOrder = default) { - ID = ID, - OwnerID = OwnerID, - RulesetID = RulesetID, - Expired = Expired, - PlaylistOrder = playlistOrder.GetOr(PlaylistOrder), - PlayedAt = PlayedAt, - AllowedMods = AllowedMods, - RequiredMods = RequiredMods, - valid = { Value = Valid.Value }, - }; + return new PlaylistItem(beatmap.GetOr(Beatmap)) + { + ID = id.GetOr(ID), + OwnerID = OwnerID, + RulesetID = RulesetID, + Expired = Expired, + PlaylistOrder = playlistOrder.GetOr(PlaylistOrder), + PlayedAt = PlayedAt, + AllowedMods = AllowedMods, + RequiredMods = RequiredMods, + valid = { Value = Valid.Value }, + }; + } public bool Equals(PlaylistItem? other) => ID == other?.ID diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index f3f8e87e54..deb5808861 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Runtime.CompilerServices; using Newtonsoft.Json; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Game.IO.Serialization.Converters; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; @@ -164,6 +163,15 @@ namespace osu.Game.Online.Rooms set => SetField(ref maxAttempts, value); } + /// + /// The room playlist. + /// + public IReadOnlyList Playlist + { + get => playlist; + set => SetList(ref playlist, value); + } + /// /// Describes the items in the playlist. /// @@ -297,6 +305,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("max_attempts", DefaultValueHandling = DefaultValueHandling.Ignore)] private int? maxAttempts; + [JsonProperty("playlist")] + private IReadOnlyList playlist = []; + [JsonProperty("playlist_item_stats")] private RoomPlaylistItemStats? playlistItemStats; @@ -332,10 +343,6 @@ namespace osu.Game.Online.Rooms // Not yet serialised (not implemented). private RoomAvailability availability; - [Cached] - [JsonProperty("playlist")] - public readonly BindableList Playlist = new BindableList(); - /// /// Copies values from another into this one. /// @@ -371,12 +378,7 @@ namespace osu.Game.Online.Rooms other.RemoveExpiredPlaylistItems(); - if (!Playlist.SequenceEqual(other.Playlist)) - { - Playlist.Clear(); - Playlist.AddRange(other.Playlist); - } - + Playlist = other.Playlist; RecentParticipants = other.RecentParticipants; } @@ -386,7 +388,7 @@ namespace osu.Game.Online.Rooms // and display only the non-expired playlist items while the room is still active. In order to achieve this, all expired items are removed from the source Room. // More refactoring is required before this can be done locally instead - DrawableRoomPlaylist is currently directly bound to the playlist to display items in the room. if (Status is not RoomStatusEnded) - Playlist.RemoveAll(i => i.Expired); + Playlist = Playlist.Where(i => !i.Expired).ToArray(); } [JsonObject(MemberSerialization.OptIn)] diff --git a/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs b/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs index 7c57f5b4f5..01b71539fe 100644 --- a/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs +++ b/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.ComponentModel; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -8,25 +9,31 @@ using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Online.Chat; +using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Components { public partial class BeatmapTitle : OnlinePlayComposite { + private readonly Room room; private readonly LinkFlowContainer textFlow; - public BeatmapTitle() - { - AutoSizeAxes = Axes.Both; + [Resolved] + private OsuColour colours { get; set; } = null!; + public BeatmapTitle(Room room) + { + this.room = room; + + AutoSizeAxes = Axes.Both; InternalChild = textFlow = new LinkFlowContainer { AutoSizeAxes = Axes.Both }; } - [BackgroundDependencyLoader] - private void load() + protected override void LoadComplete() { - Playlist.CollectionChanged += (_, _) => updateText(); + base.LoadComplete(); + room.PropertyChanged += onRoomPropertyChanged; updateText(); } @@ -46,8 +53,11 @@ namespace osu.Game.Screens.OnlinePlay.Components } } - [Resolved] - private OsuColour colours { get; set; } = null!; + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.Playlist)) + updateText(); + } private void updateText() { @@ -56,7 +66,7 @@ namespace osu.Game.Screens.OnlinePlay.Components textFlow.Clear(); - var beatmap = Playlist.FirstOrDefault()?.Beatmap; + var beatmap = room.Playlist.FirstOrDefault()?.Beatmap; if (beatmap == null) { @@ -78,5 +88,11 @@ namespace osu.Game.Screens.OnlinePlay.Components textFlow.AddLink(title, LinkAction.OpenBeatmap, beatmap.OnlineID.ToString(), "Open beatmap"); } } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs index b0ede8d9b5..1f2b2e3fc2 100644 --- a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs +++ b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs @@ -1,12 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; +using System.ComponentModel; using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterfaceV2; @@ -14,23 +11,22 @@ using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.Select; using osuTK; +using Container = osu.Framework.Graphics.Containers.Container; namespace osu.Game.Screens.OnlinePlay.Components { public partial class MatchBeatmapDetailArea : BeatmapDetailArea { - public Action CreateNewItem; - - public readonly Bindable SelectedItem = new Bindable(); - - [Resolved(typeof(Room))] - protected BindableList Playlist { get; private set; } + public Action? CreateNewItem; + private readonly Room room; private readonly GridContainer playlistArea; private readonly DrawableRoomPlaylist playlist; - public MatchBeatmapDetailArea() + public MatchBeatmapDetailArea(Room room) { + this.room = room; + Add(playlistArea = new GridContainer { RelativeSizeAxes = Axes.Both, @@ -72,10 +68,21 @@ namespace osu.Game.Screens.OnlinePlay.Components { base.LoadComplete(); - playlist.Items.BindTo(Playlist); - playlist.SelectedItem.BindTo(SelectedItem); + playlist.Items.BindCollectionChanged((_, __) => room.Playlist = playlist.Items.ToArray()); + + room.PropertyChanged += onRoomPropertyChanged; + updateRoomPlaylist(); } + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.Playlist)) + updateRoomPlaylist(); + } + + private void updateRoomPlaylist() + => playlist.Items.ReplaceRange(0, playlist.Items.Count, room.Playlist); + protected override void OnTabChanged(BeatmapDetailAreaTabItem tab, bool selectedMods) { base.OnTabChanged(tab, selectedMods); @@ -93,5 +100,11 @@ namespace osu.Game.Screens.OnlinePlay.Components } protected override BeatmapDetailAreaTabItem[] CreateTabItems() => base.CreateTabItems().Prepend(new BeatmapDetailAreaPlaylistTabItem()).ToArray(); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Components/OverlinedPlaylistHeader.cs b/osu.Game/Screens/OnlinePlay/Components/OverlinedPlaylistHeader.cs index dd728e460b..55d9f273e9 100644 --- a/osu.Game/Screens/OnlinePlay/Components/OverlinedPlaylistHeader.cs +++ b/osu.Game/Screens/OnlinePlay/Components/OverlinedPlaylistHeader.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.ComponentModel; using osu.Framework.Allocation; using osu.Game.Online.Rooms; using osu.Game.Rulesets; @@ -9,19 +10,38 @@ namespace osu.Game.Screens.OnlinePlay.Components { public partial class OverlinedPlaylistHeader : OverlinedHeader { + private readonly Room room; + [Resolved] private RulesetStore rulesets { get; set; } = null!; - public OverlinedPlaylistHeader() + public OverlinedPlaylistHeader(Room room) : base("Playlist") { + this.room = room; } protected override void LoadComplete() { base.LoadComplete(); - Playlist.BindCollectionChanged((_, _) => Details.Value = Playlist.GetTotalDuration(rulesets), true); + room.PropertyChanged += onRoomPropertyChanged; + updateDuration(); + } + + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.Playlist)) + updateDuration(); + } + + private void updateDuration() + => Details.Value = room.Playlist.GetTotalDuration(rulesets); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; } } } diff --git a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs index 13e3dc6d7c..ff2eac0f30 100644 --- a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs @@ -80,16 +80,19 @@ namespace osu.Game.Screens.OnlinePlay.Components { base.LoadComplete(); - Playlist.BindCollectionChanged((_, _) => updateRange()); - room.PropertyChanged += onRoomPropertyChanged; updateRange(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(Room.DifficultyRange)) - updateRange(); + switch (e.PropertyName) + { + case nameof(Room.Playlist): + case nameof(Room.DifficultyRange): + updateRange(); + break; + } } private void updateRange() @@ -97,7 +100,7 @@ namespace osu.Game.Screens.OnlinePlay.Components StarDifficulty minDifficulty; StarDifficulty maxDifficulty; - if (room.DifficultyRange != null && Playlist.Count == 0) + if (room.DifficultyRange != null && room.Playlist.Count == 0) { // When Playlist is empty (in lounge) we take retrieved range minDifficulty = new StarDifficulty(room.DifficultyRange.Min, 0); @@ -106,7 +109,7 @@ namespace osu.Game.Screens.OnlinePlay.Components else { // When Playlist is not empty (in room) we compute actual range - var orderedDifficulties = Playlist.Select(p => p.Beatmap).OrderBy(b => b.StarRating).ToArray(); + var orderedDifficulties = room.Playlist.Select(p => p.Beatmap).OrderBy(b => b.StarRating).ToArray(); minDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[0].StarRating : 0, 0); maxDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[^1].StarRating : 0, 0); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs index 7e530993dd..70ddf15abf 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs @@ -26,24 +26,27 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { base.LoadComplete(); - Playlist.BindCollectionChanged((_, _) => updateCount()); - room.PropertyChanged += onRoomPropertyChanged; updateCount(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(Room.PlaylistItemStats)) - updateCount(); + switch (e.PropertyName) + { + case nameof(Room.Playlist): + case nameof(Room.PlaylistItemStats): + updateCount(); + break; + } } private void updateCount() { - int activeItems = Playlist.Count > 0 || room.PlaylistItemStats == null + int activeItems = room.Playlist.Count > 0 || room.PlaylistItemStats == null // For now, use the playlist as the source of truth if it has any items. // This allows the count to display correctly on the room screen (after joining a room). - ? Playlist.Count(i => !i.Expired) + ? room.Playlist.Count(i => !i.Expired) : room.PlaylistItemStats.CountActive; TextFlow.Clear(); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeBackgroundScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeBackgroundScreen.cs index b31c351b82..4a3985c386 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeBackgroundScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeBackgroundScreen.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.ComponentModel; using osu.Framework.Bindables; using osu.Framework.Screens; using osu.Game.Online.Rooms; @@ -19,21 +20,44 @@ namespace osu.Game.Screens.OnlinePlay.Lounge playlist.BindCollectionChanged((_, _) => PlaylistItem = playlist.GetCurrentItem()); } + protected override void LoadComplete() + { + base.LoadComplete(); + SelectedRoom.BindValueChanged(onSelectedRoomChanged, true); + } + private void onSelectedRoomChanged(ValueChangedEvent room) { if (room.OldValue != null) - playlist.UnbindFrom(room.OldValue.Playlist); + room.OldValue.PropertyChanged -= onRoomPropertyChanged; if (room.NewValue != null) - playlist.BindTo(room.NewValue.Playlist); - else - playlist.Clear(); + room.NewValue.PropertyChanged += onRoomPropertyChanged; + + updateCurrentItem(); } + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.Playlist)) + updateCurrentItem(); + } + + private void updateCurrentItem() + => PlaylistItem = SelectedRoom.Value?.Playlist.GetCurrentItem(); + public override bool OnExiting(ScreenExitEvent e) { // This screen never exits. return true; } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (SelectedRoom.Value != null) + SelectedRoom.Value.PropertyChanged -= onRoomPropertyChanged; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index f883fde600..ec257a3d1b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -4,6 +4,7 @@ using System; using System.ComponentModel; using System.Diagnostics; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; @@ -277,7 +278,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match drawablePlaylist = new DrawableRoomPlaylist { RelativeSizeAxes = Axes.X, - Height = DrawableRoomPlaylistItem.HEIGHT + Height = DrawableRoomPlaylistItem.HEIGHT, + SelectedItem = { BindTarget = SelectedItem } }, selectBeatmapButton = new RoundedButton { @@ -365,9 +367,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { base.LoadComplete(); - drawablePlaylist.Items.BindTo(Playlist); - drawablePlaylist.SelectedItem.BindTo(SelectedItem); - room.PropertyChanged += onRoomPropertyChanged; updateRoomName(); @@ -377,6 +376,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match updateRoomAutoSkip(); updateRoomMaxParticipants(); updateRoomAutoStartDuration(); + updateRoomPlaylist(); + + drawablePlaylist.Items.BindCollectionChanged((_, __) => room.Playlist = drawablePlaylist.Items.ToArray()); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) @@ -410,6 +412,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match case nameof(Room.AutoStartDuration): updateRoomAutoStartDuration(); break; + + case nameof(Room.Playlist): + updateRoomPlaylist(); + break; } } @@ -434,11 +440,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void updateRoomAutoStartDuration() => typeLabel.Text = room.AutoStartDuration.GetLocalisableDescription(); + private void updateRoomPlaylist() + => drawablePlaylist.Items.ReplaceRange(0, drawablePlaylist.Items.Count, room.Playlist); + protected override void Update() { base.Update(); - ApplyButton.Enabled.Value = Playlist.Count > 0 && NameField.Text.Length > 0 && !operationInProgress.Value; + ApplyButton.Enabled.Value = room.Playlist.Count > 0 && NameField.Text.Length > 0 && !operationInProgress.Value; playlistContainer.Alpha = room.RoomID == null ? 1 : 0; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs index a44891e934..9feee0ae41 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs @@ -33,12 +33,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist [Resolved] private MultiplayerClient client { get; set; } = null!; + private readonly Room room; private readonly BindableWithCurrent selectedItem = new BindableWithCurrent(); private MultiplayerPlaylistTabControl playlistTabControl = null!; private MultiplayerQueueList queueList = null!; private MultiplayerHistoryList historyList = null!; private bool firstPopulation = true; + public MultiplayerPlaylist(Room room) + { + this.room = room; + } + [BackgroundDependencyLoader] private void load() { @@ -59,7 +65,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist Masking = true, Children = new Drawable[] { - queueList = new MultiplayerQueueList + queueList = new MultiplayerQueueList(room) { RelativeSizeAxes = Axes.Both, SelectedItem = { BindTarget = selectedItem }, diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs index 77d82c4347..04bb9b69e6 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs @@ -1,12 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Online.API; @@ -21,28 +20,49 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist /// public partial class MultiplayerQueueList : DrawableRoomPlaylist { - public MultiplayerQueueList() + private readonly Room room; + + private QueueFillFlowContainer flow = null!; + + public MultiplayerQueueList(Room room) { + this.room = room; ShowItemOwners = true; } - protected override FillFlowContainer> CreateListFillFlowContainer() => new QueueFillFlowContainer + protected override void LoadComplete() + { + base.LoadComplete(); + + room.PropertyChanged += onRoomPropertyChanged; + updateRoomPlaylist(); + } + + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.Playlist)) + updateRoomPlaylist(); + } + + private void updateRoomPlaylist() + => flow.InvalidateLayout(); + + protected override FillFlowContainer> CreateListFillFlowContainer() => flow = new QueueFillFlowContainer { Spacing = new Vector2(0, 2) }; protected override DrawableRoomPlaylistItem CreateDrawablePlaylistItem(PlaylistItem item) => new QueuePlaylistItem(item); + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } + private partial class QueueFillFlowContainer : FillFlowContainer> { - [Resolved(typeof(Room), nameof(Room.Playlist))] - private BindableList roomPlaylist { get; set; } - - protected override void LoadComplete() - { - base.LoadComplete(); - roomPlaylist.BindCollectionChanged((_, _) => InvalidateLayout()); - } + public new void InvalidateLayout() => base.InvalidateLayout(); public override IEnumerable FlowingChildren => base.FlowingChildren.OfType>().OrderBy(item => item.Model.PlaylistOrder); } @@ -50,10 +70,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist private partial class QueuePlaylistItem : DrawableRoomPlaylistItem { [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; [Resolved] - private MultiplayerClient multiplayerClient { get; set; } + private MultiplayerClient multiplayerClient { get; set; } = null!; public QueuePlaylistItem(PlaylistItem item) : base(item) @@ -91,7 +111,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist { base.Dispose(isDisposing); - if (multiplayerClient != null) + if (multiplayerClient.IsNotNull()) multiplayerClient.RoomUpdated -= onRoomUpdated; } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 873a1b0d50..4e03c19095 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -24,6 +24,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved] private OngoingOperationTracker operationTracker { get; set; } = null!; + private readonly Room room; private readonly IBindable operationInProgress = new Bindable(); private readonly PlaylistItem? itemToEdit; @@ -38,6 +39,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public MultiplayerMatchSongSelect(Room room, PlaylistItem? itemToEdit = null) : base(room, itemToEdit) { + this.room = room; this.itemToEdit = itemToEdit; } @@ -111,8 +113,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } else { - Playlist.Clear(); - Playlist.Add(item); + room.Playlist = [item]; this.Exit(); } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 8f2f3aa678..65355f02e5 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -138,7 +138,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer null, new Drawable[] { - new MultiplayerPlaylist + new MultiplayerPlaylist(Room) { RelativeSizeAxes = Axes.Both, RequestEdit = OpenSongSelection, diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index ffe36d7609..3b0e974a90 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -1,8 +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 osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Online.Rooms; @@ -11,9 +9,5 @@ namespace osu.Game.Screens.OnlinePlay /// /// A that exposes bindables for properties. /// - public partial class OnlinePlayComposite : CompositeDrawable - { - [Resolved(typeof(Room))] - protected BindableList Playlist { get; private set; } = null!; - } + public partial class OnlinePlayComposite : CompositeDrawable; } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index a8dfece916..f6b6dfd3ab 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -32,9 +32,6 @@ namespace osu.Game.Screens.OnlinePlay public override bool AllowEditing => false; - [Resolved(typeof(Room), nameof(Room.Playlist))] - protected BindableList Playlist { get; private set; } = null!; - [Resolved] private RulesetStore rulesets { get; set; } = null!; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index 6550b4c2f1..468c284d95 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -329,9 +329,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists localUser = api.LocalUser.GetBoundCopy(); localUser.BindValueChanged(populateDurations, true); - - playlist.Items.BindTo(Playlist); - Playlist.BindCollectionChanged(onPlaylistChanged, true); } protected override void LoadComplete() @@ -345,6 +342,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists updateRoomMaxParticipants(); updateRoomDuration(); updateRoomMaxAttempts(); + updateRoomPlaylist(); + + playlist.Items.BindCollectionChanged((_, __) => room.Playlist = playlist.Items.ToArray()); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) @@ -370,6 +370,10 @@ namespace osu.Game.Screens.OnlinePlay.Playlists case nameof(Room.MaxAttempts): updateRoomMaxAttempts(); break; + + case nameof(Room.Playlist): + updateRoomPlaylist(); + break; } } @@ -388,6 +392,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private void updateRoomMaxAttempts() => MaxAttemptsField.Text = room.MaxAttempts?.ToString(); + private void updateRoomPlaylist() + => playlist.Items.ReplaceRange(0, playlist.Items.Count, room.Playlist); + private void populateDurations(ValueChangedEvent user) { // roughly correct (see https://github.com/Humanizr/Humanizer/blob/18167e56c082449cc4fe805b8429e3127a7b7f93/readme.md?plain=1#L427) @@ -421,9 +428,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists public void SelectBeatmap() => editPlaylistButton.TriggerClick(); private void onPlaylistChanged(object? sender, NotifyCollectionChangedEventArgs e) => - playlistLength.Text = $"Length: {Playlist.GetTotalDuration(rulesets)}"; + playlistLength.Text = $"Length: {room.Playlist.GetTotalDuration(rulesets)}"; - private bool hasValidSettings => room.RoomID == null && NameField.Text.Length > 0 && Playlist.Count > 0 + private bool hasValidSettings => room.RoomID == null && NameField.Text.Length > 0 && room.Playlist.Count > 0 && hasValidDuration; private bool hasValidDuration => DurationField.Current.Value <= TimeSpan.FromDays(14) || localUser.Value.IsSupporter; @@ -464,7 +471,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists .Select(int.Parse) .ToArray(); - foreach (var item in Playlist) + foreach (var item in room.Playlist) { if (invalidBeatmapIDs.Contains(item.Beatmap.OnlineID)) item.MarkInvalid(); diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 079e3b03b7..c82317add4 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -38,6 +38,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private MatchLeaderboard leaderboard = null!; private SelectionPollingComponent selectionPollingComponent = null!; private FillFlowContainer progressSection = null!; + private DrawableRoomPlaylist drawablePlaylist = null!; public PlaylistsRoomSubScreen(Room room) : base(room, false) // Editing is temporarily not allowed. @@ -77,6 +78,10 @@ namespace osu.Game.Screens.OnlinePlay.Playlists case nameof(Room.MaxAttempts): updateRoomMaxAttempts(); break; + + case nameof(Room.Playlist): + updateRoomPlaylist(); + break; } } @@ -93,6 +98,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private void updateRoomMaxAttempts() => progressSection.Alpha = Room.MaxAttempts != null ? 1 : 0; + private void updateRoomPlaylist() + => drawablePlaylist.Items.ReplaceRange(0, drawablePlaylist.Items.Count, Room.Playlist); + protected override Drawable CreateMainContent() => new Container { RelativeSizeAxes = Axes.Both, @@ -122,13 +130,12 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Padding = new MarginPadding { Right = 5 }, Content = new[] { - new Drawable[] { new OverlinedPlaylistHeader(), }, + new Drawable[] { new OverlinedPlaylistHeader(Room), }, new Drawable[] { - new DrawableRoomPlaylist + drawablePlaylist = new DrawableRoomPlaylist { RelativeSizeAxes = Axes.Both, - Items = { BindTarget = Room.Playlist }, SelectedItem = { BindTarget = SelectedItem }, AllowSelection = true, AllowShowingResults = true, diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs index cedea4af70..23824b6a73 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs @@ -12,45 +12,34 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { public partial class PlaylistsSongSelect : OnlinePlaySongSelect { + private readonly Room room; + public PlaylistsSongSelect(Room room) : base(room) { + this.room = room; } - protected override BeatmapDetailArea CreateBeatmapDetailArea() => new MatchBeatmapDetailArea + protected override BeatmapDetailArea CreateBeatmapDetailArea() => new MatchBeatmapDetailArea(room) { - CreateNewItem = createNewItem + CreateNewItem = () => room.Playlist = room.Playlist.Append(createNewItem()).ToArray() }; protected override bool SelectItem(PlaylistItem item) { - switch (Playlist.Count) - { - case 0: - createNewItem(); - break; - - case 1: - Playlist.Clear(); - createNewItem(); - break; - } + if (room.Playlist.Count <= 1) + room.Playlist = [createNewItem()]; this.Exit(); return true; } - private void createNewItem() + private PlaylistItem createNewItem() => new PlaylistItem(Beatmap.Value.BeatmapInfo) { - PlaylistItem item = new PlaylistItem(Beatmap.Value.BeatmapInfo) - { - ID = Playlist.Count == 0 ? 0 : Playlist.Max(p => p.ID) + 1, - RulesetID = Ruleset.Value.OnlineID, - RequiredMods = Mods.Value.Select(m => new APIMod(m)).ToArray(), - AllowedMods = FreeMods.Value.Select(m => new APIMod(m)).ToArray() - }; - - Playlist.Add(item); - } + ID = room.Playlist.Count == 0 ? 0 : room.Playlist.Max(p => p.ID) + 1, + RulesetID = Ruleset.Value.OnlineID, + RequiredMods = Mods.Value.Select(m => new APIMod(m)).ToArray(), + AllowedMods = FreeMods.Value.Select(m => new APIMod(m)).ToArray() + }; } } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index 774b1d5931..42cf317829 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -38,12 +38,12 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "test name", Type = MatchType.HeadToHead, Playlist = - { + [ new PlaylistItem(new TestBeatmap(Ruleset.Value).BeatmapInfo) { RulesetID = Ruleset.Value.OnlineID } - } + ] }; } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 29e62cb590..4d812abf11 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -447,7 +447,7 @@ namespace osu.Game.Tests.Visual.Multiplayer item.PlaylistOrder = existingItem.PlaylistOrder; ServerRoom.Playlist[ServerRoom.Playlist.IndexOf(existingItem)] = item; - ServerAPIRoom.Playlist[ServerAPIRoom.Playlist.IndexOf(ServerAPIRoom.Playlist.Single(i => i.ID == item.ID))] = new PlaylistItem(item); + ServerAPIRoom.Playlist = ServerAPIRoom.Playlist.Select((pi, i) => pi.ID == item.ID ? new PlaylistItem(item) : ServerAPIRoom.Playlist[i]).ToArray(); await ((IMultiplayerClient)this).PlaylistItemChanged(clone(item)).ConfigureAwait(false); } @@ -474,7 +474,7 @@ namespace osu.Game.Tests.Visual.Multiplayer throw new InvalidOperationException("Attempted to remove an item which has already been played."); ServerRoom.Playlist.Remove(item); - ServerAPIRoom.Playlist.RemoveAll(i => i.ID == item.ID); + ServerAPIRoom.Playlist = ServerAPIRoom.Playlist.Where(i => i.ID != item.ID).ToArray(); await ((IMultiplayerClient)this).PlaylistItemRemoved(clone(playlistItemId)).ConfigureAwait(false); await updateCurrentItem(ServerRoom).ConfigureAwait(false); @@ -569,7 +569,7 @@ namespace osu.Game.Tests.Visual.Multiplayer item.ID = ++lastPlaylistItemId; ServerRoom.Playlist.Add(item); - ServerAPIRoom.Playlist.Add(new PlaylistItem(item)); + ServerAPIRoom.Playlist = ServerAPIRoom.Playlist.Append(new PlaylistItem(item)).ToArray(); await ((IMultiplayerClient)this).PlaylistItemAdded(clone(item)).ConfigureAwait(false); await updatePlaylistOrder(ServerRoom).ConfigureAwait(false); diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs index a384a14441..e813b49844 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs @@ -48,10 +48,13 @@ namespace osu.Game.Tests.Visual.OnlinePlay RulesetIDs = new[] { ruleset.OnlineID }, }; - room.Playlist.Add(new PlaylistItem(new BeatmapInfo { Metadata = new BeatmapMetadata() }) - { - RulesetID = ruleset.OnlineID, - }); + room.Playlist = + [ + new PlaylistItem(new BeatmapInfo { Metadata = new BeatmapMetadata() }) + { + RulesetID = ruleset.OnlineID, + } + ]; } CreateRoom(room); diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index 37325c2d6b..224bb90c8c 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -290,19 +290,17 @@ namespace osu.Game.Tests.Visual.OnlinePlay var result = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(source)); Debug.Assert(result != null); - // Playlist item IDs and beatmaps aren't serialised. - if (source.CurrentPlaylistItem != null) - { - Debug.Assert(result.CurrentPlaylistItem != null); - result.CurrentPlaylistItem = result.CurrentPlaylistItem.With(new Optional(source.CurrentPlaylistItem.Beatmap)); - result.CurrentPlaylistItem.ID = source.CurrentPlaylistItem.ID; - } + // When serialising, only beatmap IDs are sent to the server. + // When deserialising, full beatmaps and IDs are expected to arrive. - for (int i = 0; i < source.Playlist.Count; i++) - { - result.Playlist[i] = result.Playlist[i].With(new Optional(source.Playlist[i].Beatmap)); - result.Playlist[i].ID = source.Playlist[i].ID; - } + PlaylistItem? finalCurrentItem = result.CurrentPlaylistItem?.With(id: source.CurrentPlaylistItem!.ID, beatmap: new Optional(source.CurrentPlaylistItem.Beatmap)); + PlaylistItem[] finalPlaylist = result.Playlist.Select((pi, i) => pi.With(id: source.Playlist[i].ID, beatmap: new Optional(source.Playlist[i].Beatmap))).ToArray(); + + // When setting the properties, we do a clear-then-add, otherwise equality comparers (that only compare by ID) pass early and members don't get replaced. + result.CurrentPlaylistItem = null; + result.CurrentPlaylistItem = finalCurrentItem; + result.Playlist = []; + result.Playlist = finalPlaylist; return result; } From bfbae9458a629e0afa2cec4784fb367883019e45 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 15 Nov 2024 13:20:09 +0900 Subject: [PATCH 870/924] Remove `OnlinePlayComposite` --- .../Screens/OnlinePlay/Components/BeatmapTitle.cs | 3 ++- .../OnlinePlay/Components/OverlinedHeader.cs | 2 +- .../Components/ParticipantCountDisplay.cs | 2 +- .../OnlinePlay/Components/ParticipantsDisplay.cs | 3 ++- .../OnlinePlay/Components/ParticipantsList.cs | 2 +- .../OnlinePlay/Components/RoomLocalUserInfo.cs | 2 +- .../OnlinePlay/Components/StarRatingRangeDisplay.cs | 2 +- .../DailyChallengeTimeRemainingRing.cs | 2 +- .../OnlinePlay/Lounge/Components/DrawableRoom.cs | 4 ++-- .../Components/DrawableRoomParticipantsList.cs | 2 +- .../OnlinePlay/Lounge/Components/EndDateInfo.cs | 3 ++- .../OnlinePlay/Lounge/Components/OnlinePlayPill.cs | 3 ++- .../Match/Components/RoomSettingsOverlay.cs | 4 ++-- .../Match/MultiplayerMatchSettingsOverlay.cs | 4 ++-- osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs | 13 ------------- .../Playlists/PlaylistsRoomSettingsOverlay.cs | 4 ++-- 16 files changed, 23 insertions(+), 32 deletions(-) delete mode 100644 osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs diff --git a/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs b/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs index 01b71539fe..5c8ac5ce73 100644 --- a/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs +++ b/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs @@ -5,6 +5,7 @@ using System.ComponentModel; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -13,7 +14,7 @@ using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Components { - public partial class BeatmapTitle : OnlinePlayComposite + public partial class BeatmapTitle : CompositeDrawable { private readonly Room room; private readonly LinkFlowContainer textFlow; diff --git a/osu.Game/Screens/OnlinePlay/Components/OverlinedHeader.cs b/osu.Game/Screens/OnlinePlay/Components/OverlinedHeader.cs index 09a3602cdd..d9cdcac7d7 100644 --- a/osu.Game/Screens/OnlinePlay/Components/OverlinedHeader.cs +++ b/osu.Game/Screens/OnlinePlay/Components/OverlinedHeader.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.OnlinePlay.Components /// /// A header used in the multiplayer interface which shows text / details beneath a line. /// - public partial class OverlinedHeader : OnlinePlayComposite + public partial class OverlinedHeader : CompositeDrawable { private bool showLine = true; diff --git a/osu.Game/Screens/OnlinePlay/Components/ParticipantCountDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/ParticipantCountDisplay.cs index f9744717d5..db9cf3f92d 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ParticipantCountDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ParticipantCountDisplay.cs @@ -11,7 +11,7 @@ using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Components { - public partial class ParticipantCountDisplay : OnlinePlayComposite + public partial class ParticipantCountDisplay : CompositeDrawable { private const float text_size = 30; private const float transition_duration = 100; diff --git a/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs index 5a040dcadb..a12d843b0a 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs @@ -4,12 +4,13 @@ using System.ComponentModel; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Components { - public partial class ParticipantsDisplay : OnlinePlayComposite + public partial class ParticipantsDisplay : CompositeDrawable { public readonly Bindable Details = new Bindable(); diff --git a/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs index 1be23014bd..79084a5285 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs @@ -14,7 +14,7 @@ using osuTK; namespace osu.Game.Screens.OnlinePlay.Components { - public partial class ParticipantsList : OnlinePlayComposite + public partial class ParticipantsList : CompositeDrawable { public const float TILE_SIZE = 35; diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs b/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs index b3e60d4318..39b5edbd26 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs @@ -12,7 +12,7 @@ using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Components { - public partial class RoomLocalUserInfo : OnlinePlayComposite + public partial class RoomLocalUserInfo : CompositeDrawable { private readonly Room room; private OsuSpriteText attemptDisplay = null!; diff --git a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs index ff2eac0f30..2bdb41ce12 100644 --- a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs @@ -18,7 +18,7 @@ using Container = osu.Framework.Graphics.Containers.Container; namespace osu.Game.Screens.OnlinePlay.Components { - public partial class StarRatingRangeDisplay : OnlinePlayComposite + public partial class StarRatingRangeDisplay : CompositeDrawable { private readonly Room room; diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTimeRemainingRing.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTimeRemainingRing.cs index bffbb664a3..bf01ee6b52 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTimeRemainingRing.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTimeRemainingRing.cs @@ -17,7 +17,7 @@ using osuTK; namespace osu.Game.Screens.OnlinePlay.DailyChallenge { - public partial class DailyChallengeTimeRemainingRing : OnlinePlayComposite + public partial class DailyChallengeTimeRemainingRing : CompositeDrawable { private readonly Room room; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index c368666ac2..2a9bb93d94 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -346,7 +346,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components if (Room.Type != MatchType.Playlists) { - pills.AddRange(new OnlinePlayComposite[] + pills.AddRange(new Drawable[] { new MatchTypePill(Room), new QueueModePill(Room), @@ -377,7 +377,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components Room.PropertyChanged -= onRoomPropertyChanged; } - private partial class RoomStatusText : OnlinePlayComposite + private partial class RoomStatusText : CompositeDrawable { public readonly IBindable SelectedItem = new Bindable(); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs index d989bd6c82..5bcc974c26 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs @@ -21,7 +21,7 @@ using Container = osu.Framework.Graphics.Containers.Container; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { - public partial class DrawableRoomParticipantsList : OnlinePlayComposite + public partial class DrawableRoomParticipantsList : CompositeDrawable { public const float SHEAR_WIDTH = 12f; private const float avatar_size = 36; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/EndDateInfo.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/EndDateInfo.cs index 18c7972c06..3b03ce61f1 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/EndDateInfo.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/EndDateInfo.cs @@ -5,12 +5,13 @@ using System; using System.ComponentModel; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { - public partial class EndDateInfo : OnlinePlayComposite + public partial class EndDateInfo : CompositeDrawable { private readonly Room room; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs index 3e6d7a2e54..c65a5e2469 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs @@ -3,13 +3,14 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Containers; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { - public abstract partial class OnlinePlayPill : OnlinePlayComposite + public abstract partial class OnlinePlayPill : CompositeDrawable { protected PillContainer Pill { get; private set; } = null!; protected OsuTextFlowContainer TextFlow { get; private set; } = null!; diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs index 14d506a368..09aed176c4 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components protected const float TRANSITION_DURATION = 350; protected const float FIELD_PADDING = 25; - protected OnlinePlayComposite Settings { get; set; } = null!; + protected Drawable Settings { get; set; } = null!; protected override bool BlockScrollInput => false; @@ -48,7 +48,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components protected abstract void SelectBeatmap(); - protected abstract OnlinePlayComposite CreateSettings(Room room); + protected abstract Drawable CreateSettings(Room room); protected override void PopIn() { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index ec257a3d1b..1dbef079d4 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match protected override void SelectBeatmap() => settings.SelectBeatmap(); - protected override OnlinePlayComposite CreateSettings(Room room) => settings = new MatchSettings(room) + protected override Drawable CreateSettings(Room room) => settings = new MatchSettings(room) { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Y, @@ -59,7 +59,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match SelectedItem = { BindTarget = SelectedItem } }; - protected partial class MatchSettings : OnlinePlayComposite + protected partial class MatchSettings : CompositeDrawable { private const float disabled_alpha = 0.2f; diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs deleted file mode 100644 index 3b0e974a90..0000000000 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ /dev/null @@ -1,13 +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.Containers; -using osu.Game.Online.Rooms; - -namespace osu.Game.Screens.OnlinePlay -{ - /// - /// A that exposes bindables for properties. - /// - public partial class OnlinePlayComposite : CompositeDrawable; -} diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index 468c284d95..88af161cc8 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -47,14 +47,14 @@ namespace osu.Game.Screens.OnlinePlay.Playlists protected override void SelectBeatmap() => settings.SelectBeatmap(); - protected override OnlinePlayComposite CreateSettings(Room room) => settings = new MatchSettings(room) + protected override Drawable CreateSettings(Room room) => settings = new MatchSettings(room) { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Y, EditPlaylist = () => EditPlaylist?.Invoke() }; - protected partial class MatchSettings : OnlinePlayComposite + protected partial class MatchSettings : CompositeDrawable { private const float disabled_alpha = 0.2f; From 1a656d0ec353897852630bdff3839a8c2105c09d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 15 Nov 2024 13:22:03 +0900 Subject: [PATCH 871/924] Remove `CachedModelDependencyContainer` usages from online play --- .../DailyChallenge/TestSceneDailyChallengeCarousel.cs | 5 ----- .../TestSceneDailyChallengeTimeRemainingRing.cs | 5 ----- .../Visual/Multiplayer/TestSceneMatchStartControl.cs | 3 --- .../Visual/Multiplayer/TestSceneRankRangePill.cs | 5 ----- .../Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 8 -------- .../Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs | 8 -------- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 8 -------- .../Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs | 2 +- 8 files changed, 1 insertion(+), 43 deletions(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeCarousel.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeCarousel.cs index 5854c2e793..b9470f3be4 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeCarousel.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeCarousel.cs @@ -26,11 +26,6 @@ namespace osu.Game.Tests.Visual.DailyChallenge private readonly Bindable room = new Bindable(new Room()); - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => new CachedModelDependencyContainer(base.CreateChildDependencies(parent)) - { - Model = { BindTarget = room } - }; - [Test] public void TestBasicAppearance() { diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTimeRemainingRing.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTimeRemainingRing.cs index 2f522d4d6f..eebbd82190 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTimeRemainingRing.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTimeRemainingRing.cs @@ -18,11 +18,6 @@ namespace osu.Game.Tests.Visual.DailyChallenge { private readonly Bindable room = new Bindable(new Room()); - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => new CachedModelDependencyContainer(base.CreateChildDependencies(parent)) - { - Model = { BindTarget = room } - }; - [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs index 6bb5ca254b..fb9c801fb4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs @@ -43,9 +43,6 @@ namespace osu.Game.Tests.Visual.Multiplayer private OsuButton readyButton => control.ChildrenOfType().Single(); - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => - new CachedModelDependencyContainer(base.CreateChildDependencies(parent)) { Model = { BindTarget = room } }; - [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneRankRangePill.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneRankRangePill.cs index d5f53bc354..9420ddf807 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneRankRangePill.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneRankRangePill.cs @@ -9,7 +9,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; -using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Lounge.Components; namespace osu.Game.Tests.Visual.Multiplayer @@ -18,10 +17,6 @@ namespace osu.Game.Tests.Visual.Multiplayer { private readonly Mock multiplayerClient = new Mock(); - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => - // not used directly in component, but required due to it inheriting from OnlinePlayComposite. - new CachedModelDependencyContainer(base.CreateChildDependencies(parent)); - [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 9559826dab..0dc7e7930a 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -119,14 +119,6 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge Padding = new MarginPadding { Horizontal = -HORIZONTAL_OVERFLOW_PADDING }; } - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - return new CachedModelDependencyContainer(base.CreateChildDependencies(parent)) - { - Model = { Value = room } - }; - } - [BackgroundDependencyLoader] private void load(AudioManager audio) { diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 2a9bb93d94..c39ca347c7 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -316,14 +316,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components passwordIcon.Alpha = Room.HasPassword ? 1 : 0; } - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - return new CachedModelDependencyContainer(base.CreateChildDependencies(parent)) - { - Model = { Value = Room } - }; - } - private int numberOfAvatars = 7; public int NumberOfAvatars diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 1b7df9c213..4461cf5402 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -287,14 +287,6 @@ namespace osu.Game.Screens.OnlinePlay.Match } } - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - return new CachedModelDependencyContainer(base.CreateChildDependencies(parent)) - { - Model = { Value = Room } - }; - } - protected virtual bool IsConnected => api.State.Value == APIState.Online; public override bool OnBackButton() diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs index 64bd27b871..06f8cc40b9 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay UserLookupCache = new TestUserLookupCache(); BeatmapLookupCache = new BeatmapLookupCache(); - dependencies = new DependencyContainer(new CachedModelDependencyContainer(null) { Model = { BindTarget = SelectedRoom } }); + dependencies = new DependencyContainer(); CacheAs(RequestsHandler); CacheAs(SelectedRoom); From db7def9d344be36176a8b7901ed3171b79731b0e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 15 Nov 2024 16:05:06 +0900 Subject: [PATCH 872/924] Remove `IDependencyInjectionCandidate` interface from `Room` --- osu.Game/Online/Rooms/Room.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index deb5808861..a26a3fcb34 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -7,7 +7,6 @@ using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using Newtonsoft.Json; -using osu.Framework.Allocation; using osu.Game.IO.Serialization.Converters; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; @@ -16,7 +15,7 @@ using osu.Game.Online.Rooms.RoomStatuses; namespace osu.Game.Online.Rooms { [JsonObject(MemberSerialization.OptIn)] - public partial class Room : IDependencyInjectionCandidate, INotifyPropertyChanged + public partial class Room : INotifyPropertyChanged { public event PropertyChangedEventHandler? PropertyChanged; From 4c147327dfc2555cb21d33e68a170d68213a69b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 15 Nov 2024 10:19:34 +0100 Subject: [PATCH 873/924] Fix code quality --- .../Edit/Blueprints/Components/EditBodyPiece.cs | 3 --- .../Edit/Blueprints/Components/EditNotePiece.cs | 1 - .../Edit/Blueprints/HoldNoteSelectionBlueprint.cs | 2 -- 3 files changed, 6 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs index 652baccaa6..5cfcf00b33 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs @@ -6,9 +6,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osu.Game.Graphics.Backgrounds; -using osu.Game.Rulesets.Mania.Skinning.Default; -using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components { diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs index 1f9f26b5e2..f68004db28 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs @@ -9,7 +9,6 @@ using osu.Game.Graphics; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components { diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index 8c8d58654e..c0d2be229f 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -4,9 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; -using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; From 0c4c9bd2376df86af8168658f44f4412810f5cdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 15 Nov 2024 10:27:07 +0100 Subject: [PATCH 874/924] Fix incorrect selection hold note blueprint display when upscroll active --- .../Edit/Blueprints/HoldNoteSelectionBlueprint.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index c0d2be229f..aa21d53f8b 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -30,6 +30,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints [Resolved] private IPositionSnapProvider? positionSnapProvider { get; set; } + private EditBodyPiece body = null!; private EditHoldNoteEndPiece head = null!; private EditHoldNoteEndPiece tail = null!; @@ -45,7 +46,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { InternalChildren = new Drawable[] { - new EditBodyPiece + body = new EditBodyPiece { RelativeSizeAxes = Axes.Both, Anchor = Anchor.BottomCentre, @@ -113,7 +114,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints foreach (var child in InternalChildren) child.Anchor = Origin; - head.Scale = tail.Scale = new Vector2(1, direction.NewValue == ScrollingDirection.Down ? 1 : -1); + head.Scale = tail.Scale = body.Scale = new Vector2(1, direction.NewValue == ScrollingDirection.Down ? 1 : -1); } public override Quad SelectionQuad => ScreenSpaceDrawQuad; From e5480f8346bc9170b79618db5cfb809f6de808bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 15 Nov 2024 10:37:56 +0100 Subject: [PATCH 875/924] Adjust appearance of placement blueprints to be closer to previous --- .../Blueprints/HoldNotePlacementBlueprint.cs | 27 +++++++++++++++---- .../Edit/Blueprints/NotePlacementBlueprint.cs | 21 ++++++++++++--- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs index 991b7f476c..13cfc5f691 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs @@ -4,8 +4,10 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Utils; +using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; @@ -17,9 +19,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { public partial class HoldNotePlacementBlueprint : ManiaPlacementBlueprint { - private readonly EditBodyPiece bodyPiece; - private readonly EditNotePiece headPiece; - private readonly EditNotePiece tailPiece; + private EditBodyPiece bodyPiece = null!; + private Circle headPiece = null!; + private Circle tailPiece = null!; [Resolved] private IScrollingInfo scrollingInfo { get; set; } = null!; @@ -28,14 +30,29 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints public HoldNotePlacementBlueprint() : base(new HoldNote()) + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) { RelativeSizeAxes = Axes.Both; InternalChildren = new Drawable[] { bodyPiece = new EditBodyPiece { Origin = Anchor.TopCentre }, - headPiece = new EditNotePiece { Origin = Anchor.Centre }, - tailPiece = new EditNotePiece { Origin = Anchor.Centre } + headPiece = new Circle + { + Origin = Anchor.Centre, + Colour = colours.Yellow, + Height = 10 + }, + tailPiece = new Circle + { + Origin = Anchor.Centre, + Colour = colours.Yellow, + Height = 10 + }, }; } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs index b3ec3ef3e4..422215db57 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs @@ -1,10 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Game.Graphics; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; using osuTK.Input; @@ -12,14 +14,25 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { public partial class NotePlacementBlueprint : ManiaPlacementBlueprint { - private readonly EditNotePiece piece; + private Circle piece = null!; public NotePlacementBlueprint() : base(new Note()) { - RelativeSizeAxes = Axes.Both; + } - InternalChild = piece = new EditNotePiece { Origin = Anchor.Centre }; + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativeSizeAxes = Axes.Both; + Masking = true; + + InternalChild = piece = new Circle + { + Origin = Anchor.Centre, + Colour = colours.Yellow, + Height = 10 + }; } public override void UpdateTimeAndPosition(SnapResult result) From 4ae13cfd6052f71de28bbffe70788201b11549d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 15 Nov 2024 10:58:06 +0100 Subject: [PATCH 876/924] Remove unused resolved property --- .../Edit/Blueprints/HoldNoteSelectionBlueprint.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index aa21d53f8b..915706c044 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; -using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; @@ -18,9 +17,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { public partial class HoldNoteSelectionBlueprint : ManiaSelectionBlueprint { - [Resolved] - private OsuColour colours { get; set; } = null!; - [Resolved] private IEditorChangeHandler? changeHandler { get; set; } From 80761139076ae46e50ff493fed72189c947ec227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 15 Nov 2024 15:25:16 +0100 Subject: [PATCH 877/924] Revert velopack Because even if you somehow bypass the breakage that bricks your install by installing the missing libssl-1.0 or whatever (https://github.com/ppy/osu/issues/30648#issuecomment-2478856055), there's another brick upstream (https://github.com/ppy/osu/issues/30648#issuecomment-2478926177, https://github.com/velopack/velopack/issues/355). Can we even downgrade like this? No idea. How does one test this? No idea. At this point I am like a headless chicken, screaming into the void trying to restore any semblance of order into my crumbling universe. If this is tried and linux is still broken with the libssl garbage, then the `osulazer-2024.1115.1-linux-x64-*.nupkg` assets should be pulled from the release, which should be enough to stop the game from auto-updating. Maybe the appimage itself can stay up and people can upgrade manually if they so desire. Or maybe not. Who knows. --- osu.Desktop/osu.Desktop.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 2cdbaddf72..904f5edf2b 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -26,7 +26,7 @@ - + From c578c46a0737844da057b5323e772e9cdb3d04dd Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 16 Nov 2024 02:03:48 +0900 Subject: [PATCH 878/924] Update Velopack to 0.0.915 --- osu.Desktop/osu.Desktop.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 904f5edf2b..d06c4dd41b 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -26,7 +26,7 @@ - + From a5fab23e4486f06da9b670a7aeeac13caf874f1e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Nov 2024 23:35:30 +0900 Subject: [PATCH 879/924] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 757e5659d0..0ebb6be7a1 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index 25639f84f4..d4ef9a263f 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + From 0a1f589c80a5461b9085609b466a303e1a9b5b07 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 16 Nov 2024 11:50:08 +0900 Subject: [PATCH 880/924] Fix black layer not fading fast enough when exiting quickly from quick restart --- osu.Game/Screens/Play/PlayerLoader.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index cba7bb3b8a..aedc268d70 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -331,6 +331,9 @@ namespace osu.Game.Screens.Play cancelLoad(); ContentOut(); + quickRestartBlackLayer?.FadeOut(100, Easing.OutQuint).Expire(); + quickRestartBlackLayer = null; + // Ensure the screen doesn't expire until all the outwards fade operations have completed. this.Delay(CONTENT_OUT_DURATION).FadeOut(); From c2b08beae81bebee05ad1decf9de3cd145a55ee9 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 15 Nov 2024 22:52:27 -0800 Subject: [PATCH 881/924] Add basic searching by source test --- osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 10e0e46f4c..1efcc8542d 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -148,6 +148,7 @@ namespace osu.Game.Tests.NonVisual.Filtering [TestCase("tags too", false)] [TestCase("version", false)] [TestCase("an auteur", true)] + [TestCase("unit", false)] public void TestCriteriaMatchingTerms(string terms, bool filtered) { var exampleBeatmapInfo = getExampleBeatmap(); @@ -175,6 +176,7 @@ namespace osu.Game.Tests.NonVisual.Filtering [TestCase("\"Artist\"!", true)] [TestCase("\"The Artist\"!", false)] [TestCase("\"the artist\"!", false)] + [TestCase("\"unit tests\"!", false)] [TestCase("\"\\\"", true)] // nasty case, covers properly escaping user input in underlying regex. public void TestCriteriaMatchingExactTerms(string terms, bool filtered) { From 7c94973d4a71a9c8841cde154e2c6790341c6970 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 15 Nov 2024 22:51:36 -0800 Subject: [PATCH 882/924] Add failing source filter query test --- .../NonVisual/Filtering/FilterQueryParserTest.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index c8f063719d..89743b4ea8 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -501,6 +501,16 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.That(visibleBeatmaps, Is.EqualTo(expectedBeatmapIndexes)); } + [Test] + public void TestApplySourceQueries() + { + const string query = "find me songs with source=\"unit tests\" please"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("find me songs with please", filterCriteria.SearchText.Trim()); + Assert.AreEqual(5, filterCriteria.SearchTerms.Length); + } + private class CustomFilterCriteria : IRulesetFilterCriteria { public string? CustomValue { get; set; } From 465cc716d3d755de90af7355c02885ffd51814e6 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 15 Nov 2024 22:52:43 -0800 Subject: [PATCH 883/924] Add missing source query filter in song select --- osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs | 2 ++ osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 1 + osu.Game/Screens/Select/FilterCriteria.cs | 1 + osu.Game/Screens/Select/FilterQueryParser.cs | 3 +++ 4 files changed, 7 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 89743b4ea8..f4e324d7ba 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -509,6 +509,8 @@ namespace osu.Game.Tests.NonVisual.Filtering FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.AreEqual("find me songs with please", filterCriteria.SearchText.Trim()); Assert.AreEqual(5, filterCriteria.SearchTerms.Length); + Assert.AreEqual("unit tests", filterCriteria.Source.SearchTerm); + Assert.That(filterCriteria.Source.MatchMode, Is.EqualTo(FilterCriteria.MatchMode.IsolatedPhrase)); } private class CustomFilterCriteria : IRulesetFilterCriteria diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 3947cefb91..c007fa29ed 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -81,6 +81,7 @@ namespace osu.Game.Screens.Select.Carousel match &= !criteria.Title.HasFilter || criteria.Title.Matches(BeatmapInfo.Metadata.Title) || criteria.Title.Matches(BeatmapInfo.Metadata.TitleUnicode); match &= !criteria.DifficultyName.HasFilter || criteria.DifficultyName.Matches(BeatmapInfo.DifficultyName); + match &= !criteria.Source.HasFilter || criteria.Source.Matches(BeatmapInfo.Metadata.Source); match &= !criteria.UserStarDifficulty.HasFilter || criteria.UserStarDifficulty.IsInRange(BeatmapInfo.StarRating); if (!match) return false; diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 77d7ff0e9f..76c0f769f0 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -43,6 +43,7 @@ namespace osu.Game.Screens.Select public OptionalTextFilter Artist; public OptionalTextFilter Title; public OptionalTextFilter DifficultyName; + public OptionalTextFilter Source; public OptionalRange UserStarDifficulty = new OptionalRange { diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index ccffd34dc2..78f3bab114 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -113,6 +113,9 @@ namespace osu.Game.Screens.Select case "diff": return TryUpdateCriteriaText(ref criteria.DifficultyName, op, value); + case "source": + return TryUpdateCriteriaText(ref criteria.Source, op, value); + default: return criteria.RulesetCriteria?.TryParseCustomKeywordCriteria(key, op, value) ?? false; } From 706f5b3e5553d14642b1e2a5b341057f0d31cbc2 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 8 Oct 2024 11:46:57 -0700 Subject: [PATCH 884/924] Add missing clone keybinding to skin editor --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 42908f7102..7816705489 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -34,11 +34,12 @@ using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Skinning; using osu.Framework.Graphics.Cursor; +using osu.Game.Input.Bindings; namespace osu.Game.Overlays.SkinEditor { [Cached(typeof(SkinEditor))] - public partial class SkinEditor : VisibilityContainer, ICanAcceptFiles, IKeyBindingHandler, IEditorChangeHandler + public partial class SkinEditor : VisibilityContainer, ICanAcceptFiles, IKeyBindingHandler, IKeyBindingHandler, IEditorChangeHandler { public const double TRANSITION_DURATION = 300; @@ -313,6 +314,25 @@ namespace osu.Game.Overlays.SkinEditor { } + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Repeat) + return false; + + switch (e.Action) + { + case GlobalAction.EditorCloneSelection: + Clone(); + return true; + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + public void UpdateTargetScreen(Drawable targetScreen) { this.targetScreen = targetScreen; From ad9acc5a0f187080d4caa4deba3a7e365e8200e9 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 8 Oct 2024 11:54:58 -0700 Subject: [PATCH 885/924] Add hotkey hints to skin editor menus --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 7816705489..d0ee2ccd71 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -156,7 +156,7 @@ namespace osu.Game.Overlays.SkinEditor { Items = new OsuMenuItem[] { - new EditorMenuItem(Web.CommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()), + new EditorMenuItem(Web.CommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()) { Hotkey = new Hotkey(PlatformAction.Save) }, new EditorMenuItem(CommonStrings.Export, MenuItemType.Standard, () => skins.ExportCurrentSkin()) { Action = { Disabled = !RuntimeInfo.IsDesktop } }, new OsuMenuItemSpacer(), new EditorMenuItem(CommonStrings.RevertToDefault, MenuItemType.Destructive, () => dialogOverlay?.Push(new RevertConfirmDialog(revert))), @@ -168,13 +168,13 @@ namespace osu.Game.Overlays.SkinEditor { Items = new OsuMenuItem[] { - undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo), - redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo), + undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo) { Hotkey = new Hotkey(PlatformAction.Undo) }, + redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo) { Hotkey = new Hotkey(PlatformAction.Redo) }, new OsuMenuItemSpacer(), - cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut), - copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy), - pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste), - cloneMenuItem = new EditorMenuItem(CommonStrings.Clone, MenuItemType.Standard, Clone), + cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut) { Hotkey = new Hotkey(PlatformAction.Cut) }, + copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy) { Hotkey = new Hotkey(PlatformAction.Copy) }, + pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste) { Hotkey = new Hotkey(PlatformAction.Paste) }, + cloneMenuItem = new EditorMenuItem(CommonStrings.Clone, MenuItemType.Standard, Clone) { Hotkey = new Hotkey(GlobalAction.EditorCloneSelection) }, } }, } From b014bfea3e96f5195ef0822db094866d0d7aedc0 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 17 Nov 2024 09:56:19 -0500 Subject: [PATCH 886/924] Fix key counter not updating activation state on initial load --- osu.Game/Screens/Play/HUD/InputTrigger.cs | 7 +++++++ osu.Game/Screens/Play/HUD/KeyCounter.cs | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/InputTrigger.cs b/osu.Game/Screens/Play/HUD/InputTrigger.cs index edc61ec142..72dade25f6 100644 --- a/osu.Game/Screens/Play/HUD/InputTrigger.cs +++ b/osu.Game/Screens/Play/HUD/InputTrigger.cs @@ -34,6 +34,11 @@ namespace osu.Game.Screens.Play.HUD /// public IBindable ActivationCount => activationCount; + /// + /// Whether this is currently active. + /// + public bool IsActive { get; private set; } + /// /// Whether any activation or deactivation of this impacts its /// @@ -49,6 +54,7 @@ namespace osu.Game.Screens.Play.HUD if (forwardPlayback && isCounting.Value) activationCount.Value++; + IsActive = true; OnActivate?.Invoke(forwardPlayback); } @@ -57,6 +63,7 @@ namespace osu.Game.Screens.Play.HUD if (!forwardPlayback && isCounting.Value) activationCount.Value--; + IsActive = false; OnDeactivate?.Invoke(forwardPlayback); } } diff --git a/osu.Game/Screens/Play/HUD/KeyCounter.cs b/osu.Game/Screens/Play/HUD/KeyCounter.cs index 66f9dfd6f2..b506694044 100644 --- a/osu.Game/Screens/Play/HUD/KeyCounter.cs +++ b/osu.Game/Screens/Play/HUD/KeyCounter.cs @@ -36,6 +36,14 @@ namespace osu.Game.Screens.Play.HUD Trigger.OnDeactivate += Deactivate; } + protected override void LoadComplete() + { + base.LoadComplete(); + + if (Trigger.IsActive) + Activate(); + } + protected virtual void Activate(bool forwardPlayback = true) { isActive.Value = true; From e227bd5dfb1fdac3810932839b1b2585d48506f8 Mon Sep 17 00:00:00 2001 From: minisbett <39670899+minisbett@users.noreply.github.com> Date: Sun, 17 Nov 2024 20:59:48 +0100 Subject: [PATCH 887/924] add missing references to osu ruleset ios tests --- .../osu.Game.Rulesets.Osu.Tests.iOS.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj b/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj index cc0233d7fd..cd4fbcc00e 100644 --- a/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj +++ b/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj @@ -17,4 +17,8 @@ + + + + From 77ecc3da1182834e3e222399406573590d7e567d Mon Sep 17 00:00:00 2001 From: minisbett <39670899+minisbett@users.noreply.github.com> Date: Sun, 17 Nov 2024 21:00:02 +0100 Subject: [PATCH 888/924] add missing references to taiko ruleset ios tests --- .../osu.Game.Rulesets.Taiko.Tests.iOS.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj b/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj index ee2d4d703e..46ac59a1e7 100644 --- a/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj @@ -13,7 +13,8 @@ - + + From e5778eb1fc85ee3f12594e504e4aa001210d95a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Nov 2024 14:38:12 +0900 Subject: [PATCH 889/924] Add scrollbar to manage collections dialog --- osu.Game/Collections/DrawableCollectionList.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index 164ec558a4..441b7fabed 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -104,8 +104,8 @@ namespace osu.Game.Collections public Scroll() { - ScrollbarVisible = false; Padding = new MarginPadding(10); + ScrollbarOverlapsContent = false; base.Content.Add(new FillFlowContainer { @@ -187,6 +187,8 @@ namespace osu.Game.Collections { Spacing = new Vector2(0, 5); LayoutEasing = Easing.OutQuint; + + Padding = new MarginPadding { Right = 5 }; } protected override void LoadComplete() From a570863854ba794c4d9c72e84656568ce9c37d1f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Nov 2024 15:12:28 +0900 Subject: [PATCH 890/924] Scroll to new collection in list when adding a new collection --- .../Collections/DrawableCollectionList.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index 441b7fabed..5096dce55c 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -46,6 +46,26 @@ namespace osu.Game.Collections realmSubscription = realm.RegisterForNotifications(r => r.All().OrderBy(c => c.Name), collectionsChanged); } + /// + /// When non-null, signifies that a new collection was created and should be presented to the user. + /// + private Guid? lastCreated; + + protected override void OnItemsChanged() + { + base.OnItemsChanged(); + + if (lastCreated != null) + { + var createdItem = flow.Children.SingleOrDefault(item => item.Model.Value.ID == lastCreated); + + if (createdItem != null) + scroll.ScrollTo(createdItem); + + lastCreated = null; + } + } + private void collectionsChanged(IRealmCollection collections, ChangeSet? changes) { if (changes == null) @@ -60,7 +80,11 @@ namespace osu.Game.Collections foreach (int i in changes.InsertedIndices) Items.Insert(i, collections[i].ToLive(realm)); + if (changes.InsertedIndices.Length == 1) + lastCreated = collections[changes.InsertedIndices[0]].ID; + foreach (int i in changes.NewModifiedIndices) + { var updatedItem = collections[i]; From 5ce1f7679be37328fb74b456fa95bfa0540e4271 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Nov 2024 15:57:39 +0900 Subject: [PATCH 891/924] Add search bar to collection management dialog --- .../TestSceneManageCollectionsDialog.cs | 40 +++++++++++++++++-- .../Collections/DrawableCollectionList.cs | 11 +++-- .../Collections/DrawableCollectionListItem.cs | 24 ++++++++++- .../Collections/ManageCollectionsDialog.cs | 37 ++++++++++++++++- 4 files changed, 103 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 56e7b4d39f..0f2f716a07 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -12,6 +12,7 @@ using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Collections; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; using osu.Game.Rulesets; @@ -265,7 +266,6 @@ namespace osu.Game.Tests.Visual.Collections } [Test] - [Solo] public void TestCollectionRenamedExternal() { BeatmapCollection first = null!; @@ -338,10 +338,44 @@ namespace osu.Game.Tests.Visual.Collections AddUntilStep("collection has new name", () => first.Name == "First"); } + [Test] + public void TestSearch() + { + BeatmapCollection first = null!; + + AddStep("add two collections", () => + { + Realm.Write(r => + { + r.Add(new[] + { + first = new BeatmapCollection(name: "1"), + new BeatmapCollection(name: "2"), + }); + }); + }); + + assertCollectionName(0, "1"); + assertCollectionName(1, "2"); + + AddStep("search for 1", () => dialog.ChildrenOfType().Single().Current.Value = "1"); + + assertCollectionCount(1); + + AddStep("change first collection name", () => Realm.Write(_ => first.Name = "First")); + + assertCollectionCount(0); + + AddStep("search for first", () => dialog.ChildrenOfType().Single().Current.Value = "firs"); + + assertCollectionCount(1); + } + private void assertCollectionCount(int count) - => AddUntilStep($"{count} collections shown", () => dialog.ChildrenOfType().Count() == count + 1); // +1 for placeholder + => AddUntilStep($"{count} collections shown", () => dialog.ChildrenOfType().Count(i => i.IsPresent) == count + 1); // +1 for placeholder private void assertCollectionName(int index, string name) - => AddUntilStep($"item {index + 1} has correct name", () => dialog.ChildrenOfType().Single().OrderedItems.ElementAt(index).ChildrenOfType().First().Text == name); + => AddUntilStep($"item {index + 1} has correct name", + () => dialog.ChildrenOfType().Single().OrderedItems.ElementAt(index).ChildrenOfType().First().Text == name); } } diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index 5096dce55c..f00691ba0f 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -34,6 +34,12 @@ namespace osu.Game.Collections public IEnumerable OrderedItems => flow.FlowingChildren; + public string SearchTerm + { + get => flow.SearchTerm; + set => flow.SearchTerm = value; + } + protected override FillFlowContainer>> CreateListFillFlowContainer() => flow = new Flow { DragActive = { BindTarget = DragActive } @@ -128,7 +134,6 @@ namespace osu.Game.Collections public Scroll() { - Padding = new MarginPadding(10); ScrollbarOverlapsContent = false; base.Content.Add(new FillFlowContainer @@ -157,7 +162,7 @@ namespace osu.Game.Collections base.Update(); // AutoSizeAxes cannot be used as the height should represent the post-layout-transform height at all times, so that the placeholder doesn't bounce around. - content.Height = ((Flow)Child).Children.Sum(c => c.DrawHeight + 5); + content.Height = ((Flow)Child).Children.Sum(c => c.IsPresent ? c.DrawHeight + 5 : 0); } /// @@ -203,7 +208,7 @@ namespace osu.Game.Collections /// /// The flow of . Disables layout easing unless a drag is in progress. /// - private partial class Flow : FillFlowContainer>> + private partial class Flow : SearchContainer>> { public readonly IBindable DragActive = new Bindable(); diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index f07ec87353..e86254329f 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -10,6 +11,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -23,7 +25,7 @@ namespace osu.Game.Collections /// /// Visualises a inside a . /// - public partial class DrawableCollectionListItem : OsuRearrangeableListItem> + public partial class DrawableCollectionListItem : OsuRearrangeableListItem>, IFilterable { private const float item_height = 35; private const float button_width = item_height * 0.75f; @@ -207,5 +209,25 @@ namespace osu.Game.Collections private void deleteCollection() => collection.PerformWrite(c => c.Realm!.Remove(c)); } + + public IEnumerable FilterTerms => [(LocalisableString)Model.Value.Name]; + + private bool matchingFilter = true; + + public bool MatchingFilter + { + get => matchingFilter; + set + { + matchingFilter = value; + + if (matchingFilter) + this.FadeIn(200); + else + Hide(); + } + } + + public bool FilteringActive { get; set; } } } diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index 9f8158af53..90b73bc6b2 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; +using osu.Game.Resources.Localisation.Web; using osuTK; namespace osu.Game.Collections @@ -26,6 +27,9 @@ namespace osu.Game.Collections private IDisposable? duckOperation; + private BasicSearchTextBox searchTextBox = null!; + private DrawableCollectionList list = null!; + [Resolved] private MusicController? musicController { get; set; } @@ -104,10 +108,29 @@ namespace osu.Game.Collections RelativeSizeAxes = Axes.Both, Colour = colours.GreySeaFoamDarker }, - new DrawableCollectionList + new FillFlowContainer { RelativeSizeAxes = Axes.Both, - } + Direction = FillDirection.Vertical, + Padding = new MarginPadding(10), + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + searchTextBox = new BasicSearchTextBox + { + RelativeSizeAxes = Axes.X, + Y = 10, + Height = 40, + ReleaseFocusOnCommit = false, + HoldFocus = true, + PlaceholderText = HomeStrings.SearchPlaceholder, + }, + list = new DrawableCollectionList + { + RelativeSizeAxes = Axes.Both, + } + } + }, } } }, @@ -117,6 +140,16 @@ namespace osu.Game.Collections }; } + protected override void LoadComplete() + { + base.LoadComplete(); + + searchTextBox.Current.BindValueChanged(_ => + { + list.SearchTerm = searchTextBox.Current.Value; + }); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From ebc2cc35700415a2b242c03f65880d7371278462 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Nov 2024 16:51:34 +0900 Subject: [PATCH 892/924] Ensure cleanup tasks are run even on a cancelled / exceptioned import task --- osu.Game/Database/ModelDownloader.cs | 25 ++-- .../Database/RealmArchiveModelImporter.cs | 108 +++++++++--------- 2 files changed, 72 insertions(+), 61 deletions(-) diff --git a/osu.Game/Database/ModelDownloader.cs b/osu.Game/Database/ModelDownloader.cs index 8aece748a8..dfeec259fe 100644 --- a/osu.Game/Database/ModelDownloader.cs +++ b/osu.Game/Database/ModelDownloader.cs @@ -68,18 +68,23 @@ namespace osu.Game.Database { Task.Factory.StartNew(async () => { - bool importSuccessful; + bool importSuccessful = false; - if (originalModel != null) - importSuccessful = (await importer.ImportAsUpdate(notification, new ImportTask(filename), originalModel).ConfigureAwait(false)) != null; - else - importSuccessful = (await importer.Import(notification, new[] { new ImportTask(filename) }).ConfigureAwait(false)).Any(); + try + { + if (originalModel != null) + importSuccessful = (await importer.ImportAsUpdate(notification, new ImportTask(filename), originalModel).ConfigureAwait(false)) != null; + else + importSuccessful = (await importer.Import(notification, new[] { new ImportTask(filename) }).ConfigureAwait(false)).Any(); + } + finally + { + // for now a failed import will be marked as a failed download for simplicity. + if (!importSuccessful) + DownloadFailed?.Invoke(request); - // for now a failed import will be marked as a failed download for simplicity. - if (!importSuccessful) - DownloadFailed?.Invoke(request); - - CurrentDownloads.Remove(request); + CurrentDownloads.Remove(request); + } }, TaskCreationOptions.LongRunning); }; diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index 22bcc622e9..e538530b79 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -117,67 +117,73 @@ namespace osu.Game.Database await pauseIfNecessaryAsync(parameters, notification, notification.CancellationToken).ConfigureAwait(false); - await Parallel.ForEachAsync(tasks, notification.CancellationToken, async (task, cancellation) => + try { - cancellation.ThrowIfCancellationRequested(); - - try + await Parallel.ForEachAsync(tasks, notification.CancellationToken, async (task, cancellation) => { - await pauseIfNecessaryAsync(parameters, notification, cancellation).ConfigureAwait(false); + cancellation.ThrowIfCancellationRequested(); - var model = await Import(task, parameters, cancellation).ConfigureAwait(false); - - lock (imported) + try { - if (model != null) - imported.Add(model); - current++; + await pauseIfNecessaryAsync(parameters, notification, cancellation).ConfigureAwait(false); - notification.Text = $"Imported {current} of {tasks.Length} {HumanisedModelName}s"; - notification.Progress = (float)current / tasks.Length; + var model = await Import(task, parameters, cancellation).ConfigureAwait(false); + + lock (imported) + { + if (model != null) + imported.Add(model); + current++; + + notification.Text = $"Imported {current} of {tasks.Length} {HumanisedModelName}s"; + notification.Progress = (float)current / tasks.Length; + } + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception e) + { + Logger.Error(e, $@"Could not import ({task})", LoggingTarget.Database); + } + }).ConfigureAwait(false); + } + finally + { + if (imported.Count == 0) + { + if (notification.CancellationToken.IsCancellationRequested) + { + notification.State = ProgressNotificationState.Cancelled; + } + else + { + notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import failed! Check logs for more information."; + notification.State = ProgressNotificationState.Cancelled; } } - catch (OperationCanceledException) - { - throw; - } - catch (Exception e) - { - Logger.Error(e, $@"Could not import ({task})", LoggingTarget.Database); - } - }).ConfigureAwait(false); - - if (imported.Count == 0) - { - if (notification.CancellationToken.IsCancellationRequested) - { - notification.State = ProgressNotificationState.Cancelled; - return imported; - } - - notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import failed! Check logs for more information."; - notification.State = ProgressNotificationState.Cancelled; - } - else - { - if (tasks.Length > imported.Count) - notification.CompletionText = $"Imported {imported.Count} of {tasks.Length} {HumanisedModelName}s."; - else if (imported.Count > 1) - notification.CompletionText = $"Imported {imported.Count} {HumanisedModelName}s!"; else - notification.CompletionText = $"Imported {imported.First().GetDisplayString()}!"; - - if (imported.Count > 0 && PresentImport != null) { - notification.CompletionText += " Click to view."; - notification.CompletionClickAction = () => - { - PresentImport?.Invoke(imported); - return true; - }; - } + if (tasks.Length > imported.Count) + notification.CompletionText = $"Imported {imported.Count} of {tasks.Length} {HumanisedModelName}s."; + else if (imported.Count > 1) + notification.CompletionText = $"Imported {imported.Count} {HumanisedModelName}s!"; + else + notification.CompletionText = $"Imported {imported.First().GetDisplayString()}!"; - notification.State = ProgressNotificationState.Completed; + if (imported.Count > 0 && PresentImport != null) + { + notification.CompletionText += " Click to view."; + notification.CompletionClickAction = () => + { + PresentImport?.Invoke(imported); + return true; + }; + } + + notification.State = ProgressNotificationState.Completed; + } } return imported; From 02deb393351e94e6778281e76bf0a345b0cb70a2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 18 Nov 2024 17:20:11 +0900 Subject: [PATCH 893/924] Make android build on non-maui workflow --- .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 cb45447ed5..d75f09f184 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,7 +114,7 @@ jobs: dotnet-version: "8.0.x" - name: Install .NET workloads - run: dotnet workload install maui-android + run: dotnet workload install android - name: Compile run: dotnet build -c Debug osu.Android.slnf From 15a8cfe685a7e16381ed458fb5cdbe1b90c37f71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 18 Nov 2024 14:30:17 +0100 Subject: [PATCH 894/924] Fix mania notes disappearing on seek to their end time --- osu.Game.Rulesets.Mania/Edit/EditorColumn.cs | 45 +++++++++++++++++++ osu.Game.Rulesets.Mania/Edit/EditorStage.cs | 18 ++++++++ .../Edit/ManiaEditorPlayfield.cs | 3 ++ osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs | 6 ++- osu.Game.Rulesets.Mania/UI/Stage.cs | 16 ++++--- 5 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/Edit/EditorColumn.cs create mode 100644 osu.Game.Rulesets.Mania/Edit/EditorStage.cs diff --git a/osu.Game.Rulesets.Mania/Edit/EditorColumn.cs b/osu.Game.Rulesets.Mania/Edit/EditorColumn.cs new file mode 100644 index 0000000000..11d1848173 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/EditorColumn.cs @@ -0,0 +1,45 @@ +// 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.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Mania.Edit +{ + public partial class EditorColumn : Column + { + public EditorColumn(int index, bool isSpecial) + : base(index, isSpecial) + { + } + + protected override void OnNewDrawableHitObject(DrawableHitObject drawableHitObject) + { + base.OnNewDrawableHitObject(drawableHitObject); + drawableHitObject.ApplyCustomUpdateState += (dho, state) => + { + switch (dho) + { + // hold note heads are exempt from what follows due to the "freezing" mechanic + // which already ensures they'll never fade away on their own. + case DrawableHoldNoteHead: + break; + + // mania features instantaneous hitobject fade-outs. + // this means that without manual intervention stopping the clock at the precise time of hitting the object + // means the object will fade out. + // this is anti-user in editor contexts, as the user is expecting to continue the see the note on the receptor line. + // therefore, apply a crude workaround to prevent it from going away. + default: + { + if (state == ArmedState.Hit) + dho.FadeTo(1).Delay(1).FadeOut().Expire(); + break; + } + } + }; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/EditorStage.cs b/osu.Game.Rulesets.Mania/Edit/EditorStage.cs new file mode 100644 index 0000000000..c5f93f6182 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/EditorStage.cs @@ -0,0 +1,18 @@ +// 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.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.UI; + +namespace osu.Game.Rulesets.Mania.Edit +{ + public partial class EditorStage : Stage + { + public EditorStage(int firstColumnIndex, StageDefinition definition, ref ManiaAction columnStartAction) + : base(firstColumnIndex, definition, ref columnStartAction) + { + } + + protected override Column CreateColumn(int index, bool isSpecial) => new EditorColumn(index, isSpecial); + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaEditorPlayfield.cs b/osu.Game.Rulesets.Mania/Edit/ManiaEditorPlayfield.cs index 77e372d1d6..2dc2b8ae48 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaEditorPlayfield.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaEditorPlayfield.cs @@ -13,5 +13,8 @@ namespace osu.Game.Rulesets.Mania.Edit : base(stages) { } + + protected override Stage CreateStage(int firstColumnIndex, StageDefinition stageDefinition, ref ManiaAction columnAction) + => new EditorStage(firstColumnIndex, stageDefinition, ref columnAction); } } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 1f388144bd..a4ebb3347a 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using System; using System.Collections.Generic; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics.Primitives; using osu.Game.Rulesets.Mania.Beatmaps; @@ -71,7 +72,7 @@ namespace osu.Game.Rulesets.Mania.UI for (int i = 0; i < stageDefinitions.Count; i++) { - var newStage = new Stage(firstColumnIndex, stageDefinitions[i], ref columnAction); + var newStage = CreateStage(firstColumnIndex, stageDefinitions[i], ref columnAction); playfieldGrid.Content[0][i] = newStage; @@ -82,6 +83,9 @@ namespace osu.Game.Rulesets.Mania.UI } } + [Pure] + protected virtual Stage CreateStage(int firstColumnIndex, StageDefinition stageDefinition, ref ManiaAction columnAction) => new Stage(firstColumnIndex, stageDefinition, ref columnAction); + public override void Add(HitObject hitObject) => getStageByColumn(((ManiaHitObject)hitObject).Column).Add(hitObject); public override bool Remove(HitObject hitObject) => getStageByColumn(((ManiaHitObject)hitObject).Column).Remove(hitObject); diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index 86f2243561..9fb77a4995 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; @@ -134,12 +135,14 @@ namespace osu.Game.Rulesets.Mania.UI { bool isSpecial = definition.IsSpecialColumn(i); - var column = new Column(firstColumnIndex + i, isSpecial) + var action = columnStartAction; + columnStartAction++; + var column = CreateColumn(firstColumnIndex + i, isSpecial).With(c => { - RelativeSizeAxes = Axes.Both, - Width = 1, - Action = { Value = columnStartAction++ } - }; + c.RelativeSizeAxes = Axes.Both; + c.Width = 1; + c.Action.Value = action; + }); topLevelContainer.Add(column.TopLevelContainer.CreateProxy()); columnBackgrounds.Add(column.BackgroundContainer.CreateProxy()); @@ -154,6 +157,9 @@ namespace osu.Game.Rulesets.Mania.UI RegisterPool(50, 200); } + [Pure] + protected virtual Column CreateColumn(int index, bool isSpecial) => new Column(index, isSpecial); + [BackgroundDependencyLoader] private void load(ISkinSource skin) { From 259e9ecf65704068e0745afbb6c6d312a90f5339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 18 Nov 2024 15:06:13 +0100 Subject: [PATCH 895/924] Deactivate new combo toggle on deselecting objects Closes https://github.com/ppy/osu/issues/30713. Was a point of discussion doing review: https://github.com/ppy/osu/pull/30214#discussion_r1798833139 Given it got pointed out immediately for something so minor, I'm inclined to believe it's a rather undesirable change. --- .../Screens/Edit/Compose/Components/EditorSelectionHandler.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index a9dbfc29a9..6724a1dc4d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -258,8 +258,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void resetTernaryStates() { - if (SelectionNewComboState.Value == TernaryState.Indeterminate) - SelectionNewComboState.Value = TernaryState.False; + SelectionNewComboState.Value = TernaryState.False; AutoSelectionBankEnabled.Value = true; SelectionAdditionBanksEnabled.Value = true; SelectionBankStates[HIT_BANK_AUTO].Value = TernaryState.True; From 22b082d968d7d4179de0d0350867a0c0f187f377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 18 Nov 2024 15:28:30 +0100 Subject: [PATCH 896/924] Fix not being able to scroll to new collection text box when list overflows --- osu.Game/Collections/DrawableCollectionList.cs | 6 ++++++ osu.Game/Collections/ManageCollectionsDialog.cs | 8 +++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index f00691ba0f..85af1d383d 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -21,6 +21,12 @@ namespace osu.Game.Collections /// public partial class DrawableCollectionList : OsuRearrangeableListContainer> { + public new MarginPadding Padding + { + get => base.Padding; + set => base.Padding = value; + } + protected override ScrollContainer CreateScrollContainer() => scroll = new Scroll(); [Resolved] diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index 90b73bc6b2..a738ae66cb 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -108,12 +108,10 @@ namespace osu.Game.Collections RelativeSizeAxes = Axes.Both, Colour = colours.GreySeaFoamDarker }, - new FillFlowContainer + new Container { RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, Padding = new MarginPadding(10), - Spacing = new Vector2(0, 10), Children = new Drawable[] { searchTextBox = new BasicSearchTextBox @@ -127,6 +125,10 @@ namespace osu.Game.Collections }, list = new DrawableCollectionList { + Padding = new MarginPadding + { + Top = 60, + }, RelativeSizeAxes = Axes.Both, } } From a2af4cbb50f04cd528d01f3c5becee0daeadd080 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Nov 2024 23:33:48 +0900 Subject: [PATCH 897/924] Fix right click scroll at song select not quite matching scrollbar position Closes https://github.com/ppy/osu/issues/30744. --- osu.Game/Graphics/Containers/OsuScrollContainer.cs | 13 +++++++++---- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 +-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuScrollContainer.cs b/osu.Game/Graphics/Containers/OsuScrollContainer.cs index ffd28957ef..a3cd5a4902 100644 --- a/osu.Game/Graphics/Containers/OsuScrollContainer.cs +++ b/osu.Game/Graphics/Containers/OsuScrollContainer.cs @@ -42,8 +42,6 @@ namespace osu.Game.Graphics.Containers /// public double DistanceDecayOnRightMouseScrollbar = 0.02; - private bool shouldPerformRightMouseScroll(MouseButtonEvent e) => RightMouseScrollbar && e.Button == MouseButton.Right; - private bool rightMouseDragging; protected override bool IsDragging => base.IsDragging || rightMouseDragging; @@ -126,8 +124,15 @@ namespace osu.Game.Graphics.Containers return base.OnScroll(e); } - protected virtual void ScrollFromMouseEvent(MouseEvent e) => - ScrollTo(Clamp(ToLocalSpace(e.ScreenSpaceMousePosition)[ScrollDim] / DrawSize[ScrollDim] * Content.DrawSize[ScrollDim]), true, DistanceDecayOnRightMouseScrollbar); + protected virtual void ScrollFromMouseEvent(MouseEvent e) + { + float fromScrollbarPosition = FromScrollbarPosition(ToLocalSpace(e.ScreenSpaceMousePosition)[ScrollDim]); + float scrollbarCentreOffset = FromScrollbarPosition(Scrollbar.DrawHeight) * 0.5f; + + ScrollTo(Clamp(fromScrollbarPosition - scrollbarCentreOffset), true, DistanceDecayOnRightMouseScrollbar); + } + + private bool shouldPerformRightMouseScroll(MouseButtonEvent e) => RightMouseScrollbar && e.Button == MouseButton.Right; protected override ScrollbarContainer CreateScrollbar(Direction direction) => new OsuScrollbar(direction); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 95268c35da..5e1e0ce615 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -245,8 +245,7 @@ namespace osu.Game.Screens.Select config.BindWith(OsuSetting.RandomSelectAlgorithm, RandomAlgorithm); config.BindWith(OsuSetting.SongSelectRightMouseScroll, RightClickScrollingEnabled); - RightClickScrollingEnabled.ValueChanged += enabled => Scroll.RightMouseScrollbar = enabled.NewValue; - RightClickScrollingEnabled.TriggerChange(); + RightClickScrollingEnabled.BindValueChanged(enabled => Scroll.RightMouseScrollbar = enabled.NewValue, true); if (detachedBeatmapStore != null && detachedBeatmapSets == null) { From 16158710f9567a1641c272dd21f6d70a33d8338e Mon Sep 17 00:00:00 2001 From: Darius Wattimena Date: Mon, 18 Nov 2024 23:48:26 +0100 Subject: [PATCH 898/924] Made reading of ReadCurrentDistanceSnap public --- osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapProvider.cs | 2 +- osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs | 2 +- osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapProvider.cs b/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapProvider.cs index 6f5b32a41d..ae4025aa2f 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapProvider.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapProvider.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Catch.Edit { public partial class CatchDistanceSnapProvider : ComposerDistanceSnapProvider { - protected override double ReadCurrentDistanceSnap(HitObject before, HitObject after) + public override double ReadCurrentDistanceSnap(HitObject before, HitObject after) { // osu!catch's distance snap implementation is limited, in that a custom spacing cannot be specified. // Therefore this functionality is not currently used. diff --git a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs index 522943df7d..4042cfa0e2 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Edit { public partial class OsuDistanceSnapProvider : ComposerDistanceSnapProvider { - protected override double ReadCurrentDistanceSnap(HitObject before, HitObject after) + public override double ReadCurrentDistanceSnap(HitObject before, HitObject after) { float expectedDistance = DurationToDistance(before, after.StartTime - before.GetEndTime()); float actualDistance = Vector2.Distance(((OsuHitObject)before).EndPosition, ((OsuHitObject)after).Position); diff --git a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs index cf41c8e108..5bf15aee8b 100644 --- a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs @@ -163,7 +163,7 @@ namespace osu.Game.Rulesets.Edit return (lastBefore, firstAfter); } - protected abstract double ReadCurrentDistanceSnap(HitObject before, HitObject after); + public abstract double ReadCurrentDistanceSnap(HitObject before, HitObject after); protected override void Update() { From 111f029ead5d5130904bcae1e463d0a330039eec Mon Sep 17 00:00:00 2001 From: Darius Wattimena Date: Mon, 18 Nov 2024 23:53:01 +0100 Subject: [PATCH 899/924] Added a custom hitobject inspector for catch additionally show the x value of prev and next --- .../Edit/CatchHitObjectComposer.cs | 2 + .../Edit/CatchHitObjectInspector.cs | 43 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/Edit/CatchHitObjectInspector.cs diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index 09d1ff663d..aae3369d40 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -70,6 +70,8 @@ namespace osu.Game.Rulesets.Catch.Edit })); } + protected override Drawable CreateHitObjectInspector() => new CatchHitObjectInspector(DistanceSnapProvider); + protected override IEnumerable CreateTernaryButtons() => base.CreateTernaryButtons() .Concat(DistanceSnapProvider.CreateTernaryButtons()); diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectInspector.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectInspector.cs new file mode 100644 index 0000000000..6ff7e9c6a0 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectInspector.cs @@ -0,0 +1,43 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Game.Rulesets.Objects; +using osu.Game.Screens.Edit.Compose.Components; + +namespace osu.Game.Rulesets.Catch.Edit +{ + public partial class CatchHitObjectInspector(CatchDistanceSnapProvider snapProvider) : HitObjectInspector + { + protected override void AddInspectorValues(HitObject[] objects) + { + base.AddInspectorValues(objects); + + if (objects.Length > 0) + { + HitObject firstSelectedHitObject = objects.MinBy(ho => ho.StartTime)!; + HitObject lastSelectedHitObject = objects.MaxBy(ho => ho.GetEndTime())!; + + HitObject? precedingObject = EditorBeatmap.HitObjects.LastOrDefault(ho => ho.GetEndTime() < firstSelectedHitObject.StartTime); + HitObject? nextObject = EditorBeatmap.HitObjects.FirstOrDefault(ho => ho.StartTime > lastSelectedHitObject.GetEndTime()); + + if (precedingObject != null || nextObject != null) + { + AddHeader("Snapping"); + } + + if (precedingObject != null) + { + double previousSnap = snapProvider.ReadCurrentDistanceSnap(precedingObject, firstSelectedHitObject); + AddValue($"Previous: {previousSnap:#,0.##}x"); + } + + if (nextObject != null) + { + double nextSnap = snapProvider.ReadCurrentDistanceSnap(lastSelectedHitObject, nextObject); + AddValue($"Next: {nextSnap:#,0.##}x"); + } + } + } + } +} From a5327aa5627c42de00de135a1d1928cd044c44bb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 19 Nov 2024 18:48:30 +0900 Subject: [PATCH 900/924] Use properties instead of fields --- .../Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs index 8ddda485b5..be6ca43f4b 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs @@ -19,12 +19,12 @@ namespace osu.Game.Beatmaps.Drawables /// /// Delay before the background is loaded while on-screen. /// - public double BackgroundLoadDelay = 500; + public double BackgroundLoadDelay { get; set; } = 500; /// /// Delay before the background is unloaded while off-screen. /// - public double BackgroundUnloadDelay = 10000; + public double BackgroundUnloadDelay { get; set; } = 10000; [Resolved] private BeatmapManager beatmaps { get; set; } = null!; From a8e14b6625552e35687bb6bf347898ad5deb552e Mon Sep 17 00:00:00 2001 From: Darius Wattimena Date: Tue, 19 Nov 2024 19:54:12 +0100 Subject: [PATCH 901/924] Align inspector info more to how it is shown for osu --- .../Edit/CatchHitObjectInspector.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectInspector.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectInspector.cs index 6ff7e9c6a0..c279354d9a 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectInspector.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectInspector.cs @@ -21,21 +21,18 @@ namespace osu.Game.Rulesets.Catch.Edit HitObject? precedingObject = EditorBeatmap.HitObjects.LastOrDefault(ho => ho.GetEndTime() < firstSelectedHitObject.StartTime); HitObject? nextObject = EditorBeatmap.HitObjects.FirstOrDefault(ho => ho.StartTime > lastSelectedHitObject.GetEndTime()); - if (precedingObject != null || nextObject != null) - { - AddHeader("Snapping"); - } - if (precedingObject != null) { double previousSnap = snapProvider.ReadCurrentDistanceSnap(precedingObject, firstSelectedHitObject); - AddValue($"Previous: {previousSnap:#,0.##}x"); + AddHeader("To previous"); + AddValue($"{previousSnap:#,0.##}x"); } if (nextObject != null) { double nextSnap = snapProvider.ReadCurrentDistanceSnap(lastSelectedHitObject, nextObject); - AddValue($"Next: {nextSnap:#,0.##}x"); + AddHeader("To next"); + AddValue($"{nextSnap:#,0.##}x"); } } } From 9ac877606b29099112c7b5058833bb408474858b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 20 Nov 2024 12:32:42 +0900 Subject: [PATCH 902/924] Fix inspection issues --- .../TestScenePlaylistsSongSelect.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index fea6617d3f..b55102518a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -74,14 +74,14 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestItemAddedWhenCreateNewItemClicked() { - AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); + AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!()); AddAssert("playlist has 1 item", () => SelectedRoom.Value.Playlist.Count == 1); } [Test] public void TestItemNotAddedIfExistingOnStart() { - AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); + AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!()); AddStep("finalise selection", () => songSelect.FinaliseSelection()); AddAssert("playlist has 1 item", () => SelectedRoom.Value.Playlist.Count == 1); } @@ -89,19 +89,19 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestAddSameItemMultipleTimes() { - AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); - AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); + AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!()); + AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!()); AddAssert("playlist has 2 items", () => SelectedRoom.Value.Playlist.Count == 2); } [Test] public void TestAddItemAfterRearrangement() { - AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); - AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); + AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!()); + AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!()); AddStep("rearrange", () => SelectedRoom.Value.Playlist = SelectedRoom.Value.Playlist.Skip(1).Append(SelectedRoom.Value.Playlist[0]).ToArray()); - AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); + AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!()); AddAssert("new item has id 2", () => SelectedRoom.Value.Playlist.Last().ID == 2); } @@ -112,9 +112,9 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestNewItemHasNewModInstances() { AddStep("set dt mod", () => SelectedMods.Value = new[] { new OsuModDoubleTime() }); - AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem()); + AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem!()); AddStep("change mod rate", () => ((OsuModDoubleTime)SelectedMods.Value[0]).SpeedChange.Value = 2); - AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem()); + AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem!()); AddAssert("item 1 has rate 1.5", () => { @@ -145,7 +145,7 @@ namespace osu.Game.Tests.Visual.Multiplayer mod = (OsuModDoubleTime)SelectedMods.Value[0]; }); - AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem()); + AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem!()); AddStep("change stored mod rate", () => mod.SpeedChange.Value = 2); AddAssert("item has rate 1.5", () => From 50089c027eb9d4c1c1f57314d9ff1e252387f616 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Nov 2024 16:40:33 +0900 Subject: [PATCH 903/924] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index f271bdfaaf..4699beeac0 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index ecb9ae2c8d..ccae4a15ee 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + From 03de51848437b2fbadb028d2a414de8c936dc6e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Nov 2024 16:56:22 +0900 Subject: [PATCH 904/924] Fix missing `updateRoomPlaylist` call --- osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index c82317add4..f915676fb1 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -65,6 +65,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Room.PropertyChanged += onRoomPropertyChanged; updateSetupState(); updateRoomMaxAttempts(); + updateRoomPlaylist(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) From c6d08daee84e188f6fe51c95583869e16b42f209 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Nov 2024 17:11:49 +0900 Subject: [PATCH 905/924] Remove `MultiplayerMatchSubScreen` flaky test attribute Didn't really work to fix these tests due to the sticky nature of the failure. Also I can no longer reproduce locally, so the hope is that these are fixed by https://github.com/ppy/osu/pull/30634. --- .../TestSceneMultiplayerMatchSubScreen.cs | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index ef5ff25194..abcb591cf6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -77,25 +77,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] - /* - * Fail rate around 1.5% - * - * TearDown : System.AggregateException : One or more errors occurred. (Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index')) - ----> System.ArgumentOutOfRangeException : Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index') - * --TearDown - * at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions) - * at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken) - * at osu.Framework.Extensions.TaskExtensions.WaitSafely(Task task) - * at osu.Framework.Testing.TestScene.checkForErrors() - * at osu.Framework.Testing.TestScene.RunTestsFromNUnit() - *--ArgumentOutOfRangeException - * at osu.Framework.Bindables.BindableList`1.removeAt(Int32 index, BindableList`1 caller) - * at osu.Framework.Bindables.BindableList`1.removeAt(Int32 index, BindableList`1 caller) - * at osu.Framework.Bindables.BindableList`1.removeAt(Int32 index, BindableList`1 caller) - * at osu.Game.Online.Multiplayer.MultiplayerClient.<>c__DisplayClass106_0.b__0() in C:\BuildAgent\work\ecd860037212ac52\osu.Game\Online\Multiplayer\MultiplayerClient .cs:line 702 - * at osu.Framework.Threading.ScheduledDelegate.RunTaskInternal() - */ public void TestCreatedRoom() { AddStep("add playlist item", () => @@ -115,7 +96,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestTaikoOnlyMod() { AddStep("add playlist item", () => @@ -139,7 +119,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestSettingValidity() { AddAssert("create button not enabled", () => !this.ChildrenOfType().Single().Enabled.Value); @@ -159,7 +138,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestStartMatchWhileSpectating() { AddStep("set playlist", () => @@ -191,7 +169,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestFreeModSelectionHasAllowedMods() { AddStep("add playlist item with allowed mod", () => @@ -221,7 +198,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestModSelectKeyWithAllowedMods() { AddStep("add playlist item with allowed mod", () => @@ -246,7 +222,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestModSelectKeyWithNoAllowedMods() { AddStep("add playlist item with no allowed mods", () => @@ -270,7 +245,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestNextPlaylistItemSelectedAfterCompletion() { AddStep("add two playlist items", () => @@ -307,7 +281,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestModSelectOverlay() { AddStep("add playlist item", () => From 7726ca02b093df019fa56424563417507bf2b760 Mon Sep 17 00:00:00 2001 From: Fivoka <42149652+Fivoka@users.noreply.github.com> Date: Wed, 20 Nov 2024 12:29:29 +0100 Subject: [PATCH 906/924] Changed multiplier from 2 to 3.5 --- osu.Game/Screens/Play/BreakOverlay.cs | 2 +- osu.Game/Screens/Play/SkipOverlay.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 1fdb9402bc..550d29965f 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -166,7 +166,7 @@ namespace osu.Game.Screens.Play return; float timeBoxTargetWidth = (float)Math.Max(0, (remainingTimeForCurrentPeriod - timingPoint.BeatLength / currentPeriod.Value.Value.Duration)); - remainingTimeBox.ResizeWidthTo(timeBoxTargetWidth, timingPoint.BeatLength * 2, Easing.OutQuint); + remainingTimeBox.ResizeWidthTo(timeBoxTargetWidth, timingPoint.BeatLength * 3.5, Easing.OutQuint); } private void updateDisplay(ValueChangedEvent period) diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index 362677ca5c..be8517d9a0 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -219,7 +219,7 @@ namespace osu.Game.Screens.Play float progress = (float)(gameplayClock.CurrentTime - displayTime) / (float)(fadeOutBeginTime - displayTime); float newWidth = 1 - Math.Clamp(progress, 0, 1); - remainingTimeBox.ResizeWidthTo(newWidth, timingPoint.BeatLength * 2, Easing.OutQuint); + remainingTimeBox.ResizeWidthTo(newWidth, timingPoint.BeatLength * 3.5, Easing.OutQuint); } public partial class FadeContainer : Container, IStateful From dfa32302acf14ab470e0827d5efbf7bdeed09d8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 20 Nov 2024 13:16:37 +0100 Subject: [PATCH 907/924] Add test case covering stability of compatibility export operation This is important as the format will be used more when lazer beatmap submission comes online, and its stability is a useful property for that. Included archive contains an `.osu` with a few fractional-millisecond timing points and objects, as well as a multi-segment-type slider. That should cover the range of all possible modifications that the compatibility exporter currently performs. --- .../Beatmaps/IO/LegacyBeatmapExporterTest.cs | 43 ++++++++++++++++++ .../Archives/legacy-export-stability-test.olz | Bin 0 -> 946 bytes 2 files changed, 43 insertions(+) create mode 100644 osu.Game.Tests/Resources/Archives/legacy-export-stability-test.olz diff --git a/osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs b/osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs index 9947def06d..8a95d26782 100644 --- a/osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.IO; +using System.Text; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions; @@ -9,6 +10,7 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; +using osu.Game.IO.Archives; using osu.Game.Tests.Resources; using osu.Game.Tests.Visual; using MemoryStream = System.IO.MemoryStream; @@ -48,6 +50,47 @@ namespace osu.Game.Tests.Beatmaps.IO AddAssert("hit object is snapped", () => beatmap.Beatmap.HitObjects[0].StartTime, () => Is.EqualTo(28519).Within(0.001)); } + [Test] + public void TestExportStability() + { + IWorkingBeatmap beatmap = null!; + MemoryStream firstExport = null!; + MemoryStream secondExport = null!; + + // Ensure importer encoding is correct + AddStep("import beatmap", () => beatmap = importBeatmapFromArchives(@"legacy-export-stability-test.olz")); + AddStep("export once", () => + { + firstExport = new MemoryStream(); + + new LegacyBeatmapExporter(LocalStorage) + .ExportToStream((BeatmapSetInfo)beatmap.BeatmapInfo.BeatmapSet!, firstExport, null); + }); + + AddStep("import beatmap again", () => beatmap = importBeatmapFromStream(firstExport)); + AddStep("export again", () => + { + secondExport = new MemoryStream(); + + new LegacyBeatmapExporter(LocalStorage) + .ExportToStream((BeatmapSetInfo)beatmap.BeatmapInfo.BeatmapSet!, secondExport, null); + }); + + const string osu_filename = @"legacy export - stability test (spaceman_atlas) [].osu"; + + AddAssert("exports are identical", + () => getStringContentsOf(osu_filename, firstExport.GetBuffer()), + () => Is.EqualTo(getStringContentsOf(osu_filename, secondExport.GetBuffer()))); + + string getStringContentsOf(string filename, byte[] archiveBytes) + { + using var memoryStream = new MemoryStream(archiveBytes); + using var archiveReader = new ZipArchiveReader(memoryStream); + byte[] fileContent = archiveReader.GetStream(filename).ReadAllBytesToArray(); + return Encoding.UTF8.GetString(fileContent); + } + } + private IWorkingBeatmap importBeatmapFromStream(Stream stream) { var imported = beatmaps.Import(new ImportTask(stream, "filename.osz")).GetResultSafely(); diff --git a/osu.Game.Tests/Resources/Archives/legacy-export-stability-test.olz b/osu.Game.Tests/Resources/Archives/legacy-export-stability-test.olz new file mode 100644 index 0000000000000000000000000000000000000000..c6cf33acaf8587cbb040413f5c39d9cc7544d913 GIT binary patch literal 946 zcmWIWW@Zs#-~hsd86}YnP+-Brz+lFpz>t%go|s&zkXliYUsR%?t595$n3S25SyHJ` zl3HA%pix|qn4FrMm=~W|l9O1hsSq8jmtR~O8p6xKzUqQ)It-Uqa5FHnykKTv028T! z(RsHG1pYkNEMaJU@O$BYn=X}xyAq|hn0R}drp;*S)sYl=e5Ez-TEE=N@E4cXB`;xJ zD7tmCwe{v-e?N!Y+lYT(`1NS5e7K&d^eG;v^6C3NU);)F^6O4Z-J{szyP>R(n_rk8 z+<&KS>iRC>u5-Vi>zV%LZV)+pH|y@No&KR8?KW@Cm>2Wcz2@`TCrogcSbRoy=uEWiEqy`#@g*!aY*l22cITd(n3v$tLHi+8cY z3#uCR4o!#&|NZmU)rHk39?UF%sg!f*!tqrCFO1sK{#&1YYKwZk z^B5zt7IM6pwvab7TE%`z?YCs{S94qI4_uy+P!qc4-@&W557ozi|2U!cZ6)`-=J!)t zc@=J~kNJLe={4RLllz@tBraX>Qt8h9_Fp@FZ?o>273(6tYj0uaIp;Nrcdf03?&f^r z&+h+fS-C6j_`R$B?b9l`x%+|}4)5ZXa|_n_mzIBc_T0Rh>20ic68txuiIgw9Kc%p7 zsn*tQGYpy5*FIUQk?HB>Ba;3&=Sfro^OHS~c3XZ}?UrhAJ<6%=@Vm~Z0j(=HPU`9s zZfg~2Yc%%K&8fd5Df7@$>MwJEH#5M?w0;DB?8Hj-a0N_@bl>h($ literal 0 HcmV?d00001 From 1ef02fec062a85737589f9f64f291e094e791ef8 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 20 Nov 2024 12:08:57 -0500 Subject: [PATCH 908/924] Fix "positional hitsounds level" setting not specifying a precision constraint --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index af6fd61a3d..f922981d77 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -132,7 +132,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.Prefer24HourTime, !CultureInfoHelper.SystemCulture.DateTimeFormat.ShortTimePattern.Contains(@"tt")); // Gameplay - SetDefault(OsuSetting.PositionalHitsoundsLevel, 0.2f, 0, 1); + SetDefault(OsuSetting.PositionalHitsoundsLevel, 0.2f, 0, 1, 0.01f); SetDefault(OsuSetting.DimLevel, 0.7, 0, 1, 0.01); SetDefault(OsuSetting.BlurLevel, 0, 0, 1, 0.01); SetDefault(OsuSetting.LightenDuringBreaks, true); From 40b95901e3253bf17f668d46df5c46ca8e469b8e Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 20 Nov 2024 12:11:38 -0500 Subject: [PATCH 909/924] Fix more cases of settings with no defined precision values --- osu.Game/Configuration/OsuConfigManager.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index f922981d77..362c06849d 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -54,7 +54,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.ModSelectHotkeyStyle, ModSelectHotkeyStyle.Sequential); SetDefault(OsuSetting.ModSelectTextSearchStartsActive, true); - SetDefault(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2f, 1f); + SetDefault(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2f, 1f, 0.01f); SetDefault(OsuSetting.BeatmapListingCardSize, BeatmapCardSize.Normal); @@ -169,13 +169,13 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.Scaling, ScalingMode.Off); SetDefault(OsuSetting.SafeAreaConsiderations, true); - SetDefault(OsuSetting.ScalingBackgroundDim, 0.9f, 0.5f, 1f); + SetDefault(OsuSetting.ScalingBackgroundDim, 0.9f, 0.5f, 1f, 0.01f); - SetDefault(OsuSetting.ScalingSizeX, 0.8f, 0.2f, 1f); - SetDefault(OsuSetting.ScalingSizeY, 0.8f, 0.2f, 1f); + SetDefault(OsuSetting.ScalingSizeX, 0.8f, 0.2f, 1f, 0.01f); + SetDefault(OsuSetting.ScalingSizeY, 0.8f, 0.2f, 1f, 0.01f); - SetDefault(OsuSetting.ScalingPositionX, 0.5f, 0f, 1f); - SetDefault(OsuSetting.ScalingPositionY, 0.5f, 0f, 1f); + SetDefault(OsuSetting.ScalingPositionX, 0.5f, 0f, 1f, 0.01f); + SetDefault(OsuSetting.ScalingPositionY, 0.5f, 0f, 1f, 0.01f); SetDefault(OsuSetting.UIScale, 1f, 0.8f, 1.6f, 0.01f); From 945139635f99b827e405273dad54e80f2a606e3d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2024 02:23:42 +0900 Subject: [PATCH 910/924] Add failing test case --- .../Visual/Gameplay/TestScenePlayerLoader.cs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index cf813cfd51..182193714b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -18,6 +18,7 @@ using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Mods; @@ -207,7 +208,25 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] - public void TestBlockLoadViaFocus() + public void TestLoadNotBlockedViaArbitraryFocus() + { + AddStep("load dummy beatmap", () => resetPlayer(false)); + AddUntilStep("wait for current", () => loader.IsCurrentScreen()); + + AddUntilStep("click settings slider", () => + { + InputManager.MoveMouseTo(loader.ChildrenOfType>().First()); + InputManager.Click(MouseButton.Left); + + return InputManager.FocusedDrawable is OsuSliderBar; + }); + + AddUntilStep("wait for load ready", () => player?.LoadState == LoadState.Ready); + AddUntilStep("loads", () => !loader.IsCurrentScreen()); + } + + [Test] + public void TestBlockLoadViaOverlayFocus() { AddStep("load dummy beatmap", () => resetPlayer(false)); AddUntilStep("wait for current", () => loader.IsCurrentScreen()); From ae98f63b511870c11aad3d4955a1a5454a0aad58 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2024 02:23:56 +0900 Subject: [PATCH 911/924] Fix beatmap load not continuing when when settings slider is focused Regressed with recent sliderbar focus changes. Closes #30716. --- osu.Game/Screens/Play/PlayerLoader.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index aedc268d70..3e36c630db 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -122,7 +122,9 @@ namespace osu.Game.Screens.Play // not ready if the user is dragging a slider or otherwise. && (inputManager.DraggedDrawable == null || inputManager.DraggedDrawable is OsuLogo) // not ready if a focused overlay is visible, like settings. - && inputManager.FocusedDrawable == null; + && inputManager.FocusedDrawable is not OsuFocusedOverlayContainer + // or if a child of a focused overlay is focused, like settings' search textbox. + && inputManager.FocusedDrawable?.FindClosestParent() == null; private readonly Func createPlayer; From 37394a5027db31440aa06f208d7563771f79b34d Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 21 Nov 2024 14:04:42 +1300 Subject: [PATCH 912/924] Use consistent decimal places in BeatmapAttributeText --- osu.Game/Skinning/Components/BeatmapAttributeText.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index 79a1ed4d7c..f1c27434fa 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -211,19 +211,19 @@ namespace osu.Game.Skinning.Components return beatmap.Value.BeatmapInfo.Status.GetLocalisableDescription(); case BeatmapAttribute.BPM: - return FormatUtils.RoundBPM(beatmap.Value.BeatmapInfo.BPM, ModUtils.CalculateRateWithMods(mods.Value)).ToLocalisableString(@"F2"); + return FormatUtils.RoundBPM(beatmap.Value.BeatmapInfo.BPM, ModUtils.CalculateRateWithMods(mods.Value)).ToLocalisableString(@"0.##"); case BeatmapAttribute.CircleSize: - return computeDifficulty().CircleSize.ToLocalisableString(@"F2"); + return computeDifficulty().CircleSize.ToLocalisableString(@"0.##"); case BeatmapAttribute.HPDrain: - return computeDifficulty().DrainRate.ToLocalisableString(@"F2"); + return computeDifficulty().DrainRate.ToLocalisableString(@"0.##"); case BeatmapAttribute.Accuracy: - return computeDifficulty().OverallDifficulty.ToLocalisableString(@"F2"); + return computeDifficulty().OverallDifficulty.ToLocalisableString(@"0.##"); case BeatmapAttribute.ApproachRate: - return computeDifficulty().ApproachRate.ToLocalisableString(@"F2"); + return computeDifficulty().ApproachRate.ToLocalisableString(@"0.##"); case BeatmapAttribute.StarRating: return (starDifficulty?.Stars ?? 0).ToLocalisableString(@"F2"); From fe8e9d455a6a91f7e03a3be0c4331c6ab53849f0 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 21 Nov 2024 18:04:44 +0900 Subject: [PATCH 913/924] Add failing test --- .../TestScenePlaylistsRoomSubScreen.cs | 41 ++++++++++++++++++ .../Visual/OnlinePlay/TestRoomManager.cs | 42 +++++++------------ 2 files changed, 57 insertions(+), 26 deletions(-) create mode 100644 osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs new file mode 100644 index 0000000000..4ff8123fbb --- /dev/null +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs @@ -0,0 +1,41 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using NUnit.Framework; +using osu.Framework.Screens; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Rooms; +using osu.Game.Online.Rooms.RoomStatuses; +using osu.Game.Screens.OnlinePlay.Playlists; +using osu.Game.Tests.Visual.OnlinePlay; + +namespace osu.Game.Tests.Visual.Playlists +{ + public class TestScenePlaylistsRoomSubScreen : OnlinePlayTestScene + { + protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager; + + [Test] + public void TestStatusUpdateOnEnter() + { + Room room = null!; + PlaylistsRoomSubScreen roomScreen = null!; + + AddStep("create room", () => + { + RoomManager.AddRoom(room = new Room + { + Name = @"Test Room", + Host = new APIUser { Username = @"Host" }, + Category = RoomCategory.Normal, + EndDate = DateTimeOffset.Now.AddMinutes(-1) + }); + }); + + AddStep("push screen", () => LoadScreen(roomScreen = new PlaylistsRoomSubScreen(room))); + AddUntilStep("wait for screen load", () => roomScreen.IsCurrentScreen()); + AddAssert("status is still ended", () => roomScreen.Room.Status, Is.TypeOf); + } + } +} diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs index e813b49844..b1e3eafacc 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs @@ -29,38 +29,28 @@ namespace osu.Game.Tests.Visual.OnlinePlay { for (int i = 0; i < count; i++) { - var room = new Room + AddRoom(new Room { - RoomID = -currentRoomId, Name = $@"Room {currentRoomId}", Host = new APIUser { Username = @"Host" }, Duration = TimeSpan.FromSeconds(10), Category = withSpotlightRooms && i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal, - }; - - if (withPassword) - room.Password = @"password"; - - if (ruleset != null) - { - room.PlaylistItemStats = new Room.RoomPlaylistItemStats - { - RulesetIDs = new[] { ruleset.OnlineID }, - }; - - room.Playlist = - [ - new PlaylistItem(new BeatmapInfo { Metadata = new BeatmapMetadata() }) - { - RulesetID = ruleset.OnlineID, - } - ]; - } - - CreateRoom(room); - - currentRoomId++; + Password = withPassword ? @"password" : null, + PlaylistItemStats = ruleset == null + ? null + : new Room.RoomPlaylistItemStats { RulesetIDs = [ruleset.OnlineID] }, + Playlist = ruleset == null + ? Array.Empty() + : [new PlaylistItem(new BeatmapInfo { Metadata = new BeatmapMetadata() }) { RulesetID = ruleset.OnlineID }] + }); } } + + public void AddRoom(Room room) + { + room.RoomID = -currentRoomId; + CreateRoom(room); + currentRoomId++; + } } } From 7018672275fd5621d1ceb4ecf9a4411bbdc91fd7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 21 Nov 2024 18:11:41 +0900 Subject: [PATCH 914/924] Fix playlist room status resetting on enter --- osu.Game/Online/Rooms/GetRoomsRequest.cs | 21 --------------------- osu.Game/Online/Rooms/Room.cs | 13 +++++++++++++ 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/osu.Game/Online/Rooms/GetRoomsRequest.cs b/osu.Game/Online/Rooms/GetRoomsRequest.cs index cd797a9668..7feb709acb 100644 --- a/osu.Game/Online/Rooms/GetRoomsRequest.cs +++ b/osu.Game/Online/Rooms/GetRoomsRequest.cs @@ -1,12 +1,10 @@ // 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.IO.Network; using osu.Game.Extensions; using osu.Game.Online.API; -using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Screens.OnlinePlay.Lounge.Components; namespace osu.Game.Online.Rooms @@ -35,25 +33,6 @@ namespace osu.Game.Online.Rooms return req; } - protected override void PostProcess() - { - base.PostProcess(); - - if (Response != null) - { - // API doesn't populate status so let's do it here. - foreach (var room in Response) - { - if (room.EndDate != null && DateTimeOffset.Now >= room.EndDate) - room.Status = new RoomStatusEnded(); - else if (room.HasPassword) - room.Status = new RoomStatusOpenPrivate(); - else - room.Status = new RoomStatusOpen(); - } - } - } - protected override string Target => "rooms"; } } diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index a26a3fcb34..486f70c0ed 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; +using System.Runtime.Serialization; using Newtonsoft.Json; using osu.Game.IO.Serialization.Converters; using osu.Game.Online.API.Requests.Responses; @@ -264,6 +265,18 @@ namespace osu.Game.Online.Rooms set => SetField(ref availability, value); } + [OnDeserialized] + private void onDeserialised(StreamingContext context) + { + // API doesn't populate status so let's do it here. + if (EndDate != null && DateTimeOffset.Now >= EndDate) + Status = new RoomStatusEnded(); + else if (HasPassword) + Status = new RoomStatusOpenPrivate(); + else + Status = new RoomStatusOpen(); + } + [JsonProperty("id")] private long? roomId; From 8dbf750446c2ccb23079b1923acffe944ee061fd Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 21 Nov 2024 18:38:56 +0900 Subject: [PATCH 915/924] Fix inspection --- .../Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs index 4ff8123fbb..4306fc1e6a 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs @@ -12,7 +12,7 @@ using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Playlists { - public class TestScenePlaylistsRoomSubScreen : OnlinePlayTestScene + public partial class TestScenePlaylistsRoomSubScreen : OnlinePlayTestScene { protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager; From 2138729c02fda9b619a3298a7ec3b63628141073 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Nov 2024 12:17:43 +0100 Subject: [PATCH 916/924] Do not show distance to next/previous object if said object is a banana shower The results of such a display were a little bit nonsensical. --- osu.Game.Rulesets.Catch/Edit/CatchHitObjectInspector.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectInspector.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectInspector.cs index c279354d9a..bfabcbdb83 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectInspector.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectInspector.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; +using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Compose.Components; @@ -21,14 +22,14 @@ namespace osu.Game.Rulesets.Catch.Edit HitObject? precedingObject = EditorBeatmap.HitObjects.LastOrDefault(ho => ho.GetEndTime() < firstSelectedHitObject.StartTime); HitObject? nextObject = EditorBeatmap.HitObjects.FirstOrDefault(ho => ho.StartTime > lastSelectedHitObject.GetEndTime()); - if (precedingObject != null) + if (precedingObject != null && precedingObject is not BananaShower) { double previousSnap = snapProvider.ReadCurrentDistanceSnap(precedingObject, firstSelectedHitObject); AddHeader("To previous"); AddValue($"{previousSnap:#,0.##}x"); } - if (nextObject != null) + if (nextObject != null && nextObject is not BananaShower) { double nextSnap = snapProvider.ReadCurrentDistanceSnap(lastSelectedHitObject, nextObject); AddHeader("To next"); From 6870c99eb2f1429500a68258daa647a3bb44f9db Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 21 Nov 2024 20:26:44 +0900 Subject: [PATCH 917/924] Enable NRT for multiplayer and playlists --- .../StatefulMultiplayerClientTest.cs | 2 +- .../OnlinePlay/TestSceneCatchUpSyncManager.cs | 12 +- ...MultiplayerGameplayLeaderboardTestScene.cs | 10 +- .../TestSceneCreateMultiplayerMatchButton.cs | 6 +- .../TestSceneDrawableRoomParticipantsList.cs | 10 +- .../TestSceneDrawableRoomPlaylist.cs | 13 +- .../TestSceneFreeModSelectOverlay.cs | 11 +- .../TestSceneGameplayChatDisplay.cs | 4 +- .../Multiplayer/TestSceneHostOnlyQueueMode.cs | 12 +- .../TestSceneLoungeRoomsContainer.cs | 12 +- .../TestSceneMatchBeatmapDetailArea.cs | 2 +- .../TestSceneMultiSpectatorLeaderboard.cs | 10 +- .../TestSceneMultiSpectatorScreen.cs | 2 +- ...TestSceneMultiplayerGameplayLeaderboard.cs | 4 +- ...ceneMultiplayerGameplayLeaderboardTeams.cs | 2 +- .../TestSceneMultiplayerLoungeSubScreen.cs | 23 +-- .../TestSceneMultiplayerMatchSongSelect.cs | 30 ++- .../TestSceneMultiplayerMatchSubScreen.cs | 33 ++- .../TestSceneMultiplayerPlaylist.cs | 2 +- .../TestSceneMultiplayerQueueList.cs | 2 +- .../TestSceneMultiplayerResults.cs | 4 +- .../TestSceneMultiplayerSpectateButton.cs | 2 +- ...TestSceneMultiplayerSpectatorPlayerGrid.cs | 4 +- .../TestSceneMultiplayerTeamResults.cs | 4 +- .../TestScenePlaylistsRoomSettingsPlaylist.cs | 10 +- .../TestScenePlaylistsSongSelect.cs | 29 ++- .../TestSceneStarRatingRangeDisplay.cs | 2 +- .../TestScenePlaylistsLoungeSubScreen.cs | 6 +- .../TestScenePlaylistsMatchSettingsOverlay.cs | 26 +-- .../TestScenePlaylistsParticipantsList.cs | 4 +- .../TestScenePlaylistsResultsScreen.cs | 15 +- .../TestScenePlaylistsRoomCreation.cs | 8 +- .../Components/ListingPollingComponent.cs | 2 +- .../OnlinePlay/DrawableRoomPlaylist.cs | 12 +- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 192 ++++++++++-------- .../Lounge/Components/FilterCriteria.cs | 8 +- .../OnlinePlay/Lounge/LoungeSubScreen.cs | 49 +++-- .../Match/Components/MatchLeaderboardScore.cs | 4 +- .../Match/Components/MatchTypePicker.cs | 9 +- .../Components/RoomAvailabilityPicker.cs | 4 +- .../OnlinePlay/Match/RoomModSelectOverlay.cs | 2 +- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 57 +++--- .../OnlinePlay/Match/UserModSelectButton.cs | 1 - .../CreateMultiplayerMatchButton.cs | 10 +- .../Multiplayer/GameplayChatDisplay.cs | 6 +- .../Match/MultiplayerCountdownButton.cs | 11 +- .../Match/MultiplayerReadyButton.cs | 28 +-- .../Multiplayer/MultiplayerLoungeSubScreen.cs | 12 +- .../Multiplayer/MultiplayerMatchSubScreen.cs | 4 +- .../Multiplayer/MultiplayerPlayerLoader.cs | 8 +- .../OnlinePlay/OngoingOperationTracker.cs | 4 +- .../Screens/OnlinePlay/OnlinePlayScreen.cs | 12 +- .../Playlists/PlaylistsRoomSubScreen.cs | 9 +- .../IOnlinePlayTestSceneDependencies.cs | 2 +- .../Visual/OnlinePlay/OnlinePlayTestScene.cs | 2 +- .../OnlinePlayTestSceneDependencies.cs | 4 +- 56 files changed, 357 insertions(+), 411 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs index 2e9170019c..559db16751 100644 --- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs +++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs @@ -73,7 +73,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer AddStep("create room initially in gameplay", () => { var newRoom = new Room(); - newRoom.CopyFrom(SelectedRoom.Value); + newRoom.CopyFrom(SelectedRoom.Value!); newRoom.RoomID = null; MultiplayerClient.RoomSetupAction = room => diff --git a/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs b/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs index 7b0b211899..8f6325c70b 100644 --- a/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs +++ b/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using NUnit.Framework; @@ -21,12 +19,12 @@ namespace osu.Game.Tests.OnlinePlay [HeadlessTest] public partial class TestSceneCatchUpSyncManager : OsuTestScene { - private GameplayClockContainer master; - private SpectatorSyncManager syncManager; + private GameplayClockContainer master = null!; + private SpectatorSyncManager syncManager = null!; - private Dictionary clocksById; - private SpectatorPlayerClock player1; - private SpectatorPlayerClock player2; + private Dictionary clocksById = null!; + private SpectatorPlayerClock player1 = null!; + private SpectatorPlayerClock player2 = null!; [SetUp] public void Setup() diff --git a/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs index 906eea9553..1eb08ad3c8 100644 --- a/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Collections.Specialized; @@ -31,7 +29,7 @@ namespace osu.Game.Tests.Visual.Multiplayer protected readonly BindableList MultiplayerUsers = new BindableList(); - protected MultiplayerGameplayLeaderboard Leaderboard { get; private set; } + protected MultiplayerGameplayLeaderboard? Leaderboard { get; private set; } protected virtual MultiplayerRoomUser CreateUser(int userId) => new MultiplayerRoomUser(userId); @@ -40,7 +38,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private readonly BindableList multiplayerUserIds = new BindableList(); private readonly BindableDictionary watchedUserStates = new BindableDictionary(); - private OsuConfigManager config; + private OsuConfigManager config = null!; private readonly Mock spectatorClient = new Mock(); private readonly Mock multiplayerClient = new Mock(); @@ -133,7 +131,7 @@ namespace osu.Game.Tests.Visual.Multiplayer LoadComponentAsync(Leaderboard = CreateLeaderboard(), Add); }); - AddUntilStep("wait for load", () => Leaderboard.IsLoaded); + AddUntilStep("wait for load", () => Leaderboard!.IsLoaded); AddStep("check watch requests were sent", () => { @@ -146,7 +144,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestScoreUpdates() { AddRepeatStep("update state", UpdateUserStatesRandomly, 100); - AddToggleStep("switch compact mode", expanded => Leaderboard.Expanded.Value = expanded); + AddToggleStep("switch compact mode", expanded => Leaderboard!.Expanded.Value = expanded); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneCreateMultiplayerMatchButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneCreateMultiplayerMatchButton.cs index 11b0f8b91c..6d6d30d517 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneCreateMultiplayerMatchButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneCreateMultiplayerMatchButton.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using NUnit.Framework; using osu.Framework.Graphics; @@ -12,7 +10,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneCreateMultiplayerMatchButton : MultiplayerTestScene { - private CreateMultiplayerMatchButton button; + private CreateMultiplayerMatchButton button = null!; public override void SetUpSteps() { @@ -29,7 +27,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestButtonEnableStateChanges() { - IDisposable joiningRoomOperation = null; + IDisposable joiningRoomOperation = null!; assertButtonEnableState(true); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs index b909b934b3..c1662bf944 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs @@ -119,7 +119,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("4 circles displayed", () => list.ChildrenOfType().Count() == 4); AddAssert("46 hidden users", () => list.ChildrenOfType().Single().Count == 46); - AddStep("remove from end", () => removeUserAt(SelectedRoom.Value.RecentParticipants.Count - 1)); + AddStep("remove from end", () => removeUserAt(SelectedRoom.Value!.RecentParticipants.Count - 1)); AddAssert("4 circles displayed", () => list.ChildrenOfType().Count() == 4); AddAssert("45 hidden users", () => list.ChildrenOfType().Single().Count == 45); @@ -138,18 +138,18 @@ namespace osu.Game.Tests.Visual.Multiplayer private void addUser(int id) { - SelectedRoom.Value.RecentParticipants = SelectedRoom.Value.RecentParticipants.Append(new APIUser + SelectedRoom.Value!.RecentParticipants = SelectedRoom.Value!.RecentParticipants.Append(new APIUser { Id = id, Username = $"User {id}" }).ToArray(); - SelectedRoom.Value.ParticipantCount++; + SelectedRoom.Value!.ParticipantCount++; } private void removeUserAt(int index) { - SelectedRoom.Value.RecentParticipants = SelectedRoom.Value.RecentParticipants.Where(u => !u.Equals(SelectedRoom.Value.RecentParticipants[index])).ToArray(); - SelectedRoom.Value.ParticipantCount--; + SelectedRoom.Value!.RecentParticipants = SelectedRoom.Value!.RecentParticipants.Where(u => !u.Equals(SelectedRoom.Value!.RecentParticipants[index])).ToArray(); + SelectedRoom.Value!.ParticipantCount--; } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 2ef56bd54e..18cd720bf2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics; @@ -39,9 +37,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneDrawableRoomPlaylist : MultiplayerTestScene { - private TestPlaylist playlist; - - private BeatmapManager manager; + private TestPlaylist playlist = null!; + private BeatmapManager manager = null!; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) @@ -199,14 +196,14 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestDownloadButtonHiddenWhenBeatmapExists() { - Live imported = null; + Live imported = null!; AddStep("import beatmap", () => { var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; Debug.Assert(beatmap.BeatmapSet != null); - imported = manager.Import(beatmap.BeatmapSet); + imported = manager.Import(beatmap.BeatmapSet)!; }); createPlaylistWithBeatmaps(() => imported.PerformRead(s => s.Beatmaps.Detach())); @@ -378,7 +375,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - private void createPlaylist(Action setupPlaylist = null) + private void createPlaylist(Action? setupPlaylist = null) { AddStep("create playlist", () => { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs index 4316653dde..fb54b89a4b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -26,9 +24,9 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneFreeModSelectOverlay : MultiplayerTestScene { - private FreeModSelectOverlay freeModSelectOverlay; - private FooterButtonFreeMods footerButtonFreeMods; - private ScreenFooter footer; + private FreeModSelectOverlay freeModSelectOverlay = null!; + private FooterButtonFreeMods footerButtonFreeMods = null!; + private ScreenFooter footer = null!; private readonly Bindable>> availableMods = new Bindable>>(); [BackgroundDependencyLoader] @@ -49,8 +47,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddToggleStep("toggle visibility", visible => { - if (freeModSelectOverlay != null) - freeModSelectOverlay.State.Value = visible ? Visibility.Visible : Visibility.Hidden; + freeModSelectOverlay.State.Value = visible ? Visibility.Visible : Visibility.Hidden; }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.cs index 6a500bbe55..235d142820 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using Moq; using NUnit.Framework; @@ -20,7 +18,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneGameplayChatDisplay : OsuManualInputManagerTestScene { - private GameplayChatDisplay chatDisplay; + private GameplayChatDisplay chatDisplay = null!; [Cached(typeof(ILocalUserPlayInfo))] private ILocalUserPlayInfo localUserInfo; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs index e0364fd65d..55c9e8142f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using NUnit.Framework; @@ -70,13 +68,13 @@ namespace osu.Game.Tests.Visual.Multiplayer { RunGameplay(); - IBeatmapInfo firstBeatmap = null; - AddStep("get first playlist item beatmap", () => firstBeatmap = MultiplayerClient.ServerAPIRoom?.Playlist[0].Beatmap); + IBeatmapInfo firstBeatmap = null!; + AddStep("get first playlist item beatmap", () => firstBeatmap = MultiplayerClient.ServerAPIRoom!.Playlist[0].Beatmap); selectNewItem(() => OtherBeatmap); - AddUntilStep("first playlist item hasn't changed", () => MultiplayerClient.ServerAPIRoom?.Playlist[0].Beatmap == firstBeatmap); - AddUntilStep("second playlist item changed", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Beatmap != firstBeatmap); + AddUntilStep("first playlist item hasn't changed", () => MultiplayerClient.ServerAPIRoom!.Playlist[0].Beatmap == firstBeatmap); + AddUntilStep("second playlist item changed", () => MultiplayerClient.ClientAPIRoom!.Playlist[1].Beatmap != firstBeatmap); } [Test] @@ -103,7 +101,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded); - BeatmapInfo otherBeatmap = null; + BeatmapInfo otherBeatmap = null!; AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(otherBeatmap = beatmap())); AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index e28790f2ed..797b69ec72 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; @@ -23,7 +21,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager; - private RoomsContainer container; + private RoomsContainer container = null!; public override void SetUpSteps() { @@ -65,7 +63,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("select first room", () => container.Rooms.First().TriggerClick()); AddAssert("first spotlight selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category == RoomCategory.Spotlight))); - AddStep("remove last room", () => RoomManager.RemoveRoom(RoomManager.Rooms.MinBy(r => r.RoomID))); + AddStep("remove last room", () => RoomManager.RemoveRoom(RoomManager.Rooms.MinBy(r => r.RoomID)!)); AddAssert("first spotlight still selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category == RoomCategory.Spotlight))); AddStep("remove spotlight room", () => RoomManager.RemoveRoom(RoomManager.Rooms.Single(r => r.Category == RoomCategory.Spotlight))); @@ -157,7 +155,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("add rooms", () => RoomManager.AddRooms(3, new CatchRuleset().RulesetInfo)); // Todo: What even is this case...? - AddStep("set empty filter criteria", () => container.Filter.Value = null); + AddStep("set empty filter criteria", () => container.Filter.Value = new FilterCriteria()); AddUntilStep("5 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 5); AddStep("filter osu! rooms", () => container.Filter.Value = new FilterCriteria { Ruleset = new OsuRuleset().RulesetInfo }); @@ -195,9 +193,9 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("add rooms", () => RoomManager.AddRooms(3, withPassword: true)); } - private bool checkRoomSelected(Room room) => SelectedRoom.Value == room; + private bool checkRoomSelected(Room? room) => SelectedRoom.Value == room; - private Room getRoomInFlow(int index) => + private Room? getRoomInFlow(int index) => (container.ChildrenOfType>().First().FlowingChildren.ElementAt(index) as DrawableRoom)?.Room; } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs index dd39db49c5..813a420cbd 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void createNewItem() { - SelectedRoom.Value.Playlist = SelectedRoom.Value.Playlist.Append(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) + SelectedRoom.Value!.Playlist = SelectedRoom.Value.Playlist.Append(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) { ID = SelectedRoom.Value.Playlist.Count, RulesetID = new OsuRuleset().RulesetInfo.OnlineID, diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 4bf2ebc1a4..3245b3c6a9 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -18,8 +16,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneMultiSpectatorLeaderboard : MultiplayerTestScene { - private Dictionary clocks; - private MultiSpectatorLeaderboard leaderboard; + private Dictionary clocks = null!; + private MultiSpectatorLeaderboard? leaderboard; [SetUpSteps] public override void SetUpSteps() @@ -55,13 +53,13 @@ namespace osu.Game.Tests.Visual.Multiplayer }, Add); }); - AddUntilStep("wait for load", () => leaderboard.IsLoaded); + AddUntilStep("wait for load", () => leaderboard!.IsLoaded); AddUntilStep("wait for user population", () => leaderboard.ChildrenOfType().Count() == 2); AddStep("add clock sources", () => { foreach ((int userId, var clock) in clocks) - leaderboard.AddClock(userId, clock); + leaderboard!.AddClock(userId, clock); }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 2b17f91e68..85352ada3c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -455,7 +455,7 @@ namespace osu.Game.Tests.Visual.Multiplayer applyToBeatmap?.Invoke(Beatmap.Value); - LoadScreen(spectatorScreen = new MultiSpectatorScreen(SelectedRoom.Value, playingUsers.ToArray())); + LoadScreen(spectatorScreen = new MultiSpectatorScreen(SelectedRoom.Value!, playingUsers.ToArray())); }); AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded && (!waitForPlayerLoad || spectatorScreen.AllPlayersLoaded)); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index bafe373d57..2f232a6164 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -37,10 +37,10 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestPerUserMods() { - AddStep("first user has no mods", () => Assert.That(((TestLeaderboard)Leaderboard).UserMods[0], Is.Empty)); + AddStep("first user has no mods", () => Assert.That(((TestLeaderboard)Leaderboard!).UserMods[0], Is.Empty)); AddStep("last user has NF mod", () => { - Assert.That(((TestLeaderboard)Leaderboard).UserMods[TOTAL_USERS - 1], Has.One.Items); + Assert.That(((TestLeaderboard)Leaderboard!).UserMods[TOTAL_USERS - 1], Has.One.Items); Assert.That(((TestLeaderboard)Leaderboard).UserMods[TOTAL_USERS - 1].Single(), Is.TypeOf()); }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs index 37662ffce8..3f1db308c0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { LoadComponentAsync(new MatchScoreDisplay { - Team1Score = { BindTarget = Leaderboard.TeamScores[0] }, + Team1Score = { BindTarget = Leaderboard!.TeamScores[0] }, Team2Score = { BindTarget = Leaderboard.TeamScores[1] } }, Add); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs index cf25e06799..9951f62c77 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Framework.Graphics.Containers; @@ -22,10 +20,9 @@ namespace osu.Game.Tests.Visual.Multiplayer { protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager; - private LoungeSubScreen loungeScreen; - - private Room lastJoinedRoom; - private string lastJoinedPassword; + private LoungeSubScreen loungeScreen = null!; + private Room? lastJoinedRoom; + private string? lastJoinedPassword; public override void SetUpSteps() { @@ -87,7 +84,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestJoinRoomWithIncorrectPasswordViaButton() { - DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null; + DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null; AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); AddStep("select room", () => InputManager.Key(Key.Down)); @@ -97,14 +94,14 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType().First().TriggerClick()); AddAssert("room not joined", () => loungeScreen.IsCurrentScreen()); - AddUntilStep("password prompt still visible", () => passwordEntryPopover.State.Value == Visibility.Visible); + AddUntilStep("password prompt still visible", () => passwordEntryPopover!.State.Value == Visibility.Visible); AddAssert("textbox still focused", () => InputManager.FocusedDrawable is OsuPasswordTextBox); } [Test] public void TestJoinRoomWithIncorrectPasswordViaEnter() { - DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null; + DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null; AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); AddStep("select room", () => InputManager.Key(Key.Down)); @@ -114,14 +111,14 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("press enter", () => InputManager.Key(Key.Enter)); AddAssert("room not joined", () => loungeScreen.IsCurrentScreen()); - AddUntilStep("password prompt still visible", () => passwordEntryPopover.State.Value == Visibility.Visible); + AddUntilStep("password prompt still visible", () => passwordEntryPopover!.State.Value == Visibility.Visible); AddAssert("textbox still focused", () => InputManager.FocusedDrawable is OsuPasswordTextBox); } [Test] public void TestJoinRoomWithCorrectPassword() { - DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null; + DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null; AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); AddStep("select room", () => InputManager.Key(Key.Down)); @@ -137,7 +134,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestJoinRoomWithPasswordViaKeyboardOnly() { - DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null; + DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null; AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); AddStep("select room", () => InputManager.Key(Key.Down)); @@ -150,7 +147,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("room join password correct", () => lastJoinedPassword == "password"); } - private void onRoomJoined(Room room, string password) + private void onRoomJoined(Room room, string? password) { lastJoinedRoom = room; lastJoinedPassword = password; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index bd635b1669..2a5f16d091 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -1,12 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -35,17 +32,16 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneMultiplayerMatchSongSelect : MultiplayerTestScene { - private BeatmapManager manager; - private RulesetStore rulesets; + private BeatmapManager manager = null!; + private RulesetStore rulesets = null!; - private IList beatmaps => importedBeatmapSet?.PerformRead(s => s.Beatmaps) ?? new List(); + private IList beatmaps => importedBeatmapSet.PerformRead(s => s.Beatmaps); - private TestMultiplayerMatchSongSelect songSelect; - - private Live importedBeatmapSet; + private TestMultiplayerMatchSongSelect songSelect = null!; + private Live importedBeatmapSet = null!; [Resolved] - private OsuConfigManager configManager { get; set; } + private OsuConfigManager configManager { get; set; } = null!; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) @@ -57,7 +53,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore()); Dependencies.Cache(Realm); - importedBeatmapSet = manager.Import(TestResources.CreateTestBeatmapSetInfo(8, rulesets.AvailableRulesets.ToArray())); + importedBeatmapSet = manager.Import(TestResources.CreateTestBeatmapSetInfo(8, rulesets.AvailableRulesets.ToArray()))!; Add(detachedBeatmapStore); } @@ -71,7 +67,7 @@ namespace osu.Game.Tests.Visual.Multiplayer SelectedMods.SetDefault(); }); - AddStep("create song select", () => LoadScreen(songSelect = new TestMultiplayerMatchSongSelect(SelectedRoom.Value))); + AddStep("create song select", () => LoadScreen(songSelect = new TestMultiplayerMatchSongSelect(SelectedRoom.Value!))); AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && songSelect.BeatmapSetsLoaded); } @@ -88,7 +84,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestBeatmapConfirmed() { - BeatmapInfo selectedBeatmap = null; + BeatmapInfo selectedBeatmap = null!; setUp(); @@ -117,8 +113,8 @@ namespace osu.Game.Tests.Visual.Multiplayer setUp(); AddStep("change ruleset", () => Ruleset.Value = new OsuRuleset().RulesetInfo); - AddStep($"select {allowedMod.ReadableName()} as allowed", () => songSelect.FreeMods.Value = new[] { (Mod)Activator.CreateInstance(allowedMod) }); - AddStep($"select {requiredMod.ReadableName()} as required", () => songSelect.Mods.Value = new[] { (Mod)Activator.CreateInstance(requiredMod) }); + AddStep($"select {allowedMod.ReadableName()} as allowed", () => songSelect.FreeMods.Value = new[] { (Mod)Activator.CreateInstance(allowedMod)! }); + AddStep($"select {requiredMod.ReadableName()} as required", () => songSelect.Mods.Value = new[] { (Mod)Activator.CreateInstance(requiredMod)! }); AddAssert("freemods empty", () => songSelect.FreeMods.Value.Count == 0); @@ -141,7 +137,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create song select", () => { - SelectedRoom.Value.Playlist.Single().RulesetID = 2; + SelectedRoom.Value!.Playlist.Single().RulesetID = 2; songSelect = new TestMultiplayerMatchSongSelect(SelectedRoom.Value, SelectedRoom.Value.Playlist.Single()); songSelect.OnLoadComplete += _ => Ruleset.Value = new TaikoRuleset().RulesetInfo; LoadScreen(songSelect); @@ -171,7 +167,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public new BeatmapCarousel Carousel => base.Carousel; - public TestMultiplayerMatchSongSelect(Room room, [CanBeNull] PlaylistItem itemToEdit = null) + public TestMultiplayerMatchSongSelect(Room room, PlaylistItem? itemToEdit = null) : base(room, itemToEdit) { } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index abcb591cf6..8ea52f8099 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -1,10 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; -using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -42,10 +39,9 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneMultiplayerMatchSubScreen : MultiplayerTestScene { - private MultiplayerMatchSubScreen screen; - - private BeatmapManager beatmaps; - private BeatmapSetInfo importedSet; + private MultiplayerMatchSubScreen screen = null!; + private BeatmapManager beatmaps = null!; + private BeatmapSetInfo importedSet = null!; public TestSceneMultiplayerMatchSubScreen() : base(false) @@ -70,7 +66,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("load match", () => { SelectedRoom.Value = new Room { Name = "Test Room" }; - LoadScreen(screen = new TestMultiplayerMatchSubScreen(SelectedRoom.Value)); + LoadScreen(screen = new TestMultiplayerMatchSubScreen(SelectedRoom.Value!)); }); AddUntilStep("wait for load", () => screen.IsCurrentScreen()); @@ -81,7 +77,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add playlist item", () => { - SelectedRoom.Value.Playlist = + SelectedRoom.Value!.Playlist = [ new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) { @@ -100,7 +96,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add playlist item", () => { - SelectedRoom.Value.Playlist = + SelectedRoom.Value!.Playlist = [ new PlaylistItem(new TestBeatmap(new TaikoRuleset().RulesetInfo).BeatmapInfo) { @@ -125,7 +121,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("set playlist", () => { - SelectedRoom.Value.Playlist = + SelectedRoom.Value!.Playlist = [ new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) { @@ -142,7 +138,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("set playlist", () => { - SelectedRoom.Value.Playlist = + SelectedRoom.Value!.Playlist = [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo) { @@ -173,7 +169,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add playlist item with allowed mod", () => { - SelectedRoom.Value.Playlist = + SelectedRoom.Value!.Playlist = [ new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) { @@ -202,7 +198,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add playlist item with allowed mod", () => { - SelectedRoom.Value.Playlist = + SelectedRoom.Value!.Playlist = [ new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) { @@ -226,7 +222,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add playlist item with no allowed mods", () => { - SelectedRoom.Value.Playlist = + SelectedRoom.Value!.Playlist = [ new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) { @@ -249,7 +245,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add two playlist items", () => { - SelectedRoom.Value.Playlist = + SelectedRoom.Value!.Playlist = [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo) { @@ -285,7 +281,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add playlist item", () => { - SelectedRoom.Value.Playlist = + SelectedRoom.Value!.Playlist = [ new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) { @@ -320,8 +316,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private partial class TestMultiplayerMatchSubScreen : MultiplayerMatchSubScreen { [Resolved(canBeNull: true)] - [CanBeNull] - private IDialogOverlay dialogOverlay { get; set; } + private IDialogOverlay? dialogOverlay { get; set; } public TestMultiplayerMatchSubScreen(Room room) : base(room) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index 518ea2b511..36f5bba384 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create list", () => { - Child = list = new MultiplayerPlaylist(SelectedRoom.Value) + Child = list = new MultiplayerPlaylist(SelectedRoom.Value!) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index 50f55ebde0..3ef2e4ecf4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create playlist", () => { - Child = playlist = new MultiplayerQueueList(SelectedRoom.Value) + Child = playlist = new MultiplayerQueueList(SelectedRoom.Value!) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs index f030466fff..076c2c3cdd 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Osu; @@ -16,7 +14,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestDisplayResults() { - MultiplayerResultsScreen screen = null; + MultiplayerResultsScreen screen = null!; AddStep("show results screen", () => { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index b4f55bb6a3..1429f86164 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create button", () => { - PlaylistItem item = SelectedRoom.Value.Playlist.First(); + PlaylistItem item = SelectedRoom.Value!.Playlist.First(); AvailabilityTracker.SelectedItem.Value = item; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorPlayerGrid.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorPlayerGrid.cs index 8fd05dcaa9..2f461ad706 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorPlayerGrid.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorPlayerGrid.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; @@ -16,7 +14,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneMultiplayerSpectatorPlayerGrid : OsuManualInputManagerTestScene { - private PlayerGrid grid; + private PlayerGrid grid = null!; [SetUp] public void Setup() => Schedule(() => diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs index 68fd39a066..f77b6e8c68 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Bindables; @@ -32,7 +30,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [TestCase(1048576, 1048576)] public void TestDisplayTeamResults(int team1Score, int team2Score) { - MultiplayerResultsScreen screen = null; + MultiplayerResultsScreen screen = null!; AddStep("show results screen", () => { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs index ae27db0dd1..cd41884ba7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -28,18 +26,18 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestScenePlaylistsRoomSettingsPlaylist : OnlinePlayTestScene { - private TestPlaylist playlist; + private TestPlaylist playlist = null!; [Test] public void TestItemRemovedOnDeletion() { - PlaylistItem selectedItem = null; + PlaylistItem selectedItem = null!; createPlaylist(); moveToItem(0); AddStep("click", () => InputManager.Click(MouseButton.Left)); - AddStep("retrieve selection", () => selectedItem = playlist.SelectedItem.Value); + AddStep("retrieve selection", () => selectedItem = playlist.SelectedItem.Value!); moveToDeleteButton(0); AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); @@ -122,7 +120,7 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0), offset); }); - private void createPlaylist(Action setupPlaylist = null) + private void createPlaylist(Action? setupPlaylist = null) { AddStep("create playlist", () => { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index b55102518a..fa1909254a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using NUnit.Framework; @@ -27,9 +25,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestScenePlaylistsSongSelect : OnlinePlayTestScene { - private BeatmapManager manager; - - private TestPlaylistsSongSelect songSelect; + private BeatmapManager manager = null!; + private TestPlaylistsSongSelect songSelect = null!; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) @@ -60,7 +57,7 @@ namespace osu.Game.Tests.Visual.Multiplayer SelectedMods.Value = Array.Empty(); }); - AddStep("create song select", () => LoadScreen(songSelect = new TestPlaylistsSongSelect(SelectedRoom.Value))); + AddStep("create song select", () => LoadScreen(songSelect = new TestPlaylistsSongSelect(SelectedRoom.Value!))); AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && songSelect.BeatmapSetsLoaded); } @@ -68,14 +65,14 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestItemAddedIfEmptyOnStart() { AddStep("finalise selection", () => songSelect.FinaliseSelection()); - AddAssert("playlist has 1 item", () => SelectedRoom.Value.Playlist.Count == 1); + AddAssert("playlist has 1 item", () => SelectedRoom.Value!.Playlist.Count == 1); } [Test] public void TestItemAddedWhenCreateNewItemClicked() { AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!()); - AddAssert("playlist has 1 item", () => SelectedRoom.Value.Playlist.Count == 1); + AddAssert("playlist has 1 item", () => SelectedRoom.Value!.Playlist.Count == 1); } [Test] @@ -83,7 +80,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!()); AddStep("finalise selection", () => songSelect.FinaliseSelection()); - AddAssert("playlist has 1 item", () => SelectedRoom.Value.Playlist.Count == 1); + AddAssert("playlist has 1 item", () => SelectedRoom.Value!.Playlist.Count == 1); } [Test] @@ -91,7 +88,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!()); AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!()); - AddAssert("playlist has 2 items", () => SelectedRoom.Value.Playlist.Count == 2); + AddAssert("playlist has 2 items", () => SelectedRoom.Value!.Playlist.Count == 2); } [Test] @@ -99,10 +96,10 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!()); AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!()); - AddStep("rearrange", () => SelectedRoom.Value.Playlist = SelectedRoom.Value.Playlist.Skip(1).Append(SelectedRoom.Value.Playlist[0]).ToArray()); + AddStep("rearrange", () => SelectedRoom.Value!.Playlist = SelectedRoom.Value!.Playlist.Skip(1).Append(SelectedRoom.Value!.Playlist[0]).ToArray()); AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!()); - AddAssert("new item has id 2", () => SelectedRoom.Value.Playlist.Last().ID == 2); + AddAssert("new item has id 2", () => SelectedRoom.Value!.Playlist.Last().ID == 2); } /// @@ -118,13 +115,13 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("item 1 has rate 1.5", () => { - var mod = (OsuModDoubleTime)SelectedRoom.Value.Playlist.First().RequiredMods[0].ToMod(new OsuRuleset()); + var mod = (OsuModDoubleTime)SelectedRoom.Value!.Playlist.First().RequiredMods[0].ToMod(new OsuRuleset()); return Precision.AlmostEquals(1.5, mod.SpeedChange.Value); }); AddAssert("item 2 has rate 2", () => { - var mod = (OsuModDoubleTime)SelectedRoom.Value.Playlist.Last().RequiredMods[0].ToMod(new OsuRuleset()); + var mod = (OsuModDoubleTime)SelectedRoom.Value!.Playlist.Last().RequiredMods[0].ToMod(new OsuRuleset()); return Precision.AlmostEquals(2, mod.SpeedChange.Value); }); } @@ -135,7 +132,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestGlobalModInstancesNotRetained() { - OsuModDoubleTime mod = null; + OsuModDoubleTime mod = null!; AddStep("set dt mod and store", () => { @@ -150,7 +147,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("change stored mod rate", () => mod.SpeedChange.Value = 2); AddAssert("item has rate 1.5", () => { - var m = (OsuModDoubleTime)SelectedRoom.Value.Playlist.First().RequiredMods[0].ToMod(new OsuRuleset()); + var m = (OsuModDoubleTime)SelectedRoom.Value!.Playlist.First().RequiredMods[0].ToMod(new OsuRuleset()); return Precision.AlmostEquals(1.5, m.SpeedChange.Value); }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs index 195b40bd13..88afef7de2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("set playlist", () => { - SelectedRoom.Value.Playlist = + SelectedRoom.Value!.Playlist = [ new PlaylistItem(new BeatmapInfo { StarRating = min }), new PlaylistItem(new BeatmapInfo { StarRating = max }), diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs index 0c536cb1d4..8c8dc8d69a 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Framework.Bindables; @@ -21,7 +19,7 @@ namespace osu.Game.Tests.Visual.Playlists { protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager; - private TestLoungeSubScreen loungeScreen; + private TestLoungeSubScreen loungeScreen = null!; public override void SetUpSteps() { @@ -97,7 +95,7 @@ namespace osu.Game.Tests.Visual.Playlists private partial class TestLoungeSubScreen : PlaylistsLoungeSubScreen { - public new Bindable SelectedRoom => base.SelectedRoom; + public new Bindable SelectedRoom => base.SelectedRoom; } } } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs index 321f563140..5868331451 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Playlists { SelectedRoom.Value = new Room(); - Child = settings = new TestRoomSettings(SelectedRoom.Value) + Child = settings = new TestRoomSettings(SelectedRoom.Value!) { RelativeSizeAxes = Axes.Both, State = { Value = Visibility.Visible } @@ -45,19 +45,19 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("clear name and beatmap", () => { - SelectedRoom.Value.Name = ""; - SelectedRoom.Value.Playlist = []; + SelectedRoom.Value!.Name = ""; + SelectedRoom.Value!.Playlist = []; }); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); - AddStep("set name", () => SelectedRoom.Value.Name = "Room name"); + AddStep("set name", () => SelectedRoom.Value!.Name = "Room name"); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); - AddStep("set beatmap", () => SelectedRoom.Value.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)]); + AddStep("set beatmap", () => SelectedRoom.Value!.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)]); AddAssert("button enabled", () => settings.ApplyButton.Enabled.Value); - AddStep("clear name", () => SelectedRoom.Value.Name = ""); + AddStep("clear name", () => SelectedRoom.Value!.Name = ""); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); } @@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Playlists { settings.NameField.Current.Value = expected_name; settings.DurationField.Current.Value = expectedDuration; - SelectedRoom.Value.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)]; + SelectedRoom.Value!.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)]; RoomManager.CreateRequested = r => { @@ -98,8 +98,8 @@ namespace osu.Game.Tests.Visual.Playlists { var beatmap = CreateBeatmap(Ruleset.Value).BeatmapInfo; - SelectedRoom.Value.Name = "Test Room"; - SelectedRoom.Value.Playlist = [new PlaylistItem(beatmap)]; + SelectedRoom.Value!.Name = "Test Room"; + SelectedRoom.Value!.Playlist = [new PlaylistItem(beatmap)]; errorMessage = $"{not_found_prefix} {beatmap.OnlineID}"; @@ -107,13 +107,13 @@ namespace osu.Game.Tests.Visual.Playlists }); AddAssert("error not displayed", () => !settings.ErrorText.IsPresent); - AddAssert("playlist item valid", () => SelectedRoom.Value.Playlist[0].Valid.Value); + AddAssert("playlist item valid", () => SelectedRoom.Value!.Playlist[0].Valid.Value); AddStep("create room", () => settings.ApplyButton.Action.Invoke()); AddAssert("error displayed", () => settings.ErrorText.IsPresent); AddAssert("error has custom text", () => settings.ErrorText.Text != errorMessage); - AddAssert("playlist item marked invalid", () => !SelectedRoom.Value.Playlist[0].Valid.Value); + AddAssert("playlist item marked invalid", () => !SelectedRoom.Value!.Playlist[0].Valid.Value); } [Test] @@ -125,8 +125,8 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("setup", () => { - SelectedRoom.Value.Name = "Test Room"; - SelectedRoom.Value.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)]; + SelectedRoom.Value!.Name = "Test Room"; + SelectedRoom.Value!.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)]; RoomManager.CreateRequested = _ => failText; }); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs index f72a2cd655..c60b208ffc 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("create component", () => { - Child = new ParticipantsDisplay(SelectedRoom.Value, Direction.Horizontal) + Child = new ParticipantsDisplay(SelectedRoom.Value!, Direction.Horizontal) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("create component", () => { - Child = new ParticipantsDisplay(SelectedRoom.Value, Direction.Vertical) + Child = new ParticipantsDisplay(SelectedRoom.Value!, Direction.Vertical) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index 7527647b9c..5977e67b0e 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -1,13 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; using System.Net; -using JetBrains.Annotations; using Newtonsoft.Json.Linq; using NUnit.Framework; using osu.Framework.Graphics.Containers; @@ -34,14 +31,14 @@ namespace osu.Game.Tests.Visual.Playlists private const int scores_per_result = 10; private const int real_user_position = 200; - private TestResultsScreen resultsScreen; + private TestResultsScreen resultsScreen = null!; private int lowestScoreId; // Score ID of the lowest score in the list. private int highestScoreId; // Score ID of the highest score in the list. private bool requestComplete; private int totalCount; - private ScoreInfo userScore; + private ScoreInfo userScore = null!; [SetUpSteps] public override void SetUpSteps() @@ -205,7 +202,7 @@ namespace osu.Game.Tests.Visual.Playlists AddAssert("placeholder shown", () => this.ChildrenOfType().Count(), () => Is.EqualTo(1)); } - private void createResults(Func getScore = null) + private void createResults(Func? getScore = null) { AddStep("load results", () => { @@ -229,7 +226,7 @@ namespace osu.Game.Tests.Visual.Playlists AddWaitStep("wait for display", 5); } - private void bindHandler(bool delayed = false, ScoreInfo userScore = null, bool failRequests = false, bool noScores = false) => ((DummyAPIAccess)API).HandleRequest = request => + private void bindHandler(bool delayed = false, ScoreInfo? userScore = null, bool failRequests = false, bool noScores = false) => ((DummyAPIAccess)API).HandleRequest = request => { // pre-check for requests we should be handling (as they are scheduled below). switch (request) @@ -286,7 +283,7 @@ namespace osu.Game.Tests.Visual.Playlists req.TriggerFailure(new WebException("Failed.")); } - private MultiplayerScore createUserResponse([NotNull] ScoreInfo userScore) + private MultiplayerScore createUserResponse(ScoreInfo userScore) { var multiplayerUserScore = new MultiplayerScore { @@ -420,7 +417,7 @@ namespace osu.Game.Tests.Visual.Playlists public new LoadingSpinner RightSpinner => base.RightSpinner; public new ScorePanelList ScorePanelList => base.ScorePanelList; - public TestResultsScreen([CanBeNull] ScoreInfo score, int roomId, PlaylistItem playlistItem) + public TestResultsScreen(ScoreInfo? score, int roomId, PlaylistItem playlistItem) : base(score, roomId, playlistItem) { AllowRetry = true; diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index 6a53019b76..0270840597 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Playlists importBeatmap(); - AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(SelectedRoom.Value))); + AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(SelectedRoom.Value!))); AddUntilStep("wait for load", () => match.IsCurrentScreen()); } @@ -119,7 +119,7 @@ namespace osu.Game.Tests.Visual.Playlists ]; }); - AddAssert("first playlist item selected", () => match.SelectedItem.Value == SelectedRoom.Value.Playlist[0]); + AddAssert("first playlist item selected", () => match.SelectedItem.Value == SelectedRoom.Value!.Playlist[0]); } [Test] @@ -199,7 +199,7 @@ namespace osu.Game.Tests.Visual.Playlists private void setupAndCreateRoom(Action room) { - AddStep("setup room", () => room(SelectedRoom.Value)); + AddStep("setup room", () => room(SelectedRoom.Value!)); AddStep("click create button", () => { @@ -218,7 +218,7 @@ namespace osu.Game.Tests.Visual.Playlists private partial class TestPlaylistsRoomSubScreen : PlaylistsRoomSubScreen { - public new Bindable SelectedItem => base.SelectedItem; + public new Bindable SelectedItem => base.SelectedItem; public new Bindable Beatmap => base.Beatmap; diff --git a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs index 67c3586a35..b10ce8ed1b 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.OnlinePlay.Components public IBindable InitialRoomsReceived => initialRoomsReceived; private readonly Bindable initialRoomsReceived = new Bindable(); - public readonly Bindable Filter = new Bindable(); + public readonly Bindable Filter = new Bindable(); [BackgroundDependencyLoader] private void load() diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index 5a1648c91f..207e0bdf55 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Bindables; @@ -26,22 +24,22 @@ namespace osu.Game.Screens.OnlinePlay /// The currently-selected item. Selection is visually represented with a border. /// May be updated by clicking playlist items if is true. /// - public readonly Bindable SelectedItem = new Bindable(); + public readonly Bindable SelectedItem = new Bindable(); /// /// Invoked when an item is requested to be deleted. /// - public Action RequestDeletion; + public Action? RequestDeletion; /// /// Invoked when an item requests its results to be shown. /// - public Action RequestResults; + public Action? RequestResults; /// /// Invoked when an item requests to be edited. /// - public Action RequestEdit; + public Action? RequestEdit; private bool allowReordering; @@ -235,7 +233,7 @@ namespace osu.Game.Screens.OnlinePlay { var visibleItems = ListContainer.AsEnumerable().Where(r => r.IsPresent); - PlaylistItem item; + PlaylistItem? item; if (SelectedItem.Value == null) item = visibleItems.FirstOrDefault()?.Model; diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 43ffaf947e..7a773bb116 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -54,23 +52,23 @@ namespace osu.Game.Screens.OnlinePlay /// /// Invoked when this item requests to be deleted. /// - public Action RequestDeletion; + public Action? RequestDeletion; /// /// Invoked when this item requests its results to be shown. /// - public Action RequestResults; + public Action? RequestResults; /// /// Invoked when this item requests to be edited. /// - public Action RequestEdit; + public Action? RequestEdit; /// /// The currently-selected item, used to show a border around this item. /// May be updated by this item if is true. /// - public readonly Bindable SelectedItem = new Bindable(); + public readonly Bindable SelectedItem = new Bindable(); public readonly PlaylistItem Item; @@ -79,48 +77,48 @@ namespace osu.Game.Screens.OnlinePlay private readonly DelayedLoadWrapper onScreenLoader = new DelayedLoadWrapper(Empty) { RelativeSizeAxes = Axes.Both }; private readonly IBindable valid = new Bindable(); - private IBeatmapInfo beatmap; - private IRulesetInfo ruleset; + private IBeatmapInfo? beatmap; + private IRulesetInfo? ruleset; private Mod[] requiredMods = Array.Empty(); - private Container borderContainer; - private FillFlowContainer difficultyIconContainer; - private LinkFlowContainer beatmapText; - private LinkFlowContainer authorText; - private ExplicitContentBeatmapBadge explicitContent; - private ModDisplay modDisplay; - private FillFlowContainer buttonsFlow; - private UpdateableAvatar ownerAvatar; - private Drawable showResultsButton; - private Drawable editButton; - private Drawable removeButton; - private PanelBackground panelBackground; - private FillFlowContainer mainFillFlow; - private BeatmapCardThumbnail thumbnail; + private Container? borderContainer; + private FillFlowContainer? difficultyIconContainer; + private LinkFlowContainer? beatmapText; + private LinkFlowContainer? authorText; + private ExplicitContentBeatmapBadge? explicitContent; + private ModDisplay? modDisplay; + private FillFlowContainer? buttonsFlow; + private UpdateableAvatar? ownerAvatar; + private Drawable? showResultsButton; + private Drawable? editButton; + private Drawable? removeButton; + private PanelBackground? panelBackground; + private FillFlowContainer? mainFillFlow; + private BeatmapCardThumbnail? thumbnail; [Resolved] - private RealmAccess realm { get; set; } + private RealmAccess realm { get; set; } = null!; [Resolved] - private RulesetStore rulesets { get; set; } + private RulesetStore rulesets { get; set; } = null!; [Resolved] - private BeatmapManager beatmaps { get; set; } + private BeatmapManager beatmaps { get; set; } = null!; [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; [Resolved] - private UserLookupCache userLookupCache { get; set; } + private UserLookupCache userLookupCache { get; set; } = null!; [Resolved] - private BeatmapLookupCache beatmapLookupCache { get; set; } + private BeatmapLookupCache beatmapLookupCache { get; set; } = null!; [Resolved(CanBeNull = true)] - private BeatmapSetOverlay beatmapOverlay { get; set; } + private BeatmapSetOverlay? beatmapOverlay { get; set; } [Resolved(CanBeNull = true)] - private ManageCollectionsDialog manageCollectionsDialog { get; set; } + private ManageCollectionsDialog? manageCollectionsDialog { get; set; } public DrawableRoomPlaylistItem(PlaylistItem item) : base(item) @@ -136,7 +134,8 @@ namespace osu.Game.Screens.OnlinePlay [BackgroundDependencyLoader] private void load() { - borderContainer.BorderColour = colours.Yellow; + if (borderContainer != null) + borderContainer.BorderColour = colours.Yellow; ruleset = rulesets.GetRuleset(Item.RulesetID); var rulesetInstance = ruleset?.CreateInstance(); @@ -163,7 +162,8 @@ namespace osu.Game.Screens.OnlinePlay return; } - borderContainer.BorderThickness = IsSelectedItem ? border_thickness : 0; + if (borderContainer != null) + borderContainer.BorderThickness = IsSelectedItem ? border_thickness : 0; }, true); valid.BindValueChanged(_ => Scheduler.AddOnce(refresh)); @@ -177,7 +177,11 @@ namespace osu.Game.Screens.OnlinePlay if (showItemOwner) { var foundUser = await userLookupCache.GetUserAsync(Item.OwnerID).ConfigureAwait(false); - Schedule(() => ownerAvatar.User = foundUser); + Schedule(() => + { + if (ownerAvatar != null) + ownerAvatar.User = foundUser; + }); } beatmap = await beatmapLookupCache.GetBeatmapAsync(Item.Beatmap.OnlineID).ConfigureAwait(false); @@ -278,69 +282,89 @@ namespace osu.Game.Screens.OnlinePlay private void refresh() { - if (!valid.Value) + if (borderContainer != null) { - borderContainer.BorderThickness = border_thickness; - borderContainer.BorderColour = colours.Red; - } - - if (beatmap != null) - { - difficultyIconContainer.Children = new Drawable[] + if (!valid.Value) { - thumbnail = new BeatmapCardThumbnail(beatmap.BeatmapSet!, (IBeatmapSetOnlineInfo)beatmap.BeatmapSet!) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Width = 60, - Masking = true, - CornerRadius = 10, - RelativeSizeAxes = Axes.Y, - Dimmed = { Value = IsHovered } - }, - new DifficultyIcon(beatmap, ruleset, requiredMods) - { - Size = new Vector2(24), - TooltipType = DifficultyIconTooltipType.Extended, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - }; + borderContainer.BorderThickness = border_thickness; + borderContainer.BorderColour = colours.Red; + } } - else - difficultyIconContainer.Clear(); - panelBackground.Beatmap.Value = beatmap; - - beatmapText.Clear(); - - if (beatmap != null) + if (difficultyIconContainer != null) { - beatmapText.AddLink(beatmap.GetDisplayTitleRomanisable(includeCreator: false), - LinkAction.OpenBeatmap, - beatmap.OnlineID.ToString(), - null, - text => + if (beatmap != null) + { + difficultyIconContainer.Children = new Drawable[] { - text.Truncate = true; - }); + thumbnail = new BeatmapCardThumbnail(beatmap.BeatmapSet!, (IBeatmapSetOnlineInfo)beatmap.BeatmapSet!) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Width = 60, + Masking = true, + CornerRadius = 10, + RelativeSizeAxes = Axes.Y, + Dimmed = { Value = IsHovered } + }, + new DifficultyIcon(beatmap, ruleset, requiredMods) + { + Size = new Vector2(24), + TooltipType = DifficultyIconTooltipType.Extended, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + }; + } + else + difficultyIconContainer.Clear(); } - authorText.Clear(); + if (panelBackground != null) + panelBackground.Beatmap.Value = beatmap; - if (!string.IsNullOrEmpty(beatmap?.Metadata.Author.Username)) + if (beatmapText != null) { - authorText.AddText("mapped by "); - authorText.AddUserLink(beatmap.Metadata.Author); + beatmapText.Clear(); + + if (beatmap != null) + { + beatmapText.AddLink(beatmap.GetDisplayTitleRomanisable(includeCreator: false), + LinkAction.OpenBeatmap, + beatmap.OnlineID.ToString(), + null, + text => + { + text.Truncate = true; + }); + } } - bool hasExplicitContent = (beatmap?.BeatmapSet as IBeatmapSetOnlineInfo)?.HasExplicitContent == true; - explicitContent.Alpha = hasExplicitContent ? 1 : 0; + if (authorText != null) + { + authorText.Clear(); - modDisplay.Current.Value = requiredMods.ToArray(); + if (!string.IsNullOrEmpty(beatmap?.Metadata.Author.Username)) + { + authorText.AddText("mapped by "); + authorText.AddUserLink(beatmap.Metadata.Author); + } + } - buttonsFlow.Clear(); - buttonsFlow.ChildrenEnumerable = createButtons(); + if (explicitContent != null) + { + bool hasExplicitContent = (beatmap?.BeatmapSet as IBeatmapSetOnlineInfo)?.HasExplicitContent == true; + explicitContent.Alpha = hasExplicitContent ? 1 : 0; + } + + if (modDisplay != null) + modDisplay.Current.Value = requiredMods.ToArray(); + + if (buttonsFlow != null) + { + buttonsFlow.Clear(); + buttonsFlow.ChildrenEnumerable = createButtons(); + } difficultyIconContainer.FadeInFromZero(500, Easing.OutQuint); mainFillFlow.FadeInFromZero(500, Easing.OutQuint); @@ -601,7 +625,7 @@ namespace osu.Game.Screens.OnlinePlay private readonly IBeatmapInfo beatmap; [Resolved] - private BeatmapManager beatmapManager { get; set; } + private BeatmapManager beatmapManager { get; set; } = null!; // required for download tracking, as this button hides itself. can probably be removed with a bit of consideration. public override bool IsPresent => true; @@ -656,7 +680,7 @@ namespace osu.Game.Screens.OnlinePlay // For now, this is the same implementation as in PanelBackground, but supports a beatmap info rather than a working beatmap private partial class PanelBackground : Container // todo: should be a buffered container (https://github.com/ppy/osu-framework/issues/3222) { - public readonly Bindable Beatmap = new Bindable(); + public readonly Bindable Beatmap = new Bindable(); public PanelBackground() { diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs index 3a687ad351..0f63718355 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs @@ -1,18 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { public class FilterCriteria { - public string SearchString; + public string SearchString = string.Empty; public RoomStatusFilter Status; - public string Category; - public RulesetInfo Ruleset; + public string Category = string.Empty; + public RulesetInfo? Ruleset; public RoomPermissionsFilter Permissions; } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index 90288a1067..ac8caa6b88 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -1,13 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; @@ -54,42 +51,42 @@ namespace osu.Game.Screens.OnlinePlay.Lounge AutoSizeAxes = Axes.Both }; - protected ListingPollingComponent ListingPollingComponent { get; private set; } + protected ListingPollingComponent ListingPollingComponent { get; private set; } = null!; - protected readonly Bindable SelectedRoom = new Bindable(); + protected readonly Bindable SelectedRoom = new Bindable(); [Resolved] - private MusicController music { get; set; } + private MusicController music { get; set; } = null!; [Resolved(CanBeNull = true)] - private OngoingOperationTracker ongoingOperationTracker { get; set; } + private OngoingOperationTracker? ongoingOperationTracker { get; set; } [Resolved] - private IBindable ruleset { get; set; } + private IBindable ruleset { get; set; } = null!; [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; - [CanBeNull] - private IDisposable joiningRoomOperation { get; set; } - - [CanBeNull] - private LeasedBindable selectionLease; + [Resolved(CanBeNull = true)] + private IdleTracker? idleTracker { get; set; } [Resolved] - protected OsuConfigManager Config { get; private set; } + protected OsuConfigManager Config { get; private set; } = null!; - private readonly Bindable filter = new Bindable(new FilterCriteria()); + private IDisposable? joiningRoomOperation { get; set; } + private LeasedBindable? selectionLease; + + private readonly Bindable filter = new Bindable(); private readonly IBindable operationInProgress = new Bindable(); private readonly IBindable isIdle = new BindableBool(); - private PopoverContainer popoverContainer; - private LoadingLayer loadingLayer; - private RoomsContainer roomsContainer; - private SearchTextBox searchTextBox; - private Dropdown statusDropdown; + private PopoverContainer popoverContainer = null!; + private LoadingLayer loadingLayer = null!; + private RoomsContainer roomsContainer = null!; + private SearchTextBox searchTextBox = null!; + private Dropdown statusDropdown = null!; [BackgroundDependencyLoader(true)] - private void load([CanBeNull] IdleTracker idleTracker) + private void load() { const float controls_area_height = 25f; @@ -208,7 +205,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge public void UpdateFilter() => Scheduler.AddOnce(updateFilter); - private ScheduledDelegate scheduledFilterUpdate; + private ScheduledDelegate? scheduledFilterUpdate; private void updateFilterDebounced() { @@ -262,7 +259,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge if (SelectedRoom.Value?.RoomID == null) SelectedRoom.Value = new Room(); - music?.EnsurePlayingSomething(); + music.EnsurePlayingSomething(); onReturning(); } @@ -299,7 +296,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge popoverContainer.HidePopover(); } - public virtual void Join(Room room, string password, Action onSuccess = null, Action onFailure = null) => Schedule(() => + public virtual void Join(Room room, string? password, Action? onSuccess = null, Action? onFailure = null) => Schedule(() => { if (joiningRoomOperation != null) return; @@ -364,7 +361,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge /// Push a room as a new subscreen. /// /// An optional template to use when creating the room. - public void Open(Room room = null) => Schedule(() => + public void Open(Room? room = null) => Schedule(() => { // Handles the case where a room is clicked 3 times in quick succession if (!this.IsCurrentScreen()) diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboardScore.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboardScore.cs index fabebc3859..2ea0f9eb84 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboardScore.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboardScore.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; @@ -17,7 +15,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components { private readonly APIUserScoreAggregate score; - public override ScoreInfo TooltipContent => null; // match aggregate scores can't show statistics that the custom tooltip displays. + public override ScoreInfo? TooltipContent => null; // match aggregate scores can't show statistics that the custom tooltip displays. public MatchLeaderboardScore(APIUserScoreAggregate score, int? rank, bool isOnlineScope = true) : base(score.CreateScoreInfo(), rank, isOnlineScope) diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchTypePicker.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchTypePicker.cs index 477336e8ea..51bd7a2801 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchTypePicker.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchTypePicker.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -26,7 +24,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components protected override TabItem CreateTabItem(MatchType value) => new GameTypePickerItem(value); - protected override Dropdown CreateDropdown() => null; + protected override Dropdown? CreateDropdown() => null; public MatchTypePicker() { @@ -41,7 +39,8 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components { private const float transition_duration = 200; - private readonly CircularContainer hover, selection; + private readonly CircularContainer hover; + private readonly CircularContainer selection; public GameTypePickerItem(MatchType value) : base(value) @@ -84,7 +83,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components }; } - private Sample selectSample; + private Sample selectSample = null!; [BackgroundDependencyLoader] private void load(OsuColour colours, AudioManager audio) diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/RoomAvailabilityPicker.cs b/osu.Game/Screens/OnlinePlay/Match/Components/RoomAvailabilityPicker.cs index 85fac9228b..56f02ba633 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/RoomAvailabilityPicker.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/RoomAvailabilityPicker.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; @@ -22,7 +20,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components public partial class RoomAvailabilityPicker : DisableableTabControl { protected override TabItem CreateTabItem(RoomAvailability value) => new RoomAvailabilityPickerItem(value); - protected override Dropdown CreateDropdown() => null; + protected override Dropdown? CreateDropdown() => null; public RoomAvailabilityPicker() { diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs index 6a856d8d72..ffa4235167 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.OnlinePlay.Match { public partial class RoomModSelectOverlay : UserModSelectOverlay { - public Bindable SelectedItem { get; } = new Bindable(); + public Bindable SelectedItem { get; } = new Bindable(); [Resolved] private RulesetStore rulesets { get; set; } = null!; diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 4461cf5402..0d9c210149 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -1,14 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -37,7 +34,7 @@ namespace osu.Game.Screens.OnlinePlay.Match [Cached(typeof(IPreviewTrackOwner))] public abstract partial class RoomSubScreen : OnlinePlaySubScreen, IPreviewTrackOwner { - public readonly Bindable SelectedItem = new Bindable(); + public readonly Bindable SelectedItem = new Bindable(); public override bool? ApplyModTrackAdjustments => true; @@ -52,9 +49,9 @@ namespace osu.Game.Screens.OnlinePlay.Match /// A container that provides controls for selection of user mods. /// This will be shown/hidden automatically when applicable. /// - protected Drawable UserModsSection; + protected Drawable? UserModsSection; - private Sample sampleStart; + private Sample? sampleStart; /// /// Any mods applied by/to the local user. @@ -62,28 +59,28 @@ namespace osu.Game.Screens.OnlinePlay.Match protected readonly Bindable> UserMods = new Bindable>(Array.Empty()); [Resolved(CanBeNull = true)] - private IOverlayManager overlayManager { get; set; } + private IOverlayManager? overlayManager { get; set; } [Resolved] - private MusicController music { get; set; } + private MusicController music { get; set; } = null!; [Resolved] - private BeatmapManager beatmapManager { get; set; } + private BeatmapManager beatmapManager { get; set; } = null!; [Resolved] - protected RulesetStore Rulesets { get; private set; } + protected RulesetStore Rulesets { get; private set; } = null!; [Resolved] private IAPIProvider api { get; set; } = null!; [Resolved(canBeNull: true)] - protected OnlinePlayScreen ParentScreen { get; private set; } + protected OnlinePlayScreen? ParentScreen { get; private set; } [Resolved] private PreviewTrackManager previewTrackManager { get; set; } = null!; [Resolved(canBeNull: true)] - private IDialogOverlay dialogOverlay { get; set; } + private IDialogOverlay? dialogOverlay { get; set; } [Cached] private readonly OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); @@ -93,13 +90,11 @@ namespace osu.Game.Screens.OnlinePlay.Match public readonly Room Room; private readonly bool allowEdit; - internal ModSelectOverlay UserModsSelectOverlay { get; private set; } + internal ModSelectOverlay UserModsSelectOverlay { get; private set; } = null!; - [CanBeNull] - private IDisposable userModsSelectOverlayRegistration; - - private RoomSettingsOverlay settingsOverlay; - private Drawable mainContent; + private IDisposable? userModsSelectOverlayRegistration; + private RoomSettingsOverlay settingsOverlay = null!; + private Drawable mainContent = null!; /// /// Creates a new . @@ -265,7 +260,7 @@ namespace osu.Game.Screens.OnlinePlay.Match updateSetupState(); } - private void onRoomPropertyChanged(object sender, PropertyChangedEventArgs e) + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(Room.RoomID)) updateSetupState(); @@ -388,6 +383,9 @@ namespace osu.Game.Screens.OnlinePlay.Match protected void StartPlay() { + if (SelectedItem.Value == null) + return; + // User may be at song select or otherwise when the host starts gameplay. // Ensure that they first return to this screen, else global bindables (beatmap etc.) may be in a bad state. if (!this.IsCurrentScreen()) @@ -401,29 +399,28 @@ namespace osu.Game.Screens.OnlinePlay.Match sampleStart?.Play(); // fallback is to allow this class to operate when there is no parent OnlineScreen (testing purposes). - var targetScreen = (Screen)ParentScreen ?? this; + var targetScreen = (Screen?)ParentScreen ?? this; - targetScreen.Push(CreateGameplayScreen()); + targetScreen.Push(CreateGameplayScreen(SelectedItem.Value)); } /// /// Creates the gameplay screen to be entered. /// + /// /// The screen to enter. - protected abstract Screen CreateGameplayScreen(); + protected abstract Screen CreateGameplayScreen(PlaylistItem selectedItem); private void selectedItemChanged() { updateWorkingBeatmap(); - var selected = SelectedItem.Value; - - if (selected == null) + if (SelectedItem.Value is not PlaylistItem selected) return; - var rulesetInstance = Rulesets.GetRuleset(SelectedItem.Value.RulesetID)?.CreateInstance(); + var rulesetInstance = Rulesets.GetRuleset(selected.RulesetID)?.CreateInstance(); Debug.Assert(rulesetInstance != null); - var allowedMods = SelectedItem.Value.AllowedMods.Select(m => m.ToMod(rulesetInstance)); + var allowedMods = selected.AllowedMods.Select(m => m.ToMod(rulesetInstance)); // Remove any user mods that are no longer allowed. UserMods.Value = UserMods.Value.Where(m => allowedMods.Any(a => m.GetType() == a.GetType())).ToList(); @@ -494,7 +491,7 @@ namespace osu.Game.Screens.OnlinePlay.Match cancelTrackLooping(); } - private void applyLoopingToTrack(ValueChangedEvent _ = null) + private void applyLoopingToTrack(ValueChangedEvent? _ = null) { if (!this.IsCurrentScreen()) return; @@ -503,8 +500,8 @@ namespace osu.Game.Screens.OnlinePlay.Match if (track != null) { - Beatmap.Value.PrepareTrackForPreview(true); - music?.EnsurePlayingSomething(); + Beatmap.Value!.PrepareTrackForPreview(true); + music.EnsurePlayingSomething(); } } diff --git a/osu.Game/Screens/OnlinePlay/Match/UserModSelectButton.cs b/osu.Game/Screens/OnlinePlay/Match/UserModSelectButton.cs index f3ea82be99..219bb6a2e3 100644 --- a/osu.Game/Screens/OnlinePlay/Match/UserModSelectButton.cs +++ b/osu.Game/Screens/OnlinePlay/Match/UserModSelectButton.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. -#nullable disable using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Input.Bindings; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/CreateMultiplayerMatchButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/CreateMultiplayerMatchButton.cs index 7975597beb..9de32267a2 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/CreateMultiplayerMatchButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/CreateMultiplayerMatchButton.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Online.Multiplayer; @@ -12,14 +10,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { public partial class CreateMultiplayerMatchButton : CreateRoomButton { - private IBindable isConnected; - private IBindable operationInProgress; + private IBindable isConnected = null!; + private IBindable operationInProgress = null!; [Resolved] - private MultiplayerClient multiplayerClient { get; set; } + private MultiplayerClient multiplayerClient { get; set; } = null!; [Resolved] - private OngoingOperationTracker ongoingOperationTracker { get; set; } + private OngoingOperationTracker ongoingOperationTracker { get; set; } = null!; [BackgroundDependencyLoader] private void load() diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs index d4483044e0..9a03a131b4 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs @@ -1,9 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -20,8 +17,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public partial class GameplayChatDisplay : MatchChatDisplay, IKeyBindingHandler { [Resolved(CanBeNull = true)] - [CanBeNull] - private ILocalUserPlayInfo localUserInfo { get; set; } + private ILocalUserPlayInfo? localUserInfo { get; set; } private readonly IBindable localUserPlaying = new Bindable(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerCountdownButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerCountdownButton.cs index e1543eaceb..50e996d266 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerCountdownButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerCountdownButton.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using Humanizer; @@ -33,15 +31,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match TimeSpan.FromMinutes(2) }; - public new Action Action; - - public Action CancelAction; + public new required Action Action; + public required Action CancelAction; [Resolved] - private MultiplayerClient multiplayerClient { get; set; } + private MultiplayerClient multiplayerClient { get; set; } = null!; [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; private readonly Drawable background; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index 7ce3dde7c2..ca8bc0b262 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -1,14 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; +using System.Diagnostics; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Localisation; using osu.Framework.Threading; using osu.Game.Graphics; @@ -20,17 +19,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match public partial class MultiplayerReadyButton : ReadyButton { [Resolved] - private MultiplayerClient multiplayerClient { get; set; } + private MultiplayerClient multiplayerClient { get; set; } = null!; [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; - [CanBeNull] - private MultiplayerRoom room => multiplayerClient.Room; + private MultiplayerRoom? room => multiplayerClient.Room; - private Sample countdownTickSample; - private Sample countdownWarnSample; - private Sample countdownWarnFinalSample; + private Sample? countdownTickSample; + private Sample? countdownWarnSample; + private Sample? countdownWarnFinalSample; [BackgroundDependencyLoader] private void load(AudioManager audio) @@ -48,13 +46,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match onRoomUpdated(); } - private MultiplayerCountdown countdown; + private MultiplayerCountdown? countdown; private double countdownChangeTime; - private ScheduledDelegate countdownUpdateDelegate; + private ScheduledDelegate? countdownUpdateDelegate; private void onRoomUpdated() => Scheduler.AddOnce(() => { - MultiplayerCountdown newCountdown = room?.ActiveCountdowns.SingleOrDefault(c => c is MatchStartCountdown); + MultiplayerCountdown? newCountdown = room?.ActiveCountdowns.SingleOrDefault(c => c is MatchStartCountdown); if (newCountdown != countdown) { @@ -171,6 +169,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { get { + Debug.Assert(countdown != null); + double timeElapsed = Time.Current - countdownChangeTime; TimeSpan remaining; @@ -224,7 +224,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { base.Dispose(isDisposing); - if (multiplayerClient != null) + if (multiplayerClient.IsNotNull()) multiplayerClient.RoomUpdated -= onRoomUpdated; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs index a5cc267569..50358ea9d3 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -27,12 +25,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public partial class MultiplayerLoungeSubScreen : LoungeSubScreen { [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; [Resolved] - private MultiplayerClient client { get; set; } + private MultiplayerClient client { get; set; } = null!; - private Dropdown roomAccessTypeDropdown; + private Dropdown roomAccessTypeDropdown = null!; public override void OnResuming(ScreenTransitionEvent e) { @@ -83,7 +81,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override void OpenNewRoom(Room room) { - if (client?.IsConnected.Value != true) + if (!client.IsConnected.Value) { Logger.Log("Not currently connected to the multiplayer server.", LoggingTarget.Runtime, LogLevel.Important); return; @@ -95,7 +93,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private partial class MultiplayerListingPollingComponent : ListingPollingComponent { [Resolved] - private MultiplayerClient client { get; set; } + private MultiplayerClient client { get; set; } = null!; private readonly IBindable isConnected = new Bindable(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 65355f02e5..edc45dbf7c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -401,7 +401,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer StartPlay(); } - protected override Screen CreateGameplayScreen() + protected override Screen CreateGameplayScreen(PlaylistItem selectedItem) { Debug.Assert(client.LocalUser != null); Debug.Assert(client.Room != null); @@ -415,7 +415,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return new MultiSpectatorScreen(Room, users.Take(PlayerGrid.MAX_PLAYERS).ToArray()); default: - return new MultiplayerPlayerLoader(() => new MultiplayerPlayer(Room, SelectedItem.Value, users)); + return new MultiplayerPlayerLoader(() => new MultiplayerPlayer(Room, selectedItem, users)); } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayerLoader.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayerLoader.cs index f682508319..7eb7f6610e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayerLoader.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayerLoader.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Threading.Tasks; using osu.Framework.Allocation; @@ -18,9 +16,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public bool GameplayPassed => player?.GameplayState.HasPassed == true; [Resolved] - private MultiplayerClient multiplayerClient { get; set; } + private MultiplayerClient multiplayerClient { get; set; } = null!; - private Player player; + private Player? player; public MultiplayerPlayerLoader(Func createPlayer) : base(createPlayer) @@ -45,7 +43,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer .ContinueWith(task => failAndBail(task.Exception?.Message ?? "Server error"), TaskContinuationOptions.NotOnRanToCompletion); } - private void failAndBail(string message = null) + private void failAndBail(string? message = null) { if (!string.IsNullOrEmpty(message)) Logger.Log(message, LoggingTarget.Runtime, LogLevel.Important); diff --git a/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs b/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs index 7f73d6655f..0c761dba44 100644 --- a/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs +++ b/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -22,7 +20,7 @@ namespace osu.Game.Screens.OnlinePlay private readonly Bindable inProgress = new BindableBool(); - private LeasedBindable leasedInProgress; + private LeasedBindable? leasedInProgress; public OngoingOperationTracker() { diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index 1b7041c9bb..13ffb61d57 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -27,16 +25,16 @@ namespace osu.Game.Screens.OnlinePlay public IScreen CurrentSubScreen => screenStack.CurrentScreen; - public override bool CursorVisible => (screenStack?.CurrentScreen as IOnlinePlaySubScreen)?.CursorVisible ?? true; + public override bool CursorVisible => (screenStack.CurrentScreen as IOnlinePlaySubScreen)?.CursorVisible ?? true; // this is required due to PlayerLoader eventually being pushed to the main stack // while leases may be taken out by a subscreen. public override bool DisallowExternalBeatmapRulesetChanges => true; - protected LoungeSubScreen Lounge { get; private set; } + protected LoungeSubScreen Lounge { get; private set; } = null!; - private OnlinePlayScreenWaveContainer waves; - private ScreenStack screenStack; + private OnlinePlayScreenWaveContainer waves = null!; + private ScreenStack screenStack = null!; [Cached(Type = typeof(IRoomManager))] protected RoomManager RoomManager { get; private set; } @@ -45,7 +43,7 @@ namespace osu.Game.Screens.OnlinePlay private readonly OngoingOperationTracker ongoingOperationTracker = new OngoingOperationTracker(); [Resolved] - protected IAPIProvider API { get; private set; } + protected IAPIProvider API { get; private set; } = null!; protected OnlinePlayScreen() { diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index f915676fb1..44d1841fb8 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -273,10 +273,13 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Logger.Log($"Polling adjusted (selection: {selectionPollingComponent.TimeBetweenPolls.Value})"); } - protected override Screen CreateGameplayScreen() => new PlayerLoader(() => new PlaylistsPlayer(Room, SelectedItem.Value) + protected override Screen CreateGameplayScreen(PlaylistItem selectedItem) { - Exited = () => leaderboard.RefetchScores() - }); + return new PlayerLoader(() => new PlaylistsPlayer(Room, selectedItem) + { + Exited = () => leaderboard.RefetchScores() + }); + } protected override void Dispose(bool isDisposing) { diff --git a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs index 3509519113..8ddc5325db 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// The cached . /// - Bindable SelectedRoom { get; } + Bindable SelectedRoom { get; } /// /// The cached diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index eebc3503bc..3f6c175fbd 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// public abstract partial class OnlinePlayTestScene : ScreenTestScene, IOnlinePlayTestSceneDependencies { - public Bindable SelectedRoom => OnlinePlayDependencies.SelectedRoom; + public Bindable SelectedRoom => OnlinePlayDependencies.SelectedRoom; public IRoomManager RoomManager => OnlinePlayDependencies.RoomManager; public OngoingOperationTracker OngoingOperationTracker => OnlinePlayDependencies.OngoingOperationTracker; public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker => OnlinePlayDependencies.AvailabilityTracker; diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs index 06f8cc40b9..e2670c9ad8 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs @@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// public class OnlinePlayTestSceneDependencies : IReadOnlyDependencyContainer, IOnlinePlayTestSceneDependencies { - public Bindable SelectedRoom { get; } + public Bindable SelectedRoom { get; } public IRoomManager RoomManager { get; } public OngoingOperationTracker OngoingOperationTracker { get; } public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker { get; } @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay public OnlinePlayTestSceneDependencies() { - SelectedRoom = new Bindable(); + SelectedRoom = new Bindable(); RequestsHandler = new TestRoomRequestsHandler(); OngoingOperationTracker = new OngoingOperationTracker(); AvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); From dc4581656602c5c1aca05f79902f584e412010a0 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 21 Nov 2024 20:41:14 +0900 Subject: [PATCH 918/924] Fix inspection --- .../OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs index 905a2bf31b..3186cf89a4 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs @@ -58,7 +58,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match client.ToggleSpectate().ContinueWith(_ => endOperation()); - void endOperation() => clickOperation?.Dispose(); + void endOperation() => clickOperation.Dispose(); } [BackgroundDependencyLoader] From cc59434ea44d42726468f63668db0e4091e1116f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 21 Nov 2024 20:52:34 +0900 Subject: [PATCH 919/924] Fix crash due to being too forgiving of nulls This one is super duper annoying to test, because we already have e.g. `TestScenePlaylistsScreen`. The only way to test it would be to use an `OsuGameTestScene`. Maybe this is okay? --- osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index 13ffb61d57..cc6a4e09e1 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -33,8 +33,8 @@ namespace osu.Game.Screens.OnlinePlay protected LoungeSubScreen Lounge { get; private set; } = null!; + private readonly ScreenStack screenStack = new OnlinePlaySubScreenStack { RelativeSizeAxes = Axes.Both }; private OnlinePlayScreenWaveContainer waves = null!; - private ScreenStack screenStack = null!; [Cached(Type = typeof(IRoomManager))] protected RoomManager RoomManager { get; private set; } @@ -65,7 +65,7 @@ namespace osu.Game.Screens.OnlinePlay RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - screenStack = new OnlinePlaySubScreenStack { RelativeSizeAxes = Axes.Both }, + screenStack, new Header(ScreenTitle, screenStack), RoomManager, ongoingOperationTracker, From 209380cbac77939bc34e03459f376a5886f4fc30 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 21 Nov 2024 21:03:12 +0900 Subject: [PATCH 920/924] Enable NRT in `TestMultiplayerRoomManager` --- .../Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs index 8d04c808fd..b998a638e5 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using osu.Game.Online.API.Requests.Responses; @@ -28,10 +26,10 @@ namespace osu.Game.Tests.Visual.Multiplayer public IReadOnlyList ServerSideRooms => requestsHandler.ServerSideRooms; - public override void CreateRoom(Room room, Action onSuccess = null, Action onError = null) + public override void CreateRoom(Room room, Action? onSuccess = null, Action? onError = null) => base.CreateRoom(room, r => onSuccess?.Invoke(r), onError); - public override void JoinRoom(Room room, string password = null, Action onSuccess = null, Action onError = null) + public override void JoinRoom(Room room, string? password = null, Action? onSuccess = null, Action? onError = null) => base.JoinRoom(room, password, r => onSuccess?.Invoke(r), onError); /// From 42f2ad2423df7b9cab73e387241279804db4710c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Nov 2024 13:06:01 +0100 Subject: [PATCH 921/924] Fix tests --- .../TestSceneBeatmapAttributeText.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs index e3a6fca319..5acd6cb084 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs @@ -73,10 +73,10 @@ namespace osu.Game.Tests.Visual.UserInterface }); }); - [TestCase(BeatmapAttribute.CircleSize, "Circle Size: 1.00")] - [TestCase(BeatmapAttribute.HPDrain, "HP Drain: 2.00")] - [TestCase(BeatmapAttribute.Accuracy, "Accuracy: 3.00")] - [TestCase(BeatmapAttribute.ApproachRate, "Approach Rate: 4.00")] + [TestCase(BeatmapAttribute.CircleSize, "Circle Size: 1")] + [TestCase(BeatmapAttribute.HPDrain, "HP Drain: 2")] + [TestCase(BeatmapAttribute.Accuracy, "Accuracy: 3")] + [TestCase(BeatmapAttribute.ApproachRate, "Approach Rate: 4")] [TestCase(BeatmapAttribute.Title, "Title: _Title")] [TestCase(BeatmapAttribute.Artist, "Artist: _Artist")] [TestCase(BeatmapAttribute.Creator, "Creator: _Creator")] @@ -121,15 +121,15 @@ namespace osu.Game.Tests.Visual.UserInterface Difficulty = { ApproachRate = 10, - CircleSize = 9 + CircleSize = 9.5f } } })); - test(BeatmapAttribute.BPM, new OsuModDoubleTime(), "BPM: 100.00", "BPM: 150.00"); + test(BeatmapAttribute.BPM, new OsuModDoubleTime(), "BPM: 100", "BPM: 150"); test(BeatmapAttribute.Length, new OsuModDoubleTime(), "Length: 00:30", "Length: 00:20"); - test(BeatmapAttribute.ApproachRate, new OsuModDoubleTime(), "Approach Rate: 10.00", "Approach Rate: 11.00"); - test(BeatmapAttribute.CircleSize, new OsuModHardRock(), "Circle Size: 9.00", "Circle Size: 10.00"); + test(BeatmapAttribute.ApproachRate, new OsuModDoubleTime(), "Approach Rate: 10", "Approach Rate: 11"); + test(BeatmapAttribute.CircleSize, new OsuModHardRock(), "Circle Size: 9.5", "Circle Size: 10"); void test(BeatmapAttribute attribute, Mod mod, string before, string after) { From 84ac3097c23dfad41d305cee13586fd09e4a72e1 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 21 Nov 2024 22:47:46 +0900 Subject: [PATCH 922/924] Populate parameter description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 0d9c210149..ffea3878fa 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -407,7 +407,7 @@ namespace osu.Game.Screens.OnlinePlay.Match /// /// Creates the gameplay screen to be entered. /// - /// + /// The playlist item about to be played. /// The screen to enter. protected abstract Screen CreateGameplayScreen(PlaylistItem selectedItem); From 5dffc322aff5485771de4c3f72cb1df7f8c522a8 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 21 Nov 2024 17:48:12 -0500 Subject: [PATCH 923/924] Recreate beatmap every test run in `ModTestScene` --- .../Mods/TestSceneCatchModNoScope.cs | 6 +- .../Mods/TestSceneCatchModRelax.cs | 2 +- .../TestSceneCatchModHidden.cs | 2 +- .../Mods/TestSceneManiaModAutoplay.cs | 2 +- .../Mods/TestSceneManiaModDoubleTime.cs | 4 +- .../Mods/TestSceneManiaModFadeIn.cs | 2 +- .../Mods/TestSceneManiaModHidden.cs | 2 +- .../Mods/TestSceneManiaModPerfect.cs | 4 +- .../Mods/TestSceneManiaModSuddenDeath.cs | 4 +- .../Mods/TestSceneOsuModAlternate.cs | 8 +-- .../Mods/TestSceneOsuModAutoplay.cs | 4 +- .../Mods/TestSceneOsuModDifficultyAdjust.cs | 2 +- .../Mods/TestSceneOsuModFlashlight.cs | 6 +- .../Mods/TestSceneOsuModHidden.cs | 8 +-- .../Mods/TestSceneOsuModMirror.cs | 2 +- .../Mods/TestSceneOsuModNoScope.cs | 6 +- .../Mods/TestSceneOsuModPerfect.cs | 2 +- .../Mods/TestSceneOsuModRandom.cs | 4 +- .../Mods/TestSceneOsuModSingleTap.cs | 8 +-- .../Mods/TestSceneOsuModSpunOut.cs | 2 +- .../Mods/TestSceneOsuModStrictTracking.cs | 2 +- .../Mods/TestSceneOsuModSuddenDeath.cs | 4 +- .../TestSceneMissHitWindowJudgements.cs | 14 ++--- .../Mods/TestSceneTaikoModHidden.cs | 60 ++++++++++--------- .../Mods/TestSceneTaikoModRelax.cs | 37 ++++++------ .../Mods/TestSceneTaikoModSingleTap.cs | 10 ++-- .../Mods/TestSceneModAccuracyChallenge.cs | 4 +- .../Tests/Visual/ModFailConditionTestScene.cs | 2 +- osu.Game/Tests/Visual/ModTestScene.cs | 4 +- 29 files changed, 110 insertions(+), 107 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs index c8f7da1aae..d185293093 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods }, Autoplay = true, PassCondition = () => Player.ScoreProcessor.Combo.Value == 2, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods }, Autoplay = true, PassCondition = () => true, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods }, Autoplay = true, PassCondition = () => true, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs index a161615579..6114c9ce6f 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods Mod = new CatchModRelax(), Autoplay = false, PassCondition = passCondition, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs index 825e8c697c..1f32a4531b 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Tests { CreateModTest(new ModTestData { - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModAutoplay.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModAutoplay.cs index f653f209c1..4871f5b585 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModAutoplay.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModAutoplay.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods CreateModTest(new ModTestData { Autoplay = true, - Beatmap = new ManiaBeatmap(new StageDefinition(1)) + Beatmap = () => new ManiaBeatmap(new StageDefinition(1)) { HitObjects = new List { diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs index 975e43ec08..a492c53456 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods && Precision.AlmostEquals(Player.ScoreProcessor.Accuracy.Value, 0.9836, 0.01) && Player.ScoreProcessor.TotalScore.Value == 946_049, Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo }, Difficulty = { OverallDifficulty = 10 }, @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods && Player.ScoreProcessor.Accuracy.Value == 1 && Player.ScoreProcessor.TotalScore.Value == (long)(1_000_000 * doubleTime.ScoreMultiplier), Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo }, Difficulty = { OverallDifficulty = 10 }, diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModFadeIn.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModFadeIn.cs index 9620897983..c425bb05c7 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModFadeIn.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModFadeIn.cs @@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods CreateModTest(new ModTestData { Mod = new ManiaModHidden(), - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = Enumerable.Range(1, 100).Select(i => (HitObject)new Note { StartTime = 1000 + 200 * i }).ToList(), Breaks = { new BreakPeriod(2000, 28000) } diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHidden.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHidden.cs index ae23c4573c..98cceefaf5 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHidden.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHidden.cs @@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods CreateModTest(new ModTestData { Mod = new ManiaModHidden(), - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = Enumerable.Range(1, 100).Select(i => (HitObject)new Note { StartTime = 1000 + 200 * i }).ToList(), Breaks = { new BreakPeriod(2000, 28000) } diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs index 51730e2b43..7e2a0a4dd7 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods Mod = new ManiaModPerfect(), PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false), Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods Mod = new ManiaModPerfect(), PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true) && Player.Results.Count == 2, Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModSuddenDeath.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModSuddenDeath.cs index 619816a815..c220578833 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModSuddenDeath.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModSuddenDeath.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods Mod = new ManiaModSuddenDeath(), PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false), Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods Mod = new ManiaModSuddenDeath(), PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true) && Player.Results.Count == 2, Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs index 7375617aa8..2192184eef 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModAlternate(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 4, Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModAlternate(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 1, Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModAlternate(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 1, Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -131,7 +131,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModAlternate(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 2, Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { Breaks = { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs index ace7f23989..5baafb4f79 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods CreateModTest(new ModTestData { Autoplay = true, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Autoplay = true, Mod = mod, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs index 472c341bdd..0a97933df6 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods public void TestNoAdjustment() => CreateModTest(new ModTestData { Mod = new OsuModDifficultyAdjust(), - Beatmap = new Beatmap + Beatmap = () => new Beatmap { BeatmapInfo = new BeatmapInfo { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs index 075fdd88ca..e413606a3d 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Player.GameplayClockContainer.CurrentTime < 1000 && Player.ChildrenOfType.Flashlight>().Single().FlashlightDim > 0; return Player.GameplayState.HasPassed && !sliderDimmedBeforeStartTime; }, - Beatmap = new OsuBeatmap + Beatmap = () => new OsuBeatmap { HitObjects = new List { @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Player.GameplayClockContainer.CurrentTime >= 1000 && Player.ChildrenOfType.Flashlight>().Single().FlashlightDim > 0; return Player.GameplayState.HasPassed && sliderDimmed; }, - Beatmap = new OsuBeatmap + Beatmap = () => new OsuBeatmap { HitObjects = new List { @@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Player.GameplayClockContainer.CurrentTime >= 1000 && Player.ChildrenOfType.Flashlight>().Single().FlashlightDim > 0; return Player.GameplayState.HasPassed && sliderDimmed; }, - Beatmap = new OsuBeatmap + Beatmap = () => new OsuBeatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs index 58bdd805c1..fec4a748d3 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new TestOsuModHidden(), Autoplay = true, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new TestOsuModHidden(), Autoplay = true, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new TestOsuModHidden(), Autoplay = true, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -122,7 +122,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModHidden { OnlyFadeApproachCircles = { Value = true } }, Autoplay = true, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMirror.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMirror.cs index 0b3496ba68..c8e7061416 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMirror.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMirror.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods public void TestCorrectReflections([Values] OsuModMirror.MirrorType type, [Values] bool withStrictTracking) => CreateModTest(new ModTestData { Autoplay = true, - Beatmap = new OsuBeatmap + Beatmap = () => new OsuBeatmap { HitObjects = { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs index d3996ebc3b..d7922f8ba7 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods }, Autoplay = true, PassCondition = () => true, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods }, Autoplay = true, PassCondition = () => true, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -113,7 +113,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods }, Autoplay = true, PassCondition = () => true, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs index b01bbbfca1..a9a94cfd72 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModPerfect(), PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true), Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs index 060a845137..81d753fd2d 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs @@ -55,10 +55,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods PassCondition = () => true }); - private OsuBeatmap jumpBeatmap => + private OsuBeatmap jumpBeatmap() => createHitCircleBeatmap(new[] { 100, 200, 300, 400 }, 8, 300, 2 * 300); - private OsuBeatmap streamBeatmap => + private OsuBeatmap streamBeatmap() => createHitCircleBeatmap(new[] { 10, 20, 30, 40, 50, 60, 70, 80 }, 16, 150, 4 * 150); private OsuBeatmap createHitCircleBeatmap(IEnumerable spacings, int objectsPerSpacing, int interval, int beatLength) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs index bd2b205ac8..3d553fa051 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModSingleTap(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 2, Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModSingleTap(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 1, Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModSingleTap(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 1, Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -130,7 +130,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModSingleTap(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 2, Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { Breaks = { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs index de3ea5f148..ced69d59de 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs @@ -130,7 +130,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods }); } - private Beatmap singleSpinnerBeatmap => new Beatmap + private Beatmap singleSpinnerBeatmap() => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModStrictTracking.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModStrictTracking.cs index 726b415977..d295b3d891 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModStrictTracking.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModStrictTracking.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModStrictTracking(), Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSuddenDeath.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSuddenDeath.cs index ea048aaa6e..25edda34d4 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSuddenDeath.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSuddenDeath.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModSuddenDeath(), PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false), Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModSuddenDeath(), PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true), Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs index c37660831b..5164d7d01b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Tests { Autoplay = false, Mod = new TestAutoMod(), - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = { new HitCircle { Position = new Vector2(256, 192) } } }, @@ -47,18 +47,16 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestMissViaNotHitting() { - var beatmap = new Beatmap - { - HitObjects = { new HitCircle { Position = new Vector2(256, 192) } } - }; - var hitWindows = new OsuHitWindows(); - hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty); + hitWindows.SetDifficulty(IBeatmapDifficultyInfo.DEFAULT_DIFFICULTY); CreateModTest(new ModTestData { Autoplay = false, - Beatmap = beatmap, + Beatmap = () => new Beatmap + { + HitObjects = { new HitCircle { Position = new Vector2(256, 192) } } + }, PassCondition = () => Player.Results.Count > 0 && Player.Results[0].TimeOffset >= hitWindows.WindowFor(HitResult.Meh) && !Player.Results[0].IsHit }); } diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs index 6e6be26e43..cf199f062a 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs @@ -31,40 +31,42 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods { const double hit_time = 1; - var beatmap = new Beatmap - { - HitObjects = new List - { - new Hit - { - Type = HitType.Rim, - StartTime = hit_time, - }, - new Hit - { - Type = HitType.Centre, - StartTime = hit_time * 2, - }, - }, - BeatmapInfo = - { - Difficulty = new BeatmapDifficulty - { - SliderTickRate = 4, - OverallDifficulty = 0, - }, - Ruleset = new TaikoRuleset().RulesetInfo - }, - }; - - beatmap.ControlPointInfo.Add(0, new EffectControlPoint { ScrollSpeed = 0.1f }); - CreateModTest(new ModTestData { Mod = new TaikoModHidden(), Autoplay = true, PassCondition = checkAllMaxResultJudgements(2), - Beatmap = beatmap, + Beatmap = () => + { + var beatmap = new Beatmap + { + HitObjects = new List + { + new Hit + { + Type = HitType.Rim, + StartTime = hit_time, + }, + new Hit + { + Type = HitType.Centre, + StartTime = hit_time * 2, + }, + }, + BeatmapInfo = + { + Difficulty = new BeatmapDifficulty + { + SliderTickRate = 4, + OverallDifficulty = 0, + }, + Ruleset = new TaikoRuleset().RulesetInfo + }, + }; + + beatmap.ControlPointInfo.Add(0, new EffectControlPoint { ScrollSpeed = 0.1f }); + return beatmap; + }, }); } } diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModRelax.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModRelax.cs index caf8aa8e76..6894a19a1f 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModRelax.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModRelax.cs @@ -15,7 +15,26 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods [Test] public void TestRelax() { - var beatmap = new TaikoBeatmap + var beatmapForReplay = createBeatmap(); + + foreach (var ho in beatmapForReplay.HitObjects) + ho.ApplyDefaults(beatmapForReplay.ControlPointInfo, beatmapForReplay.Difficulty); + + var replay = new TaikoAutoGenerator(beatmapForReplay).Generate(); + + foreach (var frame in replay.Frames.OfType().Where(r => r.Actions.Any())) + frame.Actions = [TaikoAction.LeftCentre]; + + CreateModTest(new ModTestData + { + Mod = new TaikoModRelax(), + Beatmap = createBeatmap, + ReplayFrames = replay.Frames, + Autoplay = false, + PassCondition = () => Player.ScoreProcessor.HasCompleted.Value && Player.ScoreProcessor.Accuracy.Value == 1, + }); + + TaikoBeatmap createBeatmap() => new TaikoBeatmap { HitObjects = { @@ -25,22 +44,6 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods new Swell { StartTime = 1250, Duration = 500 }, } }; - foreach (var ho in beatmap.HitObjects) - ho.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); - - var replay = new TaikoAutoGenerator(beatmap).Generate(); - - foreach (var frame in replay.Frames.OfType().Where(r => r.Actions.Any())) - frame.Actions = [TaikoAction.LeftCentre]; - - CreateModTest(new ModTestData - { - Mod = new TaikoModRelax(), - Beatmap = beatmap, - ReplayFrames = replay.Frames, - Autoplay = false, - PassCondition = () => Player.ScoreProcessor.HasCompleted.Value && Player.ScoreProcessor.Accuracy.Value == 1, - }); } } } diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModSingleTap.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModSingleTap.cs index 3a11a91f82..840bf78ffe 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModSingleTap.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModSingleTap.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods { Mod = new TaikoModSingleTap(), Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods { Mod = new TaikoModSingleTap(), Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods { Mod = new TaikoModSingleTap(), Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods { Mod = new TaikoModSingleTap(), Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods { Mod = new TaikoModSingleTap(), Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { Breaks = { diff --git a/osu.Game.Tests/Visual/Mods/TestSceneModAccuracyChallenge.cs b/osu.Game.Tests/Visual/Mods/TestSceneModAccuracyChallenge.cs index c5e56c6453..7011303ccd 100644 --- a/osu.Game.Tests/Visual/Mods/TestSceneModAccuracyChallenge.cs +++ b/osu.Game.Tests/Visual/Mods/TestSceneModAccuracyChallenge.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.Mods MinimumAccuracy = { Value = 0.6 } }, Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = Enumerable.Range(0, 5).Select(i => new HitCircle { @@ -56,7 +56,7 @@ namespace osu.Game.Tests.Visual.Mods AccuracyJudgeMode = { Value = ModAccuracyChallenge.AccuracyMode.Standard } }, Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = Enumerable.Range(0, 5).Select(i => new HitCircle { diff --git a/osu.Game/Tests/Visual/ModFailConditionTestScene.cs b/osu.Game/Tests/Visual/ModFailConditionTestScene.cs index 8f0dff055d..364145160d 100644 --- a/osu.Game/Tests/Visual/ModFailConditionTestScene.cs +++ b/osu.Game/Tests/Visual/ModFailConditionTestScene.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual protected void CreateHitObjectTest(HitObjectTestData testData, bool shouldMiss) => CreateModTest(new ModTestData { Mod = mod, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { BeatmapInfo = { Ruleset = CreatePlayerRuleset().RulesetInfo }, HitObjects = { testData.HitObject } diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs index c2ebcdefac..3265e5c257 100644 --- a/osu.Game/Tests/Visual/ModTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual base.TearDownSteps(); } - protected sealed override IBeatmap CreateBeatmap(RulesetInfo ruleset) => CurrentTestData?.Beatmap ?? base.CreateBeatmap(ruleset); + protected sealed override IBeatmap CreateBeatmap(RulesetInfo ruleset) => CurrentTestData?.Beatmap?.Invoke() ?? base.CreateBeatmap(ruleset); protected sealed override TestPlayer CreatePlayer(Ruleset ruleset) { @@ -107,7 +107,7 @@ namespace osu.Game.Tests.Visual /// The beatmap for this test case. /// [CanBeNull] - public IBeatmap Beatmap; + public Func Beatmap; /// /// The conditions that cause this test case to pass. From 41addaae9a26575482f6682ce3b450d9420c1c3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Nov 2024 13:11:32 +0900 Subject: [PATCH 924/924] Rename variable to imply it is now a function --- .../Mods/TestSceneCatchModNoScope.cs | 6 +++--- .../Mods/TestSceneCatchModRelax.cs | 2 +- .../TestSceneCatchModHidden.cs | 2 +- .../Mods/TestSceneManiaModAutoplay.cs | 2 +- .../Mods/TestSceneManiaModDoubleTime.cs | 4 ++-- .../Mods/TestSceneManiaModFadeIn.cs | 2 +- .../Mods/TestSceneManiaModHidden.cs | 2 +- .../Mods/TestSceneManiaModPerfect.cs | 4 ++-- .../Mods/TestSceneManiaModSuddenDeath.cs | 4 ++-- .../Mods/TestSceneOsuModAlternate.cs | 8 ++++---- .../Mods/TestSceneOsuModAutoplay.cs | 4 ++-- .../Mods/TestSceneOsuModDifficultyAdjust.cs | 2 +- .../Mods/TestSceneOsuModFlashlight.cs | 6 +++--- .../Mods/TestSceneOsuModHidden.cs | 8 ++++---- .../Mods/TestSceneOsuModMirror.cs | 2 +- .../Mods/TestSceneOsuModNoScope.cs | 6 +++--- .../Mods/TestSceneOsuModPerfect.cs | 2 +- .../Mods/TestSceneOsuModRandom.cs | 4 ++-- .../Mods/TestSceneOsuModSingleTap.cs | 8 ++++---- .../Mods/TestSceneOsuModSpunOut.cs | 6 +++--- .../Mods/TestSceneOsuModStrictTracking.cs | 2 +- .../Mods/TestSceneOsuModSuddenDeath.cs | 4 ++-- .../TestSceneMissHitWindowJudgements.cs | 4 ++-- .../Mods/TestSceneTaikoModHidden.cs | 2 +- .../Mods/TestSceneTaikoModRelax.cs | 2 +- .../Mods/TestSceneTaikoModSingleTap.cs | 10 +++++----- .../Visual/Mods/TestSceneModAccuracyChallenge.cs | 4 ++-- osu.Game/Tests/Visual/ModFailConditionTestScene.cs | 2 +- osu.Game/Tests/Visual/ModTestScene.cs | 9 +++++---- 29 files changed, 62 insertions(+), 61 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs index d185293093..828e47e187 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods }, Autoplay = true, PassCondition = () => Player.ScoreProcessor.Combo.Value == 2, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods }, Autoplay = true, PassCondition = () => true, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods }, Autoplay = true, PassCondition = () => true, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs index 6114c9ce6f..92f7960183 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods Mod = new CatchModRelax(), Autoplay = false, PassCondition = passCondition, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs index 1f32a4531b..9365659f79 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Tests { CreateModTest(new ModTestData { - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModAutoplay.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModAutoplay.cs index 4871f5b585..d644ef359d 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModAutoplay.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModAutoplay.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods CreateModTest(new ModTestData { Autoplay = true, - Beatmap = () => new ManiaBeatmap(new StageDefinition(1)) + CreateBeatmap = () => new ManiaBeatmap(new StageDefinition(1)) { HitObjects = new List { diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs index a492c53456..50ec119e4d 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods && Precision.AlmostEquals(Player.ScoreProcessor.Accuracy.Value, 0.9836, 0.01) && Player.ScoreProcessor.TotalScore.Value == 946_049, Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo }, Difficulty = { OverallDifficulty = 10 }, @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods && Player.ScoreProcessor.Accuracy.Value == 1 && Player.ScoreProcessor.TotalScore.Value == (long)(1_000_000 * doubleTime.ScoreMultiplier), Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo }, Difficulty = { OverallDifficulty = 10 }, diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModFadeIn.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModFadeIn.cs index c425bb05c7..f403d67377 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModFadeIn.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModFadeIn.cs @@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods CreateModTest(new ModTestData { Mod = new ManiaModHidden(), - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = Enumerable.Range(1, 100).Select(i => (HitObject)new Note { StartTime = 1000 + 200 * i }).ToList(), Breaks = { new BreakPeriod(2000, 28000) } diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHidden.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHidden.cs index 98cceefaf5..e7e1cf3c7c 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHidden.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHidden.cs @@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods CreateModTest(new ModTestData { Mod = new ManiaModHidden(), - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = Enumerable.Range(1, 100).Select(i => (HitObject)new Note { StartTime = 1000 + 200 * i }).ToList(), Breaks = { new BreakPeriod(2000, 28000) } diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs index 7e2a0a4dd7..b7a9b31dcc 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods Mod = new ManiaModPerfect(), PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false), Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods Mod = new ManiaModPerfect(), PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true) && Player.Results.Count == 2, Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModSuddenDeath.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModSuddenDeath.cs index c220578833..15c49ab0a0 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModSuddenDeath.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModSuddenDeath.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods Mod = new ManiaModSuddenDeath(), PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false), Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods Mod = new ManiaModSuddenDeath(), PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true) && Player.Results.Count == 2, Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs index 2192184eef..27ff26b438 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModAlternate(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 4, Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModAlternate(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 1, Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModAlternate(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 1, Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -131,7 +131,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModAlternate(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 2, Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { Breaks = { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs index 5baafb4f79..8786b17b92 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods CreateModTest(new ModTestData { Autoplay = true, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Autoplay = true, Mod = mod, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs index 0a97933df6..ca752fe918 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods public void TestNoAdjustment() => CreateModTest(new ModTestData { Mod = new OsuModDifficultyAdjust(), - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { BeatmapInfo = new BeatmapInfo { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs index e413606a3d..6bd3f25bdb 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Player.GameplayClockContainer.CurrentTime < 1000 && Player.ChildrenOfType.Flashlight>().Single().FlashlightDim > 0; return Player.GameplayState.HasPassed && !sliderDimmedBeforeStartTime; }, - Beatmap = () => new OsuBeatmap + CreateBeatmap = () => new OsuBeatmap { HitObjects = new List { @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Player.GameplayClockContainer.CurrentTime >= 1000 && Player.ChildrenOfType.Flashlight>().Single().FlashlightDim > 0; return Player.GameplayState.HasPassed && sliderDimmed; }, - Beatmap = () => new OsuBeatmap + CreateBeatmap = () => new OsuBeatmap { HitObjects = new List { @@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Player.GameplayClockContainer.CurrentTime >= 1000 && Player.ChildrenOfType.Flashlight>().Single().FlashlightDim > 0; return Player.GameplayState.HasPassed && sliderDimmed; }, - Beatmap = () => new OsuBeatmap + CreateBeatmap = () => new OsuBeatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs index fec4a748d3..c513f98f21 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new TestOsuModHidden(), Autoplay = true, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new TestOsuModHidden(), Autoplay = true, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new TestOsuModHidden(), Autoplay = true, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -122,7 +122,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModHidden { OnlyFadeApproachCircles = { Value = true } }, Autoplay = true, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMirror.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMirror.cs index c8e7061416..076cb9ae15 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMirror.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMirror.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods public void TestCorrectReflections([Values] OsuModMirror.MirrorType type, [Values] bool withStrictTracking) => CreateModTest(new ModTestData { Autoplay = true, - Beatmap = () => new OsuBeatmap + CreateBeatmap = () => new OsuBeatmap { HitObjects = { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs index d7922f8ba7..b9559aeba3 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods }, Autoplay = true, PassCondition = () => true, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods }, Autoplay = true, PassCondition = () => true, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -113,7 +113,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods }, Autoplay = true, PassCondition = () => true, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs index a9a94cfd72..8498e53bf0 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModPerfect(), PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true), Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs index 81d753fd2d..3456fcbe84 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { AngleSharpness = { Value = angleSharpness } }, - Beatmap = jumpBeatmap, + CreateBeatmap = jumpBeatmap, Autoplay = true, PassCondition = () => true }); @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { AngleSharpness = { Value = angleSharpness } }, - Beatmap = streamBeatmap, + CreateBeatmap = streamBeatmap, Autoplay = true, PassCondition = () => true }); diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs index 3d553fa051..b0be70e85e 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModSingleTap(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 2, Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModSingleTap(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 1, Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModSingleTap(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 1, Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -130,7 +130,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModSingleTap(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 2, Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { Breaks = { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs index ced69d59de..3706b9ac07 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModSpunOut(), Autoplay = false, - Beatmap = singleSpinnerBeatmap, + CreateBeatmap = singleSpinnerBeatmap, PassCondition = () => { // Bind to the first spinner's results for further tracking. @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mods = mods, Autoplay = false, - Beatmap = singleSpinnerBeatmap, + CreateBeatmap = singleSpinnerBeatmap, PassCondition = () => { var counter = Player.ChildrenOfType().SingleOrDefault(); @@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModSpunOut(), Autoplay = false, - Beatmap = singleSpinnerBeatmap, + CreateBeatmap = singleSpinnerBeatmap, PassCondition = () => { // Bind to the first spinner's results for further tracking. diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModStrictTracking.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModStrictTracking.cs index d295b3d891..66a60e3542 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModStrictTracking.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModStrictTracking.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModStrictTracking(), Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSuddenDeath.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSuddenDeath.cs index 25edda34d4..688cf70f71 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSuddenDeath.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSuddenDeath.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModSuddenDeath(), PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false), Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModSuddenDeath(), PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true), Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs index 5164d7d01b..7a89140fc4 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Tests { Autoplay = false, Mod = new TestAutoMod(), - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = { new HitCircle { Position = new Vector2(256, 192) } } }, @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Tests CreateModTest(new ModTestData { Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = { new HitCircle { Position = new Vector2(256, 192) } } }, diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs index cf199f062a..e6d5c51902 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods Mod = new TaikoModHidden(), Autoplay = true, PassCondition = checkAllMaxResultJudgements(2), - Beatmap = () => + CreateBeatmap = () => { var beatmap = new Beatmap { diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModRelax.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModRelax.cs index 6894a19a1f..fffe42f1f8 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModRelax.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModRelax.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods CreateModTest(new ModTestData { Mod = new TaikoModRelax(), - Beatmap = createBeatmap, + CreateBeatmap = createBeatmap, ReplayFrames = replay.Frames, Autoplay = false, PassCondition = () => Player.ScoreProcessor.HasCompleted.Value && Player.ScoreProcessor.Accuracy.Value == 1, diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModSingleTap.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModSingleTap.cs index 840bf78ffe..b12ac10d2d 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModSingleTap.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModSingleTap.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods { Mod = new TaikoModSingleTap(), Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods { Mod = new TaikoModSingleTap(), Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods { Mod = new TaikoModSingleTap(), Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods { Mod = new TaikoModSingleTap(), Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods { Mod = new TaikoModSingleTap(), Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { Breaks = { diff --git a/osu.Game.Tests/Visual/Mods/TestSceneModAccuracyChallenge.cs b/osu.Game.Tests/Visual/Mods/TestSceneModAccuracyChallenge.cs index 7011303ccd..e1c15863ad 100644 --- a/osu.Game.Tests/Visual/Mods/TestSceneModAccuracyChallenge.cs +++ b/osu.Game.Tests/Visual/Mods/TestSceneModAccuracyChallenge.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.Mods MinimumAccuracy = { Value = 0.6 } }, Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = Enumerable.Range(0, 5).Select(i => new HitCircle { @@ -56,7 +56,7 @@ namespace osu.Game.Tests.Visual.Mods AccuracyJudgeMode = { Value = ModAccuracyChallenge.AccuracyMode.Standard } }, Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = Enumerable.Range(0, 5).Select(i => new HitCircle { diff --git a/osu.Game/Tests/Visual/ModFailConditionTestScene.cs b/osu.Game/Tests/Visual/ModFailConditionTestScene.cs index 364145160d..72bdd54c51 100644 --- a/osu.Game/Tests/Visual/ModFailConditionTestScene.cs +++ b/osu.Game/Tests/Visual/ModFailConditionTestScene.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual protected void CreateHitObjectTest(HitObjectTestData testData, bool shouldMiss) => CreateModTest(new ModTestData { Mod = mod, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { BeatmapInfo = { Ruleset = CreatePlayerRuleset().RulesetInfo }, HitObjects = { testData.HitObject } diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs index 3265e5c257..eb61518d35 100644 --- a/osu.Game/Tests/Visual/ModTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual base.TearDownSteps(); } - protected sealed override IBeatmap CreateBeatmap(RulesetInfo ruleset) => CurrentTestData?.Beatmap?.Invoke() ?? base.CreateBeatmap(ruleset); + protected sealed override IBeatmap CreateBeatmap(RulesetInfo ruleset) => CurrentTestData?.CreateBeatmap?.Invoke() ?? base.CreateBeatmap(ruleset); protected sealed override TestPlayer CreatePlayer(Ruleset ruleset) { @@ -93,7 +93,7 @@ namespace osu.Game.Tests.Visual protected class ModTestData { /// - /// Whether to use a replay to simulate an auto-play. True by default. + /// Whether to use a replay to simulate an autoplay. True by default. /// public bool Autoplay = true; @@ -104,10 +104,11 @@ namespace osu.Game.Tests.Visual public List ReplayFrames; /// - /// The beatmap for this test case. + /// A function which should create a new instance of a beatmap containing relevant + /// content to the test. /// [CanBeNull] - public Func Beatmap; + public Func CreateBeatmap; /// /// The conditions that cause this test case to pass.