From cff9dab650b8bc0e453be6b312a96b778fdcbc93 Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Tue, 14 Dec 2021 03:11:04 +0000
Subject: [PATCH 001/239] Remove combo scaling and change miss penalty
---
.../Difficulty/OsuPerformanceCalculator.cs | 17 +++--------------
1 file changed, 3 insertions(+), 14 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 8d45c7a8cc..6de6572b93 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -101,13 +101,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
aimValue *= lengthBonus;
- // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
if (effectiveMissCount > 0)
- aimValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), effectiveMissCount);
-
- // Combo scaling.
- if (Attributes.MaxCombo > 0)
- aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
+ aimValue *= Math.Pow(0.97, effectiveMissCount);
double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
@@ -151,13 +146,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
speedValue *= lengthBonus;
- // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
if (effectiveMissCount > 0)
- speedValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
-
- // Combo scaling.
- if (Attributes.MaxCombo > 0)
- speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
+ speedValue *= Math.Pow(0.97, effectiveMissCount);
double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
@@ -238,9 +228,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (mods.Any(h => h is OsuModHidden))
flashlightValue *= 1.3;
- // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
if (effectiveMissCount > 0)
- flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
+ flashlightValue *= Math.Pow(0.97, effectiveMissCount);
// Combo scaling.
if (Attributes.MaxCombo > 0)
From 86ad42a7440ec0a1478e2030209c2675985e3220 Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Tue, 14 Dec 2021 17:47:41 +0000
Subject: [PATCH 002/239] Nerf length bonus
---
osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 6de6572b93..6ade8c1732 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double aimValue = Math.Pow(5.0 * Math.Max(1.0, rawAim / 0.0675) - 4.0, 3.0) / 100000.0;
// Longer maps are worth more.
- double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
+ double lengthBonus = 0.95 + 0.25 * Math.Min(1.0, totalHits / 2000.0) +
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
aimValue *= lengthBonus;
@@ -142,7 +142,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double speedValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.SpeedStrain / 0.0675) - 4.0, 3.0) / 100000.0;
// Longer maps are worth more.
- double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
+ double lengthBonus = 0.95 + 0.25 * Math.Min(1.0, totalHits / 2000.0) +
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
speedValue *= lengthBonus;
From 489aa43b1bab6efeb0ed05e97ec66181dfe3b4f4 Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Tue, 14 Dec 2021 19:57:36 +0000
Subject: [PATCH 003/239] Make miss penalty harsher
---
.../Difficulty/OsuPerformanceCalculator.cs | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 6ade8c1732..0459c64c0e 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -96,13 +96,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double aimValue = Math.Pow(5.0 * Math.Max(1.0, rawAim / 0.0675) - 4.0, 3.0) / 100000.0;
// Longer maps are worth more.
- double lengthBonus = 0.95 + 0.25 * Math.Min(1.0, totalHits / 2000.0) +
+ double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
aimValue *= lengthBonus;
if (effectiveMissCount > 0)
- aimValue *= Math.Pow(0.97, effectiveMissCount);
+ aimValue *= Math.Pow(0.96, effectiveMissCount);
double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
@@ -142,12 +142,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double speedValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.SpeedStrain / 0.0675) - 4.0, 3.0) / 100000.0;
// Longer maps are worth more.
- double lengthBonus = 0.95 + 0.25 * Math.Min(1.0, totalHits / 2000.0) +
+ double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
speedValue *= lengthBonus;
if (effectiveMissCount > 0)
- speedValue *= Math.Pow(0.97, effectiveMissCount);
+ speedValue *= Math.Pow(0.96, effectiveMissCount);
double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
@@ -229,7 +229,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
flashlightValue *= 1.3;
if (effectiveMissCount > 0)
- flashlightValue *= Math.Pow(0.97, effectiveMissCount);
+ flashlightValue *= Math.Pow(0.96, effectiveMissCount);
// Combo scaling.
if (Attributes.MaxCombo > 0)
From bac4cfed50be4e3d1e0264ce0cd9099c3132d4b5 Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Thu, 16 Dec 2021 18:37:57 +0000
Subject: [PATCH 004/239] Use frost's miss count penalty
---
.../Difficulty/OsuPerformanceCalculator.cs | 21 ++++++++++++++++---
1 file changed, 18 insertions(+), 3 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 0459c64c0e..bba53c92fc 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
aimValue *= lengthBonus;
if (effectiveMissCount > 0)
- aimValue *= Math.Pow(0.96, effectiveMissCount);
+ aimValue *= calculateMissPenalty(effectiveMissCount);
double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
@@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
speedValue *= lengthBonus;
if (effectiveMissCount > 0)
- speedValue *= Math.Pow(0.96, effectiveMissCount);
+ speedValue *= calculateMissPenalty(effectiveMissCount);
double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
@@ -229,7 +229,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
flashlightValue *= 1.3;
if (effectiveMissCount > 0)
- flashlightValue *= Math.Pow(0.96, effectiveMissCount);
+ flashlightValue *= calculateMissPenalty(effectiveMissCount);
// Combo scaling.
if (Attributes.MaxCombo > 0)
@@ -265,6 +265,21 @@ namespace osu.Game.Rulesets.Osu.Difficulty
return Math.Max(countMiss, (int)Math.Floor(comboBasedMissCount));
}
+ private double calculateMissPenalty(double missCount)
+ {
+ double leniency = 2.0;
+
+ if (missCount > totalHits - leniency)
+ return 0;
+
+ double missApprox = erfInvApprox((totalHits - leniency - missCount) / totalHits);
+ double fcApprox = erfInvApprox((totalHits - leniency) / totalHits);
+
+ return Math.Pow(missApprox / fcApprox, 1.5);
+ }
+
+ private double logit(double x) => Math.Log(x / (1 - x));
+ private double erfInvApprox(double x) => (Math.Sqrt(Math.PI) / 4) * logit((x + 1) / 2);
private int totalHits => countGreat + countOk + countMeh + countMiss;
private int totalSuccessfulHits => countGreat + countOk + countMeh;
}
From 60e2a8ed4b2f0f38dafe0cc9bc6ab8c55128b189 Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Tue, 21 Dec 2021 17:54:26 +0000
Subject: [PATCH 005/239] Use MathNet for miss penalty calculation, and use old
penalty formula for Flashlight
---
.../Difficulty/OsuPerformanceCalculator.cs | 14 +++++++-------
osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj | 4 ++++
2 files changed, 11 insertions(+), 7 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index bba53c92fc..90caa64512 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -9,6 +9,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
+using MathNet.Numerics;
namespace osu.Game.Rulesets.Osu.Difficulty
{
@@ -228,8 +229,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (mods.Any(h => h is OsuModHidden))
flashlightValue *= 1.3;
+ // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
if (effectiveMissCount > 0)
- flashlightValue *= calculateMissPenalty(effectiveMissCount);
+ flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
// Combo scaling.
if (Attributes.MaxCombo > 0)
@@ -267,19 +269,17 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private double calculateMissPenalty(double missCount)
{
- double leniency = 2.0;
+ double leniency = 4.3;
if (missCount > totalHits - leniency)
return 0;
- double missApprox = erfInvApprox((totalHits - leniency - missCount) / totalHits);
- double fcApprox = erfInvApprox((totalHits - leniency) / totalHits);
+ double missApprox = SpecialFunctions.ErfInv((totalHits - leniency - missCount) / totalHits);
+ double fcApprox = SpecialFunctions.ErfInv((totalHits - leniency) / totalHits);
- return Math.Pow(missApprox / fcApprox, 1.5);
+ return Math.Pow(missApprox / fcApprox, 3.5);
}
- private double logit(double x) => Math.Log(x / (1 - x));
- private double erfInvApprox(double x) => (Math.Sqrt(Math.PI) / 4) * logit((x + 1) / 2);
private int totalHits => countGreat + countOk + countMeh + countMiss;
private int totalSuccessfulHits => countGreat + countOk + countMeh;
}
diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj
index 98f1e69bd1..018bdb93df 100644
--- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj
+++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj
@@ -15,4 +15,8 @@
+
+
+
+
\ No newline at end of file
From 5640918c8c7c8af2e29feec4c7ad6d501619dafc Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Sun, 26 Dec 2021 23:51:49 +0000
Subject: [PATCH 006/239] New miss penalty formula, using relevant difficult
notes in each skill (targets diffspikes)
---
.../Difficulty/OsuDifficultyAttributes.cs | 6 ++++++
.../Difficulty/OsuDifficultyCalculator.cs | 9 +++++++++
.../Difficulty/OsuPerformanceCalculator.cs | 16 ++++------------
.../Difficulty/Skills/OsuStrainSkill.cs | 7 +++++++
4 files changed, 26 insertions(+), 12 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
index 4b2e54da17..6102f4a8b2 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
@@ -24,6 +24,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty
[JsonProperty("slider_factor")]
public double SliderFactor { get; set; }
+ [JsonProperty("aim_difficult_strain_count")]
+ public int AimDifficultStrainCount { get; set; }
+
+ [JsonProperty("speed_difficult_strain_count")]
+ public int SpeedDifficultStrainCount { get; set; }
+
[JsonProperty("approach_rate")]
public double ApproachRate { get; set; }
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index ed42f333c0..ac228b0a32 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -40,6 +40,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1;
+ int aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).RelevantDifficultStrains();
+ int speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).RelevantDifficultStrains();
+
+ // Total number of strains in a map can vary by clockrate, and this needs to be corrected for.
+ aimDifficultyStrainCount = (int)(aimDifficultyStrainCount * clockRate);
+ speedDifficultyStrainCount = (int)(aimDifficultyStrainCount * clockRate);
+
if (mods.Any(h => h is OsuModRelax))
speedRating = 0.0;
@@ -78,6 +85,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
SpeedStrain = speedRating,
FlashlightRating = flashlightRating,
SliderFactor = sliderFactor,
+ AimDifficultStrainCount = aimDifficultyStrainCount,
+ SpeedDifficultStrainCount = speedDifficultyStrainCount,
ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
OverallDifficulty = (80 - hitWindowGreat) / 6,
DrainRate = drainRate,
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 90caa64512..3efb8951b3 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
aimValue *= lengthBonus;
if (effectiveMissCount > 0)
- aimValue *= calculateMissPenalty(effectiveMissCount);
+ aimValue *= calculateMissPenalty(effectiveMissCount, Attributes.AimDifficultStrainCount);
double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
@@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
speedValue *= lengthBonus;
if (effectiveMissCount > 0)
- speedValue *= calculateMissPenalty(effectiveMissCount);
+ speedValue *= calculateMissPenalty(effectiveMissCount, Attributes.SpeedDifficultStrainCount);
double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
@@ -267,17 +267,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
return Math.Max(countMiss, (int)Math.Floor(comboBasedMissCount));
}
- private double calculateMissPenalty(double missCount)
+ private double calculateMissPenalty(double missCount, double strainCount)
{
- double leniency = 4.3;
-
- if (missCount > totalHits - leniency)
- return 0;
-
- double missApprox = SpecialFunctions.ErfInv((totalHits - leniency - missCount) / totalHits);
- double fcApprox = SpecialFunctions.ErfInv((totalHits - leniency) / totalHits);
-
- return Math.Pow(missApprox / fcApprox, 3.5);
+ return 0.95 / ((missCount / (3 * Math.Sqrt(strainCount))) + 1);
}
private int totalHits => countGreat + countOk + countMeh + countMiss;
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
index e47edc37cc..43e39482a7 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
@@ -57,5 +57,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
return difficulty * DifficultyMultiplier;
}
+
+ public int RelevantDifficultStrains()
+ {
+ List strains = GetCurrentStrainPeaks().OrderByDescending(d => d).ToList();
+
+ return strains.Count(s => s > strains[0] * 0.66);
+ }
}
}
From e9589e57a662bc47297fec2429d7bfce1256db68 Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Mon, 27 Dec 2021 02:23:03 +0000
Subject: [PATCH 007/239] Fix logical error
---
osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index ac228b0a32..87ab12248f 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Total number of strains in a map can vary by clockrate, and this needs to be corrected for.
aimDifficultyStrainCount = (int)(aimDifficultyStrainCount * clockRate);
- speedDifficultyStrainCount = (int)(aimDifficultyStrainCount * clockRate);
+ speedDifficultyStrainCount = (int)(speedDifficultyStrainCount * clockRate);
if (mods.Any(h => h is OsuModRelax))
speedRating = 0.0;
From 8ce6e3c573104d55956c9a3c4cd3c8beffd41b09 Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Wed, 29 Dec 2021 18:49:13 +0000
Subject: [PATCH 008/239] Remove mathnet
---
osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 4 ----
osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj | 4 ----
2 files changed, 8 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index d7d294df47..f6e5481feb 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -99,8 +99,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (effectiveMissCount > 0)
aimValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), effectiveMissCount);
- aimValue *= getComboScalingFactor();
-
double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
approachRateFactor = 0.3 * (Attributes.ApproachRate - 10.33);
@@ -146,8 +144,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (effectiveMissCount > 0)
speedValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
- speedValue *= getComboScalingFactor();
-
double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
approachRateFactor = 0.3 * (Attributes.ApproachRate - 10.33);
diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj
index 018bdb93df..98f1e69bd1 100644
--- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj
+++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj
@@ -15,8 +15,4 @@
-
-
-
-
\ No newline at end of file
From 4f257d6987b1144912f561d5a3360593598be834 Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Wed, 29 Dec 2021 18:59:17 +0000
Subject: [PATCH 009/239] Clean up unsuccessful merge
---
.../Difficulty/OsuPerformanceCalculator.cs | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index f6e5481feb..793f9e790f 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -95,9 +95,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
aimValue *= lengthBonus;
- // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
if (effectiveMissCount > 0)
- aimValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), effectiveMissCount);
+ aimValue *= calculateMissPenalty(effectiveMissCount, Attributes.AimDifficultStrainCount);
double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
@@ -140,9 +139,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
speedValue *= lengthBonus;
- // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
if (effectiveMissCount > 0)
- speedValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
+ speedValue *= calculateMissPenalty(effectiveMissCount, Attributes.SpeedDifficultStrainCount);
double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
@@ -259,6 +257,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty
}
private double getComboScalingFactor() => Attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
+
+ private double calculateMissPenalty(double missCount, double strainCount)
+ {
+ return 0.95 / ((missCount / (3 * Math.Sqrt(strainCount))) + 1);
+ }
+
private int totalHits => countGreat + countOk + countMeh + countMiss;
private int totalSuccessfulHits => countGreat + countOk + countMeh;
}
From fd1028f3bb5c0f19ac2154974fafa528ea6e52e9 Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Wed, 29 Dec 2021 23:49:07 +0000
Subject: [PATCH 010/239] Use clockrate in the difficult strain count method
---
.../Difficulty/OsuDifficultyCalculator.cs | 8 ++------
.../Difficulty/Skills/OsuStrainSkill.cs | 9 +++++++--
2 files changed, 9 insertions(+), 8 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index 44ba6c6a58..27d97e9d75 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -40,12 +40,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1;
- int aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).RelevantDifficultStrains();
- int speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).RelevantDifficultStrains();
-
- // Total number of strains in a map can vary by clockrate, and this needs to be corrected for.
- aimDifficultyStrainCount = (int)(aimDifficultyStrainCount * clockRate);
- speedDifficultyStrainCount = (int)(speedDifficultyStrainCount * clockRate);
+ int aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountDifficultStrains(clockRate);
+ int speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountDifficultStrains(clockRate);
if (mods.Any(h => h is OsuModRelax))
speedRating = 0.0;
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
index 43e39482a7..fd751727a9 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
@@ -58,11 +58,16 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
return difficulty * DifficultyMultiplier;
}
- public int RelevantDifficultStrains()
+ ///
+ /// Returns the number of difficult strains.
+ /// A strain is considered difficult if it's higher than 66% of the highest strain.
+ ///
+ public int CountDifficultStrains(double clockRate)
{
List strains = GetCurrentStrainPeaks().OrderByDescending(d => d).ToList();
- return strains.Count(s => s > strains[0] * 0.66);
+ // Total number of strains in a map can vary by clockrate, and this needs to be corrected for.
+ return (int)(strains.Count(s => s > strains[0] * 0.66) * clockRate);
}
}
}
From d2b815b745b1cfaef241488c436b26fede000571 Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Sun, 2 Jan 2022 19:20:20 +0000
Subject: [PATCH 011/239] Add miss penalty comment
---
.../Difficulty/OsuPerformanceCalculator.cs | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 793f9e790f..d8fe85d645 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -256,13 +256,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
return Math.Max(countMiss, (int)Math.Floor(comboBasedMissCount));
}
+ // Miss penalty assumes that a player will miss on the relatively hard parts of a map, not the easy parts, hence the strain count.
+ private double calculateMissPenalty(double missCount, double strainCount) => 0.95 / ((missCount / (3 * Math.Sqrt(strainCount))) + 1);
+
private double getComboScalingFactor() => Attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
-
- private double calculateMissPenalty(double missCount, double strainCount)
- {
- return 0.95 / ((missCount / (3 * Math.Sqrt(strainCount))) + 1);
- }
-
private int totalHits => countGreat + countOk + countMeh + countMiss;
private int totalSuccessfulHits => countGreat + countOk + countMeh;
}
From 75be4e83d6a44adc7016c150b5cbd1c74d1caf34 Mon Sep 17 00:00:00 2001
From: Luminiscental
Date: Mon, 3 Jan 2022 22:22:32 +0000
Subject: [PATCH 012/239] Remove abusable 0.66 threshold by averaging
---
.../Difficulty/Skills/OsuStrainSkill.cs | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
index fd751727a9..1513befad5 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
@@ -59,15 +59,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
}
///
- /// Returns the number of difficult strains.
- /// A strain is considered difficult if it's higher than 66% of the highest strain.
+ /// Returns the number of strains above a threshold averaged as the threshold varies.
+ /// The result is scaled by clock rate as it affects the total number of strains.
///
public int CountDifficultStrains(double clockRate)
{
- List strains = GetCurrentStrainPeaks().OrderByDescending(d => d).ToList();
-
- // Total number of strains in a map can vary by clockrate, and this needs to be corrected for.
- return (int)(strains.Count(s => s > strains[0] * 0.66) * clockRate);
+ List strains = GetCurrentStrainPeaks().ToList();
+ // This is the average value of strains.Count(s => s > p * strains.Max()) for p between 0 and 1.
+ double realtimeCount = strains.Sum() / strains.Max();
+ return (int)(clockRate * realtimeCount);
}
}
}
From 132079004ca5e6df33a2bc646a52f3036edff85e Mon Sep 17 00:00:00 2001
From: Luminiscental
Date: Tue, 4 Jan 2022 12:30:05 +0000
Subject: [PATCH 013/239] Remove unnecessary truncation
---
osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs | 4 ++--
osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 4 ++--
osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 4 ++--
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
index 51842f65d5..7ab4232a19 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
@@ -25,10 +25,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
public double SliderFactor { get; set; }
[JsonProperty("aim_difficult_strain_count")]
- public int AimDifficultStrainCount { get; set; }
+ public double AimDifficultStrainCount { get; set; }
[JsonProperty("speed_difficult_strain_count")]
- public int SpeedDifficultStrainCount { get; set; }
+ public double SpeedDifficultStrainCount { get; set; }
[JsonProperty("approach_rate")]
public double ApproachRate { get; set; }
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index 27d97e9d75..3c039c9b7e 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -40,8 +40,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1;
- int aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountDifficultStrains(clockRate);
- int speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountDifficultStrains(clockRate);
+ double aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountDifficultStrains(clockRate);
+ double speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountDifficultStrains(clockRate);
if (mods.Any(h => h is OsuModRelax))
speedRating = 0.0;
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
index 1513befad5..46dc9c683b 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
@@ -62,12 +62,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
/// Returns the number of strains above a threshold averaged as the threshold varies.
/// The result is scaled by clock rate as it affects the total number of strains.
///
- public int CountDifficultStrains(double clockRate)
+ public double CountDifficultStrains(double clockRate)
{
List strains = GetCurrentStrainPeaks().ToList();
// This is the average value of strains.Count(s => s > p * strains.Max()) for p between 0 and 1.
double realtimeCount = strains.Sum() / strains.Max();
- return (int)(clockRate * realtimeCount);
+ return clockRate * realtimeCount;
}
}
}
From 443640a48c8bed53510947240e8337c9350d8d6d Mon Sep 17 00:00:00 2001
From: apollo <83023433+apollo-dw@users.noreply.github.com>
Date: Tue, 4 Jan 2022 16:39:30 +0000
Subject: [PATCH 014/239] Revert "Remove abusable 0.66 threshold by averaging"
---
.../Difficulty/OsuDifficultyAttributes.cs | 4 ++--
.../Difficulty/OsuDifficultyCalculator.cs | 4 ++--
.../Difficulty/Skills/OsuStrainSkill.cs | 14 +++++++-------
3 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
index 7ab4232a19..51842f65d5 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
@@ -25,10 +25,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
public double SliderFactor { get; set; }
[JsonProperty("aim_difficult_strain_count")]
- public double AimDifficultStrainCount { get; set; }
+ public int AimDifficultStrainCount { get; set; }
[JsonProperty("speed_difficult_strain_count")]
- public double SpeedDifficultStrainCount { get; set; }
+ public int SpeedDifficultStrainCount { get; set; }
[JsonProperty("approach_rate")]
public double ApproachRate { get; set; }
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index 3c039c9b7e..27d97e9d75 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -40,8 +40,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1;
- double aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountDifficultStrains(clockRate);
- double speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountDifficultStrains(clockRate);
+ int aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountDifficultStrains(clockRate);
+ int speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountDifficultStrains(clockRate);
if (mods.Any(h => h is OsuModRelax))
speedRating = 0.0;
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
index 46dc9c683b..fd751727a9 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
@@ -59,15 +59,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
}
///
- /// Returns the number of strains above a threshold averaged as the threshold varies.
- /// The result is scaled by clock rate as it affects the total number of strains.
+ /// Returns the number of difficult strains.
+ /// A strain is considered difficult if it's higher than 66% of the highest strain.
///
- public double CountDifficultStrains(double clockRate)
+ public int CountDifficultStrains(double clockRate)
{
- List strains = GetCurrentStrainPeaks().ToList();
- // This is the average value of strains.Count(s => s > p * strains.Max()) for p between 0 and 1.
- double realtimeCount = strains.Sum() / strains.Max();
- return clockRate * realtimeCount;
+ List strains = GetCurrentStrainPeaks().OrderByDescending(d => d).ToList();
+
+ // Total number of strains in a map can vary by clockrate, and this needs to be corrected for.
+ return (int)(strains.Count(s => s > strains[0] * 0.66) * clockRate);
}
}
}
From dcb969316dd5a7e2bd1b06ebd1450a0ab56ef831 Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Tue, 4 Jan 2022 17:33:23 +0000
Subject: [PATCH 015/239] Weight difficult strain count against the top strain
---
osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
index 46dc9c683b..3e0fccb6c4 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
@@ -59,14 +59,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
}
///
- /// Returns the number of strains above a threshold averaged as the threshold varies.
+ /// Returns the number of strains weighted against the top strain.
/// The result is scaled by clock rate as it affects the total number of strains.
///
public double CountDifficultStrains(double clockRate)
{
List strains = GetCurrentStrainPeaks().ToList();
- // This is the average value of strains.Count(s => s > p * strains.Max()) for p between 0 and 1.
- double realtimeCount = strains.Sum() / strains.Max();
+ double topStrain = strains.Max();
+
+ double realtimeCount = strains.Sum(s => Math.Pow(s / topStrain, 4));
return clockRate * realtimeCount;
}
}
From 400abc147b1f3742061125d91e80a62f5e1333f7 Mon Sep 17 00:00:00 2001
From: Dan Balasescu
Date: Thu, 6 Jan 2022 16:28:04 +0900
Subject: [PATCH 016/239] Add attribute ids to mapping functions
---
osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs | 4 ++++
osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs | 2 ++
2 files changed, 6 insertions(+)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
index 7ab4232a19..7041acc16c 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
@@ -60,6 +60,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
yield return (ATTRIB_ID_FLASHLIGHT, FlashlightDifficulty);
yield return (ATTRIB_ID_SLIDER_FACTOR, SliderFactor);
+ yield return (ATTRIB_ID_AIM_DIFFICULT_STRAIN_COUNT, AimDifficultStrainCount);
+ yield return (ATTRIB_ID_SPEED_DIFFICULT_STRAIN_COUNT, SpeedDifficultStrainCount);
}
public override void FromDatabaseAttributes(IReadOnlyDictionary values)
@@ -74,6 +76,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
StarRating = values[ATTRIB_ID_DIFFICULTY];
FlashlightDifficulty = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT);
SliderFactor = values[ATTRIB_ID_SLIDER_FACTOR];
+ AimDifficultStrainCount = values[ATTRIB_ID_AIM_DIFFICULT_STRAIN_COUNT];
+ SpeedDifficultStrainCount = values[ATTRIB_ID_SPEED_DIFFICULT_STRAIN_COUNT];
}
#region Newtonsoft.Json implicit ShouldSerialize() methods
diff --git a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs
index 991b567f57..803a5bdac7 100644
--- a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs
+++ b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs
@@ -24,6 +24,8 @@ namespace osu.Game.Rulesets.Difficulty
protected const int ATTRIB_ID_SCORE_MULTIPLIER = 15;
protected const int ATTRIB_ID_FLASHLIGHT = 17;
protected const int ATTRIB_ID_SLIDER_FACTOR = 19;
+ protected const int ATTRIB_ID_AIM_DIFFICULT_STRAIN_COUNT = 21;
+ protected const int ATTRIB_ID_SPEED_DIFFICULT_STRAIN_COUNT = 23;
///
/// The mods which were applied to the beatmap.
From 598946737f07c0f2a3ac7523666a868722906db1 Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Wed, 12 Jan 2022 14:38:53 +0000
Subject: [PATCH 017/239] Reword comment and rename argument
---
.../Difficulty/OsuPerformanceCalculator.cs | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index d8fe85d645..52658dfe38 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -256,9 +256,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
return Math.Max(countMiss, (int)Math.Floor(comboBasedMissCount));
}
- // Miss penalty assumes that a player will miss on the relatively hard parts of a map, not the easy parts, hence the strain count.
- private double calculateMissPenalty(double missCount, double strainCount) => 0.95 / ((missCount / (3 * Math.Sqrt(strainCount))) + 1);
-
+ // Miss penalty assumes that a player will miss on the hardest parts of a map,
+ // so we use the amount of relatively difficult sections to adjust miss penalty
+ // to make it more punishing on maps with lower amount of hard sections.
+ private double calculateMissPenalty(double missCount, double difficultStrainCount) => 0.95 / ((missCount / (3 * Math.Sqrt(difficultStrainCount))) + 1);
private double getComboScalingFactor() => Attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
private int totalHits => countGreat + countOk + countMeh + countMiss;
private int totalSuccessfulHits => countGreat + countOk + countMeh;
From da31ca17e7c12a2922ab83657df4d8d9b6826bbd Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Mon, 14 Feb 2022 01:53:03 +0000
Subject: [PATCH 018/239] Use note strains instead of sectional strains
---
osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 2 ++
osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 7 ++++---
osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 6 +++++-
3 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
index a6301aed6d..3486db04af 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
@@ -159,6 +159,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
currentStrain *= strainDecay(current.DeltaTime);
currentStrain += strainValueOf(current) * skillMultiplier;
+ objectStrains.Add(currentStrain);
+
return currentStrain;
}
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
index 3e0fccb6c4..94e2c9d774 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
@@ -28,6 +28,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
///
protected virtual double DifficultyMultiplier => 1.06;
+ protected List objectStrains = new List();
+
protected OsuStrainSkill(Mod[] mods)
: base(mods)
{
@@ -64,10 +66,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
///
public double CountDifficultStrains(double clockRate)
{
- List strains = GetCurrentStrainPeaks().ToList();
- double topStrain = strains.Max();
+ double topStrain = objectStrains.Max();
- double realtimeCount = strains.Sum(s => Math.Pow(s / topStrain, 4));
+ double realtimeCount = objectStrains.Sum(s => Math.Pow(s / topStrain, 4));
return clockRate * realtimeCount;
}
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
index 06d1ef7346..108edc6f2d 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
@@ -173,7 +173,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
currentRhythm = calculateRhythmBonus(current);
- return currentStrain * currentRhythm;
+ double totalStrain = currentStrain * currentRhythm;
+
+ objectStrains.Add(totalStrain);
+
+ return totalStrain;
}
}
}
From 94a46ab640b36c3ca58f03eaf2644ab51c3abd51 Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Mon, 14 Feb 2022 02:02:46 +0000
Subject: [PATCH 019/239] Rescale miss penalty for note strains
---
osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index cf2116cc5d..cf1af18d39 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -259,7 +259,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Miss penalty assumes that a player will miss on the hardest parts of a map,
// so we use the amount of relatively difficult sections to adjust miss penalty
// to make it more punishing on maps with lower amount of hard sections.
- private double calculateMissPenalty(double missCount, double difficultStrainCount) => 0.95 / ((missCount / (3 * Math.Sqrt(difficultStrainCount))) + 1);
+ private double calculateMissPenalty(double missCount, double difficultStrainCount) => 0.94 / ((missCount / (2 * Math.Sqrt(difficultStrainCount))) + 1);
private double getComboScalingFactor() => Attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
private int totalHits => countGreat + countOk + countMeh + countMiss;
private int totalSuccessfulHits => countGreat + countOk + countMeh;
From c18df86720244a7d5bba22bc502afd4f959c9082 Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Sat, 19 Feb 2022 15:33:28 +0000
Subject: [PATCH 020/239] Remove clockrate factor
---
osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 4 ++--
osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 5 ++---
2 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index 3c039c9b7e..788b515d7f 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -40,8 +40,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1;
- double aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountDifficultStrains(clockRate);
- double speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountDifficultStrains(clockRate);
+ double aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountDifficultStrains();
+ double speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountDifficultStrains();
if (mods.Any(h => h is OsuModRelax))
speedRating = 0.0;
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
index 94e2c9d774..1124c4466f 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
@@ -64,12 +64,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
/// Returns the number of strains weighted against the top strain.
/// The result is scaled by clock rate as it affects the total number of strains.
///
- public double CountDifficultStrains(double clockRate)
+ public double CountDifficultStrains()
{
double topStrain = objectStrains.Max();
- double realtimeCount = objectStrains.Sum(s => Math.Pow(s / topStrain, 4));
- return clockRate * realtimeCount;
+ return objectStrains.Sum(s => Math.Pow(s / topStrain, 4));
}
}
}
From 2f335a76dca77b0304f5ff21ed7ca2c8e1130757 Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Thu, 17 Mar 2022 22:08:56 +0000
Subject: [PATCH 021/239] Switch to using osuAttributes
---
osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 48b4f53a3e..964bd73e81 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
aimValue *= lengthBonus;
if (effectiveMissCount > 0)
- aimValue *= calculateMissPenalty(effectiveMissCount, Attributes.AimDifficultStrainCount);
+ aimValue *= calculateMissPenalty(effectiveMissCount, attributes.AimDifficultStrainCount);
double approachRateFactor = 0.0;
if (attributes.ApproachRate > 10.33)
@@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
speedValue *= lengthBonus;
if (effectiveMissCount > 0)
- speedValue *= calculateMissPenalty(effectiveMissCount, Attributes.SpeedDifficultStrainCount);
+ speedValue *= calculateMissPenalty(effectiveMissCount, attributes.SpeedDifficultStrainCount);
double approachRateFactor = 0.0;
if (attributes.ApproachRate > 10.33)
From 442e68ac1a496529869e7cbb75ad40bc4881aef4 Mon Sep 17 00:00:00 2001
From: Natelytle
Date: Tue, 25 Oct 2022 17:41:20 -0400
Subject: [PATCH 022/239] Implement taiko deviation estimation
---
.../Difficulty/TaikoPerformanceAttributes.cs | 3 +++
.../Difficulty/TaikoPerformanceCalculator.cs | 27 +++++++++++++++----
.../osu.Game.Rulesets.Taiko.csproj | 4 +++
3 files changed, 29 insertions(+), 5 deletions(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs
index b61c13a2df..fa8c3fd27a 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs
@@ -19,6 +19,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
[JsonProperty("effective_miss_count")]
public double EffectiveMissCount { get; set; }
+
+ [JsonProperty("estimated_ur")]
+ public double EstimatedUR { get; set; }
public override IEnumerable GetAttributesForDisplay()
{
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index dc7bad2f75..7d5cf3accd 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -11,6 +11,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Scoring;
+using MathNet.Numerics;
namespace osu.Game.Rulesets.Taiko.Difficulty
{
@@ -21,6 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
private int countMeh;
private int countMiss;
private double accuracy;
+ private double estimatedUR;
private double effectiveMissCount;
@@ -37,7 +39,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
countOk = score.Statistics.GetValueOrDefault(HitResult.Ok);
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
- accuracy = customAccuracy;
+ estimatedUR = 10 * computeEstimatedUR(score, taikoAttributes);
// The effectiveMissCount is calculated by gaining a ratio for totalSuccessfulHits and increasing the miss penalty for shorter object counts lower than 1000.
if (totalSuccessfulHits > 0)
@@ -64,6 +66,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
Difficulty = difficultyValue,
Accuracy = accuracyValue,
EffectiveMissCount = effectiveMissCount,
+ EstimatedUR = 10 * estimatedUR,
Total = totalValue
};
}
@@ -97,10 +100,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (attributes.GreatHitWindow <= 0)
return 0;
- double accuracyValue = Math.Pow(60.0 / attributes.GreatHitWindow, 1.1) * Math.Pow(accuracy, 8.0) * Math.Pow(attributes.StarRating, 0.4) * 27.0;
+ double accuracyValue = Math.Pow(75 / estimatedUR, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 70.0;
double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
- accuracyValue *= lengthBonus;
// Slight HDFL Bonus for accuracy. A clamp is used to prevent against negative values
if (score.Mods.Any(m => m is ModFlashlight) && score.Mods.Any(m => m is ModHidden))
@@ -109,10 +111,25 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
return accuracyValue;
}
+ private double computeEstimatedUR(ScoreInfo score, TaikoDifficultyAttributes attributes)
+ {
+ if (totalHits == 0)
+ return double.PositiveInfinity;
+
+ double greatProbability = 1 - (countOk + countMiss + 1.0) / (totalHits + 1.0);
+
+ if (greatProbability <= 0)
+ {
+ return double.PositiveInfinity;
+ }
+
+ double deviation = attributes.GreatHitWindow / (Math.Sqrt(2) * SpecialFunctions.ErfInv(greatProbability));
+
+ return deviation;
+ }
+
private int totalHits => countGreat + countOk + countMeh + countMiss;
private int totalSuccessfulHits => countGreat + countOk + countMeh;
-
- private double customAccuracy => totalHits > 0 ? (countGreat * 300 + countOk * 150) / (totalHits * 300.0) : 0;
}
}
diff --git a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj
index b752c13d18..b2953106d8 100644
--- a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj
+++ b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj
@@ -15,4 +15,8 @@
+
+
+
+
From d5b06ae9454885e3eeec8de652a8cd51c86d8b7a Mon Sep 17 00:00:00 2001
From: Natelytle
Date: Tue, 25 Oct 2022 17:52:34 -0400
Subject: [PATCH 023/239] Fix difficultyvalue acc scaling
---
.../Difficulty/TaikoPerformanceCalculator.cs | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index 7d5cf3accd..ee2105d859 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -21,7 +21,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
private int countOk;
private int countMeh;
private int countMiss;
- private double accuracy;
private double estimatedUR;
private double effectiveMissCount;
@@ -92,7 +91,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (score.Mods.Any(m => m is ModFlashlight))
difficultyValue *= 1.050 * lengthBonus;
- return difficultyValue * Math.Pow(accuracy, 2.0);
+ return difficultyValue * Math.Pow(100 * SpecialFunctions.Erf(400 / (Math.Sqrt(2) * estimatedUR)), 2.0);
}
private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes)
From 607a006c4f5b11be1f4e8c223c8cfea2b96a9c19 Mon Sep 17 00:00:00 2001
From: Natelytle
Date: Tue, 25 Oct 2022 17:55:16 -0400
Subject: [PATCH 024/239] oops
---
.../Difficulty/TaikoPerformanceCalculator.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index ee2105d859..ba96e0b95e 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (score.Mods.Any(m => m is ModFlashlight))
difficultyValue *= 1.050 * lengthBonus;
- return difficultyValue * Math.Pow(100 * SpecialFunctions.Erf(400 / (Math.Sqrt(2) * estimatedUR)), 2.0);
+ return difficultyValue * Math.Pow(SpecialFunctions.Erf(400 / (Math.Sqrt(2) * estimatedUR)), 2.0);
}
private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes)
From 87cba2d828455b88c26e6eb813667c916ec07c60 Mon Sep 17 00:00:00 2001
From: Natelytle
Date: Tue, 25 Oct 2022 19:15:58 -0400
Subject: [PATCH 025/239] Slight adjustments
---
.../Difficulty/TaikoPerformanceCalculator.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index ba96e0b95e..ec5c150ea4 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
Difficulty = difficultyValue,
Accuracy = accuracyValue,
EffectiveMissCount = effectiveMissCount,
- EstimatedUR = 10 * estimatedUR,
+ EstimatedUR = estimatedUR,
Total = totalValue
};
}
@@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (attributes.GreatHitWindow <= 0)
return 0;
- double accuracyValue = Math.Pow(75 / estimatedUR, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 70.0;
+ double accuracyValue = Math.Pow(75 / estimatedUR, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 100.0;
double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
From 7d3338a0eacb1d4ecbcdb82ee98f8f34d2ac4a3d Mon Sep 17 00:00:00 2001
From: Natelytle
Date: Wed, 26 Oct 2022 15:58:20 -0400
Subject: [PATCH 026/239] LTCA Balancing pass
---
.../Difficulty/TaikoPerformanceCalculator.cs | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index ec5c150ea4..2a7cb6c6b5 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
private int countOk;
private int countMeh;
private int countMiss;
- private double estimatedUR;
+ private double estimatedDeviation;
private double effectiveMissCount;
@@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
countOk = score.Statistics.GetValueOrDefault(HitResult.Ok);
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
- estimatedUR = 10 * computeEstimatedUR(score, taikoAttributes);
+ estimatedDeviation = computeEstimatedDeviation(score, taikoAttributes);
// The effectiveMissCount is calculated by gaining a ratio for totalSuccessfulHits and increasing the miss penalty for shorter object counts lower than 1000.
if (totalSuccessfulHits > 0)
@@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
Difficulty = difficultyValue,
Accuracy = accuracyValue,
EffectiveMissCount = effectiveMissCount,
- EstimatedUR = estimatedUR,
+ EstimatedUR = estimatedDeviation * 10,
Total = totalValue
};
}
@@ -86,12 +86,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
difficultyValue *= 1.025;
if (score.Mods.Any(m => m is ModHardRock))
- difficultyValue *= 1.050;
+ difficultyValue *= 1.10;
if (score.Mods.Any(m => m is ModFlashlight))
difficultyValue *= 1.050 * lengthBonus;
- return difficultyValue * Math.Pow(SpecialFunctions.Erf(400 / (Math.Sqrt(2) * estimatedUR)), 2.0);
+ return difficultyValue * Math.Pow(SpecialFunctions.Erf(400 / (Math.Sqrt(2) * estimatedDeviation)), 2.0);
}
private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes)
@@ -99,18 +99,18 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (attributes.GreatHitWindow <= 0)
return 0;
- double accuracyValue = Math.Pow(75 / estimatedUR, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 100.0;
+ double accuracyValue = Math.Pow(7.5 / estimatedDeviation, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 100.0;
double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
// Slight HDFL Bonus for accuracy. A clamp is used to prevent against negative values
if (score.Mods.Any(m => m is ModFlashlight) && score.Mods.Any(m => m is ModHidden))
- accuracyValue *= Math.Max(1.050, 1.075 * lengthBonus);
+ accuracyValue *= Math.Max(1.0, 1.05 * lengthBonus);
return accuracyValue;
}
- private double computeEstimatedUR(ScoreInfo score, TaikoDifficultyAttributes attributes)
+ private double computeEstimatedDeviation(ScoreInfo score, TaikoDifficultyAttributes attributes)
{
if (totalHits == 0)
return double.PositiveInfinity;
From af919a6550f58da73784931883e59e4b042f910b Mon Sep 17 00:00:00 2001
From: Natelytle
Date: Wed, 26 Oct 2022 16:10:36 -0400
Subject: [PATCH 027/239] harshen deviation scaling
---
.../Difficulty/TaikoPerformanceCalculator.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index 2a7cb6c6b5..565d166c32 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (score.Mods.Any(m => m is ModFlashlight))
difficultyValue *= 1.050 * lengthBonus;
- return difficultyValue * Math.Pow(SpecialFunctions.Erf(400 / (Math.Sqrt(2) * estimatedDeviation)), 2.0);
+ return difficultyValue * Math.Pow(SpecialFunctions.Erf(40 / (Math.Sqrt(2) * estimatedDeviation)), 2.0);
}
private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes)
From 2940d18d3393c1e006c715ec47d5d2fa88b1bf61 Mon Sep 17 00:00:00 2001
From: Natelytle
Date: Thu, 27 Oct 2022 00:07:32 -0400
Subject: [PATCH 028/239] Fix formatting
---
.../Difficulty/TaikoPerformanceAttributes.cs | 2 +-
.../Difficulty/TaikoPerformanceCalculator.cs | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs
index fa8c3fd27a..6893f24d20 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
[JsonProperty("effective_miss_count")]
public double EffectiveMissCount { get; set; }
-
+
[JsonProperty("estimated_ur")]
public double EstimatedUR { get; set; }
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index 565d166c32..1e57d77d91 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
{
if (totalHits == 0)
return double.PositiveInfinity;
-
+
double greatProbability = 1 - (countOk + countMiss + 1.0) / (totalHits + 1.0);
if (greatProbability <= 0)
From 883790c7a78a7a69b08a3d5299557d2e245c16f5 Mon Sep 17 00:00:00 2001
From: Natelytle
Date: Fri, 28 Oct 2022 16:18:17 -0400
Subject: [PATCH 029/239] Return null instead of infinity
---
.../Difficulty/TaikoPerformanceAttributes.cs | 2 +-
.../Difficulty/TaikoPerformanceCalculator.cs | 15 +++++++++------
2 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs
index 6893f24d20..3786009a1f 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
public double EffectiveMissCount { get; set; }
[JsonProperty("estimated_ur")]
- public double EstimatedUR { get; set; }
+ public double? EstimatedUR { get; set; }
public override IEnumerable GetAttributesForDisplay()
{
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index 1e57d77d91..c27efb518d 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
private int countOk;
private int countMeh;
private int countMiss;
- private double estimatedDeviation;
+ private double? estimatedDeviation;
private double effectiveMissCount;
@@ -91,15 +91,18 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (score.Mods.Any(m => m is ModFlashlight))
difficultyValue *= 1.050 * lengthBonus;
- return difficultyValue * Math.Pow(SpecialFunctions.Erf(40 / (Math.Sqrt(2) * estimatedDeviation)), 2.0);
+ if (estimatedDeviation == null)
+ return 0;
+
+ return difficultyValue * Math.Pow(SpecialFunctions.Erf(40 / (Math.Sqrt(2) * (double)estimatedDeviation)), 2.0);
}
private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes)
{
- if (attributes.GreatHitWindow <= 0)
+ if (attributes.GreatHitWindow <= 0 || estimatedDeviation == null)
return 0;
- double accuracyValue = Math.Pow(7.5 / estimatedDeviation, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 100.0;
+ double accuracyValue = Math.Pow(7.5 / (double)estimatedDeviation, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 100.0;
double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
@@ -110,10 +113,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
return accuracyValue;
}
- private double computeEstimatedDeviation(ScoreInfo score, TaikoDifficultyAttributes attributes)
+ private double? computeEstimatedDeviation(ScoreInfo score, TaikoDifficultyAttributes attributes)
{
if (totalHits == 0)
- return double.PositiveInfinity;
+ return null;
double greatProbability = 1 - (countOk + countMiss + 1.0) / (totalHits + 1.0);
From 01c79d8ef29abf4871335db06326eeef56d3fed6 Mon Sep 17 00:00:00 2001
From: Natelytle
Date: Fri, 28 Oct 2022 16:20:21 -0400
Subject: [PATCH 030/239] remove other infinity reference
---
.../Difficulty/TaikoPerformanceCalculator.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index c27efb518d..4fba926205 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -122,7 +122,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (greatProbability <= 0)
{
- return double.PositiveInfinity;
+ return null;
}
double deviation = attributes.GreatHitWindow / (Math.Sqrt(2) * SpecialFunctions.ErfInv(greatProbability));
From 7403c1cc862f6ebbd9e6a2bc791857b53a973da6 Mon Sep 17 00:00:00 2001
From: Natelytle
Date: Fri, 28 Oct 2022 23:23:50 -0400
Subject: [PATCH 031/239] Return null for greatprobability >= 1
---
.../Difficulty/TaikoPerformanceCalculator.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index 4fba926205..003e46d77b 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
double greatProbability = 1 - (countOk + countMiss + 1.0) / (totalHits + 1.0);
- if (greatProbability <= 0)
+ if (greatProbability <= 0 || greatProbability >= 1)
{
return null;
}
From 16301f052e8d64164e6f480a594e28868f940614 Mon Sep 17 00:00:00 2001
From: Natelytle
Date: Sun, 30 Oct 2022 15:01:25 -0400
Subject: [PATCH 032/239] Fix low end accuracy, buff high end
---
.../Difficulty/TaikoPerformanceCalculator.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index 003e46d77b..c57efd3c4d 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (attributes.GreatHitWindow <= 0 || estimatedDeviation == null)
return 0;
- double accuracyValue = Math.Pow(7.5 / (double)estimatedDeviation, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 100.0;
+ double accuracyValue = Math.Pow(7.5 / (double)estimatedDeviation, 1.1) * Math.Pow(attributes.StarRating / 2.7, 0.8) * 100.0;
double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
From 37c21cdd7cbe4e00ae564dfce66d7a55ab1aa68e Mon Sep 17 00:00:00 2001
From: Natelytle
Date: Tue, 1 Nov 2022 15:14:55 -0400
Subject: [PATCH 033/239] fix formatting
---
.../Difficulty/TaikoPerformanceAttributes.cs | 2 +-
.../Difficulty/TaikoPerformanceCalculator.cs | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs
index 3786009a1f..74770eaa79 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
public double EffectiveMissCount { get; set; }
[JsonProperty("estimated_ur")]
- public double? EstimatedUR { get; set; }
+ public double? EstimatedUr { get; set; }
public override IEnumerable GetAttributesForDisplay()
{
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index c57efd3c4d..0a24e33419 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
Difficulty = difficultyValue,
Accuracy = accuracyValue,
EffectiveMissCount = effectiveMissCount,
- EstimatedUR = estimatedDeviation * 10,
+ EstimatedUr = estimatedDeviation * 10,
Total = totalValue
};
}
@@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (estimatedDeviation == null)
return 0;
- return difficultyValue * Math.Pow(SpecialFunctions.Erf(40 / (Math.Sqrt(2) * (double)estimatedDeviation)), 2.0);
+ return difficultyValue * Math.Pow(SpecialFunctions.Erf(40 / (Math.Sqrt(2) * estimatedDeviation.Value)), 2.0);
}
private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes)
@@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (attributes.GreatHitWindow <= 0 || estimatedDeviation == null)
return 0;
- double accuracyValue = Math.Pow(7.5 / (double)estimatedDeviation, 1.1) * Math.Pow(attributes.StarRating / 2.7, 0.8) * 100.0;
+ double accuracyValue = Math.Pow(7.5 / estimatedDeviation.Value, 1.1) * Math.Pow(attributes.StarRating / 2.7, 0.8) * 100.0;
double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
@@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
double greatProbability = 1 - (countOk + countMiss + 1.0) / (totalHits + 1.0);
- if (greatProbability <= 0 || greatProbability >= 1)
+ if (greatProbability is <= 0 or >= 1)
{
return null;
}
From 2ba163440aae865d656cf954bed3157d39b60d7f Mon Sep 17 00:00:00 2001
From: Natelytle
Date: Thu, 24 Nov 2022 19:09:30 -0500
Subject: [PATCH 034/239] account for low acc FC deviation
---
.../Difficulty/TaikoPerformanceCalculator.cs | 51 ++++++++++++++++---
1 file changed, 45 insertions(+), 6 deletions(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index 0a24e33419..281dc19837 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -12,6 +12,9 @@ using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Scoring;
using MathNet.Numerics;
+using MathNet.Numerics.RootFinding;
+using osu.Framework.Audio.Track;
+using osu.Framework.Extensions.IEnumerableExtensions;
namespace osu.Game.Rulesets.Taiko.Difficulty
{
@@ -66,7 +69,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
Accuracy = accuracyValue,
EffectiveMissCount = effectiveMissCount,
EstimatedUr = estimatedDeviation * 10,
- Total = totalValue
+ Total = (double)estimatedDeviation * 10
};
}
@@ -118,20 +121,56 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (totalHits == 0)
return null;
- double greatProbability = 1 - (countOk + countMiss + 1.0) / (totalHits + 1.0);
+ // Create a new track to properly calculate the hit windows of 100s and 50s.
+ var track = new TrackVirtual(10000);
+ score.Mods.OfType().ForEach(m => m.ApplyToTrack(track));
+ double clockRate = track.Rate;
+ double overallDifficulty = (50 - attributes.GreatHitWindow) / 3 * clockRate;
+ double goodHitWindow = 0;
+ if (overallDifficulty <= 5)
+ goodHitWindow = (120 - 8 * overallDifficulty) / clockRate;
+ if (overallDifficulty > 5)
+ goodHitWindow = 80 - 6 * (overallDifficulty - 5);
+
+ double root2 = Math.Sqrt(2);
- if (greatProbability is <= 0 or >= 1)
+ double logLikelihoodGradient(double d)
{
- return null;
+ if (d <= 0)
+ return 1;
+
+ double p300 = SpecialFunctions.Erf(attributes.GreatHitWindow / root2 * d);
+ double lnP300 = lnErfcApprox(attributes.GreatHitWindow / root2 * d);
+ double lnP100 = lnErfcApprox(goodHitWindow / root2 * d);
+
+ double t1 = countGreat * ((attributes.GreatHitWindow * Math.Exp(-Math.Pow(attributes.GreatHitWindow / root2 * d, 2))) / p300);
+
+ double t2a = Math.Exp(Math.Log(goodHitWindow * Math.Exp(-Math.Pow(goodHitWindow / root2 * d, 2))) - logDiff(lnP300, lnP100));
+ double t2b = Math.Exp(Math.Log(attributes.GreatHitWindow * Math.Exp(-Math.Pow(attributes.GreatHitWindow / root2 * d, 2))) - logDiff(lnP300, lnP100));
+ double t2 = (countOk + 1) * (t2a - t2b);
+
+ double t3 = countMiss * Math.Exp(Math.Log(goodHitWindow * Math.Exp(-Math.Pow(goodHitWindow / root2 * d, 2))) - lnP100);
+
+ return t1 + t2 - t3;
}
- double deviation = attributes.GreatHitWindow / (Math.Sqrt(2) * SpecialFunctions.ErfInv(greatProbability));
+ double root = Brent.FindRootExpand(logLikelihoodGradient, 0.02, 0.3, expandFactor: 2);
- return deviation;
+ return 1 / root;
}
private int totalHits => countGreat + countOk + countMeh + countMiss;
private int totalSuccessfulHits => countGreat + countOk + countMeh;
+
+ double lnErfcApprox(double x)
+ {
+ if (x <= 5)
+ return Math.Log(SpecialFunctions.Erfc(x));
+
+ return -Math.Pow(x, 2) - Math.Log(x) - Math.Log(Math.Sqrt(Math.PI));
+ }
+
+ double logDiff(double l1, double l2) => l1 + SpecialFunctions.Log1p(-Math.Exp(-(l1 - l2)));
}
}
From 0e4e92b344b6bce55ccf6ceb776cc8bfb450338f Mon Sep 17 00:00:00 2001
From: Natelytle
Date: Thu, 24 Nov 2022 19:28:47 -0500
Subject: [PATCH 035/239] totalvalue
---
.../Difficulty/TaikoPerformanceCalculator.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index 281dc19837..332f6f8267 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
Accuracy = accuracyValue,
EffectiveMissCount = effectiveMissCount,
EstimatedUr = estimatedDeviation * 10,
- Total = (double)estimatedDeviation * 10
+ Total = totalValue
};
}
From b579af674e128b94294ef9d379bc9a89d4dc94df Mon Sep 17 00:00:00 2001
From: Natelytle
Date: Thu, 24 Nov 2022 19:46:55 -0500
Subject: [PATCH 036/239] fix dt
---
.../Difficulty/TaikoPerformanceCalculator.cs | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index 332f6f8267..f184a7cdce 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -125,7 +125,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
var track = new TrackVirtual(10000);
score.Mods.OfType().ForEach(m => m.ApplyToTrack(track));
double clockRate = track.Rate;
- double overallDifficulty = (50 - attributes.GreatHitWindow) / 3 * clockRate;
+
+ double overallDifficulty = (50 - attributes.GreatHitWindow * clockRate) / 3;
double goodHitWindow = 0;
if (overallDifficulty <= 5)
goodHitWindow = (120 - 8 * overallDifficulty) / clockRate;
From e3ef180c4669b3197c7a527daaad76435fa2fa7f Mon Sep 17 00:00:00 2001
From: Natelytle
Date: Fri, 25 Nov 2022 15:18:39 -0500
Subject: [PATCH 037/239] fixes
---
.../Difficulty/TaikoPerformanceCalculator.cs | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index f184a7cdce..d1e305fe9b 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
private double? computeEstimatedDeviation(ScoreInfo score, TaikoDifficultyAttributes attributes)
{
- if (totalHits == 0)
+ if (totalSuccessfulHits == 0)
return null;
// Create a new track to properly calculate the hit windows of 100s and 50s.
@@ -155,7 +155,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
return t1 + t2 - t3;
}
- double root = Brent.FindRootExpand(logLikelihoodGradient, 0.02, 0.3, expandFactor: 2);
+ double root = Brent.FindRootExpand(logLikelihoodGradient, 0.02, 0.3);
return 1 / root;
}
@@ -164,7 +164,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
private int totalSuccessfulHits => countGreat + countOk + countMeh;
- double lnErfcApprox(double x)
+ private double lnErfcApprox(double x)
{
if (x <= 5)
return Math.Log(SpecialFunctions.Erfc(x));
@@ -172,6 +172,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
return -Math.Pow(x, 2) - Math.Log(x) - Math.Log(Math.Sqrt(Math.PI));
}
- double logDiff(double l1, double l2) => l1 + SpecialFunctions.Log1p(-Math.Exp(-(l1 - l2)));
+ private double logDiff(double l1, double l2) => l1 + SpecialFunctions.Log1p(-Math.Exp(-(l1 - l2)));
}
}
From 7b5373ac5a8f70c332840a5b1fe374f96ac51c19 Mon Sep 17 00:00:00 2001
From: Natelytle
Date: Sun, 27 Nov 2022 15:24:54 -0500
Subject: [PATCH 038/239] add comments
---
.../Difficulty/TaikoPerformanceCalculator.cs | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index d1e305fe9b..1d7af4e167 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -116,12 +116,17 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
return accuracyValue;
}
+ ///
+ /// Estimates the player's tap deviation based on the OD, number of objects, and number of 300s, 100s, and misses,
+ /// assuming the player's mean hit error is 0. The estimation is consistent in that two SS scores on the same map with the same settings
+ /// will always return the same deviation. See: https://www.desmos.com/calculator/qlr946netu
+ ///
private double? computeEstimatedDeviation(ScoreInfo score, TaikoDifficultyAttributes attributes)
{
if (totalSuccessfulHits == 0)
return null;
- // Create a new track to properly calculate the hit windows of 100s and 50s.
+ // Create a new track to properly calculate the hit window of 100s.
var track = new TrackVirtual(10000);
score.Mods.OfType().ForEach(m => m.ApplyToTrack(track));
double clockRate = track.Rate;
@@ -135,6 +140,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
double root2 = Math.Sqrt(2);
+ // Log of the most likely deviation resulting in the score's hit judgements, differentiated such that 1 over the most likely deviation returns 0.
double logLikelihoodGradient(double d)
{
if (d <= 0)
From 6a27206abdffa6e18bca1c3f27df6614b9abf869 Mon Sep 17 00:00:00 2001
From: Natelytle
Date: Fri, 2 Dec 2022 17:01:15 -0500
Subject: [PATCH 039/239] bugfix + tests
---
.../Difficulty/TaikoPerformanceCalculator.cs | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index 19b43d0422..ca4e5fe53a 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -135,11 +135,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
double clockRate = track.Rate;
double overallDifficulty = (50 - attributes.GreatHitWindow * clockRate) / 3;
- double goodHitWindow = 0;
+ double goodHitWindow;
if (overallDifficulty <= 5)
goodHitWindow = (120 - 8 * overallDifficulty) / clockRate;
- if (overallDifficulty > 5)
- goodHitWindow = 80 - 6 * (overallDifficulty - 5);
+ else
+ goodHitWindow = (80 - 6 * (overallDifficulty - 5)) / clockRate;
double root2 = Math.Sqrt(2);
@@ -155,9 +155,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
double t1 = countGreat * ((attributes.GreatHitWindow * Math.Exp(-Math.Pow(attributes.GreatHitWindow / root2 * d, 2))) / p300);
- double t2a = Math.Exp(Math.Log(goodHitWindow * Math.Exp(-Math.Pow(goodHitWindow / root2 * d, 2))) - logDiff(lnP300, lnP100));
- double t2b = Math.Exp(Math.Log(attributes.GreatHitWindow * Math.Exp(-Math.Pow(attributes.GreatHitWindow / root2 * d, 2))) - logDiff(lnP300, lnP100));
- double t2 = (countOk + 1) * (t2a - t2b);
+ double t2A = Math.Exp(Math.Log(goodHitWindow * Math.Exp(-Math.Pow(goodHitWindow / root2 * d, 2))) - logDiff(lnP300, lnP100));
+ double t2B = Math.Exp(Math.Log(attributes.GreatHitWindow * Math.Exp(-Math.Pow(attributes.GreatHitWindow / root2 * d, 2))) - logDiff(lnP300, lnP100));
+ double t2 = (countOk + 1) * (t2A - t2B);
double t3 = countMiss * Math.Exp(Math.Log(goodHitWindow * Math.Exp(-Math.Pow(goodHitWindow / root2 * d, 2))) - lnP100);
From 2b74c4ef8cd60e9d3d6f7907962759573a8896d8 Mon Sep 17 00:00:00 2001
From: Natelytle
Date: Sat, 3 Dec 2022 15:39:38 -0500
Subject: [PATCH 040/239] tests return a greathitwindow of 0, add check
---
.../Difficulty/TaikoPerformanceCalculator.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index ca4e5fe53a..fd2dd47d30 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
///
private double? computeEstimatedDeviation(ScoreInfo score, TaikoDifficultyAttributes attributes)
{
- if (totalSuccessfulHits == 0)
+ if (totalSuccessfulHits == 0 || attributes.GreatHitWindow == 0)
return null;
// Create a new track to properly calculate the hit window of 100s.
From 45e8d18b1b9618d8c068731a389285dc270dd2b4 Mon Sep 17 00:00:00 2001
From: Natelytle
Date: Mon, 5 Dec 2022 22:33:13 -0500
Subject: [PATCH 041/239] fix extremely low OD breaking deviation calc
---
.../Difficulty/TaikoPerformanceCalculator.cs | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index fd2dd47d30..807e9359fc 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -153,13 +153,13 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
double lnP300 = lnErfcApprox(attributes.GreatHitWindow / root2 * d);
double lnP100 = lnErfcApprox(goodHitWindow / root2 * d);
- double t1 = countGreat * ((attributes.GreatHitWindow * Math.Exp(-Math.Pow(attributes.GreatHitWindow / root2 * d, 2))) / p300);
+ double t1 = countGreat * (Math.Exp(Math.Log(attributes.GreatHitWindow) + -Math.Pow(attributes.GreatHitWindow / root2 * d, 2)) / p300);
- double t2A = Math.Exp(Math.Log(goodHitWindow * Math.Exp(-Math.Pow(goodHitWindow / root2 * d, 2))) - logDiff(lnP300, lnP100));
- double t2B = Math.Exp(Math.Log(attributes.GreatHitWindow * Math.Exp(-Math.Pow(attributes.GreatHitWindow / root2 * d, 2))) - logDiff(lnP300, lnP100));
+ double t2A = Math.Exp(Math.Log(goodHitWindow) + -Math.Pow(goodHitWindow / root2 * d, 2) - logDiff(lnP300, lnP100));
+ double t2B = Math.Exp(Math.Log(attributes.GreatHitWindow) + -Math.Pow(attributes.GreatHitWindow / root2 * d, 2) - logDiff(lnP300, lnP100));
double t2 = (countOk + 1) * (t2A - t2B);
- double t3 = countMiss * Math.Exp(Math.Log(goodHitWindow * Math.Exp(-Math.Pow(goodHitWindow / root2 * d, 2))) - lnP100);
+ double t3 = countMiss * Math.Exp(Math.Log(goodHitWindow) + -Math.Pow(goodHitWindow / root2 * d, 2) - lnP100);
return t1 + t2 - t3;
}
From 334f60f528b130cb47634e960c8fe47ff01581cc Mon Sep 17 00:00:00 2001
From: Natelytle
Date: Wed, 22 Feb 2023 14:55:48 -0500
Subject: [PATCH 042/239] Reformat everything to be simpler
---
.../Difficulty/TaikoPerformanceCalculator.cs | 78 ++++++++++---------
1 file changed, 40 insertions(+), 38 deletions(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index 807e9359fc..84b71a98d4 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -12,7 +12,6 @@ using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Scoring;
using MathNet.Numerics;
-using MathNet.Numerics.RootFinding;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions.IEnumerableExtensions;
@@ -24,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
private int countOk;
private int countMeh;
private int countMiss;
- private double? estimatedDeviation;
+ private double? estimatedUr;
private double effectiveMissCount;
@@ -41,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
countOk = score.Statistics.GetValueOrDefault(HitResult.Ok);
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
- estimatedDeviation = computeEstimatedDeviation(score, taikoAttributes);
+ estimatedUr = computeEstimatedUr(score, taikoAttributes);
// The effectiveMissCount is calculated by gaining a ratio for totalSuccessfulHits and increasing the miss penalty for shorter object counts lower than 1000.
if (totalSuccessfulHits > 0)
@@ -71,7 +70,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
Difficulty = difficultyValue,
Accuracy = accuracyValue,
EffectiveMissCount = effectiveMissCount,
- EstimatedUr = estimatedDeviation * 10,
+ EstimatedUr = estimatedUr,
Total = totalValue
};
}
@@ -97,18 +96,18 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (score.Mods.Any(m => m is ModFlashlight))
difficultyValue *= 1.050 * lengthBonus;
- if (estimatedDeviation == null)
+ if (estimatedUr == null)
return 0;
- return difficultyValue * Math.Pow(SpecialFunctions.Erf(40 / (Math.Sqrt(2) * estimatedDeviation.Value)), 2.0);
+ return difficultyValue * Math.Pow(SpecialFunctions.Erf(400 / (Math.Sqrt(2) * estimatedUr.Value)), 2.0);
}
private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes, bool isConvert)
{
- if (attributes.GreatHitWindow <= 0 || estimatedDeviation == null)
+ if (attributes.GreatHitWindow <= 0 || estimatedUr == null)
return 0;
- double accuracyValue = Math.Pow(7.5 / estimatedDeviation.Value, 1.1) * Math.Pow(attributes.StarRating / 2.7, 0.8) * 100.0;
+ double accuracyValue = Math.Pow(75 / estimatedUr.Value, 1.1) * Math.Pow(attributes.StarRating / 2.7, 0.8) * 100.0;
double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
@@ -124,7 +123,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
/// assuming the player's mean hit error is 0. The estimation is consistent in that two SS scores on the same map with the same settings
/// will always return the same deviation. See: https://www.desmos.com/calculator/qlr946netu
///
- private double? computeEstimatedDeviation(ScoreInfo score, TaikoDifficultyAttributes attributes)
+ private double? computeEstimatedUr(ScoreInfo score, TaikoDifficultyAttributes attributes)
{
if (totalSuccessfulHits == 0 || attributes.GreatHitWindow == 0)
return null;
@@ -135,52 +134,55 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
double clockRate = track.Rate;
double overallDifficulty = (50 - attributes.GreatHitWindow * clockRate) / 3;
- double goodHitWindow;
- if (overallDifficulty <= 5)
- goodHitWindow = (120 - 8 * overallDifficulty) / clockRate;
- else
- goodHitWindow = (80 - 6 * (overallDifficulty - 5)) / clockRate;
+ double h300 = attributes.GreatHitWindow;
+ double h100 = overallDifficulty <= 5 ? (120 - 8 * overallDifficulty) / clockRate : (80 - 6 * (overallDifficulty - 5)) / clockRate;
- double root2 = Math.Sqrt(2);
-
- // Log of the most likely deviation resulting in the score's hit judgements, differentiated such that 1 over the most likely deviation returns 0.
- double logLikelihoodGradient(double d)
+ // Returns the likelihood of a deviation resulting in the score's hit judgements. The peak of the curve is the most likely deviation.
+ double likelihoodGradient(double d)
{
if (d <= 0)
- return 1;
+ return 0;
- double p300 = SpecialFunctions.Erf(attributes.GreatHitWindow / root2 * d);
- double lnP300 = lnErfcApprox(attributes.GreatHitWindow / root2 * d);
- double lnP100 = lnErfcApprox(goodHitWindow / root2 * d);
+ double p300 = logDiff(0, logPcHit(h300, d));
+ double p100 = logDiff(logPcHit(h300, d), logPcHit(h100, d));
+ double p0 = logPcHit(h100, d);
- double t1 = countGreat * (Math.Exp(Math.Log(attributes.GreatHitWindow) + -Math.Pow(attributes.GreatHitWindow / root2 * d, 2)) / p300);
+ double gradient = Math.Exp(
+ (countGreat * p300
+ + (countOk + 0.5) * p100
+ + countMiss * p0) / totalHits
+ );
- double t2A = Math.Exp(Math.Log(goodHitWindow) + -Math.Pow(goodHitWindow / root2 * d, 2) - logDiff(lnP300, lnP100));
- double t2B = Math.Exp(Math.Log(attributes.GreatHitWindow) + -Math.Pow(attributes.GreatHitWindow / root2 * d, 2) - logDiff(lnP300, lnP100));
- double t2 = (countOk + 1) * (t2A - t2B);
-
- double t3 = countMiss * Math.Exp(Math.Log(goodHitWindow) + -Math.Pow(goodHitWindow / root2 * d, 2) - lnP100);
-
- return t1 + t2 - t3;
+ return -gradient;
}
- double root = Brent.FindRootExpand(logLikelihoodGradient, 0.02, 0.3);
+ double deviation = FindMinimum.OfScalarFunction(likelihoodGradient, 30);
- return 1 / root;
+ return deviation * 10;
}
private int totalHits => countGreat + countOk + countMeh + countMiss;
private int totalSuccessfulHits => countGreat + countOk + countMeh;
- private double lnErfcApprox(double x)
+ private double logPcHit(double x, double deviation) => logErfcApprox(x / (deviation * Math.Sqrt(2)));
+
+ // There is a numerical approximation to increase how far you can calculate Erfc(x).
+ private double logErfcApprox(double x) => x <= 5 ? Math.Log(SpecialFunctions.Erfc(x)) : -Math.Pow(x, 2) - Math.Log(x) - Math.Log(Math.Sqrt(Math.PI));
+
+ // Log rules make subtraction of the non-log value non-trivial, this method simply subtracts the base value of 2 logs.
+ private double logDiff(double firstLog, double secondLog)
{
- if (x <= 5)
- return Math.Log(SpecialFunctions.Erfc(x));
+ double maxVal = Math.Max(firstLog, secondLog);
- return -Math.Pow(x, 2) - Math.Log(x) - Math.Log(Math.Sqrt(Math.PI));
+ // Avoid negative infinity - negative infinity (NaN) by checking if the higher value is negative infinity.
+ // Shouldn't ever happen, but good for redundancy purposes.
+ if (double.IsNegativeInfinity(maxVal))
+ {
+ return maxVal;
+ }
+
+ return firstLog + SpecialFunctions.Log1p(-Math.Exp(-(firstLog - secondLog)));
}
-
- private double logDiff(double l1, double l2) => l1 + SpecialFunctions.Log1p(-Math.Exp(-(l1 - l2)));
}
}
From adf16187b1d0e1ceb2520f5a3eccb7551ce7047a Mon Sep 17 00:00:00 2001
From: Natelytle
Date: Wed, 22 Feb 2023 21:03:02 -0500
Subject: [PATCH 043/239] Change accuracy scaling
---
.../Difficulty/TaikoPerformanceCalculator.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index 84b71a98d4..88c26e9e6a 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (attributes.GreatHitWindow <= 0 || estimatedUr == null)
return 0;
- double accuracyValue = Math.Pow(75 / estimatedUr.Value, 1.1) * Math.Pow(attributes.StarRating / 2.7, 0.8) * 100.0;
+ double accuracyValue = Math.Pow(60 / estimatedUr.Value, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 100.0;
double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
From 858afcd0b3ecdd10869cf540bcd7d79a9e8b1b22 Mon Sep 17 00:00:00 2001
From: Natelytle
Date: Mon, 20 Mar 2023 22:00:33 -0400
Subject: [PATCH 044/239] Pass OK hit window as a separate difficulty
attribute, fix erfc approximation
---
.../Difficulty/TaikoDifficultyAttributes.cs | 9 +++++++++
.../Difficulty/TaikoDifficultyCalculator.cs | 1 +
.../Difficulty/TaikoPerformanceCalculator.cs | 18 ++++++------------
3 files changed, 16 insertions(+), 12 deletions(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs
index 72452e27b3..f5c9280590 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs
@@ -43,6 +43,15 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
[JsonProperty("great_hit_window")]
public double GreatHitWindow { get; set; }
+ ///
+ /// The perceived hit window for an OK hit inclusive of rate-adjusting mods (DT/HT/etc).
+ ///
+ ///
+ /// Rate-adjusting mods don't directly affect the hit window, but have a perceived effect as a result of adjusting audio timing.
+ ///
+ [JsonProperty("ok_hit_window")]
+ public double OkHitWindow { get; set; }
+
public override IEnumerable<(int attributeId, object value)> ToDatabaseAttributes()
{
foreach (var v in base.ToDatabaseAttributes())
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
index 24b5f5939a..90bcb74533 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
@@ -95,6 +95,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
ColourDifficulty = colourRating,
PeakDifficulty = combinedRating,
GreatHitWindow = hitWindows.WindowFor(HitResult.Great) / clockRate,
+ OkHitWindow = hitWindows.WindowFor(HitResult.Ok) / clockRate,
MaxCombo = beatmap.HitObjects.Count(h => h is Hit),
};
}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index 88c26e9e6a..e430616d54 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -12,8 +12,6 @@ using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Scoring;
using MathNet.Numerics;
-using osu.Framework.Audio.Track;
-using osu.Framework.Extensions.IEnumerableExtensions;
namespace osu.Game.Rulesets.Taiko.Difficulty
{
@@ -125,17 +123,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
///
private double? computeEstimatedUr(ScoreInfo score, TaikoDifficultyAttributes attributes)
{
- if (totalSuccessfulHits == 0 || attributes.GreatHitWindow == 0)
+ if (totalSuccessfulHits == 0 || attributes.GreatHitWindow <= 0)
return null;
- // Create a new track to properly calculate the hit window of 100s.
- var track = new TrackVirtual(10000);
- score.Mods.OfType().ForEach(m => m.ApplyToTrack(track));
- double clockRate = track.Rate;
-
- double overallDifficulty = (50 - attributes.GreatHitWindow * clockRate) / 3;
double h300 = attributes.GreatHitWindow;
- double h100 = overallDifficulty <= 5 ? (120 - 8 * overallDifficulty) / clockRate : (80 - 6 * (overallDifficulty - 5)) / clockRate;
+ double h100 = attributes.OkHitWindow;
// Returns the likelihood of a deviation resulting in the score's hit judgements. The peak of the curve is the most likely deviation.
double likelihoodGradient(double d)
@@ -167,8 +159,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
private double logPcHit(double x, double deviation) => logErfcApprox(x / (deviation * Math.Sqrt(2)));
- // There is a numerical approximation to increase how far you can calculate Erfc(x).
- private double logErfcApprox(double x) => x <= 5 ? Math.Log(SpecialFunctions.Erfc(x)) : -Math.Pow(x, 2) - Math.Log(x) - Math.Log(Math.Sqrt(Math.PI));
+ // There's a numerical approximation to increase how far you can calculate ln(erfc(x)).
+ private double logErfcApprox(double x) => x <= 5
+ ? Math.Log(SpecialFunctions.Erfc(x))
+ : -Math.Pow(x, 2) - Math.Log(x * Math.Sqrt(Math.PI)); // https://www.desmos.com/calculator/kdbxwxgf01
// Log rules make subtraction of the non-log value non-trivial, this method simply subtracts the base value of 2 logs.
private double logDiff(double firstLog, double secondLog)
From 9aa11e00905452f9181755f3997d517c84c3365b Mon Sep 17 00:00:00 2001
From: Natelytle
Date: Mon, 20 Mar 2023 23:13:33 -0400
Subject: [PATCH 045/239] update desmos
---
.../Difficulty/TaikoPerformanceCalculator.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index e430616d54..be250f3b80 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
///
/// Estimates the player's tap deviation based on the OD, number of objects, and number of 300s, 100s, and misses,
/// assuming the player's mean hit error is 0. The estimation is consistent in that two SS scores on the same map with the same settings
- /// will always return the same deviation. See: https://www.desmos.com/calculator/qlr946netu
+ /// will always return the same deviation. See: https://www.desmos.com/calculator/x3mvtir093
///
private double? computeEstimatedUr(ScoreInfo score, TaikoDifficultyAttributes attributes)
{
From 31c8cf09332410866c994586f38483d55010c3a2 Mon Sep 17 00:00:00 2001
From: Natelytle
Date: Thu, 27 Jul 2023 22:44:56 -0400
Subject: [PATCH 046/239] Buff accuracy scaling
---
.../Difficulty/TaikoPerformanceCalculator.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index a38eaf6a31..39246f962c 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (attributes.GreatHitWindow <= 0 || estimatedUr == null)
return 0;
- double accuracyValue = Math.Pow(60 / estimatedUr.Value, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 100.0;
+ double accuracyValue = Math.Pow(70 / estimatedUr.Value, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 110.0;
double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
From 4de024675c1738d788238d66e88605ff5652159e Mon Sep 17 00:00:00 2001
From: Natelytle
Date: Thu, 27 Jul 2023 23:02:07 -0400
Subject: [PATCH 047/239] Make comments more professional
---
.../Difficulty/TaikoPerformanceCalculator.cs | 14 ++++++--------
1 file changed, 6 insertions(+), 8 deletions(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index 39246f962c..f5cdb02c82 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -115,9 +115,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
}
///
- /// Estimates the player's tap deviation based on the OD, number of objects, and number of 300s, 100s, and misses,
- /// assuming the player's mean hit error is 0. The estimation is consistent in that two SS scores on the same map with the same settings
- /// will always return the same deviation. See: https://www.desmos.com/calculator/x3mvtir093
+ /// Calculates the tap deviation for a player using the OD, object count, and scores of 300s, 100s, and misses, with an assumed mean hit error of 0.
+ /// Consistency is ensured as identical SS scores on the same map and settings yield the same deviation.
///
private double? computeEstimatedUr(ScoreInfo score, TaikoDifficultyAttributes attributes)
{
@@ -127,7 +126,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
double h300 = attributes.GreatHitWindow;
double h100 = attributes.OkHitWindow;
- // Returns the likelihood of a deviation resulting in the score's hit judgements. The peak of the curve is the most likely deviation.
+ // Determines the probability of a deviation leading to the score's hit evaluations. The curve's apex represents the most probable deviation.
double likelihoodGradient(double d)
{
if (d <= 0)
@@ -157,18 +156,17 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
private double logPcHit(double x, double deviation) => logErfcApprox(x / (deviation * Math.Sqrt(2)));
- // There's a numerical approximation to increase how far you can calculate ln(erfc(x)).
+ // Utilises a numerical approximation to extend the computation range of ln(erfc(x)).
private double logErfcApprox(double x) => x <= 5
? Math.Log(SpecialFunctions.Erfc(x))
: -Math.Pow(x, 2) - Math.Log(x * Math.Sqrt(Math.PI)); // https://www.desmos.com/calculator/kdbxwxgf01
- // Log rules make subtraction of the non-log value non-trivial, this method simply subtracts the base value of 2 logs.
+ // Subtracts the base value of two logs, circumventing log rules that typically complicate subtraction of non-logarithmic values.
private double logDiff(double firstLog, double secondLog)
{
double maxVal = Math.Max(firstLog, secondLog);
- // Avoid negative infinity - negative infinity (NaN) by checking if the higher value is negative infinity.
- // Shouldn't ever happen, but good for redundancy purposes.
+ // To avoid a NaN result, a check is performed to prevent subtraction of two negative infinity values.
if (double.IsNegativeInfinity(maxVal))
{
return maxVal;
From e56942012cf24981d517ec3d50128412feb7e76b Mon Sep 17 00:00:00 2001
From: Natelytle
Date: Fri, 28 Jul 2023 11:38:30 -0400
Subject: [PATCH 048/239] Serialize ok hit window attribute to db
---
osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs | 2 ++
osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs | 1 +
2 files changed, 3 insertions(+)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs
index 9d498d705a..451aed183d 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs
@@ -59,6 +59,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
yield return (ATTRIB_ID_DIFFICULTY, StarRating);
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
+ yield return (ATTRIB_ID_OK_HIT_WINDOW, OkHitWindow);
}
public override void FromDatabaseAttributes(IReadOnlyDictionary values, IBeatmapOnlineInfo onlineInfo)
@@ -67,6 +68,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
StarRating = values[ATTRIB_ID_DIFFICULTY];
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
+ OkHitWindow = values[ATTRIB_ID_OK_HIT_WINDOW];
}
}
}
diff --git a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs
index 5a01faa417..b21b2cf567 100644
--- a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs
+++ b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs
@@ -30,6 +30,7 @@ namespace osu.Game.Rulesets.Difficulty
protected const int ATTRIB_ID_LEGACY_ACCURACY_SCORE = 23;
protected const int ATTRIB_ID_LEGACY_COMBO_SCORE = 25;
protected const int ATTRIB_ID_LEGACY_BONUS_SCORE_RATIO = 27;
+ protected const int ATTRIB_ID_OK_HIT_WINDOW = 29;
///
/// The mods which were applied to the beatmap.
From 5f0020b0ca05aceeb6102d076dd7c3dfa8d59dd2 Mon Sep 17 00:00:00 2001
From: Natelytle
Date: Sun, 30 Jul 2023 20:14:15 -0400
Subject: [PATCH 049/239] Reduce accuracy scaling
---
.../Difficulty/TaikoPerformanceCalculator.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index af988ef2d1..723a6612bc 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (attributes.GreatHitWindow <= 0 || estimatedUr == null)
return 0;
- double accuracyValue = Math.Pow(70 / estimatedUr.Value, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 110.0;
+ double accuracyValue = Math.Pow(65 / estimatedUr.Value, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 100.0;
double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
From 3495510c7b651efee091bad375591b2aa7875102 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sat, 30 Dec 2023 19:16:05 +0100
Subject: [PATCH 050/239] kinda working grid from points
---
.../Edit/OsuBlueprintContainer.cs | 42 +++++++++++++++++++
.../Edit/OsuGridToolboxGroup.cs | 21 ++++++++++
2 files changed, 63 insertions(+)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs
index 54c54fca17..8b391cd06e 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs
@@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Framework.Allocation;
+using osu.Framework.Input.Events;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
@@ -8,16 +10,26 @@ using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Compose.Components;
+using osuTK;
namespace osu.Game.Rulesets.Osu.Edit
{
public partial class OsuBlueprintContainer : ComposeBlueprintContainer
{
+ private OsuGridToolboxGroup gridToolbox = null!;
+
public OsuBlueprintContainer(HitObjectComposer composer)
: base(composer)
{
}
+ [BackgroundDependencyLoader]
+ private void load(OsuGridToolboxGroup gridToolbox)
+ {
+ this.gridToolbox = gridToolbox;
+ gridToolbox.GridFromPointsClicked += OnGridFromPointsClicked;
+ }
+
protected override SelectionHandler CreateSelectionHandler() => new OsuSelectionHandler();
public override HitObjectSelectionBlueprint? CreateHitObjectBlueprintFor(HitObject hitObject)
@@ -36,5 +48,35 @@ namespace osu.Game.Rulesets.Osu.Edit
return base.CreateHitObjectBlueprintFor(hitObject);
}
+
+ private bool isPlacingGridFromPoints;
+ private Vector2? gridFromPointsStart;
+
+ private void OnGridFromPointsClicked()
+ {
+ isPlacingGridFromPoints = true;
+ gridFromPointsStart = null;
+ }
+
+ protected override bool OnClick(ClickEvent e)
+ {
+ if (!isPlacingGridFromPoints)
+ return base.OnClick(e);
+
+ var pos = ToLocalSpace(Composer.FindSnappedPositionAndTime(e.ScreenSpaceMousePosition).ScreenSpacePosition);
+
+ if (!gridFromPointsStart.HasValue)
+ {
+ gridFromPointsStart = pos;
+ }
+ else
+ {
+ gridToolbox.SetGridFromPoints(gridFromPointsStart.Value, pos);
+ isPlacingGridFromPoints = false;
+ gridFromPointsStart = null;
+ }
+
+ return true;
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
index 76e735449a..05b90696a0 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -12,6 +13,7 @@ using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.UI;
@@ -90,6 +92,8 @@ namespace osu.Game.Rulesets.Osu.Edit
private ExpandableSlider gridLinesRotationSlider = null!;
private EditorRadioButtonCollection gridTypeButtons = null!;
+ public event Action? GridFromPointsClicked;
+
public OsuGridToolboxGroup()
: base("grid")
{
@@ -97,6 +101,17 @@ namespace osu.Game.Rulesets.Osu.Edit
private const float max_automatic_spacing = 64;
+ public void SetGridFromPoints(Vector2 point1, Vector2 point2)
+ {
+ StartPositionX.Value = point1.X;
+ StartPositionY.Value = point1.Y;
+ GridLinesRotation.Value = (MathHelper.RadiansToDegrees(MathF.Atan2(point2.Y - point1.Y, point2.X - point1.X)) + 405) % 90 - 45;
+ float dist = Vector2.Distance(point1, point2);
+ while (dist > Spacing.MaxValue)
+ dist /= 2;
+ Spacing.Value = dist;
+ }
+
[BackgroundDependencyLoader]
private void load()
{
@@ -129,6 +144,12 @@ namespace osu.Game.Rulesets.Osu.Edit
Spacing = new Vector2(0f, 10f),
Children = new Drawable[]
{
+ new RoundedButton
+ {
+ Action = () => GridFromPointsClicked?.Invoke(),
+ RelativeSizeAxes = Axes.X,
+ Text = "Grid from points",
+ },
gridTypeButtons = new EditorRadioButtonCollection
{
RelativeSizeAxes = Axes.X,
From fea0ceb49899643371e5f81bf32ccf74fac560df Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sat, 30 Dec 2023 20:22:14 +0100
Subject: [PATCH 051/239] improve the grid from points
---
.../Edit/OsuBlueprintContainer.cs | 26 ++++++++++++++-
.../Edit/OsuGridToolboxGroup.cs | 32 ++++++++++++-------
2 files changed, 45 insertions(+), 13 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs
index 8b391cd06e..79b6fafcab 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs
@@ -58,12 +58,17 @@ namespace osu.Game.Rulesets.Osu.Edit
gridFromPointsStart = null;
}
+ protected override bool OnMouseDown(MouseDownEvent e)
+ {
+ return isPlacingGridFromPoints || base.OnMouseDown(e);
+ }
+
protected override bool OnClick(ClickEvent e)
{
if (!isPlacingGridFromPoints)
return base.OnClick(e);
- var pos = ToLocalSpace(Composer.FindSnappedPositionAndTime(e.ScreenSpaceMousePosition).ScreenSpacePosition);
+ var pos = ToLocalSpace(Composer.FindSnappedPositionAndTime(e.ScreenSpaceMousePosition, ~SnapType.GlobalGrids).ScreenSpacePosition);
if (!gridFromPointsStart.HasValue)
{
@@ -78,5 +83,24 @@ namespace osu.Game.Rulesets.Osu.Edit
return true;
}
+
+ protected override bool OnMouseMove(MouseMoveEvent e)
+ {
+ if (!isPlacingGridFromPoints)
+ return base.OnMouseMove(e);
+
+ var pos = ToLocalSpace(Composer.FindSnappedPositionAndTime(e.ScreenSpaceMousePosition, ~SnapType.GlobalGrids).ScreenSpacePosition);
+
+ if (!gridFromPointsStart.HasValue)
+ {
+ gridToolbox.StartPosition.Value = pos;
+ }
+ else
+ {
+ gridToolbox.SetGridFromPoints(gridFromPointsStart.Value, pos);
+ }
+
+ return true;
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
index 05b90696a0..b07d8dcd56 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
@@ -39,7 +39,6 @@ namespace osu.Game.Rulesets.Osu.Edit
{
MinValue = 0f,
MaxValue = OsuPlayfield.BASE_SIZE.X,
- Precision = 1f
};
///
@@ -49,7 +48,6 @@ namespace osu.Game.Rulesets.Osu.Edit
{
MinValue = 0f,
MaxValue = OsuPlayfield.BASE_SIZE.Y,
- Precision = 1f
};
///
@@ -59,7 +57,6 @@ namespace osu.Game.Rulesets.Osu.Edit
{
MinValue = 4f,
MaxValue = 128f,
- Precision = 1f
};
///
@@ -69,7 +66,6 @@ namespace osu.Game.Rulesets.Osu.Edit
{
MinValue = -45f,
MaxValue = 45f,
- Precision = 1f
};
///
@@ -105,9 +101,15 @@ namespace osu.Game.Rulesets.Osu.Edit
{
StartPositionX.Value = point1.X;
StartPositionY.Value = point1.Y;
- GridLinesRotation.Value = (MathHelper.RadiansToDegrees(MathF.Atan2(point2.Y - point1.Y, point2.X - point1.X)) + 405) % 90 - 45;
+
+ // Get the angle between the two points and normalize to the valid range.
+ float period = GridType.Value == PositionSnapGridType.Triangle ? 60 : 90;
+ GridLinesRotation.Value = (MathHelper.RadiansToDegrees(MathF.Atan2(point2.Y - point1.Y, point2.X - point1.X))
+ + 360 + period / 2) % period - period / 2;
+
+ // Divide the distance so that there is a good density of grid lines.
float dist = Vector2.Distance(point1, point2);
- while (dist > Spacing.MaxValue)
+ while (dist > 32)
dist /= 2;
Spacing.Value = dist;
}
@@ -181,22 +183,28 @@ namespace osu.Game.Rulesets.Osu.Edit
StartPositionX.BindValueChanged(x =>
{
- startPositionXSlider.ContractedLabelText = $"X: {x.NewValue:N0}";
- startPositionXSlider.ExpandedLabelText = $"X Offset: {x.NewValue:N0}";
+ startPositionXSlider.ContractedLabelText = $"X: {x.NewValue:#,0.##}";
+ startPositionXSlider.ExpandedLabelText = $"X Offset: {x.NewValue:#,0.##}";
StartPosition.Value = new Vector2(x.NewValue, StartPosition.Value.Y);
}, true);
StartPositionY.BindValueChanged(y =>
{
- startPositionYSlider.ContractedLabelText = $"Y: {y.NewValue:N0}";
- startPositionYSlider.ExpandedLabelText = $"Y Offset: {y.NewValue:N0}";
+ startPositionYSlider.ContractedLabelText = $"Y: {y.NewValue:#,0.##}";
+ startPositionYSlider.ExpandedLabelText = $"Y Offset: {y.NewValue:#,0.##}";
StartPosition.Value = new Vector2(StartPosition.Value.X, y.NewValue);
}, true);
+ StartPosition.BindValueChanged(pos =>
+ {
+ StartPositionX.Value = pos.NewValue.X;
+ StartPositionY.Value = pos.NewValue.Y;
+ });
+
Spacing.BindValueChanged(spacing =>
{
- spacingSlider.ContractedLabelText = $"S: {spacing.NewValue:N0}";
- spacingSlider.ExpandedLabelText = $"Spacing: {spacing.NewValue:N0}";
+ spacingSlider.ContractedLabelText = $"S: {spacing.NewValue:#,0.##}";
+ spacingSlider.ExpandedLabelText = $"Spacing: {spacing.NewValue:#,0.##}";
SpacingVector.Value = new Vector2(spacing.NewValue);
editorBeatmap.BeatmapInfo.GridSize = (int)spacing.NewValue;
}, true);
From c2ea1848ca96c580f36cef4393c2dd5c145f03c1 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sat, 30 Dec 2023 20:58:21 +0100
Subject: [PATCH 052/239] fix period
---
osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
index b07d8dcd56..31148f10a2 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
@@ -64,8 +64,8 @@ namespace osu.Game.Rulesets.Osu.Edit
///
public BindableFloat GridLinesRotation { get; } = new BindableFloat(0f)
{
- MinValue = -45f,
- MaxValue = 45f,
+ MinValue = -90f,
+ MaxValue = 90f,
};
///
@@ -103,9 +103,9 @@ namespace osu.Game.Rulesets.Osu.Edit
StartPositionY.Value = point1.Y;
// Get the angle between the two points and normalize to the valid range.
- float period = GridType.Value == PositionSnapGridType.Triangle ? 60 : 90;
+ const float period = 180;
GridLinesRotation.Value = (MathHelper.RadiansToDegrees(MathF.Atan2(point2.Y - point1.Y, point2.X - point1.X))
- + 360 + period / 2) % period - period / 2;
+ + period * 1.5f) % period - period * 0.5f;
// Divide the distance so that there is a good density of grid lines.
float dist = Vector2.Distance(point1, point2);
From c952e2f6209ff0cffa7b7db01266baf2cdab73d3 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sun, 31 Dec 2023 02:12:50 +0100
Subject: [PATCH 053/239] deselect stuff stuff when grid from points
---
osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs | 3 +++
1 file changed, 3 insertions(+)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs
index 79b6fafcab..a5f7185a7d 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs
@@ -56,6 +56,9 @@ namespace osu.Game.Rulesets.Osu.Edit
{
isPlacingGridFromPoints = true;
gridFromPointsStart = null;
+
+ // Deselect all objects because we cant snap to objects which are selected.
+ DeselectAll();
}
protected override bool OnMouseDown(MouseDownEvent e)
From 7a0535a2ebe1e612691f16ced09137feb1aa9da3 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sun, 31 Dec 2023 02:26:14 +0100
Subject: [PATCH 054/239] add right click for abort
---
.../Edit/OsuBlueprintContainer.cs | 24 +++++++------------
1 file changed, 9 insertions(+), 15 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs
index a5f7185a7d..45cf754c57 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs
@@ -11,6 +11,7 @@ using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
+using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Edit
{
@@ -62,28 +63,23 @@ namespace osu.Game.Rulesets.Osu.Edit
}
protected override bool OnMouseDown(MouseDownEvent e)
- {
- return isPlacingGridFromPoints || base.OnMouseDown(e);
- }
-
- protected override bool OnClick(ClickEvent e)
{
if (!isPlacingGridFromPoints)
- return base.OnClick(e);
+ return base.OnMouseDown(e);
- var pos = ToLocalSpace(Composer.FindSnappedPositionAndTime(e.ScreenSpaceMousePosition, ~SnapType.GlobalGrids).ScreenSpacePosition);
+ var pos = snappedLocalPosition(e);
if (!gridFromPointsStart.HasValue)
- {
gridFromPointsStart = pos;
- }
else
{
gridToolbox.SetGridFromPoints(gridFromPointsStart.Value, pos);
isPlacingGridFromPoints = false;
- gridFromPointsStart = null;
}
+ if (e.Button == MouseButton.Right)
+ isPlacingGridFromPoints = false;
+
return true;
}
@@ -92,18 +88,16 @@ namespace osu.Game.Rulesets.Osu.Edit
if (!isPlacingGridFromPoints)
return base.OnMouseMove(e);
- var pos = ToLocalSpace(Composer.FindSnappedPositionAndTime(e.ScreenSpaceMousePosition, ~SnapType.GlobalGrids).ScreenSpacePosition);
+ var pos = snappedLocalPosition(e);
if (!gridFromPointsStart.HasValue)
- {
gridToolbox.StartPosition.Value = pos;
- }
else
- {
gridToolbox.SetGridFromPoints(gridFromPointsStart.Value, pos);
- }
return true;
}
+
+ private Vector2 snappedLocalPosition(UIEvent e) => ToLocalSpace(Composer.FindSnappedPositionAndTime(e.ScreenSpaceMousePosition, ~SnapType.GlobalGrids).ScreenSpacePosition);
}
}
From 85bfd6137a5f286341a14615a9ac0ea1fee8137f Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sun, 31 Dec 2023 03:53:42 +0100
Subject: [PATCH 055/239] improve UI
From 4e3fe5112d9d02171fb19e23bd26520f34590c87 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Mon, 1 Jan 2024 16:09:18 +0100
Subject: [PATCH 056/239] merge conflict fix
---
osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
index 31148f10a2..c318bac64d 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
@@ -86,6 +86,7 @@ namespace osu.Game.Rulesets.Osu.Edit
private ExpandableSlider startPositionYSlider = null!;
private ExpandableSlider spacingSlider = null!;
private ExpandableSlider gridLinesRotationSlider = null!;
+ private RoundedButton gridFromPointsButton = null!;
private EditorRadioButtonCollection gridTypeButtons = null!;
public event Action? GridFromPointsClicked;
@@ -146,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.Edit
Spacing = new Vector2(0f, 10f),
Children = new Drawable[]
{
- new RoundedButton
+ gridFromPointsButton = new RoundedButton
{
Action = () => GridFromPointsClicked?.Invoke(),
RelativeSizeAxes = Axes.X,
@@ -217,6 +218,8 @@ namespace osu.Game.Rulesets.Osu.Edit
expandingContainer?.Expanded.BindValueChanged(v =>
{
+ gridFromPointsButton.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint);
+ gridFromPointsButton.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None;
gridTypeButtons.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint);
gridTypeButtons.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None;
}, true);
From 98505d0bba18fe4b08c06bf44d77298040c1c5e8 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sun, 31 Dec 2023 18:58:29 +0100
Subject: [PATCH 057/239] improve grid from points tool code
---
.../Edit/GridFromPointsTool.cs | 72 +++++++++++++++++++
.../Edit/OsuBlueprintContainer.cs | 63 ----------------
.../Edit/OsuHitObjectComposer.cs | 18 +++--
osu.Game/Rulesets/Edit/HitObjectComposer.cs | 5 +-
4 files changed, 90 insertions(+), 68 deletions(-)
create mode 100644 osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs
diff --git a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs
new file mode 100644
index 0000000000..722d9d1303
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs
@@ -0,0 +1,72 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Input.Events;
+using osu.Game.Rulesets.Edit;
+using osuTK;
+using osuTK.Input;
+
+namespace osu.Game.Rulesets.Osu.Edit
+{
+ public partial class GridFromPointsTool : Drawable
+ {
+ [Resolved]
+ private OsuGridToolboxGroup gridToolboxGroup { get; set; } = null!;
+
+ [Resolved(canBeNull: true)]
+ private IPositionSnapProvider? snapProvider { get; set; }
+
+ public bool IsPlacing { get; private set; }
+
+ private Vector2? startPosition;
+
+ public void BeginPlacement()
+ {
+ IsPlacing = true;
+ startPosition = null;
+ }
+
+ protected override bool OnMouseDown(MouseDownEvent e)
+ {
+ if (!IsPlacing)
+ return base.OnMouseDown(e);
+
+ var pos = snappedLocalPosition(e);
+
+ if (!startPosition.HasValue)
+ startPosition = pos;
+ else
+ {
+ gridToolboxGroup.SetGridFromPoints(startPosition.Value, pos);
+ IsPlacing = false;
+ }
+
+ if (e.Button == MouseButton.Right)
+ IsPlacing = false;
+
+ return true;
+ }
+
+ protected override bool OnMouseMove(MouseMoveEvent e)
+ {
+ if (!IsPlacing)
+ return base.OnMouseMove(e);
+
+ var pos = snappedLocalPosition(e);
+
+ if (!startPosition.HasValue)
+ gridToolboxGroup.StartPosition.Value = pos;
+ else
+ gridToolboxGroup.SetGridFromPoints(startPosition.Value, pos);
+
+ return true;
+ }
+
+ private Vector2 snappedLocalPosition(UIEvent e)
+ {
+ return ToLocalSpace(snapProvider?.FindSnappedPositionAndTime(e.ScreenSpaceMousePosition, ~SnapType.GlobalGrids).ScreenSpacePosition ?? e.ScreenSpaceMousePosition);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs
index 45cf754c57..54c54fca17 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework.Allocation;
-using osu.Framework.Input.Events;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
@@ -10,27 +8,16 @@ using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Compose.Components;
-using osuTK;
-using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Edit
{
public partial class OsuBlueprintContainer : ComposeBlueprintContainer
{
- private OsuGridToolboxGroup gridToolbox = null!;
-
public OsuBlueprintContainer(HitObjectComposer composer)
: base(composer)
{
}
- [BackgroundDependencyLoader]
- private void load(OsuGridToolboxGroup gridToolbox)
- {
- this.gridToolbox = gridToolbox;
- gridToolbox.GridFromPointsClicked += OnGridFromPointsClicked;
- }
-
protected override SelectionHandler CreateSelectionHandler() => new OsuSelectionHandler();
public override HitObjectSelectionBlueprint? CreateHitObjectBlueprintFor(HitObject hitObject)
@@ -49,55 +36,5 @@ namespace osu.Game.Rulesets.Osu.Edit
return base.CreateHitObjectBlueprintFor(hitObject);
}
-
- private bool isPlacingGridFromPoints;
- private Vector2? gridFromPointsStart;
-
- private void OnGridFromPointsClicked()
- {
- isPlacingGridFromPoints = true;
- gridFromPointsStart = null;
-
- // Deselect all objects because we cant snap to objects which are selected.
- DeselectAll();
- }
-
- protected override bool OnMouseDown(MouseDownEvent e)
- {
- if (!isPlacingGridFromPoints)
- return base.OnMouseDown(e);
-
- var pos = snappedLocalPosition(e);
-
- if (!gridFromPointsStart.HasValue)
- gridFromPointsStart = pos;
- else
- {
- gridToolbox.SetGridFromPoints(gridFromPointsStart.Value, pos);
- isPlacingGridFromPoints = false;
- }
-
- if (e.Button == MouseButton.Right)
- isPlacingGridFromPoints = false;
-
- return true;
- }
-
- protected override bool OnMouseMove(MouseMoveEvent e)
- {
- if (!isPlacingGridFromPoints)
- return base.OnMouseMove(e);
-
- var pos = snappedLocalPosition(e);
-
- if (!gridFromPointsStart.HasValue)
- gridToolbox.StartPosition.Value = pos;
- else
- gridToolbox.SetGridFromPoints(gridFromPointsStart.Value, pos);
-
- return true;
- }
-
- private Vector2 snappedLocalPosition(UIEvent e) => ToLocalSpace(Composer.FindSnappedPositionAndTime(e.ScreenSpaceMousePosition, ~SnapType.GlobalGrids).ScreenSpacePosition);
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index 02e98d75a7..e035317194 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -81,13 +81,12 @@ namespace osu.Game.Rulesets.Osu.Edit
// Give a bit of breathing room around the playfield content.
PlayfieldContentContainer.Padding = new MarginPadding(10);
- LayerBelowRuleset.AddRange(new Drawable[]
- {
+ LayerBelowRuleset.Add(
distanceSnapGridContainer = new Container
{
RelativeSizeAxes = Axes.Both
}
- });
+ );
selectedHitObjects = EditorBeatmap.SelectedHitObjects.GetBoundCopy();
selectedHitObjects.CollectionChanged += (_, _) => updateDistanceSnapGrid();
@@ -100,6 +99,15 @@ namespace osu.Game.Rulesets.Osu.Edit
updateDistanceSnapGrid();
OsuGridToolboxGroup.GridType.BindValueChanged(updatePositionSnapGrid, true);
+ OsuGridToolboxGroup.GridFromPointsClicked += () => gridFromPointsTool.BeginPlacement();
+
+ LayerAboveRuleset.Add(
+ // Place it above the playfield and blueprints, so it takes priority when handling input.
+ gridFromPointsTool = new GridFromPointsTool
+ {
+ RelativeSizeAxes = Axes.Both,
+ }
+ );
RightToolbox.AddRange(new EditorToolboxGroup[]
{
@@ -110,6 +118,8 @@ namespace osu.Game.Rulesets.Osu.Edit
);
}
+ private GridFromPointsTool gridFromPointsTool;
+
private void updatePositionSnapGrid(ValueChangedEvent obj)
{
if (positionSnapGrid != null)
@@ -278,7 +288,7 @@ namespace osu.Game.Rulesets.Osu.Edit
foreach (var b in blueprints)
{
- if (b.IsSelected)
+ if (b.IsSelected && !gridFromPointsTool.IsPlacing)
continue;
var snapPositions = b.ScreenSpaceSnapPoints;
diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
index 50e6393895..4f8329cc82 100644
--- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs
+++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
@@ -76,6 +76,8 @@ namespace osu.Game.Rulesets.Edit
protected readonly Container LayerBelowRuleset = new Container { RelativeSizeAxes = Axes.Both };
+ protected readonly Container LayerAboveRuleset = new Container { RelativeSizeAxes = Axes.Both };
+
protected InputManager InputManager { get; private set; }
private EditorRadioButtonCollection toolboxCollection;
@@ -137,7 +139,8 @@ namespace osu.Game.Rulesets.Edit
drawableRulesetWrapper,
// layers above playfield
drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer()
- .WithChild(BlueprintContainer = CreateBlueprintContainer())
+ .WithChild(BlueprintContainer = CreateBlueprintContainer()),
+ drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer().WithChild(LayerAboveRuleset),
}
},
new Container
From b54c9a36fea93e5bcf2ec3b05ac931eba5090c96 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sun, 31 Dec 2023 19:08:27 +0100
Subject: [PATCH 058/239] move GridFromPointsClicked handler creation code
---
osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs | 7 +++++++
osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 5 ++---
2 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs
index 722d9d1303..9bbeeaafc3 100644
--- a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs
+++ b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs
@@ -22,6 +22,13 @@ namespace osu.Game.Rulesets.Osu.Edit
private Vector2? startPosition;
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ gridToolboxGroup.GridFromPointsClicked += BeginPlacement;
+ }
+
public void BeginPlacement()
{
IsPlacing = true;
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index e035317194..cd5c4f4ba9 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -63,6 +63,8 @@ namespace osu.Game.Rulesets.Osu.Edit
private Bindable placementObject;
+ private GridFromPointsTool gridFromPointsTool;
+
[Cached(typeof(IDistanceSnapProvider))]
protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider();
@@ -99,7 +101,6 @@ namespace osu.Game.Rulesets.Osu.Edit
updateDistanceSnapGrid();
OsuGridToolboxGroup.GridType.BindValueChanged(updatePositionSnapGrid, true);
- OsuGridToolboxGroup.GridFromPointsClicked += () => gridFromPointsTool.BeginPlacement();
LayerAboveRuleset.Add(
// Place it above the playfield and blueprints, so it takes priority when handling input.
@@ -118,8 +119,6 @@ namespace osu.Game.Rulesets.Osu.Edit
);
}
- private GridFromPointsTool gridFromPointsTool;
-
private void updatePositionSnapGrid(ValueChangedEvent obj)
{
if (positionSnapGrid != null)
From f8979cff3aaf2c184f08d66fd6cc6f4aeb397f41 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Mon, 1 Jan 2024 16:14:20 +0100
Subject: [PATCH 059/239] fix distance
---
osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
index c318bac64d..b73eaecc66 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
@@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Osu.Edit
// Divide the distance so that there is a good density of grid lines.
float dist = Vector2.Distance(point1, point2);
- while (dist > 32)
+ while (dist >= max_automatic_spacing)
dist /= 2;
Spacing.Value = dist;
}
From 2918ecf46c3516039a28f58d2dd71042d505f574 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Thu, 1 Feb 2024 16:56:57 +0100
Subject: [PATCH 060/239] Remove Masking from PositionSnapGrid
This caused issues in rendering the outline of the grid because the outline was getting masked at some resolutions.
---
.../Components/LinedPositionSnapGrid.cs | 128 +++++++++++++++---
.../Compose/Components/PositionSnapGrid.cs | 2 -
2 files changed, 106 insertions(+), 24 deletions(-)
diff --git a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs
index ebdd76a4e2..8a7f6b5344 100644
--- a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs
@@ -15,18 +15,29 @@ namespace osu.Game.Screens.Edit.Compose.Components
{
protected void GenerateGridLines(Vector2 step, Vector2 drawSize)
{
+ if (Precision.AlmostEquals(step, Vector2.Zero))
+ return;
+
int index = 0;
- var currentPosition = StartPosition.Value;
// Make lines the same width independent of display resolution.
float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width;
- float lineLength = drawSize.Length * 2;
+ float rotation = MathHelper.RadiansToDegrees(MathF.Atan2(step.Y, step.X));
List generatedLines = new List();
- while (lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize) ||
- isMovingTowardsBox(currentPosition, step, drawSize))
+ while (true)
{
+ Vector2 currentPosition = StartPosition.Value + index++ * step;
+
+ if (!lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize, out var p1, out var p2))
+ {
+ if (!isMovingTowardsBox(currentPosition, step, drawSize))
+ break;
+
+ continue;
+ }
+
var gridLine = new Box
{
Colour = Colour4.White,
@@ -34,15 +45,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.None,
Width = lineWidth,
- Height = lineLength,
- Position = currentPosition,
- Rotation = MathHelper.RadiansToDegrees(MathF.Atan2(step.Y, step.X)),
+ Height = Vector2.Distance(p1, p2),
+ Position = (p1 + p2) / 2,
+ Rotation = rotation,
};
generatedLines.Add(gridLine);
-
- index += 1;
- currentPosition = StartPosition.Value + index * step;
}
if (generatedLines.Count == 0)
@@ -59,23 +67,99 @@ namespace osu.Game.Screens.Edit.Compose.Components
(currentPosition + step - box).LengthSquared < (currentPosition - box).LengthSquared;
}
- private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box)
+ ///
+ /// Determines if the line starting at and going in the direction of
+ /// definitely intersects the box on (0, 0) with the given width and height and returns the intersection points if it does.
+ ///
+ /// The start point of the line.
+ /// The direction of the line.
+ /// The width and height of the box.
+ /// The first intersection point.
+ /// The second intersection point.
+ /// Whether the line definitely intersects the box.
+ private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box, out Vector2 p1, out Vector2 p2)
{
- var p2 = lineStart + lineDir;
+ p1 = Vector2.Zero;
+ p2 = Vector2.Zero;
- double d1 = det(Vector2.Zero);
- double d2 = det(new Vector2(box.X, 0));
- double d3 = det(new Vector2(0, box.Y));
- double d4 = det(box);
+ if (Precision.AlmostEquals(lineDir.X, 0))
+ {
+ // If the line is vertical, we only need to check if the X coordinate of the line is within the box.
+ if (!Precision.DefinitelyBigger(lineStart.X, 0) || !Precision.DefinitelyBigger(box.X, lineStart.X))
+ return false;
- return definitelyDifferentSign(d1, d2) || definitelyDifferentSign(d3, d4) ||
- definitelyDifferentSign(d1, d3) || definitelyDifferentSign(d2, d4);
+ p1 = new Vector2(lineStart.X, 0);
+ p2 = new Vector2(lineStart.X, box.Y);
+ return true;
+ }
- double det(Vector2 p) => (p.X - lineStart.X) * (p2.Y - lineStart.Y) - (p.Y - lineStart.Y) * (p2.X - lineStart.X);
+ if (Precision.AlmostEquals(lineDir.Y, 0))
+ {
+ // If the line is horizontal, we only need to check if the Y coordinate of the line is within the box.
+ if (!Precision.DefinitelyBigger(lineStart.Y, 0) || !Precision.DefinitelyBigger(box.Y, lineStart.Y))
+ return false;
- bool definitelyDifferentSign(double a, double b) => !Precision.AlmostEquals(a, 0) &&
- !Precision.AlmostEquals(b, 0) &&
- Math.Sign(a) != Math.Sign(b);
+ p1 = new Vector2(0, lineStart.Y);
+ p2 = new Vector2(box.X, lineStart.Y);
+ return true;
+ }
+
+ float m = lineDir.Y / lineDir.X;
+ float mInv = lineDir.X / lineDir.Y; // Use this to improve numerical stability if X is close to zero.
+ float b = lineStart.Y - m * lineStart.X;
+
+ // Calculate intersection points with the sides of the box.
+ var p = new List(4);
+
+ if (0 <= b && b <= box.Y)
+ p.Add(new Vector2(0, b));
+ if (0 <= (box.Y - b) * mInv && (box.Y - b) * mInv <= box.X)
+ p.Add(new Vector2((box.Y - b) * mInv, box.Y));
+ if (0 <= m * box.X + b && m * box.X + b <= box.Y)
+ p.Add(new Vector2(box.X, m * box.X + b));
+ if (0 <= -b * mInv && -b * mInv <= box.X)
+ p.Add(new Vector2(-b * mInv, 0));
+
+ switch (p.Count)
+ {
+ case 4:
+ // If there are 4 intersection points, the line is a diagonal of the box.
+ if (m > 0)
+ {
+ p1 = Vector2.Zero;
+ p2 = box;
+ }
+ else
+ {
+ p1 = new Vector2(0, box.Y);
+ p2 = new Vector2(box.X, 0);
+ }
+
+ break;
+
+ case 3:
+ // If there are 3 intersection points, the line goes through a corner of the box.
+ if (p[0] == p[1])
+ {
+ p1 = p[0];
+ p2 = p[2];
+ }
+ else
+ {
+ p1 = p[0];
+ p2 = p[1];
+ }
+
+ break;
+
+ case 2:
+ p1 = p[0];
+ p2 = p[1];
+
+ break;
+ }
+
+ return !Precision.AlmostEquals(p1, p2);
}
}
}
diff --git a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs
index 36687ef73a..e576ac1e49 100644
--- a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs
@@ -21,8 +21,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected PositionSnapGrid()
{
- Masking = true;
-
StartPosition.BindValueChanged(_ => GridCache.Invalidate());
AddLayout(GridCache);
From 93dbd7507fd61086a8c9d3422e6a02560254f4d4 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Thu, 1 Feb 2024 17:07:03 +0100
Subject: [PATCH 061/239] Fix masking in circular snap grid
---
.../Edit/Compose/Components/CircularPositionSnapGrid.cs | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs
index 403a270359..791cb33439 100644
--- a/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs
@@ -82,7 +82,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
generatedCircles.First().Alpha = 0.8f;
- AddRangeInternal(generatedCircles);
+ AddInternal(new Container
+ {
+ Masking = true,
+ RelativeSizeAxes = Axes.Both,
+ Children = generatedCircles,
+ });
}
public override Vector2 GetSnappedPosition(Vector2 original)
From 8ccb14f19f1363c42eaf80718ed50c1a68338185 Mon Sep 17 00:00:00 2001
From: tsunyoku
Date: Tue, 6 Feb 2024 13:08:17 +0000
Subject: [PATCH 062/239] include slider count in accuracy pp if slider head
accuracy is in use
---
osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 ++
1 file changed, 2 insertions(+)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index b31f4ff519..7f4c5f6c46 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -192,6 +192,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window.
double betterAccuracyPercentage;
int amountHitObjectsWithAccuracy = attributes.HitCircleCount;
+ if (!score.Mods.Any(h => h is OsuModClassic cl && cl.NoSliderHeadAccuracy.Value))
+ amountHitObjectsWithAccuracy += attributes.SliderCount;
if (amountHitObjectsWithAccuracy > 0)
betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countOk * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6);
From 7d34542c12a8e1db707d101fc7988e32e9da74d8 Mon Sep 17 00:00:00 2001
From: TextAdventurer12
Date: Thu, 22 Feb 2024 15:14:56 +1300
Subject: [PATCH 063/239] use difficulty instead of topstrain
---
osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
index 918e702b45..13d89cf2b8 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
@@ -37,6 +37,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
protected virtual double DifficultyMultiplier => DEFAULT_DIFFICULTY_MULTIPLIER;
protected List objectStrains = new List();
+ protected double difficulty;
protected OsuStrainSkill(Mod[] mods)
: base(mods)
@@ -45,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
public override double DifficultyValue()
{
- double difficulty = 0;
+ difficulty = 0;
double weight = 1;
// Sections with 0 strain are excluded to avoid worst-case time complexity of the following sort (e.g. /b/2351871).
@@ -78,9 +79,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
///
public double CountDifficultStrains()
{
- double topStrain = objectStrains.Max();
+ double consistentTopStrain = difficulty / 10; // What would the top strain be if all strain values were identical
- return objectStrains.Sum(s => Math.Pow(s / topStrain, 4));
+ return objectStrains.Sum(s => Math.Pow(1, s / consistentTopStrain, 5));
}
}
}
From 0db910deb90b792f41203ee9410ef6dc05231308 Mon Sep 17 00:00:00 2001
From: TextAdventurer12
Date: Thu, 22 Feb 2024 15:20:32 +1300
Subject: [PATCH 064/239] cap each note at adding 1 difficult strain count
---
osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
index 13d89cf2b8..84bf8e3bf6 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
@@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
{
double consistentTopStrain = difficulty / 10; // What would the top strain be if all strain values were identical
- return objectStrains.Sum(s => Math.Pow(1, s / consistentTopStrain, 5));
+ return objectStrains.Sum(s => Math.Pow(Math.Min(1, s / consistentTopStrain), 5));
}
}
}
From caba0510db86b66f9e238c640a7a09e272d67fdb Mon Sep 17 00:00:00 2001
From: nathen
Date: Sat, 9 Mar 2024 23:10:53 -0500
Subject: [PATCH 065/239] Compute the upper bound on deviation with a 99%
confidence interval
---
.../Difficulty/TaikoPerformanceCalculator.cs | 81 +++++++++----------
1 file changed, 40 insertions(+), 41 deletions(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index 723a6612bc..322e3874ba 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
countOk = score.Statistics.GetValueOrDefault(HitResult.Ok);
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
- estimatedUr = computeEstimatedUr(score, taikoAttributes);
+ estimatedUr = computeDeviationUpperBound(taikoAttributes) * 10;
// The effectiveMissCount is calculated by gaining a ratio for totalSuccessfulHits and increasing the miss penalty for shorter object counts lower than 1000.
if (totalSuccessfulHits > 0)
@@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (attributes.GreatHitWindow <= 0 || estimatedUr == null)
return 0;
- double accuracyValue = Math.Pow(65 / estimatedUr.Value, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 100.0;
+ double accuracyValue = Math.Pow(70 / estimatedUr.Value, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 100.0;
double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
@@ -115,10 +115,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
}
///
- /// Calculates the tap deviation for a player using the OD, object count, and scores of 300s, 100s, and misses, with an assumed mean hit error of 0.
- /// Consistency is ensured as identical SS scores on the same map and settings yield the same deviation.
+ /// Computes an upper bound on the player's tap deviation based on the OD, number of circles and sliders,
+ /// and the hit judgements, assuming the player's mean hit error is 0. The estimation is consistent in that
+ /// two SS scores on the same map with the same settings will always return the same deviation.
///
- private double? computeEstimatedUr(ScoreInfo score, TaikoDifficultyAttributes attributes)
+ private double? computeDeviationUpperBound(TaikoDifficultyAttributes attributes)
{
if (totalSuccessfulHits == 0 || attributes.GreatHitWindow <= 0)
return null;
@@ -126,53 +127,51 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
double h300 = attributes.GreatHitWindow;
double h100 = attributes.OkHitWindow;
- // Determines the probability of a deviation leading to the score's hit evaluations. The curve's apex represents the most probable deviation.
- double likelihoodGradient(double d)
+ const double z = 2.32634787404; // 99% critical value for the normal distribution (one-tailed).
+
+ // The upper bound on deviation, calculated with the ratio of 300s to 100s, and the great hit window.
+ double? calcDeviationGreatWindow()
{
- if (d <= 0)
- return 0;
+ if (countGreat == 0) return null;
- double p300 = logDiff(0, logPcHit(h300, d));
- double p100 = logDiff(logPcHit(h300, d), logPcHit(h100, d));
- double p0 = logPcHit(h100, d);
+ double n = totalSuccessfulHits;
- double gradient = Math.Exp(
- (countGreat * p300
- + (countOk + 0.5) * p100
- + countMiss * p0) / totalHits
- );
+ // Proportion of greats hit, ignoring misses.
+ double p = countGreat / n;
- return -gradient;
+ // We can be 99% confident that p is at least this value.
+ double pLowerBound = (n * p + z * z / 2) / (n + z * z) - z / (n + z * z) * Math.Sqrt(n * p * (1 - p) + z * z / 4);
+
+ // We can be 99% confident that the deviation is not higher than:
+ return h300 / (Math.Sqrt(2) * SpecialFunctions.ErfInv(pLowerBound));
}
- double deviation = FindMinimum.OfScalarFunction(likelihoodGradient, 30);
+ // The upper bound on deviation, calculated with the ratio of 300s + 100s to misses, and the good hit window.
+ // This will return a lower value than the first method when the number of 100s is high, but the miss count is low.
+ double? calcDeviationGoodWindow()
+ {
+ if (totalSuccessfulHits == 0) return null;
- return deviation * 10;
+ double n = totalHits;
+
+ // Proportion of greats + goods hit.
+ double p = totalSuccessfulHits / n;
+
+ // We can be 99% confident that p is at least this value.
+ double pLowerBound = (n * p + z * z / 2) / (n + z * z) - z / (n + z * z) * Math.Sqrt(n * p * (1 - p) + z * z / 4);
+
+ // We can be 99% confident that the deviation is not higher than:
+ return h100 / (Math.Sqrt(2) * SpecialFunctions.ErfInv(pLowerBound));
+ }
+
+ if (calcDeviationGreatWindow() is null)
+ return calcDeviationGoodWindow();
+
+ return Math.Min(calcDeviationGreatWindow()!.Value, calcDeviationGoodWindow()!.Value);
}
private int totalHits => countGreat + countOk + countMeh + countMiss;
private int totalSuccessfulHits => countGreat + countOk + countMeh;
-
- private double logPcHit(double x, double deviation) => logErfcApprox(x / (deviation * Math.Sqrt(2)));
-
- // Utilises a numerical approximation to extend the computation range of ln(erfc(x)).
- private double logErfcApprox(double x) => x <= 5
- ? Math.Log(SpecialFunctions.Erfc(x))
- : -Math.Pow(x, 2) - Math.Log(x * Math.Sqrt(Math.PI)); // https://www.desmos.com/calculator/kdbxwxgf01
-
- // Subtracts the base value of two logs, circumventing log rules that typically complicate subtraction of non-logarithmic values.
- private double logDiff(double firstLog, double secondLog)
- {
- double maxVal = Math.Max(firstLog, secondLog);
-
- // To avoid a NaN result, a check is performed to prevent subtraction of two negative infinity values.
- if (double.IsNegativeInfinity(maxVal))
- {
- return maxVal;
- }
-
- return firstLog + SpecialFunctions.Log1p(-Math.Exp(-(firstLog - secondLog)));
- }
}
}
From 6ddb2b7f8b1a40f3fc86892b00c67486884ac025 Mon Sep 17 00:00:00 2001
From: nathen
Date: Sun, 10 Mar 2024 00:19:04 -0500
Subject: [PATCH 066/239] Include misses in the great window deviation calc
---
.../Difficulty/TaikoPerformanceCalculator.cs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index 322e3874ba..8998b5cfa3 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -129,12 +129,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
const double z = 2.32634787404; // 99% critical value for the normal distribution (one-tailed).
- // The upper bound on deviation, calculated with the ratio of 300s to 100s, and the great hit window.
+ // The upper bound on deviation, calculated with the ratio of 300s to objects, and the great hit window.
double? calcDeviationGreatWindow()
{
if (countGreat == 0) return null;
- double n = totalSuccessfulHits;
+ double n = totalHits;
// Proportion of greats hit, ignoring misses.
double p = countGreat / n;
@@ -146,7 +146,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
return h300 / (Math.Sqrt(2) * SpecialFunctions.ErfInv(pLowerBound));
}
- // The upper bound on deviation, calculated with the ratio of 300s + 100s to misses, and the good hit window.
+ // The upper bound on deviation, calculated with the ratio of 300s + 100s to objects, and the good hit window.
// This will return a lower value than the first method when the number of 100s is high, but the miss count is low.
double? calcDeviationGoodWindow()
{
From 537059504aa12c4dbc4a790c8dc24603ccdd94f6 Mon Sep 17 00:00:00 2001
From: nathen
Date: Sun, 10 Mar 2024 00:20:06 -0500
Subject: [PATCH 067/239] Fix comment
---
.../Difficulty/TaikoPerformanceCalculator.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index 8998b5cfa3..c532d24f4d 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
double n = totalHits;
- // Proportion of greats hit, ignoring misses.
+ // Proportion of greats hit.
double p = countGreat / n;
// We can be 99% confident that p is at least this value.
From a9b3416a3fe2e77f66c249c85636ca476ac3027c Mon Sep 17 00:00:00 2001
From: nathen
Date: Sun, 10 Mar 2024 00:37:28 -0500
Subject: [PATCH 068/239] 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 9f5f6b5d37405787a4308db875331060f54572b4 Mon Sep 17 00:00:00 2001
From: TextAdventurer12
Date: Sat, 6 Apr 2024 21:39:27 +1300
Subject: [PATCH 069/239] 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 b32d73ec9b7be94b3f55a7007e236cffd9228ca1 Mon Sep 17 00:00:00 2001
From: TextAdventurer12
Date: Sat, 13 Apr 2024 02:43:33 +1200
Subject: [PATCH 070/239] 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 071/239] 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 c1efcc054cf1594d3d883fcf66ab67b811d4de81 Mon Sep 17 00:00:00 2001
From: danielthirtle
Date: Tue, 21 May 2024 21:03:53 +1200
Subject: [PATCH 072/239] 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 073/239] 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 61afda1089eeee4d42f59ab6185023d699acb788 Mon Sep 17 00:00:00 2001
From: js1086
Date: Sun, 26 May 2024 11:24:06 +0100
Subject: [PATCH 074/239] 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 075/239] Use correct operation for 0 difficulty case
---
osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
index af97d90e53..ec132237db 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
@@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
///
public double CountDifficultStrains()
{
- if (double.IsNaN(Difficulty))
+ if (Difficulty == 0)
return 0.0;
double consistentTopStrain = Difficulty / 10; // What would the top strain be if all strain values were identical
From 17145673421118baee2d6971277771625512a78a Mon Sep 17 00:00:00 2001
From: Nathen
Date: Wed, 29 May 2024 09:40:39 -0400
Subject: [PATCH 076/239] 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 077/239] Fix naming convention
---
.../Difficulty/TaikoPerformanceAttributes.cs | 4 ++--
.../Difficulty/TaikoPerformanceCalculator.cs | 14 +++++++-------
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs
index 6d85aa8f3d..7c74e43db1 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs
@@ -18,8 +18,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
[JsonProperty("effective_miss_count")]
public double EffectiveMissCount { get; set; }
- [JsonProperty("estimated_ur")]
- public double? EstimatedUr { get; set; }
+ [JsonProperty("estimated_unstable_rate")]
+ public double? EstimatedUnstableRate { get; set; }
public override IEnumerable GetAttributesForDisplay()
{
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index a8ccaedfd0..ab809a0ade 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
private int countOk;
private int countMeh;
private int countMiss;
- private double? estimatedUr;
+ private double? estimatedUnstableRate;
private double effectiveMissCount;
@@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
countOk = score.Statistics.GetValueOrDefault(HitResult.Ok);
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
- estimatedUr = computeDeviationUpperBound(taikoAttributes) * 10;
+ estimatedUnstableRate = computeDeviationUpperBound(taikoAttributes) * 10;
// The effectiveMissCount is calculated by gaining a ratio for totalSuccessfulHits and increasing the miss penalty for shorter object counts lower than 1000.
if (totalSuccessfulHits > 0)
@@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
Difficulty = difficultyValue,
Accuracy = accuracyValue,
EffectiveMissCount = effectiveMissCount,
- EstimatedUr = estimatedUr,
+ EstimatedUnstableRate = estimatedUnstableRate,
Total = totalValue
};
}
@@ -92,18 +92,18 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (score.Mods.Any(m => m is ModFlashlight))
difficultyValue *= 1.050 * lengthBonus;
- if (estimatedUr == null)
+ if (estimatedUnstableRate == null)
return 0;
- return difficultyValue * Math.Pow(SpecialFunctions.Erf(400 / (Math.Sqrt(2) * estimatedUr.Value)), 2.0);
+ return difficultyValue * Math.Pow(SpecialFunctions.Erf(400 / (Math.Sqrt(2) * estimatedUnstableRate.Value)), 2.0);
}
private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes, bool isConvert)
{
- if (attributes.GreatHitWindow <= 0 || estimatedUr == null)
+ if (attributes.GreatHitWindow <= 0 || estimatedUnstableRate == null)
return 0;
- double accuracyValue = Math.Pow(70 / estimatedUr.Value, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 100.0;
+ double accuracyValue = Math.Pow(70 / estimatedUnstableRate.Value, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 100.0;
double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
From 2fb22f1febef5ad5ab120c24875e69b798ed984e Mon Sep 17 00:00:00 2001
From: Nathen
Date: Sun, 23 Jun 2024 19:17:19 -0400
Subject: [PATCH 078/239] Move the return value for deviation below the local
functions
---
.../Difficulty/TaikoPerformanceCalculator.cs | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index ab809a0ade..e42b015176 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -129,14 +129,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
const double z = 2.32634787404; // 99% critical value for the normal distribution (one-tailed).
- double? deviationGreatWindow = calcDeviationGreatWindow();
- double? deviationGoodWindow = calcDeviationGoodWindow();
-
- if (deviationGreatWindow is null)
- return deviationGoodWindow;
-
- return Math.Min(deviationGreatWindow.Value, deviationGoodWindow!.Value);
-
// The upper bound on deviation, calculated with the ratio of 300s to objects, and the great hit window.
double? calcDeviationGreatWindow()
{
@@ -171,6 +163,14 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
// We can be 99% confident that the deviation is not higher than:
return h100 / (Math.Sqrt(2) * SpecialFunctions.ErfInv(pLowerBound));
}
+
+ double? deviationGreatWindow = calcDeviationGreatWindow();
+ double? deviationGoodWindow = calcDeviationGoodWindow();
+
+ if (deviationGreatWindow is null)
+ return deviationGoodWindow;
+
+ return Math.Min(deviationGreatWindow.Value, deviationGoodWindow!.Value);
}
private int totalHits => countGreat + countOk + countMeh + countMiss;
From e25642b48472789aaffa5254106d763a4cee9cad Mon Sep 17 00:00:00 2001
From: StanR
Date: Mon, 15 Jul 2024 14:45:31 +0500
Subject: [PATCH 079/239] 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 080/239] 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 081/239] 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 082/239] Even better readability
---
osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 0aec0a9d4f..a4ebcd15a4 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window.
double betterAccuracyPercentage;
int amountHitObjectsWithAccuracy = attributes.HitCircleCount;
- if (score.Mods.All(h => h is not OsuModClassic cl || !cl.NoSliderHeadAccuracy.Value))
+ if (score.Mods.OfType().All(m => !m.NoSliderHeadAccuracy.Value))
amountHitObjectsWithAccuracy += attributes.SliderCount;
if (amountHitObjectsWithAccuracy > 0)
From bae9625b0b4785b83124bfd38ccd8ff85d74947b Mon Sep 17 00:00:00 2001
From: StanR
Date: Fri, 19 Jul 2024 10:13:50 +0500
Subject: [PATCH 083/239] 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 084/239] Reduce base ratio a bit
---
osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
index 9c48af80a7..ba77bc6a61 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
@@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
double prevDelta = prevObj.StrainTime;
double lastDelta = lastObj.StrainTime;
- double currRatio = 1.0 + 10.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses.
+ double currRatio = 1.0 + 8.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses.
double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - currObj.HitWindowGreat * 0.3) / (currObj.HitWindowGreat * 0.3));
From 3acd00b9b703a0a5a7445192eabac94bfe385594 Mon Sep 17 00:00:00 2001
From: StanR
Date: Sat, 10 Aug 2024 22:30:24 +0500
Subject: [PATCH 085/239] Tweak some values
---
.../Difficulty/Evaluators/RhythmEvaluator.cs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
index ba77bc6a61..d2f58925cd 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
@@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
}
private const int history_time_max = 5 * 1000; // 5 seconds of calculatingRhythmBonus max.
- private const double rhythm_multiplier = 1.05;
+ private const double rhythm_multiplier = 1.1;
private const int max_island_size = 7;
///
@@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
double prevDelta = prevObj.StrainTime;
double lastDelta = lastObj.StrainTime;
- double currRatio = 1.0 + 8.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses.
+ double currRatio = 1.0 + 8.4 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses.
double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - currObj.HitWindowGreat * 0.3) / (currObj.HitWindowGreat * 0.3));
@@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
effectiveRatio *= 0.25;
if (island.IsSimilarPolarity(previousIsland, deltaDifferenceEpsilon)) // repeated island polartiy (2 -> 4, 3 -> 5)
- effectiveRatio *= 0.50;
+ effectiveRatio *= 0.35;
if (lastDelta > prevDelta + deltaDifferenceEpsilon && prevDelta > currDelta + deltaDifferenceEpsilon) // previous increase happened a note ago, 1/1->1/2-1/4, dont want to buff this.
effectiveRatio *= 0.125;
From f1adc6f98cc48024a0bb35811955fb5653c3fdf6 Mon Sep 17 00:00:00 2001
From: StanR
Date: Thu, 22 Aug 2024 15:59:13 +0500
Subject: [PATCH 086/239] Don't cap max island size, make repetition nerf more
lenient on high bpm, adjust balancing
---
.../Difficulty/Evaluators/RhythmEvaluator.cs | 14 +++++---------
1 file changed, 5 insertions(+), 9 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
index d2f58925cd..9ffb4fc3d7 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
@@ -60,8 +60,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
}
private const int history_time_max = 5 * 1000; // 5 seconds of calculatingRhythmBonus max.
- private const double rhythm_multiplier = 1.1;
- private const int max_island_size = 7;
+ private const double rhythm_multiplier = 1.05;
///
/// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current .
@@ -103,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
double prevDelta = prevObj.StrainTime;
double lastDelta = lastObj.StrainTime;
- double currRatio = 1.0 + 8.4 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses.
+ double currRatio = 1.0 + 8.8 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses.
double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - currObj.HitWindowGreat * 0.3) / (currObj.HitWindowGreat * 0.3));
@@ -117,11 +116,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
{
if (!(Math.Abs(prevDelta - currDelta) > deltaDifferenceEpsilon))
{
- if (island.Deltas.Count < max_island_size)
- {
- // island is still progressing
- island.AddDelta((int)currDelta, deltaDifferenceEpsilon);
- }
+ // island is still progressing
+ island.AddDelta((int)currDelta, deltaDifferenceEpsilon);
}
else
{
@@ -143,7 +139,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
// repeated island (ex: triplet -> triplet)
double power = logistic(island.AverageDelta(), 4, 0.165, 10);
- effectiveRatio *= Math.Min(1.0 / islandCounts[island], Math.Pow(1.0 / islandCounts[island], power));
+ effectiveRatio *= Math.Min(2.0 / islandCounts[island], Math.Pow(1.0 / islandCounts[island], power));
}
rhythmComplexitySum += Math.Sqrt(effectiveRatio * startRatio) * currHistoricalDecay;
From ce8286d299f11a9e7c96baf67ca38d5a143239d5 Mon Sep 17 00:00:00 2001
From: StanR
Date: Sat, 24 Aug 2024 04:37:58 +0500
Subject: [PATCH 087/239] Scale difficulty with doubletapness, make kicksliders
not reduce the difficulty of the next object, adjust balancing
---
.../Difficulty/Evaluators/RhythmEvaluator.cs | 28 +++++++++++++------
.../Difficulty/Evaluators/SpeedEvaluator.cs | 14 +---------
.../Preprocessing/OsuDifficultyHitObject.cs | 18 ++++++++++++
3 files changed, 38 insertions(+), 22 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
index 9ffb4fc3d7..22b330bcac 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
@@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
}
private const int history_time_max = 5 * 1000; // 5 seconds of calculatingRhythmBonus max.
- private const double rhythm_multiplier = 1.05;
+ private const double rhythm_multiplier = 0.95;
///
/// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current .
@@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
double prevDelta = prevObj.StrainTime;
double lastDelta = lastObj.StrainTime;
- double currRatio = 1.0 + 8.8 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses.
+ double currRatio = 1.0 + 9.8 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses.
double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - currObj.HitWindowGreat * 0.3) / (currObj.HitWindowGreat * 0.3));
@@ -121,16 +121,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
}
else
{
- if (currObj.BaseObject is Slider) // bpm change is into slider, this is easy acc window
+ // bpm change is into slider, this is easy acc window
+ if (currObj.BaseObject is Slider)
effectiveRatio *= 0.125;
- if (prevObj.BaseObject is Slider) // bpm change was from a slider, this is easier typically than circle -> circle
- effectiveRatio *= 0.25;
+ // bpm change was from a slider, this is easier typically than circle -> circle
+ // unintentional side effect is that bursts with kicksliders at the ends might have lower difficulty than bursts without sliders
+ // therefore we're checking for quick sliders and don't lower the difficulty for them since they don't really make tapping easier (no time to adjust)
+ if (prevObj.BaseObject is Slider && prevObj.TravelTime > prevDelta * 1.5)
+ effectiveRatio *= 0.15;
- if (island.IsSimilarPolarity(previousIsland, deltaDifferenceEpsilon)) // repeated island polartiy (2 -> 4, 3 -> 5)
- effectiveRatio *= 0.35;
+ // repeated island polartiy (2 -> 4, 3 -> 5)
+ if (island.IsSimilarPolarity(previousIsland, deltaDifferenceEpsilon))
+ effectiveRatio *= 0.3;
- if (lastDelta > prevDelta + deltaDifferenceEpsilon && prevDelta > currDelta + deltaDifferenceEpsilon) // previous increase happened a note ago, 1/1->1/2-1/4, dont want to buff this.
+ // previous increase happened a note ago, 1/1->1/2-1/4, dont want to buff this.
+ if (lastDelta > prevDelta + deltaDifferenceEpsilon && prevDelta > currDelta + deltaDifferenceEpsilon)
effectiveRatio *= 0.125;
if (!islandCounts.TryAdd(island, 1))
@@ -142,6 +148,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
effectiveRatio *= Math.Min(2.0 / islandCounts[island], Math.Pow(1.0 / islandCounts[island], power));
}
+ // scale down the difficulty if the object is doubletappable
+ double doubletapness = prevObj.GetDoubletapness((OsuDifficultyHitObject?)prevObj.Next(0));
+ effectiveRatio *= 1 - doubletapness * 0.75;
+
rhythmComplexitySum += Math.Sqrt(effectiveRatio * startRatio) * currHistoricalDecay;
startRatio = effectiveRatio;
@@ -167,7 +177,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
prevObj = currObj;
}
- return Math.Sqrt(4 + rhythmComplexitySum * rhythm_multiplier) / 2; //produces multiplier that can be applied to strain. range [1, infinity) (not really though)
+ return Math.Sqrt(4 + rhythmComplexitySum * rhythm_multiplier) / 2.0; //produces multiplier that can be applied to strain. range [1, infinity) (not really though)
}
private static double logistic(double x, double maxValue, double multiplier, double offset) => (maxValue / (1 + Math.Pow(Math.E, offset - (multiplier * x))));
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs
index 2df383aaa8..3a264188f6 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs
@@ -30,21 +30,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
// derive strainTime for calculation
var osuCurrObj = (OsuDifficultyHitObject)current;
var osuPrevObj = current.Index > 0 ? (OsuDifficultyHitObject)current.Previous(0) : null;
- var osuNextObj = (OsuDifficultyHitObject?)current.Next(0);
double strainTime = osuCurrObj.StrainTime;
- double doubletapness = 1;
-
- // Nerf doubletappable doubles.
- if (osuNextObj != null)
- {
- double currDeltaTime = Math.Max(1, osuCurrObj.DeltaTime);
- double nextDeltaTime = Math.Max(1, osuNextObj.DeltaTime);
- double deltaDifference = Math.Abs(nextDeltaTime - currDeltaTime);
- double speedRatio = currDeltaTime / Math.Max(currDeltaTime, deltaDifference);
- double windowRatio = Math.Pow(Math.Min(1, currDeltaTime / osuCurrObj.HitWindowGreat), 2);
- doubletapness = Math.Pow(speedRatio, 1 - windowRatio);
- }
+ double doubletapness = 1.0 - osuCurrObj.GetDoubletapness((OsuDifficultyHitObject?)osuCurrObj.Next(0));
// Cap deltatime to the OD 300 hitwindow.
// 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly, whilst 0.92 limits the effect of the cap.
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
index 95535274c1..3eaf500ad7 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
@@ -137,6 +137,24 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
return Math.Clamp((time - fadeInStartTime) / fadeInDuration, 0.0, 1.0);
}
+ ///
+ /// Returns how possible is it to doubletap this object together with the next one and get perfect judgement in range from 0 to 1
+ ///
+ public double GetDoubletapness(OsuDifficultyHitObject? osuNextObj)
+ {
+ if (osuNextObj != null)
+ {
+ double currDeltaTime = Math.Max(1, DeltaTime);
+ double nextDeltaTime = Math.Max(1, osuNextObj.DeltaTime);
+ double deltaDifference = Math.Abs(nextDeltaTime - currDeltaTime);
+ double speedRatio = currDeltaTime / Math.Max(currDeltaTime, deltaDifference);
+ double windowRatio = Math.Pow(Math.Min(1, currDeltaTime / HitWindowGreat), 2);
+ return 1.0 - Math.Pow(speedRatio, 1 - windowRatio);
+ }
+
+ return 0;
+ }
+
private void setDistances(double clockRate)
{
if (BaseObject is Slider currentSlider)
From ed45c947fca29fd07439e1004ce17ec3069f9021 Mon Sep 17 00:00:00 2001
From: StanR
Date: Tue, 27 Aug 2024 15:50:08 +0500
Subject: [PATCH 088/239] Adjust max history by clockrate to make rhythm
calculation more consistent between rates
---
.../Difficulty/Evaluators/RhythmEvaluator.cs | 28 ++++++++++---------
.../Difficulty/OsuDifficultyCalculator.cs | 2 +-
.../Difficulty/Skills/Speed.cs | 6 ++--
3 files changed, 20 insertions(+), 16 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
index 22b330bcac..26f50efc72 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
@@ -60,12 +60,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
}
private const int history_time_max = 5 * 1000; // 5 seconds of calculatingRhythmBonus max.
- private const double rhythm_multiplier = 0.95;
+ private const int history_objects_max = 32;
+ private const double rhythm_multiplier = 1.25;
///
/// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current .
///
- public static double EvaluateDifficultyOf(DifficultyHitObject current)
+ public static double EvaluateDifficultyOf(DifficultyHitObject current, double clockRate)
{
if (current.BaseObject is Spinner)
return 0;
@@ -76,15 +77,18 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
var previousIsland = new Island();
Dictionary islandCounts = new Dictionary();
+ int historyTimeMaxAdjusted = (int)Math.Ceiling(history_time_max / clockRate);
+ int historyObjectsMaxAdjusted = (int)Math.Ceiling(history_objects_max / clockRate);
+
double startRatio = 0; // store the ratio of the current start of an island to buff for tighter rhythms
bool firstDeltaSwitch = false;
- int historicalNoteCount = Math.Min(current.Index, 32);
+ int historicalNoteCount = Math.Min(current.Index, historyObjectsMaxAdjusted);
int rhythmStart = 0;
- while (rhythmStart < historicalNoteCount - 2 && current.StartTime - current.Previous(rhythmStart).StartTime < history_time_max)
+ while (rhythmStart < historicalNoteCount - 2 && current.StartTime - current.Previous(rhythmStart).StartTime < historyTimeMaxAdjusted)
rhythmStart++;
OsuDifficultyHitObject prevObj = (OsuDifficultyHitObject)current.Previous(rhythmStart);
@@ -94,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
{
OsuDifficultyHitObject currObj = (OsuDifficultyHitObject)current.Previous(i - 1);
- double currHistoricalDecay = (history_time_max - (current.StartTime - currObj.StartTime)) / history_time_max; // scales note 0 to 1 from history to now
+ double currHistoricalDecay = (historyTimeMaxAdjusted - (current.StartTime - currObj.StartTime)) / historyTimeMaxAdjusted; // scales note 0 to 1 from history to now
currHistoricalDecay = Math.Min((double)(historicalNoteCount - i) / historicalNoteCount, currHistoricalDecay); // either we're limited by time or limited by object count.
@@ -102,16 +106,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
double prevDelta = prevObj.StrainTime;
double lastDelta = lastObj.StrainTime;
- double currRatio = 1.0 + 9.8 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses.
-
- double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - currObj.HitWindowGreat * 0.3) / (currObj.HitWindowGreat * 0.3));
-
- windowPenalty = Math.Min(1, windowPenalty);
-
- double effectiveRatio = windowPenalty * currRatio;
+ double currRatio = 1.0 + 7.27 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses.
double deltaDifferenceEpsilon = currObj.HitWindowGreat * 0.3;
+ double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - deltaDifferenceEpsilon) / deltaDifferenceEpsilon);
+
+ double effectiveRatio = windowPenalty * currRatio;
+
if (firstDeltaSwitch)
{
if (!(Math.Abs(prevDelta - currDelta) > deltaDifferenceEpsilon))
@@ -145,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
// repeated island (ex: triplet -> triplet)
double power = logistic(island.AverageDelta(), 4, 0.165, 10);
- effectiveRatio *= Math.Min(2.0 / islandCounts[island], Math.Pow(1.0 / islandCounts[island], power));
+ effectiveRatio *= Math.Min(3.0 / islandCounts[island], Math.Pow(1.0 / islandCounts[island], power));
}
// scale down the difficulty if the object is doubletappable
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index 007cd977e5..a5ef0764bb 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
new Aim(mods, true),
new Aim(mods, false),
- new Speed(mods)
+ new Speed(mods, clockRate)
};
if (mods.Any(h => h is OsuModFlashlight))
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
index 40aac013ab..ae85980815 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
@@ -16,6 +16,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
///
public class Speed : OsuStrainSkill
{
+ private readonly double clockRate;
private double skillMultiplier => 1375;
private double strainDecayBase => 0.3;
@@ -27,9 +28,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
private readonly List objectStrains = new List();
- public Speed(Mod[] mods)
+ public Speed(Mod[] mods, double clockRate)
: base(mods)
{
+ this.clockRate = clockRate;
}
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
@@ -41,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
currentStrain *= strainDecay(((OsuDifficultyHitObject)current).StrainTime);
currentStrain += SpeedEvaluator.EvaluateDifficultyOf(current) * skillMultiplier;
- currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current);
+ currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current, clockRate);
double totalStrain = currentStrain * currentRhythm;
From 7fda8bc95b8092124820306cab26561e66cda8fe Mon Sep 17 00:00:00 2001
From: StanR
Date: Tue, 27 Aug 2024 23:48:15 +0500
Subject: [PATCH 089/239] Reduce repetition nerf for non-consecutive islands
---
.../Difficulty/Evaluators/RhythmEvaluator.cs | 37 +++++++++++--------
1 file changed, 22 insertions(+), 15 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
index 26f50efc72..9e851f11a6 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
@@ -14,19 +14,24 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
{
private readonly struct Island : IEquatable
{
- public Island()
+ private readonly double deltaDifferenceEpsilon;
+
+ public Island(double epsilon)
{
+ deltaDifferenceEpsilon = epsilon;
}
public Island(int firstDelta, double epsilon)
{
- AddDelta(firstDelta, epsilon);
+ deltaDifferenceEpsilon = epsilon;
+ AddDelta(firstDelta);
}
public List Deltas { get; } = new List();
- public void AddDelta(int delta, double epsilon)
+ public void AddDelta(int delta)
{
+ double epsilon = deltaDifferenceEpsilon;
int existingDelta = Deltas.FirstOrDefault(x => Math.Abs(x - delta) >= epsilon);
Deltas.Add(existingDelta == default ? delta : existingDelta);
@@ -34,10 +39,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
public double AverageDelta() => Deltas.Count > 0 ? Math.Max(Deltas.Average(), OsuDifficultyHitObject.MIN_DELTA_TIME) : 0;
- public bool IsSimilarPolarity(Island other, double epsilon)
+ public bool IsSimilarPolarity(Island other)
{
// consider islands to be of similar polarity only if they're having the same average delta (we don't want to consider 3 singletaps similar to a triple)
- return Math.Abs(AverageDelta() - other.AverageDelta()) < epsilon &&
+ return Math.Abs(AverageDelta() - other.AverageDelta()) < deltaDifferenceEpsilon &&
Deltas.Count % 2 == other.Deltas.Count % 2;
}
@@ -61,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
private const int history_time_max = 5 * 1000; // 5 seconds of calculatingRhythmBonus max.
private const int history_objects_max = 32;
- private const double rhythm_multiplier = 1.25;
+ private const double rhythm_multiplier = 1.2;
///
/// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current .
@@ -73,8 +78,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
double rhythmComplexitySum = 0;
- var island = new Island();
- var previousIsland = new Island();
+ double deltaDifferenceEpsilon = ((OsuDifficultyHitObject)current).HitWindowGreat * 0.3;
+
+ var island = new Island(deltaDifferenceEpsilon);
+ var previousIsland = new Island(deltaDifferenceEpsilon);
Dictionary islandCounts = new Dictionary();
int historyTimeMaxAdjusted = (int)Math.Ceiling(history_time_max / clockRate);
@@ -106,9 +113,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
double prevDelta = prevObj.StrainTime;
double lastDelta = lastObj.StrainTime;
- double currRatio = 1.0 + 7.27 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses.
-
- double deltaDifferenceEpsilon = currObj.HitWindowGreat * 0.3;
+ double currRatio = 1.0 + 5.8 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses.
double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - deltaDifferenceEpsilon) / deltaDifferenceEpsilon);
@@ -119,7 +124,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
if (!(Math.Abs(prevDelta - currDelta) > deltaDifferenceEpsilon))
{
// island is still progressing
- island.AddDelta((int)currDelta, deltaDifferenceEpsilon);
+ island.AddDelta((int)currDelta);
}
else
{
@@ -131,10 +136,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
// unintentional side effect is that bursts with kicksliders at the ends might have lower difficulty than bursts without sliders
// therefore we're checking for quick sliders and don't lower the difficulty for them since they don't really make tapping easier (no time to adjust)
if (prevObj.BaseObject is Slider && prevObj.TravelTime > prevDelta * 1.5)
- effectiveRatio *= 0.15;
+ effectiveRatio *= 0.2;
// repeated island polartiy (2 -> 4, 3 -> 5)
- if (island.IsSimilarPolarity(previousIsland, deltaDifferenceEpsilon))
+ if (island.IsSimilarPolarity(previousIsland))
effectiveRatio *= 0.3;
// previous increase happened a note ago, 1/1->1/2-1/4, dont want to buff this.
@@ -143,7 +148,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
if (!islandCounts.TryAdd(island, 1))
{
- islandCounts[island]++;
+ // only add island to island counts if they're going one after another
+ if (previousIsland.Equals(island))
+ islandCounts[island]++;
// repeated island (ex: triplet -> triplet)
double power = logistic(island.AverageDelta(), 4, 0.165, 10);
From a7e1d35f648c449e749fa1346d3de704b87fe4ce Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Wed, 11 Sep 2024 12:29:17 +0100
Subject: [PATCH 090/239] Update
osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs
Co-authored-by: James Wilson
---
osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs
index 8dfaaca7b2..a0f2a04f40 100644
--- a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs
+++ b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs
@@ -25,9 +25,9 @@ namespace osu.Game.Rulesets.Difficulty
protected const int ATTRIB_ID_SCORE_MULTIPLIER = 15;
protected const int ATTRIB_ID_FLASHLIGHT = 17;
protected const int ATTRIB_ID_SLIDER_FACTOR = 19;
- protected const int ATTRIB_ID_AIM_DIFFICULT_STRAIN_COUNT = 21;
- protected const int ATTRIB_ID_SPEED_DIFFICULT_STRAIN_COUNT = 23;
protected const int ATTRIB_ID_SPEED_NOTE_COUNT = 21;
+ protected const int ATTRIB_ID_SPEED_DIFFICULT_STRAIN_COUNT = 23;
+ protected const int ATTRIB_ID_AIM_DIFFICULT_STRAIN_COUNT = 25;
///
/// The mods which were applied to the beatmap.
From 3398497d4b7ef41db750ba2b9db048528836ba18 Mon Sep 17 00:00:00 2001
From: StanR
Date: Thu, 12 Sep 2024 01:04:07 +0500
Subject: [PATCH 091/239] Remove kickslider changes
---
osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
index 9e851f11a6..ac38bc14e1 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
@@ -134,8 +134,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
// bpm change was from a slider, this is easier typically than circle -> circle
// unintentional side effect is that bursts with kicksliders at the ends might have lower difficulty than bursts without sliders
- // therefore we're checking for quick sliders and don't lower the difficulty for them since they don't really make tapping easier (no time to adjust)
- if (prevObj.BaseObject is Slider && prevObj.TravelTime > prevDelta * 1.5)
+ if (prevObj.BaseObject is Slider)
effectiveRatio *= 0.2;
// repeated island polartiy (2 -> 4, 3 -> 5)
From 1292a34b9d9ab0d932c6d56976a1e3b38f789b8d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bart=C5=82omiej=20Dach?=
Date: Fri, 13 Sep 2024 10:53:43 +0200
Subject: [PATCH 092/239] Add failing test coverage for object nudging
---
.../Editing/TestSceneComposerSelection.cs | 48 ++++++++++++++++++-
1 file changed, 47 insertions(+), 1 deletion(-)
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs
index 3884a3108f..ff93a8d83f 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs
@@ -7,6 +7,7 @@ using NUnit.Framework;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
+using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets;
@@ -78,7 +79,7 @@ namespace osu.Game.Tests.Visual.Editing
}
[Test]
- public void TestNudgeSelection()
+ public void TestNudgeSelectionTime()
{
HitCircle[] addedObjects = null!;
@@ -99,6 +100,51 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("objects reverted to original position", () => addedObjects[0].StartTime == 100);
}
+ [Test]
+ public void TestNudgeSelectionPosition()
+ {
+ HitCircle addedObject = null!;
+
+ AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[]
+ {
+ addedObject = new HitCircle { StartTime = 200, Position = new Vector2(100) },
+ }));
+
+ AddStep("select object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject));
+
+ AddStep("nudge up", () =>
+ {
+ InputManager.PressKey(Key.ControlLeft);
+ InputManager.Key(Key.Up);
+ InputManager.ReleaseKey(Key.ControlLeft);
+ });
+ AddAssert("object position moved up", () => addedObject.Position.Y, () => Is.EqualTo(99).Within(Precision.FLOAT_EPSILON));
+
+ AddStep("nudge down", () =>
+ {
+ InputManager.PressKey(Key.ControlLeft);
+ InputManager.Key(Key.Down);
+ InputManager.ReleaseKey(Key.ControlLeft);
+ });
+ AddAssert("object position moved down", () => addedObject.Position.Y, () => Is.EqualTo(100).Within(Precision.FLOAT_EPSILON));
+
+ AddStep("nudge left", () =>
+ {
+ InputManager.PressKey(Key.ControlLeft);
+ InputManager.Key(Key.Left);
+ InputManager.ReleaseKey(Key.ControlLeft);
+ });
+ AddAssert("object position moved left", () => addedObject.Position.X, () => Is.EqualTo(99).Within(Precision.FLOAT_EPSILON));
+
+ AddStep("nudge right", () =>
+ {
+ InputManager.PressKey(Key.ControlLeft);
+ InputManager.Key(Key.Right);
+ InputManager.ReleaseKey(Key.ControlLeft);
+ });
+ AddAssert("object position moved right", () => addedObject.Position.X, () => Is.EqualTo(100).Within(Precision.FLOAT_EPSILON));
+ }
+
[Test]
public void TestRotateHotkeys()
{
From 2ccb4a48eb68600559e0fbdd58712ab726211927 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bart=C5=82omiej=20Dach?=
Date: Fri, 13 Sep 2024 11:18:50 +0200
Subject: [PATCH 093/239] Add test coverage for seeking between objects in
editor
---
.../Visual/Editing/TestSceneEditorSeeking.cs | 39 ++++++++++++++++++-
1 file changed, 37 insertions(+), 2 deletions(-)
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSeeking.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSeeking.cs
index da4f159cae..06facc546d 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSeeking.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSeeking.cs
@@ -1,11 +1,13 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Linq;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Objects;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Editing
@@ -135,9 +137,42 @@ namespace osu.Game.Tests.Visual.Editing
pressAndCheckTime(Key.Up, 0);
}
- private void pressAndCheckTime(Key key, double expectedTime)
+ [Test]
+ public void TestSeekBetweenObjects()
{
- AddStep($"press {key}", () => InputManager.Key(key));
+ AddStep("add objects", () =>
+ {
+ EditorBeatmap.Clear();
+ EditorBeatmap.AddRange(new[]
+ {
+ new HitCircle { StartTime = 1000, },
+ new HitCircle { StartTime = 2250, },
+ new HitCircle { StartTime = 3600, },
+ });
+ });
+ AddStep("seek to 0", () => EditorClock.Seek(0));
+
+ pressAndCheckTime(Key.Right, 1000, Key.ControlLeft);
+ pressAndCheckTime(Key.Right, 2250, Key.ControlLeft);
+ pressAndCheckTime(Key.Right, 3600, Key.ControlLeft);
+ pressAndCheckTime(Key.Right, 3600, Key.ControlLeft);
+ pressAndCheckTime(Key.Left, 2250, Key.ControlLeft);
+ pressAndCheckTime(Key.Left, 1000, Key.ControlLeft);
+ pressAndCheckTime(Key.Left, 1000, Key.ControlLeft);
+ }
+
+ private void pressAndCheckTime(Key key, double expectedTime, params Key[] modifiers)
+ {
+ AddStep($"press {key} with {(modifiers.Any() ? string.Join(',', modifiers) : "no modifiers")}", () =>
+ {
+ foreach (var modifier in modifiers)
+ InputManager.PressKey(modifier);
+
+ InputManager.Key(key);
+
+ foreach (var modifier in modifiers)
+ InputManager.ReleaseKey(modifier);
+ });
AddUntilStep($"time is {expectedTime}", () => EditorClock.CurrentTime, () => Is.EqualTo(expectedTime).Within(1));
}
}
From 7f71ef4547981d9d3d432264087fa6a5813cbc46 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bart=C5=82omiej=20Dach?=
Date: Fri, 13 Sep 2024 15:14:09 +0200
Subject: [PATCH 094/239] Only allow seek to next/previous object via
keybinding if there is no selection
---
osu.Game/Screens/Edit/Editor.cs | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index 9bb91af806..a3549fdec5 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -721,10 +721,16 @@ namespace osu.Game.Screens.Edit
switch (e.Action)
{
case GlobalAction.EditorSeekToPreviousHitObject:
+ if (editorBeatmap.SelectedHitObjects.Any())
+ return false;
+
seekHitObject(-1);
return true;
case GlobalAction.EditorSeekToNextHitObject:
+ if (editorBeatmap.SelectedHitObjects.Any())
+ return false;
+
seekHitObject(1);
return true;
From 5e8174faa881d5a54398ddd4c0226f9a18e40f92 Mon Sep 17 00:00:00 2001
From: StanR
Date: Sat, 14 Sep 2024 22:03:01 +0500
Subject: [PATCH 095/239] Reduce ratio for island size 1
---
.../Difficulty/Evaluators/RhythmEvaluator.cs | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
index ac38bc14e1..bd99799ee0 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
@@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
private const int history_time_max = 5 * 1000; // 5 seconds of calculatingRhythmBonus max.
private const int history_objects_max = 32;
- private const double rhythm_multiplier = 1.2;
+ private const double rhythm_multiplier = 1.3;
///
/// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current .
@@ -113,7 +113,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
double prevDelta = prevObj.StrainTime;
double lastDelta = lastObj.StrainTime;
- double currRatio = 1.0 + 5.8 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses.
+ double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses.
double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - deltaDifferenceEpsilon) / deltaDifferenceEpsilon);
@@ -137,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
if (prevObj.BaseObject is Slider)
effectiveRatio *= 0.2;
- // repeated island polartiy (2 -> 4, 3 -> 5)
+ // repeated island polarity (2 -> 4, 3 -> 5)
if (island.IsSimilarPolarity(previousIsland))
effectiveRatio *= 0.3;
@@ -145,6 +145,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
if (lastDelta > prevDelta + deltaDifferenceEpsilon && prevDelta > currDelta + deltaDifferenceEpsilon)
effectiveRatio *= 0.125;
+ // singletaps are easier to control
+ if (island.Deltas.Count == 1)
+ effectiveRatio *= 0.7;
+
if (!islandCounts.TryAdd(island, 1))
{
// only add island to island counts if they're going one after another
From db626f168ad294b90ea0fbb989b0fc681557b35c Mon Sep 17 00:00:00 2001
From: StanR
Date: Sun, 15 Sep 2024 01:12:41 +0500
Subject: [PATCH 096/239] Refactor
---
.../Difficulty/Evaluators/RhythmEvaluator.cs | 40 +++++++++----------
1 file changed, 20 insertions(+), 20 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
index bd99799ee0..fb1374eccf 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
@@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Objects;
@@ -12,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
{
public static class RhythmEvaluator
{
- private readonly struct Island : IEquatable
+ private struct Island : IEquatable
{
private readonly double deltaDifferenceEpsilon;
@@ -21,41 +20,39 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
deltaDifferenceEpsilon = epsilon;
}
- public Island(int firstDelta, double epsilon)
+ public Island(int delta, double epsilon)
{
deltaDifferenceEpsilon = epsilon;
- AddDelta(firstDelta);
+ Delta = Math.Max(delta, OsuDifficultyHitObject.MIN_DELTA_TIME);
}
- public List Deltas { get; } = new List();
+ public int Delta { get; private set; }
+ public int DeltaCount { get; private set; }
public void AddDelta(int delta)
{
- double epsilon = deltaDifferenceEpsilon;
- int existingDelta = Deltas.FirstOrDefault(x => Math.Abs(x - delta) >= epsilon);
+ if (Delta == default)
+ Delta = Math.Max(delta, OsuDifficultyHitObject.MIN_DELTA_TIME);
- Deltas.Add(existingDelta == default ? delta : existingDelta);
+ DeltaCount++;
}
- public double AverageDelta() => Deltas.Count > 0 ? Math.Max(Deltas.Average(), OsuDifficultyHitObject.MIN_DELTA_TIME) : 0;
-
public bool IsSimilarPolarity(Island other)
{
// consider islands to be of similar polarity only if they're having the same average delta (we don't want to consider 3 singletaps similar to a triple)
- return Math.Abs(AverageDelta() - other.AverageDelta()) < deltaDifferenceEpsilon &&
- Deltas.Count % 2 == other.Deltas.Count % 2;
+ return DeltaCount % 2 == other.DeltaCount % 2 &&
+ Math.Abs(Delta - other.Delta) < deltaDifferenceEpsilon;
}
public override int GetHashCode()
{
- // we need to compare all deltas and they must be in the exact same order we added them
- string joinedDeltas = string.Join(string.Empty, Deltas);
- return joinedDeltas.GetHashCode();
+ return HashCode.Combine(Delta, DeltaCount);
}
public bool Equals(Island other)
{
- return other.GetHashCode() == GetHashCode();
+ return Math.Abs(Delta - other.Delta) < deltaDifferenceEpsilon &&
+ DeltaCount == other.DeltaCount;
}
public override bool Equals(object? obj)
@@ -113,7 +110,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
double prevDelta = prevObj.StrainTime;
double lastDelta = lastObj.StrainTime;
- double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses.
+ // calculate how much current delta difference deserves a rhythm bonus
+ // this function is meant to reduce rhythm bonus for deltas that are multiples of each other (i.e 100 and 200)
+ double deltaDifferenceRatio = Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta);
+ double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / deltaDifferenceRatio), 2));
double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - deltaDifferenceEpsilon) / deltaDifferenceEpsilon);
@@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
if (firstDeltaSwitch)
{
- if (!(Math.Abs(prevDelta - currDelta) > deltaDifferenceEpsilon))
+ if (Math.Abs(prevDelta - currDelta) < deltaDifferenceEpsilon)
{
// island is still progressing
island.AddDelta((int)currDelta);
@@ -146,7 +146,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
effectiveRatio *= 0.125;
// singletaps are easier to control
- if (island.Deltas.Count == 1)
+ if (island.DeltaCount == 1)
effectiveRatio *= 0.7;
if (!islandCounts.TryAdd(island, 1))
@@ -156,7 +156,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
islandCounts[island]++;
// repeated island (ex: triplet -> triplet)
- double power = logistic(island.AverageDelta(), 4, 0.165, 10);
+ double power = logistic(island.Delta, 4, 0.165, 10);
effectiveRatio *= Math.Min(3.0 / islandCounts[island], Math.Pow(1.0 / islandCounts[island], power));
}
From 738d4bcb804e2641a73a774cdd6d329cae91a054 Mon Sep 17 00:00:00 2001
From: StanR
Date: Sun, 15 Sep 2024 01:49:52 +0500
Subject: [PATCH 097/239] Reduce ratio if we're starting island counting after
a slider
---
.../Difficulty/Evaluators/RhythmEvaluator.cs | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
index fb1374eccf..5732d924f9 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
@@ -98,6 +98,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
OsuDifficultyHitObject prevObj = (OsuDifficultyHitObject)current.Previous(rhythmStart);
OsuDifficultyHitObject lastObj = (OsuDifficultyHitObject)current.Previous(rhythmStart + 1);
+ // we go from the furthest object back to the current one
for (int i = rhythmStart; i > 0; i--)
{
OsuDifficultyHitObject currObj = (OsuDifficultyHitObject)current.Previous(i - 1);
@@ -171,15 +172,20 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
previousIsland = island;
if (prevDelta + deltaDifferenceEpsilon < currDelta) // we're slowing down, stop counting
- firstDeltaSwitch = false; // if we're speeding up, this stays true and we keep counting island size.
+ firstDeltaSwitch = false; // if we're speeding up, this stays true and we keep counting island size.
island = new Island((int)currDelta, deltaDifferenceEpsilon);
}
}
- else if (prevDelta > currDelta + deltaDifferenceEpsilon) // we want to be speeding up.
+ else if (prevDelta > currDelta + deltaDifferenceEpsilon) // we're speeding up
{
// Begin counting island until we change speed again.
firstDeltaSwitch = true;
+
+ // reduce ratio if we're starting after a slider
+ if (prevObj.BaseObject is Slider)
+ effectiveRatio *= 0.3;
+
startRatio = effectiveRatio;
island = new Island((int)currDelta, deltaDifferenceEpsilon);
From 863a74f9803f787872ddfec69c903d7e81be7d27 Mon Sep 17 00:00:00 2001
From: StanR
Date: Sun, 15 Sep 2024 02:08:37 +0500
Subject: [PATCH 098/239] Balancing
---
.../Difficulty/Evaluators/RhythmEvaluator.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
index 5732d924f9..c01530194b 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
@@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
private const int history_time_max = 5 * 1000; // 5 seconds of calculatingRhythmBonus max.
private const int history_objects_max = 32;
- private const double rhythm_multiplier = 1.3;
+ private const double rhythm_multiplier = 1.4;
///
/// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current .
@@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
// calculate how much current delta difference deserves a rhythm bonus
// this function is meant to reduce rhythm bonus for deltas that are multiples of each other (i.e 100 and 200)
double deltaDifferenceRatio = Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta);
- double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / deltaDifferenceRatio), 2));
+ double currRatio = 1.0 + 5.5 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / deltaDifferenceRatio), 2));
double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - deltaDifferenceEpsilon) / deltaDifferenceEpsilon);
From 145731bdefe9b35fc89a685bbd1eca76c537d850 Mon Sep 17 00:00:00 2001
From: StanR
Date: Sun, 15 Sep 2024 02:48:41 +0500
Subject: [PATCH 099/239] More balancing
---
.../Difficulty/Evaluators/RhythmEvaluator.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
index c01530194b..c4d78c7904 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
@@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
private const int history_time_max = 5 * 1000; // 5 seconds of calculatingRhythmBonus max.
private const int history_objects_max = 32;
- private const double rhythm_multiplier = 1.4;
+ private const double rhythm_multiplier = 1.32;
///
/// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current .
@@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
// calculate how much current delta difference deserves a rhythm bonus
// this function is meant to reduce rhythm bonus for deltas that are multiples of each other (i.e 100 and 200)
double deltaDifferenceRatio = Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta);
- double currRatio = 1.0 + 5.5 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / deltaDifferenceRatio), 2));
+ double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / deltaDifferenceRatio), 2));
double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - deltaDifferenceEpsilon) / deltaDifferenceEpsilon);
From bee18b03e73ade94c43c8657ae9fa7d81887a06f Mon Sep 17 00:00:00 2001
From: StanR
Date: Mon, 16 Sep 2024 00:49:36 +0500
Subject: [PATCH 100/239] Reduce history max overall instead of using clock
rate
---
.../Difficulty/Evaluators/RhythmEvaluator.cs | 15 ++++++---------
.../Difficulty/OsuDifficultyCalculator.cs | 2 +-
osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 6 ++----
3 files changed, 9 insertions(+), 14 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
index c4d78c7904..09e9011596 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
@@ -61,14 +61,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
}
}
- private const int history_time_max = 5 * 1000; // 5 seconds of calculatingRhythmBonus max.
- private const int history_objects_max = 32;
+ private const int history_time_max = 4 * 1000; // 5 seconds of calculatingRhythmBonus max.
+ private const int history_objects_max = 24;
private const double rhythm_multiplier = 1.32;
///
/// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current .
///
- public static double EvaluateDifficultyOf(DifficultyHitObject current, double clockRate)
+ public static double EvaluateDifficultyOf(DifficultyHitObject current)
{
if (current.BaseObject is Spinner)
return 0;
@@ -81,18 +81,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
var previousIsland = new Island(deltaDifferenceEpsilon);
Dictionary islandCounts = new Dictionary();
- int historyTimeMaxAdjusted = (int)Math.Ceiling(history_time_max / clockRate);
- int historyObjectsMaxAdjusted = (int)Math.Ceiling(history_objects_max / clockRate);
-
double startRatio = 0; // store the ratio of the current start of an island to buff for tighter rhythms
bool firstDeltaSwitch = false;
- int historicalNoteCount = Math.Min(current.Index, historyObjectsMaxAdjusted);
+ int historicalNoteCount = Math.Min(current.Index, history_objects_max);
int rhythmStart = 0;
- while (rhythmStart < historicalNoteCount - 2 && current.StartTime - current.Previous(rhythmStart).StartTime < historyTimeMaxAdjusted)
+ while (rhythmStart < historicalNoteCount - 2 && current.StartTime - current.Previous(rhythmStart).StartTime < history_time_max)
rhythmStart++;
OsuDifficultyHitObject prevObj = (OsuDifficultyHitObject)current.Previous(rhythmStart);
@@ -103,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
{
OsuDifficultyHitObject currObj = (OsuDifficultyHitObject)current.Previous(i - 1);
- double currHistoricalDecay = (historyTimeMaxAdjusted - (current.StartTime - currObj.StartTime)) / historyTimeMaxAdjusted; // scales note 0 to 1 from history to now
+ double currHistoricalDecay = (history_time_max - (current.StartTime - currObj.StartTime)) / history_time_max; // scales note 0 to 1 from history to now
currHistoricalDecay = Math.Min((double)(historicalNoteCount - i) / historicalNoteCount, currHistoricalDecay); // either we're limited by time or limited by object count.
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index 835f4ee196..e93475ecff 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
new Aim(mods, true),
new Aim(mods, false),
- new Speed(mods, clockRate)
+ new Speed(mods)
};
if (mods.Any(h => h is OsuModFlashlight))
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
index d0586662ff..f7f081b7ea 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
@@ -17,7 +17,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
public class Speed : OsuStrainSkill
{
private double skillMultiplier => 1.375;
- private readonly double clockRate;
private double strainDecayBase => 0.3;
private double currentStrain;
@@ -28,10 +27,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
private readonly List objectStrains = new List();
- public Speed(Mod[] mods, double clockRate)
+ public Speed(Mod[] mods)
: base(mods)
{
- this.clockRate = clockRate;
}
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
@@ -43,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
currentStrain *= strainDecay(((OsuDifficultyHitObject)current).StrainTime);
currentStrain += SpeedEvaluator.EvaluateDifficultyOf(current) * skillMultiplier;
- currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current, clockRate);
+ currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current);
double totalStrain = currentStrain * currentRhythm;
From c9ce7d29e66f8b25d4d7fad371cb78f6b88121bc Mon Sep 17 00:00:00 2001
From: StanR
Date: Mon, 16 Sep 2024 01:04:46 +0500
Subject: [PATCH 101/239] Adjust multipliers
---
.../Difficulty/Evaluators/RhythmEvaluator.cs | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
index 09e9011596..9eae3c7a10 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
@@ -63,7 +63,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
private const int history_time_max = 4 * 1000; // 5 seconds of calculatingRhythmBonus max.
private const int history_objects_max = 24;
- private const double rhythm_multiplier = 1.32;
+ private const double rhythm_overall_multiplier = 1.2;
+ private const double rhythm_ratio_multiplier = 10.0;
///
/// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current .
@@ -111,7 +112,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
// calculate how much current delta difference deserves a rhythm bonus
// this function is meant to reduce rhythm bonus for deltas that are multiples of each other (i.e 100 and 200)
double deltaDifferenceRatio = Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta);
- double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / deltaDifferenceRatio), 2));
+ double currRatio = 1.0 + rhythm_ratio_multiplier * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / deltaDifferenceRatio), 2));
double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - deltaDifferenceEpsilon) / deltaDifferenceEpsilon);
@@ -192,7 +193,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
prevObj = currObj;
}
- return Math.Sqrt(4 + rhythmComplexitySum * rhythm_multiplier) / 2.0; //produces multiplier that can be applied to strain. range [1, infinity) (not really though)
+ return Math.Sqrt(4 + rhythmComplexitySum * rhythm_overall_multiplier) / 2.0; // produces multiplier that can be applied to strain. range [1, infinity) (not really though)
}
private static double logistic(double x, double maxValue, double multiplier, double offset) => (maxValue / (1 + Math.Pow(Math.E, offset - (multiplier * x))));
From 0bad5e468455ef2667015a0c0fea148143459d6d Mon Sep 17 00:00:00 2001
From: StanR
Date: Thu, 19 Sep 2024 04:38:01 +0500
Subject: [PATCH 102/239] Move slider-related ratio multiplier out of the delta
switch block, add nerf for ratios with delta difference fractions that are
too big, adjust consts
---
.../Difficulty/Evaluators/RhythmEvaluator.cs | 32 +++++++++----------
1 file changed, 16 insertions(+), 16 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
index 9eae3c7a10..80d75d5e67 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
@@ -63,8 +63,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
private const int history_time_max = 4 * 1000; // 5 seconds of calculatingRhythmBonus max.
private const int history_objects_max = 24;
- private const double rhythm_overall_multiplier = 1.2;
- private const double rhythm_ratio_multiplier = 10.0;
+ private const double rhythm_overall_multiplier = 1.25;
+ private const double rhythm_ratio_multiplier = 11.0;
///
/// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current .
@@ -114,9 +114,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
double deltaDifferenceRatio = Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta);
double currRatio = 1.0 + rhythm_ratio_multiplier * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / deltaDifferenceRatio), 2));
+ // reduce ratio bonus if delta difference is too big
+ double fraction = Math.Max(prevDelta / currDelta, currDelta / prevDelta);
+ double fractionMultiplier = Math.Clamp(2.0 - fraction / 8.0, 0.0, 1.0);
+
double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - deltaDifferenceEpsilon) / deltaDifferenceEpsilon);
- double effectiveRatio = windowPenalty * currRatio;
+ double effectiveRatio = windowPenalty * currRatio * fractionMultiplier;
+
+ // bpm change is into slider, this is easy acc window
+ if (currObj.BaseObject is Slider)
+ effectiveRatio *= 0.125;
+
+ // bpm change was from a slider, this is easier typically than circle -> circle
+ // unintentional side effect is that bursts with kicksliders at the ends might have lower difficulty than bursts without sliders
+ if (prevObj.BaseObject is Slider)
+ effectiveRatio *= 0.2;
if (firstDeltaSwitch)
{
@@ -127,15 +140,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
}
else
{
- // bpm change is into slider, this is easy acc window
- if (currObj.BaseObject is Slider)
- effectiveRatio *= 0.125;
-
- // bpm change was from a slider, this is easier typically than circle -> circle
- // unintentional side effect is that bursts with kicksliders at the ends might have lower difficulty than bursts without sliders
- if (prevObj.BaseObject is Slider)
- effectiveRatio *= 0.2;
-
// repeated island polarity (2 -> 4, 3 -> 5)
if (island.IsSimilarPolarity(previousIsland))
effectiveRatio *= 0.3;
@@ -180,10 +184,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
// Begin counting island until we change speed again.
firstDeltaSwitch = true;
- // reduce ratio if we're starting after a slider
- if (prevObj.BaseObject is Slider)
- effectiveRatio *= 0.3;
-
startRatio = effectiveRatio;
island = new Island((int)currDelta, deltaDifferenceEpsilon);
From 732a114b9594d2a861709591cd74d3438afdc375 Mon Sep 17 00:00:00 2001
From: StanR
Date: Thu, 19 Sep 2024 15:53:18 +0500
Subject: [PATCH 103/239] Slight refactoring
---
.../Difficulty/Evaluators/RhythmEvaluator.cs | 108 +++++++++---------
1 file changed, 55 insertions(+), 53 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
index 80d75d5e67..87fc35adcd 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
@@ -11,57 +11,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
{
public static class RhythmEvaluator
{
- private struct Island : IEquatable
- {
- private readonly double deltaDifferenceEpsilon;
-
- public Island(double epsilon)
- {
- deltaDifferenceEpsilon = epsilon;
- }
-
- public Island(int delta, double epsilon)
- {
- deltaDifferenceEpsilon = epsilon;
- Delta = Math.Max(delta, OsuDifficultyHitObject.MIN_DELTA_TIME);
- }
-
- public int Delta { get; private set; }
- public int DeltaCount { get; private set; }
-
- public void AddDelta(int delta)
- {
- if (Delta == default)
- Delta = Math.Max(delta, OsuDifficultyHitObject.MIN_DELTA_TIME);
-
- DeltaCount++;
- }
-
- public bool IsSimilarPolarity(Island other)
- {
- // consider islands to be of similar polarity only if they're having the same average delta (we don't want to consider 3 singletaps similar to a triple)
- return DeltaCount % 2 == other.DeltaCount % 2 &&
- Math.Abs(Delta - other.Delta) < deltaDifferenceEpsilon;
- }
-
- public override int GetHashCode()
- {
- return HashCode.Combine(Delta, DeltaCount);
- }
-
- public bool Equals(Island other)
- {
- return Math.Abs(Delta - other.Delta) < deltaDifferenceEpsilon &&
- DeltaCount == other.DeltaCount;
- }
-
- public override bool Equals(object? obj)
- {
- return obj?.GetHashCode() == GetHashCode();
- }
- }
-
- private const int history_time_max = 4 * 1000; // 5 seconds of calculatingRhythmBonus max.
+ private const int history_time_max = 4 * 1000; // 4 seconds
private const int history_objects_max = 24;
private const double rhythm_overall_multiplier = 1.25;
private const double rhythm_ratio_multiplier = 11.0;
@@ -101,9 +51,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
{
OsuDifficultyHitObject currObj = (OsuDifficultyHitObject)current.Previous(i - 1);
- double currHistoricalDecay = (history_time_max - (current.StartTime - currObj.StartTime)) / history_time_max; // scales note 0 to 1 from history to now
+ // scales note 0 to 1 from history to now
+ double timeDecay = (history_time_max - (current.StartTime - currObj.StartTime)) / history_time_max;
+ double noteDecay = (double)(historicalNoteCount - i) / historicalNoteCount;
- currHistoricalDecay = Math.Min((double)(historicalNoteCount - i) / historicalNoteCount, currHistoricalDecay); // either we're limited by time or limited by object count.
+ double currHistoricalDecay = Math.Min(noteDecay, timeDecay); // either we're limited by time or limited by object count.
double currDelta = currObj.StrainTime;
double prevDelta = prevObj.StrainTime;
@@ -197,5 +149,55 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
}
private static double logistic(double x, double maxValue, double multiplier, double offset) => (maxValue / (1 + Math.Pow(Math.E, offset - (multiplier * x))));
+
+ private struct Island : IEquatable
+ {
+ private readonly double deltaDifferenceEpsilon;
+
+ public Island(double epsilon)
+ {
+ deltaDifferenceEpsilon = epsilon;
+ }
+
+ public Island(int delta, double epsilon)
+ {
+ deltaDifferenceEpsilon = epsilon;
+ Delta = Math.Max(delta, OsuDifficultyHitObject.MIN_DELTA_TIME);
+ }
+
+ public int Delta { get; private set; }
+ public int DeltaCount { get; private set; }
+
+ public void AddDelta(int delta)
+ {
+ if (Delta == default)
+ Delta = Math.Max(delta, OsuDifficultyHitObject.MIN_DELTA_TIME);
+
+ DeltaCount++;
+ }
+
+ public bool IsSimilarPolarity(Island other)
+ {
+ // consider islands to be of similar polarity only if they're having the same average delta (we don't want to consider 3 singletaps similar to a triple)
+ return DeltaCount % 2 == other.DeltaCount % 2 &&
+ Math.Abs(Delta - other.Delta) < deltaDifferenceEpsilon;
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(Delta, DeltaCount);
+ }
+
+ public bool Equals(Island other)
+ {
+ return Math.Abs(Delta - other.Delta) < deltaDifferenceEpsilon &&
+ DeltaCount == other.DeltaCount;
+ }
+
+ public override bool Equals(object? obj)
+ {
+ return obj?.GetHashCode() == GetHashCode();
+ }
+ }
}
}
From 202364be5edc8f5126f12894f614a33533bc454f Mon Sep 17 00:00:00 2001
From: StanR
Date: Thu, 19 Sep 2024 17:52:55 +0500
Subject: [PATCH 104/239] Use `List` instead of `Dictionary` for island
counting, minor adjustments
---
.../Difficulty/Evaluators/RhythmEvaluator.cs | 35 +++++++++++++------
.../Difficulty/Skills/Speed.cs | 2 +-
2 files changed, 26 insertions(+), 11 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
index 87fc35adcd..d6ed60cc77 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
@@ -3,6 +3,8 @@
using System;
using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Lists;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Objects;
@@ -13,8 +15,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
{
private const int history_time_max = 4 * 1000; // 4 seconds
private const int history_objects_max = 24;
- private const double rhythm_overall_multiplier = 1.25;
- private const double rhythm_ratio_multiplier = 11.0;
+ private const double rhythm_overall_multiplier = 1.0;
+ private const double rhythm_ratio_multiplier = 14.0;
///
/// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current .
@@ -30,7 +32,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
var island = new Island(deltaDifferenceEpsilon);
var previousIsland = new Island(deltaDifferenceEpsilon);
- Dictionary islandCounts = new Dictionary();
+
+ // we can't use dictionary here because we need to compare island with a tolerance
+ // which is impossible to pass into the hash comparer
+ var islandCounts = new List<(Island Island, int Count)>();
double startRatio = 0; // store the ratio of the current start of an island to buff for tighter rhythms
@@ -104,15 +109,21 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
if (island.DeltaCount == 1)
effectiveRatio *= 0.7;
- if (!islandCounts.TryAdd(island, 1))
+ var islandCount = islandCounts.FirstOrDefault(x => x.Island.Equals(island));
+
+ if (islandCount != default)
{
// only add island to island counts if they're going one after another
if (previousIsland.Equals(island))
- islandCounts[island]++;
+ islandCount.Count++;
// repeated island (ex: triplet -> triplet)
double power = logistic(island.Delta, 4, 0.165, 10);
- effectiveRatio *= Math.Min(3.0 / islandCounts[island], Math.Pow(1.0 / islandCounts[island], power));
+ effectiveRatio *= Math.Min(3.0 / islandCount.Count, Math.Pow(1.0 / islandCount.Count, power));
+ }
+ else
+ {
+ islandCounts.Add((island, 1));
}
// scale down the difficulty if the object is doubletappable
@@ -150,7 +161,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
private static double logistic(double x, double maxValue, double multiplier, double offset) => (maxValue / (1 + Math.Pow(Math.E, offset - (multiplier * x))));
- private struct Island : IEquatable
+ private class Island : IEquatable
{
private readonly double deltaDifferenceEpsilon;
@@ -163,6 +174,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
{
deltaDifferenceEpsilon = epsilon;
Delta = Math.Max(delta, OsuDifficultyHitObject.MIN_DELTA_TIME);
+ DeltaCount++;
}
public int Delta { get; private set; }
@@ -188,15 +200,18 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
return HashCode.Combine(Delta, DeltaCount);
}
- public bool Equals(Island other)
+ public bool Equals(Island? other)
{
+ if (other == null)
+ return false;
+
return Math.Abs(Delta - other.Delta) < deltaDifferenceEpsilon &&
DeltaCount == other.DeltaCount;
}
- public override bool Equals(object? obj)
+ public override string ToString()
{
- return obj?.GetHashCode() == GetHashCode();
+ return $"{Delta}x{DeltaCount}";
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
index f7f081b7ea..37420ed026 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
///
public class Speed : OsuStrainSkill
{
- private double skillMultiplier => 1.375;
+ private double skillMultiplier => 1.38;
private double strainDecayBase => 0.3;
private double currentStrain;
From e986a7dfe117ff8b0ed1bbd691404bc5be1b7dcf Mon Sep 17 00:00:00 2001
From: StanR
Date: Thu, 19 Sep 2024 19:15:54 +0500
Subject: [PATCH 105/239] Fix repetition nerf not updating the counts
---
.../Difficulty/Evaluators/RhythmEvaluator.cs | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
index d6ed60cc77..e34989f162 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using osu.Framework.Lists;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Objects;
@@ -113,6 +112,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
if (islandCount != default)
{
+ int countIndex = islandCounts.IndexOf(islandCount);
+
// only add island to island counts if they're going one after another
if (previousIsland.Equals(island))
islandCount.Count++;
@@ -120,6 +121,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
// repeated island (ex: triplet -> triplet)
double power = logistic(island.Delta, 4, 0.165, 10);
effectiveRatio *= Math.Min(3.0 / islandCount.Count, Math.Pow(1.0 / islandCount.Count, power));
+
+ islandCounts[countIndex] = (islandCount.Island, islandCount.Count);
}
else
{
From d2f97f5908176696709e4608e3813196682ec7d2 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Thu, 19 Sep 2024 20:18:24 +0200
Subject: [PATCH 106/239] take into account rotation period for each grid type
---
.../Edit/OsuGridToolboxGroup.cs | 17 ++++++++++++-----
1 file changed, 12 insertions(+), 5 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
index 4b0353bd36..8752e17dad 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
@@ -104,9 +104,11 @@ namespace osu.Game.Rulesets.Osu.Edit
StartPositionY.Value = point1.Y;
// Get the angle between the two points and normalize to the valid range.
- const float period = 180;
- GridLinesRotation.Value = (MathHelper.RadiansToDegrees(MathF.Atan2(point2.Y - point1.Y, point2.X - point1.X))
- + period * 1.5f) % period - period * 0.5f;
+ if (!GridLinesRotation.Disabled)
+ {
+ float period = GridLinesRotation.MaxValue - GridLinesRotation.MinValue;
+ GridLinesRotation.Value = normalizeRotation(MathHelper.RadiansToDegrees(MathF.Atan2(point2.Y - point1.Y, point2.X - point1.X)), period);
+ }
// Divide the distance so that there is a good density of grid lines.
float dist = Vector2.Distance(point1, point2);
@@ -231,13 +233,13 @@ namespace osu.Game.Rulesets.Osu.Edit
switch (v.NewValue)
{
case PositionSnapGridType.Square:
- GridLinesRotation.Value = ((GridLinesRotation.Value + 405) % 90) - 45;
+ GridLinesRotation.Value = normalizeRotation(GridLinesRotation.Value, 90);
GridLinesRotation.MinValue = -45;
GridLinesRotation.MaxValue = 45;
break;
case PositionSnapGridType.Triangle:
- GridLinesRotation.Value = ((GridLinesRotation.Value + 390) % 60) - 30;
+ GridLinesRotation.Value = normalizeRotation(GridLinesRotation.Value, 60);
GridLinesRotation.MinValue = -30;
GridLinesRotation.MaxValue = 30;
break;
@@ -245,6 +247,11 @@ namespace osu.Game.Rulesets.Osu.Edit
}, true);
}
+ private float normalizeRotation(float rotation, float period)
+ {
+ return ((rotation + 360 + period * 0.5f) % period) - period * 0.5f;
+ }
+
private void nextGridSize()
{
Spacing.Value = Spacing.Value * 2 >= max_automatic_spacing ? Spacing.Value / 8 : Spacing.Value * 2;
From e04b88a9b0093c0902e4f6ecf7e6ad2d64174a84 Mon Sep 17 00:00:00 2001
From: StanR
Date: Mon, 23 Sep 2024 13:49:25 +0500
Subject: [PATCH 107/239] Replace speed buff with aim buff
---
osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs | 2 +-
osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 2 +-
osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
index e34989f162..9c7191dec5 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
{
private const int history_time_max = 4 * 1000; // 4 seconds
private const int history_objects_max = 24;
- private const double rhythm_overall_multiplier = 1.0;
+ private const double rhythm_overall_multiplier = 0.95;
private const double rhythm_ratio_multiplier = 14.0;
///
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
index 3f6b22bbb1..7963e92c8d 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
private double currentStrain;
- private double skillMultiplier => 23.55;
+ private double skillMultiplier => 23.65;
private double strainDecayBase => 0.15;
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
index 37420ed026..f7f081b7ea 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
///
public class Speed : OsuStrainSkill
{
- private double skillMultiplier => 1.38;
+ private double skillMultiplier => 1.375;
private double strainDecayBase => 0.3;
private double currentStrain;
From 08bded82fdfd1cf63d2a72b51cedb5be0de01c60 Mon Sep 17 00:00:00 2001
From: StanR
Date: Mon, 23 Sep 2024 16:30:02 +0500
Subject: [PATCH 108/239] Remove GetHashCode
---
.../Difficulty/Evaluators/RhythmEvaluator.cs | 5 -----
1 file changed, 5 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
index 9c7191dec5..abf9dbe725 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
@@ -198,11 +198,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
Math.Abs(Delta - other.Delta) < deltaDifferenceEpsilon;
}
- public override int GetHashCode()
- {
- return HashCode.Combine(Delta, DeltaCount);
- }
-
public bool Equals(Island? other)
{
if (other == null)
From 1a81e12192b6b6d2c1c8d1b9256cb2242e8d66e3 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Mon, 23 Sep 2024 16:33:36 +0200
Subject: [PATCH 109/239] Refactor PlacementBlueprint to not be hitobject
specific
---
.../CatchPlacementBlueprintTestScene.cs | 2 +-
...TestSceneBananaShowerPlacementBlueprint.cs | 4 +-
.../TestSceneFruitPlacementBlueprint.cs | 2 +-
.../TestSceneJuiceStreamPlacementBlueprint.cs | 2 +-
.../Edit/BananaShowerCompositionTool.cs | 4 +-
.../Blueprints/CatchPlacementBlueprint.cs | 2 +-
.../Edit/CatchHitObjectComposer.cs | 4 +-
.../Edit/FruitCompositionTool.cs | 4 +-
.../Edit/JuiceStreamCompositionTool.cs | 4 +-
.../ManiaPlacementBlueprintTestScene.cs | 2 +-
.../TestSceneHoldNotePlacementBlueprint.cs | 2 +-
.../Editor/TestSceneNotePlacementBlueprint.cs | 2 +-
.../Blueprints/ManiaPlacementBlueprint.cs | 2 +-
.../Edit/HoldNoteCompositionTool.cs | 4 +-
.../Edit/ManiaHitObjectComposer.cs | 2 +-
.../Edit/NoteCompositionTool.cs | 4 +-
.../TestSceneHitCirclePlacementBlueprint.cs | 2 +-
.../TestSceneSliderPlacementBlueprint.cs | 2 +-
.../TestSceneSpinnerPlacementBlueprint.cs | 2 +-
.../HitCircles/HitCirclePlacementBlueprint.cs | 2 +-
.../Sliders/SliderPlacementBlueprint.cs | 2 +-
.../Spinners/SpinnerPlacementBlueprint.cs | 2 +-
.../Edit/HitCircleCompositionTool.cs | 4 +-
.../Edit/OsuHitObjectComposer.cs | 2 +-
.../Edit/SliderCompositionTool.cs | 4 +-
.../Edit/SpinnerCompositionTool.cs | 4 +-
.../Edit/Blueprints/HitPlacementBlueprint.cs | 2 +-
.../Blueprints/TaikoSpanPlacementBlueprint.cs | 2 +-
.../Edit/DrumRollCompositionTool.cs | 4 +-
.../Edit/HitCompositionTool.cs | 4 +-
.../Edit/SwellCompositionTool.cs | 4 +-
.../Edit/TaikoHitObjectComposer.cs | 2 +-
.../Editing/TestScenePlacementBlueprint.cs | 2 +-
osu.Game/Rulesets/Edit/HitObjectComposer.cs | 4 +-
.../Edit/HitObjectCompositionToolButton.cs | 4 +-
.../Edit/HitObjectPlacementBlueprint.cs | 126 ++++++++++++++++++
osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 122 +++--------------
...tCompositionTool.cs => CompositionTool.cs} | 4 +-
osu.Game/Rulesets/Edit/Tools/SelectTool.cs | 4 +-
.../Components/ComposeBlueprintContainer.cs | 22 +--
.../Visual/PlacementBlueprintTestScene.cs | 8 +-
41 files changed, 212 insertions(+), 174 deletions(-)
create mode 100644 osu.Game/Rulesets/Edit/HitObjectPlacementBlueprint.cs
rename osu.Game/Rulesets/Edit/Tools/{HitObjectCompositionTool.cs => CompositionTool.cs} (84%)
diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs
index aae759d934..0578010c25 100644
--- a/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs
+++ b/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs
@@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
contentContainer.Playfield.HitObjectContainer.Add(hitObject);
}
- protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint)
+ protected override SnapResult SnapForBlueprint(HitObjectPlacementBlueprint blueprint)
{
var result = base.SnapForBlueprint(blueprint);
result.Time = Math.Round(HitObjectContainer.TimeAtScreenSpacePosition(result.ScreenSpacePosition) / TIME_SNAP) * TIME_SNAP;
diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs
index ed37ff4ef3..badd8e967d 100644
--- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
{
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableBananaShower((BananaShower)hitObject);
- protected override PlacementBlueprint CreateBlueprint() => new BananaShowerPlacementBlueprint();
+ protected override HitObjectPlacementBlueprint CreateBlueprint() => new BananaShowerPlacementBlueprint();
protected override void AddHitObject(DrawableHitObject hitObject)
{
@@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
AddClickStep(MouseButton.Left);
AddClickStep(MouseButton.Right);
AddAssert("banana shower is not placed", () => LastObject == null);
- AddAssert("state is waiting", () => CurrentBlueprint?.PlacementActive == PlacementBlueprint.PlacementState.Waiting);
+ AddAssert("state is waiting", () => CurrentBlueprint?.PlacementActive == HitObjectPlacementBlueprint.PlacementState.Waiting);
}
[Test]
diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs
index 75d3c3753a..80cd948e26 100644
--- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
{
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableFruit((Fruit)hitObject);
- protected override PlacementBlueprint CreateBlueprint() => new FruitPlacementBlueprint();
+ protected override HitObjectPlacementBlueprint CreateBlueprint() => new FruitPlacementBlueprint();
[Test]
public void TestFruitPlacementPosition()
diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs
index d010bb02ad..8bd60078c6 100644
--- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs
@@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableJuiceStream((JuiceStream)hitObject);
- protected override PlacementBlueprint CreateBlueprint() => new JuiceStreamPlacementBlueprint();
+ protected override HitObjectPlacementBlueprint CreateBlueprint() => new JuiceStreamPlacementBlueprint();
private void addMoveAndClickSteps(double time, float position, bool end = false)
{
diff --git a/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs b/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs
index 31075db7d1..be93ba0242 100644
--- a/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs
+++ b/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs
@@ -10,7 +10,7 @@ using osu.Game.Rulesets.Edit.Tools;
namespace osu.Game.Rulesets.Catch.Edit
{
- public class BananaShowerCompositionTool : HitObjectCompositionTool
+ public class BananaShowerCompositionTool : CompositionTool
{
public BananaShowerCompositionTool()
: base(nameof(BananaShower))
@@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Catch.Edit
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners);
- public override PlacementBlueprint CreatePlacementBlueprint() => new BananaShowerPlacementBlueprint();
+ public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new BananaShowerPlacementBlueprint();
}
}
diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs
index 1a2990e4ac..aa862375c5 100644
--- a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs
@@ -9,7 +9,7 @@ using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Catch.Edit.Blueprints
{
- public partial class CatchPlacementBlueprint : PlacementBlueprint
+ public partial class CatchPlacementBlueprint : HitObjectPlacementBlueprint
where THitObject : CatchHitObject, new()
{
protected new THitObject HitObject => (THitObject)base.HitObject;
diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs
index 83f48816f9..8460e238f6 100644
--- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs
@@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Catch.Edit
protected override BeatSnapGrid CreateBeatSnapGrid() => new CatchBeatSnapGrid();
- protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[]
+ protected override IReadOnlyList CompositionTools => new CompositionTool[]
{
new FruitCompositionTool(),
new JuiceStreamCompositionTool(),
@@ -168,7 +168,7 @@ namespace osu.Game.Rulesets.Catch.Edit
if (EditorBeatmap.PlacementObject.Value is JuiceStream)
{
// Juice stream path is not subject to snapping.
- if (BlueprintContainer.CurrentPlacement.PlacementActive is PlacementBlueprint.PlacementState.Active)
+ if (BlueprintContainer.CurrentPlacement.PlacementActive is HitObjectPlacementBlueprint.PlacementState.Active)
return null;
}
diff --git a/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs b/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs
index f776fe39c1..71c1e66903 100644
--- a/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs
+++ b/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs
@@ -10,7 +10,7 @@ using osu.Game.Rulesets.Edit.Tools;
namespace osu.Game.Rulesets.Catch.Edit
{
- public class FruitCompositionTool : HitObjectCompositionTool
+ public class FruitCompositionTool : CompositionTool
{
public FruitCompositionTool()
: base(nameof(Fruit))
@@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Catch.Edit
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
- public override PlacementBlueprint CreatePlacementBlueprint() => new FruitPlacementBlueprint();
+ public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new FruitPlacementBlueprint();
}
}
diff --git a/osu.Game.Rulesets.Catch/Edit/JuiceStreamCompositionTool.cs b/osu.Game.Rulesets.Catch/Edit/JuiceStreamCompositionTool.cs
index cb66e2952e..7a567820f3 100644
--- a/osu.Game.Rulesets.Catch/Edit/JuiceStreamCompositionTool.cs
+++ b/osu.Game.Rulesets.Catch/Edit/JuiceStreamCompositionTool.cs
@@ -10,7 +10,7 @@ using osu.Game.Rulesets.Edit.Tools;
namespace osu.Game.Rulesets.Catch.Edit
{
- public class JuiceStreamCompositionTool : HitObjectCompositionTool
+ public class JuiceStreamCompositionTool : CompositionTool
{
public JuiceStreamCompositionTool()
: base(nameof(JuiceStream))
@@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Catch.Edit
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
- public override PlacementBlueprint CreatePlacementBlueprint() => new JuiceStreamPlacementBlueprint();
+ public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new JuiceStreamPlacementBlueprint();
}
}
diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs
index d2a44122aa..5e633c3161 100644
--- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs
@@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
});
}
- protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint)
+ protected override SnapResult SnapForBlueprint(HitObjectPlacementBlueprint blueprint)
{
double time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position);
var pos = column.ScreenSpacePositionAtTime(time);
diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs
index b79bcb7682..2006879ab3 100644
--- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs
@@ -13,6 +13,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
public partial class TestSceneHoldNotePlacementBlueprint : ManiaPlacementBlueprintTestScene
{
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHoldNote((HoldNote)hitObject);
- protected override PlacementBlueprint CreateBlueprint() => new HoldNotePlacementBlueprint();
+ protected override HitObjectPlacementBlueprint CreateBlueprint() => new HoldNotePlacementBlueprint();
}
}
diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs
index a446f13cbf..0cb9639cd1 100644
--- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs
@@ -64,6 +64,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
private Note getNote() => this.ChildrenOfType().FirstOrDefault()?.HitObject;
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableNote((Note)hitObject);
- protected override PlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint();
+ protected override HitObjectPlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint();
}
}
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs
index 5e0512b5dc..a68bd5d6d6 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs
@@ -15,7 +15,7 @@ using osuTK.Input;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
- public abstract partial class ManiaPlacementBlueprint : PlacementBlueprint
+ public abstract partial class ManiaPlacementBlueprint : HitObjectPlacementBlueprint
where T : ManiaHitObject
{
protected new T HitObject => (T)base.HitObject;
diff --git a/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs b/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs
index 99e1ce04b1..592f8d9af7 100644
--- a/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs
+++ b/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs
@@ -9,7 +9,7 @@ using osu.Game.Rulesets.Mania.Edit.Blueprints;
namespace osu.Game.Rulesets.Mania.Edit
{
- public class HoldNoteCompositionTool : HitObjectCompositionTool
+ public class HoldNoteCompositionTool : CompositionTool
{
public HoldNoteCompositionTool()
: base("Hold")
@@ -18,6 +18,6 @@ namespace osu.Game.Rulesets.Mania.Edit
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
- public override PlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint();
+ public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint();
}
}
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
index 02a4f3a022..e3b4fa2fb7 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
@@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Mania.Edit
protected override BeatSnapGrid CreateBeatSnapGrid() => new ManiaBeatSnapGrid();
- protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[]
+ protected override IReadOnlyList CompositionTools => new CompositionTool[]
{
new NoteCompositionTool(),
new HoldNoteCompositionTool()
diff --git a/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs b/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs
index 08ee05ad3f..2e54d63525 100644
--- a/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs
+++ b/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs
@@ -10,7 +10,7 @@ using osu.Game.Rulesets.Mania.Objects;
namespace osu.Game.Rulesets.Mania.Edit
{
- public class NoteCompositionTool : HitObjectCompositionTool
+ public class NoteCompositionTool : CompositionTool
{
public NoteCompositionTool()
: base(nameof(Note))
@@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Mania.Edit
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
- public override PlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint();
+ public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint();
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCirclePlacementBlueprint.cs
index a49afd82f3..a105d860bf 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCirclePlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCirclePlacementBlueprint.cs
@@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
public partial class TestSceneHitCirclePlacementBlueprint : PlacementBlueprintTestScene
{
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHitCircle((HitCircle)hitObject);
- protected override PlacementBlueprint CreateBlueprint() => new HitCirclePlacementBlueprint();
+ protected override HitObjectPlacementBlueprint CreateBlueprint() => new HitCirclePlacementBlueprint();
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs
index aa6a6f08d8..019565ae29 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs
@@ -514,6 +514,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
private Slider? getSlider() => HitObjectContainer.Count > 0 ? ((DrawableSlider)HitObjectContainer[0]).HitObject : null;
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSlider((Slider)hitObject);
- protected override PlacementBlueprint CreateBlueprint() => new SliderPlacementBlueprint();
+ protected override HitObjectPlacementBlueprint CreateBlueprint() => new SliderPlacementBlueprint();
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerPlacementBlueprint.cs
index 0e8673319e..d7b5cc73be 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerPlacementBlueprint.cs
@@ -15,6 +15,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSpinner((Spinner)hitObject);
- protected override PlacementBlueprint CreateBlueprint() => new SpinnerPlacementBlueprint();
+ protected override HitObjectPlacementBlueprint CreateBlueprint() => new SpinnerPlacementBlueprint();
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs
index 20ad99baa2..78a0e36dc2 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs
@@ -9,7 +9,7 @@ using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
{
- public partial class HitCirclePlacementBlueprint : PlacementBlueprint
+ public partial class HitCirclePlacementBlueprint : HitObjectPlacementBlueprint
{
public new HitCircle HitObject => (HitCircle)base.HitObject;
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
index 42945295b8..6ffe27dc13 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
@@ -21,7 +21,7 @@ using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
- public partial class SliderPlacementBlueprint : PlacementBlueprint
+ public partial class SliderPlacementBlueprint : HitObjectPlacementBlueprint
{
public new Slider HitObject => (Slider)base.HitObject;
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs
index f59be0e0e9..17d2dcd75c 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs
@@ -13,7 +13,7 @@ using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
{
- public partial class SpinnerPlacementBlueprint : PlacementBlueprint
+ public partial class SpinnerPlacementBlueprint : HitObjectPlacementBlueprint
{
public new Spinner HitObject => (Spinner)base.HitObject;
diff --git a/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs b/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs
index c41ae10b2e..d3116ede30 100644
--- a/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs
+++ b/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs
@@ -10,7 +10,7 @@ using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit
{
- public class HitCircleCompositionTool : HitObjectCompositionTool
+ public class HitCircleCompositionTool : CompositionTool
{
public HitCircleCompositionTool()
: base(nameof(HitCircle))
@@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Osu.Edit
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
- public override PlacementBlueprint CreatePlacementBlueprint() => new HitCirclePlacementBlueprint();
+ public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new HitCirclePlacementBlueprint();
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index 68e57060ad..e6a9a95b6b 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Edit
protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods)
=> new DrawableOsuEditorRuleset(ruleset, beatmap, mods);
- protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[]
+ protected override IReadOnlyList CompositionTools => new CompositionTool[]
{
new HitCircleCompositionTool(),
new SliderCompositionTool(),
diff --git a/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs b/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs
index 617cc1c19b..d697a2ebe6 100644
--- a/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs
+++ b/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs
@@ -10,7 +10,7 @@ using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit
{
- public class SliderCompositionTool : HitObjectCompositionTool
+ public class SliderCompositionTool : CompositionTool
{
public SliderCompositionTool()
: base(nameof(Slider))
@@ -26,6 +26,6 @@ namespace osu.Game.Rulesets.Osu.Edit
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
- public override PlacementBlueprint CreatePlacementBlueprint() => new SliderPlacementBlueprint();
+ public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new SliderPlacementBlueprint();
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs b/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs
index c8160617c9..de1506e4a9 100644
--- a/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs
+++ b/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs
@@ -10,7 +10,7 @@ using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit
{
- public class SpinnerCompositionTool : HitObjectCompositionTool
+ public class SpinnerCompositionTool : CompositionTool
{
public SpinnerCompositionTool()
: base(nameof(Spinner))
@@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Osu.Edit
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners);
- public override PlacementBlueprint CreatePlacementBlueprint() => new SpinnerPlacementBlueprint();
+ public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new SpinnerPlacementBlueprint();
}
}
diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs
index 329fff5b42..7f45123bd6 100644
--- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs
@@ -10,7 +10,7 @@ using osuTK.Input;
namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
{
- public partial class HitPlacementBlueprint : PlacementBlueprint
+ public partial class HitPlacementBlueprint : HitObjectPlacementBlueprint
{
private readonly HitPiece piece;
diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs
index cd52398086..de3a4d96eb 100644
--- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs
@@ -17,7 +17,7 @@ using osuTK.Input;
namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
{
- public partial class TaikoSpanPlacementBlueprint : PlacementBlueprint
+ public partial class TaikoSpanPlacementBlueprint : HitObjectPlacementBlueprint
{
private readonly HitPiece headPiece;
private readonly HitPiece tailPiece;
diff --git a/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs
index f332441875..ba0fda6771 100644
--- a/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs
+++ b/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs
@@ -10,7 +10,7 @@ using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Edit
{
- public class DrumRollCompositionTool : HitObjectCompositionTool
+ public class DrumRollCompositionTool : CompositionTool
{
public DrumRollCompositionTool()
: base(nameof(DrumRoll))
@@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Taiko.Edit
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
- public override PlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint();
+ public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint();
}
}
diff --git a/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs
index fa50841893..f58defba83 100644
--- a/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs
+++ b/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs
@@ -10,7 +10,7 @@ using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Edit
{
- public class HitCompositionTool : HitObjectCompositionTool
+ public class HitCompositionTool : CompositionTool
{
public HitCompositionTool()
: base(nameof(Hit))
@@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Taiko.Edit
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
- public override PlacementBlueprint CreatePlacementBlueprint() => new HitPlacementBlueprint();
+ public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new HitPlacementBlueprint();
}
}
diff --git a/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs
index 4d4ee8effe..4ec623e29e 100644
--- a/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs
+++ b/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs
@@ -10,7 +10,7 @@ using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Edit
{
- public class SwellCompositionTool : HitObjectCompositionTool
+ public class SwellCompositionTool : CompositionTool
{
public SwellCompositionTool()
: base(nameof(Swell))
@@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Taiko.Edit
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners);
- public override PlacementBlueprint CreatePlacementBlueprint() => new SwellPlacementBlueprint();
+ public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new SwellPlacementBlueprint();
}
}
diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs
index 6020f6e04c..d97a854ff7 100644
--- a/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Taiko.Edit
{
}
- protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[]
+ protected override IReadOnlyList CompositionTools => new CompositionTool[]
{
new HitCompositionTool(),
new DrumRollCompositionTool(),
diff --git a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs
index fe74e1b346..6f3342f8ce 100644
--- a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs
+++ b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs
@@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("editor is still current", () => Editor.IsCurrentScreen());
AddAssert("slider not placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(0));
AddAssert("no active placement", () => this.ChildrenOfType().Single().CurrentPlacement.PlacementActive,
- () => Is.EqualTo(PlacementBlueprint.PlacementState.Waiting));
+ () => Is.EqualTo(HitObjectPlacementBlueprint.PlacementState.Waiting));
}
[Test]
diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
index 87c35eafb0..b83c8acec6 100644
--- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs
+++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
@@ -326,7 +326,7 @@ namespace osu.Game.Rulesets.Edit
///
/// A "select" tool is automatically added as the first tool.
///
- protected abstract IReadOnlyList CompositionTools { get; }
+ protected abstract IReadOnlyList CompositionTools { get; }
///
/// A collection of states which will be displayed to the user in the toolbox.
@@ -469,7 +469,7 @@ namespace osu.Game.Rulesets.Edit
private void setSelectTool() => toolboxCollection.Items.First().Select();
- private void toolSelected(HitObjectCompositionTool tool)
+ private void toolSelected(CompositionTool tool)
{
BlueprintContainer.CurrentTool = tool;
diff --git a/osu.Game/Rulesets/Edit/HitObjectCompositionToolButton.cs b/osu.Game/Rulesets/Edit/HitObjectCompositionToolButton.cs
index ba566ff5c0..641d60dbd3 100644
--- a/osu.Game/Rulesets/Edit/HitObjectCompositionToolButton.cs
+++ b/osu.Game/Rulesets/Edit/HitObjectCompositionToolButton.cs
@@ -9,9 +9,9 @@ namespace osu.Game.Rulesets.Edit
{
public class HitObjectCompositionToolButton : RadioButton
{
- public HitObjectCompositionTool Tool { get; }
+ public CompositionTool Tool { get; }
- public HitObjectCompositionToolButton(HitObjectCompositionTool tool, Action? action)
+ public HitObjectCompositionToolButton(CompositionTool tool, Action? action)
: base(tool.Name, action, tool.CreateIcon)
{
Tool = tool;
diff --git a/osu.Game/Rulesets/Edit/HitObjectPlacementBlueprint.cs b/osu.Game/Rulesets/Edit/HitObjectPlacementBlueprint.cs
new file mode 100644
index 0000000000..74025b4260
--- /dev/null
+++ b/osu.Game/Rulesets/Edit/HitObjectPlacementBlueprint.cs
@@ -0,0 +1,126 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using System.Threading;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Game.Audio;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Screens.Edit;
+using osu.Game.Screens.Edit.Compose;
+
+namespace osu.Game.Rulesets.Edit
+{
+ ///
+ /// A blueprint which governs the creation of a new to actualisation.
+ ///
+ public abstract partial class HitObjectPlacementBlueprint : PlacementBlueprint
+ {
+ ///
+ /// Whether the sample bank should be taken from the previous hit object.
+ ///
+ public bool AutomaticBankAssignment { get; set; }
+
+ ///
+ /// The that is being placed.
+ ///
+ public readonly HitObject HitObject;
+
+ [Resolved]
+ protected EditorClock EditorClock { get; private set; } = null!;
+
+ [Resolved]
+ private EditorBeatmap beatmap { get; set; } = null!;
+
+ private Bindable startTimeBindable = null!;
+
+ private HitObject? getPreviousHitObject() => beatmap.HitObjects.TakeWhile(h => h.StartTime <= startTimeBindable.Value).LastOrDefault();
+
+ [Resolved]
+ private IPlacementHandler placementHandler { get; set; } = null!;
+
+ protected HitObjectPlacementBlueprint(HitObject hitObject)
+ {
+ HitObject = hitObject;
+
+ // adding the default hit sample should be the case regardless of the ruleset.
+ HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_NORMAL));
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ startTimeBindable = HitObject.StartTimeBindable.GetBoundCopy();
+ startTimeBindable.BindValueChanged(_ => ApplyDefaultsToHitObject(), true);
+ }
+
+ protected override void BeginPlacement(bool commitStart = false)
+ {
+ base.BeginPlacement(commitStart);
+
+ placementHandler.BeginPlacement(HitObject);
+ }
+
+ public override void EndPlacement(bool commit)
+ {
+ base.EndPlacement(commit);
+
+ placementHandler.EndPlacement(HitObject, IsValidForPlacement && commit);
+ }
+
+ ///
+ /// Updates the time and position of this based on the provided snap information.
+ ///
+ /// The snap result information.
+ public override void UpdateTimeAndPosition(SnapResult result)
+ {
+ if (PlacementActive == PlacementState.Waiting)
+ {
+ HitObject.StartTime = result.Time ?? EditorClock.CurrentTime;
+
+ if (HitObject is IHasComboInformation comboInformation)
+ comboInformation.UpdateComboInformation(getPreviousHitObject() as IHasComboInformation);
+ }
+
+ var lastHitObject = getPreviousHitObject();
+
+ if (AutomaticBankAssignment)
+ {
+ // Create samples based on the sample settings of the previous hit object
+ if (lastHitObject != null)
+ {
+ for (int i = 0; i < HitObject.Samples.Count; i++)
+ HitObject.Samples[i] = lastHitObject.CreateHitSampleInfo(HitObject.Samples[i].Name);
+ }
+ }
+ else
+ {
+ var lastHitNormal = lastHitObject?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL);
+
+ if (lastHitNormal != null)
+ {
+ // Only inherit the volume from the previous hit object
+ for (int i = 0; i < HitObject.Samples.Count; i++)
+ HitObject.Samples[i] = HitObject.Samples[i].With(newVolume: lastHitNormal.Volume);
+ }
+ }
+
+ if (HitObject is IHasRepeats hasRepeats)
+ {
+ // Make sure all the node samples are identical to the hit object's samples
+ for (int i = 0; i < hasRepeats.NodeSamples.Count; i++)
+ hasRepeats.NodeSamples[i] = HitObject.Samples.Select(o => o.With()).ToList();
+ }
+ }
+
+ ///
+ /// Invokes ,
+ /// refreshing and parameters for the .
+ ///
+ protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty);
+ }
+}
diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs
index a50a7f4169..d2a54e8e03 100644
--- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs
+++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs
@@ -1,29 +1,19 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System.Linq;
-using System.Threading;
-using osu.Framework.Allocation;
-using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
-using osu.Game.Audio;
-using osu.Game.Beatmaps;
-using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Objects.Types;
-using osu.Game.Screens.Edit;
-using osu.Game.Screens.Edit.Compose;
using osuTK;
using osuTK.Input;
namespace osu.Game.Rulesets.Edit
{
///
- /// A blueprint which governs the creation of a new to actualisation.
+ /// A blueprint which governs the placement of something.
///
public abstract partial class PlacementBlueprint : CompositeDrawable, IKeyBindingHandler
{
@@ -32,29 +22,6 @@ namespace osu.Game.Rulesets.Edit
///
public PlacementState PlacementActive { get; private set; }
- ///
- /// Whether the sample bank should be taken from the previous hit object.
- ///
- public bool AutomaticBankAssignment { get; set; }
-
- ///
- /// The that is being placed.
- ///
- public readonly HitObject HitObject;
-
- [Resolved]
- protected EditorClock EditorClock { get; private set; } = null!;
-
- [Resolved]
- private EditorBeatmap beatmap { get; set; } = null!;
-
- private Bindable startTimeBindable = null!;
-
- private HitObject? getPreviousHitObject() => beatmap.HitObjects.TakeWhile(h => h.StartTime <= startTimeBindable.Value).LastOrDefault();
-
- [Resolved]
- private IPlacementHandler placementHandler { get; set; } = null!;
-
///
/// Whether this blueprint is currently in a state that can be committed.
///
@@ -64,13 +31,8 @@ namespace osu.Game.Rulesets.Edit
///
protected virtual bool IsValidForPlacement => true;
- protected PlacementBlueprint(HitObject hitObject)
+ protected PlacementBlueprint()
{
- HitObject = hitObject;
-
- // adding the default hit sample should be the case regardless of the ruleset.
- HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_NORMAL));
-
RelativeSizeAxes = Axes.Both;
// This is required to allow the blueprint's position to be updated via OnMouseMove/Handle
@@ -78,30 +40,22 @@ namespace osu.Game.Rulesets.Edit
AlwaysPresent = true;
}
- [BackgroundDependencyLoader]
- private void load()
- {
- startTimeBindable = HitObject.StartTimeBindable.GetBoundCopy();
- startTimeBindable.BindValueChanged(_ => ApplyDefaultsToHitObject(), true);
- }
-
///
- /// Signals that the placement of has started.
+ /// Signals that the placement has started.
///
- /// Whether this call is committing a value for HitObject.StartTime and continuing with further adjustments.
- protected void BeginPlacement(bool commitStart = false)
+ /// Whether this call is committing a value and continuing with further adjustments.
+ protected virtual void BeginPlacement(bool commitStart = false)
{
- placementHandler.BeginPlacement(HitObject);
if (commitStart)
PlacementActive = PlacementState.Active;
}
///
/// Signals that the placement of has finished.
- /// This will destroy this , and add the HitObject.StartTime to the .
+ /// This will destroy this , and commit the changes.
///
- /// Whether the object should be committed. Note that a commit may fail if is false.
- public void EndPlacement(bool commit)
+ /// Whether the changes should be committed. Note that a commit may fail if is false.
+ public virtual void EndPlacement(bool commit)
{
switch (PlacementActive)
{
@@ -114,10 +68,17 @@ namespace osu.Game.Rulesets.Edit
break;
}
- placementHandler.EndPlacement(HitObject, IsValidForPlacement && commit);
PlacementActive = PlacementState.Finished;
}
+ ///
+ /// Updates the time and position of this based on the provided snap information.
+ ///
+ /// The snap result information.
+ public virtual void UpdateTimeAndPosition(SnapResult result)
+ {
+ }
+
public bool OnPressed(KeyBindingPressEvent e)
{
if (PlacementActive == PlacementState.Waiting)
@@ -138,57 +99,6 @@ namespace osu.Game.Rulesets.Edit
{
}
- ///
- /// Updates the time and position of this based on the provided snap information.
- ///
- /// The snap result information.
- public virtual void UpdateTimeAndPosition(SnapResult result)
- {
- if (PlacementActive == PlacementState.Waiting)
- {
- HitObject.StartTime = result.Time ?? EditorClock.CurrentTime;
-
- if (HitObject is IHasComboInformation comboInformation)
- comboInformation.UpdateComboInformation(getPreviousHitObject() as IHasComboInformation);
- }
-
- var lastHitObject = getPreviousHitObject();
-
- if (AutomaticBankAssignment)
- {
- // Create samples based on the sample settings of the previous hit object
- if (lastHitObject != null)
- {
- for (int i = 0; i < HitObject.Samples.Count; i++)
- HitObject.Samples[i] = lastHitObject.CreateHitSampleInfo(HitObject.Samples[i].Name);
- }
- }
- else
- {
- var lastHitNormal = lastHitObject?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL);
-
- if (lastHitNormal != null)
- {
- // Only inherit the volume from the previous hit object
- for (int i = 0; i < HitObject.Samples.Count; i++)
- HitObject.Samples[i] = HitObject.Samples[i].With(newVolume: lastHitNormal.Volume);
- }
- }
-
- if (HitObject is IHasRepeats hasRepeats)
- {
- // Make sure all the node samples are identical to the hit object's samples
- for (int i = 0; i < hasRepeats.NodeSamples.Count; i++)
- hasRepeats.NodeSamples[i] = HitObject.Samples.Select(o => o.With()).ToList();
- }
- }
-
- ///
- /// Invokes ,
- /// refreshing and parameters for the .
- ///
- protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty);
-
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
protected override bool Handle(UIEvent e)
diff --git a/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs b/osu.Game/Rulesets/Edit/Tools/CompositionTool.cs
similarity index 84%
rename from osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs
rename to osu.Game/Rulesets/Edit/Tools/CompositionTool.cs
index ba1dc817bb..f509302daa 100644
--- a/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs
+++ b/osu.Game/Rulesets/Edit/Tools/CompositionTool.cs
@@ -6,13 +6,13 @@ using osu.Framework.Localisation;
namespace osu.Game.Rulesets.Edit.Tools
{
- public abstract class HitObjectCompositionTool
+ public abstract class CompositionTool
{
public readonly string Name;
public LocalisableString TooltipText { get; init; }
- protected HitObjectCompositionTool(string name)
+ protected CompositionTool(string name)
{
Name = name;
}
diff --git a/osu.Game/Rulesets/Edit/Tools/SelectTool.cs b/osu.Game/Rulesets/Edit/Tools/SelectTool.cs
index a272e9f480..7f8889bfca 100644
--- a/osu.Game/Rulesets/Edit/Tools/SelectTool.cs
+++ b/osu.Game/Rulesets/Edit/Tools/SelectTool.cs
@@ -9,7 +9,7 @@ using osu.Game.Graphics;
namespace osu.Game.Rulesets.Edit.Tools
{
- public class SelectTool : HitObjectCompositionTool
+ public class SelectTool : CompositionTool
{
public SelectTool()
: base("Select")
@@ -18,6 +18,6 @@ namespace osu.Game.Rulesets.Edit.Tools
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorSelect };
- public override PlacementBlueprint CreatePlacementBlueprint() => null;
+ public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => null;
}
}
diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs
index f1294ccc3c..f0296d45aa 100644
--- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs
@@ -41,6 +41,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
public PlacementBlueprint CurrentPlacement { get; private set; }
+ public HitObjectPlacementBlueprint CurrentHitObjectPlacement => CurrentPlacement as HitObjectPlacementBlueprint;
+
[Resolved(canBeNull: true)]
private EditorScreenWithTimeline editorScreen { get; set; }
@@ -164,13 +166,13 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void updatePlacementNewCombo()
{
- if (CurrentPlacement?.HitObject is IHasComboInformation c)
+ if (CurrentHitObjectPlacement?.HitObject is IHasComboInformation c)
c.NewCombo = NewCombo.Value == TernaryState.True;
}
private void updatePlacementSamples()
{
- if (CurrentPlacement == null) return;
+ if (CurrentHitObjectPlacement == null) return;
foreach (var kvp in SelectionHandler.SelectionSampleStates)
sampleChanged(kvp.Key, kvp.Value.Value);
@@ -181,9 +183,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void sampleChanged(string sampleName, TernaryState state)
{
- if (CurrentPlacement == null) return;
+ if (CurrentHitObjectPlacement == null) return;
- var samples = CurrentPlacement.HitObject.Samples;
+ var samples = CurrentHitObjectPlacement.HitObject.Samples;
var existingSample = samples.FirstOrDefault(s => s.Name == sampleName);
@@ -196,19 +198,19 @@ namespace osu.Game.Screens.Edit.Compose.Components
case TernaryState.True:
if (existingSample == null)
- samples.Add(CurrentPlacement.HitObject.CreateHitSampleInfo(sampleName));
+ samples.Add(CurrentHitObjectPlacement.HitObject.CreateHitSampleInfo(sampleName));
break;
}
}
private void bankChanged(string bankName, TernaryState state)
{
- if (CurrentPlacement == null) return;
+ if (CurrentHitObjectPlacement == null) return;
if (bankName == EditorSelectionHandler.HIT_BANK_AUTO)
- CurrentPlacement.AutomaticBankAssignment = state == TernaryState.True;
+ CurrentHitObjectPlacement.AutomaticBankAssignment = state == TernaryState.True;
else if (state == TernaryState.True)
- CurrentPlacement.HitObject.Samples = CurrentPlacement.HitObject.Samples.Select(s => s.With(newBank: bankName)).ToList();
+ CurrentHitObjectPlacement.HitObject.Samples = CurrentHitObjectPlacement.HitObject.Samples.Select(s => s.With(newBank: bankName)).ToList();
}
public readonly Bindable NewCombo = new Bindable { Description = "New Combo" };
@@ -386,12 +388,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
CurrentPlacement = null;
}
- private HitObjectCompositionTool currentTool;
+ private CompositionTool currentTool;
///
/// The current placement tool.
///
- public HitObjectCompositionTool CurrentTool
+ public CompositionTool CurrentTool
{
get => currentTool;
diff --git a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs
index 0027e03492..c8d9ef8fc8 100644
--- a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs
+++ b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual
public abstract partial class PlacementBlueprintTestScene : OsuManualInputManagerTestScene, IPlacementHandler
{
protected readonly Container HitObjectContainer;
- protected PlacementBlueprint CurrentBlueprint { get; private set; }
+ protected HitObjectPlacementBlueprint CurrentBlueprint { get; private set; }
protected PlacementBlueprintTestScene()
{
@@ -87,14 +87,14 @@ namespace osu.Game.Tests.Visual
CurrentBlueprint.UpdateTimeAndPosition(SnapForBlueprint(CurrentBlueprint));
}
- protected virtual SnapResult SnapForBlueprint(PlacementBlueprint blueprint) =>
+ protected virtual SnapResult SnapForBlueprint(HitObjectPlacementBlueprint blueprint) =>
new SnapResult(InputManager.CurrentState.Mouse.Position, null);
public override void Add(Drawable drawable)
{
base.Add(drawable);
- if (drawable is PlacementBlueprint blueprint)
+ if (drawable is HitObjectPlacementBlueprint blueprint)
{
blueprint.Show();
blueprint.UpdateTimeAndPosition(SnapForBlueprint(blueprint));
@@ -106,6 +106,6 @@ namespace osu.Game.Tests.Visual
protected virtual Container CreateHitObjectContainer() => new Container { RelativeSizeAxes = Axes.Both };
protected abstract DrawableHitObject CreateHitObject(HitObject hitObject);
- protected abstract PlacementBlueprint CreateBlueprint();
+ protected abstract HitObjectPlacementBlueprint CreateBlueprint();
}
}
From 0a5a4633807d20d4d95e36e9969112328d89781f Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Mon, 23 Sep 2024 16:36:11 +0200
Subject: [PATCH 110/239] Convert 'grid from points' button to placement tool
---
.../Edit/Blueprints/GridPlacementBlueprint.cs | 83 +++++++++++++++++++
.../Edit/GridFromPointsTool.cs | 78 ++++-------------
.../Edit/OsuGridToolboxGroup.cs | 12 ---
.../Edit/OsuHitObjectComposer.cs | 15 +---
osu.Game/Rulesets/Edit/HitObjectComposer.cs | 13 ++-
5 files changed, 105 insertions(+), 96 deletions(-)
create mode 100644 osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs
new file mode 100644
index 0000000000..f632828f62
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs
@@ -0,0 +1,83 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Input.Events;
+using osu.Game.Rulesets.Edit;
+using osuTK;
+using osuTK.Input;
+
+namespace osu.Game.Rulesets.Osu.Edit.Blueprints
+{
+ public partial class GridPlacementBlueprint : PlacementBlueprint
+ {
+ [Resolved]
+ private HitObjectComposer? hitObjectComposer { get; set; }
+
+ private OsuGridToolboxGroup gridToolboxGroup = null!;
+ private Vector2 originalOrigin;
+ private float originalSpacing;
+ private float originalRotation;
+
+ [BackgroundDependencyLoader]
+ private void load(OsuGridToolboxGroup gridToolboxGroup)
+ {
+ this.gridToolboxGroup = gridToolboxGroup;
+ originalOrigin = gridToolboxGroup.StartPosition.Value;
+ originalSpacing = gridToolboxGroup.Spacing.Value;
+ originalRotation = gridToolboxGroup.GridLinesRotation.Value;
+ }
+
+ public override void EndPlacement(bool commit)
+ {
+ if (!commit && PlacementActive != PlacementState.Finished)
+ {
+ gridToolboxGroup.StartPosition.Value = originalOrigin;
+ gridToolboxGroup.Spacing.Value = originalSpacing;
+ gridToolboxGroup.GridLinesRotation.Value = originalRotation;
+ }
+
+ base.EndPlacement(commit);
+
+ // You typically only place the grid once, so we switch back to the select tool after placement.
+ if (commit && hitObjectComposer is OsuHitObjectComposer osuHitObjectComposer)
+ osuHitObjectComposer.SetSelectTool();
+ }
+
+ protected override bool OnMouseDown(MouseDownEvent e)
+ {
+ switch (e.Button)
+ {
+ case MouseButton.Right:
+ EndPlacement(true);
+ return true;
+
+ case MouseButton.Left:
+ switch (PlacementActive)
+ {
+ case PlacementState.Waiting:
+ BeginPlacement(true);
+ return true;
+
+ case PlacementState.Active:
+ EndPlacement(true);
+ return true;
+ }
+
+ break;
+ }
+
+ return base.OnMouseDown(e);
+ }
+
+ public override void UpdateTimeAndPosition(SnapResult result)
+ {
+ var pos = ToLocalSpace(result.ScreenSpacePosition);
+
+ if (PlacementActive != PlacementState.Active)
+ gridToolboxGroup.StartPosition.Value = pos;
+ else
+ gridToolboxGroup.SetGridFromPoints(gridToolboxGroup.StartPosition.Value, pos);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs
index 9bbeeaafc3..1714dacd17 100644
--- a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs
+++ b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs
@@ -1,79 +1,29 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework.Allocation;
using osu.Framework.Graphics;
-using osu.Framework.Input.Events;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
-using osuTK;
-using osuTK.Input;
+using osu.Game.Rulesets.Edit.Tools;
+using osu.Game.Rulesets.Osu.Edit.Blueprints;
namespace osu.Game.Rulesets.Osu.Edit
{
- public partial class GridFromPointsTool : Drawable
+ public partial class GridFromPointsTool : CompositionTool
{
- [Resolved]
- private OsuGridToolboxGroup gridToolboxGroup { get; set; } = null!;
-
- [Resolved(canBeNull: true)]
- private IPositionSnapProvider? snapProvider { get; set; }
-
- public bool IsPlacing { get; private set; }
-
- private Vector2? startPosition;
-
- protected override void LoadComplete()
+ public GridFromPointsTool()
+ : base("Change grid")
{
- base.LoadComplete();
-
- gridToolboxGroup.GridFromPointsClicked += BeginPlacement;
+ TooltipText = """
+ Left click to set the origin.
+ Left click again to set the rotation and spacing.
+ Right click to only set the origin.
+ """;
}
- public void BeginPlacement()
- {
- IsPlacing = true;
- startPosition = null;
- }
+ public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorGridSnap };
- protected override bool OnMouseDown(MouseDownEvent e)
- {
- if (!IsPlacing)
- return base.OnMouseDown(e);
-
- var pos = snappedLocalPosition(e);
-
- if (!startPosition.HasValue)
- startPosition = pos;
- else
- {
- gridToolboxGroup.SetGridFromPoints(startPosition.Value, pos);
- IsPlacing = false;
- }
-
- if (e.Button == MouseButton.Right)
- IsPlacing = false;
-
- return true;
- }
-
- protected override bool OnMouseMove(MouseMoveEvent e)
- {
- if (!IsPlacing)
- return base.OnMouseMove(e);
-
- var pos = snappedLocalPosition(e);
-
- if (!startPosition.HasValue)
- gridToolboxGroup.StartPosition.Value = pos;
- else
- gridToolboxGroup.SetGridFromPoints(startPosition.Value, pos);
-
- return true;
- }
-
- private Vector2 snappedLocalPosition(UIEvent e)
- {
- return ToLocalSpace(snapProvider?.FindSnappedPositionAndTime(e.ScreenSpaceMousePosition, ~SnapType.GlobalGrids).ScreenSpacePosition ?? e.ScreenSpaceMousePosition);
- }
+ public override PlacementBlueprint CreatePlacementBlueprint() => new GridPlacementBlueprint();
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
index 8752e17dad..db2909a6af 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
@@ -13,7 +13,6 @@ using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
-using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.UI;
@@ -86,11 +85,8 @@ namespace osu.Game.Rulesets.Osu.Edit
private ExpandableSlider startPositionYSlider = null!;
private ExpandableSlider spacingSlider = null!;
private ExpandableSlider gridLinesRotationSlider = null!;
- private RoundedButton gridFromPointsButton = null!;
private EditorRadioButtonCollection gridTypeButtons = null!;
- public event Action? GridFromPointsClicked;
-
public OsuGridToolboxGroup()
: base("grid")
{
@@ -149,12 +145,6 @@ namespace osu.Game.Rulesets.Osu.Edit
Spacing = new Vector2(0f, 10f),
Children = new Drawable[]
{
- gridFromPointsButton = new RoundedButton
- {
- Action = () => GridFromPointsClicked?.Invoke(),
- RelativeSizeAxes = Axes.X,
- Text = "Grid from points",
- },
gridTypeButtons = new EditorRadioButtonCollection
{
RelativeSizeAxes = Axes.X,
@@ -220,8 +210,6 @@ namespace osu.Game.Rulesets.Osu.Edit
expandingContainer?.Expanded.BindValueChanged(v =>
{
- gridFromPointsButton.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint);
- gridFromPointsButton.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None;
gridTypeButtons.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint);
gridTypeButtons.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None;
}, true);
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index e6a9a95b6b..3e24a48d49 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -45,7 +45,8 @@ namespace osu.Game.Rulesets.Osu.Edit
{
new HitCircleCompositionTool(),
new SliderCompositionTool(),
- new SpinnerCompositionTool()
+ new SpinnerCompositionTool(),
+ new GridFromPointsTool(),
};
private readonly Bindable rectangularGridSnapToggle = new Bindable();
@@ -61,8 +62,6 @@ namespace osu.Game.Rulesets.Osu.Edit
private Bindable placementObject;
- private GridFromPointsTool gridFromPointsTool;
-
[Cached(typeof(IDistanceSnapProvider))]
public readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider();
@@ -100,14 +99,6 @@ namespace osu.Game.Rulesets.Osu.Edit
OsuGridToolboxGroup.GridType.BindValueChanged(updatePositionSnapGrid, true);
- LayerAboveRuleset.Add(
- // Place it above the playfield and blueprints, so it takes priority when handling input.
- gridFromPointsTool = new GridFromPointsTool
- {
- RelativeSizeAxes = Axes.Both,
- }
- );
-
RightToolbox.AddRange(new Drawable[]
{
OsuGridToolboxGroup,
@@ -291,7 +282,7 @@ namespace osu.Game.Rulesets.Osu.Edit
foreach (var b in blueprints)
{
- if (b.IsSelected && !gridFromPointsTool.IsPlacing)
+ if (b.IsSelected)
continue;
var snapPositions = b.ScreenSpaceSnapPoints;
diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
index b83c8acec6..2053f9ff5d 100644
--- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs
+++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
@@ -77,8 +77,6 @@ namespace osu.Game.Rulesets.Edit
protected readonly Container LayerBelowRuleset = new Container { RelativeSizeAxes = Axes.Both };
- protected readonly Container LayerAboveRuleset = new Container { RelativeSizeAxes = Axes.Both };
-
protected InputManager InputManager { get; private set; }
private Box leftToolboxBackground;
@@ -145,8 +143,7 @@ namespace osu.Game.Rulesets.Edit
drawableRulesetWrapper,
// layers above playfield
drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer()
- .WithChild(blueprintContainer = CreateBlueprintContainer()),
- drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer().WithChild(LayerAboveRuleset),
+ .WithChild(blueprintContainer = CreateBlueprintContainer())
}
},
new Container
@@ -234,7 +231,7 @@ namespace osu.Game.Rulesets.Edit
sampleBankTogglesCollection.AddRange(BlueprintContainer.SampleBankTernaryStates.Select(b => new DrawableTernaryButton(b)));
- setSelectTool();
+ SetSelectTool();
EditorBeatmap.SelectedHitObjects.CollectionChanged += selectionChanged;
}
@@ -259,7 +256,7 @@ namespace osu.Game.Rulesets.Edit
{
// it's important this is performed before the similar code in EditorRadioButton disables the button.
if (!timing.NewValue)
- setSelectTool();
+ SetSelectTool();
});
EditorBeatmap.HasTiming.BindValueChanged(hasTiming =>
@@ -463,11 +460,11 @@ namespace osu.Game.Rulesets.Edit
if (EditorBeatmap.SelectedHitObjects.Any())
{
// ensure in selection mode if a selection is made.
- setSelectTool();
+ SetSelectTool();
}
}
- private void setSelectTool() => toolboxCollection.Items.First().Select();
+ public void SetSelectTool() => toolboxCollection.Items.First().Select();
private void toolSelected(CompositionTool tool)
{
From fe106217718ce733a9acc1d886f94e0e25a4ca5b Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Mon, 23 Sep 2024 16:39:09 +0200
Subject: [PATCH 111/239] Clarify criteria of grid spacing subdivision
---
osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
index db2909a6af..487d73693f 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
@@ -107,6 +107,7 @@ namespace osu.Game.Rulesets.Osu.Edit
}
// Divide the distance so that there is a good density of grid lines.
+ // This matches the maximum grid size of the grid size cycling hotkey.
float dist = Vector2.Distance(point1, point2);
while (dist >= max_automatic_spacing)
dist /= 2;
From 0f0f490598a575cfa934628f4f7d461767b27f46 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Mon, 23 Sep 2024 17:24:09 +0200
Subject: [PATCH 112/239] Don't snap to global grid while placing grid
---
osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs | 2 ++
osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 2 ++
.../Edit/Compose/Components/ComposeBlueprintContainer.cs | 2 +-
3 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs
index f632828f62..d45d82579f 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs
@@ -70,6 +70,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
return base.OnMouseDown(e);
}
+ public override SnapType SnapType => ~SnapType.GlobalGrids;
+
public override void UpdateTimeAndPosition(SnapResult result)
{
var pos = ToLocalSpace(result.ScreenSpacePosition);
diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs
index d2a54e8e03..2b346d750f 100644
--- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs
+++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs
@@ -71,6 +71,8 @@ namespace osu.Game.Rulesets.Edit
PlacementActive = PlacementState.Finished;
}
+ public virtual SnapType SnapType => SnapType.All;
+
///
/// Updates the time and position of this based on the provided snap information.
///
diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs
index f0296d45aa..cbec8fc7a3 100644
--- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs
@@ -297,7 +297,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void updatePlacementPosition()
{
- var snapResult = Composer.FindSnappedPositionAndTime(InputManager.CurrentState.Mouse.Position);
+ var snapResult = Composer.FindSnappedPositionAndTime(InputManager.CurrentState.Mouse.Position, CurrentPlacement.SnapType);
// if no time was found from positional snapping, we should still quantize to the beat.
snapResult.Time ??= Beatmap.SnapTime(EditorClock.CurrentTime, null);
From b274ed9427b040aaa441be6227c8e6f0d7f1dd13 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Mon, 23 Sep 2024 17:34:45 +0200
Subject: [PATCH 113/239] fix warnings
---
.../Editor/TestSceneBananaShowerPlacementBlueprint.cs | 2 +-
osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs | 2 +-
osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs
index badd8e967d..296d34d628 100644
--- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs
@@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
AddClickStep(MouseButton.Left);
AddClickStep(MouseButton.Right);
AddAssert("banana shower is not placed", () => LastObject == null);
- AddAssert("state is waiting", () => CurrentBlueprint?.PlacementActive == HitObjectPlacementBlueprint.PlacementState.Waiting);
+ AddAssert("state is waiting", () => CurrentBlueprint?.PlacementActive == PlacementBlueprint.PlacementState.Waiting);
}
[Test]
diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs
index 8460e238f6..7323c7a91a 100644
--- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs
@@ -168,7 +168,7 @@ namespace osu.Game.Rulesets.Catch.Edit
if (EditorBeatmap.PlacementObject.Value is JuiceStream)
{
// Juice stream path is not subject to snapping.
- if (BlueprintContainer.CurrentPlacement.PlacementActive is HitObjectPlacementBlueprint.PlacementState.Active)
+ if (BlueprintContainer.CurrentPlacement.PlacementActive is PlacementBlueprint.PlacementState.Active)
return null;
}
diff --git a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs
index 6f3342f8ce..fe74e1b346 100644
--- a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs
+++ b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs
@@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("editor is still current", () => Editor.IsCurrentScreen());
AddAssert("slider not placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(0));
AddAssert("no active placement", () => this.ChildrenOfType().Single().CurrentPlacement.PlacementActive,
- () => Is.EqualTo(HitObjectPlacementBlueprint.PlacementState.Waiting));
+ () => Is.EqualTo(PlacementBlueprint.PlacementState.Waiting));
}
[Test]
From 4da78a8c009c4f2b0c5a254c923d194046b0c5c9 Mon Sep 17 00:00:00 2001
From: tsunyoku
Date: Tue, 24 Sep 2024 10:06:07 +0100
Subject: [PATCH 114/239] make speed bonuses additive, scale distanceBonus
---
.../Difficulty/Evaluators/SpeedEvaluator.cs | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs
index 37fd11391c..4dbc3d9a23 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs
@@ -13,6 +13,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
private const double single_spacing_threshold = 125; // 1.25 circles distance between centers
private const double min_speed_bonus = 75; // ~200BPM
private const double speed_balancing_factor = 40;
+ private const double distance_multiplier = 0.95;
///
/// Evaluates the difficulty of tapping the current object, based on:
@@ -51,11 +52,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
strainTime /= Math.Clamp((strainTime / osuCurrObj.HitWindowGreat) / 0.93, 0.92, 1);
// speedBonus will be 1.0 for BPM < 200
- double speedBonus = 1.0;
+ double speedBonus = 0.0;
// Add additional scaling bonus for streams/bursts higher than 200bpm
if (strainTime < min_speed_bonus)
- speedBonus = 1 + 0.75 * Math.Pow((min_speed_bonus - strainTime) / speed_balancing_factor, 2);
+ speedBonus = 0.75 * Math.Pow((min_speed_bonus - strainTime) / speed_balancing_factor, 2);
double travelDistance = osuPrevObj?.TravelDistance ?? 0;
double distance = travelDistance + osuCurrObj.MinimumJumpDistance;
@@ -64,10 +65,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
distance = Math.Min(distance, single_spacing_threshold);
// Max distance bonus is 2 at single_spacing_threshold
- double distanceBonus = 1 + Math.Pow(distance / single_spacing_threshold, 3.5);
+ double distanceBonus = Math.Pow(distance / single_spacing_threshold, 3.75) * distance_multiplier;
// Base difficulty with all bonuses
- double difficulty = speedBonus * distanceBonus * 1000 / strainTime;
+ double difficulty = (1 + speedBonus + distanceBonus) * 1000 / strainTime;
// Apply penalty if there's doubletappable doubles
return difficulty * doubletapness;
From ac9c1508b1367ab24c7c479636508a89c8bfb3b7 Mon Sep 17 00:00:00 2001
From: James Wilson
Date: Tue, 24 Sep 2024 11:22:46 +0100
Subject: [PATCH 115/239] update incorrect code comment
Co-authored-by: StanR
---
osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs
index 4dbc3d9a23..6a62fa32f3 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs
@@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
// 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly, whilst 0.92 limits the effect of the cap.
strainTime /= Math.Clamp((strainTime / osuCurrObj.HitWindowGreat) / 0.93, 0.92, 1);
- // speedBonus will be 1.0 for BPM < 200
+ // speedBonus will be 0.0 for BPM < 200
double speedBonus = 0.0;
// Add additional scaling bonus for streams/bursts higher than 200bpm
From 98d9b5eec8a1b3ba58e6373dc8c5192a3646b622 Mon Sep 17 00:00:00 2001
From: tsunyoku
Date: Tue, 24 Sep 2024 11:23:34 +0100
Subject: [PATCH 116/239] correct `distanceBonus` code comment
---
osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs
index 6a62fa32f3..ec0bec9b08 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs
@@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
// Cap distance at single_spacing_threshold
distance = Math.Min(distance, single_spacing_threshold);
- // Max distance bonus is 2 at single_spacing_threshold
+ // Max distance bonus is 1 * `distance_multiplier` at single_spacing_threshold
double distanceBonus = Math.Pow(distance / single_spacing_threshold, 3.75) * distance_multiplier;
// Base difficulty with all bonuses
From ce5c666c346ec61a42c16eb8e6b422d01cb95881 Mon Sep 17 00:00:00 2001
From: tsunyoku
Date: Tue, 24 Sep 2024 11:28:15 +0100
Subject: [PATCH 117/239] bump global multiplier
---
osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 67d88b6b01..33c33d82ab 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
public class OsuPerformanceCalculator : PerformanceCalculator
{
- public const double PERFORMANCE_BASE_MULTIPLIER = 1.14; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things.
+ public const double PERFORMANCE_BASE_MULTIPLIER = 1.15; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things.
private double accuracy;
private int scoreMaxCombo;
From 5eb23d3a7157ce32e41e8c5cbcedfc1771c15aac Mon Sep 17 00:00:00 2001
From: tsunyoku
Date: Tue, 24 Sep 2024 12:24:54 +0100
Subject: [PATCH 118/239] balancing attempts
---
osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs | 4 ++--
osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs
index ec0bec9b08..da1cb4b3b3 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
private const double single_spacing_threshold = 125; // 1.25 circles distance between centers
private const double min_speed_bonus = 75; // ~200BPM
private const double speed_balancing_factor = 40;
- private const double distance_multiplier = 0.95;
+ private const double distance_multiplier = 0.94;
///
/// Evaluates the difficulty of tapping the current object, based on:
@@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
distance = Math.Min(distance, single_spacing_threshold);
// Max distance bonus is 1 * `distance_multiplier` at single_spacing_threshold
- double distanceBonus = Math.Pow(distance / single_spacing_threshold, 3.75) * distance_multiplier;
+ double distanceBonus = Math.Pow(distance / single_spacing_threshold, 3.95) * distance_multiplier;
// Base difficulty with all bonuses
double difficulty = (1 + speedBonus + distanceBonus) * 1000 / strainTime;
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
index 1fbe03395c..48e421211f 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
private double currentStrain;
- private double skillMultiplier => 24.963;
+ private double skillMultiplier => 24.983;
private double strainDecayBase => 0.15;
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
From 75dc822540c882ad2cfa867cba7ec687d3f3d814 Mon Sep 17 00:00:00 2001
From: StanR
Date: Tue, 24 Sep 2024 17:57:31 +0500
Subject: [PATCH 119/239] Adjust some multipliers
---
.../Difficulty/Evaluators/RhythmEvaluator.cs | 26 +++++++++----------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
index abf9dbe725..7bda1c29a3 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
@@ -13,9 +13,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
public static class RhythmEvaluator
{
private const int history_time_max = 4 * 1000; // 4 seconds
- private const int history_objects_max = 24;
- private const double rhythm_overall_multiplier = 0.95;
- private const double rhythm_ratio_multiplier = 14.0;
+ private const int history_objects_max = 32;
+ private const double rhythm_overall_multiplier = 0.9;
+ private const double rhythm_ratio_multiplier = 11.5;
///
/// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current .
@@ -78,15 +78,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
double effectiveRatio = windowPenalty * currRatio * fractionMultiplier;
- // bpm change is into slider, this is easy acc window
- if (currObj.BaseObject is Slider)
- effectiveRatio *= 0.125;
-
- // bpm change was from a slider, this is easier typically than circle -> circle
- // unintentional side effect is that bursts with kicksliders at the ends might have lower difficulty than bursts without sliders
- if (prevObj.BaseObject is Slider)
- effectiveRatio *= 0.2;
-
if (firstDeltaSwitch)
{
if (Math.Abs(prevDelta - currDelta) < deltaDifferenceEpsilon)
@@ -96,6 +87,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
}
else
{
+ // bpm change is into slider, this is easy acc window
+ if (currObj.BaseObject is Slider)
+ effectiveRatio *= 0.125;
+
+ // bpm change was from a slider, this is easier typically than circle -> circle
+ // unintentional side effect is that bursts with kicksliders at the ends might have lower difficulty than bursts without sliders
+ if (prevObj.BaseObject is Slider)
+ effectiveRatio *= 0.15;
+
// repeated island polarity (2 -> 4, 3 -> 5)
if (island.IsSimilarPolarity(previousIsland))
effectiveRatio *= 0.3;
@@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
islandCount.Count++;
// repeated island (ex: triplet -> triplet)
- double power = logistic(island.Delta, 4, 0.165, 10);
+ double power = logistic(island.Delta, 2, 0.165, 10);
effectiveRatio *= Math.Min(3.0 / islandCount.Count, Math.Pow(1.0 / islandCount.Count, power));
islandCounts[countIndex] = (islandCount.Island, islandCount.Count);
From 872628b8b87c73d21579a2ffd7aa26058cd6c551 Mon Sep 17 00:00:00 2001
From: StanR
Date: Tue, 24 Sep 2024 18:24:00 +0500
Subject: [PATCH 120/239] Some extra tweaking
---
.../Difficulty/Evaluators/RhythmEvaluator.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
index 7bda1c29a3..edb67f37d9 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
{
private const int history_time_max = 4 * 1000; // 4 seconds
private const int history_objects_max = 32;
- private const double rhythm_overall_multiplier = 0.9;
+ private const double rhythm_overall_multiplier = 0.92;
private const double rhythm_ratio_multiplier = 11.5;
///
@@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
islandCount.Count++;
// repeated island (ex: triplet -> triplet)
- double power = logistic(island.Delta, 2, 0.165, 10);
+ double power = logistic(island.Delta, 2.75, 0.24, 14);
effectiveRatio *= Math.Min(3.0 / islandCount.Count, Math.Pow(1.0 / islandCount.Count, power));
islandCounts[countIndex] = (islandCount.Island, islandCount.Count);
From fe8b9536ffa085326b889dce768ed2b035c3a67f Mon Sep 17 00:00:00 2001
From: StanR
Date: Wed, 25 Sep 2024 18:58:24 +0500
Subject: [PATCH 121/239] Bring back some old nerfs as balancing factor
---
.../Difficulty/Evaluators/RhythmEvaluator.cs | 36 ++++++++++++-------
1 file changed, 23 insertions(+), 13 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
index edb67f37d9..08efb187fe 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
@@ -12,10 +12,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
{
public static class RhythmEvaluator
{
- private const int history_time_max = 4 * 1000; // 4 seconds
+ private const int history_time_max = 5 * 1000; // 5 seconds
private const int history_objects_max = 32;
- private const double rhythm_overall_multiplier = 0.92;
- private const double rhythm_ratio_multiplier = 11.5;
+ private const double rhythm_overall_multiplier = 0.95;
+ private const double rhythm_ratio_multiplier = 12.0;
///
/// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current .
@@ -94,19 +94,20 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
// bpm change was from a slider, this is easier typically than circle -> circle
// unintentional side effect is that bursts with kicksliders at the ends might have lower difficulty than bursts without sliders
if (prevObj.BaseObject is Slider)
- effectiveRatio *= 0.15;
+ effectiveRatio *= 0.3;
// repeated island polarity (2 -> 4, 3 -> 5)
if (island.IsSimilarPolarity(previousIsland))
- effectiveRatio *= 0.3;
+ effectiveRatio *= 0.5;
// previous increase happened a note ago, 1/1->1/2-1/4, dont want to buff this.
if (lastDelta > prevDelta + deltaDifferenceEpsilon && prevDelta > currDelta + deltaDifferenceEpsilon)
effectiveRatio *= 0.125;
- // singletaps are easier to control
- if (island.DeltaCount == 1)
- effectiveRatio *= 0.7;
+ // repeated island size (ex: triplet -> triplet)
+ // TODO: remove this nerf since its staying here only for balancing purposes because of the flawed ratio calculation
+ if (previousIsland.DeltaCount == island.DeltaCount)
+ effectiveRatio *= 0.5;
var islandCount = islandCounts.FirstOrDefault(x => x.Island.Equals(island));
@@ -150,6 +151,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
// Begin counting island until we change speed again.
firstDeltaSwitch = true;
+ // bpm change is into slider, this is easy acc window
+ if (currObj.BaseObject is Slider)
+ effectiveRatio *= 0.6;
+
+ // bpm change was from a slider, this is easier typically than circle -> circle
+ // unintentional side effect is that bursts with kicksliders at the ends might have lower difficulty than bursts without sliders
+ if (prevObj.BaseObject is Slider)
+ effectiveRatio *= 0.6;
+
startRatio = effectiveRatio;
island = new Island((int)currDelta, deltaDifferenceEpsilon);
@@ -180,12 +190,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
DeltaCount++;
}
- public int Delta { get; private set; }
+ public int Delta { get; private set; } = int.MaxValue;
public int DeltaCount { get; private set; }
public void AddDelta(int delta)
{
- if (Delta == default)
+ if (Delta == int.MaxValue)
Delta = Math.Max(delta, OsuDifficultyHitObject.MIN_DELTA_TIME);
DeltaCount++;
@@ -193,9 +203,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
public bool IsSimilarPolarity(Island other)
{
- // consider islands to be of similar polarity only if they're having the same average delta (we don't want to consider 3 singletaps similar to a triple)
- return DeltaCount % 2 == other.DeltaCount % 2 &&
- Math.Abs(Delta - other.Delta) < deltaDifferenceEpsilon;
+ // TODO: consider islands to be of similar polarity only if they're having the same average delta (we don't want to consider 3 singletaps similar to a triple)
+ // naively adding delta check here breaks _a lot_ of maps because of the flawed ratio calculation
+ return DeltaCount % 2 == other.DeltaCount % 2;
}
public bool Equals(Island? other)
From f4055d923f7fe28e0aed4947ca2d66cc5e8bd0c6 Mon Sep 17 00:00:00 2001
From: tsunyoku
Date: Wed, 25 Sep 2024 18:14:15 +0100
Subject: [PATCH 122/239] increase aim skill multiplier
---
osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
index 1fbe03395c..faf91e4652 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
private double currentStrain;
- private double skillMultiplier => 24.963;
+ private double skillMultiplier => 25.18;
private double strainDecayBase => 0.15;
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
From 1c23fd31d74265710b9a7b2be28de0242e8b23aa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bart=C5=82omiej=20Dach?=
Date: Fri, 27 Sep 2024 10:27:54 +0200
Subject: [PATCH 123/239] Ensure `Slider.updateNestedPositions()` actually
updates the position of all nesteds
---
osu.Game.Rulesets.Osu/Objects/Slider.cs | 27 ++++++++++++++-----
osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs | 2 ++
osu.Game.Rulesets.Osu/Objects/SliderTick.cs | 1 +
3 files changed, 24 insertions(+), 6 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index 2b3bb18844..e484efb408 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -204,6 +204,7 @@ namespace osu.Game.Rulesets.Osu.Objects
SpanStartTime = e.SpanStartTime,
StartTime = e.Time,
Position = Position + Path.PositionAt(e.PathProgress),
+ PathProgress = e.PathProgress,
StackHeight = StackHeight,
});
break;
@@ -236,6 +237,7 @@ namespace osu.Game.Rulesets.Osu.Objects
StartTime = StartTime + (e.SpanIndex + 1) * SpanDuration,
Position = Position + Path.PositionAt(e.PathProgress),
StackHeight = StackHeight,
+ PathProgress = e.PathProgress,
});
break;
}
@@ -248,14 +250,27 @@ namespace osu.Game.Rulesets.Osu.Objects
{
endPositionCache.Invalidate();
- if (HeadCircle != null)
- HeadCircle.Position = Position;
+ foreach (var nested in NestedHitObjects)
+ {
+ switch (nested)
+ {
+ case SliderHeadCircle headCircle:
+ headCircle.Position = Position;
+ break;
- if (TailCircle != null)
- TailCircle.Position = EndPosition;
+ case SliderTailCircle tailCircle:
+ tailCircle.Position = EndPosition;
+ break;
- if (LastRepeat != null)
- LastRepeat.Position = RepeatCount % 2 == 0 ? Position : Position + Path.PositionAt(1);
+ case SliderRepeat repeat:
+ repeat.Position = Position + Path.PositionAt(repeat.PathProgress);
+ break;
+
+ case SliderTick tick:
+ tick.Position = Position + Path.PositionAt(tick.PathProgress);
+ break;
+ }
+ }
}
protected void UpdateNestedSamples()
diff --git a/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs
index e95cfd369d..1bbd1e8070 100644
--- a/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs
+++ b/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs
@@ -5,6 +5,8 @@ namespace osu.Game.Rulesets.Osu.Objects
{
public class SliderRepeat : SliderEndCircle
{
+ public double PathProgress { get; set; }
+
public SliderRepeat(Slider slider)
: base(slider)
{
diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
index 74ec4d6eb3..219c2be00b 100644
--- a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
@@ -13,6 +13,7 @@ namespace osu.Game.Rulesets.Osu.Objects
{
public int SpanIndex { get; set; }
public double SpanStartTime { get; set; }
+ public double PathProgress { get; set; }
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
{
From 89f47c46544e7f1928f8b281230fbefd74904619 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bart=C5=82omiej=20Dach?=
Date: Fri, 27 Sep 2024 10:28:32 +0200
Subject: [PATCH 124/239] Fix random mod needlessly trying to fix nested object
positions on its own
---
.../OsuHitObjectGenerationUtils_Reposition.cs | 18 ------------------
1 file changed, 18 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs
index a9ae313a31..f7a0543739 100644
--- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs
+++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs
@@ -232,8 +232,6 @@ namespace osu.Game.Rulesets.Osu.Utils
slider.Position = workingObject.PositionModified = new Vector2(newX, newY);
workingObject.EndPositionModified = slider.EndPosition;
- shiftNestedObjects(slider, workingObject.PositionModified - workingObject.PositionOriginal);
-
return workingObject.PositionModified - previousPosition;
}
@@ -307,22 +305,6 @@ namespace osu.Game.Rulesets.Osu.Utils
return new RectangleF(left, top, right - left, bottom - top);
}
- ///
- /// Shifts all nested s and s by the specified shift.
- ///
- /// whose nested s and s should be shifted
- /// The the 's nested s and s should be shifted by
- private static void shiftNestedObjects(Slider slider, Vector2 shift)
- {
- foreach (var hitObject in slider.NestedHitObjects.Where(o => o is SliderTick || o is SliderRepeat))
- {
- if (!(hitObject is OsuHitObject osuHitObject))
- continue;
-
- osuHitObject.Position += shift;
- }
- }
-
///
/// Clamp a position to playfield, keeping a specified distance from the edges.
///
From 132931f6054d24b116798937ce3f087299523d7b Mon Sep 17 00:00:00 2001
From: Fina <75299710+Finadoggie@users.noreply.github.com>
Date: Fri, 27 Sep 2024 14:04:48 -0700
Subject: [PATCH 125/239] Create variable to check if using slideracc
---
osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index a4ebcd15a4..946a0535db 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
public const double PERFORMANCE_BASE_MULTIPLIER = 1.14; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things.
+ private bool usingClassicSliderAccuracy;
+
private double accuracy;
private int scoreMaxCombo;
private int countGreat;
@@ -33,6 +35,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
var osuAttributes = (OsuDifficultyAttributes)attributes;
+ usingClassicSliderAccuracy = score.Mods.OfType().All(m => m.NoSliderHeadAccuracy.Value);
accuracy = score.Accuracy;
scoreMaxCombo = score.MaxCombo;
countGreat = score.Statistics.GetValueOrDefault(HitResult.Great);
@@ -192,7 +195,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window.
double betterAccuracyPercentage;
int amountHitObjectsWithAccuracy = attributes.HitCircleCount;
- if (score.Mods.OfType().All(m => !m.NoSliderHeadAccuracy.Value))
+ if (!usingClassicSliderAccuracy)
amountHitObjectsWithAccuracy += attributes.SliderCount;
if (amountHitObjectsWithAccuracy > 0)
From 7a849c7e206e599d00735823b10407febf4d1aa3 Mon Sep 17 00:00:00 2001
From: Finadoggie <75299710+Finadoggie@users.noreply.github.com>
Date: Fri, 27 Sep 2024 14:11:12 -0700
Subject: [PATCH 126/239] Fix formatting
---
osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 946a0535db..148d2a8130 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -36,6 +36,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
var osuAttributes = (OsuDifficultyAttributes)attributes;
usingClassicSliderAccuracy = score.Mods.OfType().All(m => m.NoSliderHeadAccuracy.Value);
+
accuracy = score.Accuracy;
scoreMaxCombo = score.MaxCombo;
countGreat = score.Statistics.GetValueOrDefault(HitResult.Great);
From 9b1ae2fe26f53b6f0c529a6f191a1ba55a5b4a9f Mon Sep 17 00:00:00 2001
From: Finadoggie <75299710+Finadoggie@users.noreply.github.com>
Date: Fri, 27 Sep 2024 14:21:15 -0700
Subject: [PATCH 127/239] fix the code such that it actually works when testing
it
---
osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 148d2a8130..dc2113ed40 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
var osuAttributes = (OsuDifficultyAttributes)attributes;
- usingClassicSliderAccuracy = score.Mods.OfType().All(m => m.NoSliderHeadAccuracy.Value);
+ usingClassicSliderAccuracy = !(score.Mods.OfType().All(m => !m.NoSliderHeadAccuracy.Value));
accuracy = score.Accuracy;
scoreMaxCombo = score.MaxCombo;
From 4e2bc0d1bdae7b7f55476de675a1e1caf1d25500 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sat, 28 Sep 2024 15:56:37 +0200
Subject: [PATCH 128/239] place grid with drag instead
---
.../Edit/Blueprints/GridPlacementBlueprint.cs | 44 ++++++++++---------
1 file changed, 24 insertions(+), 20 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs
index d45d82579f..a5b9efd930 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs
@@ -44,30 +44,34 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
osuHitObjectComposer.SetSelectTool();
}
- protected override bool OnMouseDown(MouseDownEvent e)
+ protected override bool OnClick(ClickEvent e)
{
- switch (e.Button)
+ if (e.Button == MouseButton.Left)
{
- case MouseButton.Right:
- EndPlacement(true);
- return true;
-
- case MouseButton.Left:
- switch (PlacementActive)
- {
- case PlacementState.Waiting:
- BeginPlacement(true);
- return true;
-
- case PlacementState.Active:
- EndPlacement(true);
- return true;
- }
-
- break;
+ EndPlacement(true);
+ return true;
}
- return base.OnMouseDown(e);
+ return base.OnClick(e);
+ }
+
+ protected override bool OnDragStart(DragStartEvent e)
+ {
+ if (e.Button == MouseButton.Left)
+ {
+ BeginPlacement(true);
+ return true;
+ }
+
+ return base.OnDragStart(e);
+ }
+
+ protected override void OnDragEnd(DragEndEvent e)
+ {
+ if (PlacementActive == PlacementState.Active)
+ EndPlacement(true);
+
+ base.OnDragEnd(e);
}
public override SnapType SnapType => ~SnapType.GlobalGrids;
From b1e381a391fb1b3a64d141c38c92fe8abab34b33 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sat, 28 Sep 2024 15:59:07 +0200
Subject: [PATCH 129/239] Update tooltip for drag
---
osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs
index 1714dacd17..396fac4754 100644
--- a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs
+++ b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs
@@ -17,8 +17,7 @@ namespace osu.Game.Rulesets.Osu.Edit
{
TooltipText = """
Left click to set the origin.
- Left click again to set the rotation and spacing.
- Right click to only set the origin.
+ Click and drag to set the origin, rotation and spacing.
""";
}
From 1c6e42671acfe269955355ce2d7c875f3568861b Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sat, 28 Sep 2024 17:04:11 +0200
Subject: [PATCH 130/239] return grid placement tool to right toolbox
---
osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs | 9 ---------
osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 12 ++++++++++++
osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 4 +++-
osu.Game/Rulesets/Edit/HitObjectComposer.cs | 8 ++++++++
4 files changed, 23 insertions(+), 10 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs
index 396fac4754..f357d3024f 100644
--- a/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs
+++ b/osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs
@@ -1,9 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Sprites;
-using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Osu.Edit.Blueprints;
@@ -15,14 +12,8 @@ namespace osu.Game.Rulesets.Osu.Edit
public GridFromPointsTool()
: base("Change grid")
{
- TooltipText = """
- Left click to set the origin.
- Click and drag to set the origin, rotation and spacing.
- """;
}
- public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorGridSnap };
-
public override PlacementBlueprint CreatePlacementBlueprint() => new GridPlacementBlueprint();
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
index 487d73693f..b08ecb0e61 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
@@ -13,6 +13,7 @@ using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.UI;
@@ -85,8 +86,11 @@ namespace osu.Game.Rulesets.Osu.Edit
private ExpandableSlider startPositionYSlider = null!;
private ExpandableSlider spacingSlider = null!;
private ExpandableSlider gridLinesRotationSlider = null!;
+ private RoundedButton gridFromPointsButton = null!;
private EditorRadioButtonCollection gridTypeButtons = null!;
+ public event Action? GridFromPointsClicked;
+
public OsuGridToolboxGroup()
: base("grid")
{
@@ -146,6 +150,12 @@ namespace osu.Game.Rulesets.Osu.Edit
Spacing = new Vector2(0f, 10f),
Children = new Drawable[]
{
+ gridFromPointsButton = new RoundedButton
+ {
+ Action = () => GridFromPointsClicked?.Invoke(),
+ RelativeSizeAxes = Axes.X,
+ Text = "Grid from points",
+ },
gridTypeButtons = new EditorRadioButtonCollection
{
RelativeSizeAxes = Axes.X,
@@ -211,6 +221,8 @@ namespace osu.Game.Rulesets.Osu.Edit
expandingContainer?.Expanded.BindValueChanged(v =>
{
+ gridFromPointsButton.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint);
+ gridFromPointsButton.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None;
gridTypeButtons.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint);
gridTypeButtons.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None;
}, true);
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index 3e24a48d49..d9ae312b29 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -46,9 +46,10 @@ namespace osu.Game.Rulesets.Osu.Edit
new HitCircleCompositionTool(),
new SliderCompositionTool(),
new SpinnerCompositionTool(),
- new GridFromPointsTool(),
};
+ private readonly GridFromPointsTool gridFromPointsTool = new GridFromPointsTool();
+
private readonly Bindable rectangularGridSnapToggle = new Bindable();
protected override Drawable CreateHitObjectInspector() => new OsuHitObjectInspector();
@@ -98,6 +99,7 @@ namespace osu.Game.Rulesets.Osu.Edit
updateDistanceSnapGrid();
OsuGridToolboxGroup.GridType.BindValueChanged(updatePositionSnapGrid, true);
+ OsuGridToolboxGroup.GridFromPointsClicked += () => SetCustomTool(gridFromPointsTool);
RightToolbox.AddRange(new Drawable[]
{
diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
index 2053f9ff5d..cf1607acde 100644
--- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs
+++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
@@ -466,6 +466,14 @@ namespace osu.Game.Rulesets.Edit
public void SetSelectTool() => toolboxCollection.Items.First().Select();
+ protected void SetCustomTool(CompositionTool tool)
+ {
+ foreach (var toolBoxRadioButton in toolboxCollection.Items)
+ toolBoxRadioButton.Deselect();
+
+ toolSelected(tool);
+ }
+
private void toolSelected(CompositionTool tool)
{
BlueprintContainer.CurrentTool = tool;
From 3e4cd0aeacf6b5c795261f5d2adae60624d4a003 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sat, 28 Sep 2024 17:04:20 +0200
Subject: [PATCH 131/239] Add tooltip to rounded button
---
osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 4 ++++
osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs | 4 +++-
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
index b08ecb0e61..a5771c595b 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
@@ -155,6 +155,10 @@ namespace osu.Game.Rulesets.Osu.Edit
Action = () => GridFromPointsClicked?.Invoke(),
RelativeSizeAxes = Axes.X,
Text = "Grid from points",
+ TooltipText = """
+ Left click to set the origin.
+ Click and drag to set the origin, rotation and spacing.
+ """
},
gridTypeButtons = new EditorRadioButtonCollection
{
diff --git a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs
index 6aded3fe32..a993867a93 100644
--- a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs
+++ b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs
@@ -8,6 +8,7 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Cursor;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Graphics.Backgrounds;
@@ -17,7 +18,7 @@ using osuTK.Graphics;
namespace osu.Game.Graphics.UserInterfaceV2
{
- public partial class RoundedButton : OsuButton, IFilterable
+ public partial class RoundedButton : OsuButton, IFilterable, IHasTooltip
{
protected TrianglesV2? Triangles { get; private set; }
@@ -107,5 +108,6 @@ namespace osu.Game.Graphics.UserInterfaceV2
}
public bool FilteringActive { get; set; }
+ public LocalisableString TooltipText { get; set; }
}
}
From 4568af8fdae457c121fb53712c62acd6eb79372a Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sat, 28 Sep 2024 17:26:39 +0200
Subject: [PATCH 132/239] Combine drag and clicky interactions
---
.../Edit/Blueprints/GridPlacementBlueprint.cs | 34 +++++++++++++++++--
1 file changed, 32 insertions(+), 2 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs
index a5b9efd930..d0caec269f 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs
@@ -47,12 +47,31 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
protected override bool OnClick(ClickEvent e)
{
if (e.Button == MouseButton.Left)
+ {
+ switch (PlacementActive)
+ {
+ case PlacementState.Waiting:
+ BeginPlacement(true);
+ return true;
+
+ case PlacementState.Active:
+ EndPlacement(true);
+ return true;
+ }
+ }
+
+ return base.OnClick(e);
+ }
+
+ protected override bool OnMouseDown(MouseDownEvent e)
+ {
+ if (e.Button == MouseButton.Right)
{
EndPlacement(true);
return true;
}
- return base.OnClick(e);
+ return base.OnMouseDown(e);
}
protected override bool OnDragStart(DragStartEvent e)
@@ -83,7 +102,18 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
if (PlacementActive != PlacementState.Active)
gridToolboxGroup.StartPosition.Value = pos;
else
- gridToolboxGroup.SetGridFromPoints(gridToolboxGroup.StartPosition.Value, pos);
+ {
+ // Default to the original spacing and rotation if the distance is too small.
+ if (Vector2.Distance(gridToolboxGroup.StartPosition.Value, pos) < 2)
+ {
+ gridToolboxGroup.Spacing.Value = originalSpacing;
+ gridToolboxGroup.GridLinesRotation.Value = originalRotation;
+ }
+ else
+ {
+ gridToolboxGroup.SetGridFromPoints(gridToolboxGroup.StartPosition.Value, pos);
+ }
+ }
}
}
}
From 1912b1fcf3b5743459ab2981a81c6260f3f22f52 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sat, 28 Sep 2024 17:33:24 +0200
Subject: [PATCH 133/239] Revert "Add tooltip to rounded button"
This reverts commit 3e4cd0aeacf6b5c795261f5d2adae60624d4a003.
---
osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 4 ----
osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs | 4 +---
2 files changed, 1 insertion(+), 7 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
index a5771c595b..b08ecb0e61 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
@@ -155,10 +155,6 @@ namespace osu.Game.Rulesets.Osu.Edit
Action = () => GridFromPointsClicked?.Invoke(),
RelativeSizeAxes = Axes.X,
Text = "Grid from points",
- TooltipText = """
- Left click to set the origin.
- Click and drag to set the origin, rotation and spacing.
- """
},
gridTypeButtons = new EditorRadioButtonCollection
{
diff --git a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs
index a993867a93..6aded3fe32 100644
--- a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs
+++ b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs
@@ -8,7 +8,6 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Cursor;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Graphics.Backgrounds;
@@ -18,7 +17,7 @@ using osuTK.Graphics;
namespace osu.Game.Graphics.UserInterfaceV2
{
- public partial class RoundedButton : OsuButton, IFilterable, IHasTooltip
+ public partial class RoundedButton : OsuButton, IFilterable
{
protected TrianglesV2? Triangles { get; private set; }
@@ -108,6 +107,5 @@ namespace osu.Game.Graphics.UserInterfaceV2
}
public bool FilteringActive { get; set; }
- public LocalisableString TooltipText { get; set; }
}
}
From 31d1ba494938201f6f9fd85ef75b52d576ac2ab8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bart=C5=82omiej=20Dach?=
Date: Mon, 30 Sep 2024 09:30:06 +0200
Subject: [PATCH 134/239] Remove unused member
---
.../Utils/OsuHitObjectGenerationUtils_Reposition.cs | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs
index f7a0543739..7073abbc89 100644
--- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs
+++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs
@@ -413,7 +413,6 @@ namespace osu.Game.Rulesets.Osu.Utils
private class WorkingObject
{
public float RotationOriginal { get; }
- public Vector2 PositionOriginal { get; }
public Vector2 PositionModified { get; set; }
public Vector2 EndPositionModified { get; set; }
@@ -424,7 +423,7 @@ namespace osu.Game.Rulesets.Osu.Utils
{
PositionInfo = positionInfo;
RotationOriginal = HitObject is Slider slider ? getSliderRotation(slider) : 0;
- PositionModified = PositionOriginal = HitObject.Position;
+ PositionModified = HitObject.Position;
EndPositionModified = HitObject.EndPosition;
}
}
From ad3007eaadf62916e9af4616e08e2942d0756b44 Mon Sep 17 00:00:00 2001
From: Dean Herbert
Date: Tue, 1 Oct 2024 17:00:32 +0900
Subject: [PATCH 135/239] Adjust `ILocalUserPlayInfo` to expose whether
gameplay is in a paused/break state
---
osu.Desktop/Windows/GameplayWinKeyBlocker.cs | 6 ++--
.../BackgroundDataStoreProcessorTests.cs | 10 +++---
.../Input/ConfineMouseTrackerTest.cs | 15 +++++++-
.../Gameplay/TestSceneOverlayActivation.cs | 4 +--
.../TestSceneGameplayChatDisplay.cs | 6 ++--
.../Database/BackgroundDataStoreProcessor.cs | 2 +-
osu.Game/Input/ConfineMouseTracker.cs | 8 ++---
osu.Game/Input/OsuUserInputManager.cs | 5 +--
osu.Game/OsuGame.cs | 36 +++++++++----------
osu.Game/Overlays/BeatmapListingOverlay.cs | 2 +-
osu.Game/Overlays/Chat/DaySeparator.cs | 4 +--
osu.Game/Overlays/Settings/SettingsSidebar.cs | 2 +-
.../Multiplayer/GameplayChatDisplay.cs | 8 ++---
osu.Game/Screens/Play/ILocalUserPlayInfo.cs | 4 +--
.../Screens/Play/LocalUserPlayingStates.cs | 23 ++++++++++++
osu.Game/Screens/Play/Player.cs | 19 ++++++----
16 files changed, 97 insertions(+), 57 deletions(-)
create mode 100644 osu.Game/Screens/Play/LocalUserPlayingStates.cs
diff --git a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs
index 560f6fdd7f..268dacc077 100644
--- a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs
+++ b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs
@@ -13,7 +13,7 @@ namespace osu.Desktop.Windows
public partial class GameplayWinKeyBlocker : Component
{
private Bindable disableWinKey = null!;
- private IBindable localUserPlaying = null!;
+ private IBindable localUserPlaying = null!;
private IBindable isActive = null!;
[Resolved]
@@ -22,7 +22,7 @@ namespace osu.Desktop.Windows
[BackgroundDependencyLoader]
private void load(ILocalUserPlayInfo localUserInfo, OsuConfigManager config)
{
- localUserPlaying = localUserInfo.IsPlaying.GetBoundCopy();
+ localUserPlaying = localUserInfo.PlayingState.GetBoundCopy();
localUserPlaying.BindValueChanged(_ => updateBlocking());
isActive = host.IsActive.GetBoundCopy();
@@ -34,7 +34,7 @@ namespace osu.Desktop.Windows
private void updateBlocking()
{
- bool shouldDisable = isActive.Value && disableWinKey.Value && localUserPlaying.Value;
+ bool shouldDisable = isActive.Value && disableWinKey.Value && localUserPlaying.Value == LocalUserPlayingStates.Playing;
if (shouldDisable)
host.InputThread.Scheduler.Add(WindowsKey.Disable);
diff --git a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs
index f9f9fa2622..21193b5161 100644
--- a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs
+++ b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs
@@ -22,9 +22,9 @@ namespace osu.Game.Tests.Database
[HeadlessTest]
public partial class BackgroundDataStoreProcessorTests : OsuTestScene, ILocalUserPlayInfo
{
- public IBindable IsPlaying => isPlaying;
+ public IBindable PlayingState => isPlaying;
- private readonly Bindable isPlaying = new Bindable();
+ private readonly Bindable isPlaying = new Bindable();
private BeatmapSetInfo importedSet = null!;
@@ -37,7 +37,7 @@ namespace osu.Game.Tests.Database
[SetUpSteps]
public void SetUpSteps()
{
- AddStep("Set not playing", () => isPlaying.Value = false);
+ AddStep("Set not playing", () => isPlaying.Value = LocalUserPlayingStates.NotPlaying);
}
[Test]
@@ -89,7 +89,7 @@ namespace osu.Game.Tests.Database
});
});
- AddStep("Set playing", () => isPlaying.Value = true);
+ AddStep("Set playing", () => isPlaying.Value = LocalUserPlayingStates.Playing);
AddStep("Reset difficulty", () =>
{
@@ -117,7 +117,7 @@ namespace osu.Game.Tests.Database
});
});
- AddStep("Set not playing", () => isPlaying.Value = false);
+ AddStep("Set not playing", () => isPlaying.Value = LocalUserPlayingStates.NotPlaying);
AddUntilStep("wait for difficulties repopulated", () =>
{
diff --git a/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs b/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs
index 6b43ab83c5..c4fdb2b427 100644
--- a/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs
+++ b/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs
@@ -3,11 +3,13 @@
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Bindables;
using osu.Framework.Configuration;
using osu.Framework.Input;
using osu.Framework.Testing;
using osu.Game.Configuration;
using osu.Game.Input;
+using osu.Game.Screens.Play;
using osu.Game.Tests.Visual;
namespace osu.Game.Tests.Input
@@ -15,9 +17,20 @@ namespace osu.Game.Tests.Input
[HeadlessTest]
public partial class ConfineMouseTrackerTest : OsuGameTestScene
{
+ private readonly Bindable playingState = new Bindable();
+
[Resolved]
private FrameworkConfigManager frameworkConfigManager { get; set; } = null!;
+ [SetUpSteps]
+ public override void SetUpSteps()
+ {
+ base.SetUpSteps();
+
+ // a bit dodgy.
+ AddStep("bind playing state", () => ((IBindable)playingState).BindTo(((ILocalUserPlayInfo)Game).PlayingState));
+ }
+
[TestCase(WindowMode.Windowed)]
[TestCase(WindowMode.Borderless)]
public void TestDisableConfining(WindowMode windowMode)
@@ -88,7 +101,7 @@ namespace osu.Game.Tests.Input
=> AddStep($"set {mode} game-side", () => Game.LocalConfig.SetValue(OsuSetting.ConfineMouseMode, mode));
private void setLocalUserPlayingTo(bool playing)
- => AddStep($"local user {(playing ? "playing" : "not playing")}", () => Game.LocalUserPlaying.Value = playing);
+ => AddStep($"local user {(playing ? "playing" : "not playing")}", () => playingState.Value = playing ? LocalUserPlayingStates.Playing : LocalUserPlayingStates.NotPlaying);
private void gameSideModeIs(OsuConfineMouseMode mode)
=> AddAssert($"mode is {mode} game-side", () => Game.LocalConfig.Get(OsuSetting.ConfineMouseMode) == mode);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs
index 269d104fa3..751405b1d6 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Linq;
using NUnit.Framework;
using osu.Game.Overlays;
@@ -12,7 +10,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public partial class TestSceneOverlayActivation : OsuPlayerTestScene
{
- protected new OverlayTestPlayer Player => base.Player as OverlayTestPlayer;
+ protected new OverlayTestPlayer Player => (OverlayTestPlayer)base.Player;
public override void SetUpSteps()
{
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.cs
index d1a914300f..ca3d484115 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.cs
@@ -25,14 +25,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Cached(typeof(ILocalUserPlayInfo))]
private ILocalUserPlayInfo localUserInfo;
- private readonly Bindable localUserPlaying = new Bindable();
+ private readonly Bindable playingState = new Bindable();
private TextBox textBox => chatDisplay.ChildrenOfType().First();
public TestSceneGameplayChatDisplay()
{
var mockLocalUserInfo = new Mock();
- mockLocalUserInfo.SetupGet(i => i.IsPlaying).Returns(localUserPlaying);
+ mockLocalUserInfo.SetupGet(i => i.PlayingState).Returns(playingState);
localUserInfo = mockLocalUserInfo.Object;
}
@@ -124,6 +124,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert($"chat {(isFocused ? "focused" : "not focused")}", () => textBox.HasFocus == isFocused);
private void setLocalUserPlaying(bool playing) =>
- AddStep($"local user {(playing ? "playing" : "not playing")}", () => localUserPlaying.Value = playing);
+ AddStep($"local user {(playing ? "playing" : "not playing")}", () => playingState.Value = playing ? LocalUserPlayingStates.Playing : LocalUserPlayingStates.NotPlaying);
}
}
diff --git a/osu.Game/Database/BackgroundDataStoreProcessor.cs b/osu.Game/Database/BackgroundDataStoreProcessor.cs
index 0fa785e494..cc7746e34b 100644
--- a/osu.Game/Database/BackgroundDataStoreProcessor.cs
+++ b/osu.Game/Database/BackgroundDataStoreProcessor.cs
@@ -606,7 +606,7 @@ namespace osu.Game.Database
{
// Importantly, also sleep if high performance session is active.
// If we don't do this, memory usage can become runaway due to GC running in a more lenient mode.
- while (localUserPlayInfo?.IsPlaying.Value == true || highPerformanceSessionManager?.IsSessionActive == true)
+ while (localUserPlayInfo?.PlayingState.Value != LocalUserPlayingStates.NotPlaying || highPerformanceSessionManager?.IsSessionActive == true)
{
Logger.Log("Background processing sleeping due to active gameplay...");
Thread.Sleep(TimeToSleepDuringGameplay);
diff --git a/osu.Game/Input/ConfineMouseTracker.cs b/osu.Game/Input/ConfineMouseTracker.cs
index 926f68df45..0f4363e00f 100644
--- a/osu.Game/Input/ConfineMouseTracker.cs
+++ b/osu.Game/Input/ConfineMouseTracker.cs
@@ -15,7 +15,7 @@ namespace osu.Game.Input
{
///
/// Connects with .
- /// If is true, we should also confine the mouse cursor if it has been
+ /// If is playing, we should also confine the mouse cursor if it has been
/// requested with .
///
public partial class ConfineMouseTracker : Component
@@ -25,7 +25,7 @@ namespace osu.Game.Input
private Bindable frameworkMinimiseOnFocusLossInFullscreen;
private Bindable osuConfineMode;
- private IBindable localUserPlaying;
+ private IBindable localUserPlaying;
[BackgroundDependencyLoader]
private void load(ILocalUserPlayInfo localUserInfo, FrameworkConfigManager frameworkConfigManager, OsuConfigManager osuConfigManager)
@@ -37,7 +37,7 @@ namespace osu.Game.Input
frameworkMinimiseOnFocusLossInFullscreen.BindValueChanged(_ => updateConfineMode());
osuConfineMode = osuConfigManager.GetBindable(OsuSetting.ConfineMouseMode);
- localUserPlaying = localUserInfo.IsPlaying.GetBoundCopy();
+ localUserPlaying = localUserInfo.PlayingState.GetBoundCopy();
osuConfineMode.ValueChanged += _ => updateConfineMode();
localUserPlaying.BindValueChanged(_ => updateConfineMode(), true);
@@ -63,7 +63,7 @@ namespace osu.Game.Input
break;
case OsuConfineMouseMode.DuringGameplay:
- frameworkConfineMode.Value = localUserPlaying.Value ? ConfineMouseMode.Always : ConfineMouseMode.Never;
+ frameworkConfineMode.Value = localUserPlaying.Value == LocalUserPlayingStates.Playing ? ConfineMouseMode.Always : ConfineMouseMode.Never;
break;
case OsuConfineMouseMode.Always:
diff --git a/osu.Game/Input/OsuUserInputManager.cs b/osu.Game/Input/OsuUserInputManager.cs
index b8fd0bb11c..ff1b88b65a 100644
--- a/osu.Game/Input/OsuUserInputManager.cs
+++ b/osu.Game/Input/OsuUserInputManager.cs
@@ -3,15 +3,16 @@
using osu.Framework.Bindables;
using osu.Framework.Input;
+using osu.Game.Screens.Play;
using osuTK.Input;
namespace osu.Game.Input
{
public partial class OsuUserInputManager : UserInputManager
{
- protected override bool AllowRightClickFromLongTouch => !LocalUserPlaying.Value;
+ protected override bool AllowRightClickFromLongTouch => PlayingState.Value == LocalUserPlayingStates.NotPlaying;
- public readonly BindableBool LocalUserPlaying = new BindableBool();
+ public readonly Bindable PlayingState = new Bindable();
internal OsuUserInputManager()
{
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 44ba78762a..902a4ddb33 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -175,14 +175,9 @@ namespace osu.Game
///
public readonly IBindable OverlayActivationMode = new Bindable();
- ///
- /// Whether the local user is currently interacting with the game in a way that should not be interrupted.
- ///
- ///
- /// This is exclusively managed by . If other components are mutating this state, a more
- /// resilient method should be used to ensure correct state.
- ///
- public Bindable LocalUserPlaying = new BindableBool();
+ IBindable ILocalUserPlayInfo.PlayingState => playingState;
+
+ private readonly Bindable playingState = new Bindable();
protected OsuScreenStack ScreenStack;
@@ -302,7 +297,7 @@ namespace osu.Game
protected override UserInputManager CreateUserInputManager()
{
var userInputManager = base.CreateUserInputManager();
- (userInputManager as OsuUserInputManager)?.LocalUserPlaying.BindTo(LocalUserPlaying);
+ (userInputManager as OsuUserInputManager)?.PlayingState.BindTo(playingState);
return userInputManager;
}
@@ -391,11 +386,11 @@ namespace osu.Game
// Transfer any runtime changes back to configuration file.
SkinManager.CurrentSkinInfo.ValueChanged += skin => configSkin.Value = skin.NewValue.ID.ToString();
- LocalUserPlaying.BindValueChanged(p =>
+ playingState.BindValueChanged(p =>
{
- BeatmapManager.PauseImports = p.NewValue;
- SkinManager.PauseImports = p.NewValue;
- ScoreManager.PauseImports = p.NewValue;
+ BeatmapManager.PauseImports = p.NewValue != LocalUserPlayingStates.NotPlaying;
+ SkinManager.PauseImports = p.NewValue != LocalUserPlayingStates.NotPlaying;
+ ScoreManager.PauseImports = p.NewValue != LocalUserPlayingStates.NotPlaying;
}, true);
IsActive.BindValueChanged(active => updateActiveState(active.NewValue), true);
@@ -1553,6 +1548,12 @@ namespace osu.Game
scope.SetTag(@"screen", newScreen?.GetType().ReadableName() ?? @"none");
});
+ // reset on screen change for sanity.
+ playingState.Value = LocalUserPlayingStates.NotPlaying;
+
+ if (current is Player oldPlayer)
+ oldPlayer.PlayingState.UnbindFrom(playingState);
+
switch (newScreen)
{
case IntroScreen intro:
@@ -1565,14 +1566,15 @@ namespace osu.Game
versionManager?.Show();
break;
+ case Player player:
+ player.PlayingState.BindTo(playingState);
+ break;
+
default:
versionManager?.Hide();
break;
}
- // reset on screen change for sanity.
- LocalUserPlaying.Value = false;
-
if (current is IOsuScreen currentOsuScreen)
{
OverlayActivationMode.UnbindFrom(currentOsuScreen.OverlayActivationMode);
@@ -1621,7 +1623,5 @@ namespace osu.Game
if (newScreen == null)
Exit();
}
-
- IBindable ILocalUserPlayInfo.IsPlaying => LocalUserPlaying;
}
}
diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs
index 9b2f26e8ae..b47e2b82c0 100644
--- a/osu.Game/Overlays/BeatmapListingOverlay.cs
+++ b/osu.Game/Overlays/BeatmapListingOverlay.cs
@@ -98,7 +98,7 @@ namespace osu.Game.Overlays
apiUser.BindValueChanged(_ => Schedule(() =>
{
if (api.IsLoggedIn)
- replaceResultsAreaContent(Drawable.Empty());
+ replaceResultsAreaContent(Empty());
}));
}
diff --git a/osu.Game/Overlays/Chat/DaySeparator.cs b/osu.Game/Overlays/Chat/DaySeparator.cs
index fd6b15c778..c371877fcb 100644
--- a/osu.Game/Overlays/Chat/DaySeparator.cs
+++ b/osu.Game/Overlays/Chat/DaySeparator.cs
@@ -75,7 +75,7 @@ namespace osu.Game.Overlays.Chat
Height = LineHeight,
Colour = colourProvider?.Background5 ?? Colour4.White,
},
- Drawable.Empty(),
+ Empty(),
new OsuSpriteText
{
Anchor = Anchor.CentreRight,
@@ -87,7 +87,7 @@ namespace osu.Game.Overlays.Chat
}
},
},
- Drawable.Empty(),
+ Empty(),
new Circle
{
Anchor = Anchor.Centre,
diff --git a/osu.Game/Overlays/Settings/SettingsSidebar.cs b/osu.Game/Overlays/Settings/SettingsSidebar.cs
index ddbcd60ef6..d24c0a778c 100644
--- a/osu.Game/Overlays/Settings/SettingsSidebar.cs
+++ b/osu.Game/Overlays/Settings/SettingsSidebar.cs
@@ -66,7 +66,7 @@ namespace osu.Game.Overlays.Settings
[BackgroundDependencyLoader]
private void load()
{
- Size = new Vector2(SettingsSidebar.EXPANDED_WIDTH);
+ Size = new Vector2(EXPANDED_WIDTH);
Padding = new MarginPadding(40);
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs
index d1a73457e3..165e3a2440 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs
@@ -23,9 +23,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
[CanBeNull]
private ILocalUserPlayInfo localUserInfo { get; set; }
- private readonly IBindable localUserPlaying = new Bindable();
+ private readonly IBindable localUserPlaying = new Bindable();
- public override bool PropagatePositionalInputSubTree => !localUserPlaying.Value;
+ public override bool PropagatePositionalInputSubTree => localUserPlaying.Value != LocalUserPlayingStates.Playing;
public Bindable Expanded = new Bindable();
@@ -58,7 +58,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
base.LoadComplete();
if (localUserInfo != null)
- localUserPlaying.BindTo(localUserInfo.IsPlaying);
+ localUserPlaying.BindTo(localUserInfo.PlayingState);
localUserPlaying.BindValueChanged(playing =>
{
@@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
TextBox.HoldFocus = false;
// only hold focus (after sending a message) during breaks
- TextBox.ReleaseFocusOnCommit = playing.NewValue;
+ TextBox.ReleaseFocusOnCommit = playing.NewValue == LocalUserPlayingStates.Playing;
}, true);
Expanded.BindValueChanged(_ => updateExpandedState(), true);
diff --git a/osu.Game/Screens/Play/ILocalUserPlayInfo.cs b/osu.Game/Screens/Play/ILocalUserPlayInfo.cs
index 2d181a09d4..48b610dd8e 100644
--- a/osu.Game/Screens/Play/ILocalUserPlayInfo.cs
+++ b/osu.Game/Screens/Play/ILocalUserPlayInfo.cs
@@ -10,8 +10,8 @@ namespace osu.Game.Screens.Play
public interface ILocalUserPlayInfo
{
///
- /// Whether the local user is currently playing.
+ /// Whether the local user is currently interacting (playing) with the game in a way that should not be interrupted.
///
- IBindable IsPlaying { get; }
+ IBindable PlayingState { get; }
}
}
diff --git a/osu.Game/Screens/Play/LocalUserPlayingStates.cs b/osu.Game/Screens/Play/LocalUserPlayingStates.cs
new file mode 100644
index 0000000000..89f9ea16f7
--- /dev/null
+++ b/osu.Game/Screens/Play/LocalUserPlayingStates.cs
@@ -0,0 +1,23 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Screens.Play
+{
+ public enum LocalUserPlayingStates
+ {
+ ///
+ /// The local player is not current in gameplay.
+ ///
+ NotPlaying,
+
+ ///
+ /// The local player is in a break, paused, or failed or passed but still at the gameplay screen.
+ ///
+ Break,
+
+ ///
+ /// The local user is in active gameplay.
+ ///
+ Playing,
+ }
+}
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 2a66c3d5d3..ee11512bd4 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -94,6 +94,7 @@ namespace osu.Game.Screens.Play
public IBindable LocalUserPlaying => localUserPlaying;
private readonly Bindable localUserPlaying = new Bindable();
+ private readonly Bindable playingState = new Bindable();
public int RestartCount;
@@ -231,9 +232,6 @@ namespace osu.Game.Screens.Play
if (game != null)
gameActive.BindTo(game.IsActive);
- if (game is OsuGame osuGame)
- LocalUserPlaying.BindTo(osuGame.LocalUserPlaying);
-
DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, gameplayMods);
dependencies.CacheAs(DrawableRuleset);
@@ -510,9 +508,16 @@ namespace osu.Game.Screens.Play
private void updateGameplayState()
{
- bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !DrawableRuleset.IsPaused.Value && !breakTracker.IsBreakTime.Value && !GameplayState.HasFailed;
- OverlayActivationMode.Value = inGameplay ? OverlayActivation.Disabled : OverlayActivation.UserTriggered;
- localUserPlaying.Value = inGameplay;
+ bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !GameplayState.HasFailed;
+ bool inBreak = breakTracker.IsBreakTime.Value || DrawableRuleset.IsPaused.Value;
+
+ if (inGameplay)
+ playingState.Value = inBreak ? LocalUserPlayingStates.Break : LocalUserPlayingStates.Playing;
+ else
+ playingState.Value = LocalUserPlayingStates.NotPlaying;
+
+ localUserPlaying.Value = playingState.Value != LocalUserPlayingStates.NotPlaying;
+ OverlayActivationMode.Value = playingState.Value != LocalUserPlayingStates.NotPlaying ? OverlayActivation.Disabled : OverlayActivation.UserTriggered;
}
private void updateSampleDisabledState()
@@ -1279,6 +1284,6 @@ namespace osu.Game.Screens.Play
IBindable ISamplePlaybackDisabler.SamplePlaybackDisabled => samplePlaybackDisabled;
- IBindable ILocalUserPlayInfo.IsPlaying => LocalUserPlaying;
+ public IBindable PlayingState => playingState;
}
}
From 3d54f4a5ab61c41b42d7bffc879b216161f1a634 Mon Sep 17 00:00:00 2001
From: Dean Herbert
Date: Tue, 1 Oct 2024 17:41:59 +0900
Subject: [PATCH 136/239] Make states better defined
---
osu.Game/Screens/Play/LocalUserPlayingStates.cs | 4 ++--
osu.Game/Screens/Play/Player.cs | 8 ++++----
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/osu.Game/Screens/Play/LocalUserPlayingStates.cs b/osu.Game/Screens/Play/LocalUserPlayingStates.cs
index 89f9ea16f7..d1761692a7 100644
--- a/osu.Game/Screens/Play/LocalUserPlayingStates.cs
+++ b/osu.Game/Screens/Play/LocalUserPlayingStates.cs
@@ -6,12 +6,12 @@ namespace osu.Game.Screens.Play
public enum LocalUserPlayingStates
{
///
- /// The local player is not current in gameplay.
+ /// The local player is not current in gameplay. If watching a replay, gameplay always remains in this state.
///
NotPlaying,
///
- /// The local player is in a break, paused, or failed or passed but still at the gameplay screen.
+ /// The local player is in a break, paused, or failed but still at the gameplay screen.
///
Break,
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index ee11512bd4..0e0f3ae8a3 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -508,16 +508,16 @@ namespace osu.Game.Screens.Play
private void updateGameplayState()
{
- bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !GameplayState.HasFailed;
- bool inBreak = breakTracker.IsBreakTime.Value || DrawableRuleset.IsPaused.Value;
+ bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value;
+ bool inBreak = breakTracker.IsBreakTime.Value || DrawableRuleset.IsPaused.Value || GameplayState.HasFailed;
if (inGameplay)
playingState.Value = inBreak ? LocalUserPlayingStates.Break : LocalUserPlayingStates.Playing;
else
playingState.Value = LocalUserPlayingStates.NotPlaying;
- localUserPlaying.Value = playingState.Value != LocalUserPlayingStates.NotPlaying;
- OverlayActivationMode.Value = playingState.Value != LocalUserPlayingStates.NotPlaying ? OverlayActivation.Disabled : OverlayActivation.UserTriggered;
+ localUserPlaying.Value = playingState.Value == LocalUserPlayingStates.Playing;
+ OverlayActivationMode.Value = playingState.Value == LocalUserPlayingStates.Playing ? OverlayActivation.Disabled : OverlayActivation.UserTriggered;
}
private void updateSampleDisabledState()
From 4b1c2c09ee3051c173e0b3942e0f0abc6399c2ff Mon Sep 17 00:00:00 2001
From: Dean Herbert
Date: Tue, 1 Oct 2024 17:53:25 +0900
Subject: [PATCH 137/239] Avoid updates and update notifications appearing in
more gameplay cases
---
osu.Desktop/Updater/VelopackUpdateManager.cs | 32 +++++++++++++++-----
1 file changed, 24 insertions(+), 8 deletions(-)
diff --git a/osu.Desktop/Updater/VelopackUpdateManager.cs b/osu.Desktop/Updater/VelopackUpdateManager.cs
index 7a79284533..dd0014a61f 100644
--- a/osu.Desktop/Updater/VelopackUpdateManager.cs
+++ b/osu.Desktop/Updater/VelopackUpdateManager.cs
@@ -25,6 +25,8 @@ namespace osu.Desktop.Updater
[Resolved]
private ILocalUserPlayInfo? localUserInfo { get; set; }
+ private bool isInGameplay => localUserInfo?.PlayingState.Value != LocalUserPlayingStates.NotPlaying;
+
private UpdateInfo? pendingUpdate;
public VelopackUpdateManager()
@@ -51,7 +53,7 @@ namespace osu.Desktop.Updater
try
{
// Avoid any kind of update checking while gameplay is running.
- if (localUserInfo?.IsPlaying.Value == true)
+ if (isInGameplay)
{
scheduleRecheck = true;
return false;
@@ -61,14 +63,17 @@ namespace osu.Desktop.Updater
// Velopack does support this scenario (see https://github.com/ppy/osu/pull/28743#discussion_r1743495975).
if (pendingUpdate != null)
{
- // If there is an update pending restart, show the notification to restart again.
- notificationOverlay.Post(new UpdateApplicationCompleteNotification
+ runOutsideOfGameplay(() =>
{
- Activated = () =>
+ // If there is an update pending restart, show the notification to restart again.
+ notificationOverlay.Post(new UpdateApplicationCompleteNotification
{
- Task.Run(restartToApplyUpdate);
- return true;
- }
+ Activated = () =>
+ {
+ Task.Run(restartToApplyUpdate);
+ return true;
+ }
+ });
});
return true;
@@ -104,7 +109,7 @@ namespace osu.Desktop.Updater
{
await updateManager.DownloadUpdatesAsync(pendingUpdate, p => notification.Progress = p / 100f).ConfigureAwait(false);
- notification.State = ProgressNotificationState.Completed;
+ runOutsideOfGameplay(() => notification.State = ProgressNotificationState.Completed);
}
catch (Exception e)
{
@@ -131,6 +136,17 @@ namespace osu.Desktop.Updater
return true;
}
+ private void runOutsideOfGameplay(Action action)
+ {
+ if (isInGameplay)
+ {
+ Scheduler.AddDelayed(() => runOutsideOfGameplay(action), 1000);
+ return;
+ }
+
+ action();
+ }
+
private async Task restartToApplyUpdate()
{
await updateManager.WaitExitThenApplyUpdatesAsync(pendingUpdate?.TargetFullRelease).ConfigureAwait(false);
From 8f0fedbd2205ce1a886d8c61fcf8693466c9ab84 Mon Sep 17 00:00:00 2001
From: Dean Herbert
Date: Tue, 1 Oct 2024 18:21:15 +0900
Subject: [PATCH 138/239] Fix local scores never importing due to new
conditionals
---
osu.Game/OsuGame.cs | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 902a4ddb33..23a1ad2f4a 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -390,7 +390,8 @@ namespace osu.Game
{
BeatmapManager.PauseImports = p.NewValue != LocalUserPlayingStates.NotPlaying;
SkinManager.PauseImports = p.NewValue != LocalUserPlayingStates.NotPlaying;
- ScoreManager.PauseImports = p.NewValue != LocalUserPlayingStates.NotPlaying;
+ // For scores, we need to allow imports during "Break" state else local user's scores will never be imported.
+ ScoreManager.PauseImports = p.NewValue == LocalUserPlayingStates.Playing;
}, true);
IsActive.BindValueChanged(active => updateActiveState(active.NewValue), true);
From 7977ce8a0e6265e9a948525d919ab7280c967fec Mon Sep 17 00:00:00 2001
From: Dean Herbert
Date: Tue, 1 Oct 2024 18:25:45 +0900
Subject: [PATCH 139/239] Attempt to fix android class
---
osu.Android/GameplayScreenRotationLocker.cs | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/osu.Android/GameplayScreenRotationLocker.cs b/osu.Android/GameplayScreenRotationLocker.cs
index e5fc354db7..ffd4218ea9 100644
--- a/osu.Android/GameplayScreenRotationLocker.cs
+++ b/osu.Android/GameplayScreenRotationLocker.cs
@@ -6,28 +6,29 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game;
+using osu.Game.Screens.Play;
namespace osu.Android
{
public partial class GameplayScreenRotationLocker : Component
{
- private Bindable localUserPlaying = null!;
+ private IBindable localUserPlaying = null!;
[Resolved]
private OsuGameActivity gameActivity { get; set; } = null!;
[BackgroundDependencyLoader]
- private void load(OsuGame game)
+ private void load(ILocalUserPlayInfo localUserPlayInfo)
{
- localUserPlaying = game.LocalUserPlaying.GetBoundCopy();
+ localUserPlaying = localUserPlayInfo.PlayingState.GetBoundCopy();
localUserPlaying.BindValueChanged(updateLock, true);
}
- private void updateLock(ValueChangedEvent userPlaying)
+ private void updateLock(ValueChangedEvent userPlaying)
{
gameActivity.RunOnUiThread(() =>
{
- gameActivity.RequestedOrientation = userPlaying.NewValue ? ScreenOrientation.Locked : gameActivity.DefaultOrientation;
+ gameActivity.RequestedOrientation = userPlaying.NewValue != LocalUserPlayingStates.NotPlaying ? ScreenOrientation.Locked : gameActivity.DefaultOrientation;
});
}
}
From 8dba4cf760acd07735a12c3b74da7f7a15e1a34d Mon Sep 17 00:00:00 2001
From: Dean Herbert
Date: Tue, 1 Oct 2024 19:55:31 +0900
Subject: [PATCH 140/239] Passing means `NotPlaying`
---
osu.Game/OsuGame.cs | 3 +--
osu.Game/Screens/Play/Player.cs | 2 +-
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 23a1ad2f4a..902a4ddb33 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -390,8 +390,7 @@ namespace osu.Game
{
BeatmapManager.PauseImports = p.NewValue != LocalUserPlayingStates.NotPlaying;
SkinManager.PauseImports = p.NewValue != LocalUserPlayingStates.NotPlaying;
- // For scores, we need to allow imports during "Break" state else local user's scores will never be imported.
- ScoreManager.PauseImports = p.NewValue == LocalUserPlayingStates.Playing;
+ ScoreManager.PauseImports = p.NewValue != LocalUserPlayingStates.NotPlaying;
}, true);
IsActive.BindValueChanged(active => updateActiveState(active.NewValue), true);
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 0e0f3ae8a3..e9e3757629 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -508,7 +508,7 @@ namespace osu.Game.Screens.Play
private void updateGameplayState()
{
- bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value;
+ bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !GameplayState.HasPassed;
bool inBreak = breakTracker.IsBreakTime.Value || DrawableRuleset.IsPaused.Value || GameplayState.HasFailed;
if (inGameplay)
From 8773c34fddea196d1c8b21a364587803a9c59fff Mon Sep 17 00:00:00 2001
From: Dean Herbert
Date: Tue, 1 Oct 2024 19:55:46 +0900
Subject: [PATCH 141/239] Rename enum to non-plural now that it won't conflict
---
osu.Desktop/Updater/VelopackUpdateManager.cs | 2 +-
osu.Desktop/Windows/GameplayWinKeyBlocker.cs | 4 ++--
.../Database/BackgroundDataStoreProcessorTests.cs | 10 +++++-----
osu.Game.Tests/Input/ConfineMouseTrackerTest.cs | 6 +++---
.../Multiplayer/TestSceneGameplayChatDisplay.cs | 4 ++--
osu.Game/Database/BackgroundDataStoreProcessor.cs | 2 +-
osu.Game/Input/ConfineMouseTracker.cs | 4 ++--
osu.Game/Input/OsuUserInputManager.cs | 4 ++--
osu.Game/OsuGame.cs | 12 ++++++------
.../OnlinePlay/Multiplayer/GameplayChatDisplay.cs | 6 +++---
osu.Game/Screens/Play/ILocalUserPlayInfo.cs | 2 +-
...UserPlayingStates.cs => LocalUserPlayingState.cs} | 2 +-
osu.Game/Screens/Play/Player.cs | 12 ++++++------
13 files changed, 35 insertions(+), 35 deletions(-)
rename osu.Game/Screens/Play/{LocalUserPlayingStates.cs => LocalUserPlayingState.cs} (94%)
diff --git a/osu.Desktop/Updater/VelopackUpdateManager.cs b/osu.Desktop/Updater/VelopackUpdateManager.cs
index dd0014a61f..5dda03a3d3 100644
--- a/osu.Desktop/Updater/VelopackUpdateManager.cs
+++ b/osu.Desktop/Updater/VelopackUpdateManager.cs
@@ -25,7 +25,7 @@ namespace osu.Desktop.Updater
[Resolved]
private ILocalUserPlayInfo? localUserInfo { get; set; }
- private bool isInGameplay => localUserInfo?.PlayingState.Value != LocalUserPlayingStates.NotPlaying;
+ private bool isInGameplay => localUserInfo?.PlayingState.Value != LocalUserPlayingState.NotPlaying;
private UpdateInfo? pendingUpdate;
diff --git a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs
index 268dacc077..085c198cc1 100644
--- a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs
+++ b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs
@@ -13,7 +13,7 @@ namespace osu.Desktop.Windows
public partial class GameplayWinKeyBlocker : Component
{
private Bindable disableWinKey = null!;
- private IBindable localUserPlaying = null!;
+ private IBindable localUserPlaying = null!;
private IBindable isActive = null!;
[Resolved]
@@ -34,7 +34,7 @@ namespace osu.Desktop.Windows
private void updateBlocking()
{
- bool shouldDisable = isActive.Value && disableWinKey.Value && localUserPlaying.Value == LocalUserPlayingStates.Playing;
+ bool shouldDisable = isActive.Value && disableWinKey.Value && localUserPlaying.Value == LocalUserPlayingState.Playing;
if (shouldDisable)
host.InputThread.Scheduler.Add(WindowsKey.Disable);
diff --git a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs
index 21193b5161..c40624a3a0 100644
--- a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs
+++ b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs
@@ -22,9 +22,9 @@ namespace osu.Game.Tests.Database
[HeadlessTest]
public partial class BackgroundDataStoreProcessorTests : OsuTestScene, ILocalUserPlayInfo
{
- public IBindable PlayingState => isPlaying;
+ public IBindable PlayingState => isPlaying;
- private readonly Bindable isPlaying = new Bindable();
+ private readonly Bindable isPlaying = new Bindable();
private BeatmapSetInfo importedSet = null!;
@@ -37,7 +37,7 @@ namespace osu.Game.Tests.Database
[SetUpSteps]
public void SetUpSteps()
{
- AddStep("Set not playing", () => isPlaying.Value = LocalUserPlayingStates.NotPlaying);
+ AddStep("Set not playing", () => isPlaying.Value = LocalUserPlayingState.NotPlaying);
}
[Test]
@@ -89,7 +89,7 @@ namespace osu.Game.Tests.Database
});
});
- AddStep("Set playing", () => isPlaying.Value = LocalUserPlayingStates.Playing);
+ AddStep("Set playing", () => isPlaying.Value = LocalUserPlayingState.Playing);
AddStep("Reset difficulty", () =>
{
@@ -117,7 +117,7 @@ namespace osu.Game.Tests.Database
});
});
- AddStep("Set not playing", () => isPlaying.Value = LocalUserPlayingStates.NotPlaying);
+ AddStep("Set not playing", () => isPlaying.Value = LocalUserPlayingState.NotPlaying);
AddUntilStep("wait for difficulties repopulated", () =>
{
diff --git a/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs b/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs
index c4fdb2b427..42f50efdbf 100644
--- a/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs
+++ b/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Tests.Input
[HeadlessTest]
public partial class ConfineMouseTrackerTest : OsuGameTestScene
{
- private readonly Bindable playingState = new Bindable();
+ private readonly Bindable playingState = new Bindable();
[Resolved]
private FrameworkConfigManager frameworkConfigManager { get; set; } = null!;
@@ -28,7 +28,7 @@ namespace osu.Game.Tests.Input
base.SetUpSteps();
// a bit dodgy.
- AddStep("bind playing state", () => ((IBindable)playingState).BindTo(((ILocalUserPlayInfo)Game).PlayingState));
+ AddStep("bind playing state", () => ((IBindable)playingState).BindTo(((ILocalUserPlayInfo)Game).PlayingState));
}
[TestCase(WindowMode.Windowed)]
@@ -101,7 +101,7 @@ namespace osu.Game.Tests.Input
=> AddStep($"set {mode} game-side", () => Game.LocalConfig.SetValue(OsuSetting.ConfineMouseMode, mode));
private void setLocalUserPlayingTo(bool playing)
- => AddStep($"local user {(playing ? "playing" : "not playing")}", () => playingState.Value = playing ? LocalUserPlayingStates.Playing : LocalUserPlayingStates.NotPlaying);
+ => AddStep($"local user {(playing ? "playing" : "not playing")}", () => playingState.Value = playing ? LocalUserPlayingState.Playing : LocalUserPlayingState.NotPlaying);
private void gameSideModeIs(OsuConfineMouseMode mode)
=> AddAssert($"mode is {mode} game-side", () => Game.LocalConfig.Get(OsuSetting.ConfineMouseMode) == mode);
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.cs
index ca3d484115..6a500bbe55 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Cached(typeof(ILocalUserPlayInfo))]
private ILocalUserPlayInfo localUserInfo;
- private readonly Bindable playingState = new Bindable();
+ private readonly Bindable playingState = new Bindable();
private TextBox textBox => chatDisplay.ChildrenOfType().First();
@@ -124,6 +124,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert($"chat {(isFocused ? "focused" : "not focused")}", () => textBox.HasFocus == isFocused);
private void setLocalUserPlaying(bool playing) =>
- AddStep($"local user {(playing ? "playing" : "not playing")}", () => playingState.Value = playing ? LocalUserPlayingStates.Playing : LocalUserPlayingStates.NotPlaying);
+ AddStep($"local user {(playing ? "playing" : "not playing")}", () => playingState.Value = playing ? LocalUserPlayingState.Playing : LocalUserPlayingState.NotPlaying);
}
}
diff --git a/osu.Game/Database/BackgroundDataStoreProcessor.cs b/osu.Game/Database/BackgroundDataStoreProcessor.cs
index cc7746e34b..3efd4da3aa 100644
--- a/osu.Game/Database/BackgroundDataStoreProcessor.cs
+++ b/osu.Game/Database/BackgroundDataStoreProcessor.cs
@@ -606,7 +606,7 @@ namespace osu.Game.Database
{
// Importantly, also sleep if high performance session is active.
// If we don't do this, memory usage can become runaway due to GC running in a more lenient mode.
- while (localUserPlayInfo?.PlayingState.Value != LocalUserPlayingStates.NotPlaying || highPerformanceSessionManager?.IsSessionActive == true)
+ while (localUserPlayInfo?.PlayingState.Value != LocalUserPlayingState.NotPlaying || highPerformanceSessionManager?.IsSessionActive == true)
{
Logger.Log("Background processing sleeping due to active gameplay...");
Thread.Sleep(TimeToSleepDuringGameplay);
diff --git a/osu.Game/Input/ConfineMouseTracker.cs b/osu.Game/Input/ConfineMouseTracker.cs
index 0f4363e00f..eeda92a585 100644
--- a/osu.Game/Input/ConfineMouseTracker.cs
+++ b/osu.Game/Input/ConfineMouseTracker.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Input
private Bindable frameworkMinimiseOnFocusLossInFullscreen;
private Bindable osuConfineMode;
- private IBindable localUserPlaying;
+ private IBindable localUserPlaying;
[BackgroundDependencyLoader]
private void load(ILocalUserPlayInfo localUserInfo, FrameworkConfigManager frameworkConfigManager, OsuConfigManager osuConfigManager)
@@ -63,7 +63,7 @@ namespace osu.Game.Input
break;
case OsuConfineMouseMode.DuringGameplay:
- frameworkConfineMode.Value = localUserPlaying.Value == LocalUserPlayingStates.Playing ? ConfineMouseMode.Always : ConfineMouseMode.Never;
+ frameworkConfineMode.Value = localUserPlaying.Value == LocalUserPlayingState.Playing ? ConfineMouseMode.Always : ConfineMouseMode.Never;
break;
case OsuConfineMouseMode.Always:
diff --git a/osu.Game/Input/OsuUserInputManager.cs b/osu.Game/Input/OsuUserInputManager.cs
index ff1b88b65a..26ce2312d5 100644
--- a/osu.Game/Input/OsuUserInputManager.cs
+++ b/osu.Game/Input/OsuUserInputManager.cs
@@ -10,9 +10,9 @@ namespace osu.Game.Input
{
public partial class OsuUserInputManager : UserInputManager
{
- protected override bool AllowRightClickFromLongTouch => PlayingState.Value == LocalUserPlayingStates.NotPlaying;
+ protected override bool AllowRightClickFromLongTouch => PlayingState.Value == LocalUserPlayingState.NotPlaying;
- public readonly Bindable PlayingState = new Bindable();
+ public readonly Bindable PlayingState = new Bindable();
internal OsuUserInputManager()
{
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 902a4ddb33..935631c8e9 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -175,9 +175,9 @@ namespace osu.Game
///
public readonly IBindable OverlayActivationMode = new Bindable();
- IBindable ILocalUserPlayInfo.PlayingState => playingState;
+ IBindable ILocalUserPlayInfo.PlayingState => playingState;
- private readonly Bindable playingState = new Bindable();
+ private readonly Bindable playingState = new Bindable();
protected OsuScreenStack ScreenStack;
@@ -388,9 +388,9 @@ namespace osu.Game
playingState.BindValueChanged(p =>
{
- BeatmapManager.PauseImports = p.NewValue != LocalUserPlayingStates.NotPlaying;
- SkinManager.PauseImports = p.NewValue != LocalUserPlayingStates.NotPlaying;
- ScoreManager.PauseImports = p.NewValue != LocalUserPlayingStates.NotPlaying;
+ BeatmapManager.PauseImports = p.NewValue != LocalUserPlayingState.NotPlaying;
+ SkinManager.PauseImports = p.NewValue != LocalUserPlayingState.NotPlaying;
+ ScoreManager.PauseImports = p.NewValue != LocalUserPlayingState.NotPlaying;
}, true);
IsActive.BindValueChanged(active => updateActiveState(active.NewValue), true);
@@ -1549,7 +1549,7 @@ namespace osu.Game
});
// reset on screen change for sanity.
- playingState.Value = LocalUserPlayingStates.NotPlaying;
+ playingState.Value = LocalUserPlayingState.NotPlaying;
if (current is Player oldPlayer)
oldPlayer.PlayingState.UnbindFrom(playingState);
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs
index 165e3a2440..d4483044e0 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs
@@ -23,9 +23,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
[CanBeNull]
private ILocalUserPlayInfo localUserInfo { get; set; }
- private readonly IBindable localUserPlaying = new Bindable();
+ private readonly IBindable localUserPlaying = new Bindable();
- public override bool PropagatePositionalInputSubTree => localUserPlaying.Value != LocalUserPlayingStates.Playing;
+ public override bool PropagatePositionalInputSubTree => localUserPlaying.Value != LocalUserPlayingState.Playing;
public Bindable Expanded = new Bindable();
@@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
TextBox.HoldFocus = false;
// only hold focus (after sending a message) during breaks
- TextBox.ReleaseFocusOnCommit = playing.NewValue == LocalUserPlayingStates.Playing;
+ TextBox.ReleaseFocusOnCommit = playing.NewValue == LocalUserPlayingState.Playing;
}, true);
Expanded.BindValueChanged(_ => updateExpandedState(), true);
diff --git a/osu.Game/Screens/Play/ILocalUserPlayInfo.cs b/osu.Game/Screens/Play/ILocalUserPlayInfo.cs
index 48b610dd8e..dd24549c55 100644
--- a/osu.Game/Screens/Play/ILocalUserPlayInfo.cs
+++ b/osu.Game/Screens/Play/ILocalUserPlayInfo.cs
@@ -12,6 +12,6 @@ namespace osu.Game.Screens.Play
///
/// Whether the local user is currently interacting (playing) with the game in a way that should not be interrupted.
///
- IBindable PlayingState { get; }
+ IBindable PlayingState { get; }
}
}
diff --git a/osu.Game/Screens/Play/LocalUserPlayingStates.cs b/osu.Game/Screens/Play/LocalUserPlayingState.cs
similarity index 94%
rename from osu.Game/Screens/Play/LocalUserPlayingStates.cs
rename to osu.Game/Screens/Play/LocalUserPlayingState.cs
index d1761692a7..9ae4130298 100644
--- a/osu.Game/Screens/Play/LocalUserPlayingStates.cs
+++ b/osu.Game/Screens/Play/LocalUserPlayingState.cs
@@ -3,7 +3,7 @@
namespace osu.Game.Screens.Play
{
- public enum LocalUserPlayingStates
+ public enum LocalUserPlayingState
{
///
/// The local player is not current in gameplay. If watching a replay, gameplay always remains in this state.
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index e9e3757629..d6ac279494 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -94,7 +94,7 @@ namespace osu.Game.Screens.Play
public IBindable LocalUserPlaying => localUserPlaying;
private readonly Bindable localUserPlaying = new Bindable();
- private readonly Bindable playingState = new Bindable();
+ private readonly Bindable playingState = new Bindable();
public int RestartCount;
@@ -512,12 +512,12 @@ namespace osu.Game.Screens.Play
bool inBreak = breakTracker.IsBreakTime.Value || DrawableRuleset.IsPaused.Value || GameplayState.HasFailed;
if (inGameplay)
- playingState.Value = inBreak ? LocalUserPlayingStates.Break : LocalUserPlayingStates.Playing;
+ playingState.Value = inBreak ? LocalUserPlayingState.Break : LocalUserPlayingState.Playing;
else
- playingState.Value = LocalUserPlayingStates.NotPlaying;
+ playingState.Value = LocalUserPlayingState.NotPlaying;
- localUserPlaying.Value = playingState.Value == LocalUserPlayingStates.Playing;
- OverlayActivationMode.Value = playingState.Value == LocalUserPlayingStates.Playing ? OverlayActivation.Disabled : OverlayActivation.UserTriggered;
+ localUserPlaying.Value = playingState.Value == LocalUserPlayingState.Playing;
+ OverlayActivationMode.Value = playingState.Value == LocalUserPlayingState.Playing ? OverlayActivation.Disabled : OverlayActivation.UserTriggered;
}
private void updateSampleDisabledState()
@@ -1284,6 +1284,6 @@ namespace osu.Game.Screens.Play
IBindable ISamplePlaybackDisabler.SamplePlaybackDisabled => samplePlaybackDisabled;
- public IBindable PlayingState => playingState;
+ public IBindable PlayingState => playingState;
}
}
From 4f16ecdf1b5bd1dffff3ee00b13c977058ec0558 Mon Sep 17 00:00:00 2001
From: CloneWith
Date: Tue, 1 Oct 2024 20:22:46 +0800
Subject: [PATCH 142/239] Add progress tooltip for ArgonSongProgressBar
---
.../Screens/Play/HUD/ArgonSongProgressBar.cs | 27 ++++++++++++++++++-
1 file changed, 26 insertions(+), 1 deletion(-)
diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs
index 7a7870a775..87f5c495ff 100644
--- a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs
+++ b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs
@@ -6,16 +6,21 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input;
using osu.Framework.Input.Events;
+using osu.Framework.Localisation;
using osu.Framework.Utils;
+using osu.Game.Extensions;
using osu.Game.Graphics;
using osuTK;
namespace osu.Game.Screens.Play.HUD
{
- public partial class ArgonSongProgressBar : SongProgressBar
+ public partial class ArgonSongProgressBar : SongProgressBar, IHasTooltip
{
+
// Parent will handle restricting the area of valid input.
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
@@ -33,6 +38,13 @@ namespace osu.Game.Screens.Play.HUD
private double trackTime => (EndTime - StartTime) * Progress;
+ private float relativePositionX;
+
+ private InputManager? inputManager;
+
+ public LocalisableString TooltipText => $"{(relativePositionX > 0 ? Math.Round(EndTime * relativePositionX / DrawWidth, 2) : relativePositionX > DrawWidth ? EndTime : 0).ToEditorFormattedString()}"
+ + $" - {(relativePositionX > 0 ? Math.Round(relativePositionX / DrawWidth * 100, 2) : relativePositionX > DrawWidth ? 100 : 0)}%";
+
public ArgonSongProgressBar(float barHeight)
{
RelativeSizeAxes = Axes.X;
@@ -74,6 +86,7 @@ namespace osu.Game.Screens.Play.HUD
private void load(OsuColour colours)
{
catchUpColour = colours.BlueDark;
+ inputManager = GetContainingInputManager();
}
protected override void LoadComplete()
@@ -102,6 +115,18 @@ namespace osu.Game.Screens.Play.HUD
{
base.Update();
+ if (inputManager != null)
+ {
+ // Update the cursor position in time
+ var cursorPosition = inputManager.CurrentState.Mouse.Position;
+ relativePositionX = ToLocalSpace(cursorPosition).X;
+ }
+ else
+ {
+ // If null (e.g. before the game starts), try getting the input manager again
+ inputManager = GetContainingInputManager();
+ }
+
playfieldBar.Length = (float)Interpolation.Lerp(playfieldBar.Length, Progress, Math.Clamp(Time.Elapsed / 40, 0, 1));
audioBar.Length = (float)Interpolation.Lerp(audioBar.Length, AudioProgress, Math.Clamp(Time.Elapsed / 40, 0, 1));
From 5af05f1cc987f198a128f071efb345c052bf46fc Mon Sep 17 00:00:00 2001
From: CloneWith
Date: Tue, 1 Oct 2024 20:53:15 +0800
Subject: [PATCH 143/239] Use play length for timestamp calculation
---
osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs
index 87f5c495ff..ea1ebce5f7 100644
--- a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs
+++ b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs
@@ -42,7 +42,7 @@ namespace osu.Game.Screens.Play.HUD
private InputManager? inputManager;
- public LocalisableString TooltipText => $"{(relativePositionX > 0 ? Math.Round(EndTime * relativePositionX / DrawWidth, 2) : relativePositionX > DrawWidth ? EndTime : 0).ToEditorFormattedString()}"
+ public LocalisableString TooltipText => $"{(relativePositionX > 0 ? (EndTime - StartTime) * relativePositionX / DrawWidth : relativePositionX > DrawWidth ? EndTime : 0).ToEditorFormattedString()}"
+ $" - {(relativePositionX > 0 ? Math.Round(relativePositionX / DrawWidth * 100, 2) : relativePositionX > DrawWidth ? 100 : 0)}%";
public ArgonSongProgressBar(float barHeight)
From 5dd28d53521b31bc77c55f7d4fd4b6da80b7252a Mon Sep 17 00:00:00 2001
From: CloneWith
Date: Tue, 1 Oct 2024 21:08:31 +0800
Subject: [PATCH 144/239] Fix extra newline
---
osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs | 1 -
1 file changed, 1 deletion(-)
diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs
index ea1ebce5f7..28a2e1030f 100644
--- a/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs
+++ b/osu.Game/Screens/Play/HUD/ArgonSongProgressBar.cs
@@ -20,7 +20,6 @@ namespace osu.Game.Screens.Play.HUD
{
public partial class ArgonSongProgressBar : SongProgressBar, IHasTooltip
{
-
// Parent will handle restricting the area of valid input.
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
From 4de73dd9c8fc79d4e91402cb7ff7dadc2854fecf Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Tue, 1 Oct 2024 15:28:13 +0200
Subject: [PATCH 145/239] Reapply "Add tooltip to rounded button"
This reverts commit 1912b1fcf3b5743459ab2981a81c6260f3f22f52.
---
osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 4 ++++
osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs | 4 +++-
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
index b08ecb0e61..a5771c595b 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
@@ -155,6 +155,10 @@ namespace osu.Game.Rulesets.Osu.Edit
Action = () => GridFromPointsClicked?.Invoke(),
RelativeSizeAxes = Axes.X,
Text = "Grid from points",
+ TooltipText = """
+ Left click to set the origin.
+ Click and drag to set the origin, rotation and spacing.
+ """
},
gridTypeButtons = new EditorRadioButtonCollection
{
diff --git a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs
index 6aded3fe32..a993867a93 100644
--- a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs
+++ b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs
@@ -8,6 +8,7 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Cursor;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Graphics.Backgrounds;
@@ -17,7 +18,7 @@ using osuTK.Graphics;
namespace osu.Game.Graphics.UserInterfaceV2
{
- public partial class RoundedButton : OsuButton, IFilterable
+ public partial class RoundedButton : OsuButton, IFilterable, IHasTooltip
{
protected TrianglesV2? Triangles { get; private set; }
@@ -107,5 +108,6 @@ namespace osu.Game.Graphics.UserInterfaceV2
}
public bool FilteringActive { get; set; }
+ public LocalisableString TooltipText { get; set; }
}
}
From 9fa2849b14fe6499a6740547a9db5f7ece23fcb4 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Tue, 1 Oct 2024 15:34:12 +0200
Subject: [PATCH 146/239] Fixed tooltip inheritors of RoundedButton
---
osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs | 3 ++-
osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs | 5 ++---
osu.Game/Overlays/Settings/SettingsButton.cs | 4 +---
osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs | 5 ++---
4 files changed, 7 insertions(+), 10 deletions(-)
diff --git a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs
index a993867a93..9b57ebb200 100644
--- a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs
+++ b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs
@@ -108,6 +108,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
}
public bool FilteringActive { get; set; }
- public LocalisableString TooltipText { get; set; }
+
+ public virtual LocalisableString TooltipText { get; set; }
}
}
diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs
index cbdb2ea190..eab394c8f6 100644
--- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs
+++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs
@@ -7,7 +7,6 @@ using System.Diagnostics;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterface;
@@ -21,7 +20,7 @@ using APIUser = osu.Game.Online.API.Requests.Responses.APIUser;
namespace osu.Game.Overlays.BeatmapSet.Buttons
{
- public partial class FavouriteButton : HeaderButton, IHasTooltip
+ public partial class FavouriteButton : HeaderButton
{
public readonly Bindable BeatmapSet = new Bindable();
@@ -32,7 +31,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
private readonly IBindable localUser = new Bindable();
- public LocalisableString TooltipText
+ public override LocalisableString TooltipText
{
get
{
diff --git a/osu.Game/Overlays/Settings/SettingsButton.cs b/osu.Game/Overlays/Settings/SettingsButton.cs
index a837444758..d964a33578 100644
--- a/osu.Game/Overlays/Settings/SettingsButton.cs
+++ b/osu.Game/Overlays/Settings/SettingsButton.cs
@@ -12,7 +12,7 @@ using osu.Game.Graphics.UserInterfaceV2;
namespace osu.Game.Overlays.Settings
{
- public partial class SettingsButton : RoundedButton, IHasTooltip, IConditionalFilterable
+ public partial class SettingsButton : RoundedButton, IConditionalFilterable
{
public SettingsButton()
{
@@ -20,8 +20,6 @@ namespace osu.Game.Overlays.Settings
Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS };
}
- public LocalisableString TooltipText { get; set; }
-
public IEnumerable Keywords { get; set; } = Array.Empty();
public BindableBool CanBeShown { get; } = new BindableBool(true);
diff --git a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs
index 2e669fd1b2..56e2719e9c 100644
--- a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs
@@ -3,7 +3,6 @@
using osu.Framework.Allocation;
using osu.Framework.Bindables;
-using osu.Framework.Graphics.Cursor;
using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Online;
@@ -11,7 +10,7 @@ using osu.Game.Online.Rooms;
namespace osu.Game.Screens.OnlinePlay.Components
{
- public abstract partial class ReadyButton : RoundedButton, IHasTooltip
+ public abstract partial class ReadyButton : RoundedButton
{
public new readonly BindableBool Enabled = new BindableBool();
@@ -29,7 +28,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
private void updateState() =>
base.Enabled.Value = availability.Value.State == DownloadState.LocallyAvailable && Enabled.Value;
- public virtual LocalisableString TooltipText
+ public override LocalisableString TooltipText
{
get
{
From c84bb4b7978dd4e0be87c2a17912b2f204f95207 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Tue, 1 Oct 2024 15:38:21 +0200
Subject: [PATCH 147/239] Update tooltip
---
osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
index a5771c595b..9ef1aa0ff8 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
@@ -157,7 +157,9 @@ namespace osu.Game.Rulesets.Osu.Edit
Text = "Grid from points",
TooltipText = """
Left click to set the origin.
- Click and drag to set the origin, rotation and spacing.
+ Left click again to set the spacing and rotation.
+ Right click to only set the origin.
+ Click and drag to set the origin, spacing and rotation.
"""
},
gridTypeButtons = new EditorRadioButtonCollection
From 4959045851609627cd8326cf4e2ceedd3f5578ac Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Tue, 1 Oct 2024 15:53:39 +0200
Subject: [PATCH 148/239] Remove 'Centre on selected object' button
---
.../Edit/OsuGridToolboxGroup.cs | 25 -------------------
1 file changed, 25 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
index 563135a1dd..089a8a84cd 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
@@ -16,7 +16,6 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets.Edit;
-using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Components.RadioButtons;
@@ -88,7 +87,6 @@ namespace osu.Game.Rulesets.Osu.Edit
private ExpandableSlider spacingSlider = null!;
private ExpandableSlider gridLinesRotationSlider = null!;
private RoundedButton gridFromPointsButton = null!;
- private ExpandableButton useSelectedObjectPositionButton = null!;
private EditorRadioButtonCollection gridTypeButtons = null!;
public event Action? GridFromPointsClicked;
@@ -135,19 +133,6 @@ namespace osu.Game.Rulesets.Osu.Edit
Current = StartPositionY,
KeyboardStep = 1,
},
- useSelectedObjectPositionButton = new ExpandableButton
- {
- ExpandedLabelText = "Centre on selected object",
- Action = () =>
- {
- if (editorBeatmap.SelectedHitObjects.Count != 1)
- return;
-
- StartPosition.Value = ((IHasPosition)editorBeatmap.SelectedHitObjects.Single()).Position;
- updateEnabledStates();
- },
- RelativeSizeAxes = Axes.X,
- },
spacingSlider = new ExpandableSlider
{
Current = Spacing,
@@ -260,25 +245,15 @@ namespace osu.Game.Rulesets.Osu.Edit
}
}, true);
- editorBeatmap.BeatmapReprocessed += updateEnabledStates;
- editorBeatmap.SelectedHitObjects.BindCollectionChanged((_, _) => updateEnabledStates());
expandingContainer?.Expanded.BindValueChanged(v =>
{
gridFromPointsButton.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint);
gridFromPointsButton.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None;
gridTypeButtons.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint);
gridTypeButtons.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None;
- updateEnabledStates();
}, true);
}
- private void updateEnabledStates()
- {
- useSelectedObjectPositionButton.Enabled.Value = expandingContainer?.Expanded.Value == true
- && editorBeatmap.SelectedHitObjects.Count == 1
- && StartPosition.Value != ((IHasPosition)editorBeatmap.SelectedHitObjects.Single()).Position;
- }
-
private float normalizeRotation(float rotation, float period)
{
return ((rotation + 360 + period * 0.5f) % period) - period * 0.5f;
From 4bbefa360cd6b9c2358530f157b6f3d19f16f68e Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Tue, 1 Oct 2024 15:57:34 +0200
Subject: [PATCH 149/239] fix using directive
---
osu.Game/Overlays/Settings/SettingsButton.cs | 1 -
1 file changed, 1 deletion(-)
diff --git a/osu.Game/Overlays/Settings/SettingsButton.cs b/osu.Game/Overlays/Settings/SettingsButton.cs
index d964a33578..196ddca953 100644
--- a/osu.Game/Overlays/Settings/SettingsButton.cs
+++ b/osu.Game/Overlays/Settings/SettingsButton.cs
@@ -6,7 +6,6 @@ using System.Collections.Generic;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Cursor;
using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterfaceV2;
From 87835f2481f5945262dc4e94810e6ae6c9577005 Mon Sep 17 00:00:00 2001
From: StanR
Date: Wed, 2 Oct 2024 19:47:22 +0500
Subject: [PATCH 150/239] Uncap speed od accuracy scaling
---
osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 67d88b6b01..2941aeb936 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -177,7 +177,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double relevantAccuracy = attributes.SpeedNoteCount == 0 ? 0 : (relevantCountGreat * 6.0 + relevantCountOk * 2.0 + relevantCountMeh) / (attributes.SpeedNoteCount * 6.0);
// Scale the speed value with accuracy and OD.
- speedValue *= (0.95 + Math.Pow(attributes.OverallDifficulty, 2) / 750) * Math.Pow((accuracy + relevantAccuracy) / 2.0, (14.5 - Math.Max(attributes.OverallDifficulty, 8)) / 2);
+ speedValue *= (0.95 + Math.Pow(attributes.OverallDifficulty, 2) / 750) * Math.Pow((accuracy + relevantAccuracy) / 2.0, (14.5 - attributes.OverallDifficulty) / 2);
// Scale the speed value with # of 50s to punish doubletapping.
speedValue *= Math.Pow(0.99, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0);
From 1039d4e45de192aedd8e97ed05addd068df8dacc Mon Sep 17 00:00:00 2001
From: Dean Herbert
Date: Thu, 3 Oct 2024 16:21:27 +0900
Subject: [PATCH 151/239] Increase chat font size again
---
osu.Game/Online/Chat/StandAloneChatDisplay.cs | 1 -
osu.Game/Overlays/Chat/ChatLine.cs | 8 ++++----
2 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs
index e100b5fe5b..187191d232 100644
--- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs
+++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs
@@ -205,7 +205,6 @@ namespace osu.Game.Online.Chat
protected partial class StandAloneMessage : ChatLine
{
- protected override float FontSize => 13;
protected override float Spacing => 5;
protected override float UsernameWidth => 90;
diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs
index 3f8862de36..e386f2ac09 100644
--- a/osu.Game/Overlays/Chat/ChatLine.cs
+++ b/osu.Game/Overlays/Chat/ChatLine.cs
@@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Chat
public IReadOnlyCollection DrawableContentFlow => drawableContentFlow;
- protected virtual float FontSize => 12;
+ private const float font_size = 13;
protected virtual float Spacing => 15;
@@ -183,13 +183,13 @@ namespace osu.Game.Overlays.Chat
Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft,
Spacing = new Vector2(-1, 0),
- Font = OsuFont.GetFont(size: FontSize, weight: FontWeight.SemiBold, fixedWidth: true),
+ Font = OsuFont.GetFont(size: font_size, weight: FontWeight.SemiBold, fixedWidth: true),
AlwaysPresent = true,
},
drawableUsername = new DrawableChatUsername(message.Sender)
{
Width = UsernameWidth,
- FontSize = FontSize,
+ FontSize = font_size,
AutoSizeAxes = Axes.Y,
Origin = Anchor.TopRight,
Anchor = Anchor.TopRight,
@@ -258,7 +258,7 @@ namespace osu.Game.Overlays.Chat
private void styleMessageContent(SpriteText text)
{
text.Shadow = false;
- text.Font = text.Font.With(size: FontSize, italics: Message.IsAction, weight: isMention ? FontWeight.SemiBold : FontWeight.Medium);
+ text.Font = text.Font.With(size: font_size, italics: Message.IsAction, weight: isMention ? FontWeight.SemiBold : FontWeight.Medium);
Color4 messageColour = colourProvider?.Content1 ?? Colour4.White;
From ecf144f4a5866a29ac6d792c6ed73c0619f3b8bd Mon Sep 17 00:00:00 2001
From: Dean Herbert
Date: Thu, 3 Oct 2024 17:11:25 +0900
Subject: [PATCH 152/239] Add failing test of importing failed replay in
`OsuGame` flow
We had a test covering this but it wasn't within `OsuGame` so didn't
have full import blocking coverage (see
https://github.com/ppy/osu/blob/cbbe2f9dc04abb052a0d762aa798eba094fdd80c/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs#L89-L88).
---
.../Navigation/TestSceneScreenNavigation.cs | 31 +++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
index eda7ce925a..d374ae1bd9 100644
--- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
@@ -364,6 +364,37 @@ namespace osu.Game.Tests.Visual.Navigation
AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != getOriginalPlayer() && Game.ScreenStack.CurrentScreen is Player);
}
+ [Test]
+ public void TestSaveFailedReplay()
+ {
+ Player player = null;
+
+ Screens.Select.SongSelect songSelect = null;
+ PushAndConfirm(() => songSelect = new TestPlaySongSelect());
+ AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
+
+ AddStep("import beatmap", () => BeatmapImportHelper.LoadOszIntoOsu(Game, virtualTrack: true).WaitSafely());
+
+ AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
+
+ AddStep("press enter", () => InputManager.Key(Key.Enter));
+
+ AddUntilStep("wait for player", () =>
+ {
+ DismissAnyNotifications();
+ return (player = Game.ScreenStack.CurrentScreen as Player) != null;
+ });
+
+ AddUntilStep("wait for fail", () => player.GameplayState.HasFailed);
+
+ AddUntilStep("fail screen displayed", () => player.ChildrenOfType().First().State.Value == Visibility.Visible);
+ AddUntilStep("wait for button clickable", () => player.ChildrenOfType().First().ChildrenOfType().First().Enabled.Value);
+
+ AddUntilStep("score not in database", () => Realm.Run(r => r.Find(player.Score.ScoreInfo.ID) == null));
+ AddStep("click save button", () => player.ChildrenOfType().First().ChildrenOfType().First().TriggerClick());
+ AddUntilStep("score in database", () => Realm.Run(r => r.Find(player.Score.ScoreInfo.ID) != null));
+ }
+
[Test]
public void TestExitImmediatelyAfterCompletion()
{
From 24d6ea5444a5a782d29ad64dc129237587bae222 Mon Sep 17 00:00:00 2001
From: Dean Herbert
Date: Thu, 3 Oct 2024 17:11:41 +0900
Subject: [PATCH 153/239] Change failed states to be considered as `NotPlaying`
---
osu.Game/Screens/Play/Player.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index d6ac279494..536050c9bd 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -508,8 +508,8 @@ namespace osu.Game.Screens.Play
private void updateGameplayState()
{
- bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !GameplayState.HasPassed;
- bool inBreak = breakTracker.IsBreakTime.Value || DrawableRuleset.IsPaused.Value || GameplayState.HasFailed;
+ bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !GameplayState.HasPassed && !GameplayState.HasFailed;
+ bool inBreak = breakTracker.IsBreakTime.Value || DrawableRuleset.IsPaused.Value;
if (inGameplay)
playingState.Value = inBreak ? LocalUserPlayingState.Break : LocalUserPlayingState.Playing;
From 80dffa905a2596ba43805f3653d91b4eeed6ff1f Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Thu, 3 Oct 2024 11:13:49 +0200
Subject: [PATCH 154/239] Use new keyword instead of overriding TooltipText to
remove setter
---
osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs | 2 +-
osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs | 2 +-
osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs
index 9b57ebb200..5875874cf6 100644
--- a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs
+++ b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs
@@ -109,6 +109,6 @@ namespace osu.Game.Graphics.UserInterfaceV2
public bool FilteringActive { get; set; }
- public virtual LocalisableString TooltipText { get; set; }
+ public LocalisableString TooltipText { get; set; }
}
}
diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs
index eab394c8f6..9d6d617082 100644
--- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs
+++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs
@@ -31,7 +31,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
private readonly IBindable localUser = new Bindable();
- public override LocalisableString TooltipText
+ public new LocalisableString TooltipText
{
get
{
diff --git a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs
index 56e2719e9c..95fb48e3ce 100644
--- a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
private void updateState() =>
base.Enabled.Value = availability.Value.State == DownloadState.LocallyAvailable && Enabled.Value;
- public override LocalisableString TooltipText
+ public new virtual LocalisableString TooltipText
{
get
{
From 2c39ecbda3ee3b20958b0904519b534788f61477 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Thu, 3 Oct 2024 11:16:37 +0200
Subject: [PATCH 155/239] Add xmldoc to SnapType
---
osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 3 +++
1 file changed, 3 insertions(+)
diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs
index 2b346d750f..a36de02433 100644
--- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs
+++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs
@@ -71,6 +71,9 @@ namespace osu.Game.Rulesets.Edit
PlacementActive = PlacementState.Finished;
}
+ ///
+ /// Determines which objects to snap to for the snap result in .
+ ///
public virtual SnapType SnapType => SnapType.All;
///
From df730e6b6f1fb98e902a1df561272af9ba5fb058 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bart=C5=82omiej=20Dach?=
Date: Wed, 28 Aug 2024 11:45:32 +0200
Subject: [PATCH 156/239] Implement "form" colour palette control
---
.../UserInterface/TestSceneFormControls.cs | 11 +
.../UserInterfaceV2/FormColourPalette.cs | 226 ++++++++++++++++++
2 files changed, 237 insertions(+)
create mode 100644 osu.Game/Graphics/UserInterfaceV2/FormColourPalette.cs
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs
index 2a0b0515a1..518c3fc693 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs
@@ -105,6 +105,17 @@ namespace osu.Game.Tests.Visual.UserInterface
Caption = "Audio file",
PlaceholderText = "Select an audio file",
},
+ new FormColourPalette
+ {
+ Caption = "Combo colours",
+ Colours =
+ {
+ Colour4.Red,
+ Colour4.Green,
+ Colour4.Blue,
+ Colour4.Yellow,
+ }
+ },
},
},
}
diff --git a/osu.Game/Graphics/UserInterfaceV2/FormColourPalette.cs b/osu.Game/Graphics/UserInterfaceV2/FormColourPalette.cs
new file mode 100644
index 0000000000..a2c1c1622e
--- /dev/null
+++ b/osu.Game/Graphics/UserInterfaceV2/FormColourPalette.cs
@@ -0,0 +1,226 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Specialized;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Cursor;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Input.Events;
+using osu.Framework.Localisation;
+using osu.Game.Graphics.Containers;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Overlays;
+using osu.Game.Resources.Localisation.Web;
+using osuTK;
+
+namespace osu.Game.Graphics.UserInterfaceV2
+{
+ public partial class FormColourPalette : CompositeDrawable
+ {
+ public BindableList Colours { get; } = new BindableList();
+
+ public LocalisableString Caption { get; init; }
+ public LocalisableString HintText { get; init; }
+
+ private Box background = null!;
+ private FormFieldCaption caption = null!;
+ private FillFlowContainer flow = null!;
+
+ [Resolved]
+ private OverlayColourProvider colourProvider { get; set; } = null!;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+
+ Masking = true;
+ CornerRadius = 5;
+
+ RoundedButton button;
+
+ InternalChildren = new Drawable[]
+ {
+ background = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = colourProvider.Background5,
+ },
+ new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Padding = new MarginPadding(9),
+ Spacing = new Vector2(7),
+ Direction = FillDirection.Vertical,
+ Children = new Drawable[]
+ {
+ caption = new FormFieldCaption
+ {
+ Caption = Caption,
+ TooltipText = HintText,
+ },
+ flow = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Full,
+ Spacing = new Vector2(5),
+ Child = button = new RoundedButton
+ {
+ Action = () => Colours.Add(Colour4.White),
+ Size = new Vector2(70, 25),
+ Text = "+",
+ }
+ }
+ },
+ },
+ };
+
+ flow.SetLayoutPosition(button, float.MaxValue);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ Colours.BindCollectionChanged((_, args) =>
+ {
+ if (args.Action != NotifyCollectionChangedAction.Replace)
+ updateColours();
+ }, true);
+ updateState();
+ }
+
+ protected override bool OnHover(HoverEvent e)
+ {
+ updateState();
+ return true;
+ }
+
+ protected override void OnHoverLost(HoverLostEvent e)
+ {
+ base.OnHoverLost(e);
+ updateState();
+ }
+
+ private void updateState()
+ {
+ background.Colour = colourProvider.Background5;
+ caption.Colour = colourProvider.Content2;
+
+ BorderThickness = IsHovered ? 2 : 0;
+
+ if (IsHovered)
+ BorderColour = colourProvider.Light4;
+ }
+
+ private void updateColours()
+ {
+ flow.RemoveAll(d => d is ColourButton, true);
+
+ for (int i = 0; i < Colours.Count; ++i)
+ {
+ // copy to avoid accesses to modified closure.
+ int colourIndex = i;
+ var colourButton = new ColourButton { Current = { Value = Colours[colourIndex] } };
+ colourButton.Current.BindValueChanged(colour => Colours[colourIndex] = colour.NewValue);
+ colourButton.DeleteRequested = () => Colours.RemoveAt(flow.IndexOf(colourButton));
+ flow.Add(colourButton);
+ }
+ }
+
+ private partial class ColourButton : OsuClickableContainer, IHasPopover, IHasContextMenu
+ {
+ public Bindable Current { get; } = new Bindable();
+ public Action? DeleteRequested { get; set; }
+
+ private Box background = null!;
+ private OsuSpriteText hexCode = null!;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Size = new Vector2(70, 25);
+
+ Masking = true;
+ CornerRadius = 3;
+ Action = this.ShowPopover;
+
+ Children = new Drawable[]
+ {
+ background = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ },
+ hexCode = new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ Current.BindValueChanged(_ => updateState(), true);
+ }
+
+ public Popover GetPopover() => new ColourPickerPopover
+ {
+ Current = { BindTarget = Current }
+ };
+
+ public MenuItem[] ContextMenuItems => new MenuItem[]
+ {
+ new OsuMenuItem(CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => DeleteRequested?.Invoke())
+ };
+
+ private void updateState()
+ {
+ background.Colour = Current.Value;
+ hexCode.Text = Current.Value.ToHex();
+ hexCode.Colour = OsuColour.ForegroundTextColourFor(Current.Value);
+ }
+ }
+
+ private partial class ColourPickerPopover : OsuPopover, IHasCurrentValue
+ {
+ public Bindable Current
+ {
+ get => current.Current;
+ set => current.Current = value;
+ }
+
+ private readonly BindableWithCurrent current = new BindableWithCurrent();
+
+ public ColourPickerPopover()
+ : base(false)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OverlayColourProvider colourProvider)
+ {
+ Child = new OsuColourPicker
+ {
+ Current = { BindTarget = Current }
+ };
+
+ Body.BorderThickness = 2;
+ Body.BorderColour = colourProvider.Highlight1;
+ Content.Padding = new MarginPadding(2);
+ }
+ }
+ }
+}
From b15608343b8d201c77ffeafd5081faead2257d09 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bart=C5=82omiej=20Dach?=
Date: Wed, 28 Aug 2024 12:17:39 +0200
Subject: [PATCH 157/239] Replace setup screen controls with new "form"
controls
---
.../Edit/Setup/ManiaDifficultySection.cs | 69 ++++++-----
.../Edit/Setup/OsuDifficultySection.cs | 88 +++++++-------
.../Edit/Setup/TaikoDifficultySection.cs | 52 ++++-----
.../Visual/Editing/TestSceneDesignSection.cs | 6 +-
.../Editing/TestSceneMetadataSection.cs | 12 +-
.../UserInterfaceV2/FormColourPalette.cs | 2 +-
.../UserInterfaceV2/FormFileSelector.cs | 6 +-
osu.Game/Screens/Edit/Setup/ColoursSection.cs | 8 +-
osu.Game/Screens/Edit/Setup/DesignSection.cs | 60 +++++-----
.../Screens/Edit/Setup/DifficultySection.cs | 76 ++++++------
.../Edit/Setup/LabelledRomanisedTextBox.cs | 22 ----
.../Screens/Edit/Setup/MetadataSection.cs | 60 +++++-----
.../Screens/Edit/Setup/ResourcesSection.cs | 35 ++----
osu.Game/Screens/Edit/Setup/SetupScreen.cs | 110 +++++++++++-------
osu.Game/Screens/Edit/Setup/SetupSection.cs | 17 +--
15 files changed, 306 insertions(+), 317 deletions(-)
delete mode 100644 osu.Game/Screens/Edit/Setup/LabelledRomanisedTextBox.cs
diff --git a/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs b/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs
index 7168504309..ed1de591f6 100644
--- a/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs
@@ -19,12 +19,12 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup
{
public override LocalisableString Title => EditorSetupStrings.DifficultyHeader;
- private LabelledSliderBar keyCountSlider { get; set; } = null!;
- private LabelledSwitchButton specialStyle { get; set; } = null!;
- private LabelledSliderBar healthDrainSlider { get; set; } = null!;
- private LabelledSliderBar overallDifficultySlider { get; set; } = null!;
- private LabelledSliderBar baseVelocitySlider { get; set; } = null!;
- private LabelledSliderBar tickRateSlider { get; set; } = null!;
+ private FormSliderBar keyCountSlider { get; set; } = null!;
+ private FormCheckBox specialStyle { get; set; } = null!;
+ private FormSliderBar healthDrainSlider { get; set; } = null!;
+ private FormSliderBar overallDifficultySlider { get; set; } = null!;
+ private FormSliderBar baseVelocitySlider { get; set; } = null!;
+ private FormSliderBar tickRateSlider { get; set; } = null!;
[Resolved]
private Editor? editor { get; set; }
@@ -37,77 +37,76 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup
{
Children = new Drawable[]
{
- keyCountSlider = new LabelledSliderBar
+ keyCountSlider = new FormSliderBar
{
- Label = BeatmapsetsStrings.ShowStatsCsMania,
- FixedLabelWidth = LABEL_WIDTH,
- Description = "The number of columns in the beatmap",
+ Caption = BeatmapsetsStrings.ShowStatsCsMania,
+ HintText = "The number of columns in the beatmap",
Current = new BindableFloat(Beatmap.Difficulty.CircleSize)
{
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
MinValue = 0,
MaxValue = 10,
Precision = 1,
- }
+ },
+ TabbableContentContainer = this,
},
- specialStyle = new LabelledSwitchButton
+ specialStyle = new FormCheckBox
{
- Label = "Use special (N+1) style",
- FixedLabelWidth = LABEL_WIDTH,
- Description = "Changes one column to act as a classic \"scratch\" or \"special\" column, which can be moved around by the user's skin (to the left/right/centre). Generally used in 6K (5+1) or 8K (7+1) configurations.",
+ Caption = "Use special (N+1) style",
+ HintText = "Changes one column to act as a classic \"scratch\" or \"special\" column, which can be moved around by the user's skin (to the left/right/centre). Generally used in 6K (5+1) or 8K (7+1) configurations.",
Current = { Value = Beatmap.BeatmapInfo.SpecialStyle }
},
- healthDrainSlider = new LabelledSliderBar
+ healthDrainSlider = new FormSliderBar
{
- Label = BeatmapsetsStrings.ShowStatsDrain,
- FixedLabelWidth = LABEL_WIDTH,
- Description = EditorSetupStrings.DrainRateDescription,
+ Caption = BeatmapsetsStrings.ShowStatsDrain,
+ HintText = EditorSetupStrings.DrainRateDescription,
Current = new BindableFloat(Beatmap.Difficulty.DrainRate)
{
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
MinValue = 0,
MaxValue = 10,
Precision = 0.1f,
- }
+ },
+ TabbableContentContainer = this,
},
- overallDifficultySlider = new LabelledSliderBar
+ overallDifficultySlider = new FormSliderBar
{
- Label = BeatmapsetsStrings.ShowStatsAccuracy,
- FixedLabelWidth = LABEL_WIDTH,
- Description = EditorSetupStrings.OverallDifficultyDescription,
+ Caption = BeatmapsetsStrings.ShowStatsAccuracy,
+ HintText = EditorSetupStrings.OverallDifficultyDescription,
Current = new BindableFloat(Beatmap.Difficulty.OverallDifficulty)
{
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
MinValue = 0,
MaxValue = 10,
Precision = 0.1f,
- }
+ },
+ TabbableContentContainer = this,
},
- baseVelocitySlider = new LabelledSliderBar
+ baseVelocitySlider = new FormSliderBar