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 0001/1274] 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 0002/1274] 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 0003/1274] 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 0004/1274] 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 0005/1274] 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 0006/1274] 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 0007/1274] 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 0008/1274] 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 0009/1274] 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 0010/1274] 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 0011/1274] 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 0012/1274] 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 0013/1274] 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 0014/1274] 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 0015/1274] 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 0016/1274] 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 0017/1274] 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 0018/1274] 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 0019/1274] 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 0020/1274] 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 0021/1274] 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 0022/1274] 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 0023/1274] 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 0024/1274] 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 0025/1274] 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 0026/1274] 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 0027/1274] 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 0028/1274] 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 0029/1274] 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 0030/1274] 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 0031/1274] 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 0032/1274] 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 0033/1274] 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 0034/1274] 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 0035/1274] 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 0036/1274] 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 0037/1274] 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 0038/1274] 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 0039/1274] 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 0040/1274] 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 0041/1274] 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 0042/1274] 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 0043/1274] 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 0044/1274] 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 0045/1274] 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 0046/1274] 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 0047/1274] 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 0048/1274] 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 0049/1274] 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 22a0bc7d9d437ac1e502cd6e54c64fe66afb60c8 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Wed, 20 Dec 2023 00:05:32 +0100
Subject: [PATCH 0050/1274] Add basic slider distance control
---
.../TestSceneSliderControlPointPiece.cs | 2 +-
.../TestSceneSliderSelectionBlueprint.cs | 2 +-
.../Sliders/Components/SliderTailPiece.cs | 104 ++++++++++++++++++
.../Blueprints/Sliders/SliderCircleOverlay.cs | 8 +-
.../Sliders/SliderSelectionBlueprint.cs | 4 +-
5 files changed, 112 insertions(+), 8 deletions(-)
create mode 100644 osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs
index 99ced30ffe..085e11460f 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs
@@ -353,7 +353,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{
public new SliderBodyPiece BodyPiece => base.BodyPiece;
public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay;
- public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay;
+ public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailPiece;
public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
public TestSliderBlueprint(Slider slider)
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
index d4d99e1019..adc4929227 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
@@ -198,7 +198,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{
public new SliderBodyPiece BodyPiece => base.BodyPiece;
public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay;
- public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay;
+ public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailPiece;
public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
public TestSliderBlueprint(Slider slider)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
new file mode 100644
index 0000000000..96169c5e1f
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
@@ -0,0 +1,104 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Input;
+using osu.Framework.Input.Events;
+using osu.Framework.Utils;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Screens.Edit;
+using osuTK;
+using osuTK.Graphics;
+using osuTK.Input;
+
+namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
+{
+ public partial class SliderTailPiece : SliderCircleOverlay
+ {
+ ///
+ /// Whether this is currently being dragged.
+ ///
+ private bool isDragging;
+
+ private InputManager inputManager = null!;
+
+ [Resolved(CanBeNull = true)]
+ private EditorBeatmap? editorBeatmap { get; set; }
+
+ [Resolved]
+ private OsuColour colours { get; set; } = null!;
+
+ public SliderTailPiece(Slider slider, SliderPosition position)
+ : base(slider, position)
+ {
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ inputManager = GetContainingInputManager();
+ }
+
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => CirclePiece.ReceivePositionalInputAt(screenSpacePos);
+
+ protected override bool OnHover(HoverEvent e)
+ {
+ updateCirclePieceColour();
+ return false;
+ }
+
+ protected override void OnHoverLost(HoverLostEvent e)
+ {
+ updateCirclePieceColour();
+ }
+
+ private void updateCirclePieceColour()
+ {
+ Color4 colour = colours.Yellow;
+
+ if (IsHovered)
+ colour = colour.Lighten(1);
+
+ CirclePiece.Colour = colour;
+ }
+
+ protected override bool OnDragStart(DragStartEvent e)
+ {
+ if (e.Button == MouseButton.Right || !inputManager.CurrentState.Keyboard.ShiftPressed)
+ return false;
+
+ isDragging = true;
+ editorBeatmap?.BeginChange();
+
+ return true;
+ }
+
+ protected override void OnDrag(DragEvent e)
+ {
+ double proposedDistance = Slider.Path.Distance + e.Delta.X;
+
+ proposedDistance = MathHelper.Clamp(proposedDistance, 0, Slider.Path.CalculatedDistance);
+ proposedDistance = MathHelper.Clamp(proposedDistance,
+ 0.1 * Slider.Path.Distance / Slider.SliderVelocityMultiplier,
+ 10 * Slider.Path.Distance / Slider.SliderVelocityMultiplier);
+
+ if (Precision.AlmostEquals(proposedDistance, Slider.Path.Distance))
+ return;
+
+ Slider.SliderVelocityMultiplier *= proposedDistance / Slider.Path.Distance;
+ Slider.Path.ExpectedDistance.Value = proposedDistance;
+ editorBeatmap?.Update(Slider);
+ }
+
+ protected override void OnDragEnd(DragEndEvent e)
+ {
+ if (isDragging)
+ {
+ editorBeatmap?.EndChange();
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs
index d47cf6bf23..b00a42748e 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs
@@ -11,14 +11,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
public partial class SliderCircleOverlay : CompositeDrawable
{
protected readonly HitCirclePiece CirclePiece;
+ protected readonly Slider Slider;
- private readonly Slider slider;
- private readonly SliderPosition position;
private readonly HitCircleOverlapMarker marker;
+ private readonly SliderPosition position;
public SliderCircleOverlay(Slider slider, SliderPosition position)
{
- this.slider = slider;
+ Slider = slider;
this.position = position;
InternalChildren = new Drawable[]
@@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
base.Update();
- var circle = position == SliderPosition.Start ? (HitCircle)slider.HeadCircle : slider.TailCircle;
+ var circle = position == SliderPosition.Start ? (HitCircle)Slider.HeadCircle : Slider.TailCircle;
CirclePiece.UpdateFrom(circle);
marker.UpdateFrom(circle);
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
index b3efe1c495..37c433cf8b 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
@@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
protected SliderBodyPiece BodyPiece { get; private set; }
protected SliderCircleOverlay HeadOverlay { get; private set; }
- protected SliderCircleOverlay TailOverlay { get; private set; }
+ protected SliderTailPiece TailPiece { get; private set; }
[CanBeNull]
protected PathControlPointVisualiser ControlPointVisualiser { get; private set; }
@@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
BodyPiece = new SliderBodyPiece(),
HeadOverlay = CreateCircleOverlay(HitObject, SliderPosition.Start),
- TailOverlay = CreateCircleOverlay(HitObject, SliderPosition.End),
+ TailPiece = new SliderTailPiece(HitObject, SliderPosition.End),
};
}
From 1258a9d378a53a7f352bb5bd6fb01f0a44f3305e Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Wed, 20 Dec 2023 01:48:42 +0100
Subject: [PATCH 0051/1274] Find closest distance value to mouse
---
.../Sliders/Components/SliderTailPiece.cs | 60 +++++++++++++++++--
1 file changed, 55 insertions(+), 5 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
index 96169c5e1f..5cf9346f2e 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
@@ -1,12 +1,15 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Linq;
using osu.Framework.Allocation;
+using osu.Framework.Caching;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Input;
using osu.Framework.Input.Events;
using osu.Framework.Utils;
using osu.Game.Graphics;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
using osuTK;
@@ -24,6 +27,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
private InputManager inputManager = null!;
+ private readonly Cached fullPathCache = new Cached();
+
[Resolved(CanBeNull = true)]
private EditorBeatmap? editorBeatmap { get; set; }
@@ -33,6 +38,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
public SliderTailPiece(Slider slider, SliderPosition position)
: base(slider, position)
{
+ Slider.Path.ControlPoints.CollectionChanged += (_, _) => fullPathCache.Invalidate();
}
protected override void LoadComplete()
@@ -78,17 +84,18 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
protected override void OnDrag(DragEvent e)
{
- double proposedDistance = Slider.Path.Distance + e.Delta.X;
+ double oldDistance = Slider.Path.Distance;
+ double proposedDistance = findClosestPathDistance(e);
proposedDistance = MathHelper.Clamp(proposedDistance, 0, Slider.Path.CalculatedDistance);
proposedDistance = MathHelper.Clamp(proposedDistance,
- 0.1 * Slider.Path.Distance / Slider.SliderVelocityMultiplier,
- 10 * Slider.Path.Distance / Slider.SliderVelocityMultiplier);
+ 0.1 * oldDistance / Slider.SliderVelocityMultiplier,
+ 10 * oldDistance / Slider.SliderVelocityMultiplier);
- if (Precision.AlmostEquals(proposedDistance, Slider.Path.Distance))
+ if (Precision.AlmostEquals(proposedDistance, oldDistance))
return;
- Slider.SliderVelocityMultiplier *= proposedDistance / Slider.Path.Distance;
+ Slider.SliderVelocityMultiplier *= proposedDistance / oldDistance;
Slider.Path.ExpectedDistance.Value = proposedDistance;
editorBeatmap?.Update(Slider);
}
@@ -100,5 +107,48 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
editorBeatmap?.EndChange();
}
}
+
+ ///
+ /// Finds the expected distance value for which the slider end is closest to the mouse position.
+ ///
+ private double findClosestPathDistance(DragEvent e)
+ {
+ const double step1 = 10;
+ const double step2 = 0.1;
+
+ var desiredPosition = e.MousePosition - Slider.Position;
+
+ if (!fullPathCache.IsValid)
+ fullPathCache.Value = new SliderPath(Slider.Path.ControlPoints.ToArray());
+
+ // Do a linear search to find the closest point on the path to the mouse position.
+ double bestValue = 0;
+ double minDistance = double.MaxValue;
+
+ for (double d = 0; d <= fullPathCache.Value.CalculatedDistance; d += step1)
+ {
+ double t = d / fullPathCache.Value.CalculatedDistance;
+ float dist = Vector2.Distance(fullPathCache.Value.PositionAt(t), desiredPosition);
+
+ if (dist >= minDistance) continue;
+
+ minDistance = dist;
+ bestValue = d;
+ }
+
+ // Do another linear search to fine-tune the result.
+ for (double d = bestValue - step1; d <= bestValue + step1; d += step2)
+ {
+ double t = d / fullPathCache.Value.CalculatedDistance;
+ float dist = Vector2.Distance(fullPathCache.Value.PositionAt(t), desiredPosition);
+
+ if (dist >= minDistance) continue;
+
+ minDistance = dist;
+ bestValue = d;
+ }
+
+ return bestValue;
+ }
}
}
From 1365a1b7bec8d444dfe99f098ed2ad2c1863f770 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Wed, 20 Dec 2023 01:57:11 +0100
Subject: [PATCH 0052/1274] fix tests
---
.../Editor/TestSceneSliderControlPointPiece.cs | 13 ++++++++++++-
.../Editor/TestSceneSliderSelectionBlueprint.cs | 15 +++++++++++++--
.../Sliders/SliderSelectionBlueprint.cs | 3 ++-
3 files changed, 27 insertions(+), 4 deletions(-)
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs
index 085e11460f..1a7430704d 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs
@@ -353,7 +353,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{
public new SliderBodyPiece BodyPiece => base.BodyPiece;
public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay;
- public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailPiece;
+ public new TestSliderTailPiece TailPiece => (TestSliderTailPiece)base.TailPiece;
public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
public TestSliderBlueprint(Slider slider)
@@ -362,6 +362,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
}
protected override SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new TestSliderCircleOverlay(slider, position);
+ protected override SliderTailPiece CreateTailPiece(Slider slider, SliderPosition position) => new TestSliderTailPiece(slider, position);
}
private partial class TestSliderCircleOverlay : SliderCircleOverlay
@@ -373,5 +374,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{
}
}
+
+ private partial class TestSliderTailPiece : SliderTailPiece
+ {
+ public new HitCirclePiece CirclePiece => base.CirclePiece;
+
+ public TestSliderTailPiece(Slider slider, SliderPosition position)
+ : base(slider, position)
+ {
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
index adc4929227..2c5cff3f70 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
@@ -179,7 +179,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
() => Precision.AlmostEquals(blueprint.HeadOverlay.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.HeadCircle.ScreenSpaceDrawQuad.Centre));
AddAssert("tail positioned correctly",
- () => Precision.AlmostEquals(blueprint.TailOverlay.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre));
+ () => Precision.AlmostEquals(blueprint.TailPiece.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre));
}
private void moveMouseToControlPoint(int index)
@@ -198,7 +198,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{
public new SliderBodyPiece BodyPiece => base.BodyPiece;
public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay;
- public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailPiece;
+ public new TestSliderTailPiece TailPiece => (TestSliderTailPiece)base.TailPiece;
public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
public TestSliderBlueprint(Slider slider)
@@ -207,6 +207,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
}
protected override SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new TestSliderCircleOverlay(slider, position);
+ protected override SliderTailPiece CreateTailPiece(Slider slider, SliderPosition position) => new TestSliderTailPiece(slider, position);
}
private partial class TestSliderCircleOverlay : SliderCircleOverlay
@@ -218,5 +219,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{
}
}
+
+ private partial class TestSliderTailPiece : SliderTailPiece
+ {
+ public new HitCirclePiece CirclePiece => base.CirclePiece;
+
+ public TestSliderTailPiece(Slider slider, SliderPosition position)
+ : base(slider, position)
+ {
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
index 37c433cf8b..a13eeb0208 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
@@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
BodyPiece = new SliderBodyPiece(),
HeadOverlay = CreateCircleOverlay(HitObject, SliderPosition.Start),
- TailPiece = new SliderTailPiece(HitObject, SliderPosition.End),
+ TailPiece = CreateTailPiece(HitObject, SliderPosition.End),
};
}
@@ -415,5 +415,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true;
protected virtual SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new SliderCircleOverlay(slider, position);
+ protected virtual SliderTailPiece CreateTailPiece(Slider slider, SliderPosition position) => new SliderTailPiece(slider, position);
}
}
From 3aaf0b39f56e5d3df4a597a5925258470fbb73c3 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Wed, 20 Dec 2023 02:12:16 +0100
Subject: [PATCH 0053/1274] Add slider tail dragging test
---
.../TestSceneSliderSelectionBlueprint.cs | 48 +++++++++++++++++++
1 file changed, 48 insertions(+)
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
index 2c5cff3f70..3f9620a8d1 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
@@ -163,6 +163,54 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
checkControlPointSelected(1, false);
}
+ [Test]
+ public void TestDragSliderTail()
+ {
+ AddStep($"move mouse to slider tail", () =>
+ {
+ Vector2 position = slider.EndPosition + new Vector2(10, 0);
+ InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
+ });
+ AddStep("shift + drag", () =>
+ {
+ InputManager.PressKey(Key.ShiftLeft);
+ InputManager.PressButton(MouseButton.Left);
+ });
+ moveMouseToControlPoint(1);
+ AddStep("release", () =>
+ {
+ InputManager.ReleaseButton(MouseButton.Left);
+ InputManager.ReleaseKey(Key.ShiftLeft);
+ });
+
+ AddAssert("expected distance halved",
+ () => Precision.AlmostEquals(slider.Path.Distance, 172.2, 0.1));
+
+ AddStep($"move mouse to slider tail", () =>
+ {
+ Vector2 position = slider.EndPosition + new Vector2(10, 0);
+ InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
+ });
+ AddStep("shift + drag", () =>
+ {
+ InputManager.PressKey(Key.ShiftLeft);
+ InputManager.PressButton(MouseButton.Left);
+ });
+ AddStep($"move mouse beyond last control point", () =>
+ {
+ Vector2 position = slider.Position + slider.Path.ControlPoints[2].Position + new Vector2(50, 0);
+ InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
+ });
+ AddStep("release", () =>
+ {
+ InputManager.ReleaseButton(MouseButton.Left);
+ InputManager.ReleaseKey(Key.ShiftLeft);
+ });
+
+ AddAssert("expected distance is calculated distance",
+ () => Precision.AlmostEquals(slider.Path.Distance, slider.Path.CalculatedDistance, 0.1));
+ }
+
private void moveHitObject()
{
AddStep("move hitobject", () =>
From 66f4dcc578b3f760e3d2e6935fca2fecfec526a7 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Wed, 20 Dec 2023 02:32:20 +0100
Subject: [PATCH 0054/1274] fix repeat sliders half
---
.../Edit/Blueprints/Sliders/Components/SliderTailPiece.cs | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
index 5cf9346f2e..e60e41f6d5 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
@@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
Color4 colour = colours.Yellow;
- if (IsHovered)
+ if (IsHovered && Slider.RepeatCount % 2 == 0)
colour = colour.Lighten(1);
CirclePiece.Colour = colour;
@@ -73,7 +73,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
protected override bool OnDragStart(DragStartEvent e)
{
- if (e.Button == MouseButton.Right || !inputManager.CurrentState.Keyboard.ShiftPressed)
+ // Disable dragging if the slider has an uneven number of repeats because the slider tail will be on the wrong side of the path.
+ if (e.Button == MouseButton.Right || !inputManager.CurrentState.Keyboard.ShiftPressed || Slider.RepeatCount % 2 == 1)
return false;
isDragging = true;
From f7cb6b9ed09bd041f3c21448abcb83c8de3ed09e Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Wed, 20 Dec 2023 12:58:32 +0100
Subject: [PATCH 0055/1274] Fix all repeat sliders being draggable
---
.../Edit/Blueprints/Sliders/Components/SliderTailPiece.cs | 5 ++---
.../Edit/Blueprints/Sliders/SliderCircleOverlay.cs | 3 ++-
osu.Game.Rulesets.Osu/Objects/Slider.cs | 8 +++++++-
3 files changed, 11 insertions(+), 5 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
index e60e41f6d5..5cf9346f2e 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
@@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
Color4 colour = colours.Yellow;
- if (IsHovered && Slider.RepeatCount % 2 == 0)
+ if (IsHovered)
colour = colour.Lighten(1);
CirclePiece.Colour = colour;
@@ -73,8 +73,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
protected override bool OnDragStart(DragStartEvent e)
{
- // Disable dragging if the slider has an uneven number of repeats because the slider tail will be on the wrong side of the path.
- if (e.Button == MouseButton.Right || !inputManager.CurrentState.Keyboard.ShiftPressed || Slider.RepeatCount % 2 == 1)
+ if (e.Button == MouseButton.Right || !inputManager.CurrentState.Keyboard.ShiftPressed)
return false;
isDragging = true;
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs
index b00a42748e..2bf5118039 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs
@@ -32,7 +32,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
base.Update();
- var circle = position == SliderPosition.Start ? (HitCircle)Slider.HeadCircle : Slider.TailCircle;
+ var circle = position == SliderPosition.Start ? (HitCircle)Slider.HeadCircle :
+ Slider.RepeatCount % 2 == 0 ? Slider.TailCircle : Slider.LastRepeat;
CirclePiece.UpdateFrom(circle);
marker.UpdateFrom(circle);
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index 506145568e..7a22bf5c4d 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -162,6 +162,9 @@ namespace osu.Game.Rulesets.Osu.Objects
[JsonIgnore]
public SliderTailCircle TailCircle { get; protected set; }
+ [JsonIgnore]
+ public SliderRepeat LastRepeat { get; protected set; }
+
public Slider()
{
SamplesBindable.CollectionChanged += (_, _) => UpdateNestedSamples();
@@ -225,7 +228,7 @@ namespace osu.Game.Rulesets.Osu.Objects
break;
case SliderEventType.Repeat:
- AddNested(new SliderRepeat(this)
+ AddNested(LastRepeat = new SliderRepeat(this)
{
RepeatIndex = e.SpanIndex,
StartTime = StartTime + (e.SpanIndex + 1) * SpanDuration,
@@ -248,6 +251,9 @@ namespace osu.Game.Rulesets.Osu.Objects
if (TailCircle != null)
TailCircle.Position = EndPosition;
+
+ if (LastRepeat != null)
+ LastRepeat.Position = RepeatCount % 2 == 0 ? Position : Position + Path.PositionAt(1);
}
protected void UpdateNestedSamples()
From d000da725df5d5a2e031c96ce1861d581feac497 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Wed, 20 Dec 2023 13:14:05 +0100
Subject: [PATCH 0056/1274] fix code quality
---
.../Editor/TestSceneSliderSelectionBlueprint.cs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
index 3f9620a8d1..3faf181465 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
@@ -166,7 +166,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
[Test]
public void TestDragSliderTail()
{
- AddStep($"move mouse to slider tail", () =>
+ AddStep("move mouse to slider tail", () =>
{
Vector2 position = slider.EndPosition + new Vector2(10, 0);
InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
@@ -186,7 +186,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddAssert("expected distance halved",
() => Precision.AlmostEquals(slider.Path.Distance, 172.2, 0.1));
- AddStep($"move mouse to slider tail", () =>
+ AddStep("move mouse to slider tail", () =>
{
Vector2 position = slider.EndPosition + new Vector2(10, 0);
InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
@@ -196,7 +196,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
InputManager.PressKey(Key.ShiftLeft);
InputManager.PressButton(MouseButton.Left);
});
- AddStep($"move mouse beyond last control point", () =>
+ AddStep("move mouse beyond last control point", () =>
{
Vector2 position = slider.Position + slider.Path.ControlPoints[2].Position + new Vector2(50, 0);
InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
From eedb436389afe01b1666d6e3ed73d10cc8b2d070 Mon Sep 17 00:00:00 2001
From: Salman Ahmed
Date: Sat, 30 Dec 2023 03:47:42 +0300
Subject: [PATCH 0057/1274] Move combo counter to ruleset-specific HUD
components target
---
.../Argon/CatchArgonSkinTransformer.cs | 2 +-
.../Skinning/Argon/OsuArgonSkinTransformer.cs | 2 +-
.../Argon/TaikoArgonSkinTransformer.cs | 8 +--
osu.Game/Rulesets/Ruleset.cs | 14 ++++-
osu.Game/Skinning/ArgonSkin.cs | 16 +-----
osu.Game/Skinning/ArgonSkinTransformer.cs | 53 +++++++++++++++++++
osu.Game/Skinning/LegacySkin.cs | 1 -
osu.Game/Skinning/LegacySkinTransformer.cs | 44 +++++++++++++--
8 files changed, 113 insertions(+), 27 deletions(-)
create mode 100644 osu.Game/Skinning/ArgonSkinTransformer.cs
diff --git a/osu.Game.Rulesets.Catch/Skinning/Argon/CatchArgonSkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Argon/CatchArgonSkinTransformer.cs
index 520c2de248..a67945df98 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Argon/CatchArgonSkinTransformer.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Argon/CatchArgonSkinTransformer.cs
@@ -6,7 +6,7 @@ using osu.Game.Skinning;
namespace osu.Game.Rulesets.Catch.Skinning.Argon
{
- public class CatchArgonSkinTransformer : SkinTransformer
+ public class CatchArgonSkinTransformer : ArgonSkinTransformer
{
public CatchArgonSkinTransformer(ISkin skin)
: base(skin)
diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs
index 0f9c97059c..9526ea05c9 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs
@@ -7,7 +7,7 @@ using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu.Skinning.Argon
{
- public class OsuArgonSkinTransformer : SkinTransformer
+ public class OsuArgonSkinTransformer : ArgonSkinTransformer
{
public OsuArgonSkinTransformer(ISkin skin)
: base(skin)
diff --git a/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs
index 9fcecd2b1a..7d38d6c9e5 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs
@@ -7,16 +7,16 @@ using osu.Game.Skinning;
namespace osu.Game.Rulesets.Taiko.Skinning.Argon
{
- public class TaikoArgonSkinTransformer : SkinTransformer
+ public class TaikoArgonSkinTransformer : ArgonSkinTransformer
{
public TaikoArgonSkinTransformer(ISkin skin)
: base(skin)
{
}
- public override Drawable? GetDrawableComponent(ISkinComponentLookup component)
+ public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
{
- switch (component)
+ switch (lookup)
{
case GameplaySkinComponentLookup resultComponent:
// This should eventually be moved to a skin setting, when supported.
@@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
break;
}
- return base.GetDrawableComponent(component);
+ return base.GetDrawableComponent(lookup);
}
}
}
diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs
index 37a35fd3ae..c7d4779064 100644
--- a/osu.Game/Rulesets/Ruleset.cs
+++ b/osu.Game/Rulesets/Ruleset.cs
@@ -212,7 +212,19 @@ namespace osu.Game.Rulesets
/// The source skin.
/// The current beatmap.
/// A skin with a transformer applied, or null if no transformation is provided by this ruleset.
- public virtual ISkin? CreateSkinTransformer(ISkin skin, IBeatmap beatmap) => null;
+ public virtual ISkin? CreateSkinTransformer(ISkin skin, IBeatmap beatmap)
+ {
+ switch (skin)
+ {
+ case LegacySkin:
+ return new LegacySkinTransformer(skin);
+
+ case ArgonSkin:
+ return new ArgonSkinTransformer(skin);
+ }
+
+ return null;
+ }
protected Ruleset()
{
diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs
index 6fcab6a977..bdb65713a0 100644
--- a/osu.Game/Skinning/ArgonSkin.cs
+++ b/osu.Game/Skinning/ArgonSkin.cs
@@ -111,14 +111,13 @@ namespace osu.Game.Skinning
return songSelectComponents;
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
- var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container =>
+ var mainHUDComponents = new DefaultSkinComponentsContainer(container =>
{
var health = container.OfType().FirstOrDefault();
var healthLine = container.OfType().FirstOrDefault();
var wedgePieces = container.OfType().ToArray();
var score = container.OfType().FirstOrDefault();
var accuracy = container.OfType().FirstOrDefault();
- var combo = container.OfType().FirstOrDefault();
var songProgress = container.OfType().FirstOrDefault();
var keyCounter = container.OfType().FirstOrDefault();
@@ -192,13 +191,6 @@ namespace osu.Game.Skinning
keyCounter.Origin = Anchor.BottomRight;
keyCounter.Position = new Vector2(-(hitError.Width + padding), -(padding * 2 + song_progress_offset_height));
}
-
- if (combo != null && hitError != null)
- {
- combo.Anchor = Anchor.BottomLeft;
- combo.Origin = Anchor.BottomLeft;
- combo.Position = new Vector2((hitError.Width + padding), -(padding * 2 + song_progress_offset_height));
- }
}
}
})
@@ -224,10 +216,6 @@ namespace osu.Game.Skinning
CornerRadius = { Value = 0.5f }
},
new ArgonAccuracyCounter(),
- new ArgonComboCounter
- {
- Scale = new Vector2(1.3f)
- },
new BarHitErrorMeter(),
new BarHitErrorMeter(),
new ArgonSongProgress(),
@@ -235,7 +223,7 @@ namespace osu.Game.Skinning
}
};
- return skinnableTargetWrapper;
+ return mainHUDComponents;
}
return null;
diff --git a/osu.Game/Skinning/ArgonSkinTransformer.cs b/osu.Game/Skinning/ArgonSkinTransformer.cs
new file mode 100644
index 0000000000..387a7a9c0b
--- /dev/null
+++ b/osu.Game/Skinning/ArgonSkinTransformer.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.Linq;
+using osu.Framework.Graphics;
+using osu.Game.Screens.Play.HUD;
+using osuTK;
+
+namespace osu.Game.Skinning
+{
+ public class ArgonSkinTransformer : SkinTransformer
+ {
+ public ArgonSkinTransformer(ISkin skin)
+ : base(skin)
+ {
+ }
+
+ public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
+ {
+ switch (lookup)
+ {
+ case SkinComponentsContainerLookup containerLookup:
+ switch (containerLookup.Target)
+ {
+ case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null:
+ var rulesetHUDComponents = Skin.GetDrawableComponent(lookup);
+
+ rulesetHUDComponents ??= new DefaultSkinComponentsContainer(container =>
+ {
+ var combo = container.OfType().FirstOrDefault();
+
+ if (combo != null)
+ {
+ combo.Anchor = Anchor.BottomLeft;
+ combo.Origin = Anchor.BottomLeft;
+ combo.Position = new Vector2(36, -66);
+ combo.Scale = new Vector2(1.3f);
+ }
+ })
+ {
+ new ArgonComboCounter(),
+ };
+
+ return rulesetHUDComponents;
+ }
+
+ break;
+ }
+
+ return base.GetDrawableComponent(lookup);
+ }
+ }
+}
diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs
index 8f0cd59b68..b8e721165e 100644
--- a/osu.Game/Skinning/LegacySkin.cs
+++ b/osu.Game/Skinning/LegacySkin.cs
@@ -399,7 +399,6 @@ namespace osu.Game.Skinning
{
Children = new Drawable[]
{
- new LegacyComboCounter(),
new LegacyScoreCounter(),
new LegacyAccuracyCounter(),
new LegacySongProgress(),
diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs
index 367e5bae01..3ea316c0c7 100644
--- a/osu.Game/Skinning/LegacySkinTransformer.cs
+++ b/osu.Game/Skinning/LegacySkinTransformer.cs
@@ -1,28 +1,62 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Linq;
using osu.Framework.Audio.Sample;
+using osu.Framework.Graphics;
using osu.Game.Audio;
using osu.Game.Rulesets.Objects.Legacy;
+using osuTK;
using static osu.Game.Skinning.SkinConfiguration;
namespace osu.Game.Skinning
{
- ///
- /// Transformer used to handle support of legacy features for individual rulesets.
- ///
- public abstract class LegacySkinTransformer : SkinTransformer
+ public class LegacySkinTransformer : SkinTransformer
{
///
/// Whether the skin being transformed is able to provide legacy resources for the ruleset.
///
public virtual bool IsProvidingLegacyResources => this.HasFont(LegacyFont.Combo);
- protected LegacySkinTransformer(ISkin skin)
+ public LegacySkinTransformer(ISkin skin)
: base(skin)
{
}
+ public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
+ {
+ switch (lookup)
+ {
+ case SkinComponentsContainerLookup containerLookup:
+ switch (containerLookup.Target)
+ {
+ case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null:
+ var rulesetHUDComponents = base.GetDrawableComponent(lookup);
+
+ rulesetHUDComponents ??= new DefaultSkinComponentsContainer(container =>
+ {
+ var combo = container.OfType().FirstOrDefault();
+
+ if (combo != null)
+ {
+ combo.Anchor = Anchor.BottomLeft;
+ combo.Origin = Anchor.BottomLeft;
+ combo.Scale = new Vector2(1.28f);
+ }
+ })
+ {
+ new LegacyComboCounter()
+ };
+
+ return rulesetHUDComponents;
+ }
+
+ break;
+ }
+
+ return base.GetDrawableComponent(lookup);
+ }
+
public override ISample? GetSample(ISampleInfo sampleInfo)
{
if (!(sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample))
From e469e06271e4e4b23a93b5d0c30bf7693fb947e8 Mon Sep 17 00:00:00 2001
From: Salman Ahmed
Date: Sat, 30 Dec 2023 03:54:53 +0300
Subject: [PATCH 0058/1274] Refactor `CatchLegacySkinTransformer` logic and
remove `HiddenByRulesetImplementation` entirely
---
.../TestSceneCatchPlayerLegacySkin.cs | 8 +-
.../Legacy/CatchLegacySkinTransformer.cs | 102 ++++++++----------
.../TestSceneSkinnableComboCounter.cs | 13 ---
osu.Game/Skinning/LegacyComboCounter.cs | 12 ---
4 files changed, 49 insertions(+), 86 deletions(-)
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs
index 5406230359..99325e14c8 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs
@@ -4,7 +4,6 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Extensions.IEnumerableExtensions;
-using osu.Framework.Graphics.Containers;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Skinning;
@@ -19,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Tests
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
[Test]
- public void TestLegacyHUDComboCounterHidden([Values] bool withModifiedSkin)
+ public void TestLegacyHUDComboCounterNotExistent([Values] bool withModifiedSkin)
{
if (withModifiedSkin)
{
@@ -29,10 +28,7 @@ namespace osu.Game.Rulesets.Catch.Tests
CreateTest();
}
- AddAssert("legacy HUD combo counter hidden", () =>
- {
- return Player.ChildrenOfType().All(c => c.ChildrenOfType().Single().Alpha == 0f);
- });
+ AddAssert("legacy HUD combo counter not added", () => !Player.ChildrenOfType().Any());
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
index fb8af9bdb6..675c61a2c5 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
@@ -1,10 +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 System.Linq;
+using System.Diagnostics;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
using osu.Game.Skinning;
using osuTK.Graphics;
@@ -28,76 +27,69 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
{
- if (lookup is SkinComponentsContainerLookup containerLookup)
+ switch (lookup)
{
- switch (containerLookup.Target)
- {
- case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
- var components = base.GetDrawableComponent(lookup) as Container;
+ case SkinComponentsContainerLookup containerLookup:
+ switch (containerLookup.Target)
+ {
+ case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null:
+ Debug.Assert(containerLookup.Ruleset.ShortName == CatchRuleset.SHORT_NAME);
+ // todo: remove CatchSkinComponents.CatchComboCounter and refactor LegacyCatchComboCounter to be added here instead.
+ return Skin.GetDrawableComponent(lookup);
+ }
- if (providesComboCounter && components != null)
- {
- // catch may provide its own combo counter; hide the default.
- // todo: this should be done in an elegant way per ruleset, defining which HUD skin components should be displayed.
- foreach (var legacyComboCounter in components.OfType())
- legacyComboCounter.HiddenByRulesetImplementation = false;
- }
+ break;
- return components;
- }
- }
+ case CatchSkinComponentLookup catchSkinComponent:
+ switch (catchSkinComponent.Component)
+ {
+ case CatchSkinComponents.Fruit:
+ if (hasPear)
+ return new LegacyFruitPiece();
- if (lookup is CatchSkinComponentLookup catchSkinComponent)
- {
- switch (catchSkinComponent.Component)
- {
- case CatchSkinComponents.Fruit:
- if (hasPear)
- return new LegacyFruitPiece();
+ return null;
- return null;
+ case CatchSkinComponents.Banana:
+ if (GetTexture("fruit-bananas") != null)
+ return new LegacyBananaPiece();
- case CatchSkinComponents.Banana:
- if (GetTexture("fruit-bananas") != null)
- return new LegacyBananaPiece();
+ return null;
- return null;
+ case CatchSkinComponents.Droplet:
+ if (GetTexture("fruit-drop") != null)
+ return new LegacyDropletPiece();
- case CatchSkinComponents.Droplet:
- if (GetTexture("fruit-drop") != null)
- return new LegacyDropletPiece();
+ return null;
- return null;
+ case CatchSkinComponents.Catcher:
+ decimal version = GetConfig(SkinConfiguration.LegacySetting.Version)?.Value ?? 1;
- case CatchSkinComponents.Catcher:
- decimal version = GetConfig(SkinConfiguration.LegacySetting.Version)?.Value ?? 1;
+ if (version < 2.3m)
+ {
+ if (hasOldStyleCatcherSprite())
+ return new LegacyCatcherOld();
+ }
- if (version < 2.3m)
- {
- if (hasOldStyleCatcherSprite())
- return new LegacyCatcherOld();
- }
+ if (hasNewStyleCatcherSprite())
+ return new LegacyCatcherNew();
- if (hasNewStyleCatcherSprite())
- return new LegacyCatcherNew();
+ return null;
- return null;
+ case CatchSkinComponents.CatchComboCounter:
+ if (providesComboCounter)
+ return new LegacyCatchComboCounter();
- case CatchSkinComponents.CatchComboCounter:
- if (providesComboCounter)
- return new LegacyCatchComboCounter();
+ return null;
- return null;
+ case CatchSkinComponents.HitExplosion:
+ if (hasOldStyleCatcherSprite() || hasNewStyleCatcherSprite())
+ return new LegacyHitExplosion();
- case CatchSkinComponents.HitExplosion:
- if (hasOldStyleCatcherSprite() || hasNewStyleCatcherSprite())
- return new LegacyHitExplosion();
+ return null;
- return null;
-
- default:
- throw new UnsupportedSkinComponentException(lookup);
- }
+ default:
+ throw new UnsupportedSkinComponentException(lookup);
+ }
}
return base.GetDrawableComponent(lookup);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs
index 72f40d9c6f..a15a3197c5 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs
@@ -4,7 +4,6 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
-using osu.Framework.Testing;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play.HUD;
@@ -28,17 +27,5 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("reset combo", () => scoreProcessor.Combo.Value = 0);
}
-
- [Test]
- public void TestLegacyComboCounterHiddenByRulesetImplementation()
- {
- AddToggleStep("toggle legacy hidden by ruleset", visible =>
- {
- foreach (var legacyCounter in this.ChildrenOfType())
- legacyCounter.HiddenByRulesetImplementation = visible;
- });
-
- AddRepeatStep("increase combo", () => scoreProcessor.Combo.Value++, 10);
- }
}
}
diff --git a/osu.Game/Skinning/LegacyComboCounter.cs b/osu.Game/Skinning/LegacyComboCounter.cs
index cd72055fce..d77a39f607 100644
--- a/osu.Game/Skinning/LegacyComboCounter.cs
+++ b/osu.Game/Skinning/LegacyComboCounter.cs
@@ -43,18 +43,6 @@ namespace osu.Game.Skinning
private readonly Container counterContainer;
- ///
- /// Hides the combo counter internally without affecting its .
- ///
- ///
- /// This is used for rulesets that provide their own combo counter and don't want this HUD one to be visible,
- /// without potentially affecting the user's selected skin.
- ///
- public bool HiddenByRulesetImplementation
- {
- set => counterContainer.Alpha = value ? 1 : 0;
- }
-
public bool UsesFixedAnchor { get; set; }
public LegacyComboCounter()
From 78cb6b68518651a568c00c3434b9695ec76f6c53 Mon Sep 17 00:00:00 2001
From: Salman Ahmed
Date: Sat, 30 Dec 2023 05:29:18 +0300
Subject: [PATCH 0059/1274] Abstractify `LegacyComboCounter` to re-use for
mania
---
.../TestSceneCatchPlayerLegacySkin.cs | 2 +-
.../TestSceneSkinnableComboCounter.cs | 2 +-
osu.Game/Skinning/LegacyComboCounter.cs | 163 +++++-------------
.../Skinning/LegacyDefaultComboCounter.cs | 85 +++++++++
osu.Game/Skinning/LegacySkinTransformer.cs | 4 +-
osu.Game/Skinning/Skin.cs | 1 +
6 files changed, 134 insertions(+), 123 deletions(-)
create mode 100644 osu.Game/Skinning/LegacyDefaultComboCounter.cs
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs
index 99325e14c8..7812e02a63 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Tests
CreateTest();
}
- AddAssert("legacy HUD combo counter not added", () => !Player.ChildrenOfType().Any());
+ AddAssert("legacy HUD combo counter not added", () => !Player.ChildrenOfType().Any());
}
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs
index a15a3197c5..a6196a8ca0 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override Drawable CreateArgonImplementation() => new ArgonComboCounter();
protected override Drawable CreateDefaultImplementation() => new DefaultComboCounter();
- protected override Drawable CreateLegacyImplementation() => new LegacyComboCounter();
+ protected override Drawable CreateLegacyImplementation() => new LegacyDefaultComboCounter();
[Test]
public void TestComboCounterIncrementing()
diff --git a/osu.Game/Skinning/LegacyComboCounter.cs b/osu.Game/Skinning/LegacyComboCounter.cs
index d77a39f607..7003e0d3c8 100644
--- a/osu.Game/Skinning/LegacyComboCounter.cs
+++ b/osu.Game/Skinning/LegacyComboCounter.cs
@@ -5,25 +5,17 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Sprites;
using osu.Game.Rulesets.Scoring;
-using osuTK;
namespace osu.Game.Skinning
{
///
/// Uses the 'x' symbol and has a pop-out effect while rolling over.
///
- public partial class LegacyComboCounter : CompositeDrawable, ISerialisableDrawable
+ public abstract partial class LegacyComboCounter : CompositeDrawable, ISerialisableDrawable
{
public Bindable Current { get; } = new BindableInt { MinValue = 0 };
- private uint scheduledPopOutCurrentId;
-
- private const double big_pop_out_duration = 300;
-
- private const double small_pop_out_duration = 100;
-
private const double fade_out_duration = 100;
///
@@ -31,9 +23,8 @@ namespace osu.Game.Skinning
///
private const double rolling_duration = 20;
- private readonly Drawable popOutCount;
-
- private readonly Drawable displayedCountSpriteText;
+ protected readonly LegacySpriteText PopOutCountText;
+ protected readonly LegacySpriteText DisplayedCountText;
private int previousValue;
@@ -45,17 +36,10 @@ namespace osu.Game.Skinning
public bool UsesFixedAnchor { get; set; }
- public LegacyComboCounter()
+ protected LegacyComboCounter()
{
AutoSizeAxes = Axes.Both;
- Anchor = Anchor.BottomLeft;
- Origin = Anchor.BottomLeft;
-
- Margin = new MarginPadding(10);
-
- Scale = new Vector2(1.28f);
-
InternalChildren = new[]
{
counterContainer = new Container
@@ -63,18 +47,16 @@ namespace osu.Game.Skinning
AlwaysPresent = true,
Children = new[]
{
- popOutCount = new LegacySpriteText(LegacyFont.Combo)
+ PopOutCountText = new LegacySpriteText(LegacyFont.Combo)
{
Alpha = 0,
Blending = BlendingParameters.Additive,
- Anchor = Anchor.BottomLeft,
BypassAutoSizeAxes = Axes.Both,
},
- displayedCountSpriteText = new LegacySpriteText(LegacyFont.Combo)
+ DisplayedCountText = new LegacySpriteText(LegacyFont.Combo)
{
Alpha = 0,
AlwaysPresent = true,
- Anchor = Anchor.BottomLeft,
BypassAutoSizeAxes = Axes.Both,
},
}
@@ -114,26 +96,12 @@ namespace osu.Game.Skinning
{
base.LoadComplete();
- ((IHasText)displayedCountSpriteText).Text = formatCount(Current.Value);
- ((IHasText)popOutCount).Text = formatCount(Current.Value);
+ DisplayedCountText.Text = FormatCount(Current.Value);
+ PopOutCountText.Text = FormatCount(Current.Value);
Current.BindValueChanged(combo => updateCount(combo.NewValue == 0), true);
- updateLayout();
- }
-
- private void updateLayout()
- {
- const float font_height_ratio = 0.625f;
- const float vertical_offset = 9;
-
- displayedCountSpriteText.OriginPosition = new Vector2(0, font_height_ratio * displayedCountSpriteText.Height + vertical_offset);
- displayedCountSpriteText.Position = new Vector2(0, -(1 - font_height_ratio) * displayedCountSpriteText.Height + vertical_offset);
-
- popOutCount.OriginPosition = new Vector2(3, font_height_ratio * popOutCount.Height + vertical_offset); // In stable, the bigger pop out scales a bit to the left
- popOutCount.Position = new Vector2(0, -(1 - font_height_ratio) * popOutCount.Height + vertical_offset);
-
- counterContainer.Size = displayedCountSpriteText.Size;
+ counterContainer.Size = DisplayedCountText.Size;
}
private void updateCount(bool rolling)
@@ -147,127 +115,84 @@ namespace osu.Game.Skinning
if (!rolling)
{
FinishTransforms(false, nameof(DisplayedCount));
+
isRolling = false;
DisplayedCount = prev;
if (prev + 1 == Current.Value)
- onCountIncrement(prev, Current.Value);
+ OnCountIncrement();
else
- onCountChange(Current.Value);
+ OnCountChange();
}
else
{
- onCountRolling(displayedCount, Current.Value);
+ OnCountRolling();
isRolling = true;
}
}
- private void transformPopOut(int newValue)
+ ///
+ /// Raised when the counter should display the new value with transitions.
+ ///
+ protected virtual void OnCountIncrement()
{
- ((IHasText)popOutCount).Text = formatCount(newValue);
-
- popOutCount.ScaleTo(1.56f)
- .ScaleTo(1, big_pop_out_duration);
-
- popOutCount.FadeTo(0.6f)
- .FadeOut(big_pop_out_duration);
- }
-
- private void transformNoPopOut(int newValue)
- {
- ((IHasText)displayedCountSpriteText).Text = formatCount(newValue);
-
- counterContainer.Size = displayedCountSpriteText.Size;
-
- displayedCountSpriteText.ScaleTo(1);
- }
-
- private void transformPopOutSmall(int newValue)
- {
- ((IHasText)displayedCountSpriteText).Text = formatCount(newValue);
-
- counterContainer.Size = displayedCountSpriteText.Size;
-
- displayedCountSpriteText.ScaleTo(1).Then()
- .ScaleTo(1.1f, small_pop_out_duration / 2, Easing.In).Then()
- .ScaleTo(1, small_pop_out_duration / 2, Easing.Out);
- }
-
- private void scheduledPopOutSmall(uint id)
- {
- // Too late; scheduled task invalidated
- if (id != scheduledPopOutCurrentId)
- return;
+ if (DisplayedCount < Current.Value - 1)
+ DisplayedCount++;
DisplayedCount++;
}
- private void onCountIncrement(int currentValue, int newValue)
+ ///
+ /// Raised when the counter should roll to the new combo value (usually roll back to zero).
+ ///
+ protected virtual void OnCountRolling()
{
- scheduledPopOutCurrentId++;
-
- if (DisplayedCount < currentValue)
- DisplayedCount++;
-
- displayedCountSpriteText.Show();
-
- transformPopOut(newValue);
-
- uint newTaskId = scheduledPopOutCurrentId;
-
- Scheduler.AddDelayed(delegate
- {
- scheduledPopOutSmall(newTaskId);
- }, big_pop_out_duration - 140);
- }
-
- private void onCountRolling(int currentValue, int newValue)
- {
- scheduledPopOutCurrentId++;
-
// Hides displayed count if was increasing from 0 to 1 but didn't finish
- if (currentValue == 0 && newValue == 0)
- displayedCountSpriteText.FadeOut(fade_out_duration);
+ if (DisplayedCount == 0 && Current.Value == 0)
+ DisplayedCountText.FadeOut(fade_out_duration);
- transformRoll(currentValue, newValue);
+ transformRoll(DisplayedCount, Current.Value);
}
- private void onCountChange(int newValue)
+ ///
+ /// Raised when the counter should display the new combo value without any transitions.
+ ///
+ protected virtual void OnCountChange()
{
- scheduledPopOutCurrentId++;
+ if (Current.Value == 0)
+ DisplayedCountText.FadeOut();
- if (newValue == 0)
- displayedCountSpriteText.FadeOut();
-
- DisplayedCount = newValue;
+ DisplayedCount = Current.Value;
}
private void onDisplayedCountRolling(int newValue)
{
if (newValue == 0)
- displayedCountSpriteText.FadeOut(fade_out_duration);
- else
- displayedCountSpriteText.Show();
+ DisplayedCountText.FadeOut(fade_out_duration);
- transformNoPopOut(newValue);
+ DisplayedCountText.Text = FormatCount(newValue);
+ counterContainer.Size = DisplayedCountText.Size;
}
private void onDisplayedCountChange(int newValue)
{
- displayedCountSpriteText.FadeTo(newValue == 0 ? 0 : 1);
- transformNoPopOut(newValue);
+ DisplayedCountText.FadeTo(newValue == 0 ? 0 : 1);
+ DisplayedCountText.Text = FormatCount(newValue);
+
+ counterContainer.Size = DisplayedCountText.Size;
}
private void onDisplayedCountIncrement(int newValue)
{
- displayedCountSpriteText.Show();
- transformPopOutSmall(newValue);
+ DisplayedCountText.Text = FormatCount(newValue);
+
+ counterContainer.Size = DisplayedCountText.Size;
}
private void transformRoll(int currentValue, int newValue) =>
this.TransformTo(nameof(DisplayedCount), newValue, getProportionalDuration(currentValue, newValue));
- private string formatCount(int count) => $@"{count}x";
+ protected virtual string FormatCount(int count) => $@"{count}";
private double getProportionalDuration(int currentValue, int newValue)
{
diff --git a/osu.Game/Skinning/LegacyDefaultComboCounter.cs b/osu.Game/Skinning/LegacyDefaultComboCounter.cs
new file mode 100644
index 0000000000..f633358993
--- /dev/null
+++ b/osu.Game/Skinning/LegacyDefaultComboCounter.cs
@@ -0,0 +1,85 @@
+// 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.Threading;
+using osuTK;
+
+namespace osu.Game.Skinning
+{
+ ///
+ /// Uses the 'x' symbol and has a pop-out effect while rolling over.
+ ///
+ public partial class LegacyDefaultComboCounter : LegacyComboCounter
+ {
+ private const double big_pop_out_duration = 300;
+ private const double small_pop_out_duration = 100;
+
+ private ScheduledDelegate? scheduledPopOut;
+
+ public LegacyDefaultComboCounter()
+ {
+ Margin = new MarginPadding(10);
+
+ PopOutCountText.Anchor = Anchor.BottomLeft;
+ DisplayedCountText.Anchor = Anchor.BottomLeft;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ const float font_height_ratio = 0.625f;
+ const float vertical_offset = 9;
+
+ DisplayedCountText.OriginPosition = new Vector2(0, font_height_ratio * DisplayedCountText.Height + vertical_offset);
+ DisplayedCountText.Position = new Vector2(0, -(1 - font_height_ratio) * DisplayedCountText.Height + vertical_offset);
+
+ PopOutCountText.OriginPosition = new Vector2(3, font_height_ratio * PopOutCountText.Height + vertical_offset); // In stable, the bigger pop out scales a bit to the left
+ PopOutCountText.Position = new Vector2(0, -(1 - font_height_ratio) * PopOutCountText.Height + vertical_offset);
+ }
+
+ protected override void OnCountIncrement()
+ {
+ scheduledPopOut?.Cancel();
+ scheduledPopOut = null;
+
+ DisplayedCountText.Show();
+
+ PopOutCountText.Text = FormatCount(Current.Value);
+
+ PopOutCountText.ScaleTo(1.56f)
+ .ScaleTo(1, big_pop_out_duration);
+
+ PopOutCountText.FadeTo(0.6f)
+ .FadeOut(big_pop_out_duration);
+
+ this.Delay(big_pop_out_duration - 140).Schedule(() =>
+ {
+ base.OnCountIncrement();
+
+ DisplayedCountText.ScaleTo(1).Then()
+ .ScaleTo(1.1f, small_pop_out_duration / 2, Easing.In).Then()
+ .ScaleTo(1, small_pop_out_duration / 2, Easing.Out);
+ }, out scheduledPopOut);
+ }
+
+ protected override void OnCountRolling()
+ {
+ scheduledPopOut?.Cancel();
+ scheduledPopOut = null;
+
+ base.OnCountRolling();
+ }
+
+ protected override void OnCountChange()
+ {
+ scheduledPopOut?.Cancel();
+ scheduledPopOut = null;
+
+ base.OnCountChange();
+ }
+
+ protected override string FormatCount(int count) => $@"{count}x";
+ }
+}
diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs
index 3ea316c0c7..66978fc6b0 100644
--- a/osu.Game/Skinning/LegacySkinTransformer.cs
+++ b/osu.Game/Skinning/LegacySkinTransformer.cs
@@ -35,7 +35,7 @@ namespace osu.Game.Skinning
rulesetHUDComponents ??= new DefaultSkinComponentsContainer(container =>
{
- var combo = container.OfType().FirstOrDefault();
+ var combo = container.OfType().FirstOrDefault();
if (combo != null)
{
@@ -45,7 +45,7 @@ namespace osu.Game.Skinning
}
})
{
- new LegacyComboCounter()
+ new LegacyDefaultComboCounter()
};
return rulesetHUDComponents;
diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs
index 9ee69d033d..80bb340109 100644
--- a/osu.Game/Skinning/Skin.cs
+++ b/osu.Game/Skinning/Skin.cs
@@ -153,6 +153,7 @@ namespace osu.Game.Skinning
// handle namespace changes...
jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.SongProgress", @"osu.Game.Screens.Play.HUD.DefaultSongProgress");
jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.LegacyComboCounter", @"osu.Game.Skinning.LegacyComboCounter");
+ jsonContent = jsonContent.Replace(@"osu.Game.Skinning.LegacyComboCounter", @"osu.Game.Skinning.LegacyDefaultComboCounter");
var deserializedContent = JsonConvert.DeserializeObject>(jsonContent);
From ece532b837a3ebe76791e8fc5c528cba854a5ad0 Mon Sep 17 00:00:00 2001
From: Salman Ahmed
Date: Sat, 30 Dec 2023 05:18:56 +0300
Subject: [PATCH 0060/1274] Add legacy mania combo counter lookups
---
osu.Game/Skinning/LegacyManiaSkinConfiguration.cs | 1 +
osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs | 2 ++
osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 4 ++++
osu.Game/Skinning/LegacySkin.cs | 6 ++++++
4 files changed, 13 insertions(+)
diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs
index 042836984a..db1f216b6e 100644
--- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs
+++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs
@@ -39,6 +39,7 @@ namespace osu.Game.Skinning
public float HitPosition = DEFAULT_HIT_POSITION;
public float LightPosition = (480 - 413) * POSITION_SCALE_FACTOR;
+ public float ComboPosition = 111 * POSITION_SCALE_FACTOR;
public float ScorePosition = 300 * POSITION_SCALE_FACTOR;
public bool ShowJudgementLine = true;
public bool KeysUnderNotes;
diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs
index cacca0de23..fc90fc89eb 100644
--- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs
+++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs
@@ -42,6 +42,7 @@ namespace osu.Game.Skinning
LeftLineWidth,
RightLineWidth,
HitPosition,
+ ComboPosition,
ScorePosition,
LightPosition,
StagePaddingTop,
@@ -63,6 +64,7 @@ namespace osu.Game.Skinning
JudgementLineColour,
ColumnBackgroundColour,
ColumnLightColour,
+ ComboBreakColour,
MinimumColumnWidth,
LeftStageImage,
RightStageImage,
diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs
index b472afb74f..5dd8f9c52d 100644
--- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs
+++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs
@@ -94,6 +94,10 @@ namespace osu.Game.Skinning
currentConfig.LightPosition = (480 - float.Parse(pair.Value, CultureInfo.InvariantCulture)) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR;
break;
+ case "ComboPosition":
+ currentConfig.ComboPosition = (float.Parse(pair.Value, CultureInfo.InvariantCulture)) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR;
+ break;
+
case "ScorePosition":
currentConfig.ScorePosition = (float.Parse(pair.Value, CultureInfo.InvariantCulture)) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR;
break;
diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs
index b8e721165e..848a6366ed 100644
--- a/osu.Game/Skinning/LegacySkin.cs
+++ b/osu.Game/Skinning/LegacySkin.cs
@@ -152,6 +152,9 @@ namespace osu.Game.Skinning
case LegacyManiaSkinConfigurationLookups.HitPosition:
return SkinUtils.As(new Bindable(existing.HitPosition));
+ case LegacyManiaSkinConfigurationLookups.ComboPosition:
+ return SkinUtils.As(new Bindable(existing.ComboPosition));
+
case LegacyManiaSkinConfigurationLookups.ScorePosition:
return SkinUtils.As(new Bindable(existing.ScorePosition));
@@ -189,6 +192,9 @@ namespace osu.Game.Skinning
Debug.Assert(maniaLookup.ColumnIndex != null);
return SkinUtils.As(getCustomColour(existing, $"ColourLight{maniaLookup.ColumnIndex + 1}"));
+ case LegacyManiaSkinConfigurationLookups.ComboBreakColour:
+ return SkinUtils.As(getCustomColour(existing, "ColourBreak"));
+
case LegacyManiaSkinConfigurationLookups.MinimumColumnWidth:
return SkinUtils.As(new Bindable(existing.MinimumColumnWidth));
From 8be3f4f632f62d70c365c45c1f48a1747b4579f3 Mon Sep 17 00:00:00 2001
From: Salman Ahmed
Date: Sat, 30 Dec 2023 05:20:44 +0300
Subject: [PATCH 0061/1274] Add legacy mania combo counter implementation
---
.../Legacy/LegacyManiaComboCounter.cs | 94 +++++++++++++++++++
.../Legacy/ManiaLegacySkinTransformer.cs | 31 ++++++
2 files changed, 125 insertions(+)
create mode 100644 osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs
diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs
new file mode 100644
index 0000000000..fd309f6250
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs
@@ -0,0 +1,94 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.UI;
+using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Skinning;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.Skinning.Legacy
+{
+ public partial class LegacyManiaComboCounter : LegacyComboCounter, ISerialisableDrawable
+ {
+ private DrawableManiaRuleset maniaRuleset = null!;
+
+ bool ISerialisableDrawable.SupportsClosestAnchor => false;
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin, DrawableRuleset ruleset)
+ {
+ maniaRuleset = (DrawableManiaRuleset)ruleset;
+
+ DisplayedCountText.Anchor = Anchor.Centre;
+ DisplayedCountText.Origin = Anchor.Centre;
+
+ PopOutCountText.Anchor = Anchor.Centre;
+ PopOutCountText.Origin = Anchor.Centre;
+ PopOutCountText.Colour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ComboBreakColour)?.Value ?? Color4.Red;
+
+ UsesFixedAnchor = true;
+ }
+
+ private IBindable direction = null!;
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ direction = maniaRuleset.ScrollingInfo.Direction.GetBoundCopy();
+ direction.BindValueChanged(_ => updateAnchor());
+
+ // two schedules are required so that updateAnchor is executed in the next frame,
+ // which is when the combo counter receives its Y position by the default layout in LegacyManiaSkinTransformer.
+ Schedule(() => Schedule(updateAnchor));
+ }
+
+ private void updateAnchor()
+ {
+ Anchor &= ~(Anchor.y0 | Anchor.y2);
+ Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0;
+
+ // since we flip the vertical anchor when changing scroll direction,
+ // we can use the sign of the Y value as an indicator to make the combo counter displayed correctly.
+ if ((Y < 0 && direction.Value == ScrollingDirection.Down) || (Y > 0 && direction.Value == ScrollingDirection.Up))
+ Y = -Y;
+ }
+
+ protected override void OnCountIncrement()
+ {
+ base.OnCountIncrement();
+
+ PopOutCountText.Hide();
+ DisplayedCountText.ScaleTo(new Vector2(1f, 1.4f))
+ .ScaleTo(new Vector2(1f), 300, Easing.Out)
+ .FadeIn(120);
+ }
+
+ protected override void OnCountChange()
+ {
+ base.OnCountChange();
+
+ PopOutCountText.Hide();
+ DisplayedCountText.ScaleTo(1f);
+ }
+
+ protected override void OnCountRolling()
+ {
+ if (DisplayedCount > 0)
+ {
+ PopOutCountText.Text = FormatCount(DisplayedCount);
+ PopOutCountText.FadeTo(0.8f).FadeOut(200)
+ .ScaleTo(1f).ScaleTo(4f, 200);
+
+ DisplayedCountText.FadeTo(0.5f, 300);
+ }
+
+ base.OnCountRolling();
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs
index 73c521b2ed..c539c239bd 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs
@@ -5,9 +5,12 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Testing;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
@@ -78,6 +81,34 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
switch (lookup)
{
+ case SkinComponentsContainerLookup containerLookup:
+ switch (containerLookup.Target)
+ {
+ case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null:
+ Debug.Assert(containerLookup.Ruleset.ShortName == ManiaRuleset.SHORT_NAME);
+
+ var rulesetHUDComponents = Skin.GetDrawableComponent(lookup);
+
+ rulesetHUDComponents ??= new DefaultSkinComponentsContainer(container =>
+ {
+ var combo = container.ChildrenOfType().FirstOrDefault();
+
+ if (combo != null)
+ {
+ combo.Anchor = Anchor.TopCentre;
+ combo.Origin = Anchor.Centre;
+ combo.Y = this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ComboPosition)?.Value ?? 0;
+ }
+ })
+ {
+ new LegacyManiaComboCounter(),
+ };
+
+ return rulesetHUDComponents;
+ }
+
+ break;
+
case GameplaySkinComponentLookup resultComponent:
return getResult(resultComponent.Component);
From 408287e086b18187c72e0bde57571be39fd621b0 Mon Sep 17 00:00:00 2001
From: Salman Ahmed
Date: Sat, 30 Dec 2023 05:20:51 +0300
Subject: [PATCH 0062/1274] Add very basic argon mania combo counter
implementation
---
.../Skinning/Argon/ArgonManiaComboCounter.cs | 84 +++++++++++++++++++
.../Argon/ManiaArgonSkinTransformer.cs | 33 +++++++-
2 files changed, 116 insertions(+), 1 deletion(-)
create mode 100644 osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs
diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs
new file mode 100644
index 0000000000..1c8e43345a
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs
@@ -0,0 +1,84 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.UI;
+using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Screens.Play.HUD;
+using osu.Game.Skinning;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.Skinning.Argon
+{
+ public partial class ArgonManiaComboCounter : ComboCounter, ISerialisableDrawable
+ {
+ private OsuSpriteText text = null!;
+
+ protected override double RollingDuration => 500;
+ protected override Easing RollingEasing => Easing.OutQuint;
+
+ private DrawableManiaRuleset maniaRuleset = null!;
+
+ bool ISerialisableDrawable.SupportsClosestAnchor => false;
+
+ [BackgroundDependencyLoader]
+ private void load(DrawableRuleset ruleset, ScoreProcessor scoreProcessor)
+ {
+ maniaRuleset = (DrawableManiaRuleset)ruleset;
+
+ Current.BindTo(scoreProcessor.Combo);
+ Current.BindValueChanged(combo =>
+ {
+ if (combo.OldValue == 0 && combo.NewValue > 0)
+ text.FadeIn(200, Easing.OutQuint);
+ else if (combo.OldValue > 0 && combo.NewValue == 0)
+ {
+ if (combo.OldValue > 1)
+ text.FlashColour(Color4.Red, 2000, Easing.OutQuint);
+
+ text.FadeOut(200, Easing.InQuint);
+ }
+ });
+
+ UsesFixedAnchor = true;
+ }
+
+ private IBindable direction = null!;
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ text.Alpha = Current.Value > 0 ? 1 : 0;
+
+ direction = maniaRuleset.ScrollingInfo.Direction.GetBoundCopy();
+ direction.BindValueChanged(_ => updateAnchor());
+
+ // two schedules are required so that updateAnchor is executed in the next frame,
+ // which is when the combo counter receives its Y position by the default layout in ArgonManiaSkinTransformer.
+ Schedule(() => Schedule(updateAnchor));
+ }
+
+ private void updateAnchor()
+ {
+ Anchor &= ~(Anchor.y0 | Anchor.y2);
+ Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0;
+
+ // since we flip the vertical anchor when changing scroll direction,
+ // we can use the sign of the Y value as an indicator to make the combo counter displayed correctly.
+ if ((Y < 0 && direction.Value == ScrollingDirection.Down) || (Y > 0 && direction.Value == ScrollingDirection.Up))
+ Y = -Y;
+ }
+
+ protected override IHasText CreateText() => text = new OsuSpriteText
+ {
+ Font = OsuFont.Torus.With(size: 32, fixedWidth: true),
+ };
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs
index 7f6540e7b5..b0a6086f2a 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs
@@ -2,8 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Diagnostics;
+using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Scoring;
@@ -12,7 +15,7 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Skinning.Argon
{
- public class ManiaArgonSkinTransformer : SkinTransformer
+ public class ManiaArgonSkinTransformer : ArgonSkinTransformer
{
private readonly ManiaBeatmap beatmap;
@@ -26,6 +29,34 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
{
switch (lookup)
{
+ case SkinComponentsContainerLookup containerLookup:
+ switch (containerLookup.Target)
+ {
+ case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null:
+ Debug.Assert(containerLookup.Ruleset.ShortName == ManiaRuleset.SHORT_NAME);
+
+ var rulesetHUDComponents = Skin.GetDrawableComponent(lookup);
+
+ rulesetHUDComponents ??= new DefaultSkinComponentsContainer(container =>
+ {
+ var combo = container.ChildrenOfType().FirstOrDefault();
+
+ if (combo != null)
+ {
+ combo.Anchor = Anchor.TopCentre;
+ combo.Origin = Anchor.Centre;
+ combo.Y = 200;
+ }
+ })
+ {
+ new ArgonManiaComboCounter(),
+ };
+
+ return rulesetHUDComponents;
+ }
+
+ break;
+
case GameplaySkinComponentLookup resultComponent:
// This should eventually be moved to a skin setting, when supported.
if (Skin is ArgonProSkin && resultComponent.Component >= HitResult.Great)
From 95961dc98955aed19f1e5fd482ba119be868ec0b Mon Sep 17 00:00:00 2001
From: Salman Ahmed
Date: Sat, 30 Dec 2023 05:21:10 +0300
Subject: [PATCH 0063/1274] Add various visual test coverage
---
.../Skinning/TestSceneComboCounter.cs | 38 +++++++++++++++++++
.../Skinning/TestScenePlayfield.cs | 13 +++++++
.../TestSceneManiaPlayerLegacySkin.cs | 36 ++++++++++++++++++
3 files changed, 87 insertions(+)
create mode 100644 osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneComboCounter.cs
create mode 100644 osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayerLegacySkin.cs
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneComboCounter.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneComboCounter.cs
new file mode 100644
index 0000000000..c1e1cfd7af
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneComboCounter.cs
@@ -0,0 +1,38 @@
+// 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.Testing;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Mania.Skinning.Argon;
+using osu.Game.Rulesets.Mania.Skinning.Legacy;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Mania.Tests.Skinning
+{
+ public partial class TestSceneComboCounter : ManiaSkinnableTestScene
+ {
+ [Cached]
+ private ScoreProcessor scoreProcessor = new ScoreProcessor(new ManiaRuleset());
+
+ [SetUpSteps]
+ public void SetUpSteps()
+ {
+ AddStep("setup", () => SetContents(s =>
+ {
+ if (s is ArgonSkin)
+ return new ArgonManiaComboCounter();
+
+ if (s is LegacySkin)
+ return new LegacyManiaComboCounter();
+
+ return new LegacyManiaComboCounter();
+ }));
+
+ AddRepeatStep("perform hit", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Great }), 20);
+ AddStep("perform miss", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss }));
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs
index 29c47ca93a..110336d823 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs
@@ -3,15 +3,22 @@
using System.Collections.Generic;
using NUnit.Framework;
+using osu.Framework.Allocation;
using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
using osuTK;
namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
public partial class TestScenePlayfield : ManiaSkinnableTestScene
{
+ [Cached]
+ private ScoreProcessor scoreProcessor = new ScoreProcessor(new ManiaRuleset());
+
private List stageDefinitions = new List();
[Test]
@@ -29,6 +36,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
Child = new ManiaPlayfield(stageDefinitions)
});
});
+
+ AddRepeatStep("perform hit", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Perfect }), 20);
+ AddStep("perform miss", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss }));
}
[TestCase(2)]
@@ -54,6 +64,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
}
});
});
+
+ AddRepeatStep("perform hit", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Perfect }), 20);
+ AddStep("perform miss", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss }));
}
protected override IBeatmap CreateBeatmapForSkinProvider()
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayerLegacySkin.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayerLegacySkin.cs
new file mode 100644
index 0000000000..0f10f96dbf
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayerLegacySkin.cs
@@ -0,0 +1,36 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mania.Mods;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Tests.Beatmaps;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ public partial class TestSceneManiaPlayerLegacySkin : LegacySkinPlayerTestScene
+ {
+ protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
+
+ // play with a converted beatmap to allow dual stages mod to work.
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(new RulesetInfo());
+
+ protected override bool HasCustomSteps => true;
+
+ [Test]
+ public void TestSingleStage()
+ {
+ AddStep("Load single stage", LoadPlayer);
+ AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1);
+ }
+
+ [Test]
+ public void TestDualStage()
+ {
+ AddStep("Load dual stage", () => LoadPlayer(new Mod[] { new ManiaModDualStages() }));
+ AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1);
+ }
+ }
+}
From 01219fa3712f46ea55c0df150c2c3be2c0a31a48 Mon Sep 17 00:00:00 2001
From: Salman Ahmed
Date: Sat, 30 Dec 2023 05:16:41 +0300
Subject: [PATCH 0064/1274] Disable "closest" anchor in mania combo counter for
convenience
---
osu.Game/Overlays/SkinEditor/SkinEditor.cs | 3 +++
osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs | 3 ++-
osu.Game/Skinning/ISerialisableDrawable.cs | 8 ++++++++
3 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs
index f972186333..cc1e5b26ec 100644
--- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs
+++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs
@@ -441,6 +441,9 @@ namespace osu.Game.Overlays.SkinEditor
drawableComponent.Origin = Anchor.TopCentre;
drawableComponent.Anchor = Anchor.TopCentre;
drawableComponent.Y = targetContainer.DrawSize.Y / 2;
+
+ if (!component.SupportsClosestAnchor)
+ component.UsesFixedAnchor = true;
}
try
diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs
index cf6fb60636..208bd71005 100644
--- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs
+++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs
@@ -233,7 +233,8 @@ namespace osu.Game.Overlays.SkinEditor
{
var closestItem = new TernaryStateRadioMenuItem("Closest", MenuItemType.Standard, _ => applyClosestAnchors())
{
- State = { Value = GetStateFromSelection(selection, c => !c.Item.UsesFixedAnchor) }
+ State = { Value = GetStateFromSelection(selection, c => !c.Item.UsesFixedAnchor), },
+ Action = { Disabled = selection.Any(c => !c.Item.SupportsClosestAnchor) },
};
yield return new OsuMenuItem("Anchor")
diff --git a/osu.Game/Skinning/ISerialisableDrawable.cs b/osu.Game/Skinning/ISerialisableDrawable.cs
index c9dcaca6d1..898186bcc1 100644
--- a/osu.Game/Skinning/ISerialisableDrawable.cs
+++ b/osu.Game/Skinning/ISerialisableDrawable.cs
@@ -27,6 +27,14 @@ namespace osu.Game.Skinning
///
bool IsEditable => true;
+ ///
+ /// Whether this component supports the "closest" anchor.
+ ///
+ ///
+ /// This is disabled by some components that shift position automatically.
+ ///
+ bool SupportsClosestAnchor => true;
+
///
/// In the context of the skin layout editor, whether this has a permanent anchor defined.
/// If , this 's is automatically determined by proximity,
From 02f5ea200ea7b464d6d78170dc7b7beeb3e61559 Mon Sep 17 00:00:00 2001
From: Salman Ahmed
Date: Sat, 30 Dec 2023 07:41:55 +0300
Subject: [PATCH 0065/1274] Fix failing tests
---
.../Skinning/Argon/ArgonManiaComboCounter.cs | 13 +++++------
.../Legacy/LegacyManiaComboCounter.cs | 22 ++++++++++---------
osu.Game/Screens/Play/Player.cs | 4 ++++
3 files changed, 21 insertions(+), 18 deletions(-)
diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs
index 1c8e43345a..ad515528fb 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs
@@ -7,9 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
-using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Scoring;
-using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Screens.Play.HUD;
using osu.Game.Skinning;
@@ -24,15 +22,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
protected override double RollingDuration => 500;
protected override Easing RollingEasing => Easing.OutQuint;
- private DrawableManiaRuleset maniaRuleset = null!;
-
bool ISerialisableDrawable.SupportsClosestAnchor => false;
[BackgroundDependencyLoader]
- private void load(DrawableRuleset ruleset, ScoreProcessor scoreProcessor)
+ private void load(ScoreProcessor scoreProcessor)
{
- maniaRuleset = (DrawableManiaRuleset)ruleset;
-
Current.BindTo(scoreProcessor.Combo);
Current.BindValueChanged(combo =>
{
@@ -50,6 +44,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
UsesFixedAnchor = true;
}
+ [Resolved]
+ private IScrollingInfo scrollingInfo { get; set; } = null!;
+
private IBindable direction = null!;
protected override void LoadComplete()
@@ -57,7 +54,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
base.LoadComplete();
text.Alpha = Current.Value > 0 ? 1 : 0;
- direction = maniaRuleset.ScrollingInfo.Direction.GetBoundCopy();
+ direction = scrollingInfo.Direction.GetBoundCopy();
direction.BindValueChanged(_ => updateAnchor());
// two schedules are required so that updateAnchor is executed in the next frame,
diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs
index fd309f6250..00619834c8 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs
@@ -3,9 +3,8 @@
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
-using osu.Game.Rulesets.Mania.UI;
-using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
using osuTK;
@@ -15,15 +14,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
public partial class LegacyManiaComboCounter : LegacyComboCounter, ISerialisableDrawable
{
- private DrawableManiaRuleset maniaRuleset = null!;
-
bool ISerialisableDrawable.SupportsClosestAnchor => false;
[BackgroundDependencyLoader]
- private void load(ISkinSource skin, DrawableRuleset ruleset)
+ private void load(ISkinSource skin)
{
- maniaRuleset = (DrawableManiaRuleset)ruleset;
-
DisplayedCountText.Anchor = Anchor.Centre;
DisplayedCountText.Origin = Anchor.Centre;
@@ -34,13 +29,16 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
UsesFixedAnchor = true;
}
+ [Resolved]
+ private IScrollingInfo scrollingInfo { get; set; } = null!;
+
private IBindable direction = null!;
protected override void LoadComplete()
{
base.LoadComplete();
- direction = maniaRuleset.ScrollingInfo.Direction.GetBoundCopy();
+ direction = scrollingInfo.Direction.GetBoundCopy();
direction.BindValueChanged(_ => updateAnchor());
// two schedules are required so that updateAnchor is executed in the next frame,
@@ -50,8 +48,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
private void updateAnchor()
{
- Anchor &= ~(Anchor.y0 | Anchor.y2);
- Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0;
+ // if the anchor isn't a vertical center, set top or bottom anchor based on scroll direction
+ if (!Anchor.HasFlagFast(Anchor.y1))
+ {
+ Anchor &= ~(Anchor.y0 | Anchor.y2);
+ Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0;
+ }
// since we flip the vertical anchor when changing scroll direction,
// we can use the sign of the Y value as an indicator to make the combo counter displayed correctly.
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index c960ac357f..c10ef9731f 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -32,6 +32,7 @@ using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
+using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Scoring;
using osu.Game.Scoring.Legacy;
using osu.Game.Screens.Play.HUD;
@@ -224,6 +225,9 @@ namespace osu.Game.Screens.Play
DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, gameplayMods);
dependencies.CacheAs(DrawableRuleset);
+ if (DrawableRuleset is IDrawableScrollingRuleset scrollingRuleset)
+ dependencies.CacheAs(scrollingRuleset.ScrollingInfo);
+
ScoreProcessor = ruleset.CreateScoreProcessor();
ScoreProcessor.Mods.Value = gameplayMods;
ScoreProcessor.ApplyBeatmap(playableBeatmap);
From e803b0215f146e449f12a77fae1f09db4fdae30f Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sat, 30 Dec 2023 01:38:08 +0100
Subject: [PATCH 0066/1274] flip along grid axis
---
.../Edit/OsuSelectionHandler.cs | 19 ++++++++++---------
osu.Game/Utils/GeometryUtils.cs | 11 +++++++++++
2 files changed, 21 insertions(+), 9 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
index cea2adc6e2..021c735ebd 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
@@ -15,7 +16,6 @@ using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Rulesets.Osu.UI;
using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Utils;
using osuTK;
@@ -28,6 +28,9 @@ namespace osu.Game.Rulesets.Osu.Edit
[Resolved(CanBeNull = true)]
private IDistanceSnapProvider? snapProvider { get; set; }
+ [Resolved]
+ private OsuGridToolboxGroup gridToolbox { get; set; } = null!;
+
///
/// During a transform, the initial path types of a single selected slider are stored so they
/// can be maintained throughout the operation.
@@ -104,13 +107,16 @@ namespace osu.Game.Rulesets.Osu.Edit
{
var hitObjects = selectedMovableObjects;
- var flipQuad = flipOverOrigin ? new Quad(0, 0, OsuPlayfield.BASE_SIZE.X, OsuPlayfield.BASE_SIZE.Y) : GeometryUtils.GetSurroundingQuad(hitObjects);
+ var flipQuad = flipOverOrigin ? new Quad(gridToolbox.StartPositionX.Value, gridToolbox.StartPositionY.Value, 0, 0) : GeometryUtils.GetSurroundingQuad(hitObjects);
+ var flipAxis = flipOverOrigin ? new Vector2(MathF.Cos(MathHelper.DegreesToRadians(gridToolbox.GridLinesRotation.Value)), MathF.Sin(MathHelper.DegreesToRadians(gridToolbox.GridLinesRotation.Value))) : Vector2.UnitX;
+ flipAxis = direction == Direction.Vertical ? flipAxis.PerpendicularLeft : flipAxis;
+ var controlPointFlipQuad = new Quad();
bool didFlip = false;
foreach (var h in hitObjects)
{
- var flippedPosition = GeometryUtils.GetFlippedPosition(direction, flipQuad, h.Position);
+ var flippedPosition = GeometryUtils.GetFlippedPosition(flipAxis, flipQuad, h.Position);
if (!Precision.AlmostEquals(flippedPosition, h.Position))
{
@@ -123,12 +129,7 @@ namespace osu.Game.Rulesets.Osu.Edit
didFlip = true;
foreach (var cp in slider.Path.ControlPoints)
- {
- cp.Position = new Vector2(
- (direction == Direction.Horizontal ? -1 : 1) * cp.Position.X,
- (direction == Direction.Vertical ? -1 : 1) * cp.Position.Y
- );
- }
+ cp.Position = GeometryUtils.GetFlippedPosition(flipAxis, controlPointFlipQuad, cp.Position);
}
}
diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs
index e0d217dd48..aacf9b91f9 100644
--- a/osu.Game/Utils/GeometryUtils.cs
+++ b/osu.Game/Utils/GeometryUtils.cs
@@ -70,6 +70,17 @@ namespace osu.Game.Utils
return position;
}
+ ///
+ /// Given a flip axis vector, a surrounding quad for all selected objects, and a position,
+ /// will return the flipped position in screen space coordinates.
+ ///
+ public static Vector2 GetFlippedPosition(Vector2 axis, Quad quad, Vector2 position)
+ {
+ var centre = quad.Centre;
+
+ return position - 2 * Vector2.Dot(position - centre, axis) * axis;
+ }
+
///
/// Given a scale vector, a surrounding quad for all selected objects, and a position,
/// will return the scaled position in screen space coordinates.
From 078fe5a78ca5b0744d12c6f4fa8f242a0730986c Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sat, 30 Dec 2023 01:53:19 +0100
Subject: [PATCH 0067/1274] Rotate popover rotates around grid center
---
osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 2 +-
osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs | 8 +++++---
osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs | 4 +++-
3 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index 84d5adbc52..02e98d75a7 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Edit
RightToolbox.AddRange(new EditorToolboxGroup[]
{
OsuGridToolboxGroup,
- new TransformToolboxGroup { RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, },
+ new TransformToolboxGroup { RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, GridToolbox = OsuGridToolboxGroup },
FreehandlSliderToolboxGroup
}
);
diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs
index f09d6b78e6..02a8ff5872 100644
--- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs
+++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs
@@ -8,7 +8,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.UserInterfaceV2;
-using osu.Game.Rulesets.Osu.UI;
using osu.Game.Screens.Edit.Components.RadioButtons;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
@@ -19,14 +18,17 @@ namespace osu.Game.Rulesets.Osu.Edit
{
private readonly SelectionRotationHandler rotationHandler;
+ private readonly OsuGridToolboxGroup gridToolbox;
+
private readonly Bindable rotationInfo = new Bindable(new PreciseRotationInfo(0, RotationOrigin.PlayfieldCentre));
private SliderWithTextBoxInput angleInput = null!;
private EditorRadioButtonCollection rotationOrigin = null!;
- public PreciseRotationPopover(SelectionRotationHandler rotationHandler)
+ public PreciseRotationPopover(SelectionRotationHandler rotationHandler, OsuGridToolboxGroup gridToolbox)
{
this.rotationHandler = rotationHandler;
+ this.gridToolbox = gridToolbox;
AllowableAnchors = new[] { Anchor.CentreLeft, Anchor.CentreRight };
}
@@ -78,7 +80,7 @@ namespace osu.Game.Rulesets.Osu.Edit
rotationInfo.BindValueChanged(rotation =>
{
- rotationHandler.Update(rotation.NewValue.Degrees, rotation.NewValue.Origin == RotationOrigin.PlayfieldCentre ? OsuPlayfield.BASE_SIZE / 2 : null);
+ rotationHandler.Update(rotation.NewValue.Degrees, rotation.NewValue.Origin == RotationOrigin.PlayfieldCentre ? gridToolbox.StartPosition.Value : null);
});
}
diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs
index 3da9f5b69b..f8df45f545 100644
--- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs
+++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs
@@ -24,6 +24,8 @@ namespace osu.Game.Rulesets.Osu.Edit
public SelectionRotationHandler RotationHandler { get; init; } = null!;
+ public OsuGridToolboxGroup GridToolbox { get; init; } = null!;
+
public TransformToolboxGroup()
: base("transform")
{
@@ -41,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Edit
{
rotateButton = new EditorToolButton("Rotate",
() => new SpriteIcon { Icon = FontAwesome.Solid.Undo },
- () => new PreciseRotationPopover(RotationHandler)),
+ () => new PreciseRotationPopover(RotationHandler, GridToolbox)),
// TODO: scale
}
};
From 09852bc46b6b0b7a78914e19ba3414efab60afdd Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sun, 31 Dec 2023 21:23:13 +0100
Subject: [PATCH 0068/1274] fix horizontal vs vertical flips being mixed up
when rotation angle is too big
---
osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
index 021c735ebd..1cb206c2f8 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
@@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
@@ -107,9 +106,13 @@ namespace osu.Game.Rulesets.Osu.Edit
{
var hitObjects = selectedMovableObjects;
+ // If we're flipping over the origin, we take the grid origin position from the grid toolbox.
var flipQuad = flipOverOrigin ? new Quad(gridToolbox.StartPositionX.Value, gridToolbox.StartPositionY.Value, 0, 0) : GeometryUtils.GetSurroundingQuad(hitObjects);
- var flipAxis = flipOverOrigin ? new Vector2(MathF.Cos(MathHelper.DegreesToRadians(gridToolbox.GridLinesRotation.Value)), MathF.Sin(MathHelper.DegreesToRadians(gridToolbox.GridLinesRotation.Value))) : Vector2.UnitX;
+ // If we're flipping over the origin, we take the grid rotation from the grid toolbox.
+ // We want to normalize the rotation angle to -45 to 45 degrees, so horizontal vs vertical flip is not mixed up by the rotation and it stays intuitive to use.
+ var flipAxis = flipOverOrigin ? GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 405) % 90 - 45)) : Vector2.UnitX;
flipAxis = direction == Direction.Vertical ? flipAxis.PerpendicularLeft : flipAxis;
+
var controlPointFlipQuad = new Quad();
bool didFlip = false;
From 3495510c7b651efee091bad375591b2aa7875102 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sat, 30 Dec 2023 19:16:05 +0100
Subject: [PATCH 0069/1274] 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 0070/1274] 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 0071/1274] 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 0072/1274] 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 0073/1274] 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 0074/1274] improve UI
From 4e3fe5112d9d02171fb19e23bd26520f34590c87 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Mon, 1 Jan 2024 16:09:18 +0100
Subject: [PATCH 0075/1274] 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 0076/1274] 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 0077/1274] 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 0078/1274] 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 b5dbf24d2787569b4f813bf5631507492cdb0269 Mon Sep 17 00:00:00 2001
From: Sheepposu
Date: Thu, 1 Feb 2024 10:19:09 -0500
Subject: [PATCH 0079/1274] early replay analysis settings version
committing early version for others in the discussion to do their own testing with it
---
osu.Game.Rulesets.Osu/OsuRuleset.cs | 3 +
.../UI/HitMarkerContainer.cs | 134 ++++++++++++++++++
.../UI/OsuAnalysisSettings.cs | 81 +++++++++++
osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 4 +
.../PlayerSettingsOverlayStrings.cs | 15 ++
osu.Game/Rulesets/Ruleset.cs | 3 +
osu.Game/Screens/Play/Player.cs | 2 +-
.../Play/PlayerSettings/AnalysisSettings.cs | 18 +++
osu.Game/Screens/Play/ReplayPlayer.cs | 5 +
9 files changed, 264 insertions(+), 1 deletion(-)
create mode 100644 osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs
create mode 100644 osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
create mode 100644 osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index 6752712be1..358553ac59 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -38,6 +38,7 @@ using osu.Game.Rulesets.Scoring.Legacy;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
using osu.Game.Screens.Edit.Setup;
+using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Screens.Ranking.Statistics;
using osu.Game.Skinning;
@@ -356,5 +357,7 @@ namespace osu.Game.Rulesets.Osu
return adjustedDifficulty;
}
+
+ public override AnalysisSettings? CreateAnalysisSettings(DrawableRuleset drawableRuleset) => new OsuAnalysisSettings(drawableRuleset);
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs b/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs
new file mode 100644
index 0000000000..a9fc42e596
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs
@@ -0,0 +1,134 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input;
+using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.UI
+{
+ public partial class HitMarkerContainer : Container, IRequireHighFrequencyMousePosition, IKeyBindingHandler
+ {
+ private Vector2 lastMousePosition;
+
+ public Bindable HitMarkerEnabled = new BindableBool();
+ public Bindable AimMarkersEnabled = new BindableBool();
+
+ public override bool ReceivePositionalInputAt(Vector2 _) => true;
+
+ public bool OnPressed(KeyBindingPressEvent e)
+ {
+ if (HitMarkerEnabled.Value && (e.Action == OsuAction.LeftButton || e.Action == OsuAction.RightButton))
+ {
+ AddMarker(e.Action);
+ }
+
+ return false;
+ }
+
+ public void OnReleased(KeyBindingReleaseEvent e) { }
+
+ protected override bool OnMouseMove(MouseMoveEvent e)
+ {
+ lastMousePosition = e.MousePosition;
+
+ if (AimMarkersEnabled.Value)
+ {
+ AddMarker(null);
+ }
+
+ return base.OnMouseMove(e);
+ }
+
+ private void AddMarker(OsuAction? action)
+ {
+ Add(new HitMarkerDrawable(action) { Position = lastMousePosition });
+ }
+
+ private partial class HitMarkerDrawable : CompositeDrawable
+ {
+ private const double lifetime_duration = 1000;
+ private const double fade_out_time = 400;
+
+ public override bool RemoveWhenNotAlive => true;
+
+ public HitMarkerDrawable(OsuAction? action)
+ {
+ var colour = Colour4.Gray.Opacity(0.5F);
+ var length = 8;
+ var depth = float.MaxValue;
+ switch (action)
+ {
+ case OsuAction.LeftButton:
+ colour = Colour4.Orange;
+ length = 20;
+ depth = float.MinValue;
+ break;
+ case OsuAction.RightButton:
+ colour = Colour4.LightGreen;
+ length = 20;
+ depth = float.MinValue;
+ break;
+ }
+
+ this.Depth = depth;
+
+ InternalChildren = new Drawable[]
+ {
+ new Box
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(3, length),
+ Rotation = 45,
+ Colour = Colour4.Black.Opacity(0.5F)
+ },
+ new Box
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(3, length),
+ Rotation = 135,
+ Colour = Colour4.Black.Opacity(0.5F)
+ },
+ new Box
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(1, length),
+ Rotation = 45,
+ Colour = colour
+ },
+ new Box
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(1, length),
+ Rotation = 135,
+ Colour = colour
+ }
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ LifetimeStart = Time.Current;
+ LifetimeEnd = LifetimeStart + lifetime_duration;
+
+ Scheduler.AddDelayed(() =>
+ {
+ this.FadeOut(fade_out_time);
+ }, lifetime_duration - fade_out_time);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
new file mode 100644
index 0000000000..4694c1a560
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
@@ -0,0 +1,81 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Logging;
+using osu.Game.Localisation;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Rulesets.UI;
+using osu.Game.Screens.Play.PlayerSettings;
+
+namespace osu.Game.Rulesets.Osu.UI
+{
+ public partial class OsuAnalysisSettings : AnalysisSettings
+ {
+ private static readonly Logger logger = Logger.GetLogger("osu-analysis-settings");
+
+ protected new DrawableOsuRuleset drawableRuleset => (DrawableOsuRuleset)base.drawableRuleset;
+
+ private readonly PlayerCheckbox hitMarkerToggle;
+ private readonly PlayerCheckbox aimMarkerToggle;
+ private readonly PlayerCheckbox hideCursorToggle;
+ private readonly PlayerCheckbox? hiddenToggle;
+
+ public OsuAnalysisSettings(DrawableRuleset drawableRuleset)
+ : base(drawableRuleset)
+ {
+ Children = new Drawable[]
+ {
+ hitMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HitMarkers },
+ aimMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.AimMarkers },
+ hideCursorToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HideCursor }
+ };
+
+ // hidden stuff is just here for testing at the moment; to create the mod disabling functionality
+
+ foreach (var mod in drawableRuleset.Mods)
+ {
+ if (mod is OsuModHidden)
+ {
+ logger.Add("Hidden is enabled", LogLevel.Debug);
+ Add(hiddenToggle = new PlayerCheckbox { LabelText = "Disable hidden" });
+ break;
+ }
+ }
+ }
+
+ protected override void LoadComplete()
+ {
+ drawableRuleset.Playfield.MarkersContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current);
+ drawableRuleset.Playfield.MarkersContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current);
+ hideCursorToggle.Current.BindValueChanged(onCursorToggle);
+ hiddenToggle?.Current.BindValueChanged(onHiddenToggle);
+ }
+
+ private void onCursorToggle(ValueChangedEvent hide)
+ {
+ // this only hides half the cursor
+ if (hide.NewValue)
+ {
+ drawableRuleset.Playfield.Cursor.Hide();
+ } else
+ {
+ drawableRuleset.Playfield.Cursor.Show();
+ }
+ }
+
+ private void onHiddenToggle(ValueChangedEvent off)
+ {
+ if (off.NewValue)
+ {
+ logger.Add("Hidden off", LogLevel.Debug);
+ } else
+ {
+ logger.Add("Hidden on", LogLevel.Debug);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index 411a02c5af..d7e1732175 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -36,6 +36,9 @@ namespace osu.Game.Rulesets.Osu.UI
private readonly JudgementPooler judgementPooler;
public SmokeContainer Smoke { get; }
+
+ public HitMarkerContainer MarkersContainer { get; }
+
public FollowPointRenderer FollowPoints { get; }
public static readonly Vector2 BASE_SIZE = new Vector2(512, 384);
@@ -59,6 +62,7 @@ namespace osu.Game.Rulesets.Osu.UI
HitObjectContainer,
judgementAboveHitObjectLayer = new Container { RelativeSizeAxes = Axes.Both },
approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both },
+ MarkersContainer = new HitMarkerContainer { RelativeSizeAxes = Axes.Both }
};
HitPolicy = new StartTimeOrderedHitPolicy();
diff --git a/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs b/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs
index 60874da561..f829fb4ac1 100644
--- a/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs
+++ b/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs
@@ -19,6 +19,21 @@ namespace osu.Game.Localisation
///
public static LocalisableString StepForward => new TranslatableString(getKey(@"step_forward_frame"), @"Step forward one frame");
+ ///
+ /// "Hit markers"
+ ///
+ public static LocalisableString HitMarkers => new TranslatableString(getKey(@"hit_markers"), @"Hit markers");
+
+ ///
+ /// "Aim markers"
+ ///
+ public static LocalisableString AimMarkers => new TranslatableString(getKey(@"aim_markers"), @"Aim markers");
+
+ ///
+ /// "Hide cursor"
+ ///
+ public static LocalisableString HideCursor => new TranslatableString(getKey(@"hide_cursor"), @"Hide cursor");
+
///
/// "Seek backward {0} seconds"
///
diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs
index 37a35fd3ae..5fbb23f094 100644
--- a/osu.Game/Rulesets/Ruleset.cs
+++ b/osu.Game/Rulesets/Ruleset.cs
@@ -27,6 +27,7 @@ using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
using osu.Game.Screens.Edit.Setup;
+using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Screens.Ranking.Statistics;
using osu.Game.Skinning;
using osu.Game.Users;
@@ -402,5 +403,7 @@ namespace osu.Game.Rulesets
/// Can be overridden to alter the difficulty section to the editor beatmap setup screen.
///
public virtual DifficultySection? CreateEditorDifficultySection() => null;
+
+ public virtual AnalysisSettings? CreateAnalysisSettings(DrawableRuleset drawableRuleset) => null;
}
}
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index ad1f9ec897..0fa7143693 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -115,7 +115,7 @@ namespace osu.Game.Screens.Play
public GameplayState GameplayState { get; private set; }
- private Ruleset ruleset;
+ protected Ruleset ruleset;
public BreakOverlay BreakOverlay;
diff --git a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs
new file mode 100644
index 0000000000..122ca29142
--- /dev/null
+++ b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs
@@ -0,0 +1,18 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.UI;
+
+namespace osu.Game.Screens.Play.PlayerSettings
+{
+ public partial class AnalysisSettings : PlayerSettingsGroup
+ {
+ protected DrawableRuleset drawableRuleset;
+
+ public AnalysisSettings(DrawableRuleset drawableRuleset)
+ : base("Analysis Settings")
+ {
+ this.drawableRuleset = drawableRuleset;
+ }
+ }
+}
\ No newline at end of file
diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs
index 3c5b85662a..0d877785e7 100644
--- a/osu.Game/Screens/Play/ReplayPlayer.cs
+++ b/osu.Game/Screens/Play/ReplayPlayer.cs
@@ -71,6 +71,11 @@ namespace osu.Game.Screens.Play
playbackSettings.UserPlaybackRate.BindTo(master.UserPlaybackRate);
HUDOverlay.PlayerSettingsOverlay.AddAtStart(playbackSettings);
+
+ var analysisSettings = ruleset.CreateAnalysisSettings(DrawableRuleset);
+ if (analysisSettings != null) {
+ HUDOverlay.PlayerSettingsOverlay.AddAtStart(analysisSettings);
+ }
}
protected override void PrepareReplay()
From 236f029dad22722ebf73c02d40dc19b312b16642 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Thu, 1 Feb 2024 16:56:57 +0100
Subject: [PATCH 0080/1274] Remove Masking from PositionSnapGrid
This caused issues in rendering the outline of the grid because the outline was getting masked at some resolutions.
---
.../Components/LinedPositionSnapGrid.cs | 128 +++++++++++++++---
.../Compose/Components/PositionSnapGrid.cs | 2 -
2 files changed, 106 insertions(+), 24 deletions(-)
diff --git a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs
index ebdd76a4e2..8a7f6b5344 100644
--- a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs
@@ -15,18 +15,29 @@ namespace osu.Game.Screens.Edit.Compose.Components
{
protected void GenerateGridLines(Vector2 step, Vector2 drawSize)
{
+ if (Precision.AlmostEquals(step, Vector2.Zero))
+ return;
+
int index = 0;
- var currentPosition = StartPosition.Value;
// Make lines the same width independent of display resolution.
float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width;
- float lineLength = drawSize.Length * 2;
+ float rotation = MathHelper.RadiansToDegrees(MathF.Atan2(step.Y, step.X));
List generatedLines = new List();
- while (lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize) ||
- isMovingTowardsBox(currentPosition, step, drawSize))
+ while (true)
{
+ Vector2 currentPosition = StartPosition.Value + index++ * step;
+
+ if (!lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize, out var p1, out var p2))
+ {
+ if (!isMovingTowardsBox(currentPosition, step, drawSize))
+ break;
+
+ continue;
+ }
+
var gridLine = new Box
{
Colour = Colour4.White,
@@ -34,15 +45,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.None,
Width = lineWidth,
- Height = lineLength,
- Position = currentPosition,
- Rotation = MathHelper.RadiansToDegrees(MathF.Atan2(step.Y, step.X)),
+ Height = Vector2.Distance(p1, p2),
+ Position = (p1 + p2) / 2,
+ Rotation = rotation,
};
generatedLines.Add(gridLine);
-
- index += 1;
- currentPosition = StartPosition.Value + index * step;
}
if (generatedLines.Count == 0)
@@ -59,23 +67,99 @@ namespace osu.Game.Screens.Edit.Compose.Components
(currentPosition + step - box).LengthSquared < (currentPosition - box).LengthSquared;
}
- private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box)
+ ///
+ /// Determines if the line starting at and going in the direction of
+ /// definitely intersects the box on (0, 0) with the given width and height and returns the intersection points if it does.
+ ///
+ /// The start point of the line.
+ /// The direction of the line.
+ /// The width and height of the box.
+ /// The first intersection point.
+ /// The second intersection point.
+ /// Whether the line definitely intersects the box.
+ private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box, out Vector2 p1, out Vector2 p2)
{
- var p2 = lineStart + lineDir;
+ p1 = Vector2.Zero;
+ p2 = Vector2.Zero;
- double d1 = det(Vector2.Zero);
- double d2 = det(new Vector2(box.X, 0));
- double d3 = det(new Vector2(0, box.Y));
- double d4 = det(box);
+ if (Precision.AlmostEquals(lineDir.X, 0))
+ {
+ // If the line is vertical, we only need to check if the X coordinate of the line is within the box.
+ if (!Precision.DefinitelyBigger(lineStart.X, 0) || !Precision.DefinitelyBigger(box.X, lineStart.X))
+ return false;
- return definitelyDifferentSign(d1, d2) || definitelyDifferentSign(d3, d4) ||
- definitelyDifferentSign(d1, d3) || definitelyDifferentSign(d2, d4);
+ p1 = new Vector2(lineStart.X, 0);
+ p2 = new Vector2(lineStart.X, box.Y);
+ return true;
+ }
- double det(Vector2 p) => (p.X - lineStart.X) * (p2.Y - lineStart.Y) - (p.Y - lineStart.Y) * (p2.X - lineStart.X);
+ if (Precision.AlmostEquals(lineDir.Y, 0))
+ {
+ // If the line is horizontal, we only need to check if the Y coordinate of the line is within the box.
+ if (!Precision.DefinitelyBigger(lineStart.Y, 0) || !Precision.DefinitelyBigger(box.Y, lineStart.Y))
+ return false;
- bool definitelyDifferentSign(double a, double b) => !Precision.AlmostEquals(a, 0) &&
- !Precision.AlmostEquals(b, 0) &&
- Math.Sign(a) != Math.Sign(b);
+ p1 = new Vector2(0, lineStart.Y);
+ p2 = new Vector2(box.X, lineStart.Y);
+ return true;
+ }
+
+ float m = lineDir.Y / lineDir.X;
+ float mInv = lineDir.X / lineDir.Y; // Use this to improve numerical stability if X is close to zero.
+ float b = lineStart.Y - m * lineStart.X;
+
+ // Calculate intersection points with the sides of the box.
+ var p = new List(4);
+
+ if (0 <= b && b <= box.Y)
+ p.Add(new Vector2(0, b));
+ if (0 <= (box.Y - b) * mInv && (box.Y - b) * mInv <= box.X)
+ p.Add(new Vector2((box.Y - b) * mInv, box.Y));
+ if (0 <= m * box.X + b && m * box.X + b <= box.Y)
+ p.Add(new Vector2(box.X, m * box.X + b));
+ if (0 <= -b * mInv && -b * mInv <= box.X)
+ p.Add(new Vector2(-b * mInv, 0));
+
+ switch (p.Count)
+ {
+ case 4:
+ // If there are 4 intersection points, the line is a diagonal of the box.
+ if (m > 0)
+ {
+ p1 = Vector2.Zero;
+ p2 = box;
+ }
+ else
+ {
+ p1 = new Vector2(0, box.Y);
+ p2 = new Vector2(box.X, 0);
+ }
+
+ break;
+
+ case 3:
+ // If there are 3 intersection points, the line goes through a corner of the box.
+ if (p[0] == p[1])
+ {
+ p1 = p[0];
+ p2 = p[2];
+ }
+ else
+ {
+ p1 = p[0];
+ p2 = p[1];
+ }
+
+ break;
+
+ case 2:
+ p1 = p[0];
+ p2 = p[1];
+
+ break;
+ }
+
+ return !Precision.AlmostEquals(p1, p2);
}
}
}
diff --git a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs
index 36687ef73a..e576ac1e49 100644
--- a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs
@@ -21,8 +21,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected PositionSnapGrid()
{
- Masking = true;
-
StartPosition.BindValueChanged(_ => GridCache.Invalidate());
AddLayout(GridCache);
From 2918ecf46c3516039a28f58d2dd71042d505f574 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Thu, 1 Feb 2024 16:56:57 +0100
Subject: [PATCH 0081/1274] Remove Masking from PositionSnapGrid
This caused issues in rendering the outline of the grid because the outline was getting masked at some resolutions.
---
.../Components/LinedPositionSnapGrid.cs | 128 +++++++++++++++---
.../Compose/Components/PositionSnapGrid.cs | 2 -
2 files changed, 106 insertions(+), 24 deletions(-)
diff --git a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs
index ebdd76a4e2..8a7f6b5344 100644
--- a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs
@@ -15,18 +15,29 @@ namespace osu.Game.Screens.Edit.Compose.Components
{
protected void GenerateGridLines(Vector2 step, Vector2 drawSize)
{
+ if (Precision.AlmostEquals(step, Vector2.Zero))
+ return;
+
int index = 0;
- var currentPosition = StartPosition.Value;
// Make lines the same width independent of display resolution.
float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width;
- float lineLength = drawSize.Length * 2;
+ float rotation = MathHelper.RadiansToDegrees(MathF.Atan2(step.Y, step.X));
List generatedLines = new List();
- while (lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize) ||
- isMovingTowardsBox(currentPosition, step, drawSize))
+ while (true)
{
+ Vector2 currentPosition = StartPosition.Value + index++ * step;
+
+ if (!lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize, out var p1, out var p2))
+ {
+ if (!isMovingTowardsBox(currentPosition, step, drawSize))
+ break;
+
+ continue;
+ }
+
var gridLine = new Box
{
Colour = Colour4.White,
@@ -34,15 +45,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.None,
Width = lineWidth,
- Height = lineLength,
- Position = currentPosition,
- Rotation = MathHelper.RadiansToDegrees(MathF.Atan2(step.Y, step.X)),
+ Height = Vector2.Distance(p1, p2),
+ Position = (p1 + p2) / 2,
+ Rotation = rotation,
};
generatedLines.Add(gridLine);
-
- index += 1;
- currentPosition = StartPosition.Value + index * step;
}
if (generatedLines.Count == 0)
@@ -59,23 +67,99 @@ namespace osu.Game.Screens.Edit.Compose.Components
(currentPosition + step - box).LengthSquared < (currentPosition - box).LengthSquared;
}
- private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box)
+ ///
+ /// Determines if the line starting at and going in the direction of
+ /// definitely intersects the box on (0, 0) with the given width and height and returns the intersection points if it does.
+ ///
+ /// The start point of the line.
+ /// The direction of the line.
+ /// The width and height of the box.
+ /// The first intersection point.
+ /// The second intersection point.
+ /// Whether the line definitely intersects the box.
+ private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box, out Vector2 p1, out Vector2 p2)
{
- var p2 = lineStart + lineDir;
+ p1 = Vector2.Zero;
+ p2 = Vector2.Zero;
- double d1 = det(Vector2.Zero);
- double d2 = det(new Vector2(box.X, 0));
- double d3 = det(new Vector2(0, box.Y));
- double d4 = det(box);
+ if (Precision.AlmostEquals(lineDir.X, 0))
+ {
+ // If the line is vertical, we only need to check if the X coordinate of the line is within the box.
+ if (!Precision.DefinitelyBigger(lineStart.X, 0) || !Precision.DefinitelyBigger(box.X, lineStart.X))
+ return false;
- return definitelyDifferentSign(d1, d2) || definitelyDifferentSign(d3, d4) ||
- definitelyDifferentSign(d1, d3) || definitelyDifferentSign(d2, d4);
+ p1 = new Vector2(lineStart.X, 0);
+ p2 = new Vector2(lineStart.X, box.Y);
+ return true;
+ }
- double det(Vector2 p) => (p.X - lineStart.X) * (p2.Y - lineStart.Y) - (p.Y - lineStart.Y) * (p2.X - lineStart.X);
+ if (Precision.AlmostEquals(lineDir.Y, 0))
+ {
+ // If the line is horizontal, we only need to check if the Y coordinate of the line is within the box.
+ if (!Precision.DefinitelyBigger(lineStart.Y, 0) || !Precision.DefinitelyBigger(box.Y, lineStart.Y))
+ return false;
- bool definitelyDifferentSign(double a, double b) => !Precision.AlmostEquals(a, 0) &&
- !Precision.AlmostEquals(b, 0) &&
- Math.Sign(a) != Math.Sign(b);
+ p1 = new Vector2(0, lineStart.Y);
+ p2 = new Vector2(box.X, lineStart.Y);
+ return true;
+ }
+
+ float m = lineDir.Y / lineDir.X;
+ float mInv = lineDir.X / lineDir.Y; // Use this to improve numerical stability if X is close to zero.
+ float b = lineStart.Y - m * lineStart.X;
+
+ // Calculate intersection points with the sides of the box.
+ var p = new List(4);
+
+ if (0 <= b && b <= box.Y)
+ p.Add(new Vector2(0, b));
+ if (0 <= (box.Y - b) * mInv && (box.Y - b) * mInv <= box.X)
+ p.Add(new Vector2((box.Y - b) * mInv, box.Y));
+ if (0 <= m * box.X + b && m * box.X + b <= box.Y)
+ p.Add(new Vector2(box.X, m * box.X + b));
+ if (0 <= -b * mInv && -b * mInv <= box.X)
+ p.Add(new Vector2(-b * mInv, 0));
+
+ switch (p.Count)
+ {
+ case 4:
+ // If there are 4 intersection points, the line is a diagonal of the box.
+ if (m > 0)
+ {
+ p1 = Vector2.Zero;
+ p2 = box;
+ }
+ else
+ {
+ p1 = new Vector2(0, box.Y);
+ p2 = new Vector2(box.X, 0);
+ }
+
+ break;
+
+ case 3:
+ // If there are 3 intersection points, the line goes through a corner of the box.
+ if (p[0] == p[1])
+ {
+ p1 = p[0];
+ p2 = p[2];
+ }
+ else
+ {
+ p1 = p[0];
+ p2 = p[1];
+ }
+
+ break;
+
+ case 2:
+ p1 = p[0];
+ p2 = p[1];
+
+ break;
+ }
+
+ return !Precision.AlmostEquals(p1, p2);
}
}
}
diff --git a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs
index 36687ef73a..e576ac1e49 100644
--- a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs
@@ -21,8 +21,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected PositionSnapGrid()
{
- Masking = true;
-
StartPosition.BindValueChanged(_ => GridCache.Invalidate());
AddLayout(GridCache);
From b4b5cdfcf2d84527ec4d5cb9e504152be59b7e19 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Thu, 1 Feb 2024 17:07:03 +0100
Subject: [PATCH 0082/1274] Fix masking in circular snap grid
---
.../Edit/Compose/Components/CircularPositionSnapGrid.cs | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs
index 403a270359..791cb33439 100644
--- a/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs
@@ -82,7 +82,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
generatedCircles.First().Alpha = 0.8f;
- AddRangeInternal(generatedCircles);
+ AddInternal(new Container
+ {
+ Masking = true,
+ RelativeSizeAxes = Axes.Both,
+ Children = generatedCircles,
+ });
}
public override Vector2 GetSnappedPosition(Vector2 original)
From 93dbd7507fd61086a8c9d3422e6a02560254f4d4 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Thu, 1 Feb 2024 17:07:03 +0100
Subject: [PATCH 0083/1274] 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 0084/1274] 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 288eed53df439c594ffab544d3b83d9b2ea9d343 Mon Sep 17 00:00:00 2001
From: Sheepposu
Date: Sat, 10 Feb 2024 02:02:26 -0500
Subject: [PATCH 0085/1274] new features + improvements
Hit & aim markers are skinnable. Hidden can be toggled off. Aim line with skinnable color was added. The fadeout time is based on the approach rate. Cursor hide fixed.
---
osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 77 ++++++-
osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 12 +-
osu.Game.Rulesets.Osu/OsuSkinComponents.cs | 3 +
.../Skinning/Default/DefaultHitMarker.cs | 71 +++++++
.../Skinning/Default/HitMarker.cs | 33 +++
.../Legacy/OsuLegacySkinTransformer.cs | 19 ++
.../Skinning/OsuSkinColour.cs | 1 +
.../UI/HitMarkerContainer.cs | 188 ++++++++++++------
.../UI/OsuAnalysisSettings.cs | 68 +++++--
osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 +-
.../PlayerSettingsOverlayStrings.cs | 5 +
.../Rulesets/Mods/IToggleableVisibility.cs | 11 +
12 files changed, 393 insertions(+), 97 deletions(-)
create mode 100644 osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs
create mode 100644 osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs
create mode 100644 osu.Game/Rulesets/Mods/IToggleableVisibility.cs
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
index 6dc0d5d522..a69bed6fb2 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
@@ -2,9 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Transforms;
using osu.Framework.Bindables;
using osu.Framework.Localisation;
using osu.Game.Configuration;
@@ -12,13 +14,14 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Skinning;
namespace osu.Game.Rulesets.Osu.Mods
{
- public class OsuModHidden : ModHidden, IHidesApproachCircles
+ public class OsuModHidden : ModHidden, IHidesApproachCircles, IToggleableVisibility
{
[SettingSource("Only fade approach circles", "The main object body will not fade when enabled.")]
public Bindable OnlyFadeApproachCircles { get; } = new BindableBool();
@@ -28,24 +31,41 @@ namespace osu.Game.Rulesets.Osu.Mods
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModDepth) };
+ private bool toggledOff = false;
+ private IBeatmap? appliedBeatmap;
+
public const double FADE_IN_DURATION_MULTIPLIER = 0.4;
public const double FADE_OUT_DURATION_MULTIPLIER = 0.3;
protected override bool IsFirstAdjustableObject(HitObject hitObject) => !(hitObject is Spinner || hitObject is SpinnerTick);
- public override void ApplyToBeatmap(IBeatmap beatmap)
+ public override void ApplyToBeatmap(IBeatmap? beatmap = null)
{
+ if (beatmap is not null)
+ appliedBeatmap = beatmap;
+ else if (appliedBeatmap is null)
+ return;
+ else
+ beatmap = appliedBeatmap;
+
base.ApplyToBeatmap(beatmap);
foreach (var obj in beatmap.HitObjects.OfType())
applyFadeInAdjustment(obj);
+ }
- static void applyFadeInAdjustment(OsuHitObject osuObject)
- {
- osuObject.TimeFadeIn = osuObject.TimePreempt * FADE_IN_DURATION_MULTIPLIER;
- foreach (var nested in osuObject.NestedHitObjects.OfType())
- applyFadeInAdjustment(nested);
- }
+ private static void applyFadeInAdjustment(OsuHitObject osuObject)
+ {
+ osuObject.TimeFadeIn = osuObject.TimePreempt * FADE_IN_DURATION_MULTIPLIER;
+ foreach (var nested in osuObject.NestedHitObjects.OfType())
+ applyFadeInAdjustment(nested);
+ }
+
+ private static void revertFadeInAdjustment(OsuHitObject osuObject)
+ {
+ osuObject.TimeFadeIn = OsuHitObject.CalculateTimeFadeIn(osuObject.TimePreempt);
+ foreach (var nested in osuObject.NestedHitObjects.OfType())
+ revertFadeInAdjustment(nested);
}
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
@@ -60,7 +80,7 @@ namespace osu.Game.Rulesets.Osu.Mods
private void applyHiddenState(DrawableHitObject drawableObject, bool increaseVisibility)
{
- if (!(drawableObject is DrawableOsuHitObject drawableOsuObject))
+ if (!(drawableObject is DrawableOsuHitObject drawableOsuObject) || toggledOff)
return;
OsuHitObject hitObject = drawableOsuObject.HitObject;
@@ -183,8 +203,11 @@ namespace osu.Game.Rulesets.Osu.Mods
}
}
- private static void hideSpinnerApproachCircle(DrawableSpinner spinner)
+ private void hideSpinnerApproachCircle(DrawableSpinner spinner)
{
+ if (toggledOff)
+ return;
+
var approachCircle = (spinner.Body.Drawable as IHasApproachCircle)?.ApproachCircle;
if (approachCircle == null)
return;
@@ -192,5 +215,39 @@ namespace osu.Game.Rulesets.Osu.Mods
using (spinner.BeginAbsoluteSequence(spinner.HitObject.StartTime - spinner.HitObject.TimePreempt))
approachCircle.Hide();
}
+
+ public void ToggleOffVisibility(Playfield playfield)
+ {
+ if (toggledOff)
+ return;
+
+ toggledOff = true;
+
+ if (appliedBeatmap is not null)
+ foreach (var obj in appliedBeatmap.HitObjects.OfType())
+ revertFadeInAdjustment(obj);
+
+ foreach (var dho in playfield.AllHitObjects)
+ {
+ dho.RefreshStateTransforms();
+ }
+ }
+
+ public void ToggleOnVisibility(Playfield playfield)
+ {
+ if (!toggledOff)
+ return;
+
+ toggledOff = false;
+
+ if (appliedBeatmap is not null)
+ ApplyToBeatmap();
+
+ foreach (var dho in playfield.AllHitObjects)
+ {
+ dho.RefreshStateTransforms();
+ ApplyToDrawableHitObject(dho);
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
index 74631400ca..60305ed953 100644
--- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
@@ -154,17 +154,19 @@ namespace osu.Game.Rulesets.Osu.Objects
});
}
+ // Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR.
+ // This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above.
+ // Note that this doesn't exactly match the AR>10 visuals as they're classically known, but it feels good.
+ // This adjustment is necessary for AR>10, otherwise TimePreempt can become smaller leading to hitcircles not fully fading in.
+ static public double CalculateTimeFadeIn(double timePreempt) => 400 * Math.Min(1, timePreempt / PREEMPT_MIN);
+
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, PREEMPT_MAX, PREEMPT_MID, PREEMPT_MIN);
- // Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR.
- // This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above.
- // Note that this doesn't exactly match the AR>10 visuals as they're classically known, but it feels good.
- // This adjustment is necessary for AR>10, otherwise TimePreempt can become smaller leading to hitcircles not fully fading in.
- TimeFadeIn = 400 * Math.Min(1, TimePreempt / PREEMPT_MIN);
+ TimeFadeIn = CalculateTimeFadeIn(TimePreempt);
Scale = LegacyRulesetExtensions.CalculateScaleFromCircleSize(difficulty.CircleSize, true);
}
diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
index 52fdfea95f..75db18d345 100644
--- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
+++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
@@ -22,5 +22,8 @@ namespace osu.Game.Rulesets.Osu
SpinnerBody,
CursorSmoke,
ApproachCircle,
+ HitMarkerLeft,
+ HitMarkerRight,
+ AimMarker
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs
new file mode 100644
index 0000000000..f4c11e6c8a
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs
@@ -0,0 +1,71 @@
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Skinning.Default
+{
+ public partial class DefaultHitMarker : CompositeDrawable
+ {
+ public DefaultHitMarker(OsuAction? action)
+ {
+ var (colour, length, hasBorder) = getConfig(action);
+
+ if (hasBorder)
+ {
+ InternalChildren = new Drawable[]
+ {
+ new Box
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(3, length),
+ Rotation = 45,
+ Colour = Colour4.Black.Opacity(0.5F)
+ },
+ new Box
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(3, length),
+ Rotation = 135,
+ Colour = Colour4.Black.Opacity(0.5F)
+ }
+ };
+ }
+
+ AddRangeInternal(new Drawable[]
+ {
+ new Box
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(1, length),
+ Rotation = 45,
+ Colour = colour
+ },
+ new Box
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(1, length),
+ Rotation = 135,
+ Colour = colour
+ }
+ });
+ }
+
+ private (Colour4 colour, float length, bool hasBorder) getConfig(OsuAction? action)
+ {
+ switch (action)
+ {
+ case OsuAction.LeftButton:
+ return (Colour4.Orange, 20, true);
+ case OsuAction.RightButton:
+ return (Colour4.LightGreen, 20, true);
+ default:
+ return (Colour4.Gray.Opacity(0.3F), 8, false);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs
new file mode 100644
index 0000000000..d86e3d4f79
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs
@@ -0,0 +1,33 @@
+using osu.Framework.Allocation;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Osu.Skinning.Default
+{
+ public partial class HitMarker : Sprite
+ {
+ private readonly OsuAction? action;
+
+ public HitMarker(OsuAction? action = null)
+ {
+ this.action = action;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin)
+ {
+ switch (action)
+ {
+ case OsuAction.LeftButton:
+ Texture = skin.GetTexture(@"hitmarker-left");
+ break;
+ case OsuAction.RightButton:
+ Texture = skin.GetTexture(@"hitmarker-right");
+ break;
+ default:
+ Texture = skin.GetTexture(@"aimmarker");
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
index d2ebc68c52..84c055fe5e 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
@@ -5,6 +5,7 @@ using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Skinning.Default;
using osu.Game.Skinning;
using osuTK;
@@ -168,6 +169,24 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
return null;
+ case OsuSkinComponents.HitMarkerLeft:
+ if (GetTexture(@"hitmarker-left") != null)
+ return new HitMarker(OsuAction.LeftButton);
+
+ return null;
+
+ case OsuSkinComponents.HitMarkerRight:
+ if (GetTexture(@"hitmarker-right") != null)
+ return new HitMarker(OsuAction.RightButton);
+
+ return null;
+
+ case OsuSkinComponents.AimMarker:
+ if (GetTexture(@"aimmarker") != null)
+ return new HitMarker();
+
+ return null;
+
default:
throw new UnsupportedSkinComponentException(lookup);
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs
index 24f9217a5f..5c864fb6c2 100644
--- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs
@@ -10,5 +10,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
SliderBall,
SpinnerBackground,
StarBreakAdditive,
+ ReplayAimLine,
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs b/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs
index a9fc42e596..01877a9185 100644
--- a/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs
+++ b/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs
@@ -10,111 +10,128 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Skinning;
+using osu.Game.Rulesets.Osu.Skinning.Default;
+using osu.Game.Rulesets.UI;
+using osu.Game.Skinning;
using osuTK;
+using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.UI
{
public partial class HitMarkerContainer : Container, IRequireHighFrequencyMousePosition, IKeyBindingHandler
{
private Vector2 lastMousePosition;
+ private Vector2? lastlastMousePosition;
+ private double? timePreempt;
+ private double TimePreempt
+ {
+ get => timePreempt ?? default_time_preempt;
+ set => timePreempt = value;
+ }
public Bindable HitMarkerEnabled = new BindableBool();
public Bindable AimMarkersEnabled = new BindableBool();
+ public Bindable AimLinesEnabled = new BindableBool();
+
+ private const double default_time_preempt = 1000;
+
+ private readonly HitObjectContainer hitObjectContainer;
public override bool ReceivePositionalInputAt(Vector2 _) => true;
+ public HitMarkerContainer(HitObjectContainer hitObjectContainer)
+ {
+ this.hitObjectContainer = hitObjectContainer;
+ }
+
public bool OnPressed(KeyBindingPressEvent e)
{
if (HitMarkerEnabled.Value && (e.Action == OsuAction.LeftButton || e.Action == OsuAction.RightButton))
{
+ updateTimePreempt();
AddMarker(e.Action);
}
return false;
}
- public void OnReleased(KeyBindingReleaseEvent e) { }
+ public void OnReleased(KeyBindingReleaseEvent e)
+ {
+ }
protected override bool OnMouseMove(MouseMoveEvent e)
{
+ lastlastMousePosition = lastMousePosition;
lastMousePosition = e.MousePosition;
if (AimMarkersEnabled.Value)
{
+ updateTimePreempt();
AddMarker(null);
}
+ if (AimLinesEnabled.Value && lastlastMousePosition != null && lastlastMousePosition != lastMousePosition)
+ {
+ if (!AimMarkersEnabled.Value)
+ updateTimePreempt();
+ Add(new AimLineDrawable((Vector2)lastlastMousePosition, lastMousePosition, TimePreempt));
+ }
+
return base.OnMouseMove(e);
}
private void AddMarker(OsuAction? action)
{
- Add(new HitMarkerDrawable(action) { Position = lastMousePosition });
+ var component = OsuSkinComponents.AimMarker;
+ switch(action)
+ {
+ case OsuAction.LeftButton:
+ component = OsuSkinComponents.HitMarkerLeft;
+ break;
+ case OsuAction.RightButton:
+ component = OsuSkinComponents.HitMarkerRight;
+ break;
+ }
+
+ Add(new HitMarkerDrawable(action, component, TimePreempt)
+ {
+ Position = lastMousePosition,
+ Origin = Anchor.Centre,
+ Depth = action == null ? float.MaxValue : float.MinValue
+ });
}
- private partial class HitMarkerDrawable : CompositeDrawable
+ private void updateTimePreempt()
{
- private const double lifetime_duration = 1000;
- private const double fade_out_time = 400;
+ var hitObject = getHitObject();
+ if (hitObject == null)
+ return;
+
+ TimePreempt = hitObject.TimePreempt;
+ }
+
+ private OsuHitObject? getHitObject()
+ {
+ foreach (var dho in hitObjectContainer.Objects)
+ return (dho as DrawableOsuHitObject)?.HitObject;
+ return null;
+ }
+
+ private partial class HitMarkerDrawable : SkinnableDrawable
+ {
+ private readonly double lifetimeDuration;
+ private readonly double fadeOutTime;
public override bool RemoveWhenNotAlive => true;
- public HitMarkerDrawable(OsuAction? action)
+ public HitMarkerDrawable(OsuAction? action, OsuSkinComponents componenet, double timePreempt)
+ : base(new OsuSkinComponentLookup(componenet), _ => new DefaultHitMarker(action))
{
- var colour = Colour4.Gray.Opacity(0.5F);
- var length = 8;
- var depth = float.MaxValue;
- switch (action)
- {
- case OsuAction.LeftButton:
- colour = Colour4.Orange;
- length = 20;
- depth = float.MinValue;
- break;
- case OsuAction.RightButton:
- colour = Colour4.LightGreen;
- length = 20;
- depth = float.MinValue;
- break;
- }
-
- this.Depth = depth;
-
- InternalChildren = new Drawable[]
- {
- new Box
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Size = new Vector2(3, length),
- Rotation = 45,
- Colour = Colour4.Black.Opacity(0.5F)
- },
- new Box
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Size = new Vector2(3, length),
- Rotation = 135,
- Colour = Colour4.Black.Opacity(0.5F)
- },
- new Box
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Size = new Vector2(1, length),
- Rotation = 45,
- Colour = colour
- },
- new Box
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Size = new Vector2(1, length),
- Rotation = 135,
- Colour = colour
- }
- };
+ fadeOutTime = timePreempt / 2;
+ lifetimeDuration = timePreempt + fadeOutTime;
}
protected override void LoadComplete()
@@ -122,12 +139,57 @@ namespace osu.Game.Rulesets.Osu.UI
base.LoadComplete();
LifetimeStart = Time.Current;
- LifetimeEnd = LifetimeStart + lifetime_duration;
+ LifetimeEnd = LifetimeStart + lifetimeDuration;
Scheduler.AddDelayed(() =>
{
- this.FadeOut(fade_out_time);
- }, lifetime_duration - fade_out_time);
+ this.FadeOut(fadeOutTime);
+ }, lifetimeDuration - fadeOutTime);
+ }
+ }
+
+ private partial class AimLineDrawable : CompositeDrawable
+ {
+ private readonly double lifetimeDuration;
+ private readonly double fadeOutTime;
+
+ public override bool RemoveWhenNotAlive => true;
+
+ public AimLineDrawable(Vector2 fromP, Vector2 toP, double timePreempt)
+ {
+ fadeOutTime = timePreempt / 2;
+ lifetimeDuration = timePreempt + fadeOutTime;
+
+ float distance = Vector2.Distance(fromP, toP);
+ Vector2 direction = (toP - fromP);
+ InternalChild = new Box
+ {
+ Position = fromP + (direction / 2),
+ Size = new Vector2(distance, 1),
+ Rotation = (float)(Math.Atan(direction.Y / direction.X) * (180 / Math.PI)),
+ Origin = Anchor.Centre
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin)
+ {
+ var color = skin.GetConfig(OsuSkinColour.ReplayAimLine)?.Value ?? Color4.White;
+ color.A = 127;
+ InternalChild.Colour = color;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ LifetimeStart = Time.Current;
+ LifetimeEnd = LifetimeStart + lifetimeDuration;
+
+ Scheduler.AddDelayed(() =>
+ {
+ this.FadeOut(fadeOutTime);
+ }, lifetimeDuration - fadeOutTime);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
index 4694c1a560..050675d970 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
@@ -4,7 +4,9 @@
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Framework.Logging;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Input.Events;
+using osu.Game.Graphics.Containers;
using osu.Game.Localisation;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
@@ -15,14 +17,13 @@ namespace osu.Game.Rulesets.Osu.UI
{
public partial class OsuAnalysisSettings : AnalysisSettings
{
- private static readonly Logger logger = Logger.GetLogger("osu-analysis-settings");
-
protected new DrawableOsuRuleset drawableRuleset => (DrawableOsuRuleset)base.drawableRuleset;
private readonly PlayerCheckbox hitMarkerToggle;
private readonly PlayerCheckbox aimMarkerToggle;
private readonly PlayerCheckbox hideCursorToggle;
- private readonly PlayerCheckbox? hiddenToggle;
+ private readonly PlayerCheckbox aimLinesToggle;
+ private readonly FillFlowContainer modTogglesContainer;
public OsuAnalysisSettings(DrawableRuleset drawableRuleset)
: base(drawableRuleset)
@@ -31,18 +32,36 @@ namespace osu.Game.Rulesets.Osu.UI
{
hitMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HitMarkers },
aimMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.AimMarkers },
- hideCursorToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HideCursor }
+ aimLinesToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.AimLines },
+ hideCursorToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HideCursor },
+ new OsuScrollContainer(Direction.Horizontal)
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = ModSwitchSmall.DEFAULT_SIZE,
+ ScrollbarOverlapsContent = false,
+ Child = modTogglesContainer = new FillFlowContainer
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ Direction = FillDirection.Horizontal,
+ RelativeSizeAxes = Axes.Y,
+ AutoSizeAxes = Axes.X
+ }
+ }
};
-
- // hidden stuff is just here for testing at the moment; to create the mod disabling functionality
foreach (var mod in drawableRuleset.Mods)
{
- if (mod is OsuModHidden)
+ if (mod is IToggleableVisibility toggleableMod)
{
- logger.Add("Hidden is enabled", LogLevel.Debug);
- Add(hiddenToggle = new PlayerCheckbox { LabelText = "Disable hidden" });
- break;
+ var modSwitch = new SelectableModSwitchSmall(mod)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Active = { Value = true }
+ };
+ modSwitch.Active.BindValueChanged((v) => onModToggle(toggleableMod, v));
+ modTogglesContainer.Add(modSwitch);
}
}
}
@@ -51,8 +70,8 @@ namespace osu.Game.Rulesets.Osu.UI
{
drawableRuleset.Playfield.MarkersContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current);
drawableRuleset.Playfield.MarkersContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current);
+ drawableRuleset.Playfield.MarkersContainer.AimLinesEnabled.BindTo(aimLinesToggle.Current);
hideCursorToggle.Current.BindValueChanged(onCursorToggle);
- hiddenToggle?.Current.BindValueChanged(onHiddenToggle);
}
private void onCursorToggle(ValueChangedEvent hide)
@@ -60,21 +79,34 @@ namespace osu.Game.Rulesets.Osu.UI
// this only hides half the cursor
if (hide.NewValue)
{
- drawableRuleset.Playfield.Cursor.Hide();
+ drawableRuleset.Playfield.Cursor.FadeOut();
} else
{
- drawableRuleset.Playfield.Cursor.Show();
+ drawableRuleset.Playfield.Cursor.FadeIn();
}
}
- private void onHiddenToggle(ValueChangedEvent off)
+ private void onModToggle(IToggleableVisibility mod, ValueChangedEvent toggled)
{
- if (off.NewValue)
+ if (toggled.NewValue)
{
- logger.Add("Hidden off", LogLevel.Debug);
+ mod.ToggleOnVisibility(drawableRuleset.Playfield);
} else
{
- logger.Add("Hidden on", LogLevel.Debug);
+ mod.ToggleOffVisibility(drawableRuleset.Playfield);
+ }
+ }
+
+ private partial class SelectableModSwitchSmall : ModSwitchSmall
+ {
+ public SelectableModSwitchSmall(IMod mod)
+ : base(mod)
+ {}
+
+ protected override bool OnClick(ClickEvent e)
+ {
+ Active.Value = !Active.Value;
+ return true;
}
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index d7e1732175..3cb3c50ef7 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.UI
HitObjectContainer,
judgementAboveHitObjectLayer = new Container { RelativeSizeAxes = Axes.Both },
approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both },
- MarkersContainer = new HitMarkerContainer { RelativeSizeAxes = Axes.Both }
+ MarkersContainer = new HitMarkerContainer(HitObjectContainer) { RelativeSizeAxes = Axes.Both }
};
HitPolicy = new StartTimeOrderedHitPolicy();
diff --git a/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs b/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs
index f829fb4ac1..017cc9bf82 100644
--- a/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs
+++ b/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs
@@ -34,6 +34,11 @@ namespace osu.Game.Localisation
///
public static LocalisableString HideCursor => new TranslatableString(getKey(@"hide_cursor"), @"Hide cursor");
+ ///
+ /// "Aim lines"
+ ///
+ public static LocalisableString AimLines => new TranslatableString(getKey(@"aim_lines"), @"Aim lines");
+
///
/// "Seek backward {0} seconds"
///
diff --git a/osu.Game/Rulesets/Mods/IToggleableVisibility.cs b/osu.Game/Rulesets/Mods/IToggleableVisibility.cs
new file mode 100644
index 0000000000..5d90c24b9e
--- /dev/null
+++ b/osu.Game/Rulesets/Mods/IToggleableVisibility.cs
@@ -0,0 +1,11 @@
+using osu.Game.Rulesets.UI;
+
+namespace osu.Game.Rulesets.Mods
+{
+ public interface IToggleableVisibility
+ {
+ public void ToggleOffVisibility(Playfield playfield);
+
+ public void ToggleOnVisibility(Playfield playfield);
+ }
+}
\ No newline at end of file
From 1d552e7c90b8d3f7350fecaa43f5759b0154eaa7 Mon Sep 17 00:00:00 2001
From: Sheepposu
Date: Tue, 20 Feb 2024 22:28:25 -0500
Subject: [PATCH 0086/1274] move skin component logic
---
.../Default/OsuTrianglesSkinTransformer.cs | 22 +++++++++++++++++++
.../Legacy/OsuLegacySkinTransformer.cs | 22 -------------------
2 files changed, 22 insertions(+), 22 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs
index 7a4c768aa2..69ef1d9dc0 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs
@@ -29,6 +29,28 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
return new DefaultJudgementPieceSliderTickMiss(result);
}
+ break;
+ case OsuSkinComponentLookup osuComponent:
+ switch (osuComponent.Component)
+ {
+ case OsuSkinComponents.HitMarkerLeft:
+ if (GetTexture(@"hitmarker-left") != null)
+ return new HitMarker(OsuAction.LeftButton);
+
+ return null;
+
+ case OsuSkinComponents.HitMarkerRight:
+ if (GetTexture(@"hitmarker-right") != null)
+ return new HitMarker(OsuAction.RightButton);
+
+ return null;
+
+ case OsuSkinComponents.AimMarker:
+ if (GetTexture(@"aimmarker") != null)
+ return new HitMarker();
+
+ return null;
+ }
break;
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
index 84c055fe5e..a931d3ecbf 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
@@ -5,7 +5,6 @@ using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Rulesets.Osu.Skinning.Default;
using osu.Game.Skinning;
using osuTK;
@@ -168,27 +167,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
return new LegacyApproachCircle();
return null;
-
- case OsuSkinComponents.HitMarkerLeft:
- if (GetTexture(@"hitmarker-left") != null)
- return new HitMarker(OsuAction.LeftButton);
-
- return null;
-
- case OsuSkinComponents.HitMarkerRight:
- if (GetTexture(@"hitmarker-right") != null)
- return new HitMarker(OsuAction.RightButton);
-
- return null;
-
- case OsuSkinComponents.AimMarker:
- if (GetTexture(@"aimmarker") != null)
- return new HitMarker();
-
- return null;
-
- default:
- throw new UnsupportedSkinComponentException(lookup);
}
}
From 7d34542c12a8e1db707d101fc7988e32e9da74d8 Mon Sep 17 00:00:00 2001
From: TextAdventurer12
Date: Thu, 22 Feb 2024 15:14:56 +1300
Subject: [PATCH 0087/1274] 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 0088/1274] 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 35b89966bccc2455034c7aed973cf2fefbf87ca7 Mon Sep 17 00:00:00 2001
From: Sheepposu
Date: Wed, 21 Feb 2024 23:23:40 -0500
Subject: [PATCH 0089/1274] revert mod visibility toggle
---
osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 79 +++----------------
osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 12 ++-
.../UI/OsuAnalysisSettings.cs | 57 +------------
.../Rulesets/Mods/IToggleableVisibility.cs | 11 ---
4 files changed, 17 insertions(+), 142 deletions(-)
delete mode 100644 osu.Game/Rulesets/Mods/IToggleableVisibility.cs
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
index a69bed6fb2..e45daed919 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
@@ -2,11 +2,9 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Transforms;
using osu.Framework.Bindables;
using osu.Framework.Localisation;
using osu.Game.Configuration;
@@ -14,14 +12,13 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Skinning;
namespace osu.Game.Rulesets.Osu.Mods
{
- public class OsuModHidden : ModHidden, IHidesApproachCircles, IToggleableVisibility
+ public class OsuModHidden : ModHidden, IHidesApproachCircles
{
[SettingSource("Only fade approach circles", "The main object body will not fade when enabled.")]
public Bindable OnlyFadeApproachCircles { get; } = new BindableBool();
@@ -31,41 +28,24 @@ namespace osu.Game.Rulesets.Osu.Mods
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModDepth) };
- private bool toggledOff = false;
- private IBeatmap? appliedBeatmap;
-
public const double FADE_IN_DURATION_MULTIPLIER = 0.4;
public const double FADE_OUT_DURATION_MULTIPLIER = 0.3;
protected override bool IsFirstAdjustableObject(HitObject hitObject) => !(hitObject is Spinner || hitObject is SpinnerTick);
- public override void ApplyToBeatmap(IBeatmap? beatmap = null)
+ public override void ApplyToBeatmap(IBeatmap beatmap)
{
- if (beatmap is not null)
- appliedBeatmap = beatmap;
- else if (appliedBeatmap is null)
- return;
- else
- beatmap = appliedBeatmap;
-
base.ApplyToBeatmap(beatmap);
foreach (var obj in beatmap.HitObjects.OfType())
applyFadeInAdjustment(obj);
- }
- private static void applyFadeInAdjustment(OsuHitObject osuObject)
- {
- osuObject.TimeFadeIn = osuObject.TimePreempt * FADE_IN_DURATION_MULTIPLIER;
- foreach (var nested in osuObject.NestedHitObjects.OfType())
- applyFadeInAdjustment(nested);
- }
-
- private static void revertFadeInAdjustment(OsuHitObject osuObject)
- {
- osuObject.TimeFadeIn = OsuHitObject.CalculateTimeFadeIn(osuObject.TimePreempt);
- foreach (var nested in osuObject.NestedHitObjects.OfType())
- revertFadeInAdjustment(nested);
+ static void applyFadeInAdjustment(OsuHitObject osuObject)
+ {
+ osuObject.TimeFadeIn = osuObject.TimePreempt * FADE_IN_DURATION_MULTIPLIER;
+ foreach (var nested in osuObject.NestedHitObjects.OfType())
+ applyFadeInAdjustment(nested);
+ }
}
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
@@ -80,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Mods
private void applyHiddenState(DrawableHitObject drawableObject, bool increaseVisibility)
{
- if (!(drawableObject is DrawableOsuHitObject drawableOsuObject) || toggledOff)
+ if (!(drawableObject is DrawableOsuHitObject drawableOsuObject))
return;
OsuHitObject hitObject = drawableOsuObject.HitObject;
@@ -203,11 +183,8 @@ namespace osu.Game.Rulesets.Osu.Mods
}
}
- private void hideSpinnerApproachCircle(DrawableSpinner spinner)
+ private static void hideSpinnerApproachCircle(DrawableSpinner spinner)
{
- if (toggledOff)
- return;
-
var approachCircle = (spinner.Body.Drawable as IHasApproachCircle)?.ApproachCircle;
if (approachCircle == null)
return;
@@ -215,39 +192,5 @@ namespace osu.Game.Rulesets.Osu.Mods
using (spinner.BeginAbsoluteSequence(spinner.HitObject.StartTime - spinner.HitObject.TimePreempt))
approachCircle.Hide();
}
-
- public void ToggleOffVisibility(Playfield playfield)
- {
- if (toggledOff)
- return;
-
- toggledOff = true;
-
- if (appliedBeatmap is not null)
- foreach (var obj in appliedBeatmap.HitObjects.OfType())
- revertFadeInAdjustment(obj);
-
- foreach (var dho in playfield.AllHitObjects)
- {
- dho.RefreshStateTransforms();
- }
- }
-
- public void ToggleOnVisibility(Playfield playfield)
- {
- if (!toggledOff)
- return;
-
- toggledOff = false;
-
- if (appliedBeatmap is not null)
- ApplyToBeatmap();
-
- foreach (var dho in playfield.AllHitObjects)
- {
- dho.RefreshStateTransforms();
- ApplyToDrawableHitObject(dho);
- }
- }
}
-}
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
index 60305ed953..74631400ca 100644
--- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
@@ -154,19 +154,17 @@ namespace osu.Game.Rulesets.Osu.Objects
});
}
- // Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR.
- // This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above.
- // Note that this doesn't exactly match the AR>10 visuals as they're classically known, but it feels good.
- // This adjustment is necessary for AR>10, otherwise TimePreempt can become smaller leading to hitcircles not fully fading in.
- static public double CalculateTimeFadeIn(double timePreempt) => 400 * Math.Min(1, timePreempt / PREEMPT_MIN);
-
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, PREEMPT_MAX, PREEMPT_MID, PREEMPT_MIN);
- TimeFadeIn = CalculateTimeFadeIn(TimePreempt);
+ // Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR.
+ // This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above.
+ // Note that this doesn't exactly match the AR>10 visuals as they're classically known, but it feels good.
+ // This adjustment is necessary for AR>10, otherwise TimePreempt can become smaller leading to hitcircles not fully fading in.
+ TimeFadeIn = 400 * Math.Min(1, TimePreempt / PREEMPT_MIN);
Scale = LegacyRulesetExtensions.CalculateScaleFromCircleSize(difficulty.CircleSize, true);
}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
index 050675d970..319ae3e1f5 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
@@ -8,7 +8,6 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Game.Graphics.Containers;
using osu.Game.Localisation;
-using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play.PlayerSettings;
@@ -23,7 +22,6 @@ namespace osu.Game.Rulesets.Osu.UI
private readonly PlayerCheckbox aimMarkerToggle;
private readonly PlayerCheckbox hideCursorToggle;
private readonly PlayerCheckbox aimLinesToggle;
- private readonly FillFlowContainer modTogglesContainer;
public OsuAnalysisSettings(DrawableRuleset drawableRuleset)
: base(drawableRuleset)
@@ -33,37 +31,8 @@ namespace osu.Game.Rulesets.Osu.UI
hitMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HitMarkers },
aimMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.AimMarkers },
aimLinesToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.AimLines },
- hideCursorToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HideCursor },
- new OsuScrollContainer(Direction.Horizontal)
- {
- RelativeSizeAxes = Axes.X,
- Height = ModSwitchSmall.DEFAULT_SIZE,
- ScrollbarOverlapsContent = false,
- Child = modTogglesContainer = new FillFlowContainer
- {
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- Direction = FillDirection.Horizontal,
- RelativeSizeAxes = Axes.Y,
- AutoSizeAxes = Axes.X
- }
- }
+ hideCursorToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HideCursor }
};
-
- foreach (var mod in drawableRuleset.Mods)
- {
- if (mod is IToggleableVisibility toggleableMod)
- {
- var modSwitch = new SelectableModSwitchSmall(mod)
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Active = { Value = true }
- };
- modSwitch.Active.BindValueChanged((v) => onModToggle(toggleableMod, v));
- modTogglesContainer.Add(modSwitch);
- }
- }
}
protected override void LoadComplete()
@@ -85,29 +54,5 @@ namespace osu.Game.Rulesets.Osu.UI
drawableRuleset.Playfield.Cursor.FadeIn();
}
}
-
- private void onModToggle(IToggleableVisibility mod, ValueChangedEvent toggled)
- {
- if (toggled.NewValue)
- {
- mod.ToggleOnVisibility(drawableRuleset.Playfield);
- } else
- {
- mod.ToggleOffVisibility(drawableRuleset.Playfield);
- }
- }
-
- private partial class SelectableModSwitchSmall : ModSwitchSmall
- {
- public SelectableModSwitchSmall(IMod mod)
- : base(mod)
- {}
-
- protected override bool OnClick(ClickEvent e)
- {
- Active.Value = !Active.Value;
- return true;
- }
- }
}
}
\ No newline at end of file
diff --git a/osu.Game/Rulesets/Mods/IToggleableVisibility.cs b/osu.Game/Rulesets/Mods/IToggleableVisibility.cs
deleted file mode 100644
index 5d90c24b9e..0000000000
--- a/osu.Game/Rulesets/Mods/IToggleableVisibility.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using osu.Game.Rulesets.UI;
-
-namespace osu.Game.Rulesets.Mods
-{
- public interface IToggleableVisibility
- {
- public void ToggleOffVisibility(Playfield playfield);
-
- public void ToggleOnVisibility(Playfield playfield);
- }
-}
\ No newline at end of file
From f9d9df30b2104322920eb8326ad01ea946f87f06 Mon Sep 17 00:00:00 2001
From: Sheepposu
Date: Thu, 22 Feb 2024 07:31:15 -0500
Subject: [PATCH 0090/1274] add test for HitMarkerContainer
---
.../Resources/special-skin/aimmarker@2x.png | Bin 0 -> 648 bytes
.../special-skin/hitmarker-left@2x.png | Bin 0 -> 2127 bytes
.../special-skin/hitmarker-right@2x.png | Bin 0 -> 1742 bytes
.../Resources/special-skin/skin.ini | 5 +-
.../TestSceneHitMarker.cs | 134 ++++++++++++++++++
5 files changed, 138 insertions(+), 1 deletion(-)
create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/aimmarker@2x.png
create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-left@2x.png
create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-right@2x.png
create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/aimmarker@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/aimmarker@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..0b2a554193f08f7984568980956a3db8a6b39800
GIT binary patch
literal 648
zcmV;30(bq1P)EX>4Tx04R}tkv&MmKpe$iQ>7vm2a6PO$WR@`E-K4rtTK|H%@
z>74h8L#!+*#OK7523?T&k?XR{Z=6dG3p_JqWYhD+A!4!A#c~(3vY`^s5JwbMqkJLf
zvch?bvs$gQ_C5Ivg9U9R!*!aYNMH#`q#!~@9TikzAxf)8iitGs$36Tbjz2{%nOqex
zax9<*6_Voz|AXJ%n#JiUHz^ngdS7h&V+;uF0jdyW16NwdUuyz$pQJZB
zTI2{A+y*YLJDR))TI00006VoOIv00000008+zyMF)x010qNS#tmY
zE+YT{E+YYWr9XB6000McNliru=mHiD95ZRmtwR6+02y>eSad^gZEa<4bO1wgWnpw>
zWFU8GbZ8()Nlj2!fese{003Y~L_t&-(~Xd^4Zt7_1kYC1UL}8=Py}}=u;}y?!uSxX)0000EX>4Tx04R}tkv&MmKpe$iQ?)8p2Rn#3WT;NoK}8%(6^me@v=v%)FuC+YXws0R
zxHt-~1qVMCs}3&Cx;nTDg5U>;vxAeOiUKFoAxFnR+6_CX>@2HM@dakSAh-}000IiNkls_o@u5n{w3H!5jp#I_s}mSL6nvbfkWc#_vuLYo^#J%=bYz0&vPDu>`?~P
z0Nc!V3E&1C=JO;l4U7YWzyQ!=wjUbdA^U#|Xawp!3a-6gDOj^!(!4I&S*VDQsaC;d
zkpu@oS~HUIlo5?2^x2VUX1*t+Nq-yB9v@8*1=@kX09PKhkXqoU8}iuo%6H`9I*-*=
zs7QtWsq|QHIFq^%*3_$0#@g!%TuB`Tz#)<-Rfv`s2s4$%QoO2IwpJ8aHbxR!qW7trJguJ@1?U45bE-`_uOK{-b-%3G@PfU<~*e
zh?{*?U>&d#r~s;f+WnivkpmT$8`bNrWoNG~1b*F}+HfgG2XGX)$`bIrTV}Ye*3uC>
zq=ruJv2<)!2?D2qeSqx}JNi2t
zZQa#wVF)+@__FvNA8?}DEev%w+PVYHj{b&HVE||~=kU;$=+xmIQrE;mC2+(ioi{B_
z14~B(&~wBmou4?U1PDHZc=jd~eOHCfJ4>$%CvGf!H$C^B1-{^CW
zORYQQPIC*FJ;-)C)w)yeyz;EQuf9aM2(<9%X{j#}E?#-o-e-zAb+2tE-D7|^5ATq?
zKPt;x^IFGEMO1bn`Y0b*rGQ)vpN56TnkFjz%cEl&04>UXaP19WdWiP+eR%_|#
zY_xTo)~RR(2`K`4IvZ`>ZMBvT;GKKA9b5Amiycx!=6|Arl}AIhTNsKRQbSww88cm_
z&$%e?{q<>UQ8Hr~O=r?UpqZ7)iIaOQk2_>R_~GAElfeGZc(EJun2f*VoHpGKA1fE%
zW|d(4CFk^pJSI&K{I>Z$^s8!FUC@l#qnEW&;P)$7NN6_2le@kqBsYFlnE6LgSAh=E
zd{|fKvAT}?({|u}RzB|^_owZ39;*weyX}g26oT_FIwQc`1A4KK8XGV-|DrSEQ3wKM
zB2cr}D+T>i=`k~&xS0b&ZUX20Q|Yn2UMUFFh`_d*^^(>b&ZNwsC|Bt14QEm{>m?1?
zCIV$%m+ZU{)>JdH%N6_=!kX%J$xfh521*JQQMx*17-o2yD~w&8GS(IB2Y0l0XL0O!
zQb(~!i_VG2DnSO4Y0bbPVkC7`;L~hoUhdplS)RM<5J{vpdqa)piM1;R`uq0a*2A}}}-&CFL8OK}!6
zfVo0NWw8_=iDu@3K@k|}329PQv20~AjhQP{RTazDo{%Q7nAu-FPUGNcf@mb6MfPtJ
zM}Ybq5K_N?lQfP9prGz^zLl@R8mKqoXdRswQBoUra#pR{83{q(7nteTADoK?pG`A`9D8*
zL+g+7N8rOR69RB?D8@HbQD5`&4x1ww%{(zYhq8?E{44b(!oD|l*)8v0UWm1Qq+bAj
zbN5U4x*zH93LD?uTB69FHx-%Cyv%2>X8fJ)3^^T6(_aU)m*Br_F
z6_Nx3XU28k6Kk-%u-)ePB(8b=QDY16?Y-
z$_egQ2*3<*<;=LgJzv(Xzwp`DthMUSb0Oo$z$d`aQkhub=n{^MKyPm^JPw7#IhEi{TDHR{#GTxr2YT^002ovPDHLk
FV1jr`?veli
literal 0
HcmV?d00001
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-right@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-right@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..66be9a80ee2e2ae4c6dc6915d2b7ca5cf8ab589f
GIT binary patch
literal 1742
zcmV;<1~K`GP)EX>4Tx04R}tkv&MmKpe$iQ?)8p2Rn#3WT;NoK}8%(6^me@v=v%)FuC+YXws0R
zxHt-~1qVMCs}3&Cx;nTDg5U>;vxAeOiUKFoAxFnR+6_CX>@2HM@dakSAh-}000D~Nkl1L9Yb|fH_ftE7RjjO?hFdLVhHsD6;Mi3NQZON`8V+<3BQA3EPF^Lyy
zBFH5ssJYD~PVPNjt#956lhOc~_xg%spW#euk$w}6}R9gK=vhuEEC9o1$c~`Rf
z(Ry2wlf>QYeKL21@Rg4>x`?ys*smS!IAyn7e8E7rV7}_2!Kl|YJi?#x1%84Syp!In
zkY2Z>ZC|ASeH8V&R&~)}wqQQKV8CuUoJ!|IpZh27F_`+c=GVn>f!toVMTXssZ0rPq-2F
z7)4*YhF6Y6=fW|AnK={TxGQO->3NOv?ZHqu?n-9PL^x&;=hC%on8f+eXCO0UEIMoi
zC$QrUyo)<T*3ZV=R?f-~Sw)q8_7}ITNXzj(Ynz8XY#0nKO|Ffd3lJ+SQw?
zo^T_5u}|1KI1!G!Qa#~D8k*bxlSMfdpVSn!s$M%F!q0GjixAm?KUED#k}2O6e=Ub!
zzT9@~mdieLFWo14Y(4bY?{@Z~d#M9B__8N*HojnoWl8a{S^UD*#Oe5q57df^KXo6y
zFS^)9_p=4_sqNTj>tdf)v)O`qjqaxoT%0!kCVZ*Rs)f?;&EU6Nn8-Z~eiR+BtjUAq
zuj+95Y2#Stj`7sR9`G*v)Lv2)QZ=X0g)O!$}Y)kHjB1^&ZygL
zaa;~xW2Fp;tl;b7xLnk27M%fmLZ@PB*b@vZ9}Jg6605gAXe
zstYZ)p{)uZh6ZtROM+Y(5y>UDycZfo_&zMvtXfj*ar#}oX-JZ!IZ2XB>92W{iM*rM
zPniAbKjTyQRE^sNDlJm64K~qIM5Tc?-B3Fj<2yzt(TD^gFHQ4B!vebI{%dk!P-X
kiyU)`Hc@tO_2Ah*0YN2jCf7K0Z2$lO07*qoM6N<$f~iM7hyVZp
literal 0
HcmV?d00001
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini
index 9d16267d73..2952948f45 100644
--- a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini
+++ b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini
@@ -1,4 +1,7 @@
[General]
Version: latest
HitCircleOverlayAboveNumber: 0
-HitCirclePrefix: display
\ No newline at end of file
+HitCirclePrefix: display
+
+[Colours]
+ReplayAimLine: 0,0,255
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs
new file mode 100644
index 0000000000..a0748e31af
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs
@@ -0,0 +1,134 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Input.Events;
+using osu.Framework.Input.States;
+using osu.Framework.Logging;
+using osu.Framework.Testing.Input;
+using osu.Game.Rulesets.Osu.UI;
+using osu.Game.Rulesets.UI;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public partial class TestSceneHitMarker : OsuSkinnableTestScene
+ {
+ [Test]
+ public void TestHitMarkers()
+ {
+ var markerContainers = new List();
+
+ AddStep("Create hit markers", () =>
+ {
+ markerContainers.Clear();
+ SetContents(_ => {
+ markerContainers.Add(new TestHitMarkerContainer(new HitObjectContainer())
+ {
+ HitMarkerEnabled = { Value = true },
+ AimMarkersEnabled = { Value = true },
+ AimLinesEnabled = { Value = true },
+ RelativeSizeAxes = Axes.Both
+ });
+
+ return new HitMarkerInputManager
+ {
+ RelativeSizeAxes = Axes.Both,
+ Size = new Vector2(0.95f),
+ Child = markerContainers[^1],
+ };
+ });
+ });
+
+ AddUntilStep("Until skinnable expires", () =>
+ {
+ if (markerContainers.Count == 0)
+ return false;
+
+ Logger.Log("How many: " + markerContainers.Count);
+
+ foreach (var markerContainer in markerContainers)
+ {
+ if (markerContainer.Children.Count != 0)
+ return false;
+ }
+
+ return true;
+ });
+ }
+
+ private partial class HitMarkerInputManager : ManualInputManager
+ {
+ private double? startTime;
+
+ public HitMarkerInputManager()
+ {
+ UseParentInput = false;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ MoveMouseTo(ToScreenSpace(DrawSize / 2));
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ const float spin_angle = 4 * MathF.PI;
+
+ startTime ??= Time.Current;
+
+ float fraction = (float)((Time.Current - startTime) / 5_000);
+
+ float angle = fraction * spin_angle;
+ float radius = fraction * Math.Min(DrawSize.X, DrawSize.Y) / 2;
+
+ Vector2 pos = radius * new Vector2(MathF.Cos(angle), MathF.Sin(angle)) + DrawSize / 2;
+ MoveMouseTo(ToScreenSpace(pos));
+ }
+ }
+
+ private partial class TestHitMarkerContainer : HitMarkerContainer
+ {
+ private double? lastClick;
+ private double? startTime;
+ private bool finishedDrawing = false;
+ private bool leftOrRight = false;
+
+ public TestHitMarkerContainer(HitObjectContainer hitObjectContainer)
+ : base(hitObjectContainer)
+ {
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (finishedDrawing)
+ return;
+
+ startTime ??= lastClick ??= Time.Current;
+
+ if (startTime + 5_000 <= Time.Current)
+ {
+ finishedDrawing = true;
+ HitMarkerEnabled.Value = AimMarkersEnabled.Value = AimLinesEnabled.Value = false;
+ return;
+ }
+
+ if (lastClick + 400 <= Time.Current)
+ {
+ OnPressed(new KeyBindingPressEvent(new InputState(), leftOrRight ? OsuAction.LeftButton : OsuAction.RightButton));
+ leftOrRight = !leftOrRight;
+ lastClick = Time.Current;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
From af13389a9e8fc58ef549cb8e9920375c22dc99ef Mon Sep 17 00:00:00 2001
From: Sheepposu
Date: Thu, 22 Feb 2024 07:31:56 -0500
Subject: [PATCH 0091/1274] fix hit marker skinnables
---
.../Default/OsuTrianglesSkinTransformer.cs | 22 -------------------
.../Legacy/OsuLegacySkinTransformer.cs | 19 ++++++++++++++++
2 files changed, 19 insertions(+), 22 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs
index 69ef1d9dc0..7a4c768aa2 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs
@@ -29,28 +29,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
return new DefaultJudgementPieceSliderTickMiss(result);
}
- break;
- case OsuSkinComponentLookup osuComponent:
- switch (osuComponent.Component)
- {
- case OsuSkinComponents.HitMarkerLeft:
- if (GetTexture(@"hitmarker-left") != null)
- return new HitMarker(OsuAction.LeftButton);
-
- return null;
-
- case OsuSkinComponents.HitMarkerRight:
- if (GetTexture(@"hitmarker-right") != null)
- return new HitMarker(OsuAction.RightButton);
-
- return null;
-
- case OsuSkinComponents.AimMarker:
- if (GetTexture(@"aimmarker") != null)
- return new HitMarker();
-
- return null;
- }
break;
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
index a931d3ecbf..097f1732e2 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
@@ -6,6 +6,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Skinning;
+using osu.Game.Rulesets.Osu.Skinning.Default;
using osuTK;
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
@@ -167,6 +168,24 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
return new LegacyApproachCircle();
return null;
+
+ case OsuSkinComponents.HitMarkerLeft:
+ if (GetTexture(@"hitmarker-left") != null)
+ return new HitMarker(OsuAction.LeftButton);
+
+ return null;
+
+ case OsuSkinComponents.HitMarkerRight:
+ if (GetTexture(@"hitmarker-right") != null)
+ return new HitMarker(OsuAction.RightButton);
+
+ return null;
+
+ case OsuSkinComponents.AimMarker:
+ if (GetTexture(@"aimmarker") != null)
+ return new HitMarker();
+
+ return null;
}
}
From 45444b39d461ca9c497cc7ffdac8402b98e26e03 Mon Sep 17 00:00:00 2001
From: Sheepposu
Date: Thu, 22 Feb 2024 19:01:52 -0500
Subject: [PATCH 0092/1274] fix formatting issues
---
.../TestSceneHitMarker.cs | 11 ++++---
osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 2 +-
osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +-
.../Skinning/Default/DefaultHitMarker.cs | 7 ++++-
.../Skinning/Default/HitMarker.cs | 7 ++++-
.../Legacy/OsuLegacySkinTransformer.cs | 4 +--
.../UI/HitMarkerContainer.cs | 31 +++++++++----------
.../UI/OsuAnalysisSettings.cs | 22 ++++++-------
osu.Game/Rulesets/Ruleset.cs | 2 +-
osu.Game/Screens/Play/Player.cs | 2 +-
.../Play/PlayerSettings/AnalysisSettings.cs | 6 ++--
osu.Game/Screens/Play/ReplayPlayer.cs | 5 ++-
12 files changed, 53 insertions(+), 48 deletions(-)
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs
index a0748e31af..7ba681b50f 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs
@@ -25,8 +25,9 @@ namespace osu.Game.Rulesets.Osu.Tests
AddStep("Create hit markers", () =>
{
markerContainers.Clear();
- SetContents(_ => {
- markerContainers.Add(new TestHitMarkerContainer(new HitObjectContainer())
+ SetContents(_ =>
+ {
+ markerContainers.Add(new TestHitMarkerContainer(new HitObjectContainer())
{
HitMarkerEnabled = { Value = true },
AimMarkersEnabled = { Value = true },
@@ -98,8 +99,8 @@ namespace osu.Game.Rulesets.Osu.Tests
{
private double? lastClick;
private double? startTime;
- private bool finishedDrawing = false;
- private bool leftOrRight = false;
+ private bool finishedDrawing;
+ private bool leftOrRight;
public TestHitMarkerContainer(HitObjectContainer hitObjectContainer)
: base(hitObjectContainer)
@@ -131,4 +132,4 @@ namespace osu.Game.Rulesets.Osu.Tests
}
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
index e45daed919..6dc0d5d522 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
@@ -193,4 +193,4 @@ namespace osu.Game.Rulesets.Osu.Mods
approachCircle.Hide();
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index 358553ac59..224d08cbd5 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -358,6 +358,6 @@ namespace osu.Game.Rulesets.Osu
return adjustedDifficulty;
}
- public override AnalysisSettings? CreateAnalysisSettings(DrawableRuleset drawableRuleset) => new OsuAnalysisSettings(drawableRuleset);
+ public override AnalysisSettings CreateAnalysisSettings(DrawableRuleset drawableRuleset) => new OsuAnalysisSettings(drawableRuleset);
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs
index f4c11e6c8a..7dabb5182f 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs
@@ -1,3 +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.Containers;
using osu.Framework.Graphics.Shapes;
@@ -61,11 +64,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
{
case OsuAction.LeftButton:
return (Colour4.Orange, 20, true);
+
case OsuAction.RightButton:
return (Colour4.LightGreen, 20, true);
+
default:
return (Colour4.Gray.Opacity(0.3F), 8, false);
}
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs
index d86e3d4f79..28877345d0 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs
@@ -1,3 +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.Graphics.Sprites;
using osu.Game.Skinning;
@@ -21,13 +24,15 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
case OsuAction.LeftButton:
Texture = skin.GetTexture(@"hitmarker-left");
break;
+
case OsuAction.RightButton:
Texture = skin.GetTexture(@"hitmarker-right");
break;
+
default:
Texture = skin.GetTexture(@"aimmarker");
break;
}
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
index 097f1732e2..61f9eebd86 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
@@ -168,7 +168,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
return new LegacyApproachCircle();
return null;
-
+
case OsuSkinComponents.HitMarkerLeft:
if (GetTexture(@"hitmarker-left") != null)
return new HitMarker(OsuAction.LeftButton);
@@ -184,7 +184,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
case OsuSkinComponents.AimMarker:
if (GetTexture(@"aimmarker") != null)
return new HitMarker();
-
+
return null;
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs b/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs
index 01877a9185..e8916ea545 100644
--- a/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs
+++ b/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs
@@ -25,12 +25,7 @@ namespace osu.Game.Rulesets.Osu.UI
{
private Vector2 lastMousePosition;
private Vector2? lastlastMousePosition;
- private double? timePreempt;
- private double TimePreempt
- {
- get => timePreempt ?? default_time_preempt;
- set => timePreempt = value;
- }
+ private double timePreempt;
public Bindable HitMarkerEnabled = new BindableBool();
public Bindable AimMarkersEnabled = new BindableBool();
@@ -45,6 +40,7 @@ namespace osu.Game.Rulesets.Osu.UI
public HitMarkerContainer(HitObjectContainer hitObjectContainer)
{
this.hitObjectContainer = hitObjectContainer;
+ timePreempt = default_time_preempt;
}
public bool OnPressed(KeyBindingPressEvent e)
@@ -52,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.UI
if (HitMarkerEnabled.Value && (e.Action == OsuAction.LeftButton || e.Action == OsuAction.RightButton))
{
updateTimePreempt();
- AddMarker(e.Action);
+ addMarker(e.Action);
}
return false;
@@ -70,33 +66,35 @@ namespace osu.Game.Rulesets.Osu.UI
if (AimMarkersEnabled.Value)
{
updateTimePreempt();
- AddMarker(null);
+ addMarker(null);
}
if (AimLinesEnabled.Value && lastlastMousePosition != null && lastlastMousePosition != lastMousePosition)
{
if (!AimMarkersEnabled.Value)
updateTimePreempt();
- Add(new AimLineDrawable((Vector2)lastlastMousePosition, lastMousePosition, TimePreempt));
+ Add(new AimLineDrawable((Vector2)lastlastMousePosition, lastMousePosition, timePreempt));
}
return base.OnMouseMove(e);
}
- private void AddMarker(OsuAction? action)
+ private void addMarker(OsuAction? action)
{
var component = OsuSkinComponents.AimMarker;
- switch(action)
+
+ switch (action)
{
case OsuAction.LeftButton:
component = OsuSkinComponents.HitMarkerLeft;
break;
+
case OsuAction.RightButton:
component = OsuSkinComponents.HitMarkerRight;
break;
}
- Add(new HitMarkerDrawable(action, component, TimePreempt)
+ Add(new HitMarkerDrawable(action, component, timePreempt)
{
Position = lastMousePosition,
Origin = Anchor.Centre,
@@ -110,13 +108,14 @@ namespace osu.Game.Rulesets.Osu.UI
if (hitObject == null)
return;
- TimePreempt = hitObject.TimePreempt;
+ timePreempt = hitObject.TimePreempt;
}
private OsuHitObject? getHitObject()
{
foreach (var dho in hitObjectContainer.Objects)
return (dho as DrawableOsuHitObject)?.HitObject;
+
return null;
}
@@ -141,7 +140,7 @@ namespace osu.Game.Rulesets.Osu.UI
LifetimeStart = Time.Current;
LifetimeEnd = LifetimeStart + lifetimeDuration;
- Scheduler.AddDelayed(() =>
+ Scheduler.AddDelayed(() =>
{
this.FadeOut(fadeOutTime);
}, lifetimeDuration - fadeOutTime);
@@ -186,11 +185,11 @@ namespace osu.Game.Rulesets.Osu.UI
LifetimeStart = Time.Current;
LifetimeEnd = LifetimeStart + lifetimeDuration;
- Scheduler.AddDelayed(() =>
+ Scheduler.AddDelayed(() =>
{
this.FadeOut(fadeOutTime);
}, lifetimeDuration - fadeOutTime);
}
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
index 319ae3e1f5..51fd835dc5 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
@@ -1,14 +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.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Input.Events;
-using osu.Game.Graphics.Containers;
using osu.Game.Localisation;
-using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play.PlayerSettings;
@@ -16,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.UI
{
public partial class OsuAnalysisSettings : AnalysisSettings
{
- protected new DrawableOsuRuleset drawableRuleset => (DrawableOsuRuleset)base.drawableRuleset;
+ protected new DrawableOsuRuleset DrawableRuleset => (DrawableOsuRuleset)base.DrawableRuleset;
private readonly PlayerCheckbox hitMarkerToggle;
private readonly PlayerCheckbox aimMarkerToggle;
@@ -37,9 +32,9 @@ namespace osu.Game.Rulesets.Osu.UI
protected override void LoadComplete()
{
- drawableRuleset.Playfield.MarkersContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current);
- drawableRuleset.Playfield.MarkersContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current);
- drawableRuleset.Playfield.MarkersContainer.AimLinesEnabled.BindTo(aimLinesToggle.Current);
+ DrawableRuleset.Playfield.MarkersContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current);
+ DrawableRuleset.Playfield.MarkersContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current);
+ DrawableRuleset.Playfield.MarkersContainer.AimLinesEnabled.BindTo(aimLinesToggle.Current);
hideCursorToggle.Current.BindValueChanged(onCursorToggle);
}
@@ -48,11 +43,12 @@ namespace osu.Game.Rulesets.Osu.UI
// this only hides half the cursor
if (hide.NewValue)
{
- drawableRuleset.Playfield.Cursor.FadeOut();
- } else
+ DrawableRuleset.Playfield.Cursor.FadeOut();
+ }
+ else
{
- drawableRuleset.Playfield.Cursor.FadeIn();
+ DrawableRuleset.Playfield.Cursor.FadeIn();
}
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs
index 5fbb23f094..84177f26b0 100644
--- a/osu.Game/Rulesets/Ruleset.cs
+++ b/osu.Game/Rulesets/Ruleset.cs
@@ -403,7 +403,7 @@ namespace osu.Game.Rulesets
/// Can be overridden to alter the difficulty section to the editor beatmap setup screen.
///
public virtual DifficultySection? CreateEditorDifficultySection() => null;
-
+
public virtual AnalysisSettings? CreateAnalysisSettings(DrawableRuleset drawableRuleset) => null;
}
}
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 0fa7143693..ad1f9ec897 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -115,7 +115,7 @@ namespace osu.Game.Screens.Play
public GameplayState GameplayState { get; private set; }
- protected Ruleset ruleset;
+ private Ruleset ruleset;
public BreakOverlay BreakOverlay;
diff --git a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs
index 122ca29142..e30d2b2d42 100644
--- a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs
+++ b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs
@@ -7,12 +7,12 @@ namespace osu.Game.Screens.Play.PlayerSettings
{
public partial class AnalysisSettings : PlayerSettingsGroup
{
- protected DrawableRuleset drawableRuleset;
+ protected DrawableRuleset DrawableRuleset;
public AnalysisSettings(DrawableRuleset drawableRuleset)
: base("Analysis Settings")
{
- this.drawableRuleset = drawableRuleset;
+ DrawableRuleset = drawableRuleset;
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs
index 0d877785e7..65e99731dc 100644
--- a/osu.Game/Screens/Play/ReplayPlayer.cs
+++ b/osu.Game/Screens/Play/ReplayPlayer.cs
@@ -72,10 +72,9 @@ namespace osu.Game.Screens.Play
HUDOverlay.PlayerSettingsOverlay.AddAtStart(playbackSettings);
- var analysisSettings = ruleset.CreateAnalysisSettings(DrawableRuleset);
- if (analysisSettings != null) {
+ var analysisSettings = DrawableRuleset.Ruleset.CreateAnalysisSettings(DrawableRuleset);
+ if (analysisSettings != null)
HUDOverlay.PlayerSettingsOverlay.AddAtStart(analysisSettings);
- }
}
protected override void PrepareReplay()
From 2a1fa8ccacd0ece5f6a098bd4951cefcd9a862c7 Mon Sep 17 00:00:00 2001
From: Sheepposu
Date: Thu, 22 Feb 2024 22:34:38 -0500
Subject: [PATCH 0093/1274] fix OsuAnalysisSettings text not updating
---
osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
index 51fd835dc5..0411882d2a 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Localisation;
@@ -30,7 +31,8 @@ namespace osu.Game.Rulesets.Osu.UI
};
}
- protected override void LoadComplete()
+ [BackgroundDependencyLoader]
+ private void load()
{
DrawableRuleset.Playfield.MarkersContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current);
DrawableRuleset.Playfield.MarkersContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current);
From 4d669c56a2adb102031e1ea0c63538409ace3ba8 Mon Sep 17 00:00:00 2001
From: Sheepposu
Date: Fri, 23 Feb 2024 23:32:35 -0500
Subject: [PATCH 0094/1274] implement pooling
Uses pooling for all analysis objects and creates the lifetime entries from replay data when the analysis container is constructed.
---
.../UI/OsuAnalysisContainer.cs | 242 ++++++++++++++++++
.../UI/OsuAnalysisSettings.cs | 18 +-
osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 5 +-
osu.Game/Rulesets/UI/AnalysisContainer.cs | 18 ++
osu.Game/Rulesets/UI/Playfield.cs | 6 +
.../Play/PlayerSettings/AnalysisSettings.cs | 5 +-
osu.Game/Screens/Play/ReplayPlayer.cs | 3 +
7 files changed, 284 insertions(+), 13 deletions(-)
create mode 100644 osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs
create mode 100644 osu.Game/Rulesets/UI/AnalysisContainer.cs
diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs
new file mode 100644
index 0000000000..a637eddd05
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs
@@ -0,0 +1,242 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Lines;
+using osu.Framework.Graphics.Performance;
+using osu.Framework.Graphics.Pooling;
+using osu.Game.Replays;
+using osu.Game.Rulesets.Objects.Pooling;
+using osu.Game.Rulesets.Osu.Replays;
+using osu.Game.Rulesets.Osu.Skinning;
+using osu.Game.Rulesets.Osu.Skinning.Default;
+using osu.Game.Rulesets.UI;
+using osu.Game.Skinning;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Osu.UI
+{
+ public partial class OsuAnalysisContainer : AnalysisContainer
+ {
+ public Bindable HitMarkerEnabled = new BindableBool();
+ public Bindable AimMarkersEnabled = new BindableBool();
+ public Bindable AimLinesEnabled = new BindableBool();
+
+ private HitMarkersContainer hitMarkersContainer;
+ private AimMarkersContainer aimMarkersContainer;
+ private AimLinesContainer aimLinesContainer;
+
+ public OsuAnalysisContainer(Replay replay)
+ : base(replay)
+ {
+ InternalChildren = new Drawable[]
+ {
+ hitMarkersContainer = new HitMarkersContainer(),
+ aimMarkersContainer = new AimMarkersContainer() { Depth = float.MinValue },
+ aimLinesContainer = new AimLinesContainer() { Depth = float.MaxValue }
+ };
+
+ HitMarkerEnabled.ValueChanged += e => hitMarkersContainer.FadeTo(e.NewValue ? 1 : 0);
+ AimMarkersEnabled.ValueChanged += e => aimMarkersContainer.FadeTo(e.NewValue ? 1 : 0);
+ AimLinesEnabled.ValueChanged += e => aimLinesContainer.FadeTo(e.NewValue ? 1 : 0);
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin)
+ {
+ var aimLineColor = skin.GetConfig(OsuSkinColour.ReplayAimLine)?.Value ?? Color4.White;
+ aimLineColor.A = 127;
+
+ hitMarkersContainer.Hide();
+ aimMarkersContainer.Hide();
+ aimLinesContainer.Hide();
+
+ bool leftHeld = false;
+ bool rightHeld = false;
+ foreach (var frame in Replay.Frames)
+ {
+ var osuFrame = (OsuReplayFrame)frame;
+
+ aimMarkersContainer.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position));
+ aimLinesContainer.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position));
+
+ bool leftButton = osuFrame.Actions.Contains(OsuAction.LeftButton);
+ bool rightButton = osuFrame.Actions.Contains(OsuAction.RightButton);
+
+ if (leftHeld && !leftButton)
+ leftHeld = false;
+ else if (!leftHeld && leftButton)
+ {
+ hitMarkersContainer.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, true));
+ leftHeld = true;
+ }
+
+ if (rightHeld && !rightButton)
+ rightHeld = false;
+ else if (!rightHeld && rightButton)
+ {
+ hitMarkersContainer.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, false));
+ rightHeld = true;
+ }
+ }
+ }
+
+ private partial class HitMarkersContainer : PooledDrawableWithLifetimeContainer
+ {
+ private readonly HitMarkerPool leftPool;
+ private readonly HitMarkerPool rightPool;
+
+ public HitMarkersContainer()
+ {
+ AddInternal(leftPool = new HitMarkerPool(OsuSkinComponents.HitMarkerLeft, OsuAction.LeftButton, 15));
+ AddInternal(rightPool = new HitMarkerPool(OsuSkinComponents.HitMarkerRight, OsuAction.RightButton, 15));
+ }
+
+ protected override HitMarkerDrawable GetDrawable(HitMarkerEntry entry) => (entry.IsLeftMarker ? leftPool : rightPool).Get(d => d.Apply(entry));
+ }
+
+ private partial class AimMarkersContainer : PooledDrawableWithLifetimeContainer
+ {
+ private readonly HitMarkerPool pool;
+
+ public AimMarkersContainer()
+ {
+ AddInternal(pool = new HitMarkerPool(OsuSkinComponents.AimMarker, null, 80));
+ }
+
+ protected override HitMarkerDrawable GetDrawable(AimPointEntry entry) => pool.Get(d => d.Apply(entry));
+ }
+
+ private partial class AimLinesContainer : Path
+ {
+ private LifetimeEntryManager lifetimeManager = new LifetimeEntryManager();
+ private SortedSet aliveEntries = new SortedSet(new AimLinePointComparator());
+
+ public AimLinesContainer()
+ {
+ lifetimeManager.EntryBecameAlive += entryBecameAlive;
+ lifetimeManager.EntryBecameDead += entryBecameDead;
+ lifetimeManager.EntryCrossedBoundary += entryCrossedBoundary;
+
+ PathRadius = 1f;
+ Colour = new Color4(255, 255, 255, 127);
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ lifetimeManager.Update(Time.Current);
+ }
+
+ public void Add(AimPointEntry entry) => lifetimeManager.AddEntry(entry);
+
+ private void entryBecameAlive(LifetimeEntry entry)
+ {
+ aliveEntries.Add((AimPointEntry)entry);
+ updateVertices();
+ }
+
+ private void entryBecameDead(LifetimeEntry entry)
+ {
+ aliveEntries.Remove((AimPointEntry)entry);
+ updateVertices();
+ }
+
+ private void updateVertices()
+ {
+ ClearVertices();
+ foreach (var entry in aliveEntries)
+ {
+ AddVertex(entry.Position);
+ }
+ }
+
+ private void entryCrossedBoundary(LifetimeEntry entry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction)
+ {
+
+ }
+
+ private sealed class AimLinePointComparator : IComparer
+ {
+ public int Compare(AimPointEntry? x, AimPointEntry? y)
+ {
+ ArgumentNullException.ThrowIfNull(x);
+ ArgumentNullException.ThrowIfNull(y);
+
+ return x.LifetimeStart.CompareTo(y.LifetimeStart);
+ }
+ }
+ }
+
+ private partial class HitMarkerDrawable : PoolableDrawableWithLifetime
+ {
+ ///
+ /// This constructor only exists to meet the new() type constraint of .
+ ///
+ public HitMarkerDrawable()
+ {
+ }
+
+ public HitMarkerDrawable(OsuSkinComponents component, OsuAction? action)
+ {
+ Origin = Anchor.Centre;
+ InternalChild = new SkinnableDrawable(new OsuSkinComponentLookup(component), _ => new DefaultHitMarker(action));
+ }
+
+ protected override void OnApply(AimPointEntry entry)
+ {
+ Position = entry.Position;
+
+ using (BeginAbsoluteSequence(LifetimeStart))
+ Show();
+
+ using (BeginAbsoluteSequence(LifetimeEnd - 200))
+ this.FadeOut(200);
+ }
+ }
+
+ private partial class HitMarkerPool : DrawablePool
+ {
+ private readonly OsuSkinComponents component;
+ private readonly OsuAction? action;
+
+ public HitMarkerPool(OsuSkinComponents component, OsuAction? action, int initialSize)
+ : base(initialSize)
+ {
+ this.component = component;
+ this.action = action;
+ }
+
+ protected override HitMarkerDrawable CreateNewDrawable() => new HitMarkerDrawable(component, action);
+ }
+
+ private partial class AimPointEntry : LifetimeEntry
+ {
+ public Vector2 Position { get; }
+
+ public AimPointEntry(double time, Vector2 position)
+ {
+ LifetimeStart = time;
+ LifetimeEnd = time + 1_000;
+ Position = position;
+ }
+ }
+
+ private partial class HitMarkerEntry : AimPointEntry
+ {
+ public bool IsLeftMarker { get; }
+
+ public HitMarkerEntry(double lifetimeStart, Vector2 position, bool isLeftMarker)
+ : base(lifetimeStart, position)
+ {
+ IsLeftMarker = isLeftMarker;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
index 0411882d2a..fd9cb67995 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
@@ -1,10 +1,10 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Localisation;
+using osu.Game.Replays;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play.PlayerSettings;
@@ -29,14 +29,7 @@ namespace osu.Game.Rulesets.Osu.UI
aimLinesToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.AimLines },
hideCursorToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HideCursor }
};
- }
- [BackgroundDependencyLoader]
- private void load()
- {
- DrawableRuleset.Playfield.MarkersContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current);
- DrawableRuleset.Playfield.MarkersContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current);
- DrawableRuleset.Playfield.MarkersContainer.AimLinesEnabled.BindTo(aimLinesToggle.Current);
hideCursorToggle.Current.BindValueChanged(onCursorToggle);
}
@@ -52,5 +45,14 @@ namespace osu.Game.Rulesets.Osu.UI
DrawableRuleset.Playfield.Cursor.FadeIn();
}
}
+
+ public override AnalysisContainer CreateAnalysisContainer(Replay replay)
+ {
+ var analysisContainer = new OsuAnalysisContainer(replay);
+ analysisContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current);
+ analysisContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current);
+ analysisContainer.AimLinesEnabled.BindTo(aimLinesToggle.Current);
+ return analysisContainer;
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index 3cb3c50ef7..ea336e6067 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -37,8 +37,6 @@ namespace osu.Game.Rulesets.Osu.UI
public SmokeContainer Smoke { get; }
- public HitMarkerContainer MarkersContainer { get; }
-
public FollowPointRenderer FollowPoints { get; }
public static readonly Vector2 BASE_SIZE = new Vector2(512, 384);
@@ -61,8 +59,7 @@ namespace osu.Game.Rulesets.Osu.UI
judgementLayer = new JudgementContainer { RelativeSizeAxes = Axes.Both },
HitObjectContainer,
judgementAboveHitObjectLayer = new Container { RelativeSizeAxes = Axes.Both },
- approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both },
- MarkersContainer = new HitMarkerContainer(HitObjectContainer) { RelativeSizeAxes = Axes.Both }
+ approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both }
};
HitPolicy = new StartTimeOrderedHitPolicy();
diff --git a/osu.Game/Rulesets/UI/AnalysisContainer.cs b/osu.Game/Rulesets/UI/AnalysisContainer.cs
new file mode 100644
index 0000000000..62d54374e7
--- /dev/null
+++ b/osu.Game/Rulesets/UI/AnalysisContainer.cs
@@ -0,0 +1,18 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics.Containers;
+using osu.Game.Replays;
+
+namespace osu.Game.Rulesets.UI
+{
+ public partial class AnalysisContainer : Container
+ {
+ protected Replay Replay;
+
+ public AnalysisContainer(Replay replay)
+ {
+ Replay = replay;
+ }
+ }
+}
diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs
index 90a2f63faa..e116acdc19 100644
--- a/osu.Game/Rulesets/UI/Playfield.cs
+++ b/osu.Game/Rulesets/UI/Playfield.cs
@@ -291,6 +291,12 @@ namespace osu.Game.Rulesets.UI
///
protected virtual HitObjectContainer CreateHitObjectContainer() => new HitObjectContainer();
+ ///
+ /// Adds an analysis container to internal children for replays.
+ ///
+ ///
+ public virtual void AddAnalysisContainer(AnalysisContainer analysisContainer) => AddInternal(analysisContainer);
+
#region Pooling support
private readonly Dictionary pools = new Dictionary();
diff --git a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs
index e30d2b2d42..3752b2b900 100644
--- a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs
+++ b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs
@@ -1,11 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Game.Replays;
using osu.Game.Rulesets.UI;
namespace osu.Game.Screens.Play.PlayerSettings
{
- public partial class AnalysisSettings : PlayerSettingsGroup
+ public abstract partial class AnalysisSettings : PlayerSettingsGroup
{
protected DrawableRuleset DrawableRuleset;
@@ -14,5 +15,7 @@ namespace osu.Game.Screens.Play.PlayerSettings
{
DrawableRuleset = drawableRuleset;
}
+
+ public abstract AnalysisContainer CreateAnalysisContainer(Replay replay);
}
}
diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs
index 65e99731dc..ce6cb5124a 100644
--- a/osu.Game/Screens/Play/ReplayPlayer.cs
+++ b/osu.Game/Screens/Play/ReplayPlayer.cs
@@ -74,7 +74,10 @@ namespace osu.Game.Screens.Play
var analysisSettings = DrawableRuleset.Ruleset.CreateAnalysisSettings(DrawableRuleset);
if (analysisSettings != null)
+ {
HUDOverlay.PlayerSettingsOverlay.AddAtStart(analysisSettings);
+ DrawableRuleset.Playfield.AddAnalysisContainer(analysisSettings.CreateAnalysisContainer(GameplayState.Score.Replay));
+ }
}
protected override void PrepareReplay()
From c95e85368fab71e8c38e10f3dcf809f47f0986e3 Mon Sep 17 00:00:00 2001
From: Sheepposu
Date: Fri, 23 Feb 2024 23:45:58 -0500
Subject: [PATCH 0095/1274] remove old files
---
.../TestSceneHitMarker.cs | 135 ------------
.../UI/HitMarkerContainer.cs | 195 ------------------
2 files changed, 330 deletions(-)
delete mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs
delete mode 100644 osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs
deleted file mode 100644
index 7ba681b50f..0000000000
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs
+++ /dev/null
@@ -1,135 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System;
-using System.Collections.Generic;
-using NUnit.Framework;
-using osu.Framework.Graphics;
-using osu.Framework.Input.Events;
-using osu.Framework.Input.States;
-using osu.Framework.Logging;
-using osu.Framework.Testing.Input;
-using osu.Game.Rulesets.Osu.UI;
-using osu.Game.Rulesets.UI;
-using osuTK;
-
-namespace osu.Game.Rulesets.Osu.Tests
-{
- public partial class TestSceneHitMarker : OsuSkinnableTestScene
- {
- [Test]
- public void TestHitMarkers()
- {
- var markerContainers = new List();
-
- AddStep("Create hit markers", () =>
- {
- markerContainers.Clear();
- SetContents(_ =>
- {
- markerContainers.Add(new TestHitMarkerContainer(new HitObjectContainer())
- {
- HitMarkerEnabled = { Value = true },
- AimMarkersEnabled = { Value = true },
- AimLinesEnabled = { Value = true },
- RelativeSizeAxes = Axes.Both
- });
-
- return new HitMarkerInputManager
- {
- RelativeSizeAxes = Axes.Both,
- Size = new Vector2(0.95f),
- Child = markerContainers[^1],
- };
- });
- });
-
- AddUntilStep("Until skinnable expires", () =>
- {
- if (markerContainers.Count == 0)
- return false;
-
- Logger.Log("How many: " + markerContainers.Count);
-
- foreach (var markerContainer in markerContainers)
- {
- if (markerContainer.Children.Count != 0)
- return false;
- }
-
- return true;
- });
- }
-
- private partial class HitMarkerInputManager : ManualInputManager
- {
- private double? startTime;
-
- public HitMarkerInputManager()
- {
- UseParentInput = false;
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- MoveMouseTo(ToScreenSpace(DrawSize / 2));
- }
-
- protected override void Update()
- {
- base.Update();
-
- const float spin_angle = 4 * MathF.PI;
-
- startTime ??= Time.Current;
-
- float fraction = (float)((Time.Current - startTime) / 5_000);
-
- float angle = fraction * spin_angle;
- float radius = fraction * Math.Min(DrawSize.X, DrawSize.Y) / 2;
-
- Vector2 pos = radius * new Vector2(MathF.Cos(angle), MathF.Sin(angle)) + DrawSize / 2;
- MoveMouseTo(ToScreenSpace(pos));
- }
- }
-
- private partial class TestHitMarkerContainer : HitMarkerContainer
- {
- private double? lastClick;
- private double? startTime;
- private bool finishedDrawing;
- private bool leftOrRight;
-
- public TestHitMarkerContainer(HitObjectContainer hitObjectContainer)
- : base(hitObjectContainer)
- {
- }
-
- protected override void Update()
- {
- base.Update();
-
- if (finishedDrawing)
- return;
-
- startTime ??= lastClick ??= Time.Current;
-
- if (startTime + 5_000 <= Time.Current)
- {
- finishedDrawing = true;
- HitMarkerEnabled.Value = AimMarkersEnabled.Value = AimLinesEnabled.Value = false;
- return;
- }
-
- if (lastClick + 400 <= Time.Current)
- {
- OnPressed(new KeyBindingPressEvent(new InputState(), leftOrRight ? OsuAction.LeftButton : OsuAction.RightButton));
- leftOrRight = !leftOrRight;
- lastClick = Time.Current;
- }
- }
- }
- }
-}
diff --git a/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs b/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs
deleted file mode 100644
index e8916ea545..0000000000
--- a/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs
+++ /dev/null
@@ -1,195 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System;
-using osu.Framework.Allocation;
-using osu.Framework.Bindables;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Input;
-using osu.Framework.Input.Bindings;
-using osu.Framework.Input.Events;
-using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Rulesets.Osu.Objects.Drawables;
-using osu.Game.Rulesets.Osu.Skinning;
-using osu.Game.Rulesets.Osu.Skinning.Default;
-using osu.Game.Rulesets.UI;
-using osu.Game.Skinning;
-using osuTK;
-using osuTK.Graphics;
-
-namespace osu.Game.Rulesets.Osu.UI
-{
- public partial class HitMarkerContainer : Container, IRequireHighFrequencyMousePosition, IKeyBindingHandler
- {
- private Vector2 lastMousePosition;
- private Vector2? lastlastMousePosition;
- private double timePreempt;
-
- public Bindable HitMarkerEnabled = new BindableBool();
- public Bindable AimMarkersEnabled = new BindableBool();
- public Bindable AimLinesEnabled = new BindableBool();
-
- private const double default_time_preempt = 1000;
-
- private readonly HitObjectContainer hitObjectContainer;
-
- public override bool ReceivePositionalInputAt(Vector2 _) => true;
-
- public HitMarkerContainer(HitObjectContainer hitObjectContainer)
- {
- this.hitObjectContainer = hitObjectContainer;
- timePreempt = default_time_preempt;
- }
-
- public bool OnPressed(KeyBindingPressEvent e)
- {
- if (HitMarkerEnabled.Value && (e.Action == OsuAction.LeftButton || e.Action == OsuAction.RightButton))
- {
- updateTimePreempt();
- addMarker(e.Action);
- }
-
- return false;
- }
-
- public void OnReleased(KeyBindingReleaseEvent e)
- {
- }
-
- protected override bool OnMouseMove(MouseMoveEvent e)
- {
- lastlastMousePosition = lastMousePosition;
- lastMousePosition = e.MousePosition;
-
- if (AimMarkersEnabled.Value)
- {
- updateTimePreempt();
- addMarker(null);
- }
-
- if (AimLinesEnabled.Value && lastlastMousePosition != null && lastlastMousePosition != lastMousePosition)
- {
- if (!AimMarkersEnabled.Value)
- updateTimePreempt();
- Add(new AimLineDrawable((Vector2)lastlastMousePosition, lastMousePosition, timePreempt));
- }
-
- return base.OnMouseMove(e);
- }
-
- private void addMarker(OsuAction? action)
- {
- var component = OsuSkinComponents.AimMarker;
-
- switch (action)
- {
- case OsuAction.LeftButton:
- component = OsuSkinComponents.HitMarkerLeft;
- break;
-
- case OsuAction.RightButton:
- component = OsuSkinComponents.HitMarkerRight;
- break;
- }
-
- Add(new HitMarkerDrawable(action, component, timePreempt)
- {
- Position = lastMousePosition,
- Origin = Anchor.Centre,
- Depth = action == null ? float.MaxValue : float.MinValue
- });
- }
-
- private void updateTimePreempt()
- {
- var hitObject = getHitObject();
- if (hitObject == null)
- return;
-
- timePreempt = hitObject.TimePreempt;
- }
-
- private OsuHitObject? getHitObject()
- {
- foreach (var dho in hitObjectContainer.Objects)
- return (dho as DrawableOsuHitObject)?.HitObject;
-
- return null;
- }
-
- private partial class HitMarkerDrawable : SkinnableDrawable
- {
- private readonly double lifetimeDuration;
- private readonly double fadeOutTime;
-
- public override bool RemoveWhenNotAlive => true;
-
- public HitMarkerDrawable(OsuAction? action, OsuSkinComponents componenet, double timePreempt)
- : base(new OsuSkinComponentLookup(componenet), _ => new DefaultHitMarker(action))
- {
- fadeOutTime = timePreempt / 2;
- lifetimeDuration = timePreempt + fadeOutTime;
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- LifetimeStart = Time.Current;
- LifetimeEnd = LifetimeStart + lifetimeDuration;
-
- Scheduler.AddDelayed(() =>
- {
- this.FadeOut(fadeOutTime);
- }, lifetimeDuration - fadeOutTime);
- }
- }
-
- private partial class AimLineDrawable : CompositeDrawable
- {
- private readonly double lifetimeDuration;
- private readonly double fadeOutTime;
-
- public override bool RemoveWhenNotAlive => true;
-
- public AimLineDrawable(Vector2 fromP, Vector2 toP, double timePreempt)
- {
- fadeOutTime = timePreempt / 2;
- lifetimeDuration = timePreempt + fadeOutTime;
-
- float distance = Vector2.Distance(fromP, toP);
- Vector2 direction = (toP - fromP);
- InternalChild = new Box
- {
- Position = fromP + (direction / 2),
- Size = new Vector2(distance, 1),
- Rotation = (float)(Math.Atan(direction.Y / direction.X) * (180 / Math.PI)),
- Origin = Anchor.Centre
- };
- }
-
- [BackgroundDependencyLoader]
- private void load(ISkinSource skin)
- {
- var color = skin.GetConfig(OsuSkinColour.ReplayAimLine)?.Value ?? Color4.White;
- color.A = 127;
- InternalChild.Colour = color;
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- LifetimeStart = Time.Current;
- LifetimeEnd = LifetimeStart + lifetimeDuration;
-
- Scheduler.AddDelayed(() =>
- {
- this.FadeOut(fadeOutTime);
- }, lifetimeDuration - fadeOutTime);
- }
- }
- }
-}
From 8cdd9c9ddcec526dcf4866f2c0b0ce1b98dbffd4 Mon Sep 17 00:00:00 2001
From: Sheepposu
Date: Fri, 23 Feb 2024 23:46:50 -0500
Subject: [PATCH 0096/1274] skinning changes
improve default design
---
.../Skinning/Default/DefaultHitMarker.cs | 8 +++-----
osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs | 1 -
osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs | 6 +-----
3 files changed, 4 insertions(+), 11 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs
index 7dabb5182f..dc890d4d63 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs
@@ -23,7 +23,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(3, length),
- Rotation = 45,
Colour = Colour4.Black.Opacity(0.5F)
},
new Box
@@ -31,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(3, length),
- Rotation = 135,
+ Rotation = 90,
Colour = Colour4.Black.Opacity(0.5F)
}
};
@@ -44,7 +43,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(1, length),
- Rotation = 45,
Colour = colour
},
new Box
@@ -52,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(1, length),
- Rotation = 135,
+ Rotation = 90,
Colour = colour
}
});
@@ -69,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
return (Colour4.LightGreen, 20, true);
default:
- return (Colour4.Gray.Opacity(0.3F), 8, false);
+ return (Colour4.Gray.Opacity(0.5F), 8, false);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs
index 5c864fb6c2..24f9217a5f 100644
--- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs
@@ -10,6 +10,5 @@ namespace osu.Game.Rulesets.Osu.Skinning
SliderBall,
SpinnerBackground,
StarBreakAdditive,
- ReplayAimLine,
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs
index a637eddd05..68686f835d 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs
@@ -12,7 +12,6 @@ using osu.Framework.Graphics.Pooling;
using osu.Game.Replays;
using osu.Game.Rulesets.Objects.Pooling;
using osu.Game.Rulesets.Osu.Replays;
-using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Rulesets.Osu.Skinning.Default;
using osu.Game.Rulesets.UI;
using osu.Game.Skinning;
@@ -47,11 +46,8 @@ namespace osu.Game.Rulesets.Osu.UI
}
[BackgroundDependencyLoader]
- private void load(ISkinSource skin)
+ private void load()
{
- var aimLineColor = skin.GetConfig(OsuSkinColour.ReplayAimLine)?.Value ?? Color4.White;
- aimLineColor.A = 127;
-
hitMarkersContainer.Hide();
aimMarkersContainer.Hide();
aimLinesContainer.Hide();
From 822ecb71068b77e071fb590983ff2834b0f9b2e3 Mon Sep 17 00:00:00 2001
From: Sheepposu
Date: Fri, 23 Feb 2024 23:52:17 -0500
Subject: [PATCH 0097/1274] remove unnecessary changes
---
osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini | 5 +----
osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs | 6 ------
osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 +-
3 files changed, 2 insertions(+), 11 deletions(-)
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini
index 2952948f45..9d16267d73 100644
--- a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini
+++ b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini
@@ -1,7 +1,4 @@
[General]
Version: latest
HitCircleOverlayAboveNumber: 0
-HitCirclePrefix: display
-
-[Colours]
-ReplayAimLine: 0,0,255
\ No newline at end of file
+HitCirclePrefix: display
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs
index 68686f835d..c4b5135ca2 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs
@@ -117,7 +117,6 @@ namespace osu.Game.Rulesets.Osu.UI
{
lifetimeManager.EntryBecameAlive += entryBecameAlive;
lifetimeManager.EntryBecameDead += entryBecameDead;
- lifetimeManager.EntryCrossedBoundary += entryCrossedBoundary;
PathRadius = 1f;
Colour = new Color4(255, 255, 255, 127);
@@ -153,11 +152,6 @@ namespace osu.Game.Rulesets.Osu.UI
}
}
- private void entryCrossedBoundary(LifetimeEntry entry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction)
- {
-
- }
-
private sealed class AimLinePointComparator : IComparer
{
public int Compare(AimPointEntry? x, AimPointEntry? y)
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index ea336e6067..a801e3d2f7 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.UI
judgementLayer = new JudgementContainer { RelativeSizeAxes = Axes.Both },
HitObjectContainer,
judgementAboveHitObjectLayer = new Container { RelativeSizeAxes = Axes.Both },
- approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both }
+ approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both },
};
HitPolicy = new StartTimeOrderedHitPolicy();
From 29e5f409bac928bc49e1940a2e44b8ee4b8f2a70 Mon Sep 17 00:00:00 2001
From: Sheepposu
Date: Tue, 27 Feb 2024 21:51:54 -0500
Subject: [PATCH 0098/1274] remove skinnable
better to add skinnable elements later
---
.../Resources/special-skin/aimmarker@2x.png | Bin 648 -> 0 bytes
.../special-skin/hitmarker-left@2x.png | Bin 2127 -> 0 bytes
.../special-skin/hitmarker-right@2x.png | Bin 1742 -> 0 bytes
osu.Game.Rulesets.Osu/OsuSkinComponents.cs | 3 -
.../Skinning/Default/DefaultHitMarker.cs | 74 ------------------
.../Skinning/Default/HitMarker.cs | 68 ++++++++++++----
.../Legacy/OsuLegacySkinTransformer.cs | 20 +----
.../UI/OsuAnalysisContainer.cs | 69 ++++++++--------
8 files changed, 88 insertions(+), 146 deletions(-)
delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/aimmarker@2x.png
delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-left@2x.png
delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-right@2x.png
delete mode 100644 osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/aimmarker@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/aimmarker@2x.png
deleted file mode 100644
index 0b2a554193f08f7984568980956a3db8a6b39800..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 648
zcmV;30(bq1P)EX>4Tx04R}tkv&MmKpe$iQ>7vm2a6PO$WR@`E-K4rtTK|H%@
z>74h8L#!+*#OK7523?T&k?XR{Z=6dG3p_JqWYhD+A!4!A#c~(3vY`^s5JwbMqkJLf
zvch?bvs$gQ_C5Ivg9U9R!*!aYNMH#`q#!~@9TikzAxf)8iitGs$36Tbjz2{%nOqex
zax9<*6_Voz|AXJ%n#JiUHz^ngdS7h&V+;uF0jdyW16NwdUuyz$pQJZB
zTI2{A+y*YLJDR))TI00006VoOIv00000008+zyMF)x010qNS#tmY
zE+YT{E+YYWr9XB6000McNliru=mHiD95ZRmtwR6+02y>eSad^gZEa<4bO1wgWnpw>
zWFU8GbZ8()Nlj2!fese{003Y~L_t&-(~Xd^4Zt7_1kYC1UL}8=Py}}=u;}y?!uSxX)0000EX>4Tx04R}tkv&MmKpe$iQ?)8p2Rn#3WT;NoK}8%(6^me@v=v%)FuC+YXws0R
zxHt-~1qVMCs}3&Cx;nTDg5U>;vxAeOiUKFoAxFnR+6_CX>@2HM@dakSAh-}000IiNkls_o@u5n{w3H!5jp#I_s}mSL6nvbfkWc#_vuLYo^#J%=bYz0&vPDu>`?~P
z0Nc!V3E&1C=JO;l4U7YWzyQ!=wjUbdA^U#|Xawp!3a-6gDOj^!(!4I&S*VDQsaC;d
zkpu@oS~HUIlo5?2^x2VUX1*t+Nq-yB9v@8*1=@kX09PKhkXqoU8}iuo%6H`9I*-*=
zs7QtWsq|QHIFq^%*3_$0#@g!%TuB`Tz#)<-Rfv`s2s4$%QoO2IwpJ8aHbxR!qW7trJguJ@1?U45bE-`_uOK{-b-%3G@PfU<~*e
zh?{*?U>&d#r~s;f+WnivkpmT$8`bNrWoNG~1b*F}+HfgG2XGX)$`bIrTV}Ye*3uC>
zq=ruJv2<)!2?D2qeSqx}JNi2t
zZQa#wVF)+@__FvNA8?}DEev%w+PVYHj{b&HVE||~=kU;$=+xmIQrE;mC2+(ioi{B_
z14~B(&~wBmou4?U1PDHZc=jd~eOHCfJ4>$%CvGf!H$C^B1-{^CW
zORYQQPIC*FJ;-)C)w)yeyz;EQuf9aM2(<9%X{j#}E?#-o-e-zAb+2tE-D7|^5ATq?
zKPt;x^IFGEMO1bn`Y0b*rGQ)vpN56TnkFjz%cEl&04>UXaP19WdWiP+eR%_|#
zY_xTo)~RR(2`K`4IvZ`>ZMBvT;GKKA9b5Amiycx!=6|Arl}AIhTNsKRQbSww88cm_
z&$%e?{q<>UQ8Hr~O=r?UpqZ7)iIaOQk2_>R_~GAElfeGZc(EJun2f*VoHpGKA1fE%
zW|d(4CFk^pJSI&K{I>Z$^s8!FUC@l#qnEW&;P)$7NN6_2le@kqBsYFlnE6LgSAh=E
zd{|fKvAT}?({|u}RzB|^_owZ39;*weyX}g26oT_FIwQc`1A4KK8XGV-|DrSEQ3wKM
zB2cr}D+T>i=`k~&xS0b&ZUX20Q|Yn2UMUFFh`_d*^^(>b&ZNwsC|Bt14QEm{>m?1?
zCIV$%m+ZU{)>JdH%N6_=!kX%J$xfh521*JQQMx*17-o2yD~w&8GS(IB2Y0l0XL0O!
zQb(~!i_VG2DnSO4Y0bbPVkC7`;L~hoUhdplS)RM<5J{vpdqa)piM1;R`uq0a*2A}}}-&CFL8OK}!6
zfVo0NWw8_=iDu@3K@k|}329PQv20~AjhQP{RTazDo{%Q7nAu-FPUGNcf@mb6MfPtJ
zM}Ybq5K_N?lQfP9prGz^zLl@R8mKqoXdRswQBoUra#pR{83{q(7nteTADoK?pG`A`9D8*
zL+g+7N8rOR69RB?D8@HbQD5`&4x1ww%{(zYhq8?E{44b(!oD|l*)8v0UWm1Qq+bAj
zbN5U4x*zH93LD?uTB69FHx-%Cyv%2>X8fJ)3^^T6(_aU)m*Br_F
z6_Nx3XU28k6Kk-%u-)ePB(8b=QDY16?Y-
z$_egQ2*3<*<;=LgJzv(Xzwp`DthMUSb0Oo$z$d`aQkhub=n{^MKyPm^JPw7#IhEi{TDHR{#GTxr2YT^002ovPDHLk
FV1jr`?veli
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-right@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-right@2x.png
deleted file mode 100644
index 66be9a80ee2e2ae4c6dc6915d2b7ca5cf8ab589f..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 1742
zcmV;<1~K`GP)EX>4Tx04R}tkv&MmKpe$iQ?)8p2Rn#3WT;NoK}8%(6^me@v=v%)FuC+YXws0R
zxHt-~1qVMCs}3&Cx;nTDg5U>;vxAeOiUKFoAxFnR+6_CX>@2HM@dakSAh-}000D~Nkl1L9Yb|fH_ftE7RjjO?hFdLVhHsD6;Mi3NQZON`8V+<3BQA3EPF^Lyy
zBFH5ssJYD~PVPNjt#956lhOc~_xg%spW#euk$w}6}R9gK=vhuEEC9o1$c~`Rf
z(Ry2wlf>QYeKL21@Rg4>x`?ys*smS!IAyn7e8E7rV7}_2!Kl|YJi?#x1%84Syp!In
zkY2Z>ZC|ASeH8V&R&~)}wqQQKV8CuUoJ!|IpZh27F_`+c=GVn>f!toVMTXssZ0rPq-2F
z7)4*YhF6Y6=fW|AnK={TxGQO->3NOv?ZHqu?n-9PL^x&;=hC%on8f+eXCO0UEIMoi
zC$QrUyo)<T*3ZV=R?f-~Sw)q8_7}ITNXzj(Ynz8XY#0nKO|Ffd3lJ+SQw?
zo^T_5u}|1KI1!G!Qa#~D8k*bxlSMfdpVSn!s$M%F!q0GjixAm?KUED#k}2O6e=Ub!
zzT9@~mdieLFWo14Y(4bY?{@Z~d#M9B__8N*HojnoWl8a{S^UD*#Oe5q57df^KXo6y
zFS^)9_p=4_sqNTj>tdf)v)O`qjqaxoT%0!kCVZ*Rs)f?;&EU6Nn8-Z~eiR+BtjUAq
zuj+95Y2#Stj`7sR9`G*v)Lv2)QZ=X0g)O!$}Y)kHjB1^&ZygL
zaa;~xW2Fp;tl;b7xLnk27M%fmLZ@PB*b@vZ9}Jg6605gAXe
zstYZ)p{)uZh6ZtROM+Y(5y>UDycZfo_&zMvtXfj*ar#}oX-JZ!IZ2XB>92W{iM*rM
zPniAbKjTyQRE^sNDlJm64K~qIM5Tc?-B3Fj<2yzt(TD^gFHQ4B!vebI{%dk!P-X
kiyU)`Hc@tO_2Ah*0YN2jCf7K0Z2$lO07*qoM6N<$f~iM7hyVZp
diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
index 75db18d345..52fdfea95f 100644
--- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
+++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
@@ -22,8 +22,5 @@ namespace osu.Game.Rulesets.Osu
SpinnerBody,
CursorSmoke,
ApproachCircle,
- HitMarkerLeft,
- HitMarkerRight,
- AimMarker
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs
deleted file mode 100644
index dc890d4d63..0000000000
--- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osuTK;
-
-namespace osu.Game.Rulesets.Osu.Skinning.Default
-{
- public partial class DefaultHitMarker : CompositeDrawable
- {
- public DefaultHitMarker(OsuAction? action)
- {
- var (colour, length, hasBorder) = getConfig(action);
-
- if (hasBorder)
- {
- InternalChildren = new Drawable[]
- {
- new Box
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Size = new Vector2(3, length),
- Colour = Colour4.Black.Opacity(0.5F)
- },
- new Box
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Size = new Vector2(3, length),
- Rotation = 90,
- Colour = Colour4.Black.Opacity(0.5F)
- }
- };
- }
-
- AddRangeInternal(new Drawable[]
- {
- new Box
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Size = new Vector2(1, length),
- Colour = colour
- },
- new Box
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Size = new Vector2(1, length),
- Rotation = 90,
- Colour = colour
- }
- });
- }
-
- private (Colour4 colour, float length, bool hasBorder) getConfig(OsuAction? action)
- {
- switch (action)
- {
- case OsuAction.LeftButton:
- return (Colour4.Orange, 20, true);
-
- case OsuAction.RightButton:
- return (Colour4.LightGreen, 20, true);
-
- default:
- return (Colour4.Gray.Opacity(0.5F), 8, false);
- }
- }
- }
-}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs
index 28877345d0..fe662470bc 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs
@@ -1,37 +1,73 @@
// 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.Sprites;
-using osu.Game.Skinning;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osuTK;
namespace osu.Game.Rulesets.Osu.Skinning.Default
{
- public partial class HitMarker : Sprite
+ public partial class HitMarker : CompositeDrawable
{
- private readonly OsuAction? action;
-
- public HitMarker(OsuAction? action = null)
+ public HitMarker(OsuAction? action)
{
- this.action = action;
+ var (colour, length, hasBorder) = getConfig(action);
+
+ if (hasBorder)
+ {
+ InternalChildren = new Drawable[]
+ {
+ new Box
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(3, length),
+ Colour = Colour4.Black.Opacity(0.5F)
+ },
+ new Box
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(3, length),
+ Rotation = 90,
+ Colour = Colour4.Black.Opacity(0.5F)
+ }
+ };
+ }
+
+ AddRangeInternal(new Drawable[]
+ {
+ new Box
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(1, length),
+ Colour = colour
+ },
+ new Box
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(1, length),
+ Rotation = 90,
+ Colour = colour
+ }
+ });
}
- [BackgroundDependencyLoader]
- private void load(ISkinSource skin)
+ private (Colour4 colour, float length, bool hasBorder) getConfig(OsuAction? action)
{
switch (action)
{
case OsuAction.LeftButton:
- Texture = skin.GetTexture(@"hitmarker-left");
- break;
+ return (Colour4.Orange, 20, true);
case OsuAction.RightButton:
- Texture = skin.GetTexture(@"hitmarker-right");
- break;
+ return (Colour4.LightGreen, 20, true);
default:
- Texture = skin.GetTexture(@"aimmarker");
- break;
+ return (Colour4.Gray.Opacity(0.5F), 8, false);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
index 61f9eebd86..d2ebc68c52 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
@@ -6,7 +6,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Skinning;
-using osu.Game.Rulesets.Osu.Skinning.Default;
using osuTK;
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
@@ -169,23 +168,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
return null;
- case OsuSkinComponents.HitMarkerLeft:
- if (GetTexture(@"hitmarker-left") != null)
- return new HitMarker(OsuAction.LeftButton);
-
- return null;
-
- case OsuSkinComponents.HitMarkerRight:
- if (GetTexture(@"hitmarker-right") != null)
- return new HitMarker(OsuAction.RightButton);
-
- return null;
-
- case OsuSkinComponents.AimMarker:
- if (GetTexture(@"aimmarker") != null)
- return new HitMarker();
-
- return null;
+ default:
+ throw new UnsupportedSkinComponentException(lookup);
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs
index c4b5135ca2..7d8ae6980c 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs
@@ -14,7 +14,6 @@ using osu.Game.Rulesets.Objects.Pooling;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Osu.Skinning.Default;
using osu.Game.Rulesets.UI;
-using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
@@ -26,40 +25,41 @@ namespace osu.Game.Rulesets.Osu.UI
public Bindable AimMarkersEnabled = new BindableBool();
public Bindable AimLinesEnabled = new BindableBool();
- private HitMarkersContainer hitMarkersContainer;
- private AimMarkersContainer aimMarkersContainer;
- private AimLinesContainer aimLinesContainer;
+ protected HitMarkersContainer HitMarkers;
+ protected AimMarkersContainer AimMarkers;
+ protected AimLinesContainer AimLines;
public OsuAnalysisContainer(Replay replay)
: base(replay)
{
InternalChildren = new Drawable[]
{
- hitMarkersContainer = new HitMarkersContainer(),
- aimMarkersContainer = new AimMarkersContainer() { Depth = float.MinValue },
- aimLinesContainer = new AimLinesContainer() { Depth = float.MaxValue }
+ HitMarkers = new HitMarkersContainer(),
+ AimMarkers = new AimMarkersContainer { Depth = float.MinValue },
+ AimLines = new AimLinesContainer { Depth = float.MaxValue }
};
- HitMarkerEnabled.ValueChanged += e => hitMarkersContainer.FadeTo(e.NewValue ? 1 : 0);
- AimMarkersEnabled.ValueChanged += e => aimMarkersContainer.FadeTo(e.NewValue ? 1 : 0);
- AimLinesEnabled.ValueChanged += e => aimLinesContainer.FadeTo(e.NewValue ? 1 : 0);
+ HitMarkerEnabled.ValueChanged += e => HitMarkers.FadeTo(e.NewValue ? 1 : 0);
+ AimMarkersEnabled.ValueChanged += e => AimMarkers.FadeTo(e.NewValue ? 1 : 0);
+ AimLinesEnabled.ValueChanged += e => AimLines.FadeTo(e.NewValue ? 1 : 0);
}
[BackgroundDependencyLoader]
private void load()
{
- hitMarkersContainer.Hide();
- aimMarkersContainer.Hide();
- aimLinesContainer.Hide();
+ HitMarkers.Hide();
+ AimMarkers.Hide();
+ AimLines.Hide();
bool leftHeld = false;
bool rightHeld = false;
+
foreach (var frame in Replay.Frames)
{
var osuFrame = (OsuReplayFrame)frame;
- aimMarkersContainer.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position));
- aimLinesContainer.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position));
+ AimMarkers.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position));
+ AimLines.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position));
bool leftButton = osuFrame.Actions.Contains(OsuAction.LeftButton);
bool rightButton = osuFrame.Actions.Contains(OsuAction.RightButton);
@@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.UI
leftHeld = false;
else if (!leftHeld && leftButton)
{
- hitMarkersContainer.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, true));
+ HitMarkers.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, true));
leftHeld = true;
}
@@ -76,42 +76,42 @@ namespace osu.Game.Rulesets.Osu.UI
rightHeld = false;
else if (!rightHeld && rightButton)
{
- hitMarkersContainer.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, false));
+ HitMarkers.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, false));
rightHeld = true;
}
}
}
- private partial class HitMarkersContainer : PooledDrawableWithLifetimeContainer
+ protected partial class HitMarkersContainer : PooledDrawableWithLifetimeContainer
{
private readonly HitMarkerPool leftPool;
private readonly HitMarkerPool rightPool;
public HitMarkersContainer()
{
- AddInternal(leftPool = new HitMarkerPool(OsuSkinComponents.HitMarkerLeft, OsuAction.LeftButton, 15));
- AddInternal(rightPool = new HitMarkerPool(OsuSkinComponents.HitMarkerRight, OsuAction.RightButton, 15));
+ AddInternal(leftPool = new HitMarkerPool(OsuAction.LeftButton, 15));
+ AddInternal(rightPool = new HitMarkerPool(OsuAction.RightButton, 15));
}
protected override HitMarkerDrawable GetDrawable(HitMarkerEntry entry) => (entry.IsLeftMarker ? leftPool : rightPool).Get(d => d.Apply(entry));
}
- private partial class AimMarkersContainer : PooledDrawableWithLifetimeContainer
+ protected partial class AimMarkersContainer : PooledDrawableWithLifetimeContainer
{
private readonly HitMarkerPool pool;
public AimMarkersContainer()
{
- AddInternal(pool = new HitMarkerPool(OsuSkinComponents.AimMarker, null, 80));
+ AddInternal(pool = new HitMarkerPool(null, 80));
}
protected override HitMarkerDrawable GetDrawable(AimPointEntry entry) => pool.Get(d => d.Apply(entry));
}
- private partial class AimLinesContainer : Path
+ protected partial class AimLinesContainer : Path
{
- private LifetimeEntryManager lifetimeManager = new LifetimeEntryManager();
- private SortedSet aliveEntries = new SortedSet(new AimLinePointComparator());
+ private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager();
+ private readonly SortedSet aliveEntries = new SortedSet(new AimLinePointComparator());
public AimLinesContainer()
{
@@ -146,6 +146,7 @@ namespace osu.Game.Rulesets.Osu.UI
private void updateVertices()
{
ClearVertices();
+
foreach (var entry in aliveEntries)
{
AddVertex(entry.Position);
@@ -164,7 +165,7 @@ namespace osu.Game.Rulesets.Osu.UI
}
}
- private partial class HitMarkerDrawable : PoolableDrawableWithLifetime
+ protected partial class HitMarkerDrawable : PoolableDrawableWithLifetime
{
///
/// This constructor only exists to meet the new() type constraint of .
@@ -173,10 +174,10 @@ namespace osu.Game.Rulesets.Osu.UI
{
}
- public HitMarkerDrawable(OsuSkinComponents component, OsuAction? action)
+ public HitMarkerDrawable(OsuAction? action)
{
Origin = Anchor.Centre;
- InternalChild = new SkinnableDrawable(new OsuSkinComponentLookup(component), _ => new DefaultHitMarker(action));
+ InternalChild = new HitMarker(action);
}
protected override void OnApply(AimPointEntry entry)
@@ -191,22 +192,20 @@ namespace osu.Game.Rulesets.Osu.UI
}
}
- private partial class HitMarkerPool : DrawablePool
+ protected partial class HitMarkerPool : DrawablePool
{
- private readonly OsuSkinComponents component;
private readonly OsuAction? action;
- public HitMarkerPool(OsuSkinComponents component, OsuAction? action, int initialSize)
+ public HitMarkerPool(OsuAction? action, int initialSize)
: base(initialSize)
{
- this.component = component;
this.action = action;
}
- protected override HitMarkerDrawable CreateNewDrawable() => new HitMarkerDrawable(component, action);
+ protected override HitMarkerDrawable CreateNewDrawable() => new HitMarkerDrawable(action);
}
- private partial class AimPointEntry : LifetimeEntry
+ protected partial class AimPointEntry : LifetimeEntry
{
public Vector2 Position { get; }
@@ -218,7 +217,7 @@ namespace osu.Game.Rulesets.Osu.UI
}
}
- private partial class HitMarkerEntry : AimPointEntry
+ protected partial class HitMarkerEntry : AimPointEntry
{
public bool IsLeftMarker { get; }
From cefc8357bbdb732f805e848b065afcf40619b9c0 Mon Sep 17 00:00:00 2001
From: Sheepposu
Date: Tue, 27 Feb 2024 21:52:10 -0500
Subject: [PATCH 0099/1274] test scene for OsuAnalysisContainer
---
.../TestSceneHitMarker.cs | 91 +++++++++++++++++++
1 file changed, 91 insertions(+)
create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs
new file mode 100644
index 0000000000..e4c48f96b8
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs
@@ -0,0 +1,91 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+#nullable disable
+
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Game.Replays;
+using osu.Game.Rulesets.Osu.Replays;
+using osu.Game.Rulesets.Osu.UI;
+using osu.Game.Rulesets.Replays;
+using osu.Game.Tests.Visual;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public partial class TestSceneHitMarker : OsuTestScene
+ {
+ private TestOsuAnalysisContainer analysisContainer;
+
+ [Test]
+ public void TestHitMarkers()
+ {
+ createAnalysisContainer();
+ AddStep("enable hit markers", () => analysisContainer.HitMarkerEnabled.Value = true);
+ AddAssert("hit markers visible", () => analysisContainer.HitMarkersVisible);
+ AddStep("disable hit markers", () => analysisContainer.HitMarkerEnabled.Value = false);
+ AddAssert("hit markers not visible", () => !analysisContainer.HitMarkersVisible);
+ }
+
+ [Test]
+ public void TestAimMarker()
+ {
+ createAnalysisContainer();
+ AddStep("enable aim markers", () => analysisContainer.AimMarkersEnabled.Value = true);
+ AddAssert("aim markers visible", () => analysisContainer.AimMarkersVisible);
+ AddStep("disable aim markers", () => analysisContainer.AimMarkersEnabled.Value = false);
+ AddAssert("aim markers not visible", () => !analysisContainer.AimMarkersVisible);
+ }
+
+ [Test]
+ public void TestAimLines()
+ {
+ createAnalysisContainer();
+ AddStep("enable aim lines", () => analysisContainer.AimLinesEnabled.Value = true);
+ AddAssert("aim lines visible", () => analysisContainer.AimLinesVisible);
+ AddStep("disable aim lines", () => analysisContainer.AimLinesEnabled.Value = false);
+ AddAssert("aim lines not visible", () => !analysisContainer.AimLinesVisible);
+ }
+
+ private void createAnalysisContainer()
+ {
+ AddStep("create new analysis container", () => Child = analysisContainer = new TestOsuAnalysisContainer(fabricateReplay()));
+ }
+
+ private Replay fabricateReplay()
+ {
+ var frames = new List();
+
+ for (int i = 0; i < 50; i++)
+ {
+ frames.Add(new OsuReplayFrame
+ {
+ Time = Time.Current + i * 15,
+ Position = new Vector2(20 + i * 10, 20),
+ Actions =
+ {
+ i % 2 == 0 ? OsuAction.LeftButton : OsuAction.RightButton
+ }
+ });
+ }
+
+ return new Replay { Frames = frames };
+ }
+
+ private partial class TestOsuAnalysisContainer : OsuAnalysisContainer
+ {
+ public TestOsuAnalysisContainer(Replay replay)
+ : base(replay)
+ {
+ }
+
+ public bool HitMarkersVisible => HitMarkers.Alpha > 0 && HitMarkers.Entries.Any();
+
+ public bool AimMarkersVisible => AimMarkers.Alpha > 0 && AimMarkers.Entries.Any();
+
+ public bool AimLinesVisible => AimLines.Alpha > 0 && AimLines.Vertices.Count > 1;
+ }
+ }
+}
From 7687ab63edd2b49b4e1a5a2c30be2b9e2b8ca328 Mon Sep 17 00:00:00 2001
From: Sheepposu
Date: Tue, 27 Feb 2024 22:03:45 -0500
Subject: [PATCH 0100/1274] fix code formatting
---
osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs | 3 ++-
osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 1 -
osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs | 2 +-
osu.Game/Screens/Play/ReplayPlayer.cs | 1 +
4 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
index fd9cb67995..b3d5231ade 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
@@ -16,12 +16,13 @@ namespace osu.Game.Rulesets.Osu.UI
private readonly PlayerCheckbox hitMarkerToggle;
private readonly PlayerCheckbox aimMarkerToggle;
- private readonly PlayerCheckbox hideCursorToggle;
private readonly PlayerCheckbox aimLinesToggle;
public OsuAnalysisSettings(DrawableRuleset drawableRuleset)
: base(drawableRuleset)
{
+ PlayerCheckbox hideCursorToggle;
+
Children = new Drawable[]
{
hitMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HitMarkers },
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index a801e3d2f7..411a02c5af 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -36,7 +36,6 @@ namespace osu.Game.Rulesets.Osu.UI
private readonly JudgementPooler judgementPooler;
public SmokeContainer Smoke { get; }
-
public FollowPointRenderer FollowPoints { get; }
public static readonly Vector2 BASE_SIZE = new Vector2(512, 384);
diff --git a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs
index 3752b2b900..91531b28b4 100644
--- a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs
+++ b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs
@@ -10,7 +10,7 @@ namespace osu.Game.Screens.Play.PlayerSettings
{
protected DrawableRuleset DrawableRuleset;
- public AnalysisSettings(DrawableRuleset drawableRuleset)
+ protected AnalysisSettings(DrawableRuleset drawableRuleset)
: base("Analysis Settings")
{
DrawableRuleset = drawableRuleset;
diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs
index ce6cb5124a..81cc44d889 100644
--- a/osu.Game/Screens/Play/ReplayPlayer.cs
+++ b/osu.Game/Screens/Play/ReplayPlayer.cs
@@ -73,6 +73,7 @@ namespace osu.Game.Screens.Play
HUDOverlay.PlayerSettingsOverlay.AddAtStart(playbackSettings);
var analysisSettings = DrawableRuleset.Ruleset.CreateAnalysisSettings(DrawableRuleset);
+
if (analysisSettings != null)
{
HUDOverlay.PlayerSettingsOverlay.AddAtStart(analysisSettings);
From caba0510db86b66f9e238c640a7a09e272d67fdb Mon Sep 17 00:00:00 2001
From: nathen
Date: Sat, 9 Mar 2024 23:10:53 -0500
Subject: [PATCH 0101/1274] 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 0102/1274] 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 0103/1274] 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 0104/1274] 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 0105/1274] 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 0106/1274] 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 0107/1274] 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 0108/1274] 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 0109/1274] 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 0110/1274] 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 0111/1274] 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 0112/1274] 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 0113/1274] 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 0114/1274] 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 0115/1274] 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 0116/1274] 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 0117/1274] 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 0118/1274] 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 0119/1274] 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 0120/1274] 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 0121/1274] 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 0122/1274] 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 0123/1274] 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 0124/1274] 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 0125/1274] 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 0126/1274] 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 0127/1274] 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 0128/1274] 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 203e9284eb5eb54d464e7a290e106b605b7cf66e Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Tue, 28 May 2024 19:01:53 +0200
Subject: [PATCH 0129/1274] End circle only gets brighter once shift is held
---
.../Sliders/Components/SliderTailPiece.cs | 29 ++++++++++++++++++-
1 file changed, 28 insertions(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
index 5cf9346f2e..dc57d6d7ca 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
@@ -61,11 +61,38 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
updateCirclePieceColour();
}
+ protected override bool OnKeyDown(KeyDownEvent e)
+ {
+ if (e.Repeat)
+ return false;
+
+ handleDragToggle(e);
+ return base.OnKeyDown(e);
+ }
+
+ protected override void OnKeyUp(KeyUpEvent e)
+ {
+ handleDragToggle(e);
+ base.OnKeyUp(e);
+ }
+
+ private bool lastShiftPressed;
+
+ private void handleDragToggle(KeyboardEvent key)
+ {
+ bool shiftPressed = key.ShiftPressed;
+
+ if (shiftPressed == lastShiftPressed) return;
+
+ lastShiftPressed = shiftPressed;
+ updateCirclePieceColour();
+ }
+
private void updateCirclePieceColour()
{
Color4 colour = colours.Yellow;
- if (IsHovered)
+ if (IsHovered && inputManager.CurrentState.Keyboard.ShiftPressed)
colour = colour.Lighten(1);
CirclePiece.Colour = colour;
From 17145673421118baee2d6971277771625512a78a Mon Sep 17 00:00:00 2001
From: Nathen
Date: Wed, 29 May 2024 09:40:39 -0400
Subject: [PATCH 0130/1274] 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 0131/1274] 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 7254096c9011132e68f9e1ef109a6f13c076b986 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sat, 1 Jun 2024 20:28:39 +0200
Subject: [PATCH 0132/1274] fix isDragging being always true
---
.../Edit/Blueprints/Sliders/Components/SliderTailPiece.cs | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
index dc57d6d7ca..b5894eb84f 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
@@ -129,10 +129,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
protected override void OnDragEnd(DragEndEvent e)
{
- if (isDragging)
- {
- editorBeatmap?.EndChange();
- }
+ if (!isDragging) return;
+
+ isDragging = false;
+ editorBeatmap?.EndChange();
}
///
From ca41c84ba21ab50aea0e9c1fef406c7bc26293a5 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sat, 1 Jun 2024 21:15:54 +0200
Subject: [PATCH 0133/1274] trim excess control points on drag end
---
.../Sliders/Components/SliderTailPiece.cs | 29 +++++++++++++++++++
1 file changed, 29 insertions(+)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
index b5894eb84f..a0f4401ca7 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
@@ -131,10 +131,39 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
if (!isDragging) return;
+ trimExcessControlPoints(Slider.Path);
+
isDragging = false;
editorBeatmap?.EndChange();
}
+ ///
+ /// Trims control points from the end of the slider path which are not required to reach the expected end of the slider.
+ ///
+ /// The slider path to trim control points of.
+ private void trimExcessControlPoints(SliderPath sliderPath)
+ {
+ if (!sliderPath.ExpectedDistance.Value.HasValue)
+ return;
+
+ double[] segmentEnds = sliderPath.GetSegmentEnds().ToArray();
+ int segmentIndex = 0;
+
+ for (int i = 1; i < sliderPath.ControlPoints.Count - 1; i++)
+ {
+ if (!sliderPath.ControlPoints[i].Type.HasValue) continue;
+
+ if (Precision.AlmostBigger(segmentEnds[segmentIndex], 1, 1E-3))
+ {
+ sliderPath.ControlPoints.RemoveRange(i + 1, sliderPath.ControlPoints.Count - i - 1);
+ sliderPath.ControlPoints[^1].Type = null;
+ break;
+ }
+
+ segmentIndex++;
+ }
+ }
+
///
/// Finds the expected distance value for which the slider end is closest to the mouse position.
///
From bb38cb4137b6bbfa87f152bcc43b5b470736c806 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Mon, 3 Jun 2024 13:18:36 +0200
Subject: [PATCH 0134/1274] simplify tracking changes in shift key status
---
.../Sliders/Components/SliderTailPiece.cs | 21 +++++--------------
1 file changed, 5 insertions(+), 16 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
index a0f4401ca7..f28a8fafda 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
@@ -63,31 +63,20 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
protected override bool OnKeyDown(KeyDownEvent e)
{
- if (e.Repeat)
- return false;
+ if (!e.Repeat && (e.Key == Key.ShiftLeft || e.Key == Key.ShiftRight))
+ updateCirclePieceColour();
- handleDragToggle(e);
return base.OnKeyDown(e);
}
protected override void OnKeyUp(KeyUpEvent e)
{
- handleDragToggle(e);
+ if (e.Key == Key.ShiftLeft || e.Key == Key.ShiftRight)
+ updateCirclePieceColour();
+
base.OnKeyUp(e);
}
- private bool lastShiftPressed;
-
- private void handleDragToggle(KeyboardEvent key)
- {
- bool shiftPressed = key.ShiftPressed;
-
- if (shiftPressed == lastShiftPressed) return;
-
- lastShiftPressed = shiftPressed;
- updateCirclePieceColour();
- }
-
private void updateCirclePieceColour()
{
Color4 colour = colours.Yellow;
From 77b47ad2b4a3ea402017c48cb04b095dc1bd1b43 Mon Sep 17 00:00:00 2001
From: Olivier Schipper
Date: Mon, 3 Jun 2024 13:23:39 +0200
Subject: [PATCH 0135/1274] simplify nullability annotation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Bartłomiej Dach
---
.../Edit/Blueprints/Sliders/Components/SliderTailPiece.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
index a0f4401ca7..23ebd92482 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
@@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
private readonly Cached fullPathCache = new Cached();
- [Resolved(CanBeNull = true)]
+ [Resolved]
private EditorBeatmap? editorBeatmap { get; set; }
[Resolved]
From 34c4ee7de8ea1b2228221c46aea2acb2fac56afd Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Mon, 3 Jun 2024 13:38:42 +0200
Subject: [PATCH 0136/1274] add CanBeNull attribute to LastRepeat
---
.../Edit/Blueprints/Sliders/SliderCircleOverlay.cs | 2 +-
osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 ++
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs
index 2bf5118039..55ea131dab 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs
@@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
base.Update();
var circle = position == SliderPosition.Start ? (HitCircle)Slider.HeadCircle :
- Slider.RepeatCount % 2 == 0 ? Slider.TailCircle : Slider.LastRepeat;
+ Slider.RepeatCount % 2 == 0 ? Slider.TailCircle : Slider.LastRepeat!;
CirclePiece.UpdateFrom(circle);
marker.UpdateFrom(circle);
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index 041907d790..57f277e1ce 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -9,6 +9,7 @@ using System.Collections.Generic;
using osu.Game.Rulesets.Objects;
using System.Linq;
using System.Threading;
+using JetBrains.Annotations;
using Newtonsoft.Json;
using osu.Framework.Bindables;
using osu.Framework.Caching;
@@ -163,6 +164,7 @@ namespace osu.Game.Rulesets.Osu.Objects
public SliderTailCircle TailCircle { get; protected set; }
[JsonIgnore]
+ [CanBeNull]
public SliderRepeat LastRepeat { get; protected set; }
public Slider()
From 82919998dac6d38de78a4c2041e068e2a09461c0 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Tue, 4 Jun 2024 18:26:32 +0200
Subject: [PATCH 0137/1274] dont light up tail piece when hovering anchor
---
.../Sliders/Components/SliderTailPiece.cs | 28 +++----------------
1 file changed, 4 insertions(+), 24 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
index cf8f909ff6..7d39f04596 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
@@ -50,38 +50,18 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => CirclePiece.ReceivePositionalInputAt(screenSpacePos);
- protected override bool OnHover(HoverEvent e)
+ protected override void Update()
{
updateCirclePieceColour();
- return false;
- }
-
- protected override void OnHoverLost(HoverLostEvent e)
- {
- updateCirclePieceColour();
- }
-
- protected override bool OnKeyDown(KeyDownEvent e)
- {
- if (!e.Repeat && (e.Key == Key.ShiftLeft || e.Key == Key.ShiftRight))
- updateCirclePieceColour();
-
- return base.OnKeyDown(e);
- }
-
- protected override void OnKeyUp(KeyUpEvent e)
- {
- if (e.Key == Key.ShiftLeft || e.Key == Key.ShiftRight)
- updateCirclePieceColour();
-
- base.OnKeyUp(e);
+ base.Update();
}
private void updateCirclePieceColour()
{
Color4 colour = colours.Yellow;
- if (IsHovered && inputManager.CurrentState.Keyboard.ShiftPressed)
+ if (IsHovered && inputManager.CurrentState.Keyboard.ShiftPressed
+ && !inputManager.HoveredDrawables.Any(o => o is PathControlPointPiece))
colour = colour.Lighten(1);
CirclePiece.Colour = colour;
From efc8e1431a11a5e6e468ba72a30411b112d1c850 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Wed, 19 Jun 2024 23:15:35 +0200
Subject: [PATCH 0138/1274] activate length change with context menu
---
.../Blueprints/Sliders/Components/SliderTailPiece.cs | 10 ++++++++--
.../Blueprints/Sliders/SliderSelectionBlueprint.cs | 4 ++++
2 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
index 7d39f04596..2ebdf87606 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
@@ -20,6 +20,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
public partial class SliderTailPiece : SliderCircleOverlay
{
+ ///
+ /// Whether this slider tail is draggable, changing the distance of the slider.
+ ///
+ public bool IsDraggable { get; set; }
+
///
/// Whether this is currently being dragged.
///
@@ -60,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
Color4 colour = colours.Yellow;
- if (IsHovered && inputManager.CurrentState.Keyboard.ShiftPressed
+ if (IsHovered && IsDraggable
&& !inputManager.HoveredDrawables.Any(o => o is PathControlPointPiece))
colour = colour.Lighten(1);
@@ -69,7 +74,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
protected override bool OnDragStart(DragStartEvent e)
{
- if (e.Button == MouseButton.Right || !inputManager.CurrentState.Keyboard.ShiftPressed)
+ if (e.Button == MouseButton.Right || !IsDraggable)
return false;
isDragging = true;
@@ -103,6 +108,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
trimExcessControlPoints(Slider.Path);
isDragging = false;
+ IsDraggable = false;
editorBeatmap?.EndChange();
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
index 2c239a40c8..4a949f5b48 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
@@ -409,6 +409,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
addControlPoint(rightClickPosition);
changeHandler?.EndChange();
}),
+ new OsuMenuItem("Adjust distance", MenuItemType.Standard, () =>
+ {
+ TailPiece.IsDraggable = true;
+ }),
new OsuMenuItem("Convert to stream", MenuItemType.Destructive, convertToStream),
};
From b24bfa290806117753dc1e7969ddf3966d09094e Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Thu, 20 Jun 2024 00:02:43 +0200
Subject: [PATCH 0139/1274] click to choose length instead of drag
---
.../Sliders/Components/SliderTailPiece.cs | 185 ------------------
.../Sliders/SliderSelectionBlueprint.cs | 129 +++++++++++-
2 files changed, 124 insertions(+), 190 deletions(-)
delete mode 100644 osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
deleted file mode 100644
index 2ebdf87606..0000000000
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
+++ /dev/null
@@ -1,185 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System.Linq;
-using osu.Framework.Allocation;
-using osu.Framework.Caching;
-using osu.Framework.Extensions.Color4Extensions;
-using osu.Framework.Input;
-using osu.Framework.Input.Events;
-using osu.Framework.Utils;
-using osu.Game.Graphics;
-using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Screens.Edit;
-using osuTK;
-using osuTK.Graphics;
-using osuTK.Input;
-
-namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
-{
- public partial class SliderTailPiece : SliderCircleOverlay
- {
- ///
- /// Whether this slider tail is draggable, changing the distance of the slider.
- ///
- public bool IsDraggable { get; set; }
-
- ///
- /// Whether this is currently being dragged.
- ///
- private bool isDragging;
-
- private InputManager inputManager = null!;
-
- private readonly Cached fullPathCache = new Cached();
-
- [Resolved]
- private EditorBeatmap? editorBeatmap { get; set; }
-
- [Resolved]
- private OsuColour colours { get; set; } = null!;
-
- public SliderTailPiece(Slider slider, SliderPosition position)
- : base(slider, position)
- {
- Slider.Path.ControlPoints.CollectionChanged += (_, _) => fullPathCache.Invalidate();
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- inputManager = GetContainingInputManager();
- }
-
- public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => CirclePiece.ReceivePositionalInputAt(screenSpacePos);
-
- protected override void Update()
- {
- updateCirclePieceColour();
- base.Update();
- }
-
- private void updateCirclePieceColour()
- {
- Color4 colour = colours.Yellow;
-
- if (IsHovered && IsDraggable
- && !inputManager.HoveredDrawables.Any(o => o is PathControlPointPiece))
- colour = colour.Lighten(1);
-
- CirclePiece.Colour = colour;
- }
-
- protected override bool OnDragStart(DragStartEvent e)
- {
- if (e.Button == MouseButton.Right || !IsDraggable)
- return false;
-
- isDragging = true;
- editorBeatmap?.BeginChange();
-
- return true;
- }
-
- protected override void OnDrag(DragEvent e)
- {
- double oldDistance = Slider.Path.Distance;
- double proposedDistance = findClosestPathDistance(e);
-
- proposedDistance = MathHelper.Clamp(proposedDistance, 0, Slider.Path.CalculatedDistance);
- proposedDistance = MathHelper.Clamp(proposedDistance,
- 0.1 * oldDistance / Slider.SliderVelocityMultiplier,
- 10 * oldDistance / Slider.SliderVelocityMultiplier);
-
- if (Precision.AlmostEquals(proposedDistance, oldDistance))
- return;
-
- Slider.SliderVelocityMultiplier *= proposedDistance / oldDistance;
- Slider.Path.ExpectedDistance.Value = proposedDistance;
- editorBeatmap?.Update(Slider);
- }
-
- protected override void OnDragEnd(DragEndEvent e)
- {
- if (!isDragging) return;
-
- trimExcessControlPoints(Slider.Path);
-
- isDragging = false;
- IsDraggable = false;
- editorBeatmap?.EndChange();
- }
-
- ///
- /// Trims control points from the end of the slider path which are not required to reach the expected end of the slider.
- ///
- /// The slider path to trim control points of.
- private void trimExcessControlPoints(SliderPath sliderPath)
- {
- if (!sliderPath.ExpectedDistance.Value.HasValue)
- return;
-
- double[] segmentEnds = sliderPath.GetSegmentEnds().ToArray();
- int segmentIndex = 0;
-
- for (int i = 1; i < sliderPath.ControlPoints.Count - 1; i++)
- {
- if (!sliderPath.ControlPoints[i].Type.HasValue) continue;
-
- if (Precision.AlmostBigger(segmentEnds[segmentIndex], 1, 1E-3))
- {
- sliderPath.ControlPoints.RemoveRange(i + 1, sliderPath.ControlPoints.Count - i - 1);
- sliderPath.ControlPoints[^1].Type = null;
- break;
- }
-
- segmentIndex++;
- }
- }
-
- ///
- /// Finds the expected distance value for which the slider end is closest to the mouse position.
- ///
- private double findClosestPathDistance(DragEvent e)
- {
- const double step1 = 10;
- const double step2 = 0.1;
-
- var desiredPosition = e.MousePosition - Slider.Position;
-
- if (!fullPathCache.IsValid)
- fullPathCache.Value = new SliderPath(Slider.Path.ControlPoints.ToArray());
-
- // Do a linear search to find the closest point on the path to the mouse position.
- double bestValue = 0;
- double minDistance = double.MaxValue;
-
- for (double d = 0; d <= fullPathCache.Value.CalculatedDistance; d += step1)
- {
- double t = d / fullPathCache.Value.CalculatedDistance;
- float dist = Vector2.Distance(fullPathCache.Value.PositionAt(t), desiredPosition);
-
- if (dist >= minDistance) continue;
-
- minDistance = dist;
- bestValue = d;
- }
-
- // Do another linear search to fine-tune the result.
- for (double d = bestValue - step1; d <= bestValue + step1; d += step2)
- {
- double t = d / fullPathCache.Value.CalculatedDistance;
- float dist = Vector2.Distance(fullPathCache.Value.PositionAt(t), desiredPosition);
-
- if (dist >= minDistance) continue;
-
- minDistance = dist;
- bestValue = d;
- }
-
- return bestValue;
- }
- }
-}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
index 4a949f5b48..f59ef298a7 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
@@ -8,6 +8,7 @@ using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Framework.Caching;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.UserInterface;
@@ -34,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
protected SliderBodyPiece BodyPiece { get; private set; }
protected SliderCircleOverlay HeadOverlay { get; private set; }
- protected SliderTailPiece TailPiece { get; private set; }
+ protected SliderCircleOverlay TailPiece { get; private set; }
[CanBeNull]
protected PathControlPointVisualiser ControlPointVisualiser { get; private set; }
@@ -60,6 +61,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
private readonly IBindable pathVersion = new Bindable();
private readonly BindableList selectedObjects = new BindableList();
+ // Cached slider path which ignored the expected distance value.
+ private readonly Cached fullPathCache = new Cached();
+ private bool isAdjustingLength;
+
public SliderSelectionBlueprint(Slider slider)
: base(slider)
{
@@ -72,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
BodyPiece = new SliderBodyPiece(),
HeadOverlay = CreateCircleOverlay(HitObject, SliderPosition.Start),
- TailPiece = CreateTailPiece(HitObject, SliderPosition.End),
+ TailPiece = CreateCircleOverlay(HitObject, SliderPosition.End),
};
}
@@ -81,6 +86,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
base.LoadComplete();
controlPoints.BindTo(HitObject.Path.ControlPoints);
+ controlPoints.CollectionChanged += (_, _) => fullPathCache.Invalidate();
pathVersion.BindTo(HitObject.Path.Version);
pathVersion.BindValueChanged(_ => editorBeatmap?.Update(HitObject));
@@ -135,6 +141,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
base.OnDeselected();
+ if (isAdjustingLength)
+ endAdjustLength();
+
updateVisualDefinition();
BodyPiece.RecyclePath();
}
@@ -164,6 +173,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
protected override bool OnMouseDown(MouseDownEvent e)
{
+ if (isAdjustingLength)
+ {
+ endAdjustLength();
+ return true;
+ }
+
switch (e.Button)
{
case MouseButton.Right:
@@ -171,6 +186,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
return false; // Allow right click to be handled by context menu
case MouseButton.Left:
+
// If there's more than two objects selected, ctrl+click should deselect
if (e.ControlPressed && IsSelected && selectedObjects.Count < 2)
{
@@ -186,6 +202,106 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
return false;
}
+ private void endAdjustLength()
+ {
+ trimExcessControlPoints(HitObject.Path);
+ isAdjustingLength = false;
+ changeHandler?.EndChange();
+ }
+
+ protected override bool OnMouseMove(MouseMoveEvent e)
+ {
+ if (!isAdjustingLength)
+ return base.OnMouseMove(e);
+
+ double oldDistance = HitObject.Path.Distance;
+ double proposedDistance = findClosestPathDistance(e);
+
+ proposedDistance = MathHelper.Clamp(proposedDistance, 0, HitObject.Path.CalculatedDistance);
+ proposedDistance = MathHelper.Clamp(proposedDistance,
+ 0.1 * oldDistance / HitObject.SliderVelocityMultiplier,
+ 10 * oldDistance / HitObject.SliderVelocityMultiplier);
+
+ if (Precision.AlmostEquals(proposedDistance, oldDistance))
+ return false;
+
+ HitObject.SliderVelocityMultiplier *= proposedDistance / oldDistance;
+ HitObject.Path.ExpectedDistance.Value = proposedDistance;
+ editorBeatmap?.Update(HitObject);
+
+ return false;
+ }
+
+ ///
+ /// Trims control points from the end of the slider path which are not required to reach the expected end of the slider.
+ ///
+ /// The slider path to trim control points of.
+ private void trimExcessControlPoints(SliderPath sliderPath)
+ {
+ if (!sliderPath.ExpectedDistance.Value.HasValue)
+ return;
+
+ double[] segmentEnds = sliderPath.GetSegmentEnds().ToArray();
+ int segmentIndex = 0;
+
+ for (int i = 1; i < sliderPath.ControlPoints.Count - 1; i++)
+ {
+ if (!sliderPath.ControlPoints[i].Type.HasValue) continue;
+
+ if (Precision.AlmostBigger(segmentEnds[segmentIndex], 1, 1E-3))
+ {
+ sliderPath.ControlPoints.RemoveRange(i + 1, sliderPath.ControlPoints.Count - i - 1);
+ sliderPath.ControlPoints[^1].Type = null;
+ break;
+ }
+
+ segmentIndex++;
+ }
+ }
+
+ ///
+ /// Finds the expected distance value for which the slider end is closest to the mouse position.
+ ///
+ private double findClosestPathDistance(MouseMoveEvent e)
+ {
+ const double step1 = 10;
+ const double step2 = 0.1;
+
+ var desiredPosition = e.MousePosition - HitObject.Position;
+
+ if (!fullPathCache.IsValid)
+ fullPathCache.Value = new SliderPath(HitObject.Path.ControlPoints.ToArray());
+
+ // Do a linear search to find the closest point on the path to the mouse position.
+ double bestValue = 0;
+ double minDistance = double.MaxValue;
+
+ for (double d = 0; d <= fullPathCache.Value.CalculatedDistance; d += step1)
+ {
+ double t = d / fullPathCache.Value.CalculatedDistance;
+ float dist = Vector2.Distance(fullPathCache.Value.PositionAt(t), desiredPosition);
+
+ if (dist >= minDistance) continue;
+
+ minDistance = dist;
+ bestValue = d;
+ }
+
+ // Do another linear search to fine-tune the result.
+ for (double d = bestValue - step1; d <= bestValue + step1; d += step2)
+ {
+ double t = d / fullPathCache.Value.CalculatedDistance;
+ float dist = Vector2.Distance(fullPathCache.Value.PositionAt(t), desiredPosition);
+
+ if (dist >= minDistance) continue;
+
+ minDistance = dist;
+ bestValue = d;
+ }
+
+ return bestValue;
+ }
+
[CanBeNull]
private PathControlPoint placementControlPoint;
@@ -409,9 +525,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
addControlPoint(rightClickPosition);
changeHandler?.EndChange();
}),
- new OsuMenuItem("Adjust distance", MenuItemType.Standard, () =>
+ new OsuMenuItem("Adjust length", MenuItemType.Standard, () =>
{
- TailPiece.IsDraggable = true;
+ isAdjustingLength = true;
+ changeHandler?.BeginChange();
}),
new OsuMenuItem("Convert to stream", MenuItemType.Destructive, convertToStream),
};
@@ -427,6 +544,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
{
+ if (isAdjustingLength)
+ return true;
+
if (BodyPiece.ReceivePositionalInputAt(screenSpacePos))
return true;
@@ -443,6 +563,5 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
}
protected virtual SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new SliderCircleOverlay(slider, position);
- protected virtual SliderTailPiece CreateTailPiece(Slider slider, SliderPosition position) => new SliderTailPiece(slider, position);
}
}
From 956bdbca50afdc24e97bb6834adca8cc839c993e Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Thu, 20 Jun 2024 00:17:16 +0200
Subject: [PATCH 0140/1274] fix tests
---
.../TestSceneSliderControlPointPiece.cs | 13 +---
.../TestSceneSliderSelectionBlueprint.cs | 60 +++++--------------
.../Sliders/SliderSelectionBlueprint.cs | 4 +-
3 files changed, 17 insertions(+), 60 deletions(-)
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs
index 1a7430704d..99ced30ffe 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs
@@ -353,7 +353,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{
public new SliderBodyPiece BodyPiece => base.BodyPiece;
public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay;
- public new TestSliderTailPiece TailPiece => (TestSliderTailPiece)base.TailPiece;
+ public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay;
public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
public TestSliderBlueprint(Slider slider)
@@ -362,7 +362,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
}
protected override SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new TestSliderCircleOverlay(slider, position);
- protected override SliderTailPiece CreateTailPiece(Slider slider, SliderPosition position) => new TestSliderTailPiece(slider, position);
}
private partial class TestSliderCircleOverlay : SliderCircleOverlay
@@ -374,15 +373,5 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{
}
}
-
- private partial class TestSliderTailPiece : SliderTailPiece
- {
- public new HitCirclePiece CirclePiece => base.CirclePiece;
-
- public TestSliderTailPiece(Slider slider, SliderPosition position)
- : base(slider, position)
- {
- }
- }
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
index 3faf181465..812b34dfe2 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
@@ -3,6 +3,7 @@
#nullable disable
+using System.Linq;
using NUnit.Framework;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
@@ -164,51 +165,29 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
}
[Test]
- public void TestDragSliderTail()
+ public void TestAdjustDistance()
{
- AddStep("move mouse to slider tail", () =>
- {
- Vector2 position = slider.EndPosition + new Vector2(10, 0);
- InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
- });
- AddStep("shift + drag", () =>
- {
- InputManager.PressKey(Key.ShiftLeft);
- InputManager.PressButton(MouseButton.Left);
- });
+ AddStep("start adjust length",
+ () => blueprint.ContextMenuItems.Single(o => o.Text.Value == "Adjust length").Action.Value());
moveMouseToControlPoint(1);
- AddStep("release", () =>
- {
- InputManager.ReleaseButton(MouseButton.Left);
- InputManager.ReleaseKey(Key.ShiftLeft);
- });
-
+ AddStep("end adjust length", () => InputManager.Click(MouseButton.Right));
AddAssert("expected distance halved",
() => Precision.AlmostEquals(slider.Path.Distance, 172.2, 0.1));
- AddStep("move mouse to slider tail", () =>
- {
- Vector2 position = slider.EndPosition + new Vector2(10, 0);
- InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
- });
- AddStep("shift + drag", () =>
- {
- InputManager.PressKey(Key.ShiftLeft);
- InputManager.PressButton(MouseButton.Left);
- });
+ AddStep("start adjust length",
+ () => blueprint.ContextMenuItems.Single(o => o.Text.Value == "Adjust length").Action.Value());
AddStep("move mouse beyond last control point", () =>
{
Vector2 position = slider.Position + slider.Path.ControlPoints[2].Position + new Vector2(50, 0);
InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
});
- AddStep("release", () =>
- {
- InputManager.ReleaseButton(MouseButton.Left);
- InputManager.ReleaseKey(Key.ShiftLeft);
- });
-
+ AddStep("end adjust length", () => InputManager.Click(MouseButton.Right));
AddAssert("expected distance is calculated distance",
() => Precision.AlmostEquals(slider.Path.Distance, slider.Path.CalculatedDistance, 0.1));
+
+ moveMouseToControlPoint(1);
+ AddAssert("expected distance is unchanged",
+ () => Precision.AlmostEquals(slider.Path.Distance, slider.Path.CalculatedDistance, 0.1));
}
private void moveHitObject()
@@ -227,7 +206,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
() => Precision.AlmostEquals(blueprint.HeadOverlay.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.HeadCircle.ScreenSpaceDrawQuad.Centre));
AddAssert("tail positioned correctly",
- () => Precision.AlmostEquals(blueprint.TailPiece.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre));
+ () => Precision.AlmostEquals(blueprint.TailOverlay.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre));
}
private void moveMouseToControlPoint(int index)
@@ -246,7 +225,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{
public new SliderBodyPiece BodyPiece => base.BodyPiece;
public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay;
- public new TestSliderTailPiece TailPiece => (TestSliderTailPiece)base.TailPiece;
+ public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay;
public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
public TestSliderBlueprint(Slider slider)
@@ -255,7 +234,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
}
protected override SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new TestSliderCircleOverlay(slider, position);
- protected override SliderTailPiece CreateTailPiece(Slider slider, SliderPosition position) => new TestSliderTailPiece(slider, position);
}
private partial class TestSliderCircleOverlay : SliderCircleOverlay
@@ -267,15 +245,5 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{
}
}
-
- private partial class TestSliderTailPiece : SliderTailPiece
- {
- public new HitCirclePiece CirclePiece => base.CirclePiece;
-
- public TestSliderTailPiece(Slider slider, SliderPosition position)
- : base(slider, position)
- {
- }
- }
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
index f59ef298a7..eb269ba680 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
@@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
protected SliderBodyPiece BodyPiece { get; private set; }
protected SliderCircleOverlay HeadOverlay { get; private set; }
- protected SliderCircleOverlay TailPiece { get; private set; }
+ protected SliderCircleOverlay TailOverlay { get; private set; }
[CanBeNull]
protected PathControlPointVisualiser ControlPointVisualiser { get; private set; }
@@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
BodyPiece = new SliderBodyPiece(),
HeadOverlay = CreateCircleOverlay(HitObject, SliderPosition.Start),
- TailPiece = CreateCircleOverlay(HitObject, SliderPosition.End),
+ TailOverlay = CreateCircleOverlay(HitObject, SliderPosition.End),
};
}
From 225b309ba357b177a6259a447afea8e18e261ffb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bart=C5=82omiej=20Dach?=
Date: Thu, 20 Jun 2024 16:27:07 +0200
Subject: [PATCH 0141/1274] Reimplement stable polygon tool
Addresses https://github.com/ppy/osu/discussions/19970.
While yes, https://github.com/ppy/osu/pull/26303 is also a thing,
in discussing with users I don't think that grids are going to be able
to deprecate this feature.
Logic transcribed verbatim from stable.
---
.../Edit/GenerateToolboxGroup.cs | 54 +++++
.../Edit/OsuHitObjectComposer.cs | 3 +-
.../Edit/PolygonGenerationPopover.cs | 193 ++++++++++++++++++
3 files changed, 249 insertions(+), 1 deletion(-)
create mode 100644 osu.Game.Rulesets.Osu/Edit/GenerateToolboxGroup.cs
create mode 100644 osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs
diff --git a/osu.Game.Rulesets.Osu/Edit/GenerateToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/GenerateToolboxGroup.cs
new file mode 100644
index 0000000000..4e188a2b86
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Edit/GenerateToolboxGroup.cs
@@ -0,0 +1,54 @@
+// 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.Graphics.Sprites;
+using osu.Framework.Input.Events;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Screens.Edit.Components;
+using osuTK;
+using osuTK.Input;
+
+namespace osu.Game.Rulesets.Osu.Edit
+{
+ public partial class GenerateToolboxGroup : EditorToolboxGroup
+ {
+ private readonly EditorToolButton polygonButton;
+
+ public GenerateToolboxGroup()
+ : base("Generate")
+ {
+ Child = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Spacing = new Vector2(5),
+ Children = new Drawable[]
+ {
+ polygonButton = new EditorToolButton("Polygon",
+ () => new SpriteIcon { Icon = FontAwesome.Solid.Spinner },
+ () => new PolygonGenerationPopover()),
+ }
+ };
+ }
+
+ protected override bool OnKeyDown(KeyDownEvent e)
+ {
+ if (e.Repeat) return false;
+
+ switch (e.Key)
+ {
+ case Key.D:
+ if (!e.ControlPressed || !e.ShiftPressed)
+ return false;
+
+ polygonButton.TriggerClick();
+ return true;
+
+ default:
+ return false;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index 41f6b41f82..fab5298554 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Edit
private Bindable placementObject;
[Cached(typeof(IDistanceSnapProvider))]
- protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider();
+ public readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider();
[Cached]
protected readonly OsuGridToolboxGroup OsuGridToolboxGroup = new OsuGridToolboxGroup();
@@ -109,6 +109,7 @@ namespace osu.Game.Rulesets.Osu.Edit
RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler,
ScaleHandler = (OsuSelectionScaleHandler)BlueprintContainer.SelectionHandler.ScaleHandler,
},
+ new GenerateToolboxGroup(),
FreehandlSliderToolboxGroup
}
);
diff --git a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs
new file mode 100644
index 0000000000..6325de5851
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs
@@ -0,0 +1,193 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Graphics.UserInterfaceV2;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Legacy;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.UI;
+using osu.Game.Screens.Edit;
+using osu.Game.Screens.Edit.Compose.Components;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Edit
+{
+ public partial class PolygonGenerationPopover : OsuPopover
+ {
+ private SliderWithTextBoxInput distanceSnapInput = null!;
+ private SliderWithTextBoxInput offsetAngleInput = null!;
+ private SliderWithTextBoxInput repeatCountInput = null!;
+ private SliderWithTextBoxInput pointInput = null!;
+ private RoundedButton commitButton = null!;
+
+ private readonly List insertedCircles = new List();
+ private bool began;
+ private bool committed;
+
+ [Resolved]
+ private IBeatSnapProvider beatSnapProvider { get; set; } = null!;
+
+ [Resolved]
+ private EditorClock editorClock { get; set; } = null!;
+
+ [Resolved]
+ private EditorBeatmap editorBeatmap { get; set; } = null!;
+
+ [Resolved]
+ private IEditorChangeHandler? changeHandler { get; set; }
+
+ [Resolved]
+ private HitObjectComposer composer { get; set; } = null!;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Child = new FillFlowContainer
+ {
+ Width = 220,
+ AutoSizeAxes = Axes.Y,
+ Spacing = new Vector2(20),
+ Children = new Drawable[]
+ {
+ distanceSnapInput = new SliderWithTextBoxInput("Distance snap:")
+ {
+ Current = new BindableNumber(1)
+ {
+ MinValue = 0.1,
+ MaxValue = 6,
+ Precision = 0.1,
+ Value = ((OsuHitObjectComposer)composer).DistanceSnapProvider.DistanceSpacingMultiplier.Value,
+ },
+ Instantaneous = true
+ },
+ offsetAngleInput = new SliderWithTextBoxInput("Offset angle:")
+ {
+ Current = new BindableNumber
+ {
+ MinValue = 0,
+ MaxValue = 180,
+ Precision = 1
+ },
+ Instantaneous = true
+ },
+ repeatCountInput = new SliderWithTextBoxInput("Repeats:")
+ {
+ Current = new BindableNumber(1)
+ {
+ MinValue = 1,
+ MaxValue = 10,
+ Precision = 1
+ },
+ Instantaneous = true
+ },
+ pointInput = new SliderWithTextBoxInput("Vertices:")
+ {
+ Current = new BindableNumber(3)
+ {
+ MinValue = 3,
+ MaxValue = 10,
+ Precision = 1,
+ },
+ Instantaneous = true
+ },
+ commitButton = new RoundedButton
+ {
+ RelativeSizeAxes = Axes.X,
+ Text = "Create",
+ Action = commit
+ }
+ }
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ changeHandler?.BeginChange();
+ began = true;
+
+ distanceSnapInput.Current.BindValueChanged(_ => tryCreatePolygon());
+ offsetAngleInput.Current.BindValueChanged(_ => tryCreatePolygon());
+ repeatCountInput.Current.BindValueChanged(_ => tryCreatePolygon());
+ pointInput.Current.BindValueChanged(_ => tryCreatePolygon());
+ tryCreatePolygon();
+ }
+
+ private void tryCreatePolygon()
+ {
+ double startTime = beatSnapProvider.SnapTime(editorClock.CurrentTime);
+ TimingControlPoint timingPoint = editorBeatmap.ControlPointInfo.TimingPointAt(startTime);
+ double timeSpacing = timingPoint.BeatLength / editorBeatmap.BeatDivisor;
+ IHasSliderVelocity lastWithSliderVelocity = editorBeatmap.HitObjects.Where(ho => ho.GetEndTime() <= startTime).OfType().LastOrDefault() ?? new Slider();
+ double velocity = OsuHitObject.BASE_SCORING_DISTANCE * editorBeatmap.Difficulty.SliderMultiplier
+ / LegacyRulesetExtensions.GetPrecisionAdjustedBeatLength(lastWithSliderVelocity, timingPoint, OsuRuleset.SHORT_NAME);
+ double length = distanceSnapInput.Current.Value * velocity * timeSpacing;
+ float polygonRadius = (float)(length / (2 * Math.Sin(double.Pi / pointInput.Current.Value)));
+
+ editorBeatmap.RemoveRange(insertedCircles);
+ insertedCircles.Clear();
+
+ var selectionHandler = (EditorSelectionHandler)composer.BlueprintContainer.SelectionHandler;
+ bool first = true;
+
+ for (int i = 1; i <= pointInput.Current.Value * repeatCountInput.Current.Value; ++i)
+ {
+ float angle = float.DegreesToRadians(offsetAngleInput.Current.Value) + i * (2 * float.Pi / pointInput.Current.Value);
+ var position = OsuPlayfield.BASE_SIZE / 2 + new Vector2(polygonRadius * float.Cos(angle), polygonRadius * float.Sin(angle));
+
+ var circle = new HitCircle
+ {
+ Position = position,
+ StartTime = startTime,
+ NewCombo = first && selectionHandler.SelectionNewComboState.Value == TernaryState.True,
+ };
+ // TODO: probably ensure samples also follow current ternary status (not trivial)
+ circle.Samples.Add(circle.CreateHitSampleInfo());
+
+ if (position.X < 0 || position.Y < 0 || position.X > OsuPlayfield.BASE_SIZE.X || position.Y > OsuPlayfield.BASE_SIZE.Y)
+ {
+ commitButton.Enabled.Value = false;
+ return;
+ }
+
+ insertedCircles.Add(circle);
+ startTime = beatSnapProvider.SnapTime(startTime + timeSpacing);
+
+ first = false;
+ }
+
+ editorBeatmap.AddRange(insertedCircles);
+ commitButton.Enabled.Value = true;
+ }
+
+ private void commit()
+ {
+ changeHandler?.EndChange();
+ committed = true;
+ Hide();
+ }
+
+ protected override void PopOut()
+ {
+ base.PopOut();
+
+ if (began && !committed)
+ {
+ editorBeatmap.RemoveRange(insertedCircles);
+ changeHandler?.EndChange();
+ }
+ }
+ }
+}
From 2fb22f1febef5ad5ab120c24875e69b798ed984e Mon Sep 17 00:00:00 2001
From: Nathen
Date: Sun, 23 Jun 2024 19:17:19 -0400
Subject: [PATCH 0142/1274] 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 fbc99894279ab1da456cef2ceebc0615eefe24b4 Mon Sep 17 00:00:00 2001
From: Salman Ahmed
Date: Tue, 25 Jun 2024 01:01:26 +0300
Subject: [PATCH 0143/1274] Simplify default layout initialisation
---
osu.Game/Skinning/ArgonSkinTransformer.cs | 41 ++++++++--------------
osu.Game/Skinning/LegacySkinTransformer.cs | 37 +++++--------------
2 files changed, 23 insertions(+), 55 deletions(-)
diff --git a/osu.Game/Skinning/ArgonSkinTransformer.cs b/osu.Game/Skinning/ArgonSkinTransformer.cs
index 387a7a9c0b..8ca8f79b41 100644
--- a/osu.Game/Skinning/ArgonSkinTransformer.cs
+++ b/osu.Game/Skinning/ArgonSkinTransformer.cs
@@ -1,8 +1,8 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System.Linq;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Game.Screens.Play.HUD;
using osuTK;
@@ -17,34 +17,21 @@ namespace osu.Game.Skinning
public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
{
- switch (lookup)
+ if (lookup is SkinComponentsContainerLookup containerLookup
+ && containerLookup.Target == SkinComponentsContainerLookup.TargetArea.MainHUDComponents
+ && containerLookup.Ruleset != null)
{
- case SkinComponentsContainerLookup containerLookup:
- switch (containerLookup.Target)
+ return base.GetDrawableComponent(lookup) ?? new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = new ArgonComboCounter
{
- case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null:
- var rulesetHUDComponents = Skin.GetDrawableComponent(lookup);
-
- rulesetHUDComponents ??= new DefaultSkinComponentsContainer(container =>
- {
- var combo = container.OfType().FirstOrDefault();
-
- if (combo != null)
- {
- combo.Anchor = Anchor.BottomLeft;
- combo.Origin = Anchor.BottomLeft;
- combo.Position = new Vector2(36, -66);
- combo.Scale = new Vector2(1.3f);
- }
- })
- {
- new ArgonComboCounter(),
- };
-
- return rulesetHUDComponents;
- }
-
- break;
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ Position = new Vector2(36, -66),
+ Scale = new Vector2(1.3f),
+ },
+ };
}
return base.GetDrawableComponent(lookup);
diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs
index 3ea316c0c7..dbfa52de84 100644
--- a/osu.Game/Skinning/LegacySkinTransformer.cs
+++ b/osu.Game/Skinning/LegacySkinTransformer.cs
@@ -1,12 +1,11 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System.Linq;
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Game.Audio;
using osu.Game.Rulesets.Objects.Legacy;
-using osuTK;
using static osu.Game.Skinning.SkinConfiguration;
namespace osu.Game.Skinning
@@ -25,33 +24,15 @@ namespace osu.Game.Skinning
public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
{
- switch (lookup)
+ if (lookup is SkinComponentsContainerLookup containerLookup
+ && containerLookup.Target == SkinComponentsContainerLookup.TargetArea.MainHUDComponents
+ && containerLookup.Ruleset != null)
{
- case SkinComponentsContainerLookup containerLookup:
- switch (containerLookup.Target)
- {
- case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null:
- var rulesetHUDComponents = base.GetDrawableComponent(lookup);
-
- rulesetHUDComponents ??= new DefaultSkinComponentsContainer(container =>
- {
- var combo = container.OfType().FirstOrDefault();
-
- if (combo != null)
- {
- combo.Anchor = Anchor.BottomLeft;
- combo.Origin = Anchor.BottomLeft;
- combo.Scale = new Vector2(1.28f);
- }
- })
- {
- new LegacyComboCounter()
- };
-
- return rulesetHUDComponents;
- }
-
- break;
+ return base.GetDrawableComponent(lookup) ?? new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = new LegacyComboCounter(),
+ };
}
return base.GetDrawableComponent(lookup);
From e8de293be5f813731f97ab8132c569c914dca59a Mon Sep 17 00:00:00 2001
From: Salman Ahmed
Date: Tue, 25 Jun 2024 01:01:32 +0300
Subject: [PATCH 0144/1274] Remove pointless assert
---
.../Skinning/Legacy/CatchLegacySkinTransformer.cs | 2 --
1 file changed, 2 deletions(-)
diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
index 462fd5ab64..17218b459a 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
@@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System.Diagnostics;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Skinning;
@@ -33,7 +32,6 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
switch (containerLookup.Target)
{
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null:
- Debug.Assert(containerLookup.Ruleset.ShortName == CatchRuleset.SHORT_NAME);
// todo: remove CatchSkinComponents.CatchComboCounter and refactor LegacyCatchComboCounter to be added here instead.
return Skin.GetDrawableComponent(lookup);
}
From 78e0126f16e90aca5459b288e42443bbf2b3387e Mon Sep 17 00:00:00 2001
From: Salman Ahmed
Date: Tue, 25 Jun 2024 04:24:58 +0300
Subject: [PATCH 0145/1274] Migrate combo counter layouts in custom skins to
correct target-ruleset pairs
---
osu.Game/Database/RealmAccess.cs | 3 ++-
osu.Game/Skinning/ArgonSkin.cs | 19 +++++++++++++++++++
osu.Game/Skinning/LegacySkin.cs | 19 +++++++++++++++++++
osu.Game/Skinning/SkinImporter.cs | 2 ++
osu.Game/Skinning/SkinInfo.cs | 15 +++++++++++++++
5 files changed, 57 insertions(+), 1 deletion(-)
diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs
index 1ece81be50..606bc5e10c 100644
--- a/osu.Game/Database/RealmAccess.cs
+++ b/osu.Game/Database/RealmAccess.cs
@@ -93,8 +93,9 @@ namespace osu.Game.Database
/// 39 2023-12-19 Migrate any EndTimeObjectCount and TotalObjectCount values of 0 to -1 to better identify non-calculated values.
/// 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-06-25 Add SkinInfo.LayoutVersion to allow performing migrations of components on structural changes.
///
- private const int schema_version = 41;
+ private const int schema_version = 42;
///
/// Lock object which is held during sections, blocking realm retrieval during blocking periods.
diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs
index 707281db31..743ce38810 100644
--- a/osu.Game/Skinning/ArgonSkin.cs
+++ b/osu.Game/Skinning/ArgonSkin.cs
@@ -12,6 +12,7 @@ using osu.Game.Audio;
using osu.Game.Beatmaps.Formats;
using osu.Game.Extensions;
using osu.Game.IO;
+using osu.Game.Rulesets;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.HUD.HitErrorMeters;
@@ -69,6 +70,24 @@ namespace osu.Game.Skinning
// Purple
new Color4(92, 0, 241, 255),
};
+
+ if (skin.LayoutVersion < 20240625
+ && LayoutInfos.TryGetValue(SkinComponentsContainerLookup.TargetArea.MainHUDComponents, out var hudLayout)
+ && hudLayout.TryGetDrawableInfo(null, out var hudComponents))
+ {
+ var comboCounters = hudComponents.Where(h => h.Type.Name == nameof(ArgonComboCounter)).ToArray();
+
+ if (comboCounters.Any())
+ {
+ hudLayout.Update(null, hudComponents.Except(comboCounters).ToArray());
+
+ resources.RealmAccess.Run(r =>
+ {
+ foreach (var ruleset in r.All())
+ hudLayout.Update(ruleset, comboCounters);
+ });
+ }
+ }
}
public override Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => Textures?.Get(componentName, wrapModeS, wrapModeT);
diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs
index b71b626b4e..c3e619431e 100644
--- a/osu.Game/Skinning/LegacySkin.cs
+++ b/osu.Game/Skinning/LegacySkin.cs
@@ -19,6 +19,7 @@ using osu.Game.Audio;
using osu.Game.Beatmaps.Formats;
using osu.Game.Extensions;
using osu.Game.IO;
+using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play.HUD;
@@ -56,6 +57,24 @@ namespace osu.Game.Skinning
protected LegacySkin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore? fallbackStore, string configurationFilename = @"skin.ini")
: base(skin, resources, fallbackStore, configurationFilename)
{
+ if (resources != null
+ && skin.LayoutVersion < 20240625
+ && LayoutInfos.TryGetValue(SkinComponentsContainerLookup.TargetArea.MainHUDComponents, out var hudLayout)
+ && hudLayout.TryGetDrawableInfo(null, out var hudComponents))
+ {
+ var comboCounters = hudComponents.Where(h => h.Type.Name == nameof(LegacyComboCounter)).ToArray();
+
+ if (comboCounters.Any())
+ {
+ hudLayout.Update(null, hudComponents.Except(comboCounters).ToArray());
+
+ resources.RealmAccess.Run(r =>
+ {
+ foreach (var ruleset in r.All())
+ hudLayout.Update(ruleset, comboCounters);
+ });
+ }
+ }
}
protected override IResourceStore CreateTextureLoaderStore(IStorageResourceProvider resources, IResourceStore storage)
diff --git a/osu.Game/Skinning/SkinImporter.cs b/osu.Game/Skinning/SkinImporter.cs
index 59c7f0ba26..714427f40d 100644
--- a/osu.Game/Skinning/SkinImporter.cs
+++ b/osu.Game/Skinning/SkinImporter.cs
@@ -223,6 +223,8 @@ namespace osu.Game.Skinning
}
}
+ s.LayoutVersion = SkinInfo.LATEST_LAYOUT_VERSION;
+
string newHash = ComputeHash(s);
hadChanges = newHash != s.Hash;
diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs
index 9763d3b57e..a3d5771b5e 100644
--- a/osu.Game/Skinning/SkinInfo.cs
+++ b/osu.Game/Skinning/SkinInfo.cs
@@ -39,6 +39,21 @@ namespace osu.Game.Skinning
public bool Protected { get; set; }
+ ///
+ /// The latest version in YYYYMMDD format for skin layout migrations.
+ ///
+ ///
+ ///
+ /// - 20240625: Moves combo counters from ruleset-agnostic to ruleset-specific HUD targets.
+ ///
+ ///
+ public const int LATEST_LAYOUT_VERSION = 20240625;
+
+ ///
+ /// A version in YYYYMMDD format for applying skin layout migrations.
+ ///
+ public int LayoutVersion { get; set; }
+
public virtual Skin CreateInstance(IStorageResourceProvider resources)
{
var type = string.IsNullOrEmpty(InstantiationInfo)
From fc2202e0cc9cdf1191675272607aa7e51f2037e4 Mon Sep 17 00:00:00 2001
From: Salman Ahmed
Date: Tue, 25 Jun 2024 05:54:56 +0300
Subject: [PATCH 0146/1274] Fix migration logic overwriting existing components
in ruleset targets
---
osu.Game/Skinning/ArgonSkin.cs | 6 +++++-
osu.Game/Skinning/LegacySkin.cs | 6 +++++-
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs
index 743ce38810..4cd54c06f0 100644
--- a/osu.Game/Skinning/ArgonSkin.cs
+++ b/osu.Game/Skinning/ArgonSkin.cs
@@ -84,7 +84,11 @@ namespace osu.Game.Skinning
resources.RealmAccess.Run(r =>
{
foreach (var ruleset in r.All())
- hudLayout.Update(ruleset, comboCounters);
+ {
+ hudLayout.Update(ruleset, hudLayout.TryGetDrawableInfo(ruleset, out var rulesetComponents)
+ ? rulesetComponents.Concat(comboCounters).ToArray()
+ : comboCounters);
+ }
});
}
}
diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs
index c3e619431e..f148bad96e 100644
--- a/osu.Game/Skinning/LegacySkin.cs
+++ b/osu.Game/Skinning/LegacySkin.cs
@@ -71,7 +71,11 @@ namespace osu.Game.Skinning
resources.RealmAccess.Run(r =>
{
foreach (var ruleset in r.All())
- hudLayout.Update(ruleset, comboCounters);
+ {
+ hudLayout.Update(ruleset, hudLayout.TryGetDrawableInfo(ruleset, out var rulesetComponents)
+ ? rulesetComponents.Concat(comboCounters).ToArray()
+ : comboCounters);
+ }
});
}
}
From dc1fb4fdca462a8d8123e695177aa61f951a78da Mon Sep 17 00:00:00 2001
From: Salman Ahmed
Date: Tue, 25 Jun 2024 05:54:59 +0300
Subject: [PATCH 0147/1274] Add test coverage
---
.../Visual/Gameplay/TestSceneSkinEditor.cs | 84 +++++++++++++++++++
1 file changed, 84 insertions(+)
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs
index 3c97700fb0..2470c320cc 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs
@@ -13,12 +13,14 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input;
using osu.Framework.Testing;
+using osu.Game.Database;
using osu.Game.Overlays;
using osu.Game.Overlays.Settings;
using osu.Game.Overlays.SkinEditor;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit;
+using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.HUD.HitErrorMeters;
using osu.Game.Skinning;
using osu.Game.Skinning.Components;
@@ -39,6 +41,9 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached]
public readonly EditorClipboard Clipboard = new EditorClipboard();
+ [Resolved]
+ private SkinManager skins { get; set; } = null!;
+
private SkinComponentsContainer targetContainer => Player.ChildrenOfType().First();
[SetUpSteps]
@@ -46,6 +51,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
base.SetUpSteps();
+ AddStep("reset skin", () => skins.CurrentSkinInfo.SetDefault());
AddUntilStep("wait for hud load", () => targetContainer.ComponentsLoaded);
AddStep("reload skin editor", () =>
@@ -369,6 +375,84 @@ namespace osu.Game.Tests.Visual.Gameplay
() => Is.EqualTo(3));
}
+ private SkinComponentsContainer globalHUDTarget => Player.ChildrenOfType()
+ .Single(c => c.Lookup.Target == SkinComponentsContainerLookup.TargetArea.MainHUDComponents && c.Lookup.Ruleset == null);
+
+ private SkinComponentsContainer rulesetHUDTarget => Player.ChildrenOfType()
+ .Single(c => c.Lookup.Target == SkinComponentsContainerLookup.TargetArea.MainHUDComponents && c.Lookup.Ruleset != null);
+
+ [Test]
+ public void TestMigrationArgon()
+ {
+ AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded);
+ AddStep("add combo to global hud target", () =>
+ {
+ globalHUDTarget.Add(new ArgonComboCounter
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ });
+ });
+
+ Live modifiedSkin = null!;
+
+ AddStep("select another skin", () =>
+ {
+ modifiedSkin = skins.CurrentSkinInfo.Value;
+ skins.CurrentSkinInfo.SetDefault();
+ });
+ AddStep("modify version", () => modifiedSkin.PerformWrite(s => s.LayoutVersion = 0));
+ AddStep("select skin again", () => skins.CurrentSkinInfo.Value = modifiedSkin);
+ AddAssert("global hud target does not contain combo", () => !globalHUDTarget.Components.Any(c => c is ArgonComboCounter));
+ AddAssert("ruleset hud target contains both combos", () =>
+ {
+ var target = rulesetHUDTarget;
+
+ return target.Components.Count == 2 &&
+ target.Components[0] is ArgonComboCounter one && one.Anchor == Anchor.BottomLeft && one.Origin == Anchor.BottomLeft &&
+ target.Components[1] is ArgonComboCounter two && two.Anchor == Anchor.Centre && two.Origin == Anchor.Centre;
+ });
+ AddStep("save skin", () => skinEditor.Save());
+ AddAssert("version updated", () => modifiedSkin.PerformRead(s => s.LayoutVersion) == SkinInfo.LATEST_LAYOUT_VERSION);
+ }
+
+ [Test]
+ public void TestMigrationLegacy()
+ {
+ AddStep("select legacy skin", () => skins.CurrentSkinInfo.Value = skins.DefaultClassicSkin.SkinInfo);
+
+ AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded);
+ AddStep("add combo to global hud target", () =>
+ {
+ globalHUDTarget.Add(new LegacyComboCounter
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ });
+ });
+
+ Live modifiedSkin = null!;
+
+ AddStep("select another skin", () =>
+ {
+ modifiedSkin = skins.CurrentSkinInfo.Value;
+ skins.CurrentSkinInfo.SetDefault();
+ });
+ AddStep("modify version", () => modifiedSkin.PerformWrite(s => s.LayoutVersion = 0));
+ AddStep("select skin again", () => skins.CurrentSkinInfo.Value = modifiedSkin);
+ AddAssert("global hud target does not contain combo", () => !globalHUDTarget.Components.Any(c => c is LegacyComboCounter));
+ AddAssert("ruleset hud target contains both combos", () =>
+ {
+ var target = rulesetHUDTarget;
+
+ return target.Components.Count == 2 &&
+ target.Components[0] is LegacyComboCounter one && one.Anchor == Anchor.BottomLeft && one.Origin == Anchor.BottomLeft &&
+ target.Components[1] is LegacyComboCounter two && two.Anchor == Anchor.Centre && two.Origin == Anchor.Centre;
+ });
+ AddStep("save skin", () => skinEditor.Save());
+ AddAssert("version updated", () => modifiedSkin.PerformRead(s => s.LayoutVersion) == SkinInfo.LATEST_LAYOUT_VERSION);
+ }
+
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
private partial class TestSkinEditorChangeHandler : SkinEditorChangeHandler
From 0ee89183cc28b3cd39e14c30a4ec83a353a9db9d Mon Sep 17 00:00:00 2001
From: smallketchup82
Date: Wed, 26 Jun 2024 15:25:41 -0400
Subject: [PATCH 0148/1274] initial implementation
---
osu.Desktop/OsuGameDesktop.cs | 34 +++-----
osu.Desktop/Program.cs | 39 +++------
...lUpdateManager.cs => VeloUpdateManager.cs} | 86 ++++---------------
osu.Desktop/osu.Desktop.csproj | 2 +-
4 files changed, 38 insertions(+), 123 deletions(-)
rename osu.Desktop/Updater/{SquirrelUpdateManager.cs => VeloUpdateManager.cs} (52%)
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index e8783c997a..245d00dc53 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -19,7 +19,6 @@ using osu.Desktop.Windows;
using osu.Framework.Allocation;
using osu.Game.IO;
using osu.Game.IPC;
-using osu.Game.Online.Multiplayer;
using osu.Game.Performance;
using osu.Game.Utils;
using SDL;
@@ -103,35 +102,22 @@ namespace osu.Desktop
if (!string.IsNullOrEmpty(packageManaged))
return new NoActionUpdateManager();
- switch (RuntimeInfo.OS)
- {
- case RuntimeInfo.Platform.Windows:
- Debug.Assert(OperatingSystem.IsWindows());
-
- return new SquirrelUpdateManager();
-
- default:
- return new SimpleUpdateManager();
- }
+ return new VeloUpdateManager();
}
public override bool RestartAppWhenExited()
{
- switch (RuntimeInfo.OS)
+ try
{
- case RuntimeInfo.Platform.Windows:
- Debug.Assert(OperatingSystem.IsWindows());
-
- // Of note, this is an async method in squirrel that adds an arbitrary delay before returning
- // likely to ensure the external process is in a good state.
- //
- // We're not waiting on that here, but the outro playing before the actual exit should be enough
- // to cover this.
- Squirrel.UpdateManager.RestartAppWhenExited().FireAndForget();
- return true;
+ Process.Start(Process.GetCurrentProcess().MainModule?.FileName ?? throw new InvalidOperationException());
+ Environment.Exit(0);
+ return true;
+ }
+ catch (Exception e)
+ {
+ Logger.Error(e, "Failed to restart application");
+ return base.RestartAppWhenExited();
}
-
- return base.RestartAppWhenExited();
}
protected override void LoadComplete()
diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs
index 23e56cdce9..7c23c15d5a 100644
--- a/osu.Desktop/Program.cs
+++ b/osu.Desktop/Program.cs
@@ -3,7 +3,6 @@
using System;
using System.IO;
-using System.Runtime.Versioning;
using osu.Desktop.LegacyIpc;
using osu.Desktop.Windows;
using osu.Framework;
@@ -14,7 +13,7 @@ using osu.Game;
using osu.Game.IPC;
using osu.Game.Tournament;
using SDL;
-using Squirrel;
+using Velopack;
namespace osu.Desktop
{
@@ -66,10 +65,10 @@ namespace osu.Desktop
return;
}
}
-
- setupSquirrel();
}
+ setupVelo();
+
// NVIDIA profiles are based on the executable name of a process.
// Lazer and stable share the same executable name.
// Stable sets this setting to "Off", which may not be what we want, so let's force it back to the default "Auto" on startup.
@@ -177,32 +176,14 @@ namespace osu.Desktop
return false;
}
- [SupportedOSPlatform("windows")]
- private static void setupSquirrel()
+ private static void setupVelo()
{
- SquirrelAwareApp.HandleEvents(onInitialInstall: (_, tools) =>
- {
- tools.CreateShortcutForThisExe();
- tools.CreateUninstallerRegistryEntry();
- WindowsAssociationManager.InstallAssociations();
- }, onAppUpdate: (_, tools) =>
- {
- tools.CreateUninstallerRegistryEntry();
- WindowsAssociationManager.UpdateAssociations();
- }, onAppUninstall: (_, tools) =>
- {
- tools.RemoveShortcutForThisExe();
- tools.RemoveUninstallerRegistryEntry();
- WindowsAssociationManager.UninstallAssociations();
- }, onEveryRun: (_, _, _) =>
- {
- // While setting the `ProcessAppUserModelId` fixes duplicate icons/shortcuts on the taskbar, it currently
- // causes the right-click context menu to function incorrectly.
- //
- // This may turn out to be non-required after an alternative solution is implemented.
- // see https://github.com/clowd/Clowd.Squirrel/issues/24
- // tools.SetProcessAppUserModelId();
- });
+ VelopackApp
+ .Build()
+ .WithFirstRun(v =>
+ {
+ if (OperatingSystem.IsWindows()) WindowsAssociationManager.InstallAssociations();
+ }).Run();
}
}
}
diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/VeloUpdateManager.cs
similarity index 52%
rename from osu.Desktop/Updater/SquirrelUpdateManager.cs
rename to osu.Desktop/Updater/VeloUpdateManager.cs
index dba157a6e9..8fc68f77cd 100644
--- a/osu.Desktop/Updater/SquirrelUpdateManager.cs
+++ b/osu.Desktop/Updater/VeloUpdateManager.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Runtime.Versioning;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Logging;
@@ -10,30 +9,15 @@ using osu.Game;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Screens.Play;
-using Squirrel.SimpleSplat;
-using Squirrel.Sources;
-using LogLevel = Squirrel.SimpleSplat.LogLevel;
-using UpdateManager = osu.Game.Updater.UpdateManager;
+using osu.Game.Updater;
namespace osu.Desktop.Updater
{
- [SupportedOSPlatform("windows")]
- public partial class SquirrelUpdateManager : UpdateManager
+ public partial class VeloUpdateManager : UpdateManager
{
- private Squirrel.UpdateManager? updateManager;
+ private Velopack.UpdateManager? updateManager;
private INotificationOverlay notificationOverlay = null!;
- public Task PrepareUpdateAsync() => Squirrel.UpdateManager.RestartAppWhenExited();
-
- private static readonly Logger logger = Logger.GetLogger("updater");
-
- ///
- /// Whether an update has been downloaded but not yet applied.
- ///
- private bool updatePending;
-
- private readonly SquirrelLogger squirrelLogger = new SquirrelLogger();
-
[Resolved]
private OsuGameBase game { get; set; } = null!;
@@ -44,13 +28,11 @@ namespace osu.Desktop.Updater
private void load(INotificationOverlay notifications)
{
notificationOverlay = notifications;
-
- SquirrelLocator.CurrentMutable.Register(() => squirrelLogger, typeof(ILogger));
}
protected override async Task PerformUpdateCheck() => await checkForUpdateAsync().ConfigureAwait(false);
- private async Task checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification? notification = null)
+ private async Task checkForUpdateAsync(UpdateProgressNotification? notification = null)
{
// should we schedule a retry on completion of this check?
bool scheduleRecheck = true;
@@ -63,27 +45,27 @@ namespace osu.Desktop.Updater
if (localUserInfo?.IsPlaying.Value == true)
return false;
- updateManager ??= new Squirrel.UpdateManager(new GithubSource(@"https://github.com/ppy/osu", github_token, false), @"osulazer");
+ updateManager ??= new Velopack.UpdateManager(new Velopack.Sources.GithubSource(@"https://github.com/ppy/osu", github_token, false));
- var info = await updateManager.CheckForUpdate(!useDeltaPatching).ConfigureAwait(false);
+ var info = await updateManager.CheckForUpdatesAsync().ConfigureAwait(false);
- if (info.ReleasesToApply.Count == 0)
+ if (info == null)
{
- if (updatePending)
+ // If there is an update pending restart, show the notification again.
+ if (updateManager.IsUpdatePendingRestart)
{
- // the user may have dismissed the completion notice, so show it again.
notificationOverlay.Post(new UpdateApplicationCompleteNotification
{
Activated = () =>
{
restartToApplyUpdate();
return true;
- },
+ }
});
return true;
}
- // no updates available. bail and retry later.
+ // Otherwise there's no updates available. Bail and retry later.
return false;
}
@@ -103,31 +85,17 @@ namespace osu.Desktop.Updater
try
{
- await updateManager.DownloadReleases(info.ReleasesToApply, p => notification.Progress = p / 100f).ConfigureAwait(false);
+ await updateManager.DownloadUpdatesAsync(info, p => notification.Progress = p / 100f).ConfigureAwait(false);
notification.StartInstall();
- await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f).ConfigureAwait(false);
-
notification.State = ProgressNotificationState.Completed;
- updatePending = true;
}
catch (Exception e)
{
- if (useDeltaPatching)
- {
- logger.Add(@"delta patching failed; will attempt full download!");
-
- // could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959)
- // try again without deltas.
- await checkForUpdateAsync(false, notification).ConfigureAwait(false);
- }
- else
- {
// In the case of an error, a separate notification will be displayed.
notification.FailDownload();
Logger.Error(e, @"update failed!");
- }
}
}
catch (Exception)
@@ -149,32 +117,12 @@ namespace osu.Desktop.Updater
private bool restartToApplyUpdate()
{
- PrepareUpdateAsync()
- .ContinueWith(_ => Schedule(() => game.AttemptExit()));
+ if (updateManager == null)
+ return false;
+
+ updateManager.WaitExitThenApplyUpdates(null);
+ Schedule(() => game.AttemptExit());
return true;
}
-
- protected override void Dispose(bool isDisposing)
- {
- base.Dispose(isDisposing);
- updateManager?.Dispose();
- }
-
- private class SquirrelLogger : ILogger, IDisposable
- {
- public LogLevel Level { get; set; } = LogLevel.Info;
-
- public void Write(string message, LogLevel logLevel)
- {
- if (logLevel < Level)
- return;
-
- logger.Add(message);
- }
-
- public void Dispose()
- {
- }
- }
}
}
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index e7a63bd921..7df82e1281 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -23,10 +23,10 @@
-
+
From 1025e5b3cc756b1cbd01216be157b3e0b270225f Mon Sep 17 00:00:00 2001
From: smallketchup82
Date: Wed, 26 Jun 2024 21:55:22 -0400
Subject: [PATCH 0149/1274] rewrite the restart function
---
osu.Desktop/OsuGameDesktop.cs | 11 ++++++++---
.../Screens/Setup/TournamentSwitcher.cs | 3 +--
osu.Game/OsuGameBase.cs | 2 +-
.../Settings/Sections/Graphics/RendererSettings.cs | 3 +--
4 files changed, 11 insertions(+), 8 deletions(-)
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index 245d00dc53..3019aefdc0 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -105,18 +105,23 @@ namespace osu.Desktop
return new VeloUpdateManager();
}
- public override bool RestartAppWhenExited()
+ public override bool RestartApp()
{
try
{
- Process.Start(Process.GetCurrentProcess().MainModule?.FileName ?? throw new InvalidOperationException());
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = Process.GetCurrentProcess().MainModule!.FileName,
+ UseShellExecute = true
+ };
+ Process.Start(startInfo);
Environment.Exit(0);
return true;
}
catch (Exception e)
{
Logger.Error(e, "Failed to restart application");
- return base.RestartAppWhenExited();
+ return base.RestartApp();
}
}
diff --git a/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs b/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs
index e55cbc2dbb..69ead451ba 100644
--- a/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs
+++ b/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs
@@ -31,8 +31,7 @@ namespace osu.Game.Tournament.Screens.Setup
Action = () =>
{
- game.RestartAppWhenExited();
- game.AttemptExit();
+ game.RestartApp();
};
folderButton.Action = () => storage.PresentExternally();
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index 5533ee8337..db603f0046 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -513,7 +513,7 @@ namespace osu.Game
/// If supported by the platform, the game will automatically restart after the next exit.
///
/// Whether a restart operation was queued.
- public virtual bool RestartAppWhenExited() => false;
+ public virtual bool RestartApp() => false;
public bool Migrate(string path)
{
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs
index a8b127d522..17e345c2c8 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs
@@ -67,9 +67,8 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
if (r.NewValue == RendererType.Automatic && automaticRendererInUse)
return;
- if (game?.RestartAppWhenExited() == true)
+ if (game?.RestartApp() == true)
{
- game.AttemptExit();
}
else
{
From 36a3765ee4e2ef3240a265549ebc6a734f696c62 Mon Sep 17 00:00:00 2001
From: smallketchup82
Date: Thu, 27 Jun 2024 12:57:24 -0400
Subject: [PATCH 0150/1274] Replace with attemptexit to better display how
restarting is borked
---
osu.Desktop/OsuGameDesktop.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index b6053edf77..28f3d3dc5d 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -114,7 +114,7 @@ namespace osu.Desktop
UseShellExecute = true
};
Process.Start(startInfo);
- Environment.Exit(0);
+ base.AttemptExit();
return true;
}
catch (Exception e)
From 0c34e7bebbce5a989af00d8daed25331ff33321a Mon Sep 17 00:00:00 2001
From: Salman Ahmed
Date: Mon, 1 Jul 2024 06:48:05 +0300
Subject: [PATCH 0151/1274] Store layout version in `SkinLayoutVersion` instead
and refactor migration code
---
.../Archives/argon-layout-version-0.osk | Bin 0 -> 1550 bytes
.../Archives/classic-layout-version-0.osk | Bin 0 -> 1382 bytes
.../Archives/triangles-layout-version-0.osk | Bin 0 -> 1378 bytes
.../Visual/Gameplay/TestSceneSkinEditor.cs | 119 ++++++++++--------
osu.Game/Database/RealmAccess.cs | 3 +-
osu.Game/Skinning/ArgonSkin.cs | 23 ----
osu.Game/Skinning/LegacySkin.cs | 23 ----
osu.Game/Skinning/Skin.cs | 116 ++++++++++++-----
osu.Game/Skinning/SkinImporter.cs | 2 -
osu.Game/Skinning/SkinInfo.cs | 15 ---
osu.Game/Skinning/SkinLayoutInfo.cs | 18 ++-
11 files changed, 165 insertions(+), 154 deletions(-)
create mode 100644 osu.Game.Tests/Resources/Archives/argon-layout-version-0.osk
create mode 100644 osu.Game.Tests/Resources/Archives/classic-layout-version-0.osk
create mode 100644 osu.Game.Tests/Resources/Archives/triangles-layout-version-0.osk
diff --git a/osu.Game.Tests/Resources/Archives/argon-layout-version-0.osk b/osu.Game.Tests/Resources/Archives/argon-layout-version-0.osk
new file mode 100644
index 0000000000000000000000000000000000000000..f767033eb1de68d16bbb5c4fa9cb5a0fb145425d
GIT binary patch
literal 1550
zcmWIWW@Zs#U|`^2*qU%JLZU!8aS;Op!vU}e14D6kW}aSVUZ!f#My^8+0<6D(cP$TP
zF%;rfoI7>y)LR`#)wUb9m+vmxDex&*tZvf&gr>U(cs|$Gf0B7V?c$2>Ru+{D!aQ|2
znM)VgWiEJ<^{I{}^0e4hj<>&9bL5}jX`Oy-i<|3RmCy?o=baiSo)11P*eD$7wZ(9|
zGPnDu^5R)tpX{T8YF6k>6urGDLXkTw_VEqB9nmwL=STIb>U|QCJrZMC=C{;kSsX#0iV3j_=au@3OJ>&7aGzL=%wRz
z{(N^}&;GWrXNAoI=
zs&-bY&g@j3`Ll9fW#-R-nyn|evCli9?
zt#0*Z&T6|>R=Z-=i5BIL24+^)Th_cPpU*i($iqBvsb}<5@e}{wY(D?*<+~Ga8&j|L
zO^mzD*uFkVC0l-N^kPZVM@0@hEBE=#UpsZ)t#i+U4W`Ws;r!{O?%?OlBGC24a_;1N
zKMgGpd3140xLrsWaJsuoK5Ba%qn6syg~`^skF4biJ7Tx*;=ImzXkmkk^3&4Q^LZ?|
z_aFS@)8{DD)$%#&Zz6Z?${A*P*QDcKn!9CGY%AQk@?7t&FCBZYy)%pXJ7w0onAB%i
z{hUuJ<~OFz`|Z9be#64P-n+T!bH6vZZZvD-_j9usO4PadVlMAT_Dvm~x*ua2f1Sxo
z*fGy&QOvLrG6huWr`;tGL}?lJ_%j
zytJC>k;xQ}*qI!>5qd&?d3_&Nxh4rZ$O%UO+TU4l*J;9zG!A8x?SdyXd1uaBzR)^O
zO+)nb>xRpWABTvX5=Xwj-hI1$ss@L^
z4$H}sEM0#a(-)#;1UHa34H8E}Sw{t>n)vsqvb)2{|nmN)p`c9tU?eDeu
z&$FcC+Hb_&{{G4MOYo&BvnMaRZ0oipZq@0WY~A0gj)_Pc?0b^C{KL{czE94q
z5fAvhe4g6`a~+e_N-V-%$(QRjZTz{kd{fX0wk}53&)wdQb6!L!te>`8j3L0A
zkx7IBcR>gTjUX1RSVY%_UhKd@;|cUa2wf|BkpKsc>5RAv2y`>h0|E{jx3ZuIOMo{k
T8%Umofra5C0|Ns)D@Zi}@tS=8
literal 0
HcmV?d00001
diff --git a/osu.Game.Tests/Resources/Archives/classic-layout-version-0.osk b/osu.Game.Tests/Resources/Archives/classic-layout-version-0.osk
new file mode 100644
index 0000000000000000000000000000000000000000..8240510f7c20e7d192efbfbba03ff9243f6f51dc
GIT binary patch
literal 1382
zcmWIWW@Zs#U|`^2*p+ZEVoSr(w@Vlp7!H9&7#NDPGxPK^^Dbz59;J;*{OR;(
zg-7!$kE(W7s?O|Go%yqJUS;Obfab4v8mGNrO;3OJH1*li*E^RiQ=Rr~%8w<>)YZb9
zTuMa}x~A;NVnlW7QOmRSDNGCuoA?+Q#26SDd=oSCJVITZ^K%RG^HTFliotHaI?Xr#
zmV-ds`#)UWnoRexS@Yi1o4sY%Jp+!^zrR&@fbIE(Q(P_F
zpR5nR;W__f>wK%Wi*q^tG$?b$-<(u#FX10`Im@ZCW#zton(KOl^pnD8TVI%{H(l~q
z)AfdD+J__(Ie+nS_L(Q|>-#Yy-KWu@ce>8yAFNJ7=iK_9R-7pRb;5C4W{>eIBSvMm
znL;yW9nv!hcJ)Wcyn|3{WrG3^xH@^orwmqM9^@N*yRg}xT
z$6wobpV$`kWs-J`*w&KZ-v?}tiNslERg`$?nO*zuE2edo^QVDmC6|il)s1xrA5H1r
zvmiLIo@y+6?C21|RV%Avt@L#L{zV1&*B{ti#Pthrqi%FMXf@~;nen6zT7v=DVFa({0RGMYr<2b
zopJVK|Md%XM{}a?alK!2CV^|ls*+E8qLy!O`o{BLmxJB6p;`EIFDt|3jAfOJUVdfq
z?fj*}B5v<6d#{W9#ub|Hn=17!lwb8-ZMnPc-|XtG5AAO%HJTcT1|3oe(d-QOkuLl<
z!)Au=vnlCHJsUXQcd7_3bb0z&YoeoW;Ico8F`uhz<2v3uK46)3DXDfYEqRN0|SF50|Nse0|P@qPGV(RW@=6fA`ca5dg=K4
zobvp7#`|>mrl1vUU5u`uyS*FdyogX(KW()bLx49UlL!Ou0tOBmK`dBtgRTobGs8jS
yDfFz3t`$9V!9imwBd)B5ZU%Zlz(FG~3wp2wc(byB0RpEWxJl)OGWdkTtAW8me6Tob^BHbK1w3(WuvCYKg}l$=rLS8LZD(L7F?cRo-S{8w>
z9t+F(+ua&Iaq@TQyG5gx$>a)LCSIiLEyX>JrR
zGr7wZP0t>>sW!Ja@l7z_(%Db$Zu-!hzUY5iu&h*dlnb966Svb}{W`rr0ZCHtb=v>A
zh;1-An8&v#WWl}-)-%>!Kcf*oL7!nuno>X}@6S2E6dwn-m^`>z`qcB>?GLu!R1XFI
z*yg>W;7#~}5=L9aRp)E%-Cn7?C+_}t>iF%#ZECjj4bEB=E%(vosn2}5^yd3L)@=?u
z`Plhy%f)l{X|uGN&YqUBmHWr6yLplBb5=d@7VHUN=L^~f4XdJ
z*i&ly0Hj}qOj%kOos6GGxNEXZ@$$0vcxRDNOJM(z>@qKNpg>U
z)Ms{x$b<_CpOoG_-#ecnlOZ*4-%Zhjy&C5jW0y|8s+=cMWAU{iKd1WID)TElzBa!I
zeZ9B%`{kX*YAv3Vd?o}676i<$S3WTFNC%g^+a%7le$Q6#zN{Ae*}B^ER`mz{_gBCE
zI;gGs=i-hvI{%qalcvnnq$)KA1_n(A1_nL`28Mu~#LBeH)SMJV{wdP*(((5><@xoD
z_v!FWK`Yq07+pVidpFK`5uvbt+G;U|0B=Sn5eD3a3mi0pSg@i7T^D*rhJ(ga=-C=w
tD|-HdgT_2YT)7S14D^72gGNpk^k50_W@Q7(voNqQd}Lr?h-Lw)1^|H;DMkPQ
literal 0
HcmV?d00001
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs
index 2470c320cc..f44daa1ecb 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs
@@ -7,6 +7,7 @@ using System.Linq;
using System.Text;
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Extensions;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
@@ -24,6 +25,7 @@ using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.HUD.HitErrorMeters;
using osu.Game.Skinning;
using osu.Game.Skinning.Components;
+using osu.Game.Tests.Resources;
using osuTK;
using osuTK.Input;
@@ -384,73 +386,82 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestMigrationArgon()
{
- AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded);
- AddStep("add combo to global hud target", () =>
- {
- globalHUDTarget.Add(new ArgonComboCounter
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- });
- });
+ Live importedSkin = null!;
- Live modifiedSkin = null!;
+ AddStep("import old argon skin", () => skins.CurrentSkinInfo.Value = importedSkin = importSkinFromArchives(@"argon-layout-version-0.osk").SkinInfo);
+ AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded);
+ AddAssert("no combo in global target", () => !globalHUDTarget.Components.OfType().Any());
+ AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType().Count() == 1);
- AddStep("select another skin", () =>
+ AddStep("add combo to global target", () => globalHUDTarget.Add(new ArgonComboCounter
{
- modifiedSkin = skins.CurrentSkinInfo.Value;
- skins.CurrentSkinInfo.SetDefault();
- });
- AddStep("modify version", () => modifiedSkin.PerformWrite(s => s.LayoutVersion = 0));
- AddStep("select skin again", () => skins.CurrentSkinInfo.Value = modifiedSkin);
- AddAssert("global hud target does not contain combo", () => !globalHUDTarget.Components.Any(c => c is ArgonComboCounter));
- AddAssert("ruleset hud target contains both combos", () =>
- {
- var target = rulesetHUDTarget;
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Scale = new Vector2(2f),
+ }));
+ AddStep("save skin", () => skins.Save(skins.CurrentSkin.Value));
- return target.Components.Count == 2 &&
- target.Components[0] is ArgonComboCounter one && one.Anchor == Anchor.BottomLeft && one.Origin == Anchor.BottomLeft &&
- target.Components[1] is ArgonComboCounter two && two.Anchor == Anchor.Centre && two.Origin == Anchor.Centre;
- });
- AddStep("save skin", () => skinEditor.Save());
- AddAssert("version updated", () => modifiedSkin.PerformRead(s => s.LayoutVersion) == SkinInfo.LATEST_LAYOUT_VERSION);
+ AddStep("select another skin", () => skins.CurrentSkinInfo.SetDefault());
+ AddStep("select skin again", () => skins.CurrentSkinInfo.Value = importedSkin);
+ AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded);
+ AddAssert("combo placed in global target", () => globalHUDTarget.Components.OfType().Count() == 1);
+ AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType().Count() == 1);
+ }
+
+ [Test]
+ public void TestMigrationTriangles()
+ {
+ Live importedSkin = null!;
+
+ AddStep("import old triangles skin", () => skins.CurrentSkinInfo.Value = importedSkin = importSkinFromArchives(@"triangles-layout-version-0.osk").SkinInfo);
+ AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded);
+ AddAssert("no combo in global target", () => !globalHUDTarget.Components.OfType().Any());
+ AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType().Count() == 1);
+
+ AddStep("add combo to global target", () => globalHUDTarget.Add(new DefaultComboCounter
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Scale = new Vector2(2f),
+ }));
+ AddStep("save skin", () => skins.Save(skins.CurrentSkin.Value));
+
+ AddStep("select another skin", () => skins.CurrentSkinInfo.SetDefault());
+ AddStep("select skin again", () => skins.CurrentSkinInfo.Value = importedSkin);
+ AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded);
+ AddAssert("combo placed in global target", () => globalHUDTarget.Components.OfType().Count() == 1);
+ AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType().Count() == 1);
}
[Test]
public void TestMigrationLegacy()
{
- AddStep("select legacy skin", () => skins.CurrentSkinInfo.Value = skins.DefaultClassicSkin.SkinInfo);
+ Live importedSkin = null!;
- AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded);
- AddStep("add combo to global hud target", () =>
+ AddStep("import old classic skin", () => skins.CurrentSkinInfo.Value = importedSkin = importSkinFromArchives(@"classic-layout-version-0.osk").SkinInfo);
+ AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded);
+ AddAssert("no combo in global target", () => !globalHUDTarget.Components.OfType().Any());
+ AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType().Count() == 1);
+
+ AddStep("add combo to global target", () => globalHUDTarget.Add(new LegacyComboCounter
{
- globalHUDTarget.Add(new LegacyComboCounter
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- });
- });
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Scale = new Vector2(2f),
+ }));
+ AddStep("save skin", () => skins.Save(skins.CurrentSkin.Value));
- Live modifiedSkin = null!;
+ AddStep("select another skin", () => skins.CurrentSkinInfo.SetDefault());
+ AddStep("select skin again", () => skins.CurrentSkinInfo.Value = importedSkin);
+ AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded);
+ AddAssert("combo placed in global target", () => globalHUDTarget.Components.OfType().Count() == 1);
+ AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType().Count() == 1);
+ }
- AddStep("select another skin", () =>
- {
- modifiedSkin = skins.CurrentSkinInfo.Value;
- skins.CurrentSkinInfo.SetDefault();
- });
- AddStep("modify version", () => modifiedSkin.PerformWrite(s => s.LayoutVersion = 0));
- AddStep("select skin again", () => skins.CurrentSkinInfo.Value = modifiedSkin);
- AddAssert("global hud target does not contain combo", () => !globalHUDTarget.Components.Any(c => c is LegacyComboCounter));
- AddAssert("ruleset hud target contains both combos", () =>
- {
- var target = rulesetHUDTarget;
-
- return target.Components.Count == 2 &&
- target.Components[0] is LegacyComboCounter one && one.Anchor == Anchor.BottomLeft && one.Origin == Anchor.BottomLeft &&
- target.Components[1] is LegacyComboCounter two && two.Anchor == Anchor.Centre && two.Origin == Anchor.Centre;
- });
- AddStep("save skin", () => skinEditor.Save());
- AddAssert("version updated", () => modifiedSkin.PerformRead(s => s.LayoutVersion) == SkinInfo.LATEST_LAYOUT_VERSION);
+ private Skin importSkinFromArchives(string filename)
+ {
+ var imported = skins.Import(new ImportTask(TestResources.OpenResource($@"Archives/{filename}"), filename)).GetResultSafely();
+ return imported.PerformRead(skinInfo => skins.GetSkin(skinInfo));
}
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs
index 606bc5e10c..1ece81be50 100644
--- a/osu.Game/Database/RealmAccess.cs
+++ b/osu.Game/Database/RealmAccess.cs
@@ -93,9 +93,8 @@ namespace osu.Game.Database
/// 39 2023-12-19 Migrate any EndTimeObjectCount and TotalObjectCount values of 0 to -1 to better identify non-calculated values.
/// 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-06-25 Add SkinInfo.LayoutVersion to allow performing migrations of components on structural changes.
///
- private const int schema_version = 42;
+ private const int schema_version = 41;
///
/// Lock object which is held during sections, blocking realm retrieval during blocking periods.
diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs
index 4cd54c06f0..707281db31 100644
--- a/osu.Game/Skinning/ArgonSkin.cs
+++ b/osu.Game/Skinning/ArgonSkin.cs
@@ -12,7 +12,6 @@ using osu.Game.Audio;
using osu.Game.Beatmaps.Formats;
using osu.Game.Extensions;
using osu.Game.IO;
-using osu.Game.Rulesets;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.HUD.HitErrorMeters;
@@ -70,28 +69,6 @@ namespace osu.Game.Skinning
// Purple
new Color4(92, 0, 241, 255),
};
-
- if (skin.LayoutVersion < 20240625
- && LayoutInfos.TryGetValue(SkinComponentsContainerLookup.TargetArea.MainHUDComponents, out var hudLayout)
- && hudLayout.TryGetDrawableInfo(null, out var hudComponents))
- {
- var comboCounters = hudComponents.Where(h => h.Type.Name == nameof(ArgonComboCounter)).ToArray();
-
- if (comboCounters.Any())
- {
- hudLayout.Update(null, hudComponents.Except(comboCounters).ToArray());
-
- resources.RealmAccess.Run(r =>
- {
- foreach (var ruleset in r.All())
- {
- hudLayout.Update(ruleset, hudLayout.TryGetDrawableInfo(ruleset, out var rulesetComponents)
- ? rulesetComponents.Concat(comboCounters).ToArray()
- : comboCounters);
- }
- });
- }
- }
}
public override Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => Textures?.Get(componentName, wrapModeS, wrapModeT);
diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs
index f148bad96e..b71b626b4e 100644
--- a/osu.Game/Skinning/LegacySkin.cs
+++ b/osu.Game/Skinning/LegacySkin.cs
@@ -19,7 +19,6 @@ using osu.Game.Audio;
using osu.Game.Beatmaps.Formats;
using osu.Game.Extensions;
using osu.Game.IO;
-using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play.HUD;
@@ -57,28 +56,6 @@ namespace osu.Game.Skinning
protected LegacySkin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore? fallbackStore, string configurationFilename = @"skin.ini")
: base(skin, resources, fallbackStore, configurationFilename)
{
- if (resources != null
- && skin.LayoutVersion < 20240625
- && LayoutInfos.TryGetValue(SkinComponentsContainerLookup.TargetArea.MainHUDComponents, out var hudLayout)
- && hudLayout.TryGetDrawableInfo(null, out var hudComponents))
- {
- var comboCounters = hudComponents.Where(h => h.Type.Name == nameof(LegacyComboCounter)).ToArray();
-
- if (comboCounters.Any())
- {
- hudLayout.Update(null, hudComponents.Except(comboCounters).ToArray());
-
- resources.RealmAccess.Run(r =>
- {
- foreach (var ruleset in r.All())
- {
- hudLayout.Update(ruleset, hudLayout.TryGetDrawableInfo(ruleset, out var rulesetComponents)
- ? rulesetComponents.Concat(comboCounters).ToArray()
- : comboCounters);
- }
- });
- }
- }
}
protected override IResourceStore CreateTextureLoaderStore(IStorageResourceProvider resources, IResourceStore storage)
diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs
index e4ca908d90..5bac5c3d81 100644
--- a/osu.Game/Skinning/Skin.cs
+++ b/osu.Game/Skinning/Skin.cs
@@ -21,11 +21,15 @@ using osu.Framework.Logging;
using osu.Game.Audio;
using osu.Game.Database;
using osu.Game.IO;
+using osu.Game.Rulesets;
+using osu.Game.Screens.Play.HUD;
namespace osu.Game.Skinning
{
public abstract class Skin : IDisposable, ISkin
{
+ private readonly IStorageResourceProvider? resources;
+
///
/// A texture store which can be used to perform user file lookups for this skin.
///
@@ -68,6 +72,8 @@ namespace osu.Game.Skinning
/// An optional filename to read the skin configuration from. If not provided, the configuration will be retrieved from the storage using "skin.ini".
protected Skin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore? fallbackStore = null, string configurationFilename = @"skin.ini")
{
+ this.resources = resources;
+
Name = skin.Name;
if (resources != null)
@@ -131,40 +137,9 @@ namespace osu.Game.Skinning
{
string jsonContent = Encoding.UTF8.GetString(bytes);
- SkinLayoutInfo? layoutInfo = null;
-
- // handle namespace changes...
- jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.SongProgress", @"osu.Game.Screens.Play.HUD.DefaultSongProgress");
- jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.LegacyComboCounter", @"osu.Game.Skinning.LegacyComboCounter");
- jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.PerformancePointsCounter", @"osu.Game.Skinning.Triangles.TrianglesPerformancePointsCounter");
-
- try
- {
- // First attempt to deserialise using the new SkinLayoutInfo format
- layoutInfo = JsonConvert.DeserializeObject(jsonContent);
- }
- catch
- {
- }
-
- // Of note, the migration code below runs on read of skins, but there's nothing to
- // force a rewrite after migration. Let's not remove these migration rules until we
- // have something in place to ensure we don't end up breaking skins of users that haven't
- // manually saved their skin since a change was implemented.
-
- // If deserialisation using SkinLayoutInfo fails, attempt to deserialise using the old naked list.
+ var layoutInfo = parseLayoutInfo(jsonContent, skinnableTarget);
if (layoutInfo == null)
- {
- var deserializedContent = JsonConvert.DeserializeObject>(jsonContent);
-
- if (deserializedContent == null)
- continue;
-
- layoutInfo = new SkinLayoutInfo();
- layoutInfo.Update(null, deserializedContent.ToArray());
-
- Logger.Log($"Ferrying {deserializedContent.Count()} components in {skinnableTarget} to global section of new {nameof(SkinLayoutInfo)} format");
- }
+ continue;
LayoutInfos[skinnableTarget] = layoutInfo;
}
@@ -230,6 +205,81 @@ namespace osu.Game.Skinning
return null;
}
+ #region Deserialisation & Migration
+
+ private SkinLayoutInfo? parseLayoutInfo(string jsonContent, SkinComponentsContainerLookup.TargetArea target)
+ {
+ SkinLayoutInfo? layout = null;
+
+ // handle namespace changes...
+ jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.SongProgress", @"osu.Game.Screens.Play.HUD.DefaultSongProgress");
+ jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.LegacyComboCounter", @"osu.Game.Skinning.LegacyComboCounter");
+ jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.PerformancePointsCounter", @"osu.Game.Skinning.Triangles.TrianglesPerformancePointsCounter");
+
+ try
+ {
+ // First attempt to deserialise using the new SkinLayoutInfo format
+ layout = JsonConvert.DeserializeObject(jsonContent);
+ }
+ catch
+ {
+ }
+
+ // If deserialisation using SkinLayoutInfo fails, attempt to deserialise using the old naked list.
+ if (layout == null)
+ {
+ var deserializedContent = JsonConvert.DeserializeObject>(jsonContent);
+ if (deserializedContent == null)
+ return null;
+
+ layout = new SkinLayoutInfo { Version = 0 };
+ layout.Update(null, deserializedContent.ToArray());
+
+ Logger.Log($"Ferrying {deserializedContent.Count()} components in {target} to global section of new {nameof(SkinLayoutInfo)} format");
+ }
+
+ for (int i = layout.Version + 1; i <= SkinLayoutInfo.LATEST_VERSION; i++)
+ applyMigration(layout, target, i);
+
+ layout.Version = SkinLayoutInfo.LATEST_VERSION;
+ return layout;
+ }
+
+ private void applyMigration(SkinLayoutInfo layout, SkinComponentsContainerLookup.TargetArea target, int version)
+ {
+ switch (version)
+ {
+ case 1:
+ {
+ if (target != SkinComponentsContainerLookup.TargetArea.MainHUDComponents ||
+ !layout.TryGetDrawableInfo(null, out var globalHUDComponents) ||
+ resources == null)
+ break;
+
+ var comboCounters = globalHUDComponents.Where(c =>
+ c.Type.Name == nameof(LegacyComboCounter) ||
+ c.Type.Name == nameof(DefaultComboCounter) ||
+ c.Type.Name == nameof(ArgonComboCounter)).ToArray();
+
+ layout.Update(null, globalHUDComponents.Except(comboCounters).ToArray());
+
+ resources.RealmAccess.Run(r =>
+ {
+ foreach (var ruleset in r.All())
+ {
+ layout.Update(ruleset, layout.TryGetDrawableInfo(ruleset, out var rulesetHUDComponents)
+ ? rulesetHUDComponents.Concat(comboCounters).ToArray()
+ : comboCounters);
+ }
+ });
+
+ break;
+ }
+ }
+ }
+
+ #endregion
+
#region Disposal
~Skin()
diff --git a/osu.Game/Skinning/SkinImporter.cs b/osu.Game/Skinning/SkinImporter.cs
index 714427f40d..59c7f0ba26 100644
--- a/osu.Game/Skinning/SkinImporter.cs
+++ b/osu.Game/Skinning/SkinImporter.cs
@@ -223,8 +223,6 @@ namespace osu.Game.Skinning
}
}
- s.LayoutVersion = SkinInfo.LATEST_LAYOUT_VERSION;
-
string newHash = ComputeHash(s);
hadChanges = newHash != s.Hash;
diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs
index a3d5771b5e..9763d3b57e 100644
--- a/osu.Game/Skinning/SkinInfo.cs
+++ b/osu.Game/Skinning/SkinInfo.cs
@@ -39,21 +39,6 @@ namespace osu.Game.Skinning
public bool Protected { get; set; }
- ///
- /// The latest version in YYYYMMDD format for skin layout migrations.
- ///
- ///
- ///
- /// - 20240625: Moves combo counters from ruleset-agnostic to ruleset-specific HUD targets.
- ///
- ///
- public const int LATEST_LAYOUT_VERSION = 20240625;
-
- ///
- /// A version in YYYYMMDD format for applying skin layout migrations.
- ///
- public int LayoutVersion { get; set; }
-
public virtual Skin CreateInstance(IStorageResourceProvider resources)
{
var type = string.IsNullOrEmpty(InstantiationInfo)
diff --git a/osu.Game/Skinning/SkinLayoutInfo.cs b/osu.Game/Skinning/SkinLayoutInfo.cs
index 115d59b9d0..22c876e5ad 100644
--- a/osu.Game/Skinning/SkinLayoutInfo.cs
+++ b/osu.Game/Skinning/SkinLayoutInfo.cs
@@ -19,12 +19,26 @@ namespace osu.Game.Skinning
{
private const string global_identifier = @"global";
- [JsonIgnore]
- public IEnumerable AllDrawables => DrawableInfo.Values.SelectMany(v => v);
+ ///
+ /// Latest version representing the schema of the skin layout.
+ ///
+ ///
+ ///
+ /// - 0: Initial version of all skin layouts.
+ /// - 1: Moves existing combo counters from global to per-ruleset HUD targets.
+ ///
+ ///
+ public const int LATEST_VERSION = 1;
+
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
+ public int Version = LATEST_VERSION;
[JsonProperty]
public Dictionary DrawableInfo { get; set; } = new Dictionary();
+ [JsonIgnore]
+ public IEnumerable AllDrawables => DrawableInfo.Values.SelectMany(v => v);
+
public bool TryGetDrawableInfo(RulesetInfo? ruleset, [NotNullWhen(true)] out SerialisedDrawableInfo[]? components) =>
DrawableInfo.TryGetValue(ruleset?.ShortName ?? global_identifier, out components);
From b15028a918ececff597d694c3315d732cf784cdc Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Wed, 3 Jul 2024 12:36:12 +0200
Subject: [PATCH 0152/1274] fixes
---
osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 10 +---------
1 file changed, 1 insertion(+), 9 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
index 8d80ed651e..7720afe60a 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Linq;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.UserInterface;
@@ -23,18 +24,9 @@ namespace osu.Game.Rulesets.Osu.Edit
{
public partial class OsuSelectionHandler : EditorSelectionHandler
{
- [Resolved(CanBeNull = true)]
- private IDistanceSnapProvider? snapProvider { get; set; }
-
[Resolved]
private OsuGridToolboxGroup gridToolbox { get; set; } = null!;
- ///
- /// During a transform, the initial path types of a single selected slider are stored so they
- /// can be maintained throughout the operation.
- ///
- private List? referencePathTypes;
-
protected override void OnSelectionChanged()
{
base.OnSelectionChanged();
From 5f8512896e0eee0eda8ec0c9410704422d475182 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Wed, 3 Jul 2024 12:40:22 +0200
Subject: [PATCH 0153/1274] use grid origin in scale tool
---
osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 8 +++++---
osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs | 2 +-
2 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs
index a299eebbce..65a07e2e2f 100644
--- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs
+++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs
@@ -10,7 +10,6 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
-using osu.Game.Rulesets.Osu.UI;
using osu.Game.Screens.Edit.Components.RadioButtons;
using osuTK;
@@ -20,6 +19,8 @@ namespace osu.Game.Rulesets.Osu.Edit
{
private readonly OsuSelectionScaleHandler scaleHandler;
+ private readonly OsuGridToolboxGroup gridToolbox;
+
private readonly Bindable scaleInfo = new Bindable(new PreciseScaleInfo(1, ScaleOrigin.PlayfieldCentre, true, true));
private SliderWithTextBoxInput scaleInput = null!;
@@ -32,9 +33,10 @@ namespace osu.Game.Rulesets.Osu.Edit
private OsuCheckbox xCheckBox = null!;
private OsuCheckbox yCheckBox = null!;
- public PreciseScalePopover(OsuSelectionScaleHandler scaleHandler)
+ public PreciseScalePopover(OsuSelectionScaleHandler scaleHandler, OsuGridToolboxGroup gridToolbox)
{
this.scaleHandler = scaleHandler;
+ this.gridToolbox = gridToolbox;
AllowableAnchors = new[] { Anchor.CentreLeft, Anchor.CentreRight };
}
@@ -179,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Edit
updateAxisCheckBoxesEnabled();
}
- private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? OsuPlayfield.BASE_SIZE / 2 : null;
+ private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? gridToolbox.StartPosition.Value : null;
private void setAxis(bool x, bool y)
{
diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs
index 2e4d7e8b91..a41412cbe3 100644
--- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs
+++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs
@@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Edit
() => new PreciseRotationPopover(RotationHandler, GridToolbox)),
scaleButton = new EditorToolButton("Scale",
() => new SpriteIcon { Icon = FontAwesome.Solid.ArrowsAlt },
- () => new PreciseScalePopover(ScaleHandler))
+ () => new PreciseScalePopover(ScaleHandler, GridToolbox))
}
};
}
From d0715c5f12a98e9a31465600d1cc69bb0efe1df2 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Wed, 3 Jul 2024 16:23:19 +0200
Subject: [PATCH 0154/1274] scale along rotated axis
---
.../Edit/OsuSelectionScaleHandler.cs | 104 ++++++++++++++----
.../Edit/PreciseScalePopover.cs | 8 +-
.../Editing/TestSceneComposeSelectBox.cs | 2 +-
.../SkinEditor/SkinSelectionScaleHandler.cs | 2 +-
.../Components/SelectionScaleHandler.cs | 8 +-
osu.Game/Utils/GeometryUtils.cs | 4 +-
6 files changed, 97 insertions(+), 31 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs
index f4fd48f183..cfcf90e5f5 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs
@@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.Edit
defaultOrigin = OriginalSurroundingQuad.Value.Centre;
}
- public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both)
+ public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0)
{
if (!OperationInProgress.Value)
throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!");
@@ -94,6 +94,7 @@ namespace osu.Game.Rulesets.Osu.Edit
Debug.Assert(objectsInScale != null && defaultOrigin != null && OriginalSurroundingQuad != null);
Vector2 actualOrigin = origin ?? defaultOrigin.Value;
+ scale = clampScaleToAdjustAxis(scale, adjustAxis);
// for the time being, allow resizing of slider paths only if the slider is
// the only hit object selected. with a group selection, it's likely the user
@@ -102,15 +103,15 @@ namespace osu.Game.Rulesets.Osu.Edit
{
var originalInfo = objectsInScale[slider];
Debug.Assert(originalInfo.PathControlPointPositions != null && originalInfo.PathControlPointTypes != null);
- scaleSlider(slider, scale, originalInfo.PathControlPointPositions, originalInfo.PathControlPointTypes);
+ scaleSlider(slider, scale, originalInfo.PathControlPointPositions, originalInfo.PathControlPointTypes, axisRotation);
}
else
{
- scale = ClampScaleToPlayfieldBounds(scale, actualOrigin);
+ scale = ClampScaleToPlayfieldBounds(scale, actualOrigin, adjustAxis, axisRotation);
foreach (var (ho, originalState) in objectsInScale)
{
- ho.Position = GeometryUtils.GetScaledPosition(scale, actualOrigin, originalState.Position);
+ ho.Position = GeometryUtils.GetScaledPosition(scale, actualOrigin, originalState.Position, axisRotation);
}
}
@@ -134,14 +135,34 @@ namespace osu.Game.Rulesets.Osu.Edit
private IEnumerable selectedMovableObjects => selectedItems.Cast()
.Where(h => h is not Spinner);
- private void scaleSlider(Slider slider, Vector2 scale, Vector2[] originalPathPositions, PathType?[] originalPathTypes)
+ private Vector2 clampScaleToAdjustAxis(Vector2 scale, Axes adjustAxis)
+ {
+ switch (adjustAxis)
+ {
+ case Axes.Y:
+ scale.X = 1;
+ break;
+
+ case Axes.X:
+ scale.Y = 1;
+ break;
+
+ case Axes.None:
+ scale = Vector2.One;
+ break;
+ }
+
+ return scale;
+ }
+
+ private void scaleSlider(Slider slider, Vector2 scale, Vector2[] originalPathPositions, PathType?[] originalPathTypes, float axisRotation = 0)
{
scale = Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON));
// Maintain the path types in case they were defaulted to bezier at some point during scaling
for (int i = 0; i < slider.Path.ControlPoints.Count; i++)
{
- slider.Path.ControlPoints[i].Position = originalPathPositions[i] * scale;
+ slider.Path.ControlPoints[i].Position = GeometryUtils.GetScaledPosition(scale, Vector2.Zero, originalPathPositions[i], axisRotation);
slider.Path.ControlPoints[i].Type = originalPathTypes[i];
}
@@ -176,11 +197,13 @@ namespace osu.Game.Rulesets.Osu.Edit
///
/// The origin from which the scale operation is performed
/// The scale to be clamped
+ /// The axes to adjust the scale in.
+ /// The rotation of the axes in degrees
/// The clamped scale vector
- public Vector2 ClampScaleToPlayfieldBounds(Vector2 scale, Vector2? origin = null)
+ public Vector2 ClampScaleToPlayfieldBounds(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0)
{
//todo: this is not always correct for selections involving sliders. This approximation assumes each point is scaled independently, but sliderends move with the sliderhead.
- if (objectsInScale == null)
+ if (objectsInScale == null || adjustAxis == Axes.None)
return scale;
Debug.Assert(defaultOrigin != null && OriginalSurroundingQuad != null);
@@ -188,24 +211,63 @@ namespace osu.Game.Rulesets.Osu.Edit
if (objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider)
origin = slider.Position;
+ scale = clampScaleToAdjustAxis(scale, adjustAxis);
Vector2 actualOrigin = origin ?? defaultOrigin.Value;
var selectionQuad = OriginalSurroundingQuad.Value;
- var tl1 = Vector2.Divide(-actualOrigin, selectionQuad.TopLeft - actualOrigin);
- var tl2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - actualOrigin, selectionQuad.TopLeft - actualOrigin);
- var br1 = Vector2.Divide(-actualOrigin, selectionQuad.BottomRight - actualOrigin);
- var br2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - actualOrigin, selectionQuad.BottomRight - actualOrigin);
-
- if (!Precision.AlmostEquals(selectionQuad.TopLeft.X - actualOrigin.X, 0))
- scale.X = selectionQuad.TopLeft.X - actualOrigin.X < 0 ? MathHelper.Clamp(scale.X, tl2.X, tl1.X) : MathHelper.Clamp(scale.X, tl1.X, tl2.X);
- if (!Precision.AlmostEquals(selectionQuad.TopLeft.Y - actualOrigin.Y, 0))
- scale.Y = selectionQuad.TopLeft.Y - actualOrigin.Y < 0 ? MathHelper.Clamp(scale.Y, tl2.Y, tl1.Y) : MathHelper.Clamp(scale.Y, tl1.Y, tl2.Y);
- if (!Precision.AlmostEquals(selectionQuad.BottomRight.X - actualOrigin.X, 0))
- scale.X = selectionQuad.BottomRight.X - actualOrigin.X < 0 ? MathHelper.Clamp(scale.X, br2.X, br1.X) : MathHelper.Clamp(scale.X, br1.X, br2.X);
- if (!Precision.AlmostEquals(selectionQuad.BottomRight.Y - actualOrigin.Y, 0))
- scale.Y = selectionQuad.BottomRight.Y - actualOrigin.Y < 0 ? MathHelper.Clamp(scale.Y, br2.Y, br1.Y) : MathHelper.Clamp(scale.Y, br1.Y, br2.Y);
+ scale = clampToBound(scale, selectionQuad.BottomRight, OsuPlayfield.BASE_SIZE.X, Axes.X);
+ scale = clampToBound(scale, selectionQuad.BottomRight, OsuPlayfield.BASE_SIZE.Y, Axes.Y);
+ scale = clampToBound(scale, selectionQuad.TopLeft, 0, Axes.X);
+ scale = clampToBound(scale, selectionQuad.TopLeft, 0, Axes.Y);
return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON));
+
+ Vector2 clampToBound(Vector2 s, Vector2 p, float bound, Axes axis)
+ {
+ float px = p.X - actualOrigin.X;
+ float py = p.Y - actualOrigin.Y;
+ float c = axis == Axes.X ? bound - actualOrigin.X : bound - actualOrigin.Y;
+ float cos = MathF.Cos(float.DegreesToRadians(-axisRotation));
+ float sin = MathF.Sin(float.DegreesToRadians(-axisRotation));
+ float a, b;
+
+ if (axis == Axes.X)
+ {
+ a = cos * cos * px - sin * cos * py;
+ b = sin * sin * px + sin * cos * py;
+ }
+ else
+ {
+ a = -sin * cos * px + sin * sin * py;
+ b = sin * cos * px + cos * cos * py;
+ }
+
+ switch (adjustAxis)
+ {
+ case Axes.X:
+ if (Precision.AlmostEquals(a, 0) || (c - b) / a < 0)
+ break;
+
+ s.X = MathF.Min(scale.X, (c - b) / a);
+ break;
+
+ case Axes.Y:
+ if (Precision.AlmostEquals(b, 0) || (c - a) / b < 0)
+ break;
+
+ s.Y = MathF.Min(scale.Y, (c - a) / b);
+ break;
+
+ case Axes.Both:
+ if (Precision.AlmostEquals(a + b, 0) || c / (a * s.X + b * s.Y) < 0)
+ break;
+
+ s = Vector2.ComponentMin(s, s * c / (a * s.X + b * s.Y));
+ break;
+ }
+
+ return s;
+ }
}
private void moveSelectionInBounds()
diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs
index 65a07e2e2f..a1907a2fd5 100644
--- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs
+++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs
@@ -130,8 +130,8 @@ namespace osu.Game.Rulesets.Osu.Edit
scaleInfo.BindValueChanged(scale =>
{
- var newScale = new Vector2(scale.NewValue.XAxis ? scale.NewValue.Scale : 1, scale.NewValue.YAxis ? scale.NewValue.Scale : 1);
- scaleHandler.Update(newScale, getOriginPosition(scale.NewValue));
+ var newScale = new Vector2(scale.NewValue.Scale, scale.NewValue.Scale);
+ scaleHandler.Update(newScale, getOriginPosition(scale.NewValue), getAdjustAxis(scale.NewValue), gridToolbox.GridLinesRotation.Value);
});
}
@@ -164,7 +164,7 @@ namespace osu.Game.Rulesets.Osu.Edit
return;
const float max_scale = 10;
- var scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(max_scale), getOriginPosition(scaleInfo.Value));
+ var scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(max_scale), getOriginPosition(scaleInfo.Value), getAdjustAxis(scaleInfo.Value), gridToolbox.GridLinesRotation.Value);
if (!scaleInfo.Value.XAxis)
scale.X = max_scale;
@@ -183,6 +183,8 @@ namespace osu.Game.Rulesets.Osu.Edit
private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? gridToolbox.StartPosition.Value : null;
+ private Axes getAdjustAxis(PreciseScaleInfo scale) => scale.XAxis ? scale.YAxis ? Axes.Both : Axes.X : Axes.Y;
+
private void setAxis(bool x, bool y)
{
scaleInfo.Value = scaleInfo.Value with { XAxis = x, YAxis = y };
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs
index 28763051e3..30f397f518 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs
@@ -134,7 +134,7 @@ namespace osu.Game.Tests.Visual.Editing
OriginalSurroundingQuad = new Quad(targetContainer!.X, targetContainer.Y, targetContainer.Width, targetContainer.Height);
}
- public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both)
+ public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0)
{
if (targetContainer == null)
throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!");
diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs
index 4bfa7fba81..977aaade99 100644
--- a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs
+++ b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs
@@ -73,7 +73,7 @@ namespace osu.Game.Overlays.SkinEditor
isFlippedY = false;
}
- public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both)
+ public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0)
{
if (objectsInScale == null)
throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!");
diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs
index c91362219c..177de9df33 100644
--- a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs
@@ -52,10 +52,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// If the default value is supplied, a sane implementation-defined default will be used.
///
/// The axes to adjust the scale in.
- public void ScaleSelection(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both)
+ /// The rotation of the axes in degrees.
+ public void ScaleSelection(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0)
{
Begin();
- Update(scale, origin, adjustAxis);
+ Update(scale, origin, adjustAxis, axisRotation);
Commit();
}
@@ -91,7 +92,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// If the default value is supplied, a sane implementation-defined default will be used.
///
/// The axes to adjust the scale in.
- public virtual void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both)
+ /// The rotation of the axes in degrees.
+ public virtual void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0)
{
}
diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs
index bf1addf6c8..f6e7e81007 100644
--- a/osu.Game/Utils/GeometryUtils.cs
+++ b/osu.Game/Utils/GeometryUtils.cs
@@ -104,9 +104,9 @@ namespace osu.Game.Utils
/// Given a scale multiplier, an origin, and a position,
/// will return the scaled position in screen space coordinates.
///
- public static Vector2 GetScaledPosition(Vector2 scale, Vector2 origin, Vector2 position)
+ public static Vector2 GetScaledPosition(Vector2 scale, Vector2 origin, Vector2 position, float axisRotation = 0)
{
- return origin + (position - origin) * scale;
+ return origin + RotateVector(RotateVector(position - origin, axisRotation) * scale, -axisRotation);
}
///
From 979a5e9f3e5de341bcfe785ca7978f5449e0fd78 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Wed, 3 Jul 2024 16:41:41 +0200
Subject: [PATCH 0155/1274] simplify code
---
.../Edit/OsuSelectionScaleHandler.cs | 46 ++++++-------------
1 file changed, 13 insertions(+), 33 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs
index cfcf90e5f5..2cf5a604ed 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs
@@ -194,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Edit
///
/// Clamp scale for multi-object-scaling where selection does not exceed playfield bounds or flip.
- ///
+ /// The origin from which the scale operation is performed
/// The scale to be clamped
/// The axes to adjust the scale in.
@@ -215,54 +215,34 @@ namespace osu.Game.Rulesets.Osu.Edit
Vector2 actualOrigin = origin ?? defaultOrigin.Value;
var selectionQuad = OriginalSurroundingQuad.Value;
- scale = clampToBound(scale, selectionQuad.BottomRight, OsuPlayfield.BASE_SIZE.X, Axes.X);
- scale = clampToBound(scale, selectionQuad.BottomRight, OsuPlayfield.BASE_SIZE.Y, Axes.Y);
- scale = clampToBound(scale, selectionQuad.TopLeft, 0, Axes.X);
- scale = clampToBound(scale, selectionQuad.TopLeft, 0, Axes.Y);
+ scale = clampToBound(scale, selectionQuad.BottomRight, OsuPlayfield.BASE_SIZE);
+ scale = clampToBound(scale, selectionQuad.TopLeft, Vector2.Zero);
return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON));
- Vector2 clampToBound(Vector2 s, Vector2 p, float bound, Axes axis)
+ float minPositiveComponent(Vector2 v) => MathF.Min(v.X < 0 ? float.PositiveInfinity : v.X, v.Y < 0 ? float.PositiveInfinity : v.Y);
+
+ Vector2 clampToBound(Vector2 s, Vector2 p, Vector2 bound)
{
- float px = p.X - actualOrigin.X;
- float py = p.Y - actualOrigin.Y;
- float c = axis == Axes.X ? bound - actualOrigin.X : bound - actualOrigin.Y;
+ p -= actualOrigin;
+ bound -= actualOrigin;
float cos = MathF.Cos(float.DegreesToRadians(-axisRotation));
float sin = MathF.Sin(float.DegreesToRadians(-axisRotation));
- float a, b;
-
- if (axis == Axes.X)
- {
- a = cos * cos * px - sin * cos * py;
- b = sin * sin * px + sin * cos * py;
- }
- else
- {
- a = -sin * cos * px + sin * sin * py;
- b = sin * cos * px + cos * cos * py;
- }
+ var a = new Vector2(cos * cos * p.X - sin * cos * p.Y, -sin * cos * p.X + sin * sin * p.Y);
+ var b = new Vector2(sin * sin * p.X + sin * cos * p.Y, sin * cos * p.X + cos * cos * p.Y);
switch (adjustAxis)
{
case Axes.X:
- if (Precision.AlmostEquals(a, 0) || (c - b) / a < 0)
- break;
-
- s.X = MathF.Min(scale.X, (c - b) / a);
+ s.X = MathF.Min(scale.X, minPositiveComponent(Vector2.Divide(bound - b, a)));
break;
case Axes.Y:
- if (Precision.AlmostEquals(b, 0) || (c - a) / b < 0)
- break;
-
- s.Y = MathF.Min(scale.Y, (c - a) / b);
+ s.Y = MathF.Min(scale.Y, minPositiveComponent(Vector2.Divide(bound - a, b)));
break;
case Axes.Both:
- if (Precision.AlmostEquals(a + b, 0) || c / (a * s.X + b * s.Y) < 0)
- break;
-
- s = Vector2.ComponentMin(s, s * c / (a * s.X + b * s.Y));
+ s = Vector2.ComponentMin(s, s * minPositiveComponent(Vector2.Divide(bound, a * s.X + b * s.Y)));
break;
}
From 0797d942aecde4e267fca11fbec5bf73611fc9b8 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Wed, 3 Jul 2024 16:41:57 +0200
Subject: [PATCH 0156/1274] fix warning
---
osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs
index 2cf5a604ed..8b87246456 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs
@@ -194,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Edit
///
/// Clamp scale for multi-object-scaling where selection does not exceed playfield bounds or flip.
- ///
/// The origin from which the scale operation is performed
/// The scale to be clamped
/// The axes to adjust the scale in.
From 4165ded8134d05f4d6b934255a5678a6a7d74bca Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Wed, 3 Jul 2024 19:03:15 +0200
Subject: [PATCH 0157/1274] fix incorrect rotated bound checking
---
.../Edit/OsuSelectionScaleHandler.cs | 53 +++++++++++++++----
osu.Game/Utils/GeometryUtils.cs | 20 ++++---
2 files changed, 56 insertions(+), 17 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs
index 8b87246456..d336261499 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs
@@ -80,12 +80,32 @@ namespace osu.Game.Rulesets.Osu.Edit
changeHandler?.BeginChange();
objectsInScale = selectedMovableObjects.ToDictionary(ho => ho, ho => new OriginalHitObjectState(ho));
- OriginalSurroundingQuad = objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider
- ? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => slider.Position + p.Position))
- : GeometryUtils.GetSurroundingQuad(objectsInScale.Keys);
+ OriginalSurroundingQuad = getOriginalSurroundingQuad()!;
defaultOrigin = OriginalSurroundingQuad.Value.Centre;
}
+ private Quad? getOriginalSurroundingQuad(float axisRotation = 0)
+ {
+ if (objectsInScale == null)
+ return null;
+
+ return objectsInScale.Count == 1 && objectsInScale.First().Value.PathControlPointPositions != null
+ ? GeometryUtils.GetSurroundingQuad(objectsInScale.First().Value.PathControlPointPositions!.Select(p => objectsInScale.First().Value.Position + p), axisRotation)
+ : GeometryUtils.GetSurroundingQuad(objectsInScale.Values.SelectMany(s =>
+ {
+ if (s.EndPosition.HasValue)
+ {
+ return new[]
+ {
+ s.Position,
+ s.Position + s.EndPosition.Value
+ };
+ }
+
+ return new[] { s.Position };
+ }), axisRotation);
+ }
+
public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0)
{
if (!OperationInProgress.Value)
@@ -213,10 +233,23 @@ namespace osu.Game.Rulesets.Osu.Edit
scale = clampScaleToAdjustAxis(scale, adjustAxis);
Vector2 actualOrigin = origin ?? defaultOrigin.Value;
- var selectionQuad = OriginalSurroundingQuad.Value;
+ var selectionQuad = axisRotation == 0 ? OriginalSurroundingQuad.Value : getOriginalSurroundingQuad(axisRotation)!.Value;
+ var points = new[]
+ {
+ selectionQuad.TopLeft,
+ selectionQuad.TopRight,
+ selectionQuad.BottomLeft,
+ selectionQuad.BottomRight
+ };
- scale = clampToBound(scale, selectionQuad.BottomRight, OsuPlayfield.BASE_SIZE);
- scale = clampToBound(scale, selectionQuad.TopLeft, Vector2.Zero);
+ float cos = MathF.Cos(float.DegreesToRadians(-axisRotation));
+ float sin = MathF.Sin(float.DegreesToRadians(-axisRotation));
+
+ foreach (var point in points)
+ {
+ scale = clampToBound(scale, point, Vector2.Zero);
+ scale = clampToBound(scale, point, OsuPlayfield.BASE_SIZE);
+ }
return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON));
@@ -226,19 +259,17 @@ namespace osu.Game.Rulesets.Osu.Edit
{
p -= actualOrigin;
bound -= actualOrigin;
- float cos = MathF.Cos(float.DegreesToRadians(-axisRotation));
- float sin = MathF.Sin(float.DegreesToRadians(-axisRotation));
var a = new Vector2(cos * cos * p.X - sin * cos * p.Y, -sin * cos * p.X + sin * sin * p.Y);
var b = new Vector2(sin * sin * p.X + sin * cos * p.Y, sin * cos * p.X + cos * cos * p.Y);
switch (adjustAxis)
{
case Axes.X:
- s.X = MathF.Min(scale.X, minPositiveComponent(Vector2.Divide(bound - b, a)));
+ s.X = MathF.Min(s.X, minPositiveComponent(Vector2.Divide(bound - b, a)));
break;
case Axes.Y:
- s.Y = MathF.Min(scale.Y, minPositiveComponent(Vector2.Divide(bound - a, b)));
+ s.Y = MathF.Min(s.Y, minPositiveComponent(Vector2.Divide(bound - a, b)));
break;
case Axes.Both:
@@ -275,12 +306,14 @@ namespace osu.Game.Rulesets.Osu.Edit
public Vector2 Position { get; }
public Vector2[]? PathControlPointPositions { get; }
public PathType?[]? PathControlPointTypes { get; }
+ public Vector2? EndPosition { get; }
public OriginalHitObjectState(OsuHitObject hitObject)
{
Position = hitObject.Position;
PathControlPointPositions = (hitObject as IHasPath)?.Path.ControlPoints.Select(p => p.Position).ToArray();
PathControlPointTypes = (hitObject as IHasPath)?.Path.ControlPoints.Select(p => p.Type).ToArray();
+ EndPosition = (hitObject as IHasPath)?.Path.PositionAt(1);
}
}
}
diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs
index f6e7e81007..23c25cfffa 100644
--- a/osu.Game/Utils/GeometryUtils.cs
+++ b/osu.Game/Utils/GeometryUtils.cs
@@ -113,7 +113,8 @@ namespace osu.Game.Utils
/// Returns a quad surrounding the provided points.
///
/// The points to calculate a quad for.
- public static Quad GetSurroundingQuad(IEnumerable points)
+ /// The rotation in degrees of the axis to align the quad to.
+ public static Quad GetSurroundingQuad(IEnumerable points, float axisRotation = 0)
{
if (!points.Any())
return new Quad();
@@ -124,20 +125,25 @@ namespace osu.Game.Utils
// Go through all hitobjects to make sure they would remain in the bounds of the editor after movement, before any movement is attempted
foreach (var p in points)
{
- minPosition = Vector2.ComponentMin(minPosition, p);
- maxPosition = Vector2.ComponentMax(maxPosition, p);
+ var pr = RotateVector(p, axisRotation);
+ minPosition = Vector2.ComponentMin(minPosition, pr);
+ maxPosition = Vector2.ComponentMax(maxPosition, pr);
}
- Vector2 size = maxPosition - minPosition;
+ var p1 = RotateVector(minPosition, -axisRotation);
+ var p2 = RotateVector(new Vector2(minPosition.X, maxPosition.Y), -axisRotation);
+ var p3 = RotateVector(maxPosition, -axisRotation);
+ var p4 = RotateVector(new Vector2(maxPosition.X, minPosition.Y), -axisRotation);
- return new Quad(minPosition.X, minPosition.Y, size.X, size.Y);
+ return new Quad(p1, p2, p3, p4);
}
///
/// Returns a gamefield-space quad surrounding the provided hit objects.
///
/// The hit objects to calculate a quad for.
- public static Quad GetSurroundingQuad(IEnumerable hitObjects) =>
+ /// The rotation in degrees of the axis to align the quad to.
+ public static Quad GetSurroundingQuad(IEnumerable hitObjects, float axisRotation = 0) =>
GetSurroundingQuad(hitObjects.SelectMany(h =>
{
if (h is IHasPath path)
@@ -151,6 +157,6 @@ namespace osu.Game.Utils
}
return new[] { h.Position };
- }));
+ }), axisRotation);
}
}
From dfe6c70996b06fa0e597fffbf1aec75e5b1d508c Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Wed, 3 Jul 2024 19:08:31 +0200
Subject: [PATCH 0158/1274] prevent flipping objects far offscreen
---
osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
index 7720afe60a..7d6ef66909 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
@@ -15,6 +15,7 @@ using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.UI;
using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Utils;
using osuTK;
@@ -119,6 +120,9 @@ namespace osu.Game.Rulesets.Osu.Edit
{
var flippedPosition = GeometryUtils.GetFlippedPosition(flipAxis, flipQuad, h.Position);
+ // Clamp the flipped position inside the playfield bounds, because the flipped position might be outside the playfield bounds if the origin is not centered.
+ flippedPosition = Vector2.Clamp(flippedPosition, Vector2.Zero, OsuPlayfield.BASE_SIZE);
+
if (!Precision.AlmostEquals(flippedPosition, h.Position))
{
h.Position = flippedPosition;
From 3926af1053f5e4ef02b4caa7dcac1755d3658b3f Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Wed, 3 Jul 2024 20:17:39 +0200
Subject: [PATCH 0159/1274] Use draggable handle for length adjust
---
.../TestSceneSliderSelectionBlueprint.cs | 33 +++--
.../Blueprints/Sliders/SliderCircleOverlay.cs | 129 +++++++++++++++++-
.../Sliders/SliderSelectionBlueprint.cs | 57 ++++----
3 files changed, 173 insertions(+), 46 deletions(-)
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
index 812b34dfe2..c2589f11ef 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
@@ -3,7 +3,6 @@
#nullable disable
-using System.Linq;
using NUnit.Framework;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
@@ -165,23 +164,35 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
}
[Test]
- public void TestAdjustDistance()
+ public void TestAdjustLength()
{
- AddStep("start adjust length",
- () => blueprint.ContextMenuItems.Single(o => o.Text.Value == "Adjust length").Action.Value());
- moveMouseToControlPoint(1);
- AddStep("end adjust length", () => InputManager.Click(MouseButton.Right));
+ AddStep("move mouse to drag marker", () =>
+ {
+ Vector2 position = slider.Position + slider.Path.PositionAt(1) + new Vector2(60, 0);
+ InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
+ });
+ AddStep("start drag", () => InputManager.PressButton(MouseButton.Left));
+ AddStep("move mouse to control point 1", () =>
+ {
+ Vector2 position = slider.Position + slider.Path.ControlPoints[1].Position + new Vector2(60, 0);
+ InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
+ });
+ AddStep("end adjust length", () => InputManager.ReleaseButton(MouseButton.Left));
AddAssert("expected distance halved",
() => Precision.AlmostEquals(slider.Path.Distance, 172.2, 0.1));
- AddStep("start adjust length",
- () => blueprint.ContextMenuItems.Single(o => o.Text.Value == "Adjust length").Action.Value());
- AddStep("move mouse beyond last control point", () =>
+ AddStep("move mouse to drag marker", () =>
{
- Vector2 position = slider.Position + slider.Path.ControlPoints[2].Position + new Vector2(50, 0);
+ Vector2 position = slider.Position + slider.Path.PositionAt(1) + new Vector2(60, 0);
InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
});
- AddStep("end adjust length", () => InputManager.Click(MouseButton.Right));
+ AddStep("start drag", () => InputManager.PressButton(MouseButton.Left));
+ AddStep("move mouse beyond last control point", () =>
+ {
+ Vector2 position = slider.Position + slider.Path.ControlPoints[2].Position + new Vector2(100, 0);
+ InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
+ });
+ AddStep("end adjust length", () => InputManager.ReleaseButton(MouseButton.Left));
AddAssert("expected distance is calculated distance",
() => Precision.AlmostEquals(slider.Path.Distance, slider.Path.CalculatedDistance, 0.1));
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs
index 55ea131dab..9752ce4a13 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs
@@ -1,24 +1,47 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Lines;
+using osu.Framework.Graphics.Primitives;
+using osu.Framework.Input.Events;
+using osu.Framework.Utils;
+using osu.Game.Graphics;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects;
+using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
public partial class SliderCircleOverlay : CompositeDrawable
{
- protected readonly HitCirclePiece CirclePiece;
- protected readonly Slider Slider;
+ public RectangleF VisibleQuad
+ {
+ get
+ {
+ var result = CirclePiece.ScreenSpaceDrawQuad.AABBFloat;
- private readonly HitCircleOverlapMarker marker;
+ if (endDragMarkerContainer == null) return result;
+
+ var size = result.Size * 1.4f;
+ var location = result.TopLeft - result.Size * 0.2f;
+ return new RectangleF(location, size);
+ }
+ }
+
+ protected readonly HitCirclePiece CirclePiece;
+
+ private readonly Slider slider;
private readonly SliderPosition position;
+ private readonly HitCircleOverlapMarker marker;
+ private readonly Container? endDragMarkerContainer;
public SliderCircleOverlay(Slider slider, SliderPosition position)
{
- Slider = slider;
+ this.slider = slider;
this.position = position;
InternalChildren = new Drawable[]
@@ -26,27 +49,121 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
marker = new HitCircleOverlapMarker(),
CirclePiece = new HitCirclePiece(),
};
+
+ if (position == SliderPosition.End)
+ {
+ AddInternal(endDragMarkerContainer = new Container
+ {
+ AutoSizeAxes = Axes.Both,
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Padding = new MarginPadding(-2.5f),
+ Child = EndDragMarker = new SliderEndDragMarker()
+ });
+ }
}
+ public SliderEndDragMarker? EndDragMarker { get; }
+
protected override void Update()
{
base.Update();
- var circle = position == SliderPosition.Start ? (HitCircle)Slider.HeadCircle :
- Slider.RepeatCount % 2 == 0 ? Slider.TailCircle : Slider.LastRepeat!;
+ var circle = position == SliderPosition.Start ? (HitCircle)slider.HeadCircle :
+ slider.RepeatCount % 2 == 0 ? slider.TailCircle : slider.LastRepeat!;
CirclePiece.UpdateFrom(circle);
marker.UpdateFrom(circle);
+
+ if (endDragMarkerContainer != null)
+ {
+ endDragMarkerContainer.Position = circle.Position;
+ endDragMarkerContainer.Scale = CirclePiece.Scale * 1.2f;
+ var diff = slider.Path.PositionAt(1) - slider.Path.PositionAt(0.99f);
+ endDragMarkerContainer.Rotation = float.RadiansToDegrees(MathF.Atan2(diff.Y, diff.X));
+ }
}
public override void Hide()
{
CirclePiece.Hide();
+ endDragMarkerContainer?.Hide();
}
public override void Show()
{
CirclePiece.Show();
+ endDragMarkerContainer?.Show();
+ }
+
+ public partial class SliderEndDragMarker : SmoothPath
+ {
+ public Action? StartDrag { get; set; }
+ public Action? Drag { get; set; }
+ public Action? EndDrag { get; set; }
+
+ [Resolved]
+ private OsuColour colours { get; set; } = null!;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ var path = PathApproximator.CircularArcToPiecewiseLinear([
+ new Vector2(0, OsuHitObject.OBJECT_RADIUS),
+ new Vector2(OsuHitObject.OBJECT_RADIUS, 0),
+ new Vector2(0, -OsuHitObject.OBJECT_RADIUS)
+ ]);
+
+ Anchor = Anchor.CentreLeft;
+ Origin = Anchor.CentreLeft;
+ PathRadius = 5;
+ Vertices = path;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ updateState();
+ }
+
+ protected override bool OnHover(HoverEvent e)
+ {
+ updateState();
+ return true;
+ }
+
+ protected override void OnHoverLost(HoverLostEvent e)
+ {
+ updateState();
+ base.OnHoverLost(e);
+ }
+
+ protected override bool OnDragStart(DragStartEvent e)
+ {
+ updateState();
+ StartDrag?.Invoke(e);
+ return true;
+ }
+
+ protected override void OnDrag(DragEvent e)
+ {
+ updateState();
+ base.OnDrag(e);
+ Drag?.Invoke(e);
+ }
+
+ protected override void OnDragEnd(DragEndEvent e)
+ {
+ updateState();
+ EndDrag?.Invoke();
+ base.OnDragEnd(e);
+ }
+
+ private void updateState()
+ {
+ Colour = IsHovered || IsDragged ? colours.Red : colours.Yellow;
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
index eb269ba680..87f9fd41e8 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
@@ -55,7 +55,18 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
[Resolved(CanBeNull = true)]
private BindableBeatDivisor beatDivisor { get; set; }
- public override Quad SelectionQuad => BodyPiece.ScreenSpaceDrawQuad;
+ public override Quad SelectionQuad
+ {
+ get
+ {
+ var result = BodyPiece.ScreenSpaceDrawQuad.AABBFloat;
+
+ result = RectangleF.Union(result, HeadOverlay.VisibleQuad);
+ result = RectangleF.Union(result, TailOverlay.VisibleQuad);
+
+ return result;
+ }
+ }
private readonly BindableList controlPoints = new BindableList();
private readonly IBindable pathVersion = new Bindable();
@@ -63,7 +74,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
// Cached slider path which ignored the expected distance value.
private readonly Cached fullPathCache = new Cached();
- private bool isAdjustingLength;
public SliderSelectionBlueprint(Slider slider)
: base(slider)
@@ -79,6 +89,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
HeadOverlay = CreateCircleOverlay(HitObject, SliderPosition.Start),
TailOverlay = CreateCircleOverlay(HitObject, SliderPosition.End),
};
+
+ TailOverlay.EndDragMarker!.StartDrag += startAdjustingLength;
+ TailOverlay.EndDragMarker.Drag += adjustLength;
+ TailOverlay.EndDragMarker.EndDrag += endAdjustLength;
}
protected override void LoadComplete()
@@ -141,9 +155,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
base.OnDeselected();
- if (isAdjustingLength)
- endAdjustLength();
-
updateVisualDefinition();
BodyPiece.RecyclePath();
}
@@ -173,12 +184,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
protected override bool OnMouseDown(MouseDownEvent e)
{
- if (isAdjustingLength)
- {
- endAdjustLength();
- return true;
- }
-
switch (e.Button)
{
case MouseButton.Right:
@@ -202,18 +207,22 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
return false;
}
+ private Vector2 lengthAdjustMouseOffset;
+
+ private void startAdjustingLength(DragStartEvent e)
+ {
+ lengthAdjustMouseOffset = ToLocalSpace(e.ScreenSpaceMouseDownPosition) - HitObject.Position - HitObject.Path.PositionAt(1);
+ changeHandler?.BeginChange();
+ }
+
private void endAdjustLength()
{
trimExcessControlPoints(HitObject.Path);
- isAdjustingLength = false;
changeHandler?.EndChange();
}
- protected override bool OnMouseMove(MouseMoveEvent e)
+ private void adjustLength(MouseEvent e)
{
- if (!isAdjustingLength)
- return base.OnMouseMove(e);
-
double oldDistance = HitObject.Path.Distance;
double proposedDistance = findClosestPathDistance(e);
@@ -223,13 +232,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
10 * oldDistance / HitObject.SliderVelocityMultiplier);
if (Precision.AlmostEquals(proposedDistance, oldDistance))
- return false;
+ return;
HitObject.SliderVelocityMultiplier *= proposedDistance / oldDistance;
HitObject.Path.ExpectedDistance.Value = proposedDistance;
editorBeatmap?.Update(HitObject);
-
- return false;
}
///
@@ -262,12 +269,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
///
/// Finds the expected distance value for which the slider end is closest to the mouse position.
///
- private double findClosestPathDistance(MouseMoveEvent e)
+ private double findClosestPathDistance(MouseEvent e)
{
const double step1 = 10;
const double step2 = 0.1;
- var desiredPosition = e.MousePosition - HitObject.Position;
+ var desiredPosition = ToLocalSpace(e.ScreenSpaceMousePosition) - HitObject.Position - lengthAdjustMouseOffset;
if (!fullPathCache.IsValid)
fullPathCache.Value = new SliderPath(HitObject.Path.ControlPoints.ToArray());
@@ -525,11 +532,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
addControlPoint(rightClickPosition);
changeHandler?.EndChange();
}),
- new OsuMenuItem("Adjust length", MenuItemType.Standard, () =>
- {
- isAdjustingLength = true;
- changeHandler?.BeginChange();
- }),
new OsuMenuItem("Convert to stream", MenuItemType.Destructive, convertToStream),
};
@@ -544,9 +546,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
{
- if (isAdjustingLength)
- return true;
-
if (BodyPiece.ReceivePositionalInputAt(screenSpacePos))
return true;
From 5697c82bb87cc58460fe053f27039a5bb9dbaf84 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Wed, 3 Jul 2024 20:33:00 +0200
Subject: [PATCH 0160/1274] add a small bias towards longer distances to
prevent jittery behaviour on path self-intersections
---
.../Blueprints/Sliders/SliderSelectionBlueprint.cs | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
index 87f9fd41e8..586ba5b6b1 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
@@ -3,6 +3,7 @@
#nullable disable
+using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
@@ -273,6 +274,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
const double step1 = 10;
const double step2 = 0.1;
+ const double longer_distance_bias = 0.01;
var desiredPosition = ToLocalSpace(e.ScreenSpaceMousePosition) - HitObject.Position - lengthAdjustMouseOffset;
@@ -286,7 +288,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
for (double d = 0; d <= fullPathCache.Value.CalculatedDistance; d += step1)
{
double t = d / fullPathCache.Value.CalculatedDistance;
- float dist = Vector2.Distance(fullPathCache.Value.PositionAt(t), desiredPosition);
+ double dist = Vector2.Distance(fullPathCache.Value.PositionAt(t), desiredPosition) - d * longer_distance_bias;
if (dist >= minDistance) continue;
@@ -295,10 +297,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
}
// Do another linear search to fine-tune the result.
- for (double d = bestValue - step1; d <= bestValue + step1; d += step2)
+ double maxValue = Math.Min(bestValue + step1, fullPathCache.Value.CalculatedDistance);
+
+ for (double d = bestValue - step1; d <= maxValue; d += step2)
{
double t = d / fullPathCache.Value.CalculatedDistance;
- float dist = Vector2.Distance(fullPathCache.Value.PositionAt(t), desiredPosition);
+ double dist = Vector2.Distance(fullPathCache.Value.PositionAt(t), desiredPosition) - d * longer_distance_bias;
if (dist >= minDistance) continue;
From b9c6674a5885f6fda92acf72002d68f78006a47e Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Thu, 4 Jul 2024 11:47:45 +0200
Subject: [PATCH 0161/1274] Allow seeking to sample point on double-click
---
.../Components/Timeline/NodeSamplePointPiece.cs | 10 ++++++++++
.../Compose/Components/Timeline/SamplePointPiece.cs | 10 ++++++++++
2 files changed, 20 insertions(+)
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs
index ae3838bc41..e9999df76d 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs
@@ -2,7 +2,9 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
+using osu.Framework.Extensions;
using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Input.Events;
using osu.Game.Audio;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
@@ -22,6 +24,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
NodeIndex = nodeIndex;
}
+ protected override bool OnDoubleClick(DoubleClickEvent e)
+ {
+ var hasRepeats = (IHasRepeats)HitObject;
+ EditorClock?.SeekSmoothlyTo(HitObject.StartTime + hasRepeats.Duration * NodeIndex / hasRepeats.SpanCount());
+ this.ShowPopover();
+ return true;
+ }
+
protected override IList GetSamples()
{
var hasRepeats = (IHasRepeats)HitObject;
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs
index 930b78b468..0507f3d3d0 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs
@@ -32,6 +32,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{
public readonly HitObject HitObject;
+ [Resolved]
+ protected EditorClock? EditorClock { get; private set; }
+
public SamplePointPiece(HitObject hitObject)
{
HitObject = hitObject;
@@ -54,6 +57,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
return true;
}
+ protected override bool OnDoubleClick(DoubleClickEvent e)
+ {
+ EditorClock?.SeekSmoothlyTo(HitObject.StartTime);
+ this.ShowPopover();
+ return true;
+ }
+
private void updateText()
{
Label.Text = $"{abbreviateBank(GetBankValue(GetSamples()))} {GetVolumeValue(GetSamples())}";
From 00f7a34139f1a8c4d2e0112b23b7e26464893495 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Thu, 4 Jul 2024 15:25:43 +0200
Subject: [PATCH 0162/1274] Add test coverage
---
.../TestSceneHitObjectSampleAdjustments.cs | 55 +++++++++++++++++++
1 file changed, 55 insertions(+)
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs
index 9988c1cb59..28bafb79ee 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs
@@ -7,6 +7,7 @@ using Humanizer;
using NUnit.Framework;
using osu.Framework.Input;
using osu.Framework.Testing;
+using osu.Framework.Utils;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
@@ -307,6 +308,40 @@ namespace osu.Game.Tests.Visual.Editing
hitObjectNodeHasSampleVolume(0, 1, 10);
}
+ [Test]
+ public void TestSamplePointSeek()
+ {
+ AddStep("add slider", () =>
+ {
+ EditorBeatmap.Clear();
+ EditorBeatmap.Add(new Slider
+ {
+ Position = new Vector2(256, 256),
+ StartTime = 0,
+ Path = new SliderPath(new[] { new PathControlPoint(Vector2.Zero), new PathControlPoint(new Vector2(250, 0)) }),
+ Samples =
+ {
+ new HitSampleInfo(HitSampleInfo.HIT_NORMAL)
+ },
+ NodeSamples =
+ {
+ new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) },
+ new List { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) },
+ },
+ RepeatCount = 1
+ });
+ });
+
+ doubleClickNodeSamplePiece(0, 0);
+ editorTimeIs(0);
+ doubleClickNodeSamplePiece(0, 1);
+ editorTimeIs(813);
+ doubleClickNodeSamplePiece(0, 2);
+ editorTimeIs(1627);
+ doubleClickSamplePiece(0);
+ editorTimeIs(0);
+ }
+
[Test]
public void TestHotkeysMultipleSelectionWithSameSampleBank()
{
@@ -500,6 +535,24 @@ namespace osu.Game.Tests.Visual.Editing
InputManager.Click(MouseButton.Left);
});
+ private void doubleClickSamplePiece(int objectIndex) => AddStep($"double-click {objectIndex.ToOrdinalWords()} sample piece", () =>
+ {
+ var samplePiece = this.ChildrenOfType().Single(piece => piece is not NodeSamplePointPiece && piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex));
+
+ InputManager.MoveMouseTo(samplePiece);
+ InputManager.Click(MouseButton.Left);
+ InputManager.Click(MouseButton.Left);
+ });
+
+ private void doubleClickNodeSamplePiece(int objectIndex, int nodeIndex) => AddStep($"double-click {objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node sample piece", () =>
+ {
+ var samplePiece = this.ChildrenOfType().Where(piece => piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex)).ToArray()[nodeIndex];
+
+ InputManager.MoveMouseTo(samplePiece);
+ InputManager.Click(MouseButton.Left);
+ InputManager.Click(MouseButton.Left);
+ });
+
private void samplePopoverHasNoFocus() => AddUntilStep("sample popover textbox not focused", () =>
{
var popover = this.ChildrenOfType().SingleOrDefault();
@@ -644,5 +697,7 @@ namespace osu.Game.Tests.Visual.Editing
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats;
return h is not null && h.NodeSamples[nodeIndex].Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.Bank == bank);
});
+
+ private void editorTimeIs(double time) => AddAssert($"editor time is {time}", () => Precision.AlmostEquals(EditorClock.CurrentTimeAccurate, time, 1));
}
}
From fae8f5f81b4d2de10fe1e2f2e58f0258158a794b Mon Sep 17 00:00:00 2001
From: smallketchup82
Date: Thu, 4 Jul 2024 17:28:49 -0400
Subject: [PATCH 0163/1274] Refactor VeloUpdateManager
---
osu.Desktop/Updater/VeloUpdateManager.cs | 49 +++++++++++-------------
1 file changed, 23 insertions(+), 26 deletions(-)
diff --git a/osu.Desktop/Updater/VeloUpdateManager.cs b/osu.Desktop/Updater/VeloUpdateManager.cs
index 8fc68f77cd..137e48f135 100644
--- a/osu.Desktop/Updater/VeloUpdateManager.cs
+++ b/osu.Desktop/Updater/VeloUpdateManager.cs
@@ -10,12 +10,13 @@ using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Screens.Play;
using osu.Game.Updater;
+using Velopack.Sources;
namespace osu.Desktop.Updater
{
public partial class VeloUpdateManager : UpdateManager
{
- private Velopack.UpdateManager? updateManager;
+ private readonly Velopack.UpdateManager updateManager;
private INotificationOverlay notificationOverlay = null!;
[Resolved]
@@ -24,6 +25,12 @@ namespace osu.Desktop.Updater
[Resolved]
private ILocalUserPlayInfo? localUserInfo { get; set; }
+ public VeloUpdateManager()
+ {
+ const string? github_token = null; // TODO: populate.
+ updateManager = new Velopack.UpdateManager(new GithubSource(@"https://github.com/ppy/osu", github_token, false));
+ }
+
[BackgroundDependencyLoader]
private void load(INotificationOverlay notifications)
{
@@ -37,36 +44,30 @@ namespace osu.Desktop.Updater
// should we schedule a retry on completion of this check?
bool scheduleRecheck = true;
- const string? github_token = null; // TODO: populate.
-
try
{
// Avoid any kind of update checking while gameplay is running.
if (localUserInfo?.IsPlaying.Value == true)
return false;
- updateManager ??= new Velopack.UpdateManager(new Velopack.Sources.GithubSource(@"https://github.com/ppy/osu", github_token, false));
-
var info = await updateManager.CheckForUpdatesAsync().ConfigureAwait(false);
+ // Handle no updates available.
if (info == null)
{
- // If there is an update pending restart, show the notification again.
- if (updateManager.IsUpdatePendingRestart)
- {
- notificationOverlay.Post(new UpdateApplicationCompleteNotification
- {
- Activated = () =>
- {
- restartToApplyUpdate();
- return true;
- }
- });
- return true;
- }
+ // If there's no updates pending restart, bail and retry later.
+ if (!updateManager.IsUpdatePendingRestart) return false;
- // Otherwise there's no updates available. Bail and retry later.
- return false;
+ // If there is an update pending restart, show the notification to restart again.
+ notificationOverlay.Post(new UpdateApplicationCompleteNotification
+ {
+ Activated = () =>
+ {
+ restartToApplyUpdate();
+ return true;
+ }
+ });
+ return true;
}
scheduleRecheck = false;
@@ -87,8 +88,6 @@ namespace osu.Desktop.Updater
{
await updateManager.DownloadUpdatesAsync(info, p => notification.Progress = p / 100f).ConfigureAwait(false);
- notification.StartInstall();
-
notification.State = ProgressNotificationState.Completed;
}
catch (Exception e)
@@ -98,10 +97,11 @@ namespace osu.Desktop.Updater
Logger.Error(e, @"update failed!");
}
}
- catch (Exception)
+ catch (Exception e)
{
// we'll ignore this and retry later. can be triggered by no internet connection or thread abortion.
scheduleRecheck = true;
+ Logger.Error(e, @"update check failed!");
}
finally
{
@@ -117,9 +117,6 @@ namespace osu.Desktop.Updater
private bool restartToApplyUpdate()
{
- if (updateManager == null)
- return false;
-
updateManager.WaitExitThenApplyUpdates(null);
Schedule(() => game.AttemptExit());
return true;
From cae3607caf0d9322497c7be8c0bab4a9d2314cee Mon Sep 17 00:00:00 2001
From: smallketchup82
Date: Thu, 4 Jul 2024 17:30:42 -0400
Subject: [PATCH 0164/1274] Fix up restarting
Earlier I changed the restarting logic to not wait until the program
exits and instead try to facilitate restarting alone. This did not work,
and it became clear we'd need Velopack to do the restarting. This
reverts back and supposedly brings restarting logic in line with how
Velopack does it
---
osu.Desktop/OsuGameDesktop.cs | 13 +++----------
.../Screens/Setup/TournamentSwitcher.cs | 3 ++-
osu.Game/OsuGameBase.cs | 2 +-
.../Settings/Sections/Graphics/RendererSettings.cs | 3 ++-
4 files changed, 8 insertions(+), 13 deletions(-)
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index 28f3d3dc5d..c0c8d1e504 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.Versioning;
@@ -104,23 +103,17 @@ namespace osu.Desktop
return new VeloUpdateManager();
}
- public override bool RestartApp()
+ public override bool RestartAppWhenExited()
{
try
{
- var startInfo = new ProcessStartInfo
- {
- FileName = Process.GetCurrentProcess().MainModule!.FileName,
- UseShellExecute = true
- };
- Process.Start(startInfo);
- base.AttemptExit();
+ Velopack.UpdateExe.Start(null, true);
return true;
}
catch (Exception e)
{
Logger.Error(e, "Failed to restart application");
- return base.RestartApp();
+ return base.RestartAppWhenExited();
}
}
diff --git a/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs b/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs
index 69ead451ba..e55cbc2dbb 100644
--- a/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs
+++ b/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs
@@ -31,7 +31,8 @@ namespace osu.Game.Tournament.Screens.Setup
Action = () =>
{
- game.RestartApp();
+ game.RestartAppWhenExited();
+ game.AttemptExit();
};
folderButton.Action = () => storage.PresentExternally();
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index 573695613d..5e4ec5a61d 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -513,7 +513,7 @@ namespace osu.Game
/// If supported by the platform, the game will automatically restart after the next exit.
///
/// Whether a restart operation was queued.
- public virtual bool RestartApp() => false;
+ public virtual bool RestartAppWhenExited() => false;
public bool Migrate(string path)
{
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs
index 17e345c2c8..a8b127d522 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs
@@ -67,8 +67,9 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
if (r.NewValue == RendererType.Automatic && automaticRendererInUse)
return;
- if (game?.RestartApp() == true)
+ if (game?.RestartAppWhenExited() == true)
{
+ game.AttemptExit();
}
else
{
From c13f24d553a829b0d55ccdad733cee43d1509b5d Mon Sep 17 00:00:00 2001
From: smallketchup82
Date: Thu, 4 Jul 2024 17:32:30 -0400
Subject: [PATCH 0165/1274] Remove InstallingUpdate progress notification
Velopack won't install the updates while the program is open, it'll do
it in between restarts or before starting.
---
osu.Game/Localisation/NotificationsStrings.cs | 5 -----
osu.Game/Updater/UpdateManager.cs | 6 ------
2 files changed, 11 deletions(-)
diff --git a/osu.Game/Localisation/NotificationsStrings.cs b/osu.Game/Localisation/NotificationsStrings.cs
index 698fe230b2..d8f768f2d8 100644
--- a/osu.Game/Localisation/NotificationsStrings.cs
+++ b/osu.Game/Localisation/NotificationsStrings.cs
@@ -135,11 +135,6 @@ Click to see what's new!", version);
///
public static LocalisableString DownloadingUpdate => new TranslatableString(getKey(@"downloading_update"), @"Downloading update...");
- ///
- /// "Installing update..."
- ///
- public static LocalisableString InstallingUpdate => new TranslatableString(getKey(@"installing_update"), @"Installing update...");
-
private static string getKey(string key) => $@"{prefix}:{key}";
}
}
diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs
index bcb28d8b14..c114e3a8d0 100644
--- a/osu.Game/Updater/UpdateManager.cs
+++ b/osu.Game/Updater/UpdateManager.cs
@@ -176,12 +176,6 @@ namespace osu.Game.Updater
Text = NotificationsStrings.DownloadingUpdate;
}
- public void StartInstall()
- {
- Progress = 0;
- Text = NotificationsStrings.InstallingUpdate;
- }
-
public void FailDownload()
{
State = ProgressNotificationState.Cancelled;
From 461b791532ee9abebebc615d2e23f5a38f7324c8 Mon Sep 17 00:00:00 2001
From: smallketchup82
Date: Thu, 4 Jul 2024 17:32:56 -0400
Subject: [PATCH 0166/1274] Remove SimpleUpdateManager
No longer needed. Velopack supports the platforms that this covers for
---
osu.Game/Updater/SimpleUpdateManager.cs | 116 ------------------------
1 file changed, 116 deletions(-)
delete mode 100644 osu.Game/Updater/SimpleUpdateManager.cs
diff --git a/osu.Game/Updater/SimpleUpdateManager.cs b/osu.Game/Updater/SimpleUpdateManager.cs
deleted file mode 100644
index 0f9d5b929f..0000000000
--- a/osu.Game/Updater/SimpleUpdateManager.cs
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System;
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-using System.Runtime.InteropServices;
-using System.Threading.Tasks;
-using osu.Framework;
-using osu.Framework.Allocation;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Platform;
-using osu.Game.Online.API;
-using osu.Game.Overlays.Notifications;
-
-namespace osu.Game.Updater
-{
- ///
- /// An update manager that shows notifications if a newer release is detected.
- /// Installation is left up to the user.
- ///
- public partial class SimpleUpdateManager : UpdateManager
- {
- private string version = null!;
-
- [Resolved]
- private GameHost host { get; set; } = null!;
-
- [BackgroundDependencyLoader]
- private void load(OsuGameBase game)
- {
- version = game.Version;
- }
-
- protected override async Task PerformUpdateCheck()
- {
- try
- {
- var releases = new OsuJsonWebRequest("https://api.github.com/repos/ppy/osu/releases/latest");
-
- await releases.PerformAsync().ConfigureAwait(false);
-
- var latest = releases.ResponseObject;
-
- // avoid any discrepancies due to build suffixes for now.
- // eventually we will want to support release streams and consider these.
- version = version.Split('-').First();
- string latestTagName = latest.TagName.Split('-').First();
-
- if (latestTagName != version && tryGetBestUrl(latest, out string? url))
- {
- Notifications.Post(new SimpleNotification
- {
- Text = $"A newer release of osu! has been found ({version} → {latestTagName}).\n\n"
- + "Click here to download the new version, which can be installed over the top of your existing installation",
- Icon = FontAwesome.Solid.Download,
- Activated = () =>
- {
- host.OpenUrlExternally(url);
- return true;
- }
- });
-
- return true;
- }
- }
- catch
- {
- // we shouldn't crash on a web failure. or any failure for the matter.
- return true;
- }
-
- return false;
- }
-
- private bool tryGetBestUrl(GitHubRelease release, [NotNullWhen(true)] out string? url)
- {
- url = null;
- GitHubAsset? bestAsset = null;
-
- switch (RuntimeInfo.OS)
- {
- case RuntimeInfo.Platform.Windows:
- bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".exe", StringComparison.Ordinal));
- break;
-
- case RuntimeInfo.Platform.macOS:
- string arch = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "Apple.Silicon" : "Intel";
- bestAsset = release.Assets?.Find(f => f.Name.EndsWith($".app.{arch}.zip", StringComparison.Ordinal));
- break;
-
- case RuntimeInfo.Platform.Linux:
- bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".AppImage", StringComparison.Ordinal));
- break;
-
- case RuntimeInfo.Platform.iOS:
- if (release.Assets?.Exists(f => f.Name.EndsWith(".ipa", StringComparison.Ordinal)) == true)
- // iOS releases are available via testflight. this link seems to work well enough for now.
- // see https://stackoverflow.com/a/32960501
- url = "itms-beta://beta.itunes.apple.com/v1/app/1447765923";
-
- break;
-
- case RuntimeInfo.Platform.Android:
- if (release.Assets?.Exists(f => f.Name.EndsWith(".apk", StringComparison.Ordinal)) == true)
- // on our testing device using the .apk URL causes the download to magically disappear.
- url = release.HtmlUrl;
-
- break;
- }
-
- url ??= bestAsset?.BrowserDownloadUrl;
- return url != null;
- }
- }
-}
From 9e01cf7fc23812c4d0a76b09dd78ce6b38c06028 Mon Sep 17 00:00:00 2001
From: smallketchup82
Date: Thu, 4 Jul 2024 17:33:05 -0400
Subject: [PATCH 0167/1274] Move setupVelo logic higher up
---
osu.Desktop/Program.cs | 18 +++---------------
1 file changed, 3 insertions(+), 15 deletions(-)
diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs
index 7c23c15d5a..92c8f2104c 100644
--- a/osu.Desktop/Program.cs
+++ b/osu.Desktop/Program.cs
@@ -30,19 +30,9 @@ namespace osu.Desktop
[STAThread]
public static void Main(string[] args)
{
- /*
- * WARNING: DO NOT PLACE **ANY** CODE ABOVE THE FOLLOWING BLOCK!
- *
- * Logic handling Squirrel MUST run before EVERYTHING if you do not want to break it.
- * To be more precise: Squirrel is internally using a rather... crude method to determine whether it is running under NUnit,
- * namely by checking loaded assemblies:
- * https://github.com/clowd/Clowd.Squirrel/blob/24427217482deeeb9f2cacac555525edfc7bd9ac/src/Squirrel/SimpleSplat/PlatformModeDetector.cs#L17-L32
- *
- * If it finds ANY assembly from the ones listed above - REGARDLESS of the reason why it is loaded -
- * the app will then do completely broken things like:
- * - not creating system shortcuts (as the logic is if'd out if "running tests")
- * - not exiting after the install / first-update / uninstall hooks are ran (as the `Environment.Exit()` calls are if'd out if "running tests")
- */
+ // Velopack needs to run before anything else
+ setupVelo();
+
if (OperatingSystem.IsWindows())
{
var windowsVersion = Environment.OSVersion.Version;
@@ -67,8 +57,6 @@ namespace osu.Desktop
}
}
- setupVelo();
-
// NVIDIA profiles are based on the executable name of a process.
// Lazer and stable share the same executable name.
// Stable sets this setting to "Off", which may not be what we want, so let's force it back to the default "Auto" on startup.
From 6a0309294440dfd7136d2ceed72ca19e1be951eb Mon Sep 17 00:00:00 2001
From: smallketchup82
Date: Thu, 4 Jul 2024 17:45:34 -0400
Subject: [PATCH 0168/1274] Reformat
---
osu.Desktop/Updater/VeloUpdateManager.cs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/osu.Desktop/Updater/VeloUpdateManager.cs b/osu.Desktop/Updater/VeloUpdateManager.cs
index 137e48f135..8aa580caa8 100644
--- a/osu.Desktop/Updater/VeloUpdateManager.cs
+++ b/osu.Desktop/Updater/VeloUpdateManager.cs
@@ -92,9 +92,9 @@ namespace osu.Desktop.Updater
}
catch (Exception e)
{
- // In the case of an error, a separate notification will be displayed.
- notification.FailDownload();
- Logger.Error(e, @"update failed!");
+ // In the case of an error, a separate notification will be displayed.
+ notification.FailDownload();
+ Logger.Error(e, @"update failed!");
}
}
catch (Exception e)
From 72cf6bb12c71405464ce606a0334b2d6836c1bbc Mon Sep 17 00:00:00 2001
From: smallketchup82
Date: Thu, 4 Jul 2024 18:00:45 -0400
Subject: [PATCH 0169/1274] Allow downgrading
Also better address UpdateManager conflict
---
osu.Desktop/Updater/VeloUpdateManager.cs | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/osu.Desktop/Updater/VeloUpdateManager.cs b/osu.Desktop/Updater/VeloUpdateManager.cs
index 8aa580caa8..6d3eb3f3f0 100644
--- a/osu.Desktop/Updater/VeloUpdateManager.cs
+++ b/osu.Desktop/Updater/VeloUpdateManager.cs
@@ -9,14 +9,15 @@ using osu.Game;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Screens.Play;
-using osu.Game.Updater;
+using Velopack;
using Velopack.Sources;
+using UpdateManager = Velopack.UpdateManager;
namespace osu.Desktop.Updater
{
- public partial class VeloUpdateManager : UpdateManager
+ public partial class VeloUpdateManager : Game.Updater.UpdateManager
{
- private readonly Velopack.UpdateManager updateManager;
+ private readonly UpdateManager updateManager;
private INotificationOverlay notificationOverlay = null!;
[Resolved]
@@ -28,7 +29,10 @@ namespace osu.Desktop.Updater
public VeloUpdateManager()
{
const string? github_token = null; // TODO: populate.
- updateManager = new Velopack.UpdateManager(new GithubSource(@"https://github.com/ppy/osu", github_token, false));
+ updateManager = new UpdateManager(new GithubSource(@"https://github.com/ppy/osu", github_token, false), new UpdateOptions
+ {
+ AllowVersionDowngrade = true
+ });
}
[BackgroundDependencyLoader]
From 4898cff7a4186c3202cdef540c2480b48e22d55d Mon Sep 17 00:00:00 2001
From: smallketchup82
Date: Thu, 4 Jul 2024 18:25:02 -0400
Subject: [PATCH 0170/1274] Restart patch
---
osu.Desktop/OsuGameDesktop.cs | 2 +-
osu.Desktop/osu.Desktop.csproj | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index c0c8d1e504..ee73c84ba3 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -107,7 +107,7 @@ namespace osu.Desktop
{
try
{
- Velopack.UpdateExe.Start(null, true);
+ Velopack.UpdateExe.Start();
return true;
}
catch (Exception e)
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index 7df82e1281..7a2bb599fd 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -26,7 +26,7 @@
-
+
From 71816c09dccf0a17932acb647e749190371e84a0 Mon Sep 17 00:00:00 2001
From: smallketchup82
Date: Fri, 5 Jul 2024 03:29:09 -0400
Subject: [PATCH 0171/1274] Resurrect SimpleUpdateManager as
MobileUpdateNotifier
While removing the desktop specific logic from it
---
osu.Android/OsuGameAndroid.cs | 2 +-
osu.Game/Updater/MobileUpdateNotifier.cs | 102 +++++++++++++++++++++++
osu.iOS/OsuGameIOS.cs | 2 +-
3 files changed, 104 insertions(+), 2 deletions(-)
create mode 100644 osu.Game/Updater/MobileUpdateNotifier.cs
diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs
index a235913ef3..ffab7dd86d 100644
--- a/osu.Android/OsuGameAndroid.cs
+++ b/osu.Android/OsuGameAndroid.cs
@@ -80,7 +80,7 @@ namespace osu.Android
host.Window.CursorState |= CursorState.Hidden;
}
- protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager();
+ protected override UpdateManager CreateUpdateManager() => new MobileUpdateNotifier();
protected override BatteryInfo CreateBatteryInfo() => new AndroidBatteryInfo();
diff --git a/osu.Game/Updater/MobileUpdateNotifier.cs b/osu.Game/Updater/MobileUpdateNotifier.cs
new file mode 100644
index 0000000000..04b54df3c0
--- /dev/null
+++ b/osu.Game/Updater/MobileUpdateNotifier.cs
@@ -0,0 +1,102 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Threading.Tasks;
+using osu.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Platform;
+using osu.Game.Online.API;
+using osu.Game.Overlays.Notifications;
+
+namespace osu.Game.Updater
+{
+ ///
+ /// An update manager that shows notifications if a newer release is detected for mobile platforms.
+ /// Installation is left up to the user.
+ ///
+ public partial class MobileUpdateNotifier : UpdateManager
+ {
+ private string version = null!;
+
+ [Resolved]
+ private GameHost host { get; set; } = null!;
+
+ [BackgroundDependencyLoader]
+ private void load(OsuGameBase game)
+ {
+ version = game.Version;
+ }
+
+ protected override async Task PerformUpdateCheck()
+ {
+ try
+ {
+ var releases = new OsuJsonWebRequest("https://api.github.com/repos/ppy/osu/releases/latest");
+
+ await releases.PerformAsync().ConfigureAwait(false);
+
+ var latest = releases.ResponseObject;
+
+ // avoid any discrepancies due to build suffixes for now.
+ // eventually we will want to support release streams and consider these.
+ version = version.Split('-').First();
+ string latestTagName = latest.TagName.Split('-').First();
+
+ if (latestTagName != version && tryGetBestUrl(latest, out string? url))
+ {
+ Notifications.Post(new SimpleNotification
+ {
+ Text = $"A newer release of osu! has been found ({version} → {latestTagName}).\n\n"
+ + "Click here to download the new version, which can be installed over the top of your existing installation",
+ Icon = FontAwesome.Solid.Download,
+ Activated = () =>
+ {
+ host.OpenUrlExternally(url);
+ return true;
+ }
+ });
+
+ return true;
+ }
+ }
+ catch
+ {
+ // we shouldn't crash on a web failure. or any failure for the matter.
+ return true;
+ }
+
+ return false;
+ }
+
+ private bool tryGetBestUrl(GitHubRelease release, [NotNullWhen(true)] out string? url)
+ {
+ url = null;
+ GitHubAsset? bestAsset = null;
+
+ switch (RuntimeInfo.OS)
+ {
+ case RuntimeInfo.Platform.iOS:
+ if (release.Assets?.Exists(f => f.Name.EndsWith(".ipa", StringComparison.Ordinal)) == true)
+ // iOS releases are available via testflight. this link seems to work well enough for now.
+ // see https://stackoverflow.com/a/32960501
+ url = "itms-beta://beta.itunes.apple.com/v1/app/1447765923";
+
+ break;
+
+ case RuntimeInfo.Platform.Android:
+ if (release.Assets?.Exists(f => f.Name.EndsWith(".apk", StringComparison.Ordinal)) == true)
+ // on our testing device using the .apk URL causes the download to magically disappear.
+ url = release.HtmlUrl;
+
+ break;
+ }
+
+ url ??= bestAsset?.BrowserDownloadUrl;
+ return url != null;
+ }
+ }
+}
diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs
index 502f302157..2a4f9b87ac 100644
--- a/osu.iOS/OsuGameIOS.cs
+++ b/osu.iOS/OsuGameIOS.cs
@@ -15,7 +15,7 @@ namespace osu.iOS
{
public override Version AssemblyVersion => new Version(NSBundle.MainBundle.InfoDictionary["CFBundleVersion"].ToString());
- protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager();
+ protected override UpdateManager CreateUpdateManager() => new MobileUpdateNotifier();
protected override BatteryInfo CreateBatteryInfo() => new IOSBatteryInfo();
From 98610f4f6d1536842febaec22659bcf75b021872 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Fri, 5 Jul 2024 12:41:50 +0200
Subject: [PATCH 0172/1274] alt left/right or scroll to seek to neighbouring
hit objects
---
osu.Game/Screens/Edit/Editor.cs | 20 ++++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index c00b7ac4f2..c50cd09dd8 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -593,7 +593,7 @@ namespace osu.Game.Screens.Edit
protected override bool OnKeyDown(KeyDownEvent e)
{
- if (e.ControlPressed || e.AltPressed || e.SuperPressed) return false;
+ if (e.ControlPressed || e.SuperPressed) return false;
switch (e.Key)
{
@@ -674,7 +674,7 @@ namespace osu.Game.Screens.Edit
protected override bool OnScroll(ScrollEvent e)
{
- if (e.ControlPressed || e.AltPressed || e.SuperPressed)
+ if (e.ControlPressed || e.SuperPressed)
return false;
const double precision = 1;
@@ -1064,8 +1064,24 @@ namespace osu.Game.Screens.Edit
clock.Seek(found.Time);
}
+ private void seekHitObject(int direction)
+ {
+ var found = direction < 1
+ ? editorBeatmap.HitObjects.LastOrDefault(p => p.StartTime < clock.CurrentTimeAccurate)
+ : editorBeatmap.HitObjects.FirstOrDefault(p => p.StartTime > clock.CurrentTimeAccurate);
+
+ if (found != null)
+ clock.SeekSmoothlyTo(found.StartTime);
+ }
+
private void seek(UIEvent e, int direction)
{
+ if (e.AltPressed)
+ {
+ seekHitObject(direction);
+ return;
+ }
+
double amount = e.ShiftPressed ? 4 : 1;
bool trackPlaying = clock.IsRunning;
From 7d6ade7e844df0abde9a4a25e0725c26db62d4d5 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Fri, 5 Jul 2024 14:16:51 +0200
Subject: [PATCH 0173/1274] shift alt seek to open next sample edit popover
---
.../Timeline/NodeSamplePointPiece.cs | 8 +--
.../Components/Timeline/SamplePointPiece.cs | 27 ++++++++-
osu.Game/Screens/Edit/Editor.cs | 58 ++++++++++++++++++-
3 files changed, 84 insertions(+), 9 deletions(-)
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs
index e9999df76d..1245d94a92 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs
@@ -2,9 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
-using osu.Framework.Extensions;
using osu.Framework.Graphics.UserInterface;
-using osu.Framework.Input.Events;
using osu.Game.Audio;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
@@ -24,12 +22,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
NodeIndex = nodeIndex;
}
- protected override bool OnDoubleClick(DoubleClickEvent e)
+ protected override double GetTime()
{
var hasRepeats = (IHasRepeats)HitObject;
- EditorClock?.SeekSmoothlyTo(HitObject.StartTime + hasRepeats.Duration * NodeIndex / hasRepeats.SpanCount());
- this.ShowPopover();
- return true;
+ return HitObject.StartTime + hasRepeats.Duration * NodeIndex / hasRepeats.SpanCount();
}
protected override IList GetSamples()
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs
index 0507f3d3d0..8c05a8806e 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs
@@ -21,6 +21,7 @@ using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Rulesets.Objects;
using osu.Game.Screens.Edit.Components.TernaryButtons;
using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Objects.Types;
using osu.Game.Screens.Edit.Timing;
using osuTK;
using osuTK.Graphics;
@@ -33,7 +34,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
public readonly HitObject HitObject;
[Resolved]
- protected EditorClock? EditorClock { get; private set; }
+ private EditorClock? editorClock { get; set; }
+
+ [Resolved]
+ private Editor? editor { get; set; }
public SamplePointPiece(HitObject hitObject)
{
@@ -44,11 +48,30 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
protected override Color4 GetRepresentingColour(OsuColour colours) => AlternativeColor ? colours.Pink2 : colours.Pink1;
+ protected virtual double GetTime() => HitObject is IHasRepeats r ? HitObject.StartTime + r.Duration / r.SpanCount() / 2 : HitObject.StartTime;
+
[BackgroundDependencyLoader]
private void load()
{
HitObject.DefaultsApplied += _ => updateText();
updateText();
+
+ if (editor != null)
+ editor.ShowSampleEditPopoverRequested += OnShowSampleEditPopoverRequested;
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ if (editor != null)
+ editor.ShowSampleEditPopoverRequested -= OnShowSampleEditPopoverRequested;
+ }
+
+ private void OnShowSampleEditPopoverRequested(double time)
+ {
+ if (time == GetTime())
+ this.ShowPopover();
}
protected override bool OnClick(ClickEvent e)
@@ -59,7 +82,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
protected override bool OnDoubleClick(DoubleClickEvent e)
{
- EditorClock?.SeekSmoothlyTo(HitObject.StartTime);
+ editorClock?.SeekSmoothlyTo(GetTime());
this.ShowPopover();
return true;
}
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index c50cd09dd8..973908dfcb 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -43,6 +43,7 @@ using osu.Game.Overlays.OSD;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
using osu.Game.Screens.Edit.Components.Menus;
using osu.Game.Screens.Edit.Compose;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
@@ -1074,11 +1075,66 @@ namespace osu.Game.Screens.Edit
clock.SeekSmoothlyTo(found.StartTime);
}
+ [CanBeNull]
+ public event Action ShowSampleEditPopoverRequested;
+
+ private void seekSamplePoint(int direction)
+ {
+ double currentTime = clock.CurrentTimeAccurate;
+
+ var current = direction < 1
+ ? editorBeatmap.HitObjects.LastOrDefault(p => p is IHasRepeats r && p.StartTime < currentTime && r.EndTime >= currentTime)
+ : editorBeatmap.HitObjects.LastOrDefault(p => p is IHasRepeats r && p.StartTime <= currentTime && r.EndTime > currentTime);
+
+ if (current == null)
+ {
+ if (direction < 1)
+ {
+ current = editorBeatmap.HitObjects.LastOrDefault(p => p.StartTime < currentTime);
+ if (current != null)
+ clock.SeekSmoothlyTo(current is IHasRepeats r ? r.EndTime : current.StartTime);
+ }
+ else
+ {
+ current = editorBeatmap.HitObjects.FirstOrDefault(p => p.StartTime > currentTime);
+ if (current != null)
+ clock.SeekSmoothlyTo(current.StartTime);
+ }
+ }
+ else
+ {
+ // Find the next node sample point
+ var r = (IHasRepeats)current;
+ double[] nodeSamplePointTimes = new double[r.RepeatCount + 3];
+
+ nodeSamplePointTimes[0] = current.StartTime;
+ // The sample point for the main samples is sandwiched between the head and the first repeat
+ nodeSamplePointTimes[1] = current.StartTime + r.Duration / r.SpanCount() / 2;
+
+ for (int i = 0; i < r.SpanCount(); i++)
+ {
+ nodeSamplePointTimes[i + 2] = current.StartTime + r.Duration / r.SpanCount() * (i + 1);
+ }
+
+ double found = direction < 1
+ ? nodeSamplePointTimes.Last(p => p < currentTime)
+ : nodeSamplePointTimes.First(p => p > currentTime);
+
+ clock.SeekSmoothlyTo(found);
+ }
+
+ // Show the sample edit popover at the current time
+ ShowSampleEditPopoverRequested?.Invoke(clock.CurrentTimeAccurate);
+ }
+
private void seek(UIEvent e, int direction)
{
if (e.AltPressed)
{
- seekHitObject(direction);
+ if (e.ShiftPressed)
+ seekSamplePoint(direction);
+ else
+ seekHitObject(direction);
return;
}
From 8d46d6c6976039975414e34b54678293f2f9b574 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Fri, 5 Jul 2024 14:18:17 +0200
Subject: [PATCH 0174/1274] always seek on click
---
.../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 6 ------
1 file changed, 6 deletions(-)
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs
index 8c05a8806e..a3c781260d 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs
@@ -75,12 +75,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
}
protected override bool OnClick(ClickEvent e)
- {
- this.ShowPopover();
- return true;
- }
-
- protected override bool OnDoubleClick(DoubleClickEvent e)
{
editorClock?.SeekSmoothlyTo(GetTime());
this.ShowPopover();
From c05f48979bec3377c68fc462d17a95ce87c9a35d Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Fri, 5 Jul 2024 14:33:05 +0200
Subject: [PATCH 0175/1274] fix naming violation
---
.../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs
index a3c781260d..731fe8ae6a 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs
@@ -57,7 +57,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
updateText();
if (editor != null)
- editor.ShowSampleEditPopoverRequested += OnShowSampleEditPopoverRequested;
+ editor.ShowSampleEditPopoverRequested += onShowSampleEditPopoverRequested;
}
protected override void Dispose(bool isDisposing)
@@ -65,10 +65,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
base.Dispose(isDisposing);
if (editor != null)
- editor.ShowSampleEditPopoverRequested -= OnShowSampleEditPopoverRequested;
+ editor.ShowSampleEditPopoverRequested -= onShowSampleEditPopoverRequested;
}
- private void OnShowSampleEditPopoverRequested(double time)
+ private void onShowSampleEditPopoverRequested(double time)
{
if (time == GetTime())
this.ShowPopover();
From 9013c119ab586684e74a2d94aabd1c522a17f4b9 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Fri, 5 Jul 2024 14:33:15 +0200
Subject: [PATCH 0176/1274] update tests
---
.../TestSceneHitObjectSampleAdjustments.cs | 46 ++++++++++++-------
1 file changed, 29 insertions(+), 17 deletions(-)
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs
index 28bafb79ee..af68948bb7 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs
@@ -332,14 +332,29 @@ namespace osu.Game.Tests.Visual.Editing
});
});
- doubleClickNodeSamplePiece(0, 0);
+ clickNodeSamplePiece(0, 0);
editorTimeIs(0);
- doubleClickNodeSamplePiece(0, 1);
+ clickNodeSamplePiece(0, 1);
editorTimeIs(813);
- doubleClickNodeSamplePiece(0, 2);
+ clickNodeSamplePiece(0, 2);
editorTimeIs(1627);
- doubleClickSamplePiece(0);
+ clickSamplePiece(0);
+ editorTimeIs(406);
+
+ seekSamplePiece(-1);
editorTimeIs(0);
+ samplePopoverIsOpen();
+ seekSamplePiece(-1);
+ editorTimeIs(0);
+ samplePopoverIsOpen();
+ seekSamplePiece(1);
+ editorTimeIs(406);
+ seekSamplePiece(1);
+ editorTimeIs(813);
+ seekSamplePiece(1);
+ editorTimeIs(1627);
+ seekSamplePiece(1);
+ editorTimeIs(1627);
}
[Test]
@@ -521,7 +536,7 @@ namespace osu.Game.Tests.Visual.Editing
private void clickSamplePiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} sample piece", () =>
{
- var samplePiece = this.ChildrenOfType().Single(piece => piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex));
+ var samplePiece = this.ChildrenOfType().Single(piece => piece is not NodeSamplePointPiece && piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex));
InputManager.MoveMouseTo(samplePiece);
InputManager.Click(MouseButton.Left);
@@ -535,22 +550,19 @@ namespace osu.Game.Tests.Visual.Editing
InputManager.Click(MouseButton.Left);
});
- private void doubleClickSamplePiece(int objectIndex) => AddStep($"double-click {objectIndex.ToOrdinalWords()} sample piece", () =>
+ private void seekSamplePiece(int direction) => AddStep($"seek sample piece {direction}", () =>
{
- var samplePiece = this.ChildrenOfType().Single(piece => piece is not NodeSamplePointPiece && piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex));
-
- InputManager.MoveMouseTo(samplePiece);
- InputManager.Click(MouseButton.Left);
- InputManager.Click(MouseButton.Left);
+ InputManager.PressKey(Key.ShiftLeft);
+ InputManager.PressKey(Key.AltLeft);
+ InputManager.Key(direction < 1 ? Key.Left : Key.Right);
+ InputManager.ReleaseKey(Key.AltLeft);
+ InputManager.ReleaseKey(Key.ShiftLeft);
});
- private void doubleClickNodeSamplePiece(int objectIndex, int nodeIndex) => AddStep($"double-click {objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node sample piece", () =>
+ private void samplePopoverIsOpen() => AddUntilStep("sample popover is open", () =>
{
- var samplePiece = this.ChildrenOfType().Where(piece => piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex)).ToArray()[nodeIndex];
-
- InputManager.MoveMouseTo(samplePiece);
- InputManager.Click(MouseButton.Left);
- InputManager.Click(MouseButton.Left);
+ var popover = this.ChildrenOfType().SingleOrDefault(o => o.IsPresent);
+ return popover != null;
});
private void samplePopoverHasNoFocus() => AddUntilStep("sample popover textbox not focused", () =>
From ba44757c86f6a36e0debb7b88a6ce7b06f162dff Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Fri, 5 Jul 2024 15:24:39 +0200
Subject: [PATCH 0177/1274] clarify logic
---
osu.Game/Screens/Edit/Editor.cs | 33 +++++++++++++++++----------------
1 file changed, 17 insertions(+), 16 deletions(-)
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index 973908dfcb..847ad3eba8 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -1082,26 +1082,12 @@ namespace osu.Game.Screens.Edit
{
double currentTime = clock.CurrentTimeAccurate;
+ // Check if we are currently inside a hit object with node samples, if so seek to the next node sample point
var current = direction < 1
? editorBeatmap.HitObjects.LastOrDefault(p => p is IHasRepeats r && p.StartTime < currentTime && r.EndTime >= currentTime)
: editorBeatmap.HitObjects.LastOrDefault(p => p is IHasRepeats r && p.StartTime <= currentTime && r.EndTime > currentTime);
- if (current == null)
- {
- if (direction < 1)
- {
- current = editorBeatmap.HitObjects.LastOrDefault(p => p.StartTime < currentTime);
- if (current != null)
- clock.SeekSmoothlyTo(current is IHasRepeats r ? r.EndTime : current.StartTime);
- }
- else
- {
- current = editorBeatmap.HitObjects.FirstOrDefault(p => p.StartTime > currentTime);
- if (current != null)
- clock.SeekSmoothlyTo(current.StartTime);
- }
- }
- else
+ if (current != null)
{
// Find the next node sample point
var r = (IHasRepeats)current;
@@ -1122,6 +1108,21 @@ namespace osu.Game.Screens.Edit
clock.SeekSmoothlyTo(found);
}
+ else
+ {
+ if (direction < 1)
+ {
+ current = editorBeatmap.HitObjects.LastOrDefault(p => p.StartTime < currentTime);
+ if (current != null)
+ clock.SeekSmoothlyTo(current is IHasRepeats r ? r.EndTime : current.StartTime);
+ }
+ else
+ {
+ current = editorBeatmap.HitObjects.FirstOrDefault(p => p.StartTime > currentTime);
+ if (current != null)
+ clock.SeekSmoothlyTo(current.StartTime);
+ }
+ }
// Show the sample edit popover at the current time
ShowSampleEditPopoverRequested?.Invoke(clock.CurrentTimeAccurate);
From 5da8bb5becf461f571257c0bbd2041f7781e57cf Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sun, 7 Jul 2024 21:33:27 +0200
Subject: [PATCH 0178/1274] prevent volume control from eating inputs
---
osu.Game/Overlays/Volume/VolumeControlReceptor.cs | 8 ++++----
osu.Game/Overlays/VolumeOverlay.cs | 12 ++++++++----
2 files changed, 12 insertions(+), 8 deletions(-)
diff --git a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs
index 4ddbc9dd48..2e8d86d4c7 100644
--- a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs
+++ b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs
@@ -23,15 +23,15 @@ namespace osu.Game.Overlays.Volume
{
case GlobalAction.DecreaseVolume:
case GlobalAction.IncreaseVolume:
- ActionRequested?.Invoke(e.Action);
- return true;
+ return ActionRequested?.Invoke(e.Action) == true;
case GlobalAction.ToggleMute:
case GlobalAction.NextVolumeMeter:
case GlobalAction.PreviousVolumeMeter:
if (!e.Repeat)
- ActionRequested?.Invoke(e.Action);
- return true;
+ return ActionRequested?.Invoke(e.Action) == true;
+
+ return false;
}
return false;
diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs
index 5470c70400..fa6e797c9c 100644
--- a/osu.Game/Overlays/VolumeOverlay.cs
+++ b/osu.Game/Overlays/VolumeOverlay.cs
@@ -120,14 +120,18 @@ namespace osu.Game.Overlays
return true;
case GlobalAction.NextVolumeMeter:
- if (State.Value == Visibility.Visible)
- volumeMeters.SelectNext();
+ if (State.Value != Visibility.Visible)
+ return false;
+
+ volumeMeters.SelectNext();
Show();
return true;
case GlobalAction.PreviousVolumeMeter:
- if (State.Value == Visibility.Visible)
- volumeMeters.SelectPrevious();
+ if (State.Value != Visibility.Visible)
+ return false;
+
+ volumeMeters.SelectPrevious();
Show();
return true;
From f36321a8ea74a599f77263ef4d127bc58263006b Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sun, 7 Jul 2024 21:33:43 +0200
Subject: [PATCH 0179/1274] allow alt scroll for volume in editor
---
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 847ad3eba8..acb9b93114 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -675,7 +675,7 @@ namespace osu.Game.Screens.Edit
protected override bool OnScroll(ScrollEvent e)
{
- if (e.ControlPressed || e.SuperPressed)
+ if (e.ControlPressed || e.AltPressed || e.SuperPressed)
return false;
const double precision = 1;
From 306dc37ab5159d825adf9d5db29d50ab491e1e83 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Tue, 9 Jul 2024 12:28:23 +0200
Subject: [PATCH 0180/1274] Make hit object and sample point seek keybinds
configurable
---
.../Input/Bindings/GlobalActionContainer.cs | 16 +++++++++++
.../GlobalActionKeyBindingStrings.cs | 20 ++++++++++++++
osu.Game/Screens/Edit/Editor.cs | 27 ++++++++++++-------
3 files changed, 53 insertions(+), 10 deletions(-)
diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs
index ef0c60cd20..542073476f 100644
--- a/osu.Game/Input/Bindings/GlobalActionContainer.cs
+++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs
@@ -147,6 +147,10 @@ namespace osu.Game.Input.Bindings
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.MouseWheelLeft }, GlobalAction.EditorCycleNextBeatSnapDivisor),
new KeyBinding(new[] { InputKey.Control, InputKey.R }, GlobalAction.EditorToggleRotateControl),
new KeyBinding(new[] { InputKey.Control, InputKey.E }, GlobalAction.EditorToggleScaleControl),
+ new KeyBinding(new[] { InputKey.Alt, InputKey.Left }, GlobalAction.EditorSeekToPreviousHitObject),
+ new KeyBinding(new[] { InputKey.Alt, InputKey.Right }, GlobalAction.EditorSeekToNextHitObject),
+ new KeyBinding(new[] { InputKey.Alt, InputKey.Shift, InputKey.Left }, GlobalAction.EditorSeekToPreviousSamplePoint),
+ new KeyBinding(new[] { InputKey.Alt, InputKey.Shift, InputKey.Right }, GlobalAction.EditorSeekToNextSamplePoint),
};
private static IEnumerable editorTestPlayKeyBindings => new[]
@@ -456,6 +460,18 @@ namespace osu.Game.Input.Bindings
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTestPlayQuickExitToCurrentTime))]
EditorTestPlayQuickExitToCurrentTime,
+
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSeekToPreviousHitObject))]
+ EditorSeekToPreviousHitObject,
+
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSeekToNextHitObject))]
+ EditorSeekToNextHitObject,
+
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSeekToPreviousSamplePoint))]
+ EditorSeekToPreviousSamplePoint,
+
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSeekToNextSamplePoint))]
+ EditorSeekToNextSamplePoint,
}
public enum GlobalActionCategory
diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs
index 450585f79a..206db1a166 100644
--- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs
+++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs
@@ -404,6 +404,26 @@ namespace osu.Game.Localisation
///
public static LocalisableString DecreaseModSpeed => new TranslatableString(getKey(@"decrease_mod_speed"), @"Decrease mod speed");
+ ///
+ /// "Seek to previous hit object"
+ ///
+ public static LocalisableString EditorSeekToPreviousHitObject => new TranslatableString(getKey(@"editor_seek_to_previous_hit_object"), @"Seek to previous hit object");
+
+ ///
+ /// "Seek to next hit object"
+ ///
+ public static LocalisableString EditorSeekToNextHitObject => new TranslatableString(getKey(@"editor_seek_to_next_hit_object"), @"Seek to next hit object");
+
+ ///
+ /// "Seek to previous sample point"
+ ///
+ public static LocalisableString EditorSeekToPreviousSamplePoint => new TranslatableString(getKey(@"editor_seek_to_previous_sample_point"), @"Seek to previous sample point");
+
+ ///
+ /// "Seek to next sample point"
+ ///
+ public static LocalisableString EditorSeekToNextSamplePoint => new TranslatableString(getKey(@"editor_seek_to_next_sample_point"), @"Seek to next sample point");
+
private static string getKey(string key) => $@"{prefix}:{key}";
}
}
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index acb9b93114..214549a68d 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -594,7 +594,7 @@ namespace osu.Game.Screens.Edit
protected override bool OnKeyDown(KeyDownEvent e)
{
- if (e.ControlPressed || e.SuperPressed) return false;
+ if (e.ControlPressed || e.AltPressed || e.SuperPressed) return false;
switch (e.Key)
{
@@ -746,6 +746,22 @@ namespace osu.Game.Screens.Edit
bottomBar.TestGameplayButton.TriggerClick();
return true;
+ case GlobalAction.EditorSeekToPreviousHitObject:
+ seekHitObject(-1);
+ return true;
+
+ case GlobalAction.EditorSeekToNextHitObject:
+ seekHitObject(1);
+ return true;
+
+ case GlobalAction.EditorSeekToPreviousSamplePoint:
+ seekSamplePoint(-1);
+ return true;
+
+ case GlobalAction.EditorSeekToNextSamplePoint:
+ seekSamplePoint(1);
+ return true;
+
default:
return false;
}
@@ -1130,15 +1146,6 @@ namespace osu.Game.Screens.Edit
private void seek(UIEvent e, int direction)
{
- if (e.AltPressed)
- {
- if (e.ShiftPressed)
- seekSamplePoint(direction);
- else
- seekHitObject(direction);
- return;
- }
-
double amount = e.ShiftPressed ? 4 : 1;
bool trackPlaying = clock.IsRunning;
From ae380027772867b45baf80acc910c98cb39fac46 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sun, 14 Jul 2024 15:46:40 +0200
Subject: [PATCH 0181/1274] Revert "fix incorrect rotated bound checking"
This reverts commit 4165ded8134d05f4d6b934255a5678a6a7d74bca.
---
.../Edit/OsuSelectionScaleHandler.cs | 53 ++++---------------
osu.Game/Utils/GeometryUtils.cs | 20 +++----
2 files changed, 17 insertions(+), 56 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs
index d336261499..8b87246456 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs
@@ -80,32 +80,12 @@ namespace osu.Game.Rulesets.Osu.Edit
changeHandler?.BeginChange();
objectsInScale = selectedMovableObjects.ToDictionary(ho => ho, ho => new OriginalHitObjectState(ho));
- OriginalSurroundingQuad = getOriginalSurroundingQuad()!;
+ OriginalSurroundingQuad = objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider
+ ? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => slider.Position + p.Position))
+ : GeometryUtils.GetSurroundingQuad(objectsInScale.Keys);
defaultOrigin = OriginalSurroundingQuad.Value.Centre;
}
- private Quad? getOriginalSurroundingQuad(float axisRotation = 0)
- {
- if (objectsInScale == null)
- return null;
-
- return objectsInScale.Count == 1 && objectsInScale.First().Value.PathControlPointPositions != null
- ? GeometryUtils.GetSurroundingQuad(objectsInScale.First().Value.PathControlPointPositions!.Select(p => objectsInScale.First().Value.Position + p), axisRotation)
- : GeometryUtils.GetSurroundingQuad(objectsInScale.Values.SelectMany(s =>
- {
- if (s.EndPosition.HasValue)
- {
- return new[]
- {
- s.Position,
- s.Position + s.EndPosition.Value
- };
- }
-
- return new[] { s.Position };
- }), axisRotation);
- }
-
public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0)
{
if (!OperationInProgress.Value)
@@ -233,23 +213,10 @@ namespace osu.Game.Rulesets.Osu.Edit
scale = clampScaleToAdjustAxis(scale, adjustAxis);
Vector2 actualOrigin = origin ?? defaultOrigin.Value;
- var selectionQuad = axisRotation == 0 ? OriginalSurroundingQuad.Value : getOriginalSurroundingQuad(axisRotation)!.Value;
- var points = new[]
- {
- selectionQuad.TopLeft,
- selectionQuad.TopRight,
- selectionQuad.BottomLeft,
- selectionQuad.BottomRight
- };
+ var selectionQuad = OriginalSurroundingQuad.Value;
- float cos = MathF.Cos(float.DegreesToRadians(-axisRotation));
- float sin = MathF.Sin(float.DegreesToRadians(-axisRotation));
-
- foreach (var point in points)
- {
- scale = clampToBound(scale, point, Vector2.Zero);
- scale = clampToBound(scale, point, OsuPlayfield.BASE_SIZE);
- }
+ scale = clampToBound(scale, selectionQuad.BottomRight, OsuPlayfield.BASE_SIZE);
+ scale = clampToBound(scale, selectionQuad.TopLeft, Vector2.Zero);
return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON));
@@ -259,17 +226,19 @@ namespace osu.Game.Rulesets.Osu.Edit
{
p -= actualOrigin;
bound -= actualOrigin;
+ float cos = MathF.Cos(float.DegreesToRadians(-axisRotation));
+ float sin = MathF.Sin(float.DegreesToRadians(-axisRotation));
var a = new Vector2(cos * cos * p.X - sin * cos * p.Y, -sin * cos * p.X + sin * sin * p.Y);
var b = new Vector2(sin * sin * p.X + sin * cos * p.Y, sin * cos * p.X + cos * cos * p.Y);
switch (adjustAxis)
{
case Axes.X:
- s.X = MathF.Min(s.X, minPositiveComponent(Vector2.Divide(bound - b, a)));
+ s.X = MathF.Min(scale.X, minPositiveComponent(Vector2.Divide(bound - b, a)));
break;
case Axes.Y:
- s.Y = MathF.Min(s.Y, minPositiveComponent(Vector2.Divide(bound - a, b)));
+ s.Y = MathF.Min(scale.Y, minPositiveComponent(Vector2.Divide(bound - a, b)));
break;
case Axes.Both:
@@ -306,14 +275,12 @@ namespace osu.Game.Rulesets.Osu.Edit
public Vector2 Position { get; }
public Vector2[]? PathControlPointPositions { get; }
public PathType?[]? PathControlPointTypes { get; }
- public Vector2? EndPosition { get; }
public OriginalHitObjectState(OsuHitObject hitObject)
{
Position = hitObject.Position;
PathControlPointPositions = (hitObject as IHasPath)?.Path.ControlPoints.Select(p => p.Position).ToArray();
PathControlPointTypes = (hitObject as IHasPath)?.Path.ControlPoints.Select(p => p.Type).ToArray();
- EndPosition = (hitObject as IHasPath)?.Path.PositionAt(1);
}
}
}
diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs
index 23c25cfffa..f6e7e81007 100644
--- a/osu.Game/Utils/GeometryUtils.cs
+++ b/osu.Game/Utils/GeometryUtils.cs
@@ -113,8 +113,7 @@ namespace osu.Game.Utils
/// Returns a quad surrounding the provided points.
///
/// The points to calculate a quad for.
- /// The rotation in degrees of the axis to align the quad to.
- public static Quad GetSurroundingQuad(IEnumerable points, float axisRotation = 0)
+ public static Quad GetSurroundingQuad(IEnumerable points)
{
if (!points.Any())
return new Quad();
@@ -125,25 +124,20 @@ namespace osu.Game.Utils
// Go through all hitobjects to make sure they would remain in the bounds of the editor after movement, before any movement is attempted
foreach (var p in points)
{
- var pr = RotateVector(p, axisRotation);
- minPosition = Vector2.ComponentMin(minPosition, pr);
- maxPosition = Vector2.ComponentMax(maxPosition, pr);
+ minPosition = Vector2.ComponentMin(minPosition, p);
+ maxPosition = Vector2.ComponentMax(maxPosition, p);
}
- var p1 = RotateVector(minPosition, -axisRotation);
- var p2 = RotateVector(new Vector2(minPosition.X, maxPosition.Y), -axisRotation);
- var p3 = RotateVector(maxPosition, -axisRotation);
- var p4 = RotateVector(new Vector2(maxPosition.X, minPosition.Y), -axisRotation);
+ Vector2 size = maxPosition - minPosition;
- return new Quad(p1, p2, p3, p4);
+ return new Quad(minPosition.X, minPosition.Y, size.X, size.Y);
}
///
/// Returns a gamefield-space quad surrounding the provided hit objects.
///
/// The hit objects to calculate a quad for.
- /// The rotation in degrees of the axis to align the quad to.
- public static Quad GetSurroundingQuad(IEnumerable hitObjects, float axisRotation = 0) =>
+ public static Quad GetSurroundingQuad(IEnumerable hitObjects) =>
GetSurroundingQuad(hitObjects.SelectMany(h =>
{
if (h is IHasPath path)
@@ -157,6 +151,6 @@ namespace osu.Game.Utils
}
return new[] { h.Position };
- }), axisRotation);
+ }));
}
}
From 58eb7f6fe174ba72f06304d0334b0668dde14c74 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sun, 14 Jul 2024 16:58:05 +0200
Subject: [PATCH 0182/1274] fix rotated scale bounds again
---
.../Edit/OsuSelectionScaleHandler.cs | 31 ++++++++++--
osu.Game/Utils/GeometryUtils.cs | 49 ++++++++++++++++++-
2 files changed, 73 insertions(+), 7 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs
index 8b87246456..56c3ba9315 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs
@@ -69,6 +69,7 @@ namespace osu.Game.Rulesets.Osu.Edit
private Dictionary? objectsInScale;
private Vector2? defaultOrigin;
+ private List? originalConvexHull;
public override void Begin()
{
@@ -84,6 +85,9 @@ namespace osu.Game.Rulesets.Osu.Edit
? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => slider.Position + p.Position))
: GeometryUtils.GetSurroundingQuad(objectsInScale.Keys);
defaultOrigin = OriginalSurroundingQuad.Value.Centre;
+ originalConvexHull = objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider2
+ ? GeometryUtils.GetConvexHull(slider2.Path.ControlPoints.Select(p => slider2.Position + p.Position))
+ : GeometryUtils.GetConvexHull(objectsInScale.Keys);
}
public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0)
@@ -211,12 +215,31 @@ namespace osu.Game.Rulesets.Osu.Edit
if (objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider)
origin = slider.Position;
+ float cos = MathF.Cos(float.DegreesToRadians(-axisRotation));
+ float sin = MathF.Sin(float.DegreesToRadians(-axisRotation));
scale = clampScaleToAdjustAxis(scale, adjustAxis);
Vector2 actualOrigin = origin ?? defaultOrigin.Value;
- var selectionQuad = OriginalSurroundingQuad.Value;
+ IEnumerable points;
- scale = clampToBound(scale, selectionQuad.BottomRight, OsuPlayfield.BASE_SIZE);
- scale = clampToBound(scale, selectionQuad.TopLeft, Vector2.Zero);
+ if (axisRotation == 0)
+ {
+ var selectionQuad = OriginalSurroundingQuad.Value;
+ points = new[]
+ {
+ selectionQuad.TopLeft,
+ selectionQuad.TopRight,
+ selectionQuad.BottomLeft,
+ selectionQuad.BottomRight
+ };
+ }
+ else
+ points = originalConvexHull!;
+
+ foreach (var point in points)
+ {
+ scale = clampToBound(scale, point, Vector2.Zero);
+ scale = clampToBound(scale, point, OsuPlayfield.BASE_SIZE);
+ }
return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON));
@@ -226,8 +249,6 @@ namespace osu.Game.Rulesets.Osu.Edit
{
p -= actualOrigin;
bound -= actualOrigin;
- float cos = MathF.Cos(float.DegreesToRadians(-axisRotation));
- float sin = MathF.Sin(float.DegreesToRadians(-axisRotation));
var a = new Vector2(cos * cos * p.X - sin * cos * p.Y, -sin * cos * p.X + sin * sin * p.Y);
var b = new Vector2(sin * sin * p.X + sin * cos * p.Y, sin * cos * p.X + cos * cos * p.Y);
diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs
index f6e7e81007..5a8ca9722e 100644
--- a/osu.Game/Utils/GeometryUtils.cs
+++ b/osu.Game/Utils/GeometryUtils.cs
@@ -138,7 +138,52 @@ namespace osu.Game.Utils
///
/// The hit objects to calculate a quad for.
public static Quad GetSurroundingQuad(IEnumerable hitObjects) =>
- GetSurroundingQuad(hitObjects.SelectMany(h =>
+ GetSurroundingQuad(enumerateStartAndEndPositions(hitObjects));
+
+ ///
+ /// Returns the points that make up the convex hull of the provided points.
+ ///
+ /// The points to calculate a convex hull.
+ public static List GetConvexHull(IEnumerable points)
+ {
+ List p = points.ToList();
+
+ if (p.Count <= 1)
+ return p;
+
+ int n = p.Count, k = 0;
+ List hull = new List(new Vector2[2 * n]);
+
+ p.Sort((a, b) => a.X == b.X ? a.Y.CompareTo(b.Y) : a.X.CompareTo(b.X));
+
+ // Build lower hull
+ for (int i = 0; i < n; ++i)
+ {
+ while (k >= 2 && cross(hull[k - 2], hull[k - 1], p[i]) <= 0)
+ k--;
+ hull[k] = p[i];
+ k++;
+ }
+
+ // Build upper hull
+ for (int i = n - 2, t = k + 1; i >= 0; i--)
+ {
+ while (k >= t && cross(hull[k - 2], hull[k - 1], p[i]) <= 0)
+ k--;
+ hull[k] = p[i];
+ k++;
+ }
+
+ return hull.Take(k - 1).ToList();
+
+ float cross(Vector2 o, Vector2 a, Vector2 b) => (a.X - o.X) * (b.Y - o.Y) - (a.Y - o.Y) * (b.X - o.X);
+ }
+
+ public static List GetConvexHull(IEnumerable hitObjects) =>
+ GetConvexHull(enumerateStartAndEndPositions(hitObjects));
+
+ private static IEnumerable enumerateStartAndEndPositions(IEnumerable hitObjects) =>
+ hitObjects.SelectMany(h =>
{
if (h is IHasPath path)
{
@@ -151,6 +196,6 @@ namespace osu.Game.Utils
}
return new[] { h.Position };
- }));
+ });
}
}
From 7a319a6d74ee29fbf3e7b5dbc2b7c6c9ca8e4990 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sun, 14 Jul 2024 17:03:17 +0200
Subject: [PATCH 0183/1274] dont rotate scale when in selection origin mode
---
osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs
index a1907a2fd5..0f04efcfa5 100644
--- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs
+++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs
@@ -131,7 +131,7 @@ namespace osu.Game.Rulesets.Osu.Edit
scaleInfo.BindValueChanged(scale =>
{
var newScale = new Vector2(scale.NewValue.Scale, scale.NewValue.Scale);
- scaleHandler.Update(newScale, getOriginPosition(scale.NewValue), getAdjustAxis(scale.NewValue), gridToolbox.GridLinesRotation.Value);
+ scaleHandler.Update(newScale, getOriginPosition(scale.NewValue), getAdjustAxis(scale.NewValue), getRotation(scale.NewValue));
});
}
@@ -164,7 +164,7 @@ namespace osu.Game.Rulesets.Osu.Edit
return;
const float max_scale = 10;
- var scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(max_scale), getOriginPosition(scaleInfo.Value), getAdjustAxis(scaleInfo.Value), gridToolbox.GridLinesRotation.Value);
+ var scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(max_scale), getOriginPosition(scaleInfo.Value), getAdjustAxis(scaleInfo.Value), getRotation(scaleInfo.Value));
if (!scaleInfo.Value.XAxis)
scale.X = max_scale;
@@ -185,6 +185,8 @@ namespace osu.Game.Rulesets.Osu.Edit
private Axes getAdjustAxis(PreciseScaleInfo scale) => scale.XAxis ? scale.YAxis ? Axes.Both : Axes.X : Axes.Y;
+ private float getRotation(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? gridToolbox.GridLinesRotation.Value : 0;
+
private void setAxis(bool x, bool y)
{
scaleInfo.Value = scaleInfo.Value with { XAxis = x, YAxis = y };
From 9e5d099b1b1113304294c17155f42ab4a8ea76cd Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sun, 14 Jul 2024 17:13:22 +0200
Subject: [PATCH 0184/1274] rename playfield centre origin to grid centre
---
.../Edit/PreciseRotationPopover.cs | 12 ++++++------
.../Edit/PreciseScalePopover.cs | 16 ++++++++--------
2 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs
index 6a3e326c2b..4a1ccc4b61 100644
--- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs
+++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Edit
private readonly OsuGridToolboxGroup gridToolbox;
- private readonly Bindable rotationInfo = new Bindable(new PreciseRotationInfo(0, RotationOrigin.PlayfieldCentre));
+ private readonly Bindable rotationInfo = new Bindable(new PreciseRotationInfo(0, RotationOrigin.GridCentre));
private SliderWithTextBoxInput angleInput = null!;
private EditorRadioButtonCollection rotationOrigin = null!;
@@ -60,9 +60,9 @@ namespace osu.Game.Rulesets.Osu.Edit
RelativeSizeAxes = Axes.X,
Items = new[]
{
- new RadioButton("Playfield centre",
- () => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.PlayfieldCentre },
- () => new SpriteIcon { Icon = FontAwesome.Regular.Square }),
+ new RadioButton("Grid centre",
+ () => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.GridCentre },
+ () => new SpriteIcon { Icon = FontAwesome.Regular.PlusSquare }),
selectionCentreButton = new RadioButton("Selection centre",
() => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.SelectionCentre },
() => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare })
@@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Osu.Edit
rotationInfo.BindValueChanged(rotation =>
{
- rotationHandler.Update(rotation.NewValue.Degrees, rotation.NewValue.Origin == RotationOrigin.PlayfieldCentre ? gridToolbox.StartPosition.Value : null);
+ rotationHandler.Update(rotation.NewValue.Degrees, rotation.NewValue.Origin == RotationOrigin.GridCentre ? gridToolbox.StartPosition.Value : null);
});
}
@@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Osu.Edit
public enum RotationOrigin
{
- PlayfieldCentre,
+ GridCentre,
SelectionCentre
}
diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs
index 0f04efcfa5..15ed4c59c3 100644
--- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs
+++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Edit
private readonly OsuGridToolboxGroup gridToolbox;
- private readonly Bindable scaleInfo = new Bindable(new PreciseScaleInfo(1, ScaleOrigin.PlayfieldCentre, true, true));
+ private readonly Bindable scaleInfo = new Bindable(new PreciseScaleInfo(1, ScaleOrigin.GridCentre, true, true));
private SliderWithTextBoxInput scaleInput = null!;
private BindableNumber scaleInputBindable = null!;
@@ -68,9 +68,9 @@ namespace osu.Game.Rulesets.Osu.Edit
RelativeSizeAxes = Axes.X,
Items = new[]
{
- playfieldCentreButton = new RadioButton("Playfield centre",
- () => setOrigin(ScaleOrigin.PlayfieldCentre),
- () => new SpriteIcon { Icon = FontAwesome.Regular.Square }),
+ playfieldCentreButton = new RadioButton("Grid centre",
+ () => setOrigin(ScaleOrigin.GridCentre),
+ () => new SpriteIcon { Icon = FontAwesome.Regular.PlusSquare }),
selectionCentreButton = new RadioButton("Selection centre",
() => setOrigin(ScaleOrigin.SelectionCentre),
() => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare })
@@ -137,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Edit
private void updateAxisCheckBoxesEnabled()
{
- if (scaleInfo.Value.Origin == ScaleOrigin.PlayfieldCentre)
+ if (scaleInfo.Value.Origin == ScaleOrigin.GridCentre)
{
toggleAxisAvailable(xCheckBox.Current, true);
toggleAxisAvailable(yCheckBox.Current, true);
@@ -181,11 +181,11 @@ namespace osu.Game.Rulesets.Osu.Edit
updateAxisCheckBoxesEnabled();
}
- private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? gridToolbox.StartPosition.Value : null;
+ private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.GridCentre ? gridToolbox.StartPosition.Value : null;
private Axes getAdjustAxis(PreciseScaleInfo scale) => scale.XAxis ? scale.YAxis ? Axes.Both : Axes.X : Axes.Y;
- private float getRotation(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? gridToolbox.GridLinesRotation.Value : 0;
+ private float getRotation(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.GridCentre ? gridToolbox.GridLinesRotation.Value : 0;
private void setAxis(bool x, bool y)
{
@@ -210,7 +210,7 @@ namespace osu.Game.Rulesets.Osu.Edit
public enum ScaleOrigin
{
- PlayfieldCentre,
+ GridCentre,
SelectionCentre
}
From a80e3337860773790c88010a7592219984a31570 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sun, 14 Jul 2024 17:27:04 +0200
Subject: [PATCH 0185/1274] add playfield origin as third origin option
---
.../Edit/PreciseRotationPopover.cs | 17 ++++++++++++-
.../Edit/PreciseScalePopover.cs | 24 ++++++++++++++++---
2 files changed, 37 insertions(+), 4 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs
index 4a1ccc4b61..352debf500 100644
--- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs
+++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -8,6 +9,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.UserInterfaceV2;
+using osu.Game.Rulesets.Osu.UI;
using osu.Game.Screens.Edit.Components.RadioButtons;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
@@ -63,6 +65,9 @@ namespace osu.Game.Rulesets.Osu.Edit
new RadioButton("Grid centre",
() => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.GridCentre },
() => new SpriteIcon { Icon = FontAwesome.Regular.PlusSquare }),
+ new RadioButton("Playfield centre",
+ () => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.PlayfieldCentre },
+ () => new SpriteIcon { Icon = FontAwesome.Regular.Square }),
selectionCentreButton = new RadioButton("Selection centre",
() => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.SelectionCentre },
() => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare })
@@ -95,10 +100,19 @@ namespace osu.Game.Rulesets.Osu.Edit
rotationInfo.BindValueChanged(rotation =>
{
- rotationHandler.Update(rotation.NewValue.Degrees, rotation.NewValue.Origin == RotationOrigin.GridCentre ? gridToolbox.StartPosition.Value : null);
+ rotationHandler.Update(rotation.NewValue.Degrees, getOriginPosition(rotation.NewValue));
});
}
+ private Vector2? getOriginPosition(PreciseRotationInfo rotation) =>
+ rotation.Origin switch
+ {
+ RotationOrigin.GridCentre => gridToolbox.StartPosition.Value,
+ RotationOrigin.PlayfieldCentre => OsuPlayfield.BASE_SIZE / 2,
+ RotationOrigin.SelectionCentre => null,
+ _ => throw new ArgumentOutOfRangeException(nameof(rotation))
+ };
+
protected override void PopIn()
{
base.PopIn();
@@ -117,6 +131,7 @@ namespace osu.Game.Rulesets.Osu.Edit
public enum RotationOrigin
{
GridCentre,
+ PlayfieldCentre,
SelectionCentre
}
diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs
index 15ed4c59c3..dff370d259 100644
--- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs
+++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs
@@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
+using osu.Game.Rulesets.Osu.UI;
using osu.Game.Screens.Edit.Components.RadioButtons;
using osuTK;
@@ -27,6 +28,7 @@ namespace osu.Game.Rulesets.Osu.Edit
private BindableNumber scaleInputBindable = null!;
private EditorRadioButtonCollection scaleOrigin = null!;
+ private RadioButton gridCentreButton = null!;
private RadioButton playfieldCentreButton = null!;
private RadioButton selectionCentreButton = null!;
@@ -68,9 +70,12 @@ namespace osu.Game.Rulesets.Osu.Edit
RelativeSizeAxes = Axes.X,
Items = new[]
{
- playfieldCentreButton = new RadioButton("Grid centre",
+ gridCentreButton = new RadioButton("Grid centre",
() => setOrigin(ScaleOrigin.GridCentre),
() => new SpriteIcon { Icon = FontAwesome.Regular.PlusSquare }),
+ playfieldCentreButton = new RadioButton("Playfield centre",
+ () => setOrigin(ScaleOrigin.PlayfieldCentre),
+ () => new SpriteIcon { Icon = FontAwesome.Regular.Square }),
selectionCentreButton = new RadioButton("Selection centre",
() => setOrigin(ScaleOrigin.SelectionCentre),
() => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare })
@@ -99,6 +104,10 @@ namespace osu.Game.Rulesets.Osu.Edit
},
}
};
+ gridCentreButton.Selected.DisabledChanged += isDisabled =>
+ {
+ gridCentreButton.TooltipText = isDisabled ? "The current selection cannot be scaled relative to grid centre." : string.Empty;
+ };
playfieldCentreButton.Selected.DisabledChanged += isDisabled =>
{
playfieldCentreButton.TooltipText = isDisabled ? "The current selection cannot be scaled relative to playfield centre." : string.Empty;
@@ -125,6 +134,7 @@ namespace osu.Game.Rulesets.Osu.Edit
selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleX.Value || scaleHandler.CanScaleY.Value);
playfieldCentreButton.Selected.Disabled = scaleHandler.IsScalingSlider.Value && !selectionCentreButton.Selected.Disabled;
+ gridCentreButton.Selected.Disabled = playfieldCentreButton.Selected.Disabled;
scaleOrigin.Items.First(b => !b.Selected.Disabled).Select();
@@ -137,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.Edit
private void updateAxisCheckBoxesEnabled()
{
- if (scaleInfo.Value.Origin == ScaleOrigin.GridCentre)
+ if (scaleInfo.Value.Origin != ScaleOrigin.SelectionCentre)
{
toggleAxisAvailable(xCheckBox.Current, true);
toggleAxisAvailable(yCheckBox.Current, true);
@@ -181,7 +191,14 @@ namespace osu.Game.Rulesets.Osu.Edit
updateAxisCheckBoxesEnabled();
}
- private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.GridCentre ? gridToolbox.StartPosition.Value : null;
+ private Vector2? getOriginPosition(PreciseScaleInfo scale) =>
+ scale.Origin switch
+ {
+ ScaleOrigin.GridCentre => gridToolbox.StartPosition.Value,
+ ScaleOrigin.PlayfieldCentre => OsuPlayfield.BASE_SIZE / 2,
+ ScaleOrigin.SelectionCentre => null,
+ _ => throw new ArgumentOutOfRangeException(nameof(scale))
+ };
private Axes getAdjustAxis(PreciseScaleInfo scale) => scale.XAxis ? scale.YAxis ? Axes.Both : Axes.X : Axes.Y;
@@ -211,6 +228,7 @@ namespace osu.Game.Rulesets.Osu.Edit
public enum ScaleOrigin
{
GridCentre,
+ PlayfieldCentre,
SelectionCentre
}
From 2bbaa8e43ccce0a1bf42f2c5790ad469a81a195e Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sun, 14 Jul 2024 18:12:55 +0200
Subject: [PATCH 0186/1274] make flips grid-type aware
---
.../Edit/OsuSelectionHandler.cs | 26 ++++++++++++++++---
1 file changed, 22 insertions(+), 4 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
index 7d6ef66909..1334dbdbec 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
@@ -107,10 +107,28 @@ namespace osu.Game.Rulesets.Osu.Edit
// If we're flipping over the origin, we take the grid origin position from the grid toolbox.
var flipQuad = flipOverOrigin ? new Quad(gridToolbox.StartPositionX.Value, gridToolbox.StartPositionY.Value, 0, 0) : GeometryUtils.GetSurroundingQuad(hitObjects);
- // If we're flipping over the origin, we take the grid rotation from the grid toolbox.
- // We want to normalize the rotation angle to -45 to 45 degrees, so horizontal vs vertical flip is not mixed up by the rotation and it stays intuitive to use.
- var flipAxis = flipOverOrigin ? GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 405) % 90 - 45)) : Vector2.UnitX;
- flipAxis = direction == Direction.Vertical ? flipAxis.PerpendicularLeft : flipAxis;
+ Vector2 flipAxis = direction == Direction.Vertical ? Vector2.UnitY : Vector2.UnitX;
+
+ if (flipOverOrigin)
+ {
+ // If we're flipping over the origin, we take one of the axes of the grid.
+ // Take the axis closest to the direction we want to flip over.
+ switch (gridToolbox.GridType.Value)
+ {
+ case PositionSnapGridType.Square:
+ flipAxis = GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 405) % 90 - 45));
+ flipAxis = direction == Direction.Vertical ? flipAxis.PerpendicularLeft : flipAxis;
+ break;
+
+ case PositionSnapGridType.Triangle:
+ // Hex grid has 3 axes, so you can not directly flip over one of the axes,
+ // however it's still possible to achieve that flip by combining multiple flips over the other axes.
+ flipAxis = direction == Direction.Vertical
+ ? GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 390) % 60 + 60))
+ : GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 390) % 60 - 60));
+ break;
+ }
+ }
var controlPointFlipQuad = new Quad();
From 952540024eeb41c039d5a1072d7b1d520ff2cab3 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sun, 14 Jul 2024 20:43:16 +0200
Subject: [PATCH 0187/1274] addition bank toggles in compose
---
osu.Game/Rulesets/Edit/HitObjectComposer.cs | 24 +++-
.../Components/ComposeBlueprintContainer.cs | 9 +-
.../Components/EditorSelectionHandler.cs | 131 +++++++++++++++++-
3 files changed, 149 insertions(+), 15 deletions(-)
diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
index 3c38a7258e..ac9c830f03 100644
--- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs
+++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
@@ -85,6 +85,7 @@ namespace osu.Game.Rulesets.Edit
private EditorRadioButtonCollection toolboxCollection;
private FillFlowContainer togglesCollection;
private FillFlowContainer sampleBankTogglesCollection;
+ private FillFlowContainer sampleAdditionBankTogglesCollection;
private IBindable hasTiming;
private Bindable autoSeekOnPlacement;
@@ -184,7 +185,17 @@ namespace osu.Game.Rulesets.Edit
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 5),
},
- }
+ },
+ new EditorToolboxGroup("additions (Alt-Q~R)")
+ {
+ Child = sampleAdditionBankTogglesCollection = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(0, 5),
+ },
+ },
}
},
}
@@ -230,6 +241,7 @@ namespace osu.Game.Rulesets.Edit
togglesCollection.AddRange(TernaryStates.Select(b => new DrawableTernaryButton(b)));
sampleBankTogglesCollection.AddRange(BlueprintContainer.SampleBankTernaryStates.Select(b => new DrawableTernaryButton(b)));
+ sampleAdditionBankTogglesCollection.AddRange(BlueprintContainer.SampleAdditionBankTernaryStates.Select(b => new DrawableTernaryButton(b)));
setSelectTool();
@@ -358,7 +370,7 @@ namespace osu.Game.Rulesets.Edit
protected override bool OnKeyDown(KeyDownEvent e)
{
- if (e.ControlPressed || e.AltPressed || e.SuperPressed)
+ if (e.ControlPressed || e.SuperPressed)
return false;
if (checkLeftToggleFromKey(e.Key, out int leftIndex))
@@ -375,9 +387,11 @@ namespace osu.Game.Rulesets.Edit
if (checkRightToggleFromKey(e.Key, out int rightIndex))
{
- var item = e.ShiftPressed
- ? sampleBankTogglesCollection.ElementAtOrDefault(rightIndex)
- : togglesCollection.ElementAtOrDefault(rightIndex);
+ var item = e.AltPressed
+ ? sampleAdditionBankTogglesCollection.ElementAtOrDefault(rightIndex)
+ : e.ShiftPressed
+ ? sampleBankTogglesCollection.ElementAtOrDefault(rightIndex)
+ : togglesCollection.ElementAtOrDefault(rightIndex);
if (item is DrawableTernaryButton button)
{
diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs
index fc8bce4c96..671180348d 100644
--- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs
@@ -62,7 +62,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void load()
{
MainTernaryStates = CreateTernaryButtons().ToArray();
- SampleBankTernaryStates = createSampleBankTernaryButtons().ToArray();
+ SampleBankTernaryStates = createSampleBankTernaryButtons(SelectionHandler.SelectionBankStates).ToArray();
+ SampleAdditionBankTernaryStates = createSampleBankTernaryButtons(SelectionHandler.SelectionAdditionBankStates).ToArray();
AddInternal(new DrawableRulesetDependenciesProvidingContainer(Composer.Ruleset)
{
@@ -219,6 +220,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
public TernaryButton[] SampleBankTernaryStates { get; private set; }
+ public TernaryButton[] SampleAdditionBankTernaryStates { get; private set; }
+
///
/// Create all ternary states required to be displayed to the user.
///
@@ -231,9 +234,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
yield return new TernaryButton(kvp.Value, kvp.Key.Replace("hit", string.Empty).Titleize(), () => GetIconForSample(kvp.Key));
}
- private IEnumerable createSampleBankTernaryButtons()
+ private IEnumerable createSampleBankTernaryButtons(Dictionary> sampleBankStates)
{
- foreach (var kvp in SelectionHandler.SelectionBankStates)
+ foreach (var kvp in sampleBankStates)
yield return new TernaryButton(kvp.Value, kvp.Key.Titleize(), () => getIconForBank(kvp.Key));
}
diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs
index a4efe66bf8..04baaa6134 100644
--- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs
@@ -58,6 +58,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
///
public readonly Dictionary> SelectionBankStates = new Dictionary>();
+ ///
+ /// The state of each sample addition bank type for all selected hitobjects.
+ ///
+ public readonly Dictionary> SelectionAdditionBankStates = new Dictionary>();
+
///
/// Set up ternary state bindables and bind them to selection/hitobject changes (in both directions)
///
@@ -90,7 +95,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
// Never remove a sample bank.
// These are basically radio buttons, not toggles.
- if (SelectedItems.All(h => h.Samples.All(s => s.Bank == bankName)))
+ if (SelectedItems.All(h => h.Samples.Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName)))
bindable.Value = TernaryState.True;
}
@@ -127,8 +132,70 @@ namespace osu.Game.Screens.Edit.Compose.Components
SelectionBankStates[bankName] = bindable;
}
+ foreach (string bankName in HitSampleInfo.AllBanks.Prepend(HIT_BANK_AUTO))
+ {
+ var bindable = new Bindable
+ {
+ Description = bankName.Titleize()
+ };
+
+ bindable.ValueChanged += state =>
+ {
+ switch (state.NewValue)
+ {
+ case TernaryState.False:
+ if (SelectedItems.Count == 0)
+ {
+ // Ensure that if this is the last selected bank, it should remain selected.
+ if (SelectionAdditionBankStates.Values.All(b => b.Value == TernaryState.False))
+ bindable.Value = TernaryState.True;
+ }
+
+ // Auto should never apply when there is a selection made.
+ // Completely empty selections should be allowed in the case that none of the selected objects have any addition samples.
+ break;
+
+ case TernaryState.True:
+ if (SelectedItems.Count == 0)
+ {
+ // Ensure the user can't stack multiple bank selections when there's no hitobject selection.
+ // Note that in normal scenarios this is sorted out by the feedback from applying the bank to the selected objects.
+ foreach (var other in SelectionAdditionBankStates.Values)
+ {
+ if (other != bindable)
+ other.Value = TernaryState.False;
+ }
+ }
+ else
+ {
+ // Auto should just not apply if there's a selection already made.
+ // Maybe we could make it a disabled button in the future, but right now the editor buttons don't support disabled state.
+ if (bankName == HIT_BANK_AUTO)
+ {
+ bindable.Value = TernaryState.False;
+ break;
+ }
+
+ // If none of the selected objects have any addition samples, we should not apply the bank.
+ if (SelectedItems.All(h => h.Samples.All(o => o.Name == HitSampleInfo.HIT_NORMAL)))
+ {
+ bindable.Value = TernaryState.False;
+ break;
+ }
+
+ SetSampleAdditionBank(bankName);
+ }
+
+ break;
+ }
+ };
+
+ SelectionAdditionBankStates[bankName] = bindable;
+ }
+
// start with normal selected.
SelectionBankStates[SampleControlPoint.DEFAULT_BANK].Value = TernaryState.True;
+ SelectionAdditionBankStates[SampleControlPoint.DEFAULT_BANK].Value = TernaryState.True;
foreach (string sampleName in HitSampleInfo.AllAdditions)
{
@@ -186,7 +253,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
foreach ((string bankName, var bindable) in SelectionBankStates)
{
- bindable.Value = GetStateFromSelection(samplesInSelection.SelectMany(s => s), h => h.Bank == bankName);
+ bindable.Value = GetStateFromSelection(samplesInSelection.SelectMany(s => s).Where(o => o.Name == HitSampleInfo.HIT_NORMAL), h => h.Bank == bankName);
+ }
+
+ foreach ((string bankName, var bindable) in SelectionAdditionBankStates)
+ {
+ bindable.Value = GetStateFromSelection(samplesInSelection.SelectMany(s => s).Where(o => o.Name != HitSampleInfo.HIT_NORMAL), h => h.Bank == bankName);
}
IEnumerable> enumerateAllSamples(HitObject hitObject)
@@ -213,12 +285,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
{
bool hasRelevantBank(HitObject hitObject)
{
- bool result = hitObject.Samples.All(s => s.Bank == bankName);
+ bool result = hitObject.Samples.Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName);
if (hitObject is IHasRepeats hasRepeats)
{
foreach (var node in hasRepeats.NodeSamples)
- result &= node.All(s => s.Bank == bankName);
+ result &= node.Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName);
}
return result;
@@ -229,15 +301,54 @@ namespace osu.Game.Screens.Edit.Compose.Components
EditorBeatmap.PerformOnSelection(h =>
{
- if (h.Samples.All(s => s.Bank == bankName))
+ if (h.Samples.Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName))
return;
- h.Samples = h.Samples.Select(s => s.With(newBank: bankName)).ToList();
+ h.Samples = h.Samples.Select(s => s.Name == HitSampleInfo.HIT_NORMAL ? s.With(newBank: bankName) : s).ToList();
if (h is IHasRepeats hasRepeats)
{
for (int i = 0; i < hasRepeats.NodeSamples.Count; ++i)
- hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(s => s.With(newBank: bankName)).ToList();
+ hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(s => s.Name == HitSampleInfo.HIT_NORMAL ? s.With(newBank: bankName) : s).ToList();
+ }
+
+ EditorBeatmap.Update(h);
+ });
+ }
+
+ ///
+ /// Sets the sample addition bank for all selected s.
+ ///
+ /// The name of the sample bank.
+ public void SetSampleAdditionBank(string bankName)
+ {
+ bool hasRelevantBank(HitObject hitObject)
+ {
+ bool result = hitObject.Samples.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName);
+
+ if (hitObject is IHasRepeats hasRepeats)
+ {
+ foreach (var node in hasRepeats.NodeSamples)
+ result &= node.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName);
+ }
+
+ return result;
+ }
+
+ if (SelectedItems.All(hasRelevantBank))
+ return;
+
+ EditorBeatmap.PerformOnSelection(h =>
+ {
+ if (h.Samples.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName))
+ return;
+
+ h.Samples = h.Samples.Select(s => s.Name != HitSampleInfo.HIT_NORMAL ? s.With(newBank: bankName) : s).ToList();
+
+ if (h is IHasRepeats hasRepeats)
+ {
+ for (int i = 0; i < hasRepeats.NodeSamples.Count; ++i)
+ hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(s => s.Name != HitSampleInfo.HIT_NORMAL ? s.With(newBank: bankName) : s).ToList();
}
EditorBeatmap.Update(h);
@@ -366,6 +477,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
Items = SelectionBankStates.Select(kvp =>
new TernaryStateToggleMenuItem(kvp.Value.Description) { State = { BindTarget = kvp.Value } }).ToArray()
};
+
+ yield return new OsuMenuItem("Addition bank")
+ {
+ Items = SelectionAdditionBankStates.Select(kvp =>
+ new TernaryStateToggleMenuItem(kvp.Value.Description) { State = { BindTarget = kvp.Value } }).ToArray()
+ };
}
#endregion
From 5e86a01e5e3e91404d5021572cd8170661d59618 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sun, 14 Jul 2024 20:59:46 +0200
Subject: [PATCH 0188/1274] allow hotkeys in sample point piece
---
osu.Game/Rulesets/Edit/HitObjectComposer.cs | 28 +++++++++++++------
.../Components/Timeline/SamplePointPiece.cs | 19 +++++++++----
2 files changed, 33 insertions(+), 14 deletions(-)
diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
index ac9c830f03..f2713739b9 100644
--- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs
+++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
@@ -373,6 +373,8 @@ namespace osu.Game.Rulesets.Edit
if (e.ControlPressed || e.SuperPressed)
return false;
+ bool handled = false;
+
if (checkLeftToggleFromKey(e.Key, out int leftIndex))
{
var item = toolboxCollection.Items.ElementAtOrDefault(leftIndex);
@@ -387,20 +389,30 @@ namespace osu.Game.Rulesets.Edit
if (checkRightToggleFromKey(e.Key, out int rightIndex))
{
- var item = e.AltPressed
- ? sampleAdditionBankTogglesCollection.ElementAtOrDefault(rightIndex)
- : e.ShiftPressed
- ? sampleBankTogglesCollection.ElementAtOrDefault(rightIndex)
- : togglesCollection.ElementAtOrDefault(rightIndex);
+ if (e.ShiftPressed || e.AltPressed)
+ {
+ if (e.ShiftPressed)
+ attemptToggle(rightIndex, sampleBankTogglesCollection);
+
+ if (e.AltPressed)
+ attemptToggle(rightIndex, sampleAdditionBankTogglesCollection);
+ }
+ else
+ attemptToggle(rightIndex, togglesCollection);
+ }
+
+ return handled || base.OnKeyDown(e);
+
+ void attemptToggle(int index, FillFlowContainer collection)
+ {
+ var item = collection.ElementAtOrDefault(index);
if (item is DrawableTernaryButton button)
{
button.Button.Toggle();
- return true;
+ handled = true;
}
}
-
- return base.OnKeyDown(e);
}
private bool checkLeftToggleFromKey(Key key, out int index)
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs
index 8c7603021a..07c6cab7a1 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs
@@ -381,20 +381,27 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
protected override bool OnKeyDown(KeyDownEvent e)
{
- if (e.ControlPressed || e.AltPressed || e.SuperPressed || !checkRightToggleFromKey(e.Key, out int rightIndex))
+ if (e.ControlPressed || e.SuperPressed || !checkRightToggleFromKey(e.Key, out int rightIndex))
return base.OnKeyDown(e);
- if (e.ShiftPressed)
+ if (e.ShiftPressed || e.AltPressed)
{
string? newBank = banks.ElementAtOrDefault(rightIndex);
if (string.IsNullOrEmpty(newBank))
return true;
- setBank(newBank);
- updatePrimaryBankState();
- setAdditionBank(newBank);
- updateAdditionBankState();
+ if (e.ShiftPressed)
+ {
+ setBank(newBank);
+ updatePrimaryBankState();
+ }
+
+ if (e.AltPressed)
+ {
+ setAdditionBank(newBank);
+ updateAdditionBankState();
+ }
}
else
{
From df7a42a00e95ceae944114fe162f266a5fc7708d Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sun, 14 Jul 2024 22:33:23 +0200
Subject: [PATCH 0189/1274] fix placement samples
---
osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 37 +++++++++++--------
.../Components/ComposeBlueprintContainer.cs | 18 ++++++++-
2 files changed, 38 insertions(+), 17 deletions(-)
diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs
index 2817e26abd..26b18828d3 100644
--- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs
+++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs
@@ -37,6 +37,11 @@ namespace osu.Game.Rulesets.Edit
///
public bool AutomaticBankAssignment { get; set; }
+ ///
+ /// Whether the sample addition bank should be taken from the previous hit objects.
+ ///
+ public bool AutomaticAdditionBankAssignment { get; set; }
+
///
/// The that is being placed.
///
@@ -157,26 +162,26 @@ namespace osu.Game.Rulesets.Edit
}
var lastHitObject = getPreviousHitObject();
+ var lastHitNormal = lastHitObject?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL);
- if (AutomaticBankAssignment)
+ if (AutomaticAdditionBankAssignment)
{
- // Create samples based on the sample settings of the previous hit object
- if (lastHitObject != null)
- {
- for (int i = 0; i < HitObject.Samples.Count; i++)
- HitObject.Samples[i] = lastHitObject.CreateHitSampleInfo(HitObject.Samples[i].Name);
- }
+ // Inherit the addition bank from the previous hit object
+ // If there is no previous addition, inherit from the normal sample
+ var lastAddition = lastHitObject?.Samples?.FirstOrDefault(o => o.Name != HitSampleInfo.HIT_NORMAL) ?? lastHitNormal;
+
+ if (lastAddition != null)
+ HitObject.Samples = HitObject.Samples.Select(s => s.Name != HitSampleInfo.HIT_NORMAL ? s.With(newBank: lastAddition.Bank) : s).ToList();
}
- else
- {
- var lastHitNormal = lastHitObject?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL);
- if (lastHitNormal != null)
- {
- // Only inherit the volume from the previous hit object
- for (int i = 0; i < HitObject.Samples.Count; i++)
- HitObject.Samples[i] = HitObject.Samples[i].With(newVolume: lastHitNormal.Volume);
- }
+ if (lastHitNormal != null)
+ {
+ if (AutomaticBankAssignment)
+ // Inherit the bank from the previous hit object
+ HitObject.Samples = HitObject.Samples.Select(s => s.Name == HitSampleInfo.HIT_NORMAL ? s.With(newBank: lastHitNormal.Bank) : s).ToList();
+
+ // Inherit the volume from the previous hit object
+ HitObject.Samples = HitObject.Samples.Select(s => s.With(newVolume: lastHitNormal.Volume)).ToList();
}
if (HitObject is IHasRepeats hasRepeats)
diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs
index 671180348d..fc81bc5706 100644
--- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs
@@ -89,6 +89,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
foreach (var kvp in SelectionHandler.SelectionBankStates)
kvp.Value.BindValueChanged(_ => updatePlacementSamples());
+
+ foreach (var kvp in SelectionHandler.SelectionAdditionBankStates)
+ kvp.Value.BindValueChanged(_ => updatePlacementSamples());
}
protected override void TransferBlueprintFor(HitObject hitObject, DrawableHitObject drawableObject)
@@ -177,6 +180,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
foreach (var kvp in SelectionHandler.SelectionBankStates)
bankChanged(kvp.Key, kvp.Value.Value);
+
+ foreach (var kvp in SelectionHandler.SelectionAdditionBankStates)
+ additionBankChanged(kvp.Key, kvp.Value.Value);
}
private void sampleChanged(string sampleName, TernaryState state)
@@ -208,7 +214,17 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (bankName == EditorSelectionHandler.HIT_BANK_AUTO)
CurrentPlacement.AutomaticBankAssignment = state == TernaryState.True;
else if (state == TernaryState.True)
- CurrentPlacement.HitObject.Samples = CurrentPlacement.HitObject.Samples.Select(s => s.With(newBank: bankName)).ToList();
+ CurrentPlacement.HitObject.Samples = CurrentPlacement.HitObject.Samples.Select(s => s.Name == HitSampleInfo.HIT_NORMAL ? s.With(newBank: bankName) : s).ToList();
+ }
+
+ private void additionBankChanged(string bankName, TernaryState state)
+ {
+ if (CurrentPlacement == null) return;
+
+ if (bankName == EditorSelectionHandler.HIT_BANK_AUTO)
+ CurrentPlacement.AutomaticAdditionBankAssignment = state == TernaryState.True;
+ else if (state == TernaryState.True)
+ CurrentPlacement.HitObject.Samples = CurrentPlacement.HitObject.Samples.Select(s => s.Name != HitSampleInfo.HIT_NORMAL ? s.With(newBank: bankName) : s).ToList();
}
public readonly Bindable NewCombo = new Bindable { Description = "New Combo" };
From 9f02a1f1bf248a3ea395f63e6da6eb4ce19ef639 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sun, 14 Jul 2024 22:50:15 +0200
Subject: [PATCH 0190/1274] fix addition on node sample not working
---
.../Components/EditorSelectionHandler.cs | 22 +++++++++----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs
index 04baaa6134..3a0428e7fc 100644
--- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs
@@ -176,8 +176,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
break;
}
- // If none of the selected objects have any addition samples, we should not apply the bank.
- if (SelectedItems.All(h => h.Samples.All(o => o.Name == HitSampleInfo.HIT_NORMAL)))
+ // If none of the selected objects have any addition samples, we should not apply the addition bank.
+ if (SelectedItems.SelectMany(enumerateAllSamples).All(h => h.All(o => o.Name == HitSampleInfo.HIT_NORMAL)))
{
bindable.Value = TernaryState.False;
break;
@@ -260,16 +260,16 @@ namespace osu.Game.Screens.Edit.Compose.Components
{
bindable.Value = GetStateFromSelection(samplesInSelection.SelectMany(s => s).Where(o => o.Name != HitSampleInfo.HIT_NORMAL), h => h.Bank == bankName);
}
+ }
- IEnumerable> enumerateAllSamples(HitObject hitObject)
+ private IEnumerable> enumerateAllSamples(HitObject hitObject)
+ {
+ yield return hitObject.Samples;
+
+ if (hitObject is IHasRepeats withRepeats)
{
- yield return hitObject.Samples;
-
- if (hitObject is IHasRepeats withRepeats)
- {
- foreach (var node in withRepeats.NodeSamples)
- yield return node;
- }
+ foreach (var node in withRepeats.NodeSamples)
+ yield return node;
}
}
@@ -340,7 +340,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
EditorBeatmap.PerformOnSelection(h =>
{
- if (h.Samples.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName))
+ if (enumerateAllSamples(h).SelectMany(o => o).Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName))
return;
h.Samples = h.Samples.Select(s => s.Name != HitSampleInfo.HIT_NORMAL ? s.With(newBank: bankName) : s).ToList();
From 29c2e821ea3e66f9b7846bae4a19a419cdf38d95 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sun, 14 Jul 2024 22:50:25 +0200
Subject: [PATCH 0191/1274] extend tests
---
.../TestSceneHitObjectSampleAdjustments.cs | 136 ++++++++++++++++--
1 file changed, 123 insertions(+), 13 deletions(-)
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs
index 558d8dce94..e962d3975c 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs
@@ -321,6 +321,12 @@ namespace osu.Game.Tests.Visual.Editing
}
});
+ AddStep("add whistle addition", () =>
+ {
+ foreach (var h in EditorBeatmap.HitObjects)
+ h.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, HitSampleInfo.BANK_SOFT));
+ });
+
AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT);
@@ -333,8 +339,10 @@ namespace osu.Game.Tests.Visual.Editing
InputManager.ReleaseKey(Key.ShiftLeft);
});
- hitObjectHasSampleBank(0, HitSampleInfo.BANK_NORMAL);
- hitObjectHasSampleBank(1, HitSampleInfo.BANK_NORMAL);
+ hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
+ hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_NORMAL);
+ hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT);
+ hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_SOFT);
AddStep("Press drum bank shortcut", () =>
{
@@ -343,8 +351,10 @@ namespace osu.Game.Tests.Visual.Editing
InputManager.ReleaseKey(Key.ShiftLeft);
});
- hitObjectHasSampleBank(0, HitSampleInfo.BANK_DRUM);
- hitObjectHasSampleBank(1, HitSampleInfo.BANK_DRUM);
+ hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
+ hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_DRUM);
+ hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT);
+ hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_SOFT);
AddStep("Press auto bank shortcut", () =>
{
@@ -354,8 +364,47 @@ namespace osu.Game.Tests.Visual.Editing
});
// Should be a noop.
- hitObjectHasSampleBank(0, HitSampleInfo.BANK_DRUM);
- hitObjectHasSampleBank(1, HitSampleInfo.BANK_DRUM);
+ hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
+ hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_DRUM);
+ hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT);
+ hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_SOFT);
+
+ AddStep("Press addition normal bank shortcut", () =>
+ {
+ InputManager.PressKey(Key.AltLeft);
+ InputManager.Key(Key.W);
+ InputManager.ReleaseKey(Key.AltLeft);
+ });
+
+ hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
+ hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_DRUM);
+ hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_NORMAL);
+ hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_NORMAL);
+
+ AddStep("Press addition drum bank shortcut", () =>
+ {
+ InputManager.PressKey(Key.AltLeft);
+ InputManager.Key(Key.R);
+ InputManager.ReleaseKey(Key.AltLeft);
+ });
+
+ hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
+ hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_DRUM);
+ hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
+ hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_DRUM);
+
+ AddStep("Press auto bank shortcut", () =>
+ {
+ InputManager.PressKey(Key.AltLeft);
+ InputManager.Key(Key.Q);
+ InputManager.ReleaseKey(Key.AltLeft);
+ });
+
+ // Should be a noop.
+ hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
+ hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_DRUM);
+ hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
+ hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_DRUM);
}
[Test]
@@ -373,7 +422,21 @@ namespace osu.Game.Tests.Visual.Editing
InputManager.ReleaseKey(Key.ShiftLeft);
});
- checkPlacementSample(HitSampleInfo.BANK_NORMAL);
+ AddStep("Press soft addition bank shortcut", () =>
+ {
+ InputManager.PressKey(Key.AltLeft);
+ InputManager.Key(Key.E);
+ InputManager.ReleaseKey(Key.AltLeft);
+ });
+
+ checkPlacementSampleBank(HitSampleInfo.BANK_NORMAL);
+
+ AddStep("Press finish sample shortcut", () =>
+ {
+ InputManager.Key(Key.E);
+ });
+
+ checkPlacementSampleAdditionBank(HitSampleInfo.BANK_SOFT);
AddStep("Press drum bank shortcut", () =>
{
@@ -382,7 +445,18 @@ namespace osu.Game.Tests.Visual.Editing
InputManager.ReleaseKey(Key.ShiftLeft);
});
- checkPlacementSample(HitSampleInfo.BANK_DRUM);
+ checkPlacementSampleBank(HitSampleInfo.BANK_DRUM);
+ checkPlacementSampleAdditionBank(HitSampleInfo.BANK_SOFT);
+
+ AddStep("Press drum addition bank shortcut", () =>
+ {
+ InputManager.PressKey(Key.AltLeft);
+ InputManager.Key(Key.R);
+ InputManager.ReleaseKey(Key.AltLeft);
+ });
+
+ checkPlacementSampleBank(HitSampleInfo.BANK_DRUM);
+ checkPlacementSampleAdditionBank(HitSampleInfo.BANK_DRUM);
AddStep("Press auto bank shortcut", () =>
{
@@ -391,15 +465,29 @@ namespace osu.Game.Tests.Visual.Editing
InputManager.ReleaseKey(Key.ShiftLeft);
});
- checkPlacementSample(HitSampleInfo.BANK_NORMAL);
+ checkPlacementSampleBank(HitSampleInfo.BANK_NORMAL);
+ checkPlacementSampleAdditionBank(HitSampleInfo.BANK_DRUM);
+
+ AddStep("Press auto addition bank shortcut", () =>
+ {
+ InputManager.PressKey(Key.AltLeft);
+ InputManager.Key(Key.Q);
+ InputManager.ReleaseKey(Key.AltLeft);
+ });
+
+ checkPlacementSampleBank(HitSampleInfo.BANK_NORMAL);
+ checkPlacementSampleAdditionBank(HitSampleInfo.BANK_NORMAL);
AddStep("Move after second object", () => EditorClock.Seek(750));
- checkPlacementSample(HitSampleInfo.BANK_SOFT);
+ checkPlacementSampleBank(HitSampleInfo.BANK_SOFT);
+ checkPlacementSampleAdditionBank(HitSampleInfo.BANK_SOFT);
AddStep("Move to first object", () => EditorClock.Seek(0));
- checkPlacementSample(HitSampleInfo.BANK_NORMAL);
+ checkPlacementSampleBank(HitSampleInfo.BANK_NORMAL);
+ checkPlacementSampleAdditionBank(HitSampleInfo.BANK_NORMAL);
- void checkPlacementSample(string expected) => AddAssert($"Placement sample is {expected}", () => EditorBeatmap.PlacementObject.Value.Samples.First().Bank, () => Is.EqualTo(expected));
+ void checkPlacementSampleBank(string expected) => AddAssert($"Placement sample is {expected}", () => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name == HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(expected));
+ void checkPlacementSampleAdditionBank(string expected) => AddAssert($"Placement sample addition is {expected}", () => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name != HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(expected));
}
[Test]
@@ -480,7 +568,29 @@ namespace osu.Game.Tests.Visual.Editing
hitObjectHasSamples(2, HitSampleInfo.HIT_NORMAL);
hitObjectNodeHasSampleBank(2, 0, HitSampleInfo.BANK_DRUM);
hitObjectNodeHasSamples(2, 0, HitSampleInfo.HIT_NORMAL);
- hitObjectNodeHasSampleBank(2, 1, HitSampleInfo.BANK_DRUM);
+ hitObjectNodeHasSampleNormalBank(2, 1, HitSampleInfo.BANK_DRUM);
+ hitObjectNodeHasSampleAdditionBank(2, 1, HitSampleInfo.BANK_SOFT);
+ hitObjectNodeHasSamples(2, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
+
+ AddStep("set normal addition bank", () =>
+ {
+ InputManager.PressKey(Key.LAlt);
+ InputManager.Key(Key.W);
+ InputManager.ReleaseKey(Key.LAlt);
+ });
+
+ hitObjectHasSampleBank(0, HitSampleInfo.BANK_DRUM);
+ hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL);
+
+ hitObjectHasSampleBank(1, HitSampleInfo.BANK_DRUM);
+ hitObjectHasSamples(1, HitSampleInfo.HIT_NORMAL);
+
+ hitObjectHasSampleBank(2, HitSampleInfo.BANK_DRUM);
+ hitObjectHasSamples(2, HitSampleInfo.HIT_NORMAL);
+ hitObjectNodeHasSampleBank(2, 0, HitSampleInfo.BANK_DRUM);
+ hitObjectNodeHasSamples(2, 0, HitSampleInfo.HIT_NORMAL);
+ hitObjectNodeHasSampleNormalBank(2, 1, HitSampleInfo.BANK_DRUM);
+ hitObjectNodeHasSampleAdditionBank(2, 1, HitSampleInfo.BANK_NORMAL);
hitObjectNodeHasSamples(2, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
}
From e5bc359b8811fcd0281dd6052031e05a950644b5 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sun, 14 Jul 2024 23:39:19 +0200
Subject: [PATCH 0192/1274] fix test
---
osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs
index e9b442f8dd..fba84108d0 100644
--- a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs
+++ b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs
@@ -123,7 +123,9 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("enable automatic bank assignment", () =>
{
InputManager.PressKey(Key.LShift);
+ InputManager.PressKey(Key.LAlt);
InputManager.Key(Key.Q);
+ InputManager.ReleaseKey(Key.LAlt);
InputManager.ReleaseKey(Key.LShift);
});
AddStep("select circle placement tool", () => InputManager.Key(Key.Number2));
@@ -186,7 +188,9 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("select drum bank", () =>
{
InputManager.PressKey(Key.LShift);
+ InputManager.PressKey(Key.LAlt);
InputManager.Key(Key.R);
+ InputManager.ReleaseKey(Key.LAlt);
InputManager.ReleaseKey(Key.LShift);
});
AddStep("enable clap addition", () => InputManager.Key(Key.R));
From e25642b48472789aaffa5254106d763a4cee9cad Mon Sep 17 00:00:00 2001
From: StanR
Date: Mon, 15 Jul 2024 14:45:31 +0500
Subject: [PATCH 0193/1274] 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 0194/1274] 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 0195/1274] 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 0196/1274] Even better readability
---
osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 0aec0a9d4f..a4ebcd15a4 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window.
double betterAccuracyPercentage;
int amountHitObjectsWithAccuracy = attributes.HitCircleCount;
- if (score.Mods.All(h => h is not OsuModClassic cl || !cl.NoSliderHeadAccuracy.Value))
+ if (score.Mods.OfType().All(m => !m.NoSliderHeadAccuracy.Value))
amountHitObjectsWithAccuracy += attributes.SliderCount;
if (amountHitObjectsWithAccuracy > 0)
From c18814817b7d5b1f907574b6cca89f4fdc0efa53 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Tue, 16 Jul 2024 11:17:54 +0200
Subject: [PATCH 0197/1274] fix test
---
osu.Game.Rulesets.Osu.Tests/Editor/TestScenePreciseRotation.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePreciseRotation.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePreciseRotation.cs
index 30e0dbbf2e..d14bd1fc87 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePreciseRotation.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePreciseRotation.cs
@@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
() => EditorBeatmap.HitObjects.OfType().ElementAt(1).Position,
() => Is.EqualTo(OsuPlayfield.BASE_SIZE - new Vector2(200)));
- AddStep("change rotation origin", () => getPopover().ChildrenOfType().ElementAt(1).TriggerClick());
+ AddStep("change rotation origin", () => getPopover().ChildrenOfType().ElementAt(2).TriggerClick());
AddAssert("first object rotated 90deg around selection centre",
() => EditorBeatmap.HitObjects.OfType().ElementAt(0).Position, () => Is.EqualTo(new Vector2(200, 200)));
AddAssert("second object rotated 90deg around selection centre",
From ba501a8eb80cddea92a77cbedea8cfc4c8a540e0 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Tue, 16 Jul 2024 11:30:52 +0200
Subject: [PATCH 0198/1274] fix addition bank toggle can be switched off with
selection
---
.../Components/EditorSelectionHandler.cs | 18 ++++++++++++++++--
1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs
index 3a0428e7fc..df8c0176e1 100644
--- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs
@@ -150,9 +150,23 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (SelectionAdditionBankStates.Values.All(b => b.Value == TernaryState.False))
bindable.Value = TernaryState.True;
}
+ else
+ {
+ // Auto should never apply when there is a selection made.
+ if (bankName == HIT_BANK_AUTO)
+ break;
+
+ // Completely empty selections should be allowed in the case that none of the selected objects have any addition samples.
+ // This is also required to stop a bindable feedback loop when a HitObject has zero addition samples (and LINQ `All` below becomes true).
+ if (SelectedItems.SelectMany(enumerateAllSamples).All(h => h.All(o => o.Name == HitSampleInfo.HIT_NORMAL)))
+ break;
+
+ // Never remove a sample bank.
+ // These are basically radio buttons, not toggles.
+ if (SelectedItems.SelectMany(enumerateAllSamples).All(h => h.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName)))
+ bindable.Value = TernaryState.True;
+ }
- // Auto should never apply when there is a selection made.
- // Completely empty selections should be allowed in the case that none of the selected objects have any addition samples.
break;
case TernaryState.True:
From 7dc006f9bab4f22e326c5692a40a2afc5bfdc566 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Tue, 16 Jul 2024 13:19:01 +0200
Subject: [PATCH 0199/1274] fix horizontal flip rotation
---
osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
index 1334dbdbec..2dc43deee1 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
@@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.Osu.Edit
// however it's still possible to achieve that flip by combining multiple flips over the other axes.
flipAxis = direction == Direction.Vertical
? GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 390) % 60 + 60))
- : GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 390) % 60 - 60));
+ : GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 360) % 60 - 30));
break;
}
}
From 0bc14ba646e8fc7adfa9d8e3b53d0bf1341239e0 Mon Sep 17 00:00:00 2001
From: Layendan
Date: Wed, 17 Jul 2024 12:45:20 -0700
Subject: [PATCH 0200/1274] Add favourite button to results screen
---
osu.Game/Screens/Ranking/FavouriteButton.cs | 145 ++++++++++++++++++++
osu.Game/Screens/Ranking/ResultsScreen.cs | 25 +++-
2 files changed, 169 insertions(+), 1 deletion(-)
create mode 100644 osu.Game/Screens/Ranking/FavouriteButton.cs
diff --git a/osu.Game/Screens/Ranking/FavouriteButton.cs b/osu.Game/Screens/Ranking/FavouriteButton.cs
new file mode 100644
index 0000000000..ee093d343e
--- /dev/null
+++ b/osu.Game/Screens/Ranking/FavouriteButton.cs
@@ -0,0 +1,145 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+#nullable disable
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Logging;
+using osu.Game.Beatmaps.Drawables.Cards;
+using osu.Game.Graphics;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
+using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Resources.Localisation.Web;
+using osuTK;
+
+namespace osu.Game.Screens.Ranking
+{
+ public partial class FavouriteButton : OsuAnimatedButton
+ {
+ private readonly Box background;
+ private readonly SpriteIcon icon;
+ private readonly BindableWithCurrent current;
+
+ public Bindable Current
+ {
+ get => current.Current;
+ set => current.Current = value;
+ }
+
+ private readonly APIBeatmapSet beatmapSet;
+
+ private PostBeatmapFavouriteRequest favouriteRequest;
+ private LoadingLayer loading;
+
+ private readonly IBindable localUser = new Bindable();
+
+ [Resolved]
+ private IAPIProvider api { get; set; }
+
+ [Resolved]
+ private OsuColour colours { get; set; }
+
+ public FavouriteButton(APIBeatmapSet beatmapSet)
+ {
+ this.beatmapSet = beatmapSet;
+ current = new BindableWithCurrent(new BeatmapSetFavouriteState(this.beatmapSet.HasFavourited, this.beatmapSet.FavouriteCount));
+
+ Size = new Vector2(50, 30);
+
+ Children = new Drawable[]
+ {
+ background = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Depth = float.MaxValue
+ },
+ icon = new SpriteIcon
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(13),
+ Icon = FontAwesome.Regular.Heart,
+ },
+ loading = new LoadingLayer(true, false),
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours, IAPIProvider api)
+ {
+ this.api = api;
+
+ updateState();
+
+ localUser.BindTo(api.LocalUser);
+ localUser.BindValueChanged(_ => updateEnabled());
+
+ Action = () => toggleFavouriteStatus();
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ Action = toggleFavouriteStatus;
+ current.BindValueChanged(_ => updateState(), true);
+ }
+
+ private void toggleFavouriteStatus()
+ {
+
+ Enabled.Value = false;
+ loading.Show();
+
+ var actionType = current.Value.Favourited ? BeatmapFavouriteAction.UnFavourite : BeatmapFavouriteAction.Favourite;
+
+ favouriteRequest?.Cancel();
+ favouriteRequest = new PostBeatmapFavouriteRequest(beatmapSet.OnlineID, actionType);
+
+ favouriteRequest.Success += () =>
+ {
+ bool favourited = actionType == BeatmapFavouriteAction.Favourite;
+
+ current.Value = new BeatmapSetFavouriteState(favourited, current.Value.FavouriteCount + (favourited ? 1 : -1));
+
+ Enabled.Value = true;
+ loading.Hide();
+ };
+ favouriteRequest.Failure += e =>
+ {
+ Logger.Error(e, $"Failed to {actionType.ToString().ToLowerInvariant()} beatmap: {e.Message}");
+ Enabled.Value = true;
+ loading.Hide();
+ };
+
+ api.Queue(favouriteRequest);
+ }
+
+ private void updateEnabled() => Enabled.Value = !(localUser.Value is GuestUser) && beatmapSet.OnlineID > 0;
+
+ private void updateState()
+ {
+ if (current?.Value == null)
+ return;
+
+ if (current.Value.Favourited)
+ {
+ background.Colour = colours.Green;
+ icon.Icon = FontAwesome.Solid.Heart;
+ TooltipText = BeatmapsetsStrings.ShowDetailsUnfavourite;
+ }
+ else
+ {
+ background.Colour = colours.Gray4;
+ icon.Icon = FontAwesome.Regular.Heart;
+ TooltipText = BeatmapsetsStrings.ShowDetailsFavourite;
+ }
+ }
+ }
+}
diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs
index 44b270db53..e96265be3d 100644
--- a/osu.Game/Screens/Ranking/ResultsScreen.cs
+++ b/osu.Game/Screens/Ranking/ResultsScreen.cs
@@ -15,6 +15,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
+using osu.Framework.Logging;
using osu.Framework.Screens;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
@@ -22,6 +23,7 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Localisation;
using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
using osu.Game.Online.Placeholders;
using osu.Game.Overlays;
using osu.Game.Scoring;
@@ -76,7 +78,7 @@ namespace osu.Game.Screens.Ranking
///
/// Whether the user's personal statistics should be shown on the extended statistics panel
- /// after clicking the score panel associated with the being presented.
+ /// after clicking the score panel associated with the being presented.
/// Requires to be present.
///
public bool ShowUserStatistics { get; init; }
@@ -202,6 +204,27 @@ namespace osu.Game.Screens.Ranking
},
});
}
+
+ // Do not render if user is not logged in or the mapset does not have a valid online ID.
+ if (api.IsLoggedIn && Score?.BeatmapInfo?.BeatmapSet != null && Score.BeatmapInfo.BeatmapSet.OnlineID > 0)
+ {
+ GetBeatmapSetRequest beatmapSetRequest;
+ beatmapSetRequest = new GetBeatmapSetRequest(Score.BeatmapInfo.BeatmapSet.OnlineID);
+
+ beatmapSetRequest.Success += (beatmapSet) =>
+ {
+ buttons.Add(new FavouriteButton(beatmapSet)
+ {
+ Width = 75
+ });
+ };
+ beatmapSetRequest.Failure += e =>
+ {
+ Logger.Error(e, $"Failed to fetch beatmap info: {e.Message}");
+ };
+
+ api.Queue(beatmapSetRequest);
+ }
}
protected override void LoadComplete()
From bae9625b0b4785b83124bfd38ccd8ff85d74947b Mon Sep 17 00:00:00 2001
From: StanR
Date: Fri, 19 Jul 2024 10:13:50 +0500
Subject: [PATCH 0201/1274] 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 0202/1274] 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 3296beb00362a55e012f6b809d901da02174552b Mon Sep 17 00:00:00 2001
From: Layendan
Date: Sat, 20 Jul 2024 11:49:46 -0700
Subject: [PATCH 0203/1274] Added collection button to result screen
---
osu.Game/Screens/Ranking/CollectionButton.cs | 64 ++++++++++
osu.Game/Screens/Ranking/CollectionPopover.cs | 70 +++++++++++
osu.Game/Screens/Ranking/FavouriteButton.cs | 7 +-
osu.Game/Screens/Ranking/ResultsScreen.cs | 118 ++++++++++--------
4 files changed, 201 insertions(+), 58 deletions(-)
create mode 100644 osu.Game/Screens/Ranking/CollectionButton.cs
create mode 100644 osu.Game/Screens/Ranking/CollectionPopover.cs
diff --git a/osu.Game/Screens/Ranking/CollectionButton.cs b/osu.Game/Screens/Ranking/CollectionButton.cs
new file mode 100644
index 0000000000..99a51e03d9
--- /dev/null
+++ b/osu.Game/Screens/Ranking/CollectionButton.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.
+
+#nullable disable
+
+using osu.Framework.Allocation;
+using osu.Framework.Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Cursor;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.UserInterface;
+using osu.Game.Beatmaps;
+using osu.Game.Graphics;
+using osu.Game.Graphics.UserInterface;
+using osuTK;
+
+namespace osu.Game.Screens.Ranking
+{
+ public partial class CollectionButton : OsuAnimatedButton, IHasPopover
+ {
+ private readonly Box background;
+
+ private readonly BeatmapInfo beatmapInfo;
+
+ public CollectionButton(BeatmapInfo beatmapInfo)
+ {
+ this.beatmapInfo = beatmapInfo;
+
+ Size = new Vector2(50, 30);
+
+ Children = new Drawable[]
+ {
+ background = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Depth = float.MaxValue
+ },
+ new SpriteIcon
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(13),
+ Icon = FontAwesome.Solid.Book,
+ },
+ };
+
+ TooltipText = "collections";
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ background.Colour = colours.Green;
+
+ Action = this.ShowPopover;
+ }
+
+ // use Content for tracking input as some buttons might be temporarily hidden with DisappearToBottom, and they become hidden by moving Content away from screen.
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Content.ReceivePositionalInputAt(screenSpacePos);
+
+ public Popover GetPopover() => new CollectionPopover(beatmapInfo);
+ }
+}
diff --git a/osu.Game/Screens/Ranking/CollectionPopover.cs b/osu.Game/Screens/Ranking/CollectionPopover.cs
new file mode 100644
index 0000000000..926745d4d9
--- /dev/null
+++ b/osu.Game/Screens/Ranking/CollectionPopover.cs
@@ -0,0 +1,70 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+#nullable disable
+
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Input.Events;
+using osu.Game.Beatmaps;
+using osu.Game.Collections;
+using osu.Game.Database;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Graphics.UserInterfaceV2;
+
+namespace osu.Game.Screens.Ranking
+{
+ public partial class CollectionPopover : OsuPopover
+ {
+ private OsuMenu menu;
+ private readonly BeatmapInfo beatmapInfo;
+
+ [Resolved]
+ private RealmAccess realm { get; set; } = null!;
+ [Resolved]
+ private ManageCollectionsDialog? manageCollectionsDialog { get; set; }
+
+ public CollectionPopover(BeatmapInfo beatmapInfo) : base(false)
+ {
+ this.beatmapInfo = beatmapInfo;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Margin = new MarginPadding(5);
+ Body.CornerRadius = 4;
+
+ Children = new[]
+ {
+ menu = new OsuMenu(Direction.Vertical, true)
+ {
+ Items = items,
+ },
+ };
+ }
+
+ protected override void OnFocusLost(FocusLostEvent e)
+ {
+ base.OnFocusLost(e);
+ Hide();
+ }
+
+ private OsuMenuItem[] items
+ {
+ get
+ {
+ var collectionItems = realm.Realm.All()
+ .OrderBy(c => c.Name)
+ .AsEnumerable()
+ .Select(c => new CollectionToggleMenuItem(c.ToLive(realm), beatmapInfo)).Cast().ToList();
+
+ if (manageCollectionsDialog != null)
+ collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show));
+
+ return collectionItems.ToArray();
+ }
+ }
+ }
+}
diff --git a/osu.Game/Screens/Ranking/FavouriteButton.cs b/osu.Game/Screens/Ranking/FavouriteButton.cs
index ee093d343e..6014929242 100644
--- a/osu.Game/Screens/Ranking/FavouriteButton.cs
+++ b/osu.Game/Screens/Ranking/FavouriteButton.cs
@@ -71,23 +71,20 @@ namespace osu.Game.Screens.Ranking
}
[BackgroundDependencyLoader]
- private void load(OsuColour colours, IAPIProvider api)
+ private void load()
{
- this.api = api;
-
updateState();
localUser.BindTo(api.LocalUser);
localUser.BindValueChanged(_ => updateEnabled());
- Action = () => toggleFavouriteStatus();
+ Action = toggleFavouriteStatus;
}
protected override void LoadComplete()
{
base.LoadComplete();
- Action = toggleFavouriteStatus;
current.BindValueChanged(_ => updateState(), true);
}
diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs
index e96265be3d..b88a3cd2f8 100644
--- a/osu.Game/Screens/Ranking/ResultsScreen.cs
+++ b/osu.Game/Screens/Ranking/ResultsScreen.cs
@@ -12,6 +12,7 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
@@ -99,73 +100,79 @@ namespace osu.Game.Screens.Ranking
popInSample = audio.Samples.Get(@"UI/overlay-pop-in");
- InternalChild = new GridContainer
+ InternalChild = new PopoverContainer
{
+ Depth = -1,
RelativeSizeAxes = Axes.Both,
- Content = new[]
+ Padding = new MarginPadding(0),
+ Child = new GridContainer
{
- new Drawable[]
+ RelativeSizeAxes = Axes.Both,
+ Content = new[]
{
- VerticalScrollContent = new VerticalScrollContainer
+ new Drawable[]
{
- RelativeSizeAxes = Axes.Both,
- ScrollbarVisible = false,
- Child = new Container
+ VerticalScrollContent = new VerticalScrollContainer
{
RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
- {
- StatisticsPanel = createStatisticsPanel().With(panel =>
- {
- panel.RelativeSizeAxes = Axes.Both;
- panel.Score.BindTarget = SelectedScore;
- }),
- ScorePanelList = new ScorePanelList
- {
- RelativeSizeAxes = Axes.Both,
- SelectedScore = { BindTarget = SelectedScore },
- PostExpandAction = () => StatisticsPanel.ToggleVisibility()
- },
- detachedPanelContainer = new Container
- {
- RelativeSizeAxes = Axes.Both
- },
- }
- }
- },
- },
- new[]
- {
- bottomPanel = new Container
- {
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- RelativeSizeAxes = Axes.X,
- Height = TwoLayerButton.SIZE_EXTENDED.Y,
- Alpha = 0,
- Children = new Drawable[]
- {
- new Box
+ ScrollbarVisible = false,
+ Child = new Container
{
RelativeSizeAxes = Axes.Both,
- Colour = Color4Extensions.FromHex("#333")
- },
- buttons = new FillFlowContainer
+ Children = new Drawable[]
+ {
+ StatisticsPanel = createStatisticsPanel().With(panel =>
+ {
+ panel.RelativeSizeAxes = Axes.Both;
+ panel.Score.BindTarget = SelectedScore;
+ }),
+ ScorePanelList = new ScorePanelList
+ {
+ RelativeSizeAxes = Axes.Both,
+ SelectedScore = { BindTarget = SelectedScore },
+ PostExpandAction = () => StatisticsPanel.ToggleVisibility()
+ },
+ detachedPanelContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both
+ },
+ }
+ }
+ },
+ },
+ new[]
+ {
+ bottomPanel = new Container
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ RelativeSizeAxes = Axes.X,
+ Height = TwoLayerButton.SIZE_EXTENDED.Y,
+ Alpha = 0,
+ Children = new Drawable[]
{
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- AutoSizeAxes = Axes.Both,
- Spacing = new Vector2(5),
- Direction = FillDirection.Horizontal
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4Extensions.FromHex("#333")
+ },
+ buttons = new FillFlowContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ AutoSizeAxes = Axes.Both,
+ Spacing = new Vector2(5),
+ Direction = FillDirection.Horizontal
+ },
}
}
}
+ },
+ RowDimensions = new[]
+ {
+ new Dimension(),
+ new Dimension(GridSizeMode.AutoSize)
}
- },
- RowDimensions = new[]
- {
- new Dimension(),
- new Dimension(GridSizeMode.AutoSize)
}
};
@@ -205,6 +212,11 @@ namespace osu.Game.Screens.Ranking
});
}
+ if (Score?.BeatmapInfo != null)
+ {
+ buttons.Add(new CollectionButton(Score.BeatmapInfo) { Width = 75 });
+ }
+
// Do not render if user is not logged in or the mapset does not have a valid online ID.
if (api.IsLoggedIn && Score?.BeatmapInfo?.BeatmapSet != null && Score.BeatmapInfo.BeatmapSet.OnlineID > 0)
{
From c16b7c5c707f62be237d280fd477101532dbf8a8 Mon Sep 17 00:00:00 2001
From: Layendan
Date: Sun, 21 Jul 2024 10:01:06 -0700
Subject: [PATCH 0204/1274] Update favorite button
---
osu.Game/Screens/Ranking/FavouriteButton.cs | 62 ++++++++++++++-------
osu.Game/Screens/Ranking/ResultsScreen.cs | 23 ++------
2 files changed, 47 insertions(+), 38 deletions(-)
diff --git a/osu.Game/Screens/Ranking/FavouriteButton.cs b/osu.Game/Screens/Ranking/FavouriteButton.cs
index 6014929242..5a8cd51c65 100644
--- a/osu.Game/Screens/Ranking/FavouriteButton.cs
+++ b/osu.Game/Screens/Ranking/FavouriteButton.cs
@@ -9,6 +9,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Logging;
+using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables.Cards;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
@@ -24,15 +25,10 @@ namespace osu.Game.Screens.Ranking
{
private readonly Box background;
private readonly SpriteIcon icon;
- private readonly BindableWithCurrent current;
- public Bindable Current
- {
- get => current.Current;
- set => current.Current = value;
- }
-
- private readonly APIBeatmapSet beatmapSet;
+ private readonly BeatmapSetInfo beatmapSetInfo;
+ private APIBeatmapSet beatmapSet;
+ private Bindable