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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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/411] 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 3495510c7b651efee091bad375591b2aa7875102 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 30 Dec 2023 19:16:05 +0100 Subject: [PATCH 050/411] 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 051/411] 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 052/411] 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 053/411] 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 054/411] 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 055/411] improve UI From 4e3fe5112d9d02171fb19e23bd26520f34590c87 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 1 Jan 2024 16:09:18 +0100 Subject: [PATCH 056/411] 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 057/411] 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 058/411] 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 059/411] 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 2918ecf46c3516039a28f58d2dd71042d505f574 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 1 Feb 2024 16:56:57 +0100 Subject: [PATCH 060/411] 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 93dbd7507fd61086a8c9d3422e6a02560254f4d4 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 1 Feb 2024 17:07:03 +0100 Subject: [PATCH 061/411] 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 062/411] 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 063/411] 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 064/411] 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 065/411] 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 066/411] 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 067/411] 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 068/411] 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 069/411] 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 070/411] 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 071/411] 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 072/411] 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 073/411] 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 074/411] 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 075/411] 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 076/411] 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 077/411] 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 078/411] 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 079/411] 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 080/411] 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 081/411] 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 082/411] 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 083/411] 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 084/411] 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 085/411] 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 086/411] 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 087/411] 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 088/411] 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 089/411] 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 090/411] 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 091/411] 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 092/411] 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 093/411] 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 094/411] 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 2fb22f1febef5ad5ab120c24875e69b798ed984e Mon Sep 17 00:00:00 2001 From: Nathen Date: Sun, 23 Jun 2024 19:17:19 -0400 Subject: [PATCH 095/411] 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 e25642b48472789aaffa5254106d763a4cee9cad Mon Sep 17 00:00:00 2001 From: StanR Date: Mon, 15 Jul 2024 14:45:31 +0500 Subject: [PATCH 096/411] 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 097/411] 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 098/411] 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 099/411] 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 bae9625b0b4785b83124bfd38ccd8ff85d74947b Mon Sep 17 00:00:00 2001 From: StanR Date: Fri, 19 Jul 2024 10:13:50 +0500 Subject: [PATCH 100/411] 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 101/411] 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 102/411] 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 f1adc6f98cc48024a0bb35811955fb5653c3fdf6 Mon Sep 17 00:00:00 2001 From: StanR Date: Thu, 22 Aug 2024 15:59:13 +0500 Subject: [PATCH 103/411] 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 104/411] 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 105/411] 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 106/411] 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 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 107/411] 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 108/411] 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 109/411] 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 110/411] 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 111/411] 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 112/411] 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 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 113/411] 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 114/411] 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 115/411] 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 116/411] 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 117/411] 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 5e8174faa881d5a54398ddd4c0226f9a18e40f92 Mon Sep 17 00:00:00 2001 From: StanR Date: Sat, 14 Sep 2024 22:03:01 +0500 Subject: [PATCH 118/411] 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 119/411] 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 120/411] 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 121/411] 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 122/411] 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 123/411] 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 124/411] 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 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 125/411] 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 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 126/411] 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 0bad5e468455ef2667015a0c0fea148143459d6d Mon Sep 17 00:00:00 2001 From: StanR Date: Thu, 19 Sep 2024 04:38:01 +0500 Subject: [PATCH 127/411] 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 732a114b9594d2a861709591cd74d3438afdc375 Mon Sep 17 00:00:00 2001 From: StanR Date: Thu, 19 Sep 2024 15:53:18 +0500 Subject: [PATCH 128/411] 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 202364be5edc8f5126f12894f614a33533bc454f Mon Sep 17 00:00:00 2001 From: StanR Date: Thu, 19 Sep 2024 17:52:55 +0500 Subject: [PATCH 129/411] 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 130/411] 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 d2f97f5908176696709e4608e3813196682ec7d2 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 19 Sep 2024 20:18:24 +0200 Subject: [PATCH 131/411] 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 e04b88a9b0093c0902e4f6ecf7e6ad2d64174a84 Mon Sep 17 00:00:00 2001 From: StanR Date: Mon, 23 Sep 2024 13:49:25 +0500 Subject: [PATCH 132/411] 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 08bded82fdfd1cf63d2a72b51cedb5be0de01c60 Mon Sep 17 00:00:00 2001 From: StanR Date: Mon, 23 Sep 2024 16:30:02 +0500 Subject: [PATCH 133/411] 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 1a81e12192b6b6d2c1c8d1b9256cb2242e8d66e3 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 23 Sep 2024 16:33:36 +0200 Subject: [PATCH 134/411] 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 135/411] 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 136/411] 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 137/411] 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 138/411] 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 139/411] 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 ac9c1508b1367ab24c7c479636508a89c8bfb3b7 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Tue, 24 Sep 2024 11:22:46 +0100 Subject: [PATCH 140/411] 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 141/411] 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 142/411] 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 5eb23d3a7157ce32e41e8c5cbcedfc1771c15aac Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Tue, 24 Sep 2024 12:24:54 +0100 Subject: [PATCH 143/411] 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 75dc822540c882ad2cfa867cba7ec687d3f3d814 Mon Sep 17 00:00:00 2001 From: StanR Date: Tue, 24 Sep 2024 17:57:31 +0500 Subject: [PATCH 144/411] 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 872628b8b87c73d21579a2ffd7aa26058cd6c551 Mon Sep 17 00:00:00 2001 From: StanR Date: Tue, 24 Sep 2024 18:24:00 +0500 Subject: [PATCH 145/411] 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 fe8b9536ffa085326b889dce768ed2b035c3a67f Mon Sep 17 00:00:00 2001 From: StanR Date: Wed, 25 Sep 2024 18:58:24 +0500 Subject: [PATCH 146/411] 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 f4055d923f7fe28e0aed4947ca2d66cc5e8bd0c6 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Wed, 25 Sep 2024 18:14:15 +0100 Subject: [PATCH 147/411] 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 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 148/411] 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 149/411] 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 f6c5f975ee8e1389f2c8f708b99cdacaabf056e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Sep 2024 20:08:26 +0900 Subject: [PATCH 150/411] 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 151/411] 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 152/411] 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 153/411] 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 154/411] 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 155/411] 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 156/411] 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 157/411] 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 158/411] 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 159/411] 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 160/411] 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 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 161/411] 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 162/411] 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 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 163/411] 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 164/411] 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 0409edccce27fc7dfb11fb39ce4a1a6722d7ca6a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Oct 2024 15:14:40 +0900 Subject: [PATCH 165/411] 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 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 166/411] 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 167/411] 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 168/411] 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 169/411] 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 170/411] 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 171/411] 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 172/411] 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 173/411] 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 174/411] 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 8f0fedbd2205ce1a886d8c61fcf8693466c9ab84 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Oct 2024 18:21:15 +0900 Subject: [PATCH 175/411] 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 176/411] 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 177/411] 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 178/411] 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 179/411] 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 180/411] 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 8dba4cf760acd07735a12c3b74da7f7a15e1a34d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Oct 2024 19:55:31 +0900 Subject: [PATCH 181/411] 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 182/411] 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 4f16ecdf1b5bd1dffff3ee00b13c977058ec0558 Mon Sep 17 00:00:00 2001 From: CloneWith Date: Tue, 1 Oct 2024 20:22:46 +0800 Subject: [PATCH 183/411] 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 184/411] 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 185/411] 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 186/411] 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 187/411] 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 188/411] 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 189/411] 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 190/411] 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 191/411] 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 192/411] 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 193/411] 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 194/411] 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 195/411] 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 196/411] 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 197/411] 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 198/411] 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 199/411] 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 200/411] 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 201/411] 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 202/411] 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 203/411] 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 204/411] 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 205/411] 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 206/411] 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 207/411] 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 208/411] 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 209/411] 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 210/411] 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 211/411] 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 212/411] 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 213/411] 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 214/411] 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 215/411] 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 216/411] 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 217/411] 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 218/411] 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 219/411] 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 220/411] 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 221/411] 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 222/411] 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 223/411] 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 224/411] 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 225/411] 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 226/411] 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 227/411] 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 228/411] 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 229/411] 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 230/411] 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 231/411] 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 232/411] 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 ae8abc7f3576309bd68076c23d7358b02a94637d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 5 Oct 2024 21:46:00 +0200 Subject: [PATCH 233/411] 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 234/411] 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 235/411] 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 236/411] 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 237/411] 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 238/411] 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 239/411] 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 240/411] 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 241/411] 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 242/411] 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 243/411] 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 244/411] 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 245/411] 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 246/411] 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 247/411] 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 248/411] 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 249/411] 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 250/411] 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 251/411] 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 252/411] 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 253/411] 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 254/411] 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 255/411] 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 256/411] 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 257/411] 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 258/411] 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 259/411] 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 260/411] 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 261/411] 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 262/411] 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 263/411] 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 264/411] 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 265/411] 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 266/411] 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 267/411] 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 268/411] 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 269/411] 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 270/411] 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 271/411] 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 272/411] 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 273/411] 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 274/411] 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 275/411] 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 276/411] 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 277/411] 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 278/411] 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 279/411] 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 280/411] 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 281/411] 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 282/411] 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 283/411] 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 284/411] 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 285/411] 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 286/411] 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 287/411] 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 288/411] 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 289/411] 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 290/411] 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 291/411] 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 292/411] 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 293/411] 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 294/411] 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 295/411] 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 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 296/411] 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 297/411] 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 298/411] 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 299/411] 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 300/411] 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 301/411] 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 302/411] 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 303/411] 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 304/411] 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 305/411] 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 306/411] 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 307/411] 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 308/411] 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 309/411] 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 310/411] 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 311/411] 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 312/411] 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 313/411] 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 314/411] 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 315/411] Update test --- .../Editor/TestSceneOsuEditorGrids.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index 9d6135bbf8..f666812082 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -206,7 +206,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private void nextGridTypeIs() where T : PositionSnapGrid { - AddStep("toggle to next grid type", () => InputManager.Key(Key.H)); + AddStep("toggle to next grid type", () => + { + InputManager.PressKey(Key.ShiftLeft); + InputManager.Key(Key.G); + InputManager.ReleaseKey(Key.ShiftLeft); + }); gridActive(true); } From 9f73a45580c2452238aa80d313a8158da4b0c4a5 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 11 Oct 2024 23:57:26 +0200 Subject: [PATCH 316/411] Explicitly assert specific grid type --- osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index f666812082..fb109ba6f9 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -130,6 +130,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private void gridActive(bool active) where T : PositionSnapGrid { + AddAssert($"grid type is {typeof(T).Name}", () => this.ChildrenOfType().Any()); AddStep("choose placement tool", () => InputManager.Key(Key.Number2)); AddStep("move cursor to spacing + (1, 1)", () => { From 1e7e2e0b1c76f5dcfd2a7ab76564910f1df0aaf6 Mon Sep 17 00:00:00 2001 From: schiavoanto Date: Sat, 12 Oct 2024 00:55:33 +0200 Subject: [PATCH 317/411] 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 318/411] 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 319/411] 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 320/411] 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 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 321/411] 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 322/411] 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 323/411] 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 324/411] 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 325/411] 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 326/411] 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 327/411] 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 328/411] 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 329/411] 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 330/411] 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 331/411] 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 332/411] 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 333/411] 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 334/411] 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 335/411] 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 336/411] 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 337/411] 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 338/411] 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 339/411] Fix Beatmap Delete Dialog --- osu.Game/Screens/Select/BeatmapDeleteDialog.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapDeleteDialog.cs b/osu.Game/Screens/Select/BeatmapDeleteDialog.cs index 8bc40dbd9e..16f0cbe65e 100644 --- a/osu.Game/Screens/Select/BeatmapDeleteDialog.cs +++ b/osu.Game/Screens/Select/BeatmapDeleteDialog.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Select public BeatmapDeleteDialog(BeatmapSetInfo beatmapSet) { this.beatmapSet = beatmapSet; - BodyText = $@"{beatmapSet.Metadata.Artist} - {beatmapSet.Metadata.Title}"; + BodyText = beatmapSet.Metadata.GetDisplayTitleRomanisable(false); } [BackgroundDependencyLoader] From 17aed26f854ee5c45de5e84bb2ea5639446e708a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 14 Oct 2024 13:03:58 +0200 Subject: [PATCH 340/411] 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 341/411] 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 342/411] 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 343/411] Remove no longer required nested object reposition hacks As touched on in https://github.com/ppy/osu/issues/30237#issuecomment-2408557766, these types of maneouvers are no longer required after https://github.com/ppy/osu/pull/30021 - although as it turns out on closer inspection, these things being there still *did not actually break anything*, because the `slider.Path` mutation at the end of `modifySlider()` causes `updateNestedPositions()` to be called eventually anyway. So this is at mostly a code quality upgrade. --- .../Utils/OsuHitObjectGenerationUtils.cs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index e936c24c08..f27624a633 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -3,7 +3,6 @@ using System; using System.Linq; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Beatmaps; @@ -117,10 +116,9 @@ namespace osu.Game.Rulesets.Osu.Utils if (osuObject is not Slider slider) return; - void reflectNestedObject(OsuHitObject nested) => nested.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - nested.Position.X, nested.Position.Y); static void reflectControlPoint(PathControlPoint point) => point.Position = new Vector2(-point.Position.X, point.Position.Y); - modifySlider(slider, reflectNestedObject, reflectControlPoint); + modifySlider(slider, reflectControlPoint); } /// @@ -134,10 +132,9 @@ namespace osu.Game.Rulesets.Osu.Utils if (osuObject is not Slider slider) return; - void reflectNestedObject(OsuHitObject nested) => nested.Position = new Vector2(nested.Position.X, OsuPlayfield.BASE_SIZE.Y - nested.Position.Y); static void reflectControlPoint(PathControlPoint point) => point.Position = new Vector2(point.Position.X, -point.Position.Y); - modifySlider(slider, reflectNestedObject, reflectControlPoint); + modifySlider(slider, reflectControlPoint); } /// @@ -146,10 +143,9 @@ namespace osu.Game.Rulesets.Osu.Utils /// The slider to be flipped. public static void FlipSliderInPlaceHorizontally(Slider slider) { - void flipNestedObject(OsuHitObject nested) => nested.Position = new Vector2(slider.X - (nested.X - slider.X), nested.Y); static void flipControlPoint(PathControlPoint point) => point.Position = new Vector2(-point.Position.X, point.Position.Y); - modifySlider(slider, flipNestedObject, flipControlPoint); + modifySlider(slider, flipControlPoint); } /// @@ -159,18 +155,13 @@ namespace osu.Game.Rulesets.Osu.Utils /// The angle, measured in radians, to rotate the slider by. public static void RotateSlider(Slider slider, float rotation) { - void rotateNestedObject(OsuHitObject nested) => nested.Position = rotateVector(nested.Position - slider.Position, rotation) + slider.Position; void rotateControlPoint(PathControlPoint point) => point.Position = rotateVector(point.Position, rotation); - modifySlider(slider, rotateNestedObject, rotateControlPoint); + modifySlider(slider, rotateControlPoint); } - private static void modifySlider(Slider slider, Action modifyNestedObject, Action modifyControlPoint) + private static void modifySlider(Slider slider, Action modifyControlPoint) { - // No need to update the head and tail circles, since slider handles that when the new slider path is set - slider.NestedHitObjects.OfType().ForEach(modifyNestedObject); - slider.NestedHitObjects.OfType().ForEach(modifyNestedObject); - var controlPoints = slider.Path.ControlPoints.Select(p => new PathControlPoint(p.Position, p.Type)).ToArray(); foreach (var point in controlPoints) modifyControlPoint(point); From 8cec318c1fc2ec9d6eafd0b4a00d8320ca2c5123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 14 Oct 2024 15:28:32 +0200 Subject: [PATCH 344/411] 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 345/411] 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 346/411] 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 347/411] 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 348/411] 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 349/411] 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 350/411] 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 351/411] 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 352/411] 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 353/411] 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 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 354/411] Ensure at least scale popover axis toggle is active at any time As in, toggling off an axis if it is the only one enabled will enable the other one in turn. Co-authored-by: Dean Herbert --- .../Edit/PreciseScalePopover.cs | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index 25515a90d5..71a7793fc9 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -136,8 +136,26 @@ namespace osu.Game.Rulesets.Osu.Edit }); scaleInput.Current.BindValueChanged(scale => scaleInfo.Value = scaleInfo.Value with { Scale = scale.NewValue }); - xCheckBox.Current.BindValueChanged(x => setAxis(x.NewValue, yCheckBox.Current.Value)); - yCheckBox.Current.BindValueChanged(y => setAxis(xCheckBox.Current.Value, y.NewValue)); + xCheckBox.Current.BindValueChanged(_ => + { + if (!xCheckBox.Current.Value && !yCheckBox.Current.Value) + { + yCheckBox.Current.Value = true; + return; + } + + updateAxes(); + }); + yCheckBox.Current.BindValueChanged(_ => + { + if (!xCheckBox.Current.Value && !yCheckBox.Current.Value) + { + xCheckBox.Current.Value = true; + return; + } + + updateAxes(); + }); selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleX.Value || scaleHandler.CanScaleY.Value); playfieldCentreButton.Selected.Disabled = scaleHandler.IsScalingSlider.Value && !selectionCentreButton.Selected.Disabled; @@ -152,6 +170,12 @@ namespace osu.Game.Rulesets.Osu.Edit }); } + private void updateAxes() + { + scaleInfo.Value = scaleInfo.Value with { XAxis = xCheckBox.Current.Value, YAxis = yCheckBox.Current.Value }; + updateMaxScale(); + } + private void updateAxisCheckBoxesEnabled() { if (scaleInfo.Value.Origin != ScaleOrigin.SelectionCentre) @@ -234,12 +258,6 @@ namespace osu.Game.Rulesets.Osu.Edit private float getRotation(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.GridCentre ? gridToolbox.GridLinesRotation.Value : 0; - private void setAxis(bool x, bool y) - { - scaleInfo.Value = scaleInfo.Value with { XAxis = x, YAxis = y }; - updateMaxScale(); - } - protected override void PopIn() { base.PopIn(); From f8a13b0beb6c47a4e354cd2732e51b7171a60c33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 16 Oct 2024 08:17:22 +0200 Subject: [PATCH 355/411] 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 356/411] 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 357/411] Remove hold-to-confirm delay when pausing using keyboard shortcuts --- osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 89d083eca9..806985e19d 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -299,7 +299,7 @@ namespace osu.Game.Screens.Play.HUD { case GlobalAction.Back: if (!pendingAnimation) - BeginConfirm(); + Confirm(); return true; case GlobalAction.PauseGameplay: @@ -307,7 +307,7 @@ namespace osu.Game.Screens.Play.HUD if (ReplayLoaded.Value) return false; if (!pendingAnimation) - BeginConfirm(); + Confirm(); return true; } From 135b85a55a21d08bd713ffa291ebd723459771c9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Oct 2024 13:43:53 +0900 Subject: [PATCH 358/411] 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 359/411] 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 360/411] Fix argument order in diffcalc workflow --- .github/workflows/diffcalc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml index 805523de8b..c2eeff20df 100644 --- a/.github/workflows/diffcalc.yml +++ b/.github/workflows/diffcalc.yml @@ -346,7 +346,7 @@ jobs: docker compose logs --follow & docker compose wait generator - link=$(docker compose logs generator --tail 10 | grep 'http' | sed -E 's/^.*(http.*)$/\1/') + link=$(docker compose logs --tail 10 generator | grep 'http' | sed -E 's/^.*(http.*)$/\1/') target=$(cat "${{ needs.directory.outputs.GENERATOR_ENV }}" | grep -E '^OSU_B=' | cut -d '=' -f2-) echo "TARGET=${target}" >> "${GITHUB_OUTPUT}" From f3178b1fef38ae606b8b632735556fa9dff50b61 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Oct 2024 21:16:45 +0900 Subject: [PATCH 361/411] Add test scene --- .../TestSceneBeatmapAttributeText.cs | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs new file mode 100644 index 0000000000..bf959d9862 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs @@ -0,0 +1,98 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Models; +using osu.Game.Rulesets.Osu; +using osu.Game.Skinning.Components; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public partial class TestSceneBeatmapAttributeText : OsuTestScene + { + private readonly BeatmapAttributeText text; + + public TestSceneBeatmapAttributeText() + { + Child = text = new BeatmapAttributeText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }; + } + + [SetUp] + public void Setup() => Schedule(() => + { + Beatmap.Value = CreateWorkingBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo) + { + BeatmapInfo = + { + BPM = 100, + DifficultyName = "_Difficulty", + Status = BeatmapOnlineStatus.Loved, + Metadata = + { + Title = "_Title", + TitleUnicode = "_Title", + Artist = "_Artist", + ArtistUnicode = "_Artist", + Author = new RealmUser { Username = "_Creator" }, + Source = "_Source", + }, + Difficulty = + { + CircleSize = 1, + DrainRate = 2, + OverallDifficulty = 3, + ApproachRate = 4, + } + } + }); + }); + + [TestCase(BeatmapAttribute.CircleSize, "Circle Size: 1.00")] + [TestCase(BeatmapAttribute.HPDrain, "HP Drain: 2.00")] + [TestCase(BeatmapAttribute.Accuracy, "Accuracy: 3.00")] + [TestCase(BeatmapAttribute.ApproachRate, "Approach Rate: 4.00")] + [TestCase(BeatmapAttribute.Title, "Title: _Title")] + [TestCase(BeatmapAttribute.Artist, "Artist: _Artist")] + [TestCase(BeatmapAttribute.Creator, "Creator: _Creator")] + [TestCase(BeatmapAttribute.DifficultyName, "Difficulty: _Difficulty")] + [TestCase(BeatmapAttribute.Source, "Source: _Source")] + [TestCase(BeatmapAttribute.RankedStatus, "Beatmap Status: Loved")] + public void TestAttributeDisplay(BeatmapAttribute attribute, string expectedText) + { + AddStep($"set attribute: {attribute}", () => text.Attribute.Value = attribute); + AddAssert("check correct text", getText, () => Is.EqualTo(expectedText)); + } + + [Test] + public void TestChangeBeatmap() + { + AddStep("set title attribute", () => text.Attribute.Value = BeatmapAttribute.Title); + AddAssert("check initial title", getText, () => Is.EqualTo("Title: _Title")); + + AddStep("change to beatmap with another title", () => Beatmap.Value = CreateWorkingBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo) + { + BeatmapInfo = + { + Metadata = + { + Title = "Another" + } + } + })); + + AddAssert("check new title", getText, () => Is.EqualTo("Title: Another")); + } + + private string getText() => text.ChildrenOfType().Single().Text.ToString(); + } +} From 7416106321d6c8bf80bc971b088c1f6344d80ef5 Mon Sep 17 00:00:00 2001 From: jhk2601 Date: Thu, 17 Oct 2024 14:38:13 -0400 Subject: [PATCH 362/411] 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 363/411] 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 364/411] 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 365/411] 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 366/411] 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 367/411] 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 368/411] 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 369/411] 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 370/411] 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 371/411] 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 372/411] 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 373/411] 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 374/411] 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 375/411] 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 376/411] 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 377/411] 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 378/411] 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 379/411] 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 380/411] 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 381/411] 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 382/411] 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 383/411] 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 384/411] 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 385/411] 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 386/411] 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 387/411] 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 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 388/411] 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 389/411] 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 390/411] 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 391/411] 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 392/411] 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 393/411] 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 394/411] 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 395/411] 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 396/411] 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 397/411] Change property type Nobody is supposed to be using `ColourInfo` directly, really. --- .../Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index f1eed81e56..9250a82902 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -236,7 +236,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores set => text.Text = value; } - public ColourInfo TextColour + public Colour4 TextColour { set => text.Colour = value; } From 0a15eec7de9a9d435f200453af736859f0cda658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 21 Oct 2024 13:52:17 +0200 Subject: [PATCH 398/411] 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 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 399/411] 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 400/411] 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 401/411] 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 402/411] 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 403/411] 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 13fba9f92e8e7e8129efda00c5f4311b71b6af7b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Oct 2024 17:09:46 +0900 Subject: [PATCH 404/411] 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 405/411] 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 406/411] 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 407/411] 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 408/411] 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 409/411] 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 410/411] 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 411/411] 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]));