From cff9dab650b8bc0e453be6b312a96b778fdcbc93 Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Tue, 14 Dec 2021 03:11:04 +0000
Subject: [PATCH 0001/1450] Remove combo scaling and change miss penalty
---
.../Difficulty/OsuPerformanceCalculator.cs | 17 +++--------------
1 file changed, 3 insertions(+), 14 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 8d45c7a8cc..6de6572b93 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -101,13 +101,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
aimValue *= lengthBonus;
- // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
if (effectiveMissCount > 0)
- aimValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), effectiveMissCount);
-
- // Combo scaling.
- if (Attributes.MaxCombo > 0)
- aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
+ aimValue *= Math.Pow(0.97, effectiveMissCount);
double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
@@ -151,13 +146,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
speedValue *= lengthBonus;
- // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
if (effectiveMissCount > 0)
- speedValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
-
- // Combo scaling.
- if (Attributes.MaxCombo > 0)
- speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
+ speedValue *= Math.Pow(0.97, effectiveMissCount);
double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
@@ -238,9 +228,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (mods.Any(h => h is OsuModHidden))
flashlightValue *= 1.3;
- // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
if (effectiveMissCount > 0)
- flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
+ flashlightValue *= Math.Pow(0.97, effectiveMissCount);
// Combo scaling.
if (Attributes.MaxCombo > 0)
From 86ad42a7440ec0a1478e2030209c2675985e3220 Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Tue, 14 Dec 2021 17:47:41 +0000
Subject: [PATCH 0002/1450] Nerf length bonus
---
osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 6de6572b93..6ade8c1732 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double aimValue = Math.Pow(5.0 * Math.Max(1.0, rawAim / 0.0675) - 4.0, 3.0) / 100000.0;
// Longer maps are worth more.
- double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
+ double lengthBonus = 0.95 + 0.25 * Math.Min(1.0, totalHits / 2000.0) +
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
aimValue *= lengthBonus;
@@ -142,7 +142,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double speedValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.SpeedStrain / 0.0675) - 4.0, 3.0) / 100000.0;
// Longer maps are worth more.
- double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
+ double lengthBonus = 0.95 + 0.25 * Math.Min(1.0, totalHits / 2000.0) +
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
speedValue *= lengthBonus;
From 489aa43b1bab6efeb0ed05e97ec66181dfe3b4f4 Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Tue, 14 Dec 2021 19:57:36 +0000
Subject: [PATCH 0003/1450] Make miss penalty harsher
---
.../Difficulty/OsuPerformanceCalculator.cs | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 6ade8c1732..0459c64c0e 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -96,13 +96,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double aimValue = Math.Pow(5.0 * Math.Max(1.0, rawAim / 0.0675) - 4.0, 3.0) / 100000.0;
// Longer maps are worth more.
- double lengthBonus = 0.95 + 0.25 * Math.Min(1.0, totalHits / 2000.0) +
+ double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
aimValue *= lengthBonus;
if (effectiveMissCount > 0)
- aimValue *= Math.Pow(0.97, effectiveMissCount);
+ aimValue *= Math.Pow(0.96, effectiveMissCount);
double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
@@ -142,12 +142,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double speedValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.SpeedStrain / 0.0675) - 4.0, 3.0) / 100000.0;
// Longer maps are worth more.
- double lengthBonus = 0.95 + 0.25 * Math.Min(1.0, totalHits / 2000.0) +
+ double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
speedValue *= lengthBonus;
if (effectiveMissCount > 0)
- speedValue *= Math.Pow(0.97, effectiveMissCount);
+ speedValue *= Math.Pow(0.96, effectiveMissCount);
double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
@@ -229,7 +229,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
flashlightValue *= 1.3;
if (effectiveMissCount > 0)
- flashlightValue *= Math.Pow(0.97, effectiveMissCount);
+ flashlightValue *= Math.Pow(0.96, effectiveMissCount);
// Combo scaling.
if (Attributes.MaxCombo > 0)
From bac4cfed50be4e3d1e0264ce0cd9099c3132d4b5 Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Thu, 16 Dec 2021 18:37:57 +0000
Subject: [PATCH 0004/1450] Use frost's miss count penalty
---
.../Difficulty/OsuPerformanceCalculator.cs | 21 ++++++++++++++++---
1 file changed, 18 insertions(+), 3 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 0459c64c0e..bba53c92fc 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
aimValue *= lengthBonus;
if (effectiveMissCount > 0)
- aimValue *= Math.Pow(0.96, effectiveMissCount);
+ aimValue *= calculateMissPenalty(effectiveMissCount);
double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
@@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
speedValue *= lengthBonus;
if (effectiveMissCount > 0)
- speedValue *= Math.Pow(0.96, effectiveMissCount);
+ speedValue *= calculateMissPenalty(effectiveMissCount);
double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
@@ -229,7 +229,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
flashlightValue *= 1.3;
if (effectiveMissCount > 0)
- flashlightValue *= Math.Pow(0.96, effectiveMissCount);
+ flashlightValue *= calculateMissPenalty(effectiveMissCount);
// Combo scaling.
if (Attributes.MaxCombo > 0)
@@ -265,6 +265,21 @@ namespace osu.Game.Rulesets.Osu.Difficulty
return Math.Max(countMiss, (int)Math.Floor(comboBasedMissCount));
}
+ private double calculateMissPenalty(double missCount)
+ {
+ double leniency = 2.0;
+
+ if (missCount > totalHits - leniency)
+ return 0;
+
+ double missApprox = erfInvApprox((totalHits - leniency - missCount) / totalHits);
+ double fcApprox = erfInvApprox((totalHits - leniency) / totalHits);
+
+ return Math.Pow(missApprox / fcApprox, 1.5);
+ }
+
+ private double logit(double x) => Math.Log(x / (1 - x));
+ private double erfInvApprox(double x) => (Math.Sqrt(Math.PI) / 4) * logit((x + 1) / 2);
private int totalHits => countGreat + countOk + countMeh + countMiss;
private int totalSuccessfulHits => countGreat + countOk + countMeh;
}
From 60e2a8ed4b2f0f38dafe0cc9bc6ab8c55128b189 Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Tue, 21 Dec 2021 17:54:26 +0000
Subject: [PATCH 0005/1450] Use MathNet for miss penalty calculation, and use
old penalty formula for Flashlight
---
.../Difficulty/OsuPerformanceCalculator.cs | 14 +++++++-------
osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj | 4 ++++
2 files changed, 11 insertions(+), 7 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index bba53c92fc..90caa64512 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -9,6 +9,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
+using MathNet.Numerics;
namespace osu.Game.Rulesets.Osu.Difficulty
{
@@ -228,8 +229,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (mods.Any(h => h is OsuModHidden))
flashlightValue *= 1.3;
+ // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
if (effectiveMissCount > 0)
- flashlightValue *= calculateMissPenalty(effectiveMissCount);
+ flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
// Combo scaling.
if (Attributes.MaxCombo > 0)
@@ -267,19 +269,17 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private double calculateMissPenalty(double missCount)
{
- double leniency = 2.0;
+ double leniency = 4.3;
if (missCount > totalHits - leniency)
return 0;
- double missApprox = erfInvApprox((totalHits - leniency - missCount) / totalHits);
- double fcApprox = erfInvApprox((totalHits - leniency) / totalHits);
+ double missApprox = SpecialFunctions.ErfInv((totalHits - leniency - missCount) / totalHits);
+ double fcApprox = SpecialFunctions.ErfInv((totalHits - leniency) / totalHits);
- return Math.Pow(missApprox / fcApprox, 1.5);
+ return Math.Pow(missApprox / fcApprox, 3.5);
}
- private double logit(double x) => Math.Log(x / (1 - x));
- private double erfInvApprox(double x) => (Math.Sqrt(Math.PI) / 4) * logit((x + 1) / 2);
private int totalHits => countGreat + countOk + countMeh + countMiss;
private int totalSuccessfulHits => countGreat + countOk + countMeh;
}
diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj
index 98f1e69bd1..018bdb93df 100644
--- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj
+++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj
@@ -15,4 +15,8 @@
+
+
+
+
\ No newline at end of file
From 5640918c8c7c8af2e29feec4c7ad6d501619dafc Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Sun, 26 Dec 2021 23:51:49 +0000
Subject: [PATCH 0006/1450] New miss penalty formula, using relevant difficult
notes in each skill (targets diffspikes)
---
.../Difficulty/OsuDifficultyAttributes.cs | 6 ++++++
.../Difficulty/OsuDifficultyCalculator.cs | 9 +++++++++
.../Difficulty/OsuPerformanceCalculator.cs | 16 ++++------------
.../Difficulty/Skills/OsuStrainSkill.cs | 7 +++++++
4 files changed, 26 insertions(+), 12 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
index 4b2e54da17..6102f4a8b2 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
@@ -24,6 +24,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty
[JsonProperty("slider_factor")]
public double SliderFactor { get; set; }
+ [JsonProperty("aim_difficult_strain_count")]
+ public int AimDifficultStrainCount { get; set; }
+
+ [JsonProperty("speed_difficult_strain_count")]
+ public int SpeedDifficultStrainCount { get; set; }
+
[JsonProperty("approach_rate")]
public double ApproachRate { get; set; }
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index ed42f333c0..ac228b0a32 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -40,6 +40,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1;
+ int aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).RelevantDifficultStrains();
+ int speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).RelevantDifficultStrains();
+
+ // Total number of strains in a map can vary by clockrate, and this needs to be corrected for.
+ aimDifficultyStrainCount = (int)(aimDifficultyStrainCount * clockRate);
+ speedDifficultyStrainCount = (int)(aimDifficultyStrainCount * clockRate);
+
if (mods.Any(h => h is OsuModRelax))
speedRating = 0.0;
@@ -78,6 +85,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
SpeedStrain = speedRating,
FlashlightRating = flashlightRating,
SliderFactor = sliderFactor,
+ AimDifficultStrainCount = aimDifficultyStrainCount,
+ SpeedDifficultStrainCount = speedDifficultyStrainCount,
ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
OverallDifficulty = (80 - hitWindowGreat) / 6,
DrainRate = drainRate,
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 90caa64512..3efb8951b3 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
aimValue *= lengthBonus;
if (effectiveMissCount > 0)
- aimValue *= calculateMissPenalty(effectiveMissCount);
+ aimValue *= calculateMissPenalty(effectiveMissCount, Attributes.AimDifficultStrainCount);
double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
@@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
speedValue *= lengthBonus;
if (effectiveMissCount > 0)
- speedValue *= calculateMissPenalty(effectiveMissCount);
+ speedValue *= calculateMissPenalty(effectiveMissCount, Attributes.SpeedDifficultStrainCount);
double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
@@ -267,17 +267,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
return Math.Max(countMiss, (int)Math.Floor(comboBasedMissCount));
}
- private double calculateMissPenalty(double missCount)
+ private double calculateMissPenalty(double missCount, double strainCount)
{
- double leniency = 4.3;
-
- if (missCount > totalHits - leniency)
- return 0;
-
- double missApprox = SpecialFunctions.ErfInv((totalHits - leniency - missCount) / totalHits);
- double fcApprox = SpecialFunctions.ErfInv((totalHits - leniency) / totalHits);
-
- return Math.Pow(missApprox / fcApprox, 3.5);
+ return 0.95 / ((missCount / (3 * Math.Sqrt(strainCount))) + 1);
}
private int totalHits => countGreat + countOk + countMeh + countMiss;
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
index e47edc37cc..43e39482a7 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
@@ -57,5 +57,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
return difficulty * DifficultyMultiplier;
}
+
+ public int RelevantDifficultStrains()
+ {
+ List strains = GetCurrentStrainPeaks().OrderByDescending(d => d).ToList();
+
+ return strains.Count(s => s > strains[0] * 0.66);
+ }
}
}
From e9589e57a662bc47297fec2429d7bfce1256db68 Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Mon, 27 Dec 2021 02:23:03 +0000
Subject: [PATCH 0007/1450] Fix logical error
---
osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index ac228b0a32..87ab12248f 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Total number of strains in a map can vary by clockrate, and this needs to be corrected for.
aimDifficultyStrainCount = (int)(aimDifficultyStrainCount * clockRate);
- speedDifficultyStrainCount = (int)(aimDifficultyStrainCount * clockRate);
+ speedDifficultyStrainCount = (int)(speedDifficultyStrainCount * clockRate);
if (mods.Any(h => h is OsuModRelax))
speedRating = 0.0;
From 8ce6e3c573104d55956c9a3c4cd3c8beffd41b09 Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Wed, 29 Dec 2021 18:49:13 +0000
Subject: [PATCH 0008/1450] Remove mathnet
---
osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 4 ----
osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj | 4 ----
2 files changed, 8 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index d7d294df47..f6e5481feb 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -99,8 +99,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (effectiveMissCount > 0)
aimValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), effectiveMissCount);
- aimValue *= getComboScalingFactor();
-
double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
approachRateFactor = 0.3 * (Attributes.ApproachRate - 10.33);
@@ -146,8 +144,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (effectiveMissCount > 0)
speedValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
- speedValue *= getComboScalingFactor();
-
double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
approachRateFactor = 0.3 * (Attributes.ApproachRate - 10.33);
diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj
index 018bdb93df..98f1e69bd1 100644
--- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj
+++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj
@@ -15,8 +15,4 @@
-
-
-
-
\ No newline at end of file
From 4f257d6987b1144912f561d5a3360593598be834 Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Wed, 29 Dec 2021 18:59:17 +0000
Subject: [PATCH 0009/1450] Clean up unsuccessful merge
---
.../Difficulty/OsuPerformanceCalculator.cs | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index f6e5481feb..793f9e790f 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -95,9 +95,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
aimValue *= lengthBonus;
- // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
if (effectiveMissCount > 0)
- aimValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), effectiveMissCount);
+ aimValue *= calculateMissPenalty(effectiveMissCount, Attributes.AimDifficultStrainCount);
double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
@@ -140,9 +139,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
speedValue *= lengthBonus;
- // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
if (effectiveMissCount > 0)
- speedValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
+ speedValue *= calculateMissPenalty(effectiveMissCount, Attributes.SpeedDifficultStrainCount);
double approachRateFactor = 0.0;
if (Attributes.ApproachRate > 10.33)
@@ -259,6 +257,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty
}
private double getComboScalingFactor() => Attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
+
+ private double calculateMissPenalty(double missCount, double strainCount)
+ {
+ return 0.95 / ((missCount / (3 * Math.Sqrt(strainCount))) + 1);
+ }
+
private int totalHits => countGreat + countOk + countMeh + countMiss;
private int totalSuccessfulHits => countGreat + countOk + countMeh;
}
From fd1028f3bb5c0f19ac2154974fafa528ea6e52e9 Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Wed, 29 Dec 2021 23:49:07 +0000
Subject: [PATCH 0010/1450] Use clockrate in the difficult strain count method
---
.../Difficulty/OsuDifficultyCalculator.cs | 8 ++------
.../Difficulty/Skills/OsuStrainSkill.cs | 9 +++++++--
2 files changed, 9 insertions(+), 8 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index 44ba6c6a58..27d97e9d75 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -40,12 +40,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1;
- int aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).RelevantDifficultStrains();
- int speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).RelevantDifficultStrains();
-
- // Total number of strains in a map can vary by clockrate, and this needs to be corrected for.
- aimDifficultyStrainCount = (int)(aimDifficultyStrainCount * clockRate);
- speedDifficultyStrainCount = (int)(speedDifficultyStrainCount * clockRate);
+ int aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountDifficultStrains(clockRate);
+ int speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountDifficultStrains(clockRate);
if (mods.Any(h => h is OsuModRelax))
speedRating = 0.0;
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
index 43e39482a7..fd751727a9 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
@@ -58,11 +58,16 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
return difficulty * DifficultyMultiplier;
}
- public int RelevantDifficultStrains()
+ ///
+ /// Returns the number of difficult strains.
+ /// A strain is considered difficult if it's higher than 66% of the highest strain.
+ ///
+ public int CountDifficultStrains(double clockRate)
{
List strains = GetCurrentStrainPeaks().OrderByDescending(d => d).ToList();
- return strains.Count(s => s > strains[0] * 0.66);
+ // Total number of strains in a map can vary by clockrate, and this needs to be corrected for.
+ return (int)(strains.Count(s => s > strains[0] * 0.66) * clockRate);
}
}
}
From d2b815b745b1cfaef241488c436b26fede000571 Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Sun, 2 Jan 2022 19:20:20 +0000
Subject: [PATCH 0011/1450] Add miss penalty comment
---
.../Difficulty/OsuPerformanceCalculator.cs | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 793f9e790f..d8fe85d645 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -256,13 +256,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
return Math.Max(countMiss, (int)Math.Floor(comboBasedMissCount));
}
+ // Miss penalty assumes that a player will miss on the relatively hard parts of a map, not the easy parts, hence the strain count.
+ private double calculateMissPenalty(double missCount, double strainCount) => 0.95 / ((missCount / (3 * Math.Sqrt(strainCount))) + 1);
+
private double getComboScalingFactor() => Attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
-
- private double calculateMissPenalty(double missCount, double strainCount)
- {
- return 0.95 / ((missCount / (3 * Math.Sqrt(strainCount))) + 1);
- }
-
private int totalHits => countGreat + countOk + countMeh + countMiss;
private int totalSuccessfulHits => countGreat + countOk + countMeh;
}
From 75be4e83d6a44adc7016c150b5cbd1c74d1caf34 Mon Sep 17 00:00:00 2001
From: Luminiscental
Date: Mon, 3 Jan 2022 22:22:32 +0000
Subject: [PATCH 0012/1450] Remove abusable 0.66 threshold by averaging
---
.../Difficulty/Skills/OsuStrainSkill.cs | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
index fd751727a9..1513befad5 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
@@ -59,15 +59,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
}
///
- /// Returns the number of difficult strains.
- /// A strain is considered difficult if it's higher than 66% of the highest strain.
+ /// Returns the number of strains above a threshold averaged as the threshold varies.
+ /// The result is scaled by clock rate as it affects the total number of strains.
///
public int CountDifficultStrains(double clockRate)
{
- List strains = GetCurrentStrainPeaks().OrderByDescending(d => d).ToList();
-
- // Total number of strains in a map can vary by clockrate, and this needs to be corrected for.
- return (int)(strains.Count(s => s > strains[0] * 0.66) * clockRate);
+ List strains = GetCurrentStrainPeaks().ToList();
+ // This is the average value of strains.Count(s => s > p * strains.Max()) for p between 0 and 1.
+ double realtimeCount = strains.Sum() / strains.Max();
+ return (int)(clockRate * realtimeCount);
}
}
}
From 132079004ca5e6df33a2bc646a52f3036edff85e Mon Sep 17 00:00:00 2001
From: Luminiscental
Date: Tue, 4 Jan 2022 12:30:05 +0000
Subject: [PATCH 0013/1450] Remove unnecessary truncation
---
osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs | 4 ++--
osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 4 ++--
osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 4 ++--
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
index 51842f65d5..7ab4232a19 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
@@ -25,10 +25,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
public double SliderFactor { get; set; }
[JsonProperty("aim_difficult_strain_count")]
- public int AimDifficultStrainCount { get; set; }
+ public double AimDifficultStrainCount { get; set; }
[JsonProperty("speed_difficult_strain_count")]
- public int SpeedDifficultStrainCount { get; set; }
+ public double SpeedDifficultStrainCount { get; set; }
[JsonProperty("approach_rate")]
public double ApproachRate { get; set; }
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index 27d97e9d75..3c039c9b7e 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -40,8 +40,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1;
- int aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountDifficultStrains(clockRate);
- int speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountDifficultStrains(clockRate);
+ double aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountDifficultStrains(clockRate);
+ double speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountDifficultStrains(clockRate);
if (mods.Any(h => h is OsuModRelax))
speedRating = 0.0;
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
index 1513befad5..46dc9c683b 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
@@ -62,12 +62,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
/// Returns the number of strains above a threshold averaged as the threshold varies.
/// The result is scaled by clock rate as it affects the total number of strains.
///
- public int CountDifficultStrains(double clockRate)
+ public double CountDifficultStrains(double clockRate)
{
List strains = GetCurrentStrainPeaks().ToList();
// This is the average value of strains.Count(s => s > p * strains.Max()) for p between 0 and 1.
double realtimeCount = strains.Sum() / strains.Max();
- return (int)(clockRate * realtimeCount);
+ return clockRate * realtimeCount;
}
}
}
From 443640a48c8bed53510947240e8337c9350d8d6d Mon Sep 17 00:00:00 2001
From: apollo <83023433+apollo-dw@users.noreply.github.com>
Date: Tue, 4 Jan 2022 16:39:30 +0000
Subject: [PATCH 0014/1450] Revert "Remove abusable 0.66 threshold by
averaging"
---
.../Difficulty/OsuDifficultyAttributes.cs | 4 ++--
.../Difficulty/OsuDifficultyCalculator.cs | 4 ++--
.../Difficulty/Skills/OsuStrainSkill.cs | 14 +++++++-------
3 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
index 7ab4232a19..51842f65d5 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
@@ -25,10 +25,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
public double SliderFactor { get; set; }
[JsonProperty("aim_difficult_strain_count")]
- public double AimDifficultStrainCount { get; set; }
+ public int AimDifficultStrainCount { get; set; }
[JsonProperty("speed_difficult_strain_count")]
- public double SpeedDifficultStrainCount { get; set; }
+ public int SpeedDifficultStrainCount { get; set; }
[JsonProperty("approach_rate")]
public double ApproachRate { get; set; }
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index 3c039c9b7e..27d97e9d75 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -40,8 +40,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1;
- double aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountDifficultStrains(clockRate);
- double speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountDifficultStrains(clockRate);
+ int aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountDifficultStrains(clockRate);
+ int speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountDifficultStrains(clockRate);
if (mods.Any(h => h is OsuModRelax))
speedRating = 0.0;
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
index 46dc9c683b..fd751727a9 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
@@ -59,15 +59,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
}
///
- /// Returns the number of strains above a threshold averaged as the threshold varies.
- /// The result is scaled by clock rate as it affects the total number of strains.
+ /// Returns the number of difficult strains.
+ /// A strain is considered difficult if it's higher than 66% of the highest strain.
///
- public double CountDifficultStrains(double clockRate)
+ public int CountDifficultStrains(double clockRate)
{
- List strains = GetCurrentStrainPeaks().ToList();
- // This is the average value of strains.Count(s => s > p * strains.Max()) for p between 0 and 1.
- double realtimeCount = strains.Sum() / strains.Max();
- return clockRate * realtimeCount;
+ List strains = GetCurrentStrainPeaks().OrderByDescending(d => d).ToList();
+
+ // Total number of strains in a map can vary by clockrate, and this needs to be corrected for.
+ return (int)(strains.Count(s => s > strains[0] * 0.66) * clockRate);
}
}
}
From dcb969316dd5a7e2bd1b06ebd1450a0ab56ef831 Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Tue, 4 Jan 2022 17:33:23 +0000
Subject: [PATCH 0015/1450] Weight difficult strain count against the top
strain
---
osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
index 46dc9c683b..3e0fccb6c4 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
@@ -59,14 +59,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
}
///
- /// Returns the number of strains above a threshold averaged as the threshold varies.
+ /// Returns the number of strains weighted against the top strain.
/// The result is scaled by clock rate as it affects the total number of strains.
///
public double CountDifficultStrains(double clockRate)
{
List strains = GetCurrentStrainPeaks().ToList();
- // This is the average value of strains.Count(s => s > p * strains.Max()) for p between 0 and 1.
- double realtimeCount = strains.Sum() / strains.Max();
+ double topStrain = strains.Max();
+
+ double realtimeCount = strains.Sum(s => Math.Pow(s / topStrain, 4));
return clockRate * realtimeCount;
}
}
From 400abc147b1f3742061125d91e80a62f5e1333f7 Mon Sep 17 00:00:00 2001
From: Dan Balasescu
Date: Thu, 6 Jan 2022 16:28:04 +0900
Subject: [PATCH 0016/1450] Add attribute ids to mapping functions
---
osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs | 4 ++++
osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs | 2 ++
2 files changed, 6 insertions(+)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
index 7ab4232a19..7041acc16c 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
@@ -60,6 +60,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
yield return (ATTRIB_ID_FLASHLIGHT, FlashlightDifficulty);
yield return (ATTRIB_ID_SLIDER_FACTOR, SliderFactor);
+ yield return (ATTRIB_ID_AIM_DIFFICULT_STRAIN_COUNT, AimDifficultStrainCount);
+ yield return (ATTRIB_ID_SPEED_DIFFICULT_STRAIN_COUNT, SpeedDifficultStrainCount);
}
public override void FromDatabaseAttributes(IReadOnlyDictionary values)
@@ -74,6 +76,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
StarRating = values[ATTRIB_ID_DIFFICULTY];
FlashlightDifficulty = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT);
SliderFactor = values[ATTRIB_ID_SLIDER_FACTOR];
+ AimDifficultStrainCount = values[ATTRIB_ID_AIM_DIFFICULT_STRAIN_COUNT];
+ SpeedDifficultStrainCount = values[ATTRIB_ID_SPEED_DIFFICULT_STRAIN_COUNT];
}
#region Newtonsoft.Json implicit ShouldSerialize() methods
diff --git a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs
index 991b567f57..803a5bdac7 100644
--- a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs
+++ b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs
@@ -24,6 +24,8 @@ namespace osu.Game.Rulesets.Difficulty
protected const int ATTRIB_ID_SCORE_MULTIPLIER = 15;
protected const int ATTRIB_ID_FLASHLIGHT = 17;
protected const int ATTRIB_ID_SLIDER_FACTOR = 19;
+ protected const int ATTRIB_ID_AIM_DIFFICULT_STRAIN_COUNT = 21;
+ protected const int ATTRIB_ID_SPEED_DIFFICULT_STRAIN_COUNT = 23;
///
/// The mods which were applied to the beatmap.
From 598946737f07c0f2a3ac7523666a868722906db1 Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Wed, 12 Jan 2022 14:38:53 +0000
Subject: [PATCH 0017/1450] Reword comment and rename argument
---
.../Difficulty/OsuPerformanceCalculator.cs | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index d8fe85d645..52658dfe38 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -256,9 +256,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
return Math.Max(countMiss, (int)Math.Floor(comboBasedMissCount));
}
- // Miss penalty assumes that a player will miss on the relatively hard parts of a map, not the easy parts, hence the strain count.
- private double calculateMissPenalty(double missCount, double strainCount) => 0.95 / ((missCount / (3 * Math.Sqrt(strainCount))) + 1);
-
+ // Miss penalty assumes that a player will miss on the hardest parts of a map,
+ // so we use the amount of relatively difficult sections to adjust miss penalty
+ // to make it more punishing on maps with lower amount of hard sections.
+ private double calculateMissPenalty(double missCount, double difficultStrainCount) => 0.95 / ((missCount / (3 * Math.Sqrt(difficultStrainCount))) + 1);
private double getComboScalingFactor() => Attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
private int totalHits => countGreat + countOk + countMeh + countMiss;
private int totalSuccessfulHits => countGreat + countOk + countMeh;
From da31ca17e7c12a2922ab83657df4d8d9b6826bbd Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Mon, 14 Feb 2022 01:53:03 +0000
Subject: [PATCH 0018/1450] Use note strains instead of sectional strains
---
osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 2 ++
osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 7 ++++---
osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 6 +++++-
3 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
index a6301aed6d..3486db04af 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
@@ -159,6 +159,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
currentStrain *= strainDecay(current.DeltaTime);
currentStrain += strainValueOf(current) * skillMultiplier;
+ objectStrains.Add(currentStrain);
+
return currentStrain;
}
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
index 3e0fccb6c4..94e2c9d774 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
@@ -28,6 +28,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
///
protected virtual double DifficultyMultiplier => 1.06;
+ protected List objectStrains = new List();
+
protected OsuStrainSkill(Mod[] mods)
: base(mods)
{
@@ -64,10 +66,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
///
public double CountDifficultStrains(double clockRate)
{
- List strains = GetCurrentStrainPeaks().ToList();
- double topStrain = strains.Max();
+ double topStrain = objectStrains.Max();
- double realtimeCount = strains.Sum(s => Math.Pow(s / topStrain, 4));
+ double realtimeCount = objectStrains.Sum(s => Math.Pow(s / topStrain, 4));
return clockRate * realtimeCount;
}
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
index 06d1ef7346..108edc6f2d 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
@@ -173,7 +173,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
currentRhythm = calculateRhythmBonus(current);
- return currentStrain * currentRhythm;
+ double totalStrain = currentStrain * currentRhythm;
+
+ objectStrains.Add(totalStrain);
+
+ return totalStrain;
}
}
}
From 94a46ab640b36c3ca58f03eaf2644ab51c3abd51 Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Mon, 14 Feb 2022 02:02:46 +0000
Subject: [PATCH 0019/1450] Rescale miss penalty for note strains
---
osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index cf2116cc5d..cf1af18d39 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -259,7 +259,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Miss penalty assumes that a player will miss on the hardest parts of a map,
// so we use the amount of relatively difficult sections to adjust miss penalty
// to make it more punishing on maps with lower amount of hard sections.
- private double calculateMissPenalty(double missCount, double difficultStrainCount) => 0.95 / ((missCount / (3 * Math.Sqrt(difficultStrainCount))) + 1);
+ private double calculateMissPenalty(double missCount, double difficultStrainCount) => 0.94 / ((missCount / (2 * Math.Sqrt(difficultStrainCount))) + 1);
private double getComboScalingFactor() => Attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
private int totalHits => countGreat + countOk + countMeh + countMiss;
private int totalSuccessfulHits => countGreat + countOk + countMeh;
From c18df86720244a7d5bba22bc502afd4f959c9082 Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Sat, 19 Feb 2022 15:33:28 +0000
Subject: [PATCH 0020/1450] Remove clockrate factor
---
osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 4 ++--
osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 5 ++---
2 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index 3c039c9b7e..788b515d7f 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -40,8 +40,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1;
- double aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountDifficultStrains(clockRate);
- double speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountDifficultStrains(clockRate);
+ double aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountDifficultStrains();
+ double speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountDifficultStrains();
if (mods.Any(h => h is OsuModRelax))
speedRating = 0.0;
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
index 94e2c9d774..1124c4466f 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
@@ -64,12 +64,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
/// Returns the number of strains weighted against the top strain.
/// The result is scaled by clock rate as it affects the total number of strains.
///
- public double CountDifficultStrains(double clockRate)
+ public double CountDifficultStrains()
{
double topStrain = objectStrains.Max();
- double realtimeCount = objectStrains.Sum(s => Math.Pow(s / topStrain, 4));
- return clockRate * realtimeCount;
+ return objectStrains.Sum(s => Math.Pow(s / topStrain, 4));
}
}
}
From 2f335a76dca77b0304f5ff21ed7ca2c8e1130757 Mon Sep 17 00:00:00 2001
From: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Date: Thu, 17 Mar 2022 22:08:56 +0000
Subject: [PATCH 0021/1450] 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 cda9440a296304bd710c1787436ea1a948f6c999 Mon Sep 17 00:00:00 2001
From: Dan Balasescu
Date: Mon, 11 Dec 2023 14:39:50 +0900
Subject: [PATCH 0022/1450] Fix JuiceStream velocity calculation
---
.../Beatmaps/CatchBeatmapConverter.cs | 2 +-
.../Objects/JuiceStream.cs | 21 +++++++++----------
2 files changed, 11 insertions(+), 12 deletions(-)
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
index 8c460586b0..f5c5ffb529 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
@@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y,
// prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance.
// this results in more (or less) ticks being generated in SliderVelocityMultiplierBindable { get; } = new BindableDouble(1)
{
- Precision = 0.01,
MinValue = 0.1,
MaxValue = 10
};
@@ -48,16 +48,10 @@ namespace osu.Game.Rulesets.Catch.Objects
public double TickDistanceMultiplier = 1;
[JsonIgnore]
- private double velocityFactor;
+ public double Velocity { get; private set; }
[JsonIgnore]
- private double tickDistanceFactor;
-
- [JsonIgnore]
- public double Velocity => velocityFactor * SliderVelocityMultiplier;
-
- [JsonIgnore]
- public double TickDistance => tickDistanceFactor * TickDistanceMultiplier;
+ public double TickDistance { get; private set; }
///
/// The length of one span of this .
@@ -70,8 +64,13 @@ namespace osu.Game.Rulesets.Catch.Objects
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
- velocityFactor = base_scoring_distance * difficulty.SliderMultiplier / timingPoint.BeatLength;
- tickDistanceFactor = base_scoring_distance * difficulty.SliderMultiplier / difficulty.SliderTickRate;
+ Velocity = base_scoring_distance * difficulty.SliderMultiplier / LegacyRulesetExtensions.GetPrecisionAdjustedBeatLength(this, timingPoint, CatchRuleset.SHORT_NAME);
+
+ // WARNING: this is intentionally not computed as `BASE_SCORING_DISTANCE * difficulty.SliderMultiplier`
+ // for backwards compatibility reasons (intentionally introducing floating point errors to match stable).
+ double scoringDistance = Velocity * timingPoint.BeatLength;
+
+ TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier;
}
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
From 22a0bc7d9d437ac1e502cd6e54c64fe66afb60c8 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Wed, 20 Dec 2023 00:05:32 +0100
Subject: [PATCH 0023/1450] Add basic slider distance control
---
.../TestSceneSliderControlPointPiece.cs | 2 +-
.../TestSceneSliderSelectionBlueprint.cs | 2 +-
.../Sliders/Components/SliderTailPiece.cs | 104 ++++++++++++++++++
.../Blueprints/Sliders/SliderCircleOverlay.cs | 8 +-
.../Sliders/SliderSelectionBlueprint.cs | 4 +-
5 files changed, 112 insertions(+), 8 deletions(-)
create mode 100644 osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs
index 99ced30ffe..085e11460f 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs
@@ -353,7 +353,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{
public new SliderBodyPiece BodyPiece => base.BodyPiece;
public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay;
- public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay;
+ public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailPiece;
public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
public TestSliderBlueprint(Slider slider)
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
index d4d99e1019..adc4929227 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
@@ -198,7 +198,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{
public new SliderBodyPiece BodyPiece => base.BodyPiece;
public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay;
- public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay;
+ public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailPiece;
public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
public TestSliderBlueprint(Slider slider)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
new file mode 100644
index 0000000000..96169c5e1f
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
@@ -0,0 +1,104 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Input;
+using osu.Framework.Input.Events;
+using osu.Framework.Utils;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Screens.Edit;
+using osuTK;
+using osuTK.Graphics;
+using osuTK.Input;
+
+namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
+{
+ public partial class SliderTailPiece : SliderCircleOverlay
+ {
+ ///
+ /// Whether this is currently being dragged.
+ ///
+ private bool isDragging;
+
+ private InputManager inputManager = null!;
+
+ [Resolved(CanBeNull = true)]
+ private EditorBeatmap? editorBeatmap { get; set; }
+
+ [Resolved]
+ private OsuColour colours { get; set; } = null!;
+
+ public SliderTailPiece(Slider slider, SliderPosition position)
+ : base(slider, position)
+ {
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ inputManager = GetContainingInputManager();
+ }
+
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => CirclePiece.ReceivePositionalInputAt(screenSpacePos);
+
+ protected override bool OnHover(HoverEvent e)
+ {
+ updateCirclePieceColour();
+ return false;
+ }
+
+ protected override void OnHoverLost(HoverLostEvent e)
+ {
+ updateCirclePieceColour();
+ }
+
+ private void updateCirclePieceColour()
+ {
+ Color4 colour = colours.Yellow;
+
+ if (IsHovered)
+ colour = colour.Lighten(1);
+
+ CirclePiece.Colour = colour;
+ }
+
+ protected override bool OnDragStart(DragStartEvent e)
+ {
+ if (e.Button == MouseButton.Right || !inputManager.CurrentState.Keyboard.ShiftPressed)
+ return false;
+
+ isDragging = true;
+ editorBeatmap?.BeginChange();
+
+ return true;
+ }
+
+ protected override void OnDrag(DragEvent e)
+ {
+ double proposedDistance = Slider.Path.Distance + e.Delta.X;
+
+ proposedDistance = MathHelper.Clamp(proposedDistance, 0, Slider.Path.CalculatedDistance);
+ proposedDistance = MathHelper.Clamp(proposedDistance,
+ 0.1 * Slider.Path.Distance / Slider.SliderVelocityMultiplier,
+ 10 * Slider.Path.Distance / Slider.SliderVelocityMultiplier);
+
+ if (Precision.AlmostEquals(proposedDistance, Slider.Path.Distance))
+ return;
+
+ Slider.SliderVelocityMultiplier *= proposedDistance / Slider.Path.Distance;
+ Slider.Path.ExpectedDistance.Value = proposedDistance;
+ editorBeatmap?.Update(Slider);
+ }
+
+ protected override void OnDragEnd(DragEndEvent e)
+ {
+ if (isDragging)
+ {
+ editorBeatmap?.EndChange();
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs
index d47cf6bf23..b00a42748e 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs
@@ -11,14 +11,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
public partial class SliderCircleOverlay : CompositeDrawable
{
protected readonly HitCirclePiece CirclePiece;
+ protected readonly Slider Slider;
- private readonly Slider slider;
- private readonly SliderPosition position;
private readonly HitCircleOverlapMarker marker;
+ private readonly SliderPosition position;
public SliderCircleOverlay(Slider slider, SliderPosition position)
{
- this.slider = slider;
+ Slider = slider;
this.position = position;
InternalChildren = new Drawable[]
@@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
base.Update();
- var circle = position == SliderPosition.Start ? (HitCircle)slider.HeadCircle : slider.TailCircle;
+ var circle = position == SliderPosition.Start ? (HitCircle)Slider.HeadCircle : Slider.TailCircle;
CirclePiece.UpdateFrom(circle);
marker.UpdateFrom(circle);
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
index b3efe1c495..37c433cf8b 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
@@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
protected SliderBodyPiece BodyPiece { get; private set; }
protected SliderCircleOverlay HeadOverlay { get; private set; }
- protected SliderCircleOverlay TailOverlay { get; private set; }
+ protected SliderTailPiece TailPiece { get; private set; }
[CanBeNull]
protected PathControlPointVisualiser ControlPointVisualiser { get; private set; }
@@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
BodyPiece = new SliderBodyPiece(),
HeadOverlay = CreateCircleOverlay(HitObject, SliderPosition.Start),
- TailOverlay = CreateCircleOverlay(HitObject, SliderPosition.End),
+ TailPiece = new SliderTailPiece(HitObject, SliderPosition.End),
};
}
From 1258a9d378a53a7f352bb5bd6fb01f0a44f3305e Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Wed, 20 Dec 2023 01:48:42 +0100
Subject: [PATCH 0024/1450] Find closest distance value to mouse
---
.../Sliders/Components/SliderTailPiece.cs | 60 +++++++++++++++++--
1 file changed, 55 insertions(+), 5 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
index 96169c5e1f..5cf9346f2e 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
@@ -1,12 +1,15 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Linq;
using osu.Framework.Allocation;
+using osu.Framework.Caching;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Input;
using osu.Framework.Input.Events;
using osu.Framework.Utils;
using osu.Game.Graphics;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
using osuTK;
@@ -24,6 +27,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
private InputManager inputManager = null!;
+ private readonly Cached fullPathCache = new Cached();
+
[Resolved(CanBeNull = true)]
private EditorBeatmap? editorBeatmap { get; set; }
@@ -33,6 +38,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
public SliderTailPiece(Slider slider, SliderPosition position)
: base(slider, position)
{
+ Slider.Path.ControlPoints.CollectionChanged += (_, _) => fullPathCache.Invalidate();
}
protected override void LoadComplete()
@@ -78,17 +84,18 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
protected override void OnDrag(DragEvent e)
{
- double proposedDistance = Slider.Path.Distance + e.Delta.X;
+ double oldDistance = Slider.Path.Distance;
+ double proposedDistance = findClosestPathDistance(e);
proposedDistance = MathHelper.Clamp(proposedDistance, 0, Slider.Path.CalculatedDistance);
proposedDistance = MathHelper.Clamp(proposedDistance,
- 0.1 * Slider.Path.Distance / Slider.SliderVelocityMultiplier,
- 10 * Slider.Path.Distance / Slider.SliderVelocityMultiplier);
+ 0.1 * oldDistance / Slider.SliderVelocityMultiplier,
+ 10 * oldDistance / Slider.SliderVelocityMultiplier);
- if (Precision.AlmostEquals(proposedDistance, Slider.Path.Distance))
+ if (Precision.AlmostEquals(proposedDistance, oldDistance))
return;
- Slider.SliderVelocityMultiplier *= proposedDistance / Slider.Path.Distance;
+ Slider.SliderVelocityMultiplier *= proposedDistance / oldDistance;
Slider.Path.ExpectedDistance.Value = proposedDistance;
editorBeatmap?.Update(Slider);
}
@@ -100,5 +107,48 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
editorBeatmap?.EndChange();
}
}
+
+ ///
+ /// Finds the expected distance value for which the slider end is closest to the mouse position.
+ ///
+ private double findClosestPathDistance(DragEvent e)
+ {
+ const double step1 = 10;
+ const double step2 = 0.1;
+
+ var desiredPosition = e.MousePosition - Slider.Position;
+
+ if (!fullPathCache.IsValid)
+ fullPathCache.Value = new SliderPath(Slider.Path.ControlPoints.ToArray());
+
+ // Do a linear search to find the closest point on the path to the mouse position.
+ double bestValue = 0;
+ double minDistance = double.MaxValue;
+
+ for (double d = 0; d <= fullPathCache.Value.CalculatedDistance; d += step1)
+ {
+ double t = d / fullPathCache.Value.CalculatedDistance;
+ float dist = Vector2.Distance(fullPathCache.Value.PositionAt(t), desiredPosition);
+
+ if (dist >= minDistance) continue;
+
+ minDistance = dist;
+ bestValue = d;
+ }
+
+ // Do another linear search to fine-tune the result.
+ for (double d = bestValue - step1; d <= bestValue + step1; d += step2)
+ {
+ double t = d / fullPathCache.Value.CalculatedDistance;
+ float dist = Vector2.Distance(fullPathCache.Value.PositionAt(t), desiredPosition);
+
+ if (dist >= minDistance) continue;
+
+ minDistance = dist;
+ bestValue = d;
+ }
+
+ return bestValue;
+ }
}
}
From 1365a1b7bec8d444dfe99f098ed2ad2c1863f770 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Wed, 20 Dec 2023 01:57:11 +0100
Subject: [PATCH 0025/1450] fix tests
---
.../Editor/TestSceneSliderControlPointPiece.cs | 13 ++++++++++++-
.../Editor/TestSceneSliderSelectionBlueprint.cs | 15 +++++++++++++--
.../Sliders/SliderSelectionBlueprint.cs | 3 ++-
3 files changed, 27 insertions(+), 4 deletions(-)
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs
index 085e11460f..1a7430704d 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs
@@ -353,7 +353,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{
public new SliderBodyPiece BodyPiece => base.BodyPiece;
public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay;
- public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailPiece;
+ public new TestSliderTailPiece TailPiece => (TestSliderTailPiece)base.TailPiece;
public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
public TestSliderBlueprint(Slider slider)
@@ -362,6 +362,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
}
protected override SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new TestSliderCircleOverlay(slider, position);
+ protected override SliderTailPiece CreateTailPiece(Slider slider, SliderPosition position) => new TestSliderTailPiece(slider, position);
}
private partial class TestSliderCircleOverlay : SliderCircleOverlay
@@ -373,5 +374,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{
}
}
+
+ private partial class TestSliderTailPiece : SliderTailPiece
+ {
+ public new HitCirclePiece CirclePiece => base.CirclePiece;
+
+ public TestSliderTailPiece(Slider slider, SliderPosition position)
+ : base(slider, position)
+ {
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
index adc4929227..2c5cff3f70 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
@@ -179,7 +179,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
() => Precision.AlmostEquals(blueprint.HeadOverlay.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.HeadCircle.ScreenSpaceDrawQuad.Centre));
AddAssert("tail positioned correctly",
- () => Precision.AlmostEquals(blueprint.TailOverlay.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre));
+ () => Precision.AlmostEquals(blueprint.TailPiece.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre));
}
private void moveMouseToControlPoint(int index)
@@ -198,7 +198,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{
public new SliderBodyPiece BodyPiece => base.BodyPiece;
public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay;
- public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailPiece;
+ public new TestSliderTailPiece TailPiece => (TestSliderTailPiece)base.TailPiece;
public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
public TestSliderBlueprint(Slider slider)
@@ -207,6 +207,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
}
protected override SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new TestSliderCircleOverlay(slider, position);
+ protected override SliderTailPiece CreateTailPiece(Slider slider, SliderPosition position) => new TestSliderTailPiece(slider, position);
}
private partial class TestSliderCircleOverlay : SliderCircleOverlay
@@ -218,5 +219,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{
}
}
+
+ private partial class TestSliderTailPiece : SliderTailPiece
+ {
+ public new HitCirclePiece CirclePiece => base.CirclePiece;
+
+ public TestSliderTailPiece(Slider slider, SliderPosition position)
+ : base(slider, position)
+ {
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
index 37c433cf8b..a13eeb0208 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
@@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
BodyPiece = new SliderBodyPiece(),
HeadOverlay = CreateCircleOverlay(HitObject, SliderPosition.Start),
- TailPiece = new SliderTailPiece(HitObject, SliderPosition.End),
+ TailPiece = CreateTailPiece(HitObject, SliderPosition.End),
};
}
@@ -415,5 +415,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true;
protected virtual SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new SliderCircleOverlay(slider, position);
+ protected virtual SliderTailPiece CreateTailPiece(Slider slider, SliderPosition position) => new SliderTailPiece(slider, position);
}
}
From 3aaf0b39f56e5d3df4a597a5925258470fbb73c3 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Wed, 20 Dec 2023 02:12:16 +0100
Subject: [PATCH 0026/1450] Add slider tail dragging test
---
.../TestSceneSliderSelectionBlueprint.cs | 48 +++++++++++++++++++
1 file changed, 48 insertions(+)
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
index 2c5cff3f70..3f9620a8d1 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
@@ -163,6 +163,54 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
checkControlPointSelected(1, false);
}
+ [Test]
+ public void TestDragSliderTail()
+ {
+ AddStep($"move mouse to slider tail", () =>
+ {
+ Vector2 position = slider.EndPosition + new Vector2(10, 0);
+ InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
+ });
+ AddStep("shift + drag", () =>
+ {
+ InputManager.PressKey(Key.ShiftLeft);
+ InputManager.PressButton(MouseButton.Left);
+ });
+ moveMouseToControlPoint(1);
+ AddStep("release", () =>
+ {
+ InputManager.ReleaseButton(MouseButton.Left);
+ InputManager.ReleaseKey(Key.ShiftLeft);
+ });
+
+ AddAssert("expected distance halved",
+ () => Precision.AlmostEquals(slider.Path.Distance, 172.2, 0.1));
+
+ AddStep($"move mouse to slider tail", () =>
+ {
+ Vector2 position = slider.EndPosition + new Vector2(10, 0);
+ InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
+ });
+ AddStep("shift + drag", () =>
+ {
+ InputManager.PressKey(Key.ShiftLeft);
+ InputManager.PressButton(MouseButton.Left);
+ });
+ AddStep($"move mouse beyond last control point", () =>
+ {
+ Vector2 position = slider.Position + slider.Path.ControlPoints[2].Position + new Vector2(50, 0);
+ InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
+ });
+ AddStep("release", () =>
+ {
+ InputManager.ReleaseButton(MouseButton.Left);
+ InputManager.ReleaseKey(Key.ShiftLeft);
+ });
+
+ AddAssert("expected distance is calculated distance",
+ () => Precision.AlmostEquals(slider.Path.Distance, slider.Path.CalculatedDistance, 0.1));
+ }
+
private void moveHitObject()
{
AddStep("move hitobject", () =>
From 66f4dcc578b3f760e3d2e6935fca2fecfec526a7 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Wed, 20 Dec 2023 02:32:20 +0100
Subject: [PATCH 0027/1450] fix repeat sliders half
---
.../Edit/Blueprints/Sliders/Components/SliderTailPiece.cs | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
index 5cf9346f2e..e60e41f6d5 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
@@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
Color4 colour = colours.Yellow;
- if (IsHovered)
+ if (IsHovered && Slider.RepeatCount % 2 == 0)
colour = colour.Lighten(1);
CirclePiece.Colour = colour;
@@ -73,7 +73,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
protected override bool OnDragStart(DragStartEvent e)
{
- if (e.Button == MouseButton.Right || !inputManager.CurrentState.Keyboard.ShiftPressed)
+ // Disable dragging if the slider has an uneven number of repeats because the slider tail will be on the wrong side of the path.
+ if (e.Button == MouseButton.Right || !inputManager.CurrentState.Keyboard.ShiftPressed || Slider.RepeatCount % 2 == 1)
return false;
isDragging = true;
From f7cb6b9ed09bd041f3c21448abcb83c8de3ed09e Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Wed, 20 Dec 2023 12:58:32 +0100
Subject: [PATCH 0028/1450] Fix all repeat sliders being draggable
---
.../Edit/Blueprints/Sliders/Components/SliderTailPiece.cs | 5 ++---
.../Edit/Blueprints/Sliders/SliderCircleOverlay.cs | 3 ++-
osu.Game.Rulesets.Osu/Objects/Slider.cs | 8 +++++++-
3 files changed, 11 insertions(+), 5 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
index e60e41f6d5..5cf9346f2e 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
@@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
Color4 colour = colours.Yellow;
- if (IsHovered && Slider.RepeatCount % 2 == 0)
+ if (IsHovered)
colour = colour.Lighten(1);
CirclePiece.Colour = colour;
@@ -73,8 +73,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
protected override bool OnDragStart(DragStartEvent e)
{
- // Disable dragging if the slider has an uneven number of repeats because the slider tail will be on the wrong side of the path.
- if (e.Button == MouseButton.Right || !inputManager.CurrentState.Keyboard.ShiftPressed || Slider.RepeatCount % 2 == 1)
+ if (e.Button == MouseButton.Right || !inputManager.CurrentState.Keyboard.ShiftPressed)
return false;
isDragging = true;
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs
index b00a42748e..2bf5118039 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs
@@ -32,7 +32,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
base.Update();
- var circle = position == SliderPosition.Start ? (HitCircle)Slider.HeadCircle : Slider.TailCircle;
+ var circle = position == SliderPosition.Start ? (HitCircle)Slider.HeadCircle :
+ Slider.RepeatCount % 2 == 0 ? Slider.TailCircle : Slider.LastRepeat;
CirclePiece.UpdateFrom(circle);
marker.UpdateFrom(circle);
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index 506145568e..7a22bf5c4d 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -162,6 +162,9 @@ namespace osu.Game.Rulesets.Osu.Objects
[JsonIgnore]
public SliderTailCircle TailCircle { get; protected set; }
+ [JsonIgnore]
+ public SliderRepeat LastRepeat { get; protected set; }
+
public Slider()
{
SamplesBindable.CollectionChanged += (_, _) => UpdateNestedSamples();
@@ -225,7 +228,7 @@ namespace osu.Game.Rulesets.Osu.Objects
break;
case SliderEventType.Repeat:
- AddNested(new SliderRepeat(this)
+ AddNested(LastRepeat = new SliderRepeat(this)
{
RepeatIndex = e.SpanIndex,
StartTime = StartTime + (e.SpanIndex + 1) * SpanDuration,
@@ -248,6 +251,9 @@ namespace osu.Game.Rulesets.Osu.Objects
if (TailCircle != null)
TailCircle.Position = EndPosition;
+
+ if (LastRepeat != null)
+ LastRepeat.Position = RepeatCount % 2 == 0 ? Position : Position + Path.PositionAt(1);
}
protected void UpdateNestedSamples()
From d000da725df5d5a2e031c96ce1861d581feac497 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Wed, 20 Dec 2023 13:14:05 +0100
Subject: [PATCH 0029/1450] fix code quality
---
.../Editor/TestSceneSliderSelectionBlueprint.cs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
index 3f9620a8d1..3faf181465 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
@@ -166,7 +166,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
[Test]
public void TestDragSliderTail()
{
- AddStep($"move mouse to slider tail", () =>
+ AddStep("move mouse to slider tail", () =>
{
Vector2 position = slider.EndPosition + new Vector2(10, 0);
InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
@@ -186,7 +186,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddAssert("expected distance halved",
() => Precision.AlmostEquals(slider.Path.Distance, 172.2, 0.1));
- AddStep($"move mouse to slider tail", () =>
+ AddStep("move mouse to slider tail", () =>
{
Vector2 position = slider.EndPosition + new Vector2(10, 0);
InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
@@ -196,7 +196,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
InputManager.PressKey(Key.ShiftLeft);
InputManager.PressButton(MouseButton.Left);
});
- AddStep($"move mouse beyond last control point", () =>
+ AddStep("move mouse beyond last control point", () =>
{
Vector2 position = slider.Position + slider.Path.ControlPoints[2].Position + new Vector2(50, 0);
InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
From eedb436389afe01b1666d6e3ed73d10cc8b2d070 Mon Sep 17 00:00:00 2001
From: Salman Ahmed
Date: Sat, 30 Dec 2023 03:47:42 +0300
Subject: [PATCH 0030/1450] Move combo counter to ruleset-specific HUD
components target
---
.../Argon/CatchArgonSkinTransformer.cs | 2 +-
.../Skinning/Argon/OsuArgonSkinTransformer.cs | 2 +-
.../Argon/TaikoArgonSkinTransformer.cs | 8 +--
osu.Game/Rulesets/Ruleset.cs | 14 ++++-
osu.Game/Skinning/ArgonSkin.cs | 16 +-----
osu.Game/Skinning/ArgonSkinTransformer.cs | 53 +++++++++++++++++++
osu.Game/Skinning/LegacySkin.cs | 1 -
osu.Game/Skinning/LegacySkinTransformer.cs | 44 +++++++++++++--
8 files changed, 113 insertions(+), 27 deletions(-)
create mode 100644 osu.Game/Skinning/ArgonSkinTransformer.cs
diff --git a/osu.Game.Rulesets.Catch/Skinning/Argon/CatchArgonSkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Argon/CatchArgonSkinTransformer.cs
index 520c2de248..a67945df98 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Argon/CatchArgonSkinTransformer.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Argon/CatchArgonSkinTransformer.cs
@@ -6,7 +6,7 @@ using osu.Game.Skinning;
namespace osu.Game.Rulesets.Catch.Skinning.Argon
{
- public class CatchArgonSkinTransformer : SkinTransformer
+ public class CatchArgonSkinTransformer : ArgonSkinTransformer
{
public CatchArgonSkinTransformer(ISkin skin)
: base(skin)
diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs
index 0f9c97059c..9526ea05c9 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs
@@ -7,7 +7,7 @@ using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu.Skinning.Argon
{
- public class OsuArgonSkinTransformer : SkinTransformer
+ public class OsuArgonSkinTransformer : ArgonSkinTransformer
{
public OsuArgonSkinTransformer(ISkin skin)
: base(skin)
diff --git a/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs
index 9fcecd2b1a..7d38d6c9e5 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs
@@ -7,16 +7,16 @@ using osu.Game.Skinning;
namespace osu.Game.Rulesets.Taiko.Skinning.Argon
{
- public class TaikoArgonSkinTransformer : SkinTransformer
+ public class TaikoArgonSkinTransformer : ArgonSkinTransformer
{
public TaikoArgonSkinTransformer(ISkin skin)
: base(skin)
{
}
- public override Drawable? GetDrawableComponent(ISkinComponentLookup component)
+ public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
{
- switch (component)
+ switch (lookup)
{
case GameplaySkinComponentLookup resultComponent:
// This should eventually be moved to a skin setting, when supported.
@@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
break;
}
- return base.GetDrawableComponent(component);
+ return base.GetDrawableComponent(lookup);
}
}
}
diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs
index 37a35fd3ae..c7d4779064 100644
--- a/osu.Game/Rulesets/Ruleset.cs
+++ b/osu.Game/Rulesets/Ruleset.cs
@@ -212,7 +212,19 @@ namespace osu.Game.Rulesets
/// The source skin.
/// The current beatmap.
/// A skin with a transformer applied, or null if no transformation is provided by this ruleset.
- public virtual ISkin? CreateSkinTransformer(ISkin skin, IBeatmap beatmap) => null;
+ public virtual ISkin? CreateSkinTransformer(ISkin skin, IBeatmap beatmap)
+ {
+ switch (skin)
+ {
+ case LegacySkin:
+ return new LegacySkinTransformer(skin);
+
+ case ArgonSkin:
+ return new ArgonSkinTransformer(skin);
+ }
+
+ return null;
+ }
protected Ruleset()
{
diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs
index 6fcab6a977..bdb65713a0 100644
--- a/osu.Game/Skinning/ArgonSkin.cs
+++ b/osu.Game/Skinning/ArgonSkin.cs
@@ -111,14 +111,13 @@ namespace osu.Game.Skinning
return songSelectComponents;
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
- var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container =>
+ var mainHUDComponents = new DefaultSkinComponentsContainer(container =>
{
var health = container.OfType().FirstOrDefault();
var healthLine = container.OfType().FirstOrDefault();
var wedgePieces = container.OfType().ToArray();
var score = container.OfType().FirstOrDefault();
var accuracy = container.OfType().FirstOrDefault();
- var combo = container.OfType().FirstOrDefault();
var songProgress = container.OfType().FirstOrDefault();
var keyCounter = container.OfType().FirstOrDefault();
@@ -192,13 +191,6 @@ namespace osu.Game.Skinning
keyCounter.Origin = Anchor.BottomRight;
keyCounter.Position = new Vector2(-(hitError.Width + padding), -(padding * 2 + song_progress_offset_height));
}
-
- if (combo != null && hitError != null)
- {
- combo.Anchor = Anchor.BottomLeft;
- combo.Origin = Anchor.BottomLeft;
- combo.Position = new Vector2((hitError.Width + padding), -(padding * 2 + song_progress_offset_height));
- }
}
}
})
@@ -224,10 +216,6 @@ namespace osu.Game.Skinning
CornerRadius = { Value = 0.5f }
},
new ArgonAccuracyCounter(),
- new ArgonComboCounter
- {
- Scale = new Vector2(1.3f)
- },
new BarHitErrorMeter(),
new BarHitErrorMeter(),
new ArgonSongProgress(),
@@ -235,7 +223,7 @@ namespace osu.Game.Skinning
}
};
- return skinnableTargetWrapper;
+ return mainHUDComponents;
}
return null;
diff --git a/osu.Game/Skinning/ArgonSkinTransformer.cs b/osu.Game/Skinning/ArgonSkinTransformer.cs
new file mode 100644
index 0000000000..387a7a9c0b
--- /dev/null
+++ b/osu.Game/Skinning/ArgonSkinTransformer.cs
@@ -0,0 +1,53 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using osu.Framework.Graphics;
+using osu.Game.Screens.Play.HUD;
+using osuTK;
+
+namespace osu.Game.Skinning
+{
+ public class ArgonSkinTransformer : SkinTransformer
+ {
+ public ArgonSkinTransformer(ISkin skin)
+ : base(skin)
+ {
+ }
+
+ public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
+ {
+ switch (lookup)
+ {
+ case SkinComponentsContainerLookup containerLookup:
+ switch (containerLookup.Target)
+ {
+ case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null:
+ var rulesetHUDComponents = Skin.GetDrawableComponent(lookup);
+
+ rulesetHUDComponents ??= new DefaultSkinComponentsContainer(container =>
+ {
+ var combo = container.OfType().FirstOrDefault();
+
+ if (combo != null)
+ {
+ combo.Anchor = Anchor.BottomLeft;
+ combo.Origin = Anchor.BottomLeft;
+ combo.Position = new Vector2(36, -66);
+ combo.Scale = new Vector2(1.3f);
+ }
+ })
+ {
+ new ArgonComboCounter(),
+ };
+
+ return rulesetHUDComponents;
+ }
+
+ break;
+ }
+
+ return base.GetDrawableComponent(lookup);
+ }
+ }
+}
diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs
index 8f0cd59b68..b8e721165e 100644
--- a/osu.Game/Skinning/LegacySkin.cs
+++ b/osu.Game/Skinning/LegacySkin.cs
@@ -399,7 +399,6 @@ namespace osu.Game.Skinning
{
Children = new Drawable[]
{
- new LegacyComboCounter(),
new LegacyScoreCounter(),
new LegacyAccuracyCounter(),
new LegacySongProgress(),
diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs
index 367e5bae01..3ea316c0c7 100644
--- a/osu.Game/Skinning/LegacySkinTransformer.cs
+++ b/osu.Game/Skinning/LegacySkinTransformer.cs
@@ -1,28 +1,62 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Linq;
using osu.Framework.Audio.Sample;
+using osu.Framework.Graphics;
using osu.Game.Audio;
using osu.Game.Rulesets.Objects.Legacy;
+using osuTK;
using static osu.Game.Skinning.SkinConfiguration;
namespace osu.Game.Skinning
{
- ///
- /// Transformer used to handle support of legacy features for individual rulesets.
- ///
- public abstract class LegacySkinTransformer : SkinTransformer
+ public class LegacySkinTransformer : SkinTransformer
{
///
/// Whether the skin being transformed is able to provide legacy resources for the ruleset.
///
public virtual bool IsProvidingLegacyResources => this.HasFont(LegacyFont.Combo);
- protected LegacySkinTransformer(ISkin skin)
+ public LegacySkinTransformer(ISkin skin)
: base(skin)
{
}
+ public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
+ {
+ switch (lookup)
+ {
+ case SkinComponentsContainerLookup containerLookup:
+ switch (containerLookup.Target)
+ {
+ case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null:
+ var rulesetHUDComponents = base.GetDrawableComponent(lookup);
+
+ rulesetHUDComponents ??= new DefaultSkinComponentsContainer(container =>
+ {
+ var combo = container.OfType().FirstOrDefault();
+
+ if (combo != null)
+ {
+ combo.Anchor = Anchor.BottomLeft;
+ combo.Origin = Anchor.BottomLeft;
+ combo.Scale = new Vector2(1.28f);
+ }
+ })
+ {
+ new LegacyComboCounter()
+ };
+
+ return rulesetHUDComponents;
+ }
+
+ break;
+ }
+
+ return base.GetDrawableComponent(lookup);
+ }
+
public override ISample? GetSample(ISampleInfo sampleInfo)
{
if (!(sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample))
From e469e06271e4e4b23a93b5d0c30bf7693fb947e8 Mon Sep 17 00:00:00 2001
From: Salman Ahmed
Date: Sat, 30 Dec 2023 03:54:53 +0300
Subject: [PATCH 0031/1450] Refactor `CatchLegacySkinTransformer` logic and
remove `HiddenByRulesetImplementation` entirely
---
.../TestSceneCatchPlayerLegacySkin.cs | 8 +-
.../Legacy/CatchLegacySkinTransformer.cs | 102 ++++++++----------
.../TestSceneSkinnableComboCounter.cs | 13 ---
osu.Game/Skinning/LegacyComboCounter.cs | 12 ---
4 files changed, 49 insertions(+), 86 deletions(-)
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs
index 5406230359..99325e14c8 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs
@@ -4,7 +4,6 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Extensions.IEnumerableExtensions;
-using osu.Framework.Graphics.Containers;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Skinning;
@@ -19,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Tests
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
[Test]
- public void TestLegacyHUDComboCounterHidden([Values] bool withModifiedSkin)
+ public void TestLegacyHUDComboCounterNotExistent([Values] bool withModifiedSkin)
{
if (withModifiedSkin)
{
@@ -29,10 +28,7 @@ namespace osu.Game.Rulesets.Catch.Tests
CreateTest();
}
- AddAssert("legacy HUD combo counter hidden", () =>
- {
- return Player.ChildrenOfType().All(c => c.ChildrenOfType().Single().Alpha == 0f);
- });
+ AddAssert("legacy HUD combo counter not added", () => !Player.ChildrenOfType().Any());
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
index fb8af9bdb6..675c61a2c5 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
@@ -1,10 +1,9 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System.Linq;
+using System.Diagnostics;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
using osu.Game.Skinning;
using osuTK.Graphics;
@@ -28,76 +27,69 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
{
- if (lookup is SkinComponentsContainerLookup containerLookup)
+ switch (lookup)
{
- switch (containerLookup.Target)
- {
- case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
- var components = base.GetDrawableComponent(lookup) as Container;
+ case SkinComponentsContainerLookup containerLookup:
+ switch (containerLookup.Target)
+ {
+ case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null:
+ Debug.Assert(containerLookup.Ruleset.ShortName == CatchRuleset.SHORT_NAME);
+ // todo: remove CatchSkinComponents.CatchComboCounter and refactor LegacyCatchComboCounter to be added here instead.
+ return Skin.GetDrawableComponent(lookup);
+ }
- if (providesComboCounter && components != null)
- {
- // catch may provide its own combo counter; hide the default.
- // todo: this should be done in an elegant way per ruleset, defining which HUD skin components should be displayed.
- foreach (var legacyComboCounter in components.OfType())
- legacyComboCounter.HiddenByRulesetImplementation = false;
- }
+ break;
- return components;
- }
- }
+ case CatchSkinComponentLookup catchSkinComponent:
+ switch (catchSkinComponent.Component)
+ {
+ case CatchSkinComponents.Fruit:
+ if (hasPear)
+ return new LegacyFruitPiece();
- if (lookup is CatchSkinComponentLookup catchSkinComponent)
- {
- switch (catchSkinComponent.Component)
- {
- case CatchSkinComponents.Fruit:
- if (hasPear)
- return new LegacyFruitPiece();
+ return null;
- return null;
+ case CatchSkinComponents.Banana:
+ if (GetTexture("fruit-bananas") != null)
+ return new LegacyBananaPiece();
- case CatchSkinComponents.Banana:
- if (GetTexture("fruit-bananas") != null)
- return new LegacyBananaPiece();
+ return null;
- return null;
+ case CatchSkinComponents.Droplet:
+ if (GetTexture("fruit-drop") != null)
+ return new LegacyDropletPiece();
- case CatchSkinComponents.Droplet:
- if (GetTexture("fruit-drop") != null)
- return new LegacyDropletPiece();
+ return null;
- return null;
+ case CatchSkinComponents.Catcher:
+ decimal version = GetConfig(SkinConfiguration.LegacySetting.Version)?.Value ?? 1;
- case CatchSkinComponents.Catcher:
- decimal version = GetConfig(SkinConfiguration.LegacySetting.Version)?.Value ?? 1;
+ if (version < 2.3m)
+ {
+ if (hasOldStyleCatcherSprite())
+ return new LegacyCatcherOld();
+ }
- if (version < 2.3m)
- {
- if (hasOldStyleCatcherSprite())
- return new LegacyCatcherOld();
- }
+ if (hasNewStyleCatcherSprite())
+ return new LegacyCatcherNew();
- if (hasNewStyleCatcherSprite())
- return new LegacyCatcherNew();
+ return null;
- return null;
+ case CatchSkinComponents.CatchComboCounter:
+ if (providesComboCounter)
+ return new LegacyCatchComboCounter();
- case CatchSkinComponents.CatchComboCounter:
- if (providesComboCounter)
- return new LegacyCatchComboCounter();
+ return null;
- return null;
+ case CatchSkinComponents.HitExplosion:
+ if (hasOldStyleCatcherSprite() || hasNewStyleCatcherSprite())
+ return new LegacyHitExplosion();
- case CatchSkinComponents.HitExplosion:
- if (hasOldStyleCatcherSprite() || hasNewStyleCatcherSprite())
- return new LegacyHitExplosion();
+ return null;
- return null;
-
- default:
- throw new UnsupportedSkinComponentException(lookup);
- }
+ default:
+ throw new UnsupportedSkinComponentException(lookup);
+ }
}
return base.GetDrawableComponent(lookup);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs
index 72f40d9c6f..a15a3197c5 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs
@@ -4,7 +4,6 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
-using osu.Framework.Testing;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play.HUD;
@@ -28,17 +27,5 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("reset combo", () => scoreProcessor.Combo.Value = 0);
}
-
- [Test]
- public void TestLegacyComboCounterHiddenByRulesetImplementation()
- {
- AddToggleStep("toggle legacy hidden by ruleset", visible =>
- {
- foreach (var legacyCounter in this.ChildrenOfType())
- legacyCounter.HiddenByRulesetImplementation = visible;
- });
-
- AddRepeatStep("increase combo", () => scoreProcessor.Combo.Value++, 10);
- }
}
}
diff --git a/osu.Game/Skinning/LegacyComboCounter.cs b/osu.Game/Skinning/LegacyComboCounter.cs
index cd72055fce..d77a39f607 100644
--- a/osu.Game/Skinning/LegacyComboCounter.cs
+++ b/osu.Game/Skinning/LegacyComboCounter.cs
@@ -43,18 +43,6 @@ namespace osu.Game.Skinning
private readonly Container counterContainer;
- ///
- /// Hides the combo counter internally without affecting its .
- ///
- ///
- /// This is used for rulesets that provide their own combo counter and don't want this HUD one to be visible,
- /// without potentially affecting the user's selected skin.
- ///
- public bool HiddenByRulesetImplementation
- {
- set => counterContainer.Alpha = value ? 1 : 0;
- }
-
public bool UsesFixedAnchor { get; set; }
public LegacyComboCounter()
From 78cb6b68518651a568c00c3434b9695ec76f6c53 Mon Sep 17 00:00:00 2001
From: Salman Ahmed
Date: Sat, 30 Dec 2023 05:29:18 +0300
Subject: [PATCH 0032/1450] Abstractify `LegacyComboCounter` to re-use for
mania
---
.../TestSceneCatchPlayerLegacySkin.cs | 2 +-
.../TestSceneSkinnableComboCounter.cs | 2 +-
osu.Game/Skinning/LegacyComboCounter.cs | 163 +++++-------------
.../Skinning/LegacyDefaultComboCounter.cs | 85 +++++++++
osu.Game/Skinning/LegacySkinTransformer.cs | 4 +-
osu.Game/Skinning/Skin.cs | 1 +
6 files changed, 134 insertions(+), 123 deletions(-)
create mode 100644 osu.Game/Skinning/LegacyDefaultComboCounter.cs
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs
index 99325e14c8..7812e02a63 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Tests
CreateTest();
}
- AddAssert("legacy HUD combo counter not added", () => !Player.ChildrenOfType().Any());
+ AddAssert("legacy HUD combo counter not added", () => !Player.ChildrenOfType().Any());
}
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs
index a15a3197c5..a6196a8ca0 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override Drawable CreateArgonImplementation() => new ArgonComboCounter();
protected override Drawable CreateDefaultImplementation() => new DefaultComboCounter();
- protected override Drawable CreateLegacyImplementation() => new LegacyComboCounter();
+ protected override Drawable CreateLegacyImplementation() => new LegacyDefaultComboCounter();
[Test]
public void TestComboCounterIncrementing()
diff --git a/osu.Game/Skinning/LegacyComboCounter.cs b/osu.Game/Skinning/LegacyComboCounter.cs
index d77a39f607..7003e0d3c8 100644
--- a/osu.Game/Skinning/LegacyComboCounter.cs
+++ b/osu.Game/Skinning/LegacyComboCounter.cs
@@ -5,25 +5,17 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Sprites;
using osu.Game.Rulesets.Scoring;
-using osuTK;
namespace osu.Game.Skinning
{
///
/// Uses the 'x' symbol and has a pop-out effect while rolling over.
///
- public partial class LegacyComboCounter : CompositeDrawable, ISerialisableDrawable
+ public abstract partial class LegacyComboCounter : CompositeDrawable, ISerialisableDrawable
{
public Bindable Current { get; } = new BindableInt { MinValue = 0 };
- private uint scheduledPopOutCurrentId;
-
- private const double big_pop_out_duration = 300;
-
- private const double small_pop_out_duration = 100;
-
private const double fade_out_duration = 100;
///
@@ -31,9 +23,8 @@ namespace osu.Game.Skinning
///
private const double rolling_duration = 20;
- private readonly Drawable popOutCount;
-
- private readonly Drawable displayedCountSpriteText;
+ protected readonly LegacySpriteText PopOutCountText;
+ protected readonly LegacySpriteText DisplayedCountText;
private int previousValue;
@@ -45,17 +36,10 @@ namespace osu.Game.Skinning
public bool UsesFixedAnchor { get; set; }
- public LegacyComboCounter()
+ protected LegacyComboCounter()
{
AutoSizeAxes = Axes.Both;
- Anchor = Anchor.BottomLeft;
- Origin = Anchor.BottomLeft;
-
- Margin = new MarginPadding(10);
-
- Scale = new Vector2(1.28f);
-
InternalChildren = new[]
{
counterContainer = new Container
@@ -63,18 +47,16 @@ namespace osu.Game.Skinning
AlwaysPresent = true,
Children = new[]
{
- popOutCount = new LegacySpriteText(LegacyFont.Combo)
+ PopOutCountText = new LegacySpriteText(LegacyFont.Combo)
{
Alpha = 0,
Blending = BlendingParameters.Additive,
- Anchor = Anchor.BottomLeft,
BypassAutoSizeAxes = Axes.Both,
},
- displayedCountSpriteText = new LegacySpriteText(LegacyFont.Combo)
+ DisplayedCountText = new LegacySpriteText(LegacyFont.Combo)
{
Alpha = 0,
AlwaysPresent = true,
- Anchor = Anchor.BottomLeft,
BypassAutoSizeAxes = Axes.Both,
},
}
@@ -114,26 +96,12 @@ namespace osu.Game.Skinning
{
base.LoadComplete();
- ((IHasText)displayedCountSpriteText).Text = formatCount(Current.Value);
- ((IHasText)popOutCount).Text = formatCount(Current.Value);
+ DisplayedCountText.Text = FormatCount(Current.Value);
+ PopOutCountText.Text = FormatCount(Current.Value);
Current.BindValueChanged(combo => updateCount(combo.NewValue == 0), true);
- updateLayout();
- }
-
- private void updateLayout()
- {
- const float font_height_ratio = 0.625f;
- const float vertical_offset = 9;
-
- displayedCountSpriteText.OriginPosition = new Vector2(0, font_height_ratio * displayedCountSpriteText.Height + vertical_offset);
- displayedCountSpriteText.Position = new Vector2(0, -(1 - font_height_ratio) * displayedCountSpriteText.Height + vertical_offset);
-
- popOutCount.OriginPosition = new Vector2(3, font_height_ratio * popOutCount.Height + vertical_offset); // In stable, the bigger pop out scales a bit to the left
- popOutCount.Position = new Vector2(0, -(1 - font_height_ratio) * popOutCount.Height + vertical_offset);
-
- counterContainer.Size = displayedCountSpriteText.Size;
+ counterContainer.Size = DisplayedCountText.Size;
}
private void updateCount(bool rolling)
@@ -147,127 +115,84 @@ namespace osu.Game.Skinning
if (!rolling)
{
FinishTransforms(false, nameof(DisplayedCount));
+
isRolling = false;
DisplayedCount = prev;
if (prev + 1 == Current.Value)
- onCountIncrement(prev, Current.Value);
+ OnCountIncrement();
else
- onCountChange(Current.Value);
+ OnCountChange();
}
else
{
- onCountRolling(displayedCount, Current.Value);
+ OnCountRolling();
isRolling = true;
}
}
- private void transformPopOut(int newValue)
+ ///
+ /// Raised when the counter should display the new value with transitions.
+ ///
+ protected virtual void OnCountIncrement()
{
- ((IHasText)popOutCount).Text = formatCount(newValue);
-
- popOutCount.ScaleTo(1.56f)
- .ScaleTo(1, big_pop_out_duration);
-
- popOutCount.FadeTo(0.6f)
- .FadeOut(big_pop_out_duration);
- }
-
- private void transformNoPopOut(int newValue)
- {
- ((IHasText)displayedCountSpriteText).Text = formatCount(newValue);
-
- counterContainer.Size = displayedCountSpriteText.Size;
-
- displayedCountSpriteText.ScaleTo(1);
- }
-
- private void transformPopOutSmall(int newValue)
- {
- ((IHasText)displayedCountSpriteText).Text = formatCount(newValue);
-
- counterContainer.Size = displayedCountSpriteText.Size;
-
- displayedCountSpriteText.ScaleTo(1).Then()
- .ScaleTo(1.1f, small_pop_out_duration / 2, Easing.In).Then()
- .ScaleTo(1, small_pop_out_duration / 2, Easing.Out);
- }
-
- private void scheduledPopOutSmall(uint id)
- {
- // Too late; scheduled task invalidated
- if (id != scheduledPopOutCurrentId)
- return;
+ if (DisplayedCount < Current.Value - 1)
+ DisplayedCount++;
DisplayedCount++;
}
- private void onCountIncrement(int currentValue, int newValue)
+ ///
+ /// Raised when the counter should roll to the new combo value (usually roll back to zero).
+ ///
+ protected virtual void OnCountRolling()
{
- scheduledPopOutCurrentId++;
-
- if (DisplayedCount < currentValue)
- DisplayedCount++;
-
- displayedCountSpriteText.Show();
-
- transformPopOut(newValue);
-
- uint newTaskId = scheduledPopOutCurrentId;
-
- Scheduler.AddDelayed(delegate
- {
- scheduledPopOutSmall(newTaskId);
- }, big_pop_out_duration - 140);
- }
-
- private void onCountRolling(int currentValue, int newValue)
- {
- scheduledPopOutCurrentId++;
-
// Hides displayed count if was increasing from 0 to 1 but didn't finish
- if (currentValue == 0 && newValue == 0)
- displayedCountSpriteText.FadeOut(fade_out_duration);
+ if (DisplayedCount == 0 && Current.Value == 0)
+ DisplayedCountText.FadeOut(fade_out_duration);
- transformRoll(currentValue, newValue);
+ transformRoll(DisplayedCount, Current.Value);
}
- private void onCountChange(int newValue)
+ ///
+ /// Raised when the counter should display the new combo value without any transitions.
+ ///
+ protected virtual void OnCountChange()
{
- scheduledPopOutCurrentId++;
+ if (Current.Value == 0)
+ DisplayedCountText.FadeOut();
- if (newValue == 0)
- displayedCountSpriteText.FadeOut();
-
- DisplayedCount = newValue;
+ DisplayedCount = Current.Value;
}
private void onDisplayedCountRolling(int newValue)
{
if (newValue == 0)
- displayedCountSpriteText.FadeOut(fade_out_duration);
- else
- displayedCountSpriteText.Show();
+ DisplayedCountText.FadeOut(fade_out_duration);
- transformNoPopOut(newValue);
+ DisplayedCountText.Text = FormatCount(newValue);
+ counterContainer.Size = DisplayedCountText.Size;
}
private void onDisplayedCountChange(int newValue)
{
- displayedCountSpriteText.FadeTo(newValue == 0 ? 0 : 1);
- transformNoPopOut(newValue);
+ DisplayedCountText.FadeTo(newValue == 0 ? 0 : 1);
+ DisplayedCountText.Text = FormatCount(newValue);
+
+ counterContainer.Size = DisplayedCountText.Size;
}
private void onDisplayedCountIncrement(int newValue)
{
- displayedCountSpriteText.Show();
- transformPopOutSmall(newValue);
+ DisplayedCountText.Text = FormatCount(newValue);
+
+ counterContainer.Size = DisplayedCountText.Size;
}
private void transformRoll(int currentValue, int newValue) =>
this.TransformTo(nameof(DisplayedCount), newValue, getProportionalDuration(currentValue, newValue));
- private string formatCount(int count) => $@"{count}x";
+ protected virtual string FormatCount(int count) => $@"{count}";
private double getProportionalDuration(int currentValue, int newValue)
{
diff --git a/osu.Game/Skinning/LegacyDefaultComboCounter.cs b/osu.Game/Skinning/LegacyDefaultComboCounter.cs
new file mode 100644
index 0000000000..f633358993
--- /dev/null
+++ b/osu.Game/Skinning/LegacyDefaultComboCounter.cs
@@ -0,0 +1,85 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Threading;
+using osuTK;
+
+namespace osu.Game.Skinning
+{
+ ///
+ /// Uses the 'x' symbol and has a pop-out effect while rolling over.
+ ///
+ public partial class LegacyDefaultComboCounter : LegacyComboCounter
+ {
+ private const double big_pop_out_duration = 300;
+ private const double small_pop_out_duration = 100;
+
+ private ScheduledDelegate? scheduledPopOut;
+
+ public LegacyDefaultComboCounter()
+ {
+ Margin = new MarginPadding(10);
+
+ PopOutCountText.Anchor = Anchor.BottomLeft;
+ DisplayedCountText.Anchor = Anchor.BottomLeft;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ const float font_height_ratio = 0.625f;
+ const float vertical_offset = 9;
+
+ DisplayedCountText.OriginPosition = new Vector2(0, font_height_ratio * DisplayedCountText.Height + vertical_offset);
+ DisplayedCountText.Position = new Vector2(0, -(1 - font_height_ratio) * DisplayedCountText.Height + vertical_offset);
+
+ PopOutCountText.OriginPosition = new Vector2(3, font_height_ratio * PopOutCountText.Height + vertical_offset); // In stable, the bigger pop out scales a bit to the left
+ PopOutCountText.Position = new Vector2(0, -(1 - font_height_ratio) * PopOutCountText.Height + vertical_offset);
+ }
+
+ protected override void OnCountIncrement()
+ {
+ scheduledPopOut?.Cancel();
+ scheduledPopOut = null;
+
+ DisplayedCountText.Show();
+
+ PopOutCountText.Text = FormatCount(Current.Value);
+
+ PopOutCountText.ScaleTo(1.56f)
+ .ScaleTo(1, big_pop_out_duration);
+
+ PopOutCountText.FadeTo(0.6f)
+ .FadeOut(big_pop_out_duration);
+
+ this.Delay(big_pop_out_duration - 140).Schedule(() =>
+ {
+ base.OnCountIncrement();
+
+ DisplayedCountText.ScaleTo(1).Then()
+ .ScaleTo(1.1f, small_pop_out_duration / 2, Easing.In).Then()
+ .ScaleTo(1, small_pop_out_duration / 2, Easing.Out);
+ }, out scheduledPopOut);
+ }
+
+ protected override void OnCountRolling()
+ {
+ scheduledPopOut?.Cancel();
+ scheduledPopOut = null;
+
+ base.OnCountRolling();
+ }
+
+ protected override void OnCountChange()
+ {
+ scheduledPopOut?.Cancel();
+ scheduledPopOut = null;
+
+ base.OnCountChange();
+ }
+
+ protected override string FormatCount(int count) => $@"{count}x";
+ }
+}
diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs
index 3ea316c0c7..66978fc6b0 100644
--- a/osu.Game/Skinning/LegacySkinTransformer.cs
+++ b/osu.Game/Skinning/LegacySkinTransformer.cs
@@ -35,7 +35,7 @@ namespace osu.Game.Skinning
rulesetHUDComponents ??= new DefaultSkinComponentsContainer(container =>
{
- var combo = container.OfType().FirstOrDefault();
+ var combo = container.OfType().FirstOrDefault();
if (combo != null)
{
@@ -45,7 +45,7 @@ namespace osu.Game.Skinning
}
})
{
- new LegacyComboCounter()
+ new LegacyDefaultComboCounter()
};
return rulesetHUDComponents;
diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs
index 9ee69d033d..80bb340109 100644
--- a/osu.Game/Skinning/Skin.cs
+++ b/osu.Game/Skinning/Skin.cs
@@ -153,6 +153,7 @@ namespace osu.Game.Skinning
// handle namespace changes...
jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.SongProgress", @"osu.Game.Screens.Play.HUD.DefaultSongProgress");
jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.LegacyComboCounter", @"osu.Game.Skinning.LegacyComboCounter");
+ jsonContent = jsonContent.Replace(@"osu.Game.Skinning.LegacyComboCounter", @"osu.Game.Skinning.LegacyDefaultComboCounter");
var deserializedContent = JsonConvert.DeserializeObject>(jsonContent);
From ece532b837a3ebe76791e8fc5c528cba854a5ad0 Mon Sep 17 00:00:00 2001
From: Salman Ahmed
Date: Sat, 30 Dec 2023 05:18:56 +0300
Subject: [PATCH 0033/1450] Add legacy mania combo counter lookups
---
osu.Game/Skinning/LegacyManiaSkinConfiguration.cs | 1 +
osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs | 2 ++
osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 4 ++++
osu.Game/Skinning/LegacySkin.cs | 6 ++++++
4 files changed, 13 insertions(+)
diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs
index 042836984a..db1f216b6e 100644
--- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs
+++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs
@@ -39,6 +39,7 @@ namespace osu.Game.Skinning
public float HitPosition = DEFAULT_HIT_POSITION;
public float LightPosition = (480 - 413) * POSITION_SCALE_FACTOR;
+ public float ComboPosition = 111 * POSITION_SCALE_FACTOR;
public float ScorePosition = 300 * POSITION_SCALE_FACTOR;
public bool ShowJudgementLine = true;
public bool KeysUnderNotes;
diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs
index cacca0de23..fc90fc89eb 100644
--- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs
+++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs
@@ -42,6 +42,7 @@ namespace osu.Game.Skinning
LeftLineWidth,
RightLineWidth,
HitPosition,
+ ComboPosition,
ScorePosition,
LightPosition,
StagePaddingTop,
@@ -63,6 +64,7 @@ namespace osu.Game.Skinning
JudgementLineColour,
ColumnBackgroundColour,
ColumnLightColour,
+ ComboBreakColour,
MinimumColumnWidth,
LeftStageImage,
RightStageImage,
diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs
index b472afb74f..5dd8f9c52d 100644
--- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs
+++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs
@@ -94,6 +94,10 @@ namespace osu.Game.Skinning
currentConfig.LightPosition = (480 - float.Parse(pair.Value, CultureInfo.InvariantCulture)) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR;
break;
+ case "ComboPosition":
+ currentConfig.ComboPosition = (float.Parse(pair.Value, CultureInfo.InvariantCulture)) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR;
+ break;
+
case "ScorePosition":
currentConfig.ScorePosition = (float.Parse(pair.Value, CultureInfo.InvariantCulture)) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR;
break;
diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs
index b8e721165e..848a6366ed 100644
--- a/osu.Game/Skinning/LegacySkin.cs
+++ b/osu.Game/Skinning/LegacySkin.cs
@@ -152,6 +152,9 @@ namespace osu.Game.Skinning
case LegacyManiaSkinConfigurationLookups.HitPosition:
return SkinUtils.As(new Bindable(existing.HitPosition));
+ case LegacyManiaSkinConfigurationLookups.ComboPosition:
+ return SkinUtils.As(new Bindable(existing.ComboPosition));
+
case LegacyManiaSkinConfigurationLookups.ScorePosition:
return SkinUtils.As(new Bindable(existing.ScorePosition));
@@ -189,6 +192,9 @@ namespace osu.Game.Skinning
Debug.Assert(maniaLookup.ColumnIndex != null);
return SkinUtils.As(getCustomColour(existing, $"ColourLight{maniaLookup.ColumnIndex + 1}"));
+ case LegacyManiaSkinConfigurationLookups.ComboBreakColour:
+ return SkinUtils.As(getCustomColour(existing, "ColourBreak"));
+
case LegacyManiaSkinConfigurationLookups.MinimumColumnWidth:
return SkinUtils.As(new Bindable(existing.MinimumColumnWidth));
From 8be3f4f632f62d70c365c45c1f48a1747b4579f3 Mon Sep 17 00:00:00 2001
From: Salman Ahmed
Date: Sat, 30 Dec 2023 05:20:44 +0300
Subject: [PATCH 0034/1450] Add legacy mania combo counter implementation
---
.../Legacy/LegacyManiaComboCounter.cs | 94 +++++++++++++++++++
.../Legacy/ManiaLegacySkinTransformer.cs | 31 ++++++
2 files changed, 125 insertions(+)
create mode 100644 osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs
diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs
new file mode 100644
index 0000000000..fd309f6250
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs
@@ -0,0 +1,94 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.UI;
+using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Skinning;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.Skinning.Legacy
+{
+ public partial class LegacyManiaComboCounter : LegacyComboCounter, ISerialisableDrawable
+ {
+ private DrawableManiaRuleset maniaRuleset = null!;
+
+ bool ISerialisableDrawable.SupportsClosestAnchor => false;
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin, DrawableRuleset ruleset)
+ {
+ maniaRuleset = (DrawableManiaRuleset)ruleset;
+
+ DisplayedCountText.Anchor = Anchor.Centre;
+ DisplayedCountText.Origin = Anchor.Centre;
+
+ PopOutCountText.Anchor = Anchor.Centre;
+ PopOutCountText.Origin = Anchor.Centre;
+ PopOutCountText.Colour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ComboBreakColour)?.Value ?? Color4.Red;
+
+ UsesFixedAnchor = true;
+ }
+
+ private IBindable direction = null!;
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ direction = maniaRuleset.ScrollingInfo.Direction.GetBoundCopy();
+ direction.BindValueChanged(_ => updateAnchor());
+
+ // two schedules are required so that updateAnchor is executed in the next frame,
+ // which is when the combo counter receives its Y position by the default layout in LegacyManiaSkinTransformer.
+ Schedule(() => Schedule(updateAnchor));
+ }
+
+ private void updateAnchor()
+ {
+ Anchor &= ~(Anchor.y0 | Anchor.y2);
+ Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0;
+
+ // since we flip the vertical anchor when changing scroll direction,
+ // we can use the sign of the Y value as an indicator to make the combo counter displayed correctly.
+ if ((Y < 0 && direction.Value == ScrollingDirection.Down) || (Y > 0 && direction.Value == ScrollingDirection.Up))
+ Y = -Y;
+ }
+
+ protected override void OnCountIncrement()
+ {
+ base.OnCountIncrement();
+
+ PopOutCountText.Hide();
+ DisplayedCountText.ScaleTo(new Vector2(1f, 1.4f))
+ .ScaleTo(new Vector2(1f), 300, Easing.Out)
+ .FadeIn(120);
+ }
+
+ protected override void OnCountChange()
+ {
+ base.OnCountChange();
+
+ PopOutCountText.Hide();
+ DisplayedCountText.ScaleTo(1f);
+ }
+
+ protected override void OnCountRolling()
+ {
+ if (DisplayedCount > 0)
+ {
+ PopOutCountText.Text = FormatCount(DisplayedCount);
+ PopOutCountText.FadeTo(0.8f).FadeOut(200)
+ .ScaleTo(1f).ScaleTo(4f, 200);
+
+ DisplayedCountText.FadeTo(0.5f, 300);
+ }
+
+ base.OnCountRolling();
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs
index 73c521b2ed..c539c239bd 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs
@@ -5,9 +5,12 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Testing;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
@@ -78,6 +81,34 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
switch (lookup)
{
+ case SkinComponentsContainerLookup containerLookup:
+ switch (containerLookup.Target)
+ {
+ case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null:
+ Debug.Assert(containerLookup.Ruleset.ShortName == ManiaRuleset.SHORT_NAME);
+
+ var rulesetHUDComponents = Skin.GetDrawableComponent(lookup);
+
+ rulesetHUDComponents ??= new DefaultSkinComponentsContainer(container =>
+ {
+ var combo = container.ChildrenOfType().FirstOrDefault();
+
+ if (combo != null)
+ {
+ combo.Anchor = Anchor.TopCentre;
+ combo.Origin = Anchor.Centre;
+ combo.Y = this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ComboPosition)?.Value ?? 0;
+ }
+ })
+ {
+ new LegacyManiaComboCounter(),
+ };
+
+ return rulesetHUDComponents;
+ }
+
+ break;
+
case GameplaySkinComponentLookup resultComponent:
return getResult(resultComponent.Component);
From 408287e086b18187c72e0bde57571be39fd621b0 Mon Sep 17 00:00:00 2001
From: Salman Ahmed
Date: Sat, 30 Dec 2023 05:20:51 +0300
Subject: [PATCH 0035/1450] Add very basic argon mania combo counter
implementation
---
.../Skinning/Argon/ArgonManiaComboCounter.cs | 84 +++++++++++++++++++
.../Argon/ManiaArgonSkinTransformer.cs | 33 +++++++-
2 files changed, 116 insertions(+), 1 deletion(-)
create mode 100644 osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs
diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs
new file mode 100644
index 0000000000..1c8e43345a
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs
@@ -0,0 +1,84 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.UI;
+using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Screens.Play.HUD;
+using osu.Game.Skinning;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.Skinning.Argon
+{
+ public partial class ArgonManiaComboCounter : ComboCounter, ISerialisableDrawable
+ {
+ private OsuSpriteText text = null!;
+
+ protected override double RollingDuration => 500;
+ protected override Easing RollingEasing => Easing.OutQuint;
+
+ private DrawableManiaRuleset maniaRuleset = null!;
+
+ bool ISerialisableDrawable.SupportsClosestAnchor => false;
+
+ [BackgroundDependencyLoader]
+ private void load(DrawableRuleset ruleset, ScoreProcessor scoreProcessor)
+ {
+ maniaRuleset = (DrawableManiaRuleset)ruleset;
+
+ Current.BindTo(scoreProcessor.Combo);
+ Current.BindValueChanged(combo =>
+ {
+ if (combo.OldValue == 0 && combo.NewValue > 0)
+ text.FadeIn(200, Easing.OutQuint);
+ else if (combo.OldValue > 0 && combo.NewValue == 0)
+ {
+ if (combo.OldValue > 1)
+ text.FlashColour(Color4.Red, 2000, Easing.OutQuint);
+
+ text.FadeOut(200, Easing.InQuint);
+ }
+ });
+
+ UsesFixedAnchor = true;
+ }
+
+ private IBindable direction = null!;
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ text.Alpha = Current.Value > 0 ? 1 : 0;
+
+ direction = maniaRuleset.ScrollingInfo.Direction.GetBoundCopy();
+ direction.BindValueChanged(_ => updateAnchor());
+
+ // two schedules are required so that updateAnchor is executed in the next frame,
+ // which is when the combo counter receives its Y position by the default layout in ArgonManiaSkinTransformer.
+ Schedule(() => Schedule(updateAnchor));
+ }
+
+ private void updateAnchor()
+ {
+ Anchor &= ~(Anchor.y0 | Anchor.y2);
+ Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0;
+
+ // since we flip the vertical anchor when changing scroll direction,
+ // we can use the sign of the Y value as an indicator to make the combo counter displayed correctly.
+ if ((Y < 0 && direction.Value == ScrollingDirection.Down) || (Y > 0 && direction.Value == ScrollingDirection.Up))
+ Y = -Y;
+ }
+
+ protected override IHasText CreateText() => text = new OsuSpriteText
+ {
+ Font = OsuFont.Torus.With(size: 32, fixedWidth: true),
+ };
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs
index 7f6540e7b5..b0a6086f2a 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs
@@ -2,8 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Diagnostics;
+using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Scoring;
@@ -12,7 +15,7 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Skinning.Argon
{
- public class ManiaArgonSkinTransformer : SkinTransformer
+ public class ManiaArgonSkinTransformer : ArgonSkinTransformer
{
private readonly ManiaBeatmap beatmap;
@@ -26,6 +29,34 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
{
switch (lookup)
{
+ case SkinComponentsContainerLookup containerLookup:
+ switch (containerLookup.Target)
+ {
+ case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null:
+ Debug.Assert(containerLookup.Ruleset.ShortName == ManiaRuleset.SHORT_NAME);
+
+ var rulesetHUDComponents = Skin.GetDrawableComponent(lookup);
+
+ rulesetHUDComponents ??= new DefaultSkinComponentsContainer(container =>
+ {
+ var combo = container.ChildrenOfType().FirstOrDefault();
+
+ if (combo != null)
+ {
+ combo.Anchor = Anchor.TopCentre;
+ combo.Origin = Anchor.Centre;
+ combo.Y = 200;
+ }
+ })
+ {
+ new ArgonManiaComboCounter(),
+ };
+
+ return rulesetHUDComponents;
+ }
+
+ break;
+
case GameplaySkinComponentLookup resultComponent:
// This should eventually be moved to a skin setting, when supported.
if (Skin is ArgonProSkin && resultComponent.Component >= HitResult.Great)
From 95961dc98955aed19f1e5fd482ba119be868ec0b Mon Sep 17 00:00:00 2001
From: Salman Ahmed
Date: Sat, 30 Dec 2023 05:21:10 +0300
Subject: [PATCH 0036/1450] Add various visual test coverage
---
.../Skinning/TestSceneComboCounter.cs | 38 +++++++++++++++++++
.../Skinning/TestScenePlayfield.cs | 13 +++++++
.../TestSceneManiaPlayerLegacySkin.cs | 36 ++++++++++++++++++
3 files changed, 87 insertions(+)
create mode 100644 osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneComboCounter.cs
create mode 100644 osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayerLegacySkin.cs
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneComboCounter.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneComboCounter.cs
new file mode 100644
index 0000000000..c1e1cfd7af
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneComboCounter.cs
@@ -0,0 +1,38 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Testing;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Mania.Skinning.Argon;
+using osu.Game.Rulesets.Mania.Skinning.Legacy;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Mania.Tests.Skinning
+{
+ public partial class TestSceneComboCounter : ManiaSkinnableTestScene
+ {
+ [Cached]
+ private ScoreProcessor scoreProcessor = new ScoreProcessor(new ManiaRuleset());
+
+ [SetUpSteps]
+ public void SetUpSteps()
+ {
+ AddStep("setup", () => SetContents(s =>
+ {
+ if (s is ArgonSkin)
+ return new ArgonManiaComboCounter();
+
+ if (s is LegacySkin)
+ return new LegacyManiaComboCounter();
+
+ return new LegacyManiaComboCounter();
+ }));
+
+ AddRepeatStep("perform hit", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Great }), 20);
+ AddStep("perform miss", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss }));
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs
index 29c47ca93a..110336d823 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs
@@ -3,15 +3,22 @@
using System.Collections.Generic;
using NUnit.Framework;
+using osu.Framework.Allocation;
using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
using osuTK;
namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
public partial class TestScenePlayfield : ManiaSkinnableTestScene
{
+ [Cached]
+ private ScoreProcessor scoreProcessor = new ScoreProcessor(new ManiaRuleset());
+
private List stageDefinitions = new List();
[Test]
@@ -29,6 +36,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
Child = new ManiaPlayfield(stageDefinitions)
});
});
+
+ AddRepeatStep("perform hit", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Perfect }), 20);
+ AddStep("perform miss", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss }));
}
[TestCase(2)]
@@ -54,6 +64,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
}
});
});
+
+ AddRepeatStep("perform hit", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Perfect }), 20);
+ AddStep("perform miss", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss }));
}
protected override IBeatmap CreateBeatmapForSkinProvider()
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayerLegacySkin.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayerLegacySkin.cs
new file mode 100644
index 0000000000..0f10f96dbf
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayerLegacySkin.cs
@@ -0,0 +1,36 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mania.Mods;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Tests.Beatmaps;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ public partial class TestSceneManiaPlayerLegacySkin : LegacySkinPlayerTestScene
+ {
+ protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
+
+ // play with a converted beatmap to allow dual stages mod to work.
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(new RulesetInfo());
+
+ protected override bool HasCustomSteps => true;
+
+ [Test]
+ public void TestSingleStage()
+ {
+ AddStep("Load single stage", LoadPlayer);
+ AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1);
+ }
+
+ [Test]
+ public void TestDualStage()
+ {
+ AddStep("Load dual stage", () => LoadPlayer(new Mod[] { new ManiaModDualStages() }));
+ AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1);
+ }
+ }
+}
From 01219fa3712f46ea55c0df150c2c3be2c0a31a48 Mon Sep 17 00:00:00 2001
From: Salman Ahmed
Date: Sat, 30 Dec 2023 05:16:41 +0300
Subject: [PATCH 0037/1450] Disable "closest" anchor in mania combo counter for
convenience
---
osu.Game/Overlays/SkinEditor/SkinEditor.cs | 3 +++
osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs | 3 ++-
osu.Game/Skinning/ISerialisableDrawable.cs | 8 ++++++++
3 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs
index f972186333..cc1e5b26ec 100644
--- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs
+++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs
@@ -441,6 +441,9 @@ namespace osu.Game.Overlays.SkinEditor
drawableComponent.Origin = Anchor.TopCentre;
drawableComponent.Anchor = Anchor.TopCentre;
drawableComponent.Y = targetContainer.DrawSize.Y / 2;
+
+ if (!component.SupportsClosestAnchor)
+ component.UsesFixedAnchor = true;
}
try
diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs
index cf6fb60636..208bd71005 100644
--- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs
+++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs
@@ -233,7 +233,8 @@ namespace osu.Game.Overlays.SkinEditor
{
var closestItem = new TernaryStateRadioMenuItem("Closest", MenuItemType.Standard, _ => applyClosestAnchors())
{
- State = { Value = GetStateFromSelection(selection, c => !c.Item.UsesFixedAnchor) }
+ State = { Value = GetStateFromSelection(selection, c => !c.Item.UsesFixedAnchor), },
+ Action = { Disabled = selection.Any(c => !c.Item.SupportsClosestAnchor) },
};
yield return new OsuMenuItem("Anchor")
diff --git a/osu.Game/Skinning/ISerialisableDrawable.cs b/osu.Game/Skinning/ISerialisableDrawable.cs
index c9dcaca6d1..898186bcc1 100644
--- a/osu.Game/Skinning/ISerialisableDrawable.cs
+++ b/osu.Game/Skinning/ISerialisableDrawable.cs
@@ -27,6 +27,14 @@ namespace osu.Game.Skinning
///
bool IsEditable => true;
+ ///
+ /// Whether this component supports the "closest" anchor.
+ ///
+ ///
+ /// This is disabled by some components that shift position automatically.
+ ///
+ bool SupportsClosestAnchor => true;
+
///
/// In the context of the skin layout editor, whether this has a permanent anchor defined.
/// If , this 's is automatically determined by proximity,
From 02f5ea200ea7b464d6d78170dc7b7beeb3e61559 Mon Sep 17 00:00:00 2001
From: Salman Ahmed
Date: Sat, 30 Dec 2023 07:41:55 +0300
Subject: [PATCH 0038/1450] Fix failing tests
---
.../Skinning/Argon/ArgonManiaComboCounter.cs | 13 +++++------
.../Legacy/LegacyManiaComboCounter.cs | 22 ++++++++++---------
osu.Game/Screens/Play/Player.cs | 4 ++++
3 files changed, 21 insertions(+), 18 deletions(-)
diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs
index 1c8e43345a..ad515528fb 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs
@@ -7,9 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
-using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Scoring;
-using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Screens.Play.HUD;
using osu.Game.Skinning;
@@ -24,15 +22,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
protected override double RollingDuration => 500;
protected override Easing RollingEasing => Easing.OutQuint;
- private DrawableManiaRuleset maniaRuleset = null!;
-
bool ISerialisableDrawable.SupportsClosestAnchor => false;
[BackgroundDependencyLoader]
- private void load(DrawableRuleset ruleset, ScoreProcessor scoreProcessor)
+ private void load(ScoreProcessor scoreProcessor)
{
- maniaRuleset = (DrawableManiaRuleset)ruleset;
-
Current.BindTo(scoreProcessor.Combo);
Current.BindValueChanged(combo =>
{
@@ -50,6 +44,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
UsesFixedAnchor = true;
}
+ [Resolved]
+ private IScrollingInfo scrollingInfo { get; set; } = null!;
+
private IBindable direction = null!;
protected override void LoadComplete()
@@ -57,7 +54,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
base.LoadComplete();
text.Alpha = Current.Value > 0 ? 1 : 0;
- direction = maniaRuleset.ScrollingInfo.Direction.GetBoundCopy();
+ direction = scrollingInfo.Direction.GetBoundCopy();
direction.BindValueChanged(_ => updateAnchor());
// two schedules are required so that updateAnchor is executed in the next frame,
diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs
index fd309f6250..00619834c8 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs
@@ -3,9 +3,8 @@
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
-using osu.Game.Rulesets.Mania.UI;
-using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
using osuTK;
@@ -15,15 +14,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
public partial class LegacyManiaComboCounter : LegacyComboCounter, ISerialisableDrawable
{
- private DrawableManiaRuleset maniaRuleset = null!;
-
bool ISerialisableDrawable.SupportsClosestAnchor => false;
[BackgroundDependencyLoader]
- private void load(ISkinSource skin, DrawableRuleset ruleset)
+ private void load(ISkinSource skin)
{
- maniaRuleset = (DrawableManiaRuleset)ruleset;
-
DisplayedCountText.Anchor = Anchor.Centre;
DisplayedCountText.Origin = Anchor.Centre;
@@ -34,13 +29,16 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
UsesFixedAnchor = true;
}
+ [Resolved]
+ private IScrollingInfo scrollingInfo { get; set; } = null!;
+
private IBindable direction = null!;
protected override void LoadComplete()
{
base.LoadComplete();
- direction = maniaRuleset.ScrollingInfo.Direction.GetBoundCopy();
+ direction = scrollingInfo.Direction.GetBoundCopy();
direction.BindValueChanged(_ => updateAnchor());
// two schedules are required so that updateAnchor is executed in the next frame,
@@ -50,8 +48,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
private void updateAnchor()
{
- Anchor &= ~(Anchor.y0 | Anchor.y2);
- Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0;
+ // if the anchor isn't a vertical center, set top or bottom anchor based on scroll direction
+ if (!Anchor.HasFlagFast(Anchor.y1))
+ {
+ Anchor &= ~(Anchor.y0 | Anchor.y2);
+ Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0;
+ }
// since we flip the vertical anchor when changing scroll direction,
// we can use the sign of the Y value as an indicator to make the combo counter displayed correctly.
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index c960ac357f..c10ef9731f 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -32,6 +32,7 @@ using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
+using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Scoring;
using osu.Game.Scoring.Legacy;
using osu.Game.Screens.Play.HUD;
@@ -224,6 +225,9 @@ namespace osu.Game.Screens.Play
DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, gameplayMods);
dependencies.CacheAs(DrawableRuleset);
+ if (DrawableRuleset is IDrawableScrollingRuleset scrollingRuleset)
+ dependencies.CacheAs(scrollingRuleset.ScrollingInfo);
+
ScoreProcessor = ruleset.CreateScoreProcessor();
ScoreProcessor.Mods.Value = gameplayMods;
ScoreProcessor.ApplyBeatmap(playableBeatmap);
From 6bb72a9fccb92760377e3b081b99088a480c33c9 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Mon, 1 Jan 2024 15:46:07 +0100
Subject: [PATCH 0039/1450] Revert "Remove other grid types"
This reverts commit de14da95fa6d0230af1aeef7e9b0afd5caaa059e.
---
.../Editor/TestSceneOsuEditorGrids.cs | 3 +
.../Edit/OsuGridToolboxGroup.cs | 79 ++++++++++++++
.../Edit/OsuHitObjectComposer.cs | 41 +++++--
.../Editing/TestScenePositionSnapGrid.cs | 45 ++++++++
.../Components/CircularPositionSnapGrid.cs | 101 ++++++++++++++++++
.../Components/TriangularPositionSnapGrid.cs | 89 +++++++++++++++
6 files changed, 351 insertions(+), 7 deletions(-)
create mode 100644 osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs
create mode 100644 osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs
index 7cafd10454..21427ba281 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.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 NUnit.Framework;
using osu.Framework.Testing;
@@ -100,6 +101,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
return grid switch
{
RectangularPositionSnapGrid rectangular => rectangular.StartPosition.Value + GeometryUtils.RotateVector(rectangular.Spacing.Value, -rectangular.GridLineRotation.Value),
+ TriangularPositionSnapGrid triangular => triangular.StartPosition.Value + GeometryUtils.RotateVector(new Vector2(triangular.Spacing.Value / 2, triangular.Spacing.Value / 2 * MathF.Sqrt(3)), -triangular.GridLineRotation.Value),
+ CircularPositionSnapGrid circular => circular.StartPosition.Value + GeometryUtils.RotateVector(new Vector2(circular.Spacing.Value, 0), -45),
_ => Vector2.Zero
};
}
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
index e82ca780ad..76e735449a 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
@@ -1,9 +1,13 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Graphics.Containers;
@@ -12,7 +16,9 @@ using osu.Game.Input.Bindings;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Screens.Edit;
+using osu.Game.Screens.Edit.Components.RadioButtons;
using osuTK;
+using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Edit
{
@@ -76,10 +82,13 @@ namespace osu.Game.Rulesets.Osu.Edit
///
public Bindable SpacingVector { get; } = new Bindable();
+ public Bindable GridType { get; } = new Bindable();
+
private ExpandableSlider startPositionXSlider = null!;
private ExpandableSlider startPositionYSlider = null!;
private ExpandableSlider spacingSlider = null!;
private ExpandableSlider gridLinesRotationSlider = null!;
+ private EditorRadioButtonCollection gridTypeButtons = null!;
public OsuGridToolboxGroup()
: base("grid")
@@ -113,6 +122,31 @@ namespace osu.Game.Rulesets.Osu.Edit
Current = GridLinesRotation,
KeyboardStep = 1,
},
+ new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Spacing = new Vector2(0f, 10f),
+ Children = new Drawable[]
+ {
+ gridTypeButtons = new EditorRadioButtonCollection
+ {
+ RelativeSizeAxes = Axes.X,
+ Items = new[]
+ {
+ new RadioButton("Square",
+ () => GridType.Value = PositionSnapGridType.Square,
+ () => new SpriteIcon { Icon = FontAwesome.Regular.Square }),
+ new RadioButton("Triangle",
+ () => GridType.Value = PositionSnapGridType.Triangle,
+ () => new OutlineTriangle(true, 20)),
+ new RadioButton("Circle",
+ () => GridType.Value = PositionSnapGridType.Circle,
+ () => new SpriteIcon { Icon = FontAwesome.Regular.Circle }),
+ }
+ },
+ }
+ },
};
Spacing.Value = editorBeatmap.BeatmapInfo.GridSize;
@@ -122,6 +156,8 @@ namespace osu.Game.Rulesets.Osu.Edit
{
base.LoadComplete();
+ gridTypeButtons.Items.First().Select();
+
StartPositionX.BindValueChanged(x =>
{
startPositionXSlider.ContractedLabelText = $"X: {x.NewValue:N0}";
@@ -149,6 +185,12 @@ namespace osu.Game.Rulesets.Osu.Edit
gridLinesRotationSlider.ContractedLabelText = $"R: {rotation.NewValue:#,0.##}";
gridLinesRotationSlider.ExpandedLabelText = $"Rotation: {rotation.NewValue:#,0.##}";
}, true);
+
+ expandingContainer?.Expanded.BindValueChanged(v =>
+ {
+ gridTypeButtons.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint);
+ gridTypeButtons.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None;
+ }, true);
}
private void nextGridSize()
@@ -171,5 +213,42 @@ namespace osu.Game.Rulesets.Osu.Edit
public void OnReleased(KeyBindingReleaseEvent e)
{
}
+
+ public partial class OutlineTriangle : BufferedContainer
+ {
+ public OutlineTriangle(bool outlineOnly, float size)
+ : base(cachedFrameBuffer: true)
+ {
+ Size = new Vector2(size);
+
+ InternalChildren = new Drawable[]
+ {
+ new EquilateralTriangle { RelativeSizeAxes = Axes.Both },
+ };
+
+ if (outlineOnly)
+ {
+ AddInternal(new EquilateralTriangle
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.Centre,
+ RelativePositionAxes = Axes.Y,
+ Y = 0.48f,
+ Colour = Color4.Black,
+ Size = new Vector2(size - 7),
+ Blending = BlendingParameters.None,
+ });
+ }
+
+ Blending = BlendingParameters.Additive;
+ }
+ }
+ }
+
+ public enum PositionSnapGridType
+ {
+ Square,
+ Triangle,
+ Circle,
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index 51bb74926f..84d5adbc52 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Edit
// we may be entering the screen with a selection already active
updateDistanceSnapGrid();
- updatePositionSnapGrid();
+ OsuGridToolboxGroup.GridType.BindValueChanged(updatePositionSnapGrid, true);
RightToolbox.AddRange(new EditorToolboxGroup[]
{
@@ -110,18 +110,45 @@ namespace osu.Game.Rulesets.Osu.Edit
);
}
- private void updatePositionSnapGrid()
+ private void updatePositionSnapGrid(ValueChangedEvent obj)
{
if (positionSnapGrid != null)
LayerBelowRuleset.Remove(positionSnapGrid, true);
- var rectangularPositionSnapGrid = new RectangularPositionSnapGrid();
+ switch (obj.NewValue)
+ {
+ case PositionSnapGridType.Square:
+ var rectangularPositionSnapGrid = new RectangularPositionSnapGrid();
- rectangularPositionSnapGrid.StartPosition.BindTo(OsuGridToolboxGroup.StartPosition);
- rectangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.SpacingVector);
- rectangularPositionSnapGrid.GridLineRotation.BindTo(OsuGridToolboxGroup.GridLinesRotation);
+ rectangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.SpacingVector);
+ rectangularPositionSnapGrid.GridLineRotation.BindTo(OsuGridToolboxGroup.GridLinesRotation);
- positionSnapGrid = rectangularPositionSnapGrid;
+ positionSnapGrid = rectangularPositionSnapGrid;
+ break;
+
+ case PositionSnapGridType.Triangle:
+ var triangularPositionSnapGrid = new TriangularPositionSnapGrid();
+
+ triangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.Spacing);
+ triangularPositionSnapGrid.GridLineRotation.BindTo(OsuGridToolboxGroup.GridLinesRotation);
+
+ positionSnapGrid = triangularPositionSnapGrid;
+ break;
+
+ case PositionSnapGridType.Circle:
+ var circularPositionSnapGrid = new CircularPositionSnapGrid();
+
+ circularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.Spacing);
+
+ positionSnapGrid = circularPositionSnapGrid;
+ break;
+
+ default:
+ throw new NotImplementedException($"{OsuGridToolboxGroup.GridType} has an incorrect value.");
+ }
+
+ // Bind the start position to the toolbox sliders.
+ positionSnapGrid.StartPosition.BindTo(OsuGridToolboxGroup.StartPosition);
positionSnapGrid.RelativeSizeAxes = Axes.Both;
LayerBelowRuleset.Add(positionSnapGrid);
diff --git a/osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs
index 2721bc3602..7e66edc2dd 100644
--- a/osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs
+++ b/osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs
@@ -70,6 +70,51 @@ namespace osu.Game.Tests.Visual.Editing
}));
}
+ [TestCaseSource(nameof(test_cases))]
+ public void TestTriangularGrid(Vector2 position, Vector2 spacing, float rotation)
+ {
+ TriangularPositionSnapGrid grid = null;
+
+ AddStep("create grid", () =>
+ {
+ Child = grid = new TriangularPositionSnapGrid
+ {
+ RelativeSizeAxes = Axes.Both,
+ };
+ grid.StartPosition.Value = position;
+ grid.Spacing.Value = spacing.X;
+ grid.GridLineRotation.Value = rotation;
+ });
+
+ AddStep("add snapping cursor", () => Add(new SnappingCursorContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos))
+ }));
+ }
+
+ [TestCaseSource(nameof(test_cases))]
+ public void TestCircularGrid(Vector2 position, Vector2 spacing, float rotation)
+ {
+ CircularPositionSnapGrid grid = null;
+
+ AddStep("create grid", () =>
+ {
+ Child = grid = new CircularPositionSnapGrid
+ {
+ RelativeSizeAxes = Axes.Both,
+ };
+ grid.StartPosition.Value = position;
+ grid.Spacing.Value = spacing.X;
+ });
+
+ AddStep("add snapping cursor", () => Add(new SnappingCursorContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos))
+ }));
+ }
+
private partial class SnappingCursorContainer : CompositeDrawable
{
public Func GetSnapPosition;
diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs
new file mode 100644
index 0000000000..403a270359
--- /dev/null
+++ b/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs
@@ -0,0 +1,101 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Utils;
+using osuTK;
+
+namespace osu.Game.Screens.Edit.Compose.Components
+{
+ public partial class CircularPositionSnapGrid : PositionSnapGrid
+ {
+ ///
+ /// The spacing between grid lines of this .
+ ///
+ public BindableFloat Spacing { get; } = new BindableFloat(1f)
+ {
+ MinValue = 0f,
+ };
+
+ public CircularPositionSnapGrid()
+ {
+ Spacing.BindValueChanged(_ => GridCache.Invalidate());
+ }
+
+ protected override void CreateContent()
+ {
+ var drawSize = DrawSize;
+
+ // Calculate the maximum distance from the origin to the edge of the grid.
+ float maxDist = MathF.Max(
+ MathF.Max(StartPosition.Value.Length, (StartPosition.Value - drawSize).Length),
+ MathF.Max((StartPosition.Value - new Vector2(drawSize.X, 0)).Length, (StartPosition.Value - new Vector2(0, drawSize.Y)).Length)
+ );
+
+ generateCircles((int)(maxDist / Spacing.Value) + 1);
+
+ GenerateOutline(drawSize);
+ }
+
+ private void generateCircles(int count)
+ {
+ // Make lines the same width independent of display resolution.
+ float lineWidth = 2 * DrawWidth / ScreenSpaceDrawQuad.Width;
+
+ List generatedCircles = new List();
+
+ for (int i = 0; i < count; i++)
+ {
+ // Add a minimum diameter so the center circle is clearly visible.
+ float diameter = MathF.Max(lineWidth * 1.5f, i * Spacing.Value * 2);
+
+ var gridCircle = new CircularContainer
+ {
+ BorderColour = Colour4.White,
+ BorderThickness = lineWidth,
+ Alpha = 0.2f,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.None,
+ Width = diameter,
+ Height = diameter,
+ Position = StartPosition.Value,
+ Masking = true,
+ Child = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ AlwaysPresent = true,
+ Alpha = 0f,
+ }
+ };
+
+ generatedCircles.Add(gridCircle);
+ }
+
+ if (generatedCircles.Count == 0)
+ return;
+
+ generatedCircles.First().Alpha = 0.8f;
+
+ AddRangeInternal(generatedCircles);
+ }
+
+ public override Vector2 GetSnappedPosition(Vector2 original)
+ {
+ Vector2 relativeToStart = original - StartPosition.Value;
+
+ if (relativeToStart.LengthSquared < Precision.FLOAT_EPSILON)
+ return StartPosition.Value;
+
+ float length = relativeToStart.Length;
+ float wantedLength = MathF.Round(length / Spacing.Value) * Spacing.Value;
+
+ return StartPosition.Value + Vector2.Multiply(relativeToStart, wantedLength / length);
+ }
+ }
+}
diff --git a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs
new file mode 100644
index 0000000000..93d2c6a74a
--- /dev/null
+++ b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs
@@ -0,0 +1,89 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Bindables;
+using osu.Game.Utils;
+using osuTK;
+
+namespace osu.Game.Screens.Edit.Compose.Components
+{
+ public partial class TriangularPositionSnapGrid : LinedPositionSnapGrid
+ {
+ ///
+ /// The spacing between grid lines of this .
+ ///
+ public BindableFloat Spacing { get; } = new BindableFloat(1f)
+ {
+ MinValue = 0f,
+ };
+
+ ///
+ /// The rotation in degrees of the grid lines of this .
+ ///
+ public BindableFloat GridLineRotation { get; } = new BindableFloat();
+
+ public TriangularPositionSnapGrid()
+ {
+ Spacing.BindValueChanged(_ => GridCache.Invalidate());
+ GridLineRotation.BindValueChanged(_ => GridCache.Invalidate());
+ }
+
+ private const float sqrt3 = 1.73205080757f;
+ private const float sqrt3_over2 = 0.86602540378f;
+ private const float one_over_sqrt3 = 0.57735026919f;
+
+ protected override void CreateContent()
+ {
+ var drawSize = DrawSize;
+ float stepSpacing = Spacing.Value * sqrt3_over2;
+ var step1 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation.Value - 30);
+ var step2 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation.Value - 90);
+ var step3 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation.Value - 150);
+
+ GenerateGridLines(step1, drawSize);
+ GenerateGridLines(-step1, drawSize);
+
+ GenerateGridLines(step2, drawSize);
+ GenerateGridLines(-step2, drawSize);
+
+ GenerateGridLines(step3, drawSize);
+ GenerateGridLines(-step3, drawSize);
+
+ GenerateOutline(drawSize);
+ }
+
+ public override Vector2 GetSnappedPosition(Vector2 original)
+ {
+ Vector2 relativeToStart = GeometryUtils.RotateVector(original - StartPosition.Value, GridLineRotation.Value);
+ Vector2 hex = pixelToHex(relativeToStart);
+
+ return StartPosition.Value + GeometryUtils.RotateVector(hexToPixel(hex), -GridLineRotation.Value);
+ }
+
+ private Vector2 pixelToHex(Vector2 pixel)
+ {
+ float x = pixel.X / Spacing.Value;
+ float y = pixel.Y / Spacing.Value;
+ // Algorithm from Charles Chambers
+ // with modifications and comments by Chris Cox 2023
+ //
+ float t = sqrt3 * y + 1; // scaled y, plus phase
+ float temp1 = MathF.Floor(t + x); // (y+x) diagonal, this calc needs floor
+ float temp2 = t - x; // (y-x) diagonal, no floor needed
+ float temp3 = 2 * x + 1; // scaled horizontal, no floor needed, needs +1 to get correct phase
+ float qf = (temp1 + temp3) / 3.0f; // pseudo x with fraction
+ float rf = (temp1 + temp2) / 3.0f; // pseudo y with fraction
+ float q = MathF.Floor(qf); // pseudo x, quantized and thus requires floor
+ float r = MathF.Floor(rf); // pseudo y, quantized and thus requires floor
+ return new Vector2(q, r);
+ }
+
+ private Vector2 hexToPixel(Vector2 hex)
+ {
+ // Taken from
+ // with modifications for the different definition of size.
+ return new Vector2(Spacing.Value * (hex.X - hex.Y / 2), Spacing.Value * one_over_sqrt3 * 1.5f * hex.Y);
+ }
+ }
+}
From e803b0215f146e449f12a77fae1f09db4fdae30f Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sat, 30 Dec 2023 01:38:08 +0100
Subject: [PATCH 0040/1450] flip along grid axis
---
.../Edit/OsuSelectionHandler.cs | 19 ++++++++++---------
osu.Game/Utils/GeometryUtils.cs | 11 +++++++++++
2 files changed, 21 insertions(+), 9 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
index cea2adc6e2..021c735ebd 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
@@ -15,7 +16,6 @@ using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Rulesets.Osu.UI;
using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Utils;
using osuTK;
@@ -28,6 +28,9 @@ namespace osu.Game.Rulesets.Osu.Edit
[Resolved(CanBeNull = true)]
private IDistanceSnapProvider? snapProvider { get; set; }
+ [Resolved]
+ private OsuGridToolboxGroup gridToolbox { get; set; } = null!;
+
///
/// During a transform, the initial path types of a single selected slider are stored so they
/// can be maintained throughout the operation.
@@ -104,13 +107,16 @@ namespace osu.Game.Rulesets.Osu.Edit
{
var hitObjects = selectedMovableObjects;
- var flipQuad = flipOverOrigin ? new Quad(0, 0, OsuPlayfield.BASE_SIZE.X, OsuPlayfield.BASE_SIZE.Y) : GeometryUtils.GetSurroundingQuad(hitObjects);
+ var flipQuad = flipOverOrigin ? new Quad(gridToolbox.StartPositionX.Value, gridToolbox.StartPositionY.Value, 0, 0) : GeometryUtils.GetSurroundingQuad(hitObjects);
+ var flipAxis = flipOverOrigin ? new Vector2(MathF.Cos(MathHelper.DegreesToRadians(gridToolbox.GridLinesRotation.Value)), MathF.Sin(MathHelper.DegreesToRadians(gridToolbox.GridLinesRotation.Value))) : Vector2.UnitX;
+ flipAxis = direction == Direction.Vertical ? flipAxis.PerpendicularLeft : flipAxis;
+ var controlPointFlipQuad = new Quad();
bool didFlip = false;
foreach (var h in hitObjects)
{
- var flippedPosition = GeometryUtils.GetFlippedPosition(direction, flipQuad, h.Position);
+ var flippedPosition = GeometryUtils.GetFlippedPosition(flipAxis, flipQuad, h.Position);
if (!Precision.AlmostEquals(flippedPosition, h.Position))
{
@@ -123,12 +129,7 @@ namespace osu.Game.Rulesets.Osu.Edit
didFlip = true;
foreach (var cp in slider.Path.ControlPoints)
- {
- cp.Position = new Vector2(
- (direction == Direction.Horizontal ? -1 : 1) * cp.Position.X,
- (direction == Direction.Vertical ? -1 : 1) * cp.Position.Y
- );
- }
+ cp.Position = GeometryUtils.GetFlippedPosition(flipAxis, controlPointFlipQuad, cp.Position);
}
}
diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs
index e0d217dd48..aacf9b91f9 100644
--- a/osu.Game/Utils/GeometryUtils.cs
+++ b/osu.Game/Utils/GeometryUtils.cs
@@ -70,6 +70,17 @@ namespace osu.Game.Utils
return position;
}
+ ///
+ /// Given a flip axis vector, a surrounding quad for all selected objects, and a position,
+ /// will return the flipped position in screen space coordinates.
+ ///
+ public static Vector2 GetFlippedPosition(Vector2 axis, Quad quad, Vector2 position)
+ {
+ var centre = quad.Centre;
+
+ return position - 2 * Vector2.Dot(position - centre, axis) * axis;
+ }
+
///
/// Given a scale vector, a surrounding quad for all selected objects, and a position,
/// will return the scaled position in screen space coordinates.
From 078fe5a78ca5b0744d12c6f4fa8f242a0730986c Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sat, 30 Dec 2023 01:53:19 +0100
Subject: [PATCH 0041/1450] Rotate popover rotates around grid center
---
osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 2 +-
osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs | 8 +++++---
osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs | 4 +++-
3 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index 84d5adbc52..02e98d75a7 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Edit
RightToolbox.AddRange(new EditorToolboxGroup[]
{
OsuGridToolboxGroup,
- new TransformToolboxGroup { RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, },
+ new TransformToolboxGroup { RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, GridToolbox = OsuGridToolboxGroup },
FreehandlSliderToolboxGroup
}
);
diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs
index f09d6b78e6..02a8ff5872 100644
--- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs
+++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs
@@ -8,7 +8,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.UserInterfaceV2;
-using osu.Game.Rulesets.Osu.UI;
using osu.Game.Screens.Edit.Components.RadioButtons;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
@@ -19,14 +18,17 @@ namespace osu.Game.Rulesets.Osu.Edit
{
private readonly SelectionRotationHandler rotationHandler;
+ private readonly OsuGridToolboxGroup gridToolbox;
+
private readonly Bindable rotationInfo = new Bindable(new PreciseRotationInfo(0, RotationOrigin.PlayfieldCentre));
private SliderWithTextBoxInput angleInput = null!;
private EditorRadioButtonCollection rotationOrigin = null!;
- public PreciseRotationPopover(SelectionRotationHandler rotationHandler)
+ public PreciseRotationPopover(SelectionRotationHandler rotationHandler, OsuGridToolboxGroup gridToolbox)
{
this.rotationHandler = rotationHandler;
+ this.gridToolbox = gridToolbox;
AllowableAnchors = new[] { Anchor.CentreLeft, Anchor.CentreRight };
}
@@ -78,7 +80,7 @@ namespace osu.Game.Rulesets.Osu.Edit
rotationInfo.BindValueChanged(rotation =>
{
- rotationHandler.Update(rotation.NewValue.Degrees, rotation.NewValue.Origin == RotationOrigin.PlayfieldCentre ? OsuPlayfield.BASE_SIZE / 2 : null);
+ rotationHandler.Update(rotation.NewValue.Degrees, rotation.NewValue.Origin == RotationOrigin.PlayfieldCentre ? gridToolbox.StartPosition.Value : null);
});
}
diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs
index 3da9f5b69b..f8df45f545 100644
--- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs
+++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs
@@ -24,6 +24,8 @@ namespace osu.Game.Rulesets.Osu.Edit
public SelectionRotationHandler RotationHandler { get; init; } = null!;
+ public OsuGridToolboxGroup GridToolbox { get; init; } = null!;
+
public TransformToolboxGroup()
: base("transform")
{
@@ -41,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Edit
{
rotateButton = new EditorToolButton("Rotate",
() => new SpriteIcon { Icon = FontAwesome.Solid.Undo },
- () => new PreciseRotationPopover(RotationHandler)),
+ () => new PreciseRotationPopover(RotationHandler, GridToolbox)),
// TODO: scale
}
};
From 09852bc46b6b0b7a78914e19ba3414efab60afdd Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sun, 31 Dec 2023 21:23:13 +0100
Subject: [PATCH 0042/1450] fix horizontal vs vertical flips being mixed up
when rotation angle is too big
---
osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
index 021c735ebd..1cb206c2f8 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
@@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
@@ -107,9 +106,13 @@ namespace osu.Game.Rulesets.Osu.Edit
{
var hitObjects = selectedMovableObjects;
+ // If we're flipping over the origin, we take the grid origin position from the grid toolbox.
var flipQuad = flipOverOrigin ? new Quad(gridToolbox.StartPositionX.Value, gridToolbox.StartPositionY.Value, 0, 0) : GeometryUtils.GetSurroundingQuad(hitObjects);
- var flipAxis = flipOverOrigin ? new Vector2(MathF.Cos(MathHelper.DegreesToRadians(gridToolbox.GridLinesRotation.Value)), MathF.Sin(MathHelper.DegreesToRadians(gridToolbox.GridLinesRotation.Value))) : Vector2.UnitX;
+ // If we're flipping over the origin, we take the grid rotation from the grid toolbox.
+ // We want to normalize the rotation angle to -45 to 45 degrees, so horizontal vs vertical flip is not mixed up by the rotation and it stays intuitive to use.
+ var flipAxis = flipOverOrigin ? GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 405) % 90 - 45)) : Vector2.UnitX;
flipAxis = direction == Direction.Vertical ? flipAxis.PerpendicularLeft : flipAxis;
+
var controlPointFlipQuad = new Quad();
bool didFlip = false;
From b74c3b1c5cbdd1d4bfc2b108bc9bd0a9b7b68e7f Mon Sep 17 00:00:00 2001
From: Nitrous
Date: Wed, 10 Jan 2024 15:19:38 +0800
Subject: [PATCH 0043/1450] Make all hit objects before the start time marked
as hit.
---
osu.Game/Screens/Edit/Editor.cs | 2 +-
.../Screens/Edit/GameplayTest/EditorPlayer.cs | 37 ++++++++++++++++++-
.../Edit/GameplayTest/EditorPlayerLoader.cs | 5 ++-
3 files changed, 40 insertions(+), 4 deletions(-)
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index c1f6c02301..224823de70 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -452,7 +452,7 @@ namespace osu.Game.Screens.Edit
pushEditorPlayer();
}
- void pushEditorPlayer() => this.Push(new EditorPlayerLoader(this));
+ void pushEditorPlayer() => this.Push(new EditorPlayerLoader(this, playableBeatmap));
}
///
diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs
index 7dff05667d..47abcff476 100644
--- a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs
+++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs
@@ -1,11 +1,17 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
+using osu.Game.Online.Spectator;
using osu.Game.Overlays;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Replays;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Users;
@@ -15,17 +21,19 @@ namespace osu.Game.Screens.Edit.GameplayTest
{
private readonly Editor editor;
private readonly EditorState editorState;
+ private readonly IBeatmap playableBeatmap;
protected override UserActivity InitialActivity => new UserActivity.TestingBeatmap(Beatmap.Value.BeatmapInfo, Ruleset.Value);
[Resolved]
private MusicController musicController { get; set; } = null!;
- public EditorPlayer(Editor editor)
+ public EditorPlayer(Editor editor, IBeatmap playableBeatmap)
: base(new PlayerConfiguration { ShowResults = false })
{
this.editor = editor;
editorState = editor.GetState();
+ this.playableBeatmap = playableBeatmap;
}
protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart)
@@ -43,6 +51,22 @@ namespace osu.Game.Screens.Edit.GameplayTest
protected override void LoadComplete()
{
base.LoadComplete();
+
+ var frame = new ReplayFrame { Header = new FrameHeader(new ScoreInfo(), new ScoreProcessorStatistics()) };
+
+ foreach (var hitObject in enumerateHitObjects(playableBeatmap.HitObjects.Where(h => h.StartTime < editorState.Time)))
+ {
+ var judgement = hitObject.CreateJudgement();
+
+ if (!frame.Header.Statistics.ContainsKey(judgement.MaxResult))
+ frame.Header.Statistics.Add(judgement.MaxResult, 0);
+
+ frame.Header.Statistics[judgement.MaxResult]++;
+ }
+
+ HealthProcessor.ResetFromReplayFrame(frame);
+ ScoreProcessor.ResetFromReplayFrame(frame);
+
ScoreProcessor.HasCompleted.BindValueChanged(completed =>
{
if (completed.NewValue)
@@ -54,6 +78,17 @@ namespace osu.Game.Screens.Edit.GameplayTest
}, RESULTS_DISPLAY_DELAY);
}
});
+
+ static IEnumerable enumerateHitObjects(IEnumerable hitObjects)
+ {
+ foreach (var hitObject in hitObjects)
+ {
+ foreach (var nested in hitObject.NestedHitObjects)
+ yield return nested;
+
+ yield return hitObject;
+ }
+ }
}
protected override void PrepareReplay()
diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs
index bb151e4a45..c62b8cafb8 100644
--- a/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs
+++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs
@@ -4,6 +4,7 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Screens;
+using osu.Game.Beatmaps;
using osu.Game.Screens.Menu;
using osu.Game.Screens.Play;
@@ -14,8 +15,8 @@ namespace osu.Game.Screens.Edit.GameplayTest
[Resolved]
private OsuLogo osuLogo { get; set; } = null!;
- public EditorPlayerLoader(Editor editor)
- : base(() => new EditorPlayer(editor))
+ public EditorPlayerLoader(Editor editor, IBeatmap playableBeatmap)
+ : base(() => new EditorPlayer(editor, playableBeatmap))
{
}
From 72e302dfac77c3b8a980e7f8026ace08eb95d191 Mon Sep 17 00:00:00 2001
From: Nitrous
Date: Wed, 10 Jan 2024 15:27:41 +0800
Subject: [PATCH 0044/1450] Enumerate nested hit objects
---
osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs
index 47abcff476..e2b2b067e0 100644
--- a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs
+++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs
@@ -83,7 +83,7 @@ namespace osu.Game.Screens.Edit.GameplayTest
{
foreach (var hitObject in hitObjects)
{
- foreach (var nested in hitObject.NestedHitObjects)
+ foreach (var nested in enumerateHitObjects(hitObject.NestedHitObjects))
yield return nested;
yield return hitObject;
From aa83ac1896f0c54e15d967157f8ddd113a520342 Mon Sep 17 00:00:00 2001
From: Nitrous
Date: Wed, 10 Jan 2024 15:53:54 +0800
Subject: [PATCH 0045/1450] add test case
---
.../Editing/TestSceneEditorTestGameplay.cs | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs
index bbd7123f20..ccc17dc3f0 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs
@@ -126,6 +126,24 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("sample playback re-enabled", () => !Editor.SamplePlaybackDisabled.Value);
}
+ [Test]
+ public void TestGameplayTestAtEndOfBeatmap()
+ {
+ AddStep("seek to last 2 seconds", () => EditorClock.Seek(importedBeatmapSet.MaxLength - 2000));
+ AddStep("click test gameplay button", () =>
+ {
+ var button = Editor.ChildrenOfType().Single();
+
+ InputManager.MoveMouseTo(button);
+ InputManager.Click(MouseButton.Left);
+ });
+
+ AddUntilStep("player pushed", () => Stack.CurrentScreen is EditorPlayer);
+
+ AddWaitStep("wait some", 5);
+ AddAssert("current screen is editor", () => Stack.CurrentScreen is Editor);
+ }
+
[Test]
public void TestCancelGameplayTestWithUnsavedChanges()
{
From 6cd255f549f6a7499bc2b87f90ce9fef2180e5ac Mon Sep 17 00:00:00 2001
From: Nitrous
Date: Thu, 11 Jan 2024 11:36:58 +0800
Subject: [PATCH 0046/1450] `Contains` + `Add` to `TryAdd`
---
osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs
index e2b2b067e0..e1519f9a09 100644
--- a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs
+++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs
@@ -57,10 +57,7 @@ namespace osu.Game.Screens.Edit.GameplayTest
foreach (var hitObject in enumerateHitObjects(playableBeatmap.HitObjects.Where(h => h.StartTime < editorState.Time)))
{
var judgement = hitObject.CreateJudgement();
-
- if (!frame.Header.Statistics.ContainsKey(judgement.MaxResult))
- frame.Header.Statistics.Add(judgement.MaxResult, 0);
-
+ frame.Header.Statistics.TryAdd(judgement.MaxResult, 0);
frame.Header.Statistics[judgement.MaxResult]++;
}
From b5dbf24d2787569b4f813bf5631507492cdb0269 Mon Sep 17 00:00:00 2001
From: Sheepposu
Date: Thu, 1 Feb 2024 10:19:09 -0500
Subject: [PATCH 0047/1450] early replay analysis settings version
committing early version for others in the discussion to do their own testing with it
---
osu.Game.Rulesets.Osu/OsuRuleset.cs | 3 +
.../UI/HitMarkerContainer.cs | 134 ++++++++++++++++++
.../UI/OsuAnalysisSettings.cs | 81 +++++++++++
osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 4 +
.../PlayerSettingsOverlayStrings.cs | 15 ++
osu.Game/Rulesets/Ruleset.cs | 3 +
osu.Game/Screens/Play/Player.cs | 2 +-
.../Play/PlayerSettings/AnalysisSettings.cs | 18 +++
osu.Game/Screens/Play/ReplayPlayer.cs | 5 +
9 files changed, 264 insertions(+), 1 deletion(-)
create mode 100644 osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs
create mode 100644 osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
create mode 100644 osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index 6752712be1..358553ac59 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -38,6 +38,7 @@ using osu.Game.Rulesets.Scoring.Legacy;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
using osu.Game.Screens.Edit.Setup;
+using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Screens.Ranking.Statistics;
using osu.Game.Skinning;
@@ -356,5 +357,7 @@ namespace osu.Game.Rulesets.Osu
return adjustedDifficulty;
}
+
+ public override AnalysisSettings? CreateAnalysisSettings(DrawableRuleset drawableRuleset) => new OsuAnalysisSettings(drawableRuleset);
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs b/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs
new file mode 100644
index 0000000000..a9fc42e596
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs
@@ -0,0 +1,134 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input;
+using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.UI
+{
+ public partial class HitMarkerContainer : Container, IRequireHighFrequencyMousePosition, IKeyBindingHandler
+ {
+ private Vector2 lastMousePosition;
+
+ public Bindable HitMarkerEnabled = new BindableBool();
+ public Bindable AimMarkersEnabled = new BindableBool();
+
+ public override bool ReceivePositionalInputAt(Vector2 _) => true;
+
+ public bool OnPressed(KeyBindingPressEvent e)
+ {
+ if (HitMarkerEnabled.Value && (e.Action == OsuAction.LeftButton || e.Action == OsuAction.RightButton))
+ {
+ AddMarker(e.Action);
+ }
+
+ return false;
+ }
+
+ public void OnReleased(KeyBindingReleaseEvent e) { }
+
+ protected override bool OnMouseMove(MouseMoveEvent e)
+ {
+ lastMousePosition = e.MousePosition;
+
+ if (AimMarkersEnabled.Value)
+ {
+ AddMarker(null);
+ }
+
+ return base.OnMouseMove(e);
+ }
+
+ private void AddMarker(OsuAction? action)
+ {
+ Add(new HitMarkerDrawable(action) { Position = lastMousePosition });
+ }
+
+ private partial class HitMarkerDrawable : CompositeDrawable
+ {
+ private const double lifetime_duration = 1000;
+ private const double fade_out_time = 400;
+
+ public override bool RemoveWhenNotAlive => true;
+
+ public HitMarkerDrawable(OsuAction? action)
+ {
+ var colour = Colour4.Gray.Opacity(0.5F);
+ var length = 8;
+ var depth = float.MaxValue;
+ switch (action)
+ {
+ case OsuAction.LeftButton:
+ colour = Colour4.Orange;
+ length = 20;
+ depth = float.MinValue;
+ break;
+ case OsuAction.RightButton:
+ colour = Colour4.LightGreen;
+ length = 20;
+ depth = float.MinValue;
+ break;
+ }
+
+ this.Depth = depth;
+
+ InternalChildren = new Drawable[]
+ {
+ new Box
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(3, length),
+ Rotation = 45,
+ Colour = Colour4.Black.Opacity(0.5F)
+ },
+ new Box
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(3, length),
+ Rotation = 135,
+ Colour = Colour4.Black.Opacity(0.5F)
+ },
+ new Box
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(1, length),
+ Rotation = 45,
+ Colour = colour
+ },
+ new Box
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(1, length),
+ Rotation = 135,
+ Colour = colour
+ }
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ LifetimeStart = Time.Current;
+ LifetimeEnd = LifetimeStart + lifetime_duration;
+
+ Scheduler.AddDelayed(() =>
+ {
+ this.FadeOut(fade_out_time);
+ }, lifetime_duration - fade_out_time);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
new file mode 100644
index 0000000000..4694c1a560
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
@@ -0,0 +1,81 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Logging;
+using osu.Game.Localisation;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Rulesets.UI;
+using osu.Game.Screens.Play.PlayerSettings;
+
+namespace osu.Game.Rulesets.Osu.UI
+{
+ public partial class OsuAnalysisSettings : AnalysisSettings
+ {
+ private static readonly Logger logger = Logger.GetLogger("osu-analysis-settings");
+
+ protected new DrawableOsuRuleset drawableRuleset => (DrawableOsuRuleset)base.drawableRuleset;
+
+ private readonly PlayerCheckbox hitMarkerToggle;
+ private readonly PlayerCheckbox aimMarkerToggle;
+ private readonly PlayerCheckbox hideCursorToggle;
+ private readonly PlayerCheckbox? hiddenToggle;
+
+ public OsuAnalysisSettings(DrawableRuleset drawableRuleset)
+ : base(drawableRuleset)
+ {
+ Children = new Drawable[]
+ {
+ hitMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HitMarkers },
+ aimMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.AimMarkers },
+ hideCursorToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HideCursor }
+ };
+
+ // hidden stuff is just here for testing at the moment; to create the mod disabling functionality
+
+ foreach (var mod in drawableRuleset.Mods)
+ {
+ if (mod is OsuModHidden)
+ {
+ logger.Add("Hidden is enabled", LogLevel.Debug);
+ Add(hiddenToggle = new PlayerCheckbox { LabelText = "Disable hidden" });
+ break;
+ }
+ }
+ }
+
+ protected override void LoadComplete()
+ {
+ drawableRuleset.Playfield.MarkersContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current);
+ drawableRuleset.Playfield.MarkersContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current);
+ hideCursorToggle.Current.BindValueChanged(onCursorToggle);
+ hiddenToggle?.Current.BindValueChanged(onHiddenToggle);
+ }
+
+ private void onCursorToggle(ValueChangedEvent hide)
+ {
+ // this only hides half the cursor
+ if (hide.NewValue)
+ {
+ drawableRuleset.Playfield.Cursor.Hide();
+ } else
+ {
+ drawableRuleset.Playfield.Cursor.Show();
+ }
+ }
+
+ private void onHiddenToggle(ValueChangedEvent off)
+ {
+ if (off.NewValue)
+ {
+ logger.Add("Hidden off", LogLevel.Debug);
+ } else
+ {
+ logger.Add("Hidden on", LogLevel.Debug);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index 411a02c5af..d7e1732175 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -36,6 +36,9 @@ namespace osu.Game.Rulesets.Osu.UI
private readonly JudgementPooler judgementPooler;
public SmokeContainer Smoke { get; }
+
+ public HitMarkerContainer MarkersContainer { get; }
+
public FollowPointRenderer FollowPoints { get; }
public static readonly Vector2 BASE_SIZE = new Vector2(512, 384);
@@ -59,6 +62,7 @@ namespace osu.Game.Rulesets.Osu.UI
HitObjectContainer,
judgementAboveHitObjectLayer = new Container { RelativeSizeAxes = Axes.Both },
approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both },
+ MarkersContainer = new HitMarkerContainer { RelativeSizeAxes = Axes.Both }
};
HitPolicy = new StartTimeOrderedHitPolicy();
diff --git a/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs b/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs
index 60874da561..f829fb4ac1 100644
--- a/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs
+++ b/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs
@@ -19,6 +19,21 @@ namespace osu.Game.Localisation
///
public static LocalisableString StepForward => new TranslatableString(getKey(@"step_forward_frame"), @"Step forward one frame");
+ ///
+ /// "Hit markers"
+ ///
+ public static LocalisableString HitMarkers => new TranslatableString(getKey(@"hit_markers"), @"Hit markers");
+
+ ///
+ /// "Aim markers"
+ ///
+ public static LocalisableString AimMarkers => new TranslatableString(getKey(@"aim_markers"), @"Aim markers");
+
+ ///
+ /// "Hide cursor"
+ ///
+ public static LocalisableString HideCursor => new TranslatableString(getKey(@"hide_cursor"), @"Hide cursor");
+
///
/// "Seek backward {0} seconds"
///
diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs
index 37a35fd3ae..5fbb23f094 100644
--- a/osu.Game/Rulesets/Ruleset.cs
+++ b/osu.Game/Rulesets/Ruleset.cs
@@ -27,6 +27,7 @@ using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
using osu.Game.Screens.Edit.Setup;
+using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Screens.Ranking.Statistics;
using osu.Game.Skinning;
using osu.Game.Users;
@@ -402,5 +403,7 @@ namespace osu.Game.Rulesets
/// Can be overridden to alter the difficulty section to the editor beatmap setup screen.
///
public virtual DifficultySection? CreateEditorDifficultySection() => null;
+
+ public virtual AnalysisSettings? CreateAnalysisSettings(DrawableRuleset drawableRuleset) => null;
}
}
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index ad1f9ec897..0fa7143693 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -115,7 +115,7 @@ namespace osu.Game.Screens.Play
public GameplayState GameplayState { get; private set; }
- private Ruleset ruleset;
+ protected Ruleset ruleset;
public BreakOverlay BreakOverlay;
diff --git a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs
new file mode 100644
index 0000000000..122ca29142
--- /dev/null
+++ b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs
@@ -0,0 +1,18 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.UI;
+
+namespace osu.Game.Screens.Play.PlayerSettings
+{
+ public partial class AnalysisSettings : PlayerSettingsGroup
+ {
+ protected DrawableRuleset drawableRuleset;
+
+ public AnalysisSettings(DrawableRuleset drawableRuleset)
+ : base("Analysis Settings")
+ {
+ this.drawableRuleset = drawableRuleset;
+ }
+ }
+}
\ No newline at end of file
diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs
index 3c5b85662a..0d877785e7 100644
--- a/osu.Game/Screens/Play/ReplayPlayer.cs
+++ b/osu.Game/Screens/Play/ReplayPlayer.cs
@@ -71,6 +71,11 @@ namespace osu.Game.Screens.Play
playbackSettings.UserPlaybackRate.BindTo(master.UserPlaybackRate);
HUDOverlay.PlayerSettingsOverlay.AddAtStart(playbackSettings);
+
+ var analysisSettings = ruleset.CreateAnalysisSettings(DrawableRuleset);
+ if (analysisSettings != null) {
+ HUDOverlay.PlayerSettingsOverlay.AddAtStart(analysisSettings);
+ }
}
protected override void PrepareReplay()
From f807a3fd971527ba62938b674946989c44a4966c Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Thu, 1 Feb 2024 16:56:57 +0100
Subject: [PATCH 0048/1450] 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 236f029dad22722ebf73c02d40dc19b312b16642 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Thu, 1 Feb 2024 16:56:57 +0100
Subject: [PATCH 0049/1450] 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 576d6ff7990a7bbb4842cc943c8842c3ed562a3b Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Thu, 1 Feb 2024 17:07:03 +0100
Subject: [PATCH 0050/1450] 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 b4b5cdfcf2d84527ec4d5cb9e504152be59b7e19 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Thu, 1 Feb 2024 17:07:03 +0100
Subject: [PATCH 0051/1450] 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 0052/1450] include slider count in accuracy pp if slider head
accuracy is in use
---
osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 ++
1 file changed, 2 insertions(+)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index b31f4ff519..7f4c5f6c46 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -192,6 +192,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window.
double betterAccuracyPercentage;
int amountHitObjectsWithAccuracy = attributes.HitCircleCount;
+ if (!score.Mods.Any(h => h is OsuModClassic cl && cl.NoSliderHeadAccuracy.Value))
+ amountHitObjectsWithAccuracy += attributes.SliderCount;
if (amountHitObjectsWithAccuracy > 0)
betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countOk * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6);
From 288eed53df439c594ffab544d3b83d9b2ea9d343 Mon Sep 17 00:00:00 2001
From: Sheepposu
Date: Sat, 10 Feb 2024 02:02:26 -0500
Subject: [PATCH 0053/1450] new features + improvements
Hit & aim markers are skinnable. Hidden can be toggled off. Aim line with skinnable color was added. The fadeout time is based on the approach rate. Cursor hide fixed.
---
osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 77 ++++++-
osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 12 +-
osu.Game.Rulesets.Osu/OsuSkinComponents.cs | 3 +
.../Skinning/Default/DefaultHitMarker.cs | 71 +++++++
.../Skinning/Default/HitMarker.cs | 33 +++
.../Legacy/OsuLegacySkinTransformer.cs | 19 ++
.../Skinning/OsuSkinColour.cs | 1 +
.../UI/HitMarkerContainer.cs | 188 ++++++++++++------
.../UI/OsuAnalysisSettings.cs | 68 +++++--
osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 +-
.../PlayerSettingsOverlayStrings.cs | 5 +
.../Rulesets/Mods/IToggleableVisibility.cs | 11 +
12 files changed, 393 insertions(+), 97 deletions(-)
create mode 100644 osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs
create mode 100644 osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs
create mode 100644 osu.Game/Rulesets/Mods/IToggleableVisibility.cs
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
index 6dc0d5d522..a69bed6fb2 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
@@ -2,9 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Transforms;
using osu.Framework.Bindables;
using osu.Framework.Localisation;
using osu.Game.Configuration;
@@ -12,13 +14,14 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Skinning;
namespace osu.Game.Rulesets.Osu.Mods
{
- public class OsuModHidden : ModHidden, IHidesApproachCircles
+ public class OsuModHidden : ModHidden, IHidesApproachCircles, IToggleableVisibility
{
[SettingSource("Only fade approach circles", "The main object body will not fade when enabled.")]
public Bindable OnlyFadeApproachCircles { get; } = new BindableBool();
@@ -28,24 +31,41 @@ namespace osu.Game.Rulesets.Osu.Mods
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModDepth) };
+ private bool toggledOff = false;
+ private IBeatmap? appliedBeatmap;
+
public const double FADE_IN_DURATION_MULTIPLIER = 0.4;
public const double FADE_OUT_DURATION_MULTIPLIER = 0.3;
protected override bool IsFirstAdjustableObject(HitObject hitObject) => !(hitObject is Spinner || hitObject is SpinnerTick);
- public override void ApplyToBeatmap(IBeatmap beatmap)
+ public override void ApplyToBeatmap(IBeatmap? beatmap = null)
{
+ if (beatmap is not null)
+ appliedBeatmap = beatmap;
+ else if (appliedBeatmap is null)
+ return;
+ else
+ beatmap = appliedBeatmap;
+
base.ApplyToBeatmap(beatmap);
foreach (var obj in beatmap.HitObjects.OfType())
applyFadeInAdjustment(obj);
+ }
- static void applyFadeInAdjustment(OsuHitObject osuObject)
- {
- osuObject.TimeFadeIn = osuObject.TimePreempt * FADE_IN_DURATION_MULTIPLIER;
- foreach (var nested in osuObject.NestedHitObjects.OfType())
- applyFadeInAdjustment(nested);
- }
+ private static void applyFadeInAdjustment(OsuHitObject osuObject)
+ {
+ osuObject.TimeFadeIn = osuObject.TimePreempt * FADE_IN_DURATION_MULTIPLIER;
+ foreach (var nested in osuObject.NestedHitObjects.OfType())
+ applyFadeInAdjustment(nested);
+ }
+
+ private static void revertFadeInAdjustment(OsuHitObject osuObject)
+ {
+ osuObject.TimeFadeIn = OsuHitObject.CalculateTimeFadeIn(osuObject.TimePreempt);
+ foreach (var nested in osuObject.NestedHitObjects.OfType())
+ revertFadeInAdjustment(nested);
}
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
@@ -60,7 +80,7 @@ namespace osu.Game.Rulesets.Osu.Mods
private void applyHiddenState(DrawableHitObject drawableObject, bool increaseVisibility)
{
- if (!(drawableObject is DrawableOsuHitObject drawableOsuObject))
+ if (!(drawableObject is DrawableOsuHitObject drawableOsuObject) || toggledOff)
return;
OsuHitObject hitObject = drawableOsuObject.HitObject;
@@ -183,8 +203,11 @@ namespace osu.Game.Rulesets.Osu.Mods
}
}
- private static void hideSpinnerApproachCircle(DrawableSpinner spinner)
+ private void hideSpinnerApproachCircle(DrawableSpinner spinner)
{
+ if (toggledOff)
+ return;
+
var approachCircle = (spinner.Body.Drawable as IHasApproachCircle)?.ApproachCircle;
if (approachCircle == null)
return;
@@ -192,5 +215,39 @@ namespace osu.Game.Rulesets.Osu.Mods
using (spinner.BeginAbsoluteSequence(spinner.HitObject.StartTime - spinner.HitObject.TimePreempt))
approachCircle.Hide();
}
+
+ public void ToggleOffVisibility(Playfield playfield)
+ {
+ if (toggledOff)
+ return;
+
+ toggledOff = true;
+
+ if (appliedBeatmap is not null)
+ foreach (var obj in appliedBeatmap.HitObjects.OfType())
+ revertFadeInAdjustment(obj);
+
+ foreach (var dho in playfield.AllHitObjects)
+ {
+ dho.RefreshStateTransforms();
+ }
+ }
+
+ public void ToggleOnVisibility(Playfield playfield)
+ {
+ if (!toggledOff)
+ return;
+
+ toggledOff = false;
+
+ if (appliedBeatmap is not null)
+ ApplyToBeatmap();
+
+ foreach (var dho in playfield.AllHitObjects)
+ {
+ dho.RefreshStateTransforms();
+ ApplyToDrawableHitObject(dho);
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
index 74631400ca..60305ed953 100644
--- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
@@ -154,17 +154,19 @@ namespace osu.Game.Rulesets.Osu.Objects
});
}
+ // Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR.
+ // This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above.
+ // Note that this doesn't exactly match the AR>10 visuals as they're classically known, but it feels good.
+ // This adjustment is necessary for AR>10, otherwise TimePreempt can become smaller leading to hitcircles not fully fading in.
+ static public double CalculateTimeFadeIn(double timePreempt) => 400 * Math.Min(1, timePreempt / PREEMPT_MIN);
+
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, PREEMPT_MAX, PREEMPT_MID, PREEMPT_MIN);
- // Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR.
- // This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above.
- // Note that this doesn't exactly match the AR>10 visuals as they're classically known, but it feels good.
- // This adjustment is necessary for AR>10, otherwise TimePreempt can become smaller leading to hitcircles not fully fading in.
- TimeFadeIn = 400 * Math.Min(1, TimePreempt / PREEMPT_MIN);
+ TimeFadeIn = CalculateTimeFadeIn(TimePreempt);
Scale = LegacyRulesetExtensions.CalculateScaleFromCircleSize(difficulty.CircleSize, true);
}
diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
index 52fdfea95f..75db18d345 100644
--- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
+++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
@@ -22,5 +22,8 @@ namespace osu.Game.Rulesets.Osu
SpinnerBody,
CursorSmoke,
ApproachCircle,
+ HitMarkerLeft,
+ HitMarkerRight,
+ AimMarker
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs
new file mode 100644
index 0000000000..f4c11e6c8a
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs
@@ -0,0 +1,71 @@
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Skinning.Default
+{
+ public partial class DefaultHitMarker : CompositeDrawable
+ {
+ public DefaultHitMarker(OsuAction? action)
+ {
+ var (colour, length, hasBorder) = getConfig(action);
+
+ if (hasBorder)
+ {
+ InternalChildren = new Drawable[]
+ {
+ new Box
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(3, length),
+ Rotation = 45,
+ Colour = Colour4.Black.Opacity(0.5F)
+ },
+ new Box
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(3, length),
+ Rotation = 135,
+ Colour = Colour4.Black.Opacity(0.5F)
+ }
+ };
+ }
+
+ AddRangeInternal(new Drawable[]
+ {
+ new Box
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(1, length),
+ Rotation = 45,
+ Colour = colour
+ },
+ new Box
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(1, length),
+ Rotation = 135,
+ Colour = colour
+ }
+ });
+ }
+
+ private (Colour4 colour, float length, bool hasBorder) getConfig(OsuAction? action)
+ {
+ switch (action)
+ {
+ case OsuAction.LeftButton:
+ return (Colour4.Orange, 20, true);
+ case OsuAction.RightButton:
+ return (Colour4.LightGreen, 20, true);
+ default:
+ return (Colour4.Gray.Opacity(0.3F), 8, false);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs
new file mode 100644
index 0000000000..d86e3d4f79
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs
@@ -0,0 +1,33 @@
+using osu.Framework.Allocation;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Osu.Skinning.Default
+{
+ public partial class HitMarker : Sprite
+ {
+ private readonly OsuAction? action;
+
+ public HitMarker(OsuAction? action = null)
+ {
+ this.action = action;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin)
+ {
+ switch (action)
+ {
+ case OsuAction.LeftButton:
+ Texture = skin.GetTexture(@"hitmarker-left");
+ break;
+ case OsuAction.RightButton:
+ Texture = skin.GetTexture(@"hitmarker-right");
+ break;
+ default:
+ Texture = skin.GetTexture(@"aimmarker");
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
index d2ebc68c52..84c055fe5e 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
@@ -5,6 +5,7 @@ using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Skinning.Default;
using osu.Game.Skinning;
using osuTK;
@@ -168,6 +169,24 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
return null;
+ case OsuSkinComponents.HitMarkerLeft:
+ if (GetTexture(@"hitmarker-left") != null)
+ return new HitMarker(OsuAction.LeftButton);
+
+ return null;
+
+ case OsuSkinComponents.HitMarkerRight:
+ if (GetTexture(@"hitmarker-right") != null)
+ return new HitMarker(OsuAction.RightButton);
+
+ return null;
+
+ case OsuSkinComponents.AimMarker:
+ if (GetTexture(@"aimmarker") != null)
+ return new HitMarker();
+
+ return null;
+
default:
throw new UnsupportedSkinComponentException(lookup);
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs
index 24f9217a5f..5c864fb6c2 100644
--- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs
@@ -10,5 +10,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
SliderBall,
SpinnerBackground,
StarBreakAdditive,
+ ReplayAimLine,
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs b/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs
index a9fc42e596..01877a9185 100644
--- a/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs
+++ b/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs
@@ -10,111 +10,128 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Skinning;
+using osu.Game.Rulesets.Osu.Skinning.Default;
+using osu.Game.Rulesets.UI;
+using osu.Game.Skinning;
using osuTK;
+using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.UI
{
public partial class HitMarkerContainer : Container, IRequireHighFrequencyMousePosition, IKeyBindingHandler
{
private Vector2 lastMousePosition;
+ private Vector2? lastlastMousePosition;
+ private double? timePreempt;
+ private double TimePreempt
+ {
+ get => timePreempt ?? default_time_preempt;
+ set => timePreempt = value;
+ }
public Bindable HitMarkerEnabled = new BindableBool();
public Bindable AimMarkersEnabled = new BindableBool();
+ public Bindable AimLinesEnabled = new BindableBool();
+
+ private const double default_time_preempt = 1000;
+
+ private readonly HitObjectContainer hitObjectContainer;
public override bool ReceivePositionalInputAt(Vector2 _) => true;
+ public HitMarkerContainer(HitObjectContainer hitObjectContainer)
+ {
+ this.hitObjectContainer = hitObjectContainer;
+ }
+
public bool OnPressed(KeyBindingPressEvent e)
{
if (HitMarkerEnabled.Value && (e.Action == OsuAction.LeftButton || e.Action == OsuAction.RightButton))
{
+ updateTimePreempt();
AddMarker(e.Action);
}
return false;
}
- public void OnReleased(KeyBindingReleaseEvent e) { }
+ public void OnReleased(KeyBindingReleaseEvent e)
+ {
+ }
protected override bool OnMouseMove(MouseMoveEvent e)
{
+ lastlastMousePosition = lastMousePosition;
lastMousePosition = e.MousePosition;
if (AimMarkersEnabled.Value)
{
+ updateTimePreempt();
AddMarker(null);
}
+ if (AimLinesEnabled.Value && lastlastMousePosition != null && lastlastMousePosition != lastMousePosition)
+ {
+ if (!AimMarkersEnabled.Value)
+ updateTimePreempt();
+ Add(new AimLineDrawable((Vector2)lastlastMousePosition, lastMousePosition, TimePreempt));
+ }
+
return base.OnMouseMove(e);
}
private void AddMarker(OsuAction? action)
{
- Add(new HitMarkerDrawable(action) { Position = lastMousePosition });
+ var component = OsuSkinComponents.AimMarker;
+ switch(action)
+ {
+ case OsuAction.LeftButton:
+ component = OsuSkinComponents.HitMarkerLeft;
+ break;
+ case OsuAction.RightButton:
+ component = OsuSkinComponents.HitMarkerRight;
+ break;
+ }
+
+ Add(new HitMarkerDrawable(action, component, TimePreempt)
+ {
+ Position = lastMousePosition,
+ Origin = Anchor.Centre,
+ Depth = action == null ? float.MaxValue : float.MinValue
+ });
}
- private partial class HitMarkerDrawable : CompositeDrawable
+ private void updateTimePreempt()
{
- private const double lifetime_duration = 1000;
- private const double fade_out_time = 400;
+ var hitObject = getHitObject();
+ if (hitObject == null)
+ return;
+
+ TimePreempt = hitObject.TimePreempt;
+ }
+
+ private OsuHitObject? getHitObject()
+ {
+ foreach (var dho in hitObjectContainer.Objects)
+ return (dho as DrawableOsuHitObject)?.HitObject;
+ return null;
+ }
+
+ private partial class HitMarkerDrawable : SkinnableDrawable
+ {
+ private readonly double lifetimeDuration;
+ private readonly double fadeOutTime;
public override bool RemoveWhenNotAlive => true;
- public HitMarkerDrawable(OsuAction? action)
+ public HitMarkerDrawable(OsuAction? action, OsuSkinComponents componenet, double timePreempt)
+ : base(new OsuSkinComponentLookup(componenet), _ => new DefaultHitMarker(action))
{
- var colour = Colour4.Gray.Opacity(0.5F);
- var length = 8;
- var depth = float.MaxValue;
- switch (action)
- {
- case OsuAction.LeftButton:
- colour = Colour4.Orange;
- length = 20;
- depth = float.MinValue;
- break;
- case OsuAction.RightButton:
- colour = Colour4.LightGreen;
- length = 20;
- depth = float.MinValue;
- break;
- }
-
- this.Depth = depth;
-
- InternalChildren = new Drawable[]
- {
- new Box
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Size = new Vector2(3, length),
- Rotation = 45,
- Colour = Colour4.Black.Opacity(0.5F)
- },
- new Box
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Size = new Vector2(3, length),
- Rotation = 135,
- Colour = Colour4.Black.Opacity(0.5F)
- },
- new Box
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Size = new Vector2(1, length),
- Rotation = 45,
- Colour = colour
- },
- new Box
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Size = new Vector2(1, length),
- Rotation = 135,
- Colour = colour
- }
- };
+ fadeOutTime = timePreempt / 2;
+ lifetimeDuration = timePreempt + fadeOutTime;
}
protected override void LoadComplete()
@@ -122,12 +139,57 @@ namespace osu.Game.Rulesets.Osu.UI
base.LoadComplete();
LifetimeStart = Time.Current;
- LifetimeEnd = LifetimeStart + lifetime_duration;
+ LifetimeEnd = LifetimeStart + lifetimeDuration;
Scheduler.AddDelayed(() =>
{
- this.FadeOut(fade_out_time);
- }, lifetime_duration - fade_out_time);
+ this.FadeOut(fadeOutTime);
+ }, lifetimeDuration - fadeOutTime);
+ }
+ }
+
+ private partial class AimLineDrawable : CompositeDrawable
+ {
+ private readonly double lifetimeDuration;
+ private readonly double fadeOutTime;
+
+ public override bool RemoveWhenNotAlive => true;
+
+ public AimLineDrawable(Vector2 fromP, Vector2 toP, double timePreempt)
+ {
+ fadeOutTime = timePreempt / 2;
+ lifetimeDuration = timePreempt + fadeOutTime;
+
+ float distance = Vector2.Distance(fromP, toP);
+ Vector2 direction = (toP - fromP);
+ InternalChild = new Box
+ {
+ Position = fromP + (direction / 2),
+ Size = new Vector2(distance, 1),
+ Rotation = (float)(Math.Atan(direction.Y / direction.X) * (180 / Math.PI)),
+ Origin = Anchor.Centre
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin)
+ {
+ var color = skin.GetConfig(OsuSkinColour.ReplayAimLine)?.Value ?? Color4.White;
+ color.A = 127;
+ InternalChild.Colour = color;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ LifetimeStart = Time.Current;
+ LifetimeEnd = LifetimeStart + lifetimeDuration;
+
+ Scheduler.AddDelayed(() =>
+ {
+ this.FadeOut(fadeOutTime);
+ }, lifetimeDuration - fadeOutTime);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
index 4694c1a560..050675d970 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
@@ -4,7 +4,9 @@
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Framework.Logging;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Input.Events;
+using osu.Game.Graphics.Containers;
using osu.Game.Localisation;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
@@ -15,14 +17,13 @@ namespace osu.Game.Rulesets.Osu.UI
{
public partial class OsuAnalysisSettings : AnalysisSettings
{
- private static readonly Logger logger = Logger.GetLogger("osu-analysis-settings");
-
protected new DrawableOsuRuleset drawableRuleset => (DrawableOsuRuleset)base.drawableRuleset;
private readonly PlayerCheckbox hitMarkerToggle;
private readonly PlayerCheckbox aimMarkerToggle;
private readonly PlayerCheckbox hideCursorToggle;
- private readonly PlayerCheckbox? hiddenToggle;
+ private readonly PlayerCheckbox aimLinesToggle;
+ private readonly FillFlowContainer modTogglesContainer;
public OsuAnalysisSettings(DrawableRuleset drawableRuleset)
: base(drawableRuleset)
@@ -31,18 +32,36 @@ namespace osu.Game.Rulesets.Osu.UI
{
hitMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HitMarkers },
aimMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.AimMarkers },
- hideCursorToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HideCursor }
+ aimLinesToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.AimLines },
+ hideCursorToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HideCursor },
+ new OsuScrollContainer(Direction.Horizontal)
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = ModSwitchSmall.DEFAULT_SIZE,
+ ScrollbarOverlapsContent = false,
+ Child = modTogglesContainer = new FillFlowContainer
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ Direction = FillDirection.Horizontal,
+ RelativeSizeAxes = Axes.Y,
+ AutoSizeAxes = Axes.X
+ }
+ }
};
-
- // hidden stuff is just here for testing at the moment; to create the mod disabling functionality
foreach (var mod in drawableRuleset.Mods)
{
- if (mod is OsuModHidden)
+ if (mod is IToggleableVisibility toggleableMod)
{
- logger.Add("Hidden is enabled", LogLevel.Debug);
- Add(hiddenToggle = new PlayerCheckbox { LabelText = "Disable hidden" });
- break;
+ var modSwitch = new SelectableModSwitchSmall(mod)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Active = { Value = true }
+ };
+ modSwitch.Active.BindValueChanged((v) => onModToggle(toggleableMod, v));
+ modTogglesContainer.Add(modSwitch);
}
}
}
@@ -51,8 +70,8 @@ namespace osu.Game.Rulesets.Osu.UI
{
drawableRuleset.Playfield.MarkersContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current);
drawableRuleset.Playfield.MarkersContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current);
+ drawableRuleset.Playfield.MarkersContainer.AimLinesEnabled.BindTo(aimLinesToggle.Current);
hideCursorToggle.Current.BindValueChanged(onCursorToggle);
- hiddenToggle?.Current.BindValueChanged(onHiddenToggle);
}
private void onCursorToggle(ValueChangedEvent hide)
@@ -60,21 +79,34 @@ namespace osu.Game.Rulesets.Osu.UI
// this only hides half the cursor
if (hide.NewValue)
{
- drawableRuleset.Playfield.Cursor.Hide();
+ drawableRuleset.Playfield.Cursor.FadeOut();
} else
{
- drawableRuleset.Playfield.Cursor.Show();
+ drawableRuleset.Playfield.Cursor.FadeIn();
}
}
- private void onHiddenToggle(ValueChangedEvent off)
+ private void onModToggle(IToggleableVisibility mod, ValueChangedEvent toggled)
{
- if (off.NewValue)
+ if (toggled.NewValue)
{
- logger.Add("Hidden off", LogLevel.Debug);
+ mod.ToggleOnVisibility(drawableRuleset.Playfield);
} else
{
- logger.Add("Hidden on", LogLevel.Debug);
+ mod.ToggleOffVisibility(drawableRuleset.Playfield);
+ }
+ }
+
+ private partial class SelectableModSwitchSmall : ModSwitchSmall
+ {
+ public SelectableModSwitchSmall(IMod mod)
+ : base(mod)
+ {}
+
+ protected override bool OnClick(ClickEvent e)
+ {
+ Active.Value = !Active.Value;
+ return true;
}
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index d7e1732175..3cb3c50ef7 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.UI
HitObjectContainer,
judgementAboveHitObjectLayer = new Container { RelativeSizeAxes = Axes.Both },
approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both },
- MarkersContainer = new HitMarkerContainer { RelativeSizeAxes = Axes.Both }
+ MarkersContainer = new HitMarkerContainer(HitObjectContainer) { RelativeSizeAxes = Axes.Both }
};
HitPolicy = new StartTimeOrderedHitPolicy();
diff --git a/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs b/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs
index f829fb4ac1..017cc9bf82 100644
--- a/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs
+++ b/osu.Game/Localisation/PlayerSettingsOverlayStrings.cs
@@ -34,6 +34,11 @@ namespace osu.Game.Localisation
///
public static LocalisableString HideCursor => new TranslatableString(getKey(@"hide_cursor"), @"Hide cursor");
+ ///
+ /// "Aim lines"
+ ///
+ public static LocalisableString AimLines => new TranslatableString(getKey(@"aim_lines"), @"Aim lines");
+
///
/// "Seek backward {0} seconds"
///
diff --git a/osu.Game/Rulesets/Mods/IToggleableVisibility.cs b/osu.Game/Rulesets/Mods/IToggleableVisibility.cs
new file mode 100644
index 0000000000..5d90c24b9e
--- /dev/null
+++ b/osu.Game/Rulesets/Mods/IToggleableVisibility.cs
@@ -0,0 +1,11 @@
+using osu.Game.Rulesets.UI;
+
+namespace osu.Game.Rulesets.Mods
+{
+ public interface IToggleableVisibility
+ {
+ public void ToggleOffVisibility(Playfield playfield);
+
+ public void ToggleOnVisibility(Playfield playfield);
+ }
+}
\ No newline at end of file
From 1d552e7c90b8d3f7350fecaa43f5759b0154eaa7 Mon Sep 17 00:00:00 2001
From: Sheepposu
Date: Tue, 20 Feb 2024 22:28:25 -0500
Subject: [PATCH 0054/1450] move skin component logic
---
.../Default/OsuTrianglesSkinTransformer.cs | 22 +++++++++++++++++++
.../Legacy/OsuLegacySkinTransformer.cs | 22 -------------------
2 files changed, 22 insertions(+), 22 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs
index 7a4c768aa2..69ef1d9dc0 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs
@@ -29,6 +29,28 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
return new DefaultJudgementPieceSliderTickMiss(result);
}
+ break;
+ case OsuSkinComponentLookup osuComponent:
+ switch (osuComponent.Component)
+ {
+ case OsuSkinComponents.HitMarkerLeft:
+ if (GetTexture(@"hitmarker-left") != null)
+ return new HitMarker(OsuAction.LeftButton);
+
+ return null;
+
+ case OsuSkinComponents.HitMarkerRight:
+ if (GetTexture(@"hitmarker-right") != null)
+ return new HitMarker(OsuAction.RightButton);
+
+ return null;
+
+ case OsuSkinComponents.AimMarker:
+ if (GetTexture(@"aimmarker") != null)
+ return new HitMarker();
+
+ return null;
+ }
break;
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
index 84c055fe5e..a931d3ecbf 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
@@ -5,7 +5,6 @@ using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Rulesets.Osu.Skinning.Default;
using osu.Game.Skinning;
using osuTK;
@@ -168,27 +167,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
return new LegacyApproachCircle();
return null;
-
- case OsuSkinComponents.HitMarkerLeft:
- if (GetTexture(@"hitmarker-left") != null)
- return new HitMarker(OsuAction.LeftButton);
-
- return null;
-
- case OsuSkinComponents.HitMarkerRight:
- if (GetTexture(@"hitmarker-right") != null)
- return new HitMarker(OsuAction.RightButton);
-
- return null;
-
- case OsuSkinComponents.AimMarker:
- if (GetTexture(@"aimmarker") != null)
- return new HitMarker();
-
- return null;
-
- default:
- throw new UnsupportedSkinComponentException(lookup);
}
}
From 7d34542c12a8e1db707d101fc7988e32e9da74d8 Mon Sep 17 00:00:00 2001
From: TextAdventurer12
Date: Thu, 22 Feb 2024 15:14:56 +1300
Subject: [PATCH 0055/1450] 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 0056/1450] cap each note at adding 1 difficult strain count
---
osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
index 13d89cf2b8..84bf8e3bf6 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
@@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
{
double consistentTopStrain = difficulty / 10; // What would the top strain be if all strain values were identical
- return objectStrains.Sum(s => Math.Pow(1, s / consistentTopStrain, 5));
+ return objectStrains.Sum(s => Math.Pow(Math.Min(1, s / consistentTopStrain), 5));
}
}
}
From 35b89966bccc2455034c7aed973cf2fefbf87ca7 Mon Sep 17 00:00:00 2001
From: Sheepposu
Date: Wed, 21 Feb 2024 23:23:40 -0500
Subject: [PATCH 0057/1450] revert mod visibility toggle
---
osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 79 +++----------------
osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 12 ++-
.../UI/OsuAnalysisSettings.cs | 57 +------------
.../Rulesets/Mods/IToggleableVisibility.cs | 11 ---
4 files changed, 17 insertions(+), 142 deletions(-)
delete mode 100644 osu.Game/Rulesets/Mods/IToggleableVisibility.cs
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
index a69bed6fb2..e45daed919 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
@@ -2,11 +2,9 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Transforms;
using osu.Framework.Bindables;
using osu.Framework.Localisation;
using osu.Game.Configuration;
@@ -14,14 +12,13 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Skinning;
namespace osu.Game.Rulesets.Osu.Mods
{
- public class OsuModHidden : ModHidden, IHidesApproachCircles, IToggleableVisibility
+ public class OsuModHidden : ModHidden, IHidesApproachCircles
{
[SettingSource("Only fade approach circles", "The main object body will not fade when enabled.")]
public Bindable OnlyFadeApproachCircles { get; } = new BindableBool();
@@ -31,41 +28,24 @@ namespace osu.Game.Rulesets.Osu.Mods
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModDepth) };
- private bool toggledOff = false;
- private IBeatmap? appliedBeatmap;
-
public const double FADE_IN_DURATION_MULTIPLIER = 0.4;
public const double FADE_OUT_DURATION_MULTIPLIER = 0.3;
protected override bool IsFirstAdjustableObject(HitObject hitObject) => !(hitObject is Spinner || hitObject is SpinnerTick);
- public override void ApplyToBeatmap(IBeatmap? beatmap = null)
+ public override void ApplyToBeatmap(IBeatmap beatmap)
{
- if (beatmap is not null)
- appliedBeatmap = beatmap;
- else if (appliedBeatmap is null)
- return;
- else
- beatmap = appliedBeatmap;
-
base.ApplyToBeatmap(beatmap);
foreach (var obj in beatmap.HitObjects.OfType())
applyFadeInAdjustment(obj);
- }
- private static void applyFadeInAdjustment(OsuHitObject osuObject)
- {
- osuObject.TimeFadeIn = osuObject.TimePreempt * FADE_IN_DURATION_MULTIPLIER;
- foreach (var nested in osuObject.NestedHitObjects.OfType())
- applyFadeInAdjustment(nested);
- }
-
- private static void revertFadeInAdjustment(OsuHitObject osuObject)
- {
- osuObject.TimeFadeIn = OsuHitObject.CalculateTimeFadeIn(osuObject.TimePreempt);
- foreach (var nested in osuObject.NestedHitObjects.OfType())
- revertFadeInAdjustment(nested);
+ static void applyFadeInAdjustment(OsuHitObject osuObject)
+ {
+ osuObject.TimeFadeIn = osuObject.TimePreempt * FADE_IN_DURATION_MULTIPLIER;
+ foreach (var nested in osuObject.NestedHitObjects.OfType())
+ applyFadeInAdjustment(nested);
+ }
}
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
@@ -80,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Mods
private void applyHiddenState(DrawableHitObject drawableObject, bool increaseVisibility)
{
- if (!(drawableObject is DrawableOsuHitObject drawableOsuObject) || toggledOff)
+ if (!(drawableObject is DrawableOsuHitObject drawableOsuObject))
return;
OsuHitObject hitObject = drawableOsuObject.HitObject;
@@ -203,11 +183,8 @@ namespace osu.Game.Rulesets.Osu.Mods
}
}
- private void hideSpinnerApproachCircle(DrawableSpinner spinner)
+ private static void hideSpinnerApproachCircle(DrawableSpinner spinner)
{
- if (toggledOff)
- return;
-
var approachCircle = (spinner.Body.Drawable as IHasApproachCircle)?.ApproachCircle;
if (approachCircle == null)
return;
@@ -215,39 +192,5 @@ namespace osu.Game.Rulesets.Osu.Mods
using (spinner.BeginAbsoluteSequence(spinner.HitObject.StartTime - spinner.HitObject.TimePreempt))
approachCircle.Hide();
}
-
- public void ToggleOffVisibility(Playfield playfield)
- {
- if (toggledOff)
- return;
-
- toggledOff = true;
-
- if (appliedBeatmap is not null)
- foreach (var obj in appliedBeatmap.HitObjects.OfType())
- revertFadeInAdjustment(obj);
-
- foreach (var dho in playfield.AllHitObjects)
- {
- dho.RefreshStateTransforms();
- }
- }
-
- public void ToggleOnVisibility(Playfield playfield)
- {
- if (!toggledOff)
- return;
-
- toggledOff = false;
-
- if (appliedBeatmap is not null)
- ApplyToBeatmap();
-
- foreach (var dho in playfield.AllHitObjects)
- {
- dho.RefreshStateTransforms();
- ApplyToDrawableHitObject(dho);
- }
- }
}
-}
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
index 60305ed953..74631400ca 100644
--- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
@@ -154,19 +154,17 @@ namespace osu.Game.Rulesets.Osu.Objects
});
}
- // Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR.
- // This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above.
- // Note that this doesn't exactly match the AR>10 visuals as they're classically known, but it feels good.
- // This adjustment is necessary for AR>10, otherwise TimePreempt can become smaller leading to hitcircles not fully fading in.
- static public double CalculateTimeFadeIn(double timePreempt) => 400 * Math.Min(1, timePreempt / PREEMPT_MIN);
-
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, PREEMPT_MAX, PREEMPT_MID, PREEMPT_MIN);
- TimeFadeIn = CalculateTimeFadeIn(TimePreempt);
+ // Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR.
+ // This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above.
+ // Note that this doesn't exactly match the AR>10 visuals as they're classically known, but it feels good.
+ // This adjustment is necessary for AR>10, otherwise TimePreempt can become smaller leading to hitcircles not fully fading in.
+ TimeFadeIn = 400 * Math.Min(1, TimePreempt / PREEMPT_MIN);
Scale = LegacyRulesetExtensions.CalculateScaleFromCircleSize(difficulty.CircleSize, true);
}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
index 050675d970..319ae3e1f5 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
@@ -8,7 +8,6 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Game.Graphics.Containers;
using osu.Game.Localisation;
-using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play.PlayerSettings;
@@ -23,7 +22,6 @@ namespace osu.Game.Rulesets.Osu.UI
private readonly PlayerCheckbox aimMarkerToggle;
private readonly PlayerCheckbox hideCursorToggle;
private readonly PlayerCheckbox aimLinesToggle;
- private readonly FillFlowContainer modTogglesContainer;
public OsuAnalysisSettings(DrawableRuleset drawableRuleset)
: base(drawableRuleset)
@@ -33,37 +31,8 @@ namespace osu.Game.Rulesets.Osu.UI
hitMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HitMarkers },
aimMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.AimMarkers },
aimLinesToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.AimLines },
- hideCursorToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HideCursor },
- new OsuScrollContainer(Direction.Horizontal)
- {
- RelativeSizeAxes = Axes.X,
- Height = ModSwitchSmall.DEFAULT_SIZE,
- ScrollbarOverlapsContent = false,
- Child = modTogglesContainer = new FillFlowContainer
- {
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- Direction = FillDirection.Horizontal,
- RelativeSizeAxes = Axes.Y,
- AutoSizeAxes = Axes.X
- }
- }
+ hideCursorToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HideCursor }
};
-
- foreach (var mod in drawableRuleset.Mods)
- {
- if (mod is IToggleableVisibility toggleableMod)
- {
- var modSwitch = new SelectableModSwitchSmall(mod)
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Active = { Value = true }
- };
- modSwitch.Active.BindValueChanged((v) => onModToggle(toggleableMod, v));
- modTogglesContainer.Add(modSwitch);
- }
- }
}
protected override void LoadComplete()
@@ -85,29 +54,5 @@ namespace osu.Game.Rulesets.Osu.UI
drawableRuleset.Playfield.Cursor.FadeIn();
}
}
-
- private void onModToggle(IToggleableVisibility mod, ValueChangedEvent toggled)
- {
- if (toggled.NewValue)
- {
- mod.ToggleOnVisibility(drawableRuleset.Playfield);
- } else
- {
- mod.ToggleOffVisibility(drawableRuleset.Playfield);
- }
- }
-
- private partial class SelectableModSwitchSmall : ModSwitchSmall
- {
- public SelectableModSwitchSmall(IMod mod)
- : base(mod)
- {}
-
- protected override bool OnClick(ClickEvent e)
- {
- Active.Value = !Active.Value;
- return true;
- }
- }
}
}
\ No newline at end of file
diff --git a/osu.Game/Rulesets/Mods/IToggleableVisibility.cs b/osu.Game/Rulesets/Mods/IToggleableVisibility.cs
deleted file mode 100644
index 5d90c24b9e..0000000000
--- a/osu.Game/Rulesets/Mods/IToggleableVisibility.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using osu.Game.Rulesets.UI;
-
-namespace osu.Game.Rulesets.Mods
-{
- public interface IToggleableVisibility
- {
- public void ToggleOffVisibility(Playfield playfield);
-
- public void ToggleOnVisibility(Playfield playfield);
- }
-}
\ No newline at end of file
From f9d9df30b2104322920eb8326ad01ea946f87f06 Mon Sep 17 00:00:00 2001
From: Sheepposu
Date: Thu, 22 Feb 2024 07:31:15 -0500
Subject: [PATCH 0058/1450] add test for HitMarkerContainer
---
.../Resources/special-skin/aimmarker@2x.png | Bin 0 -> 648 bytes
.../special-skin/hitmarker-left@2x.png | Bin 0 -> 2127 bytes
.../special-skin/hitmarker-right@2x.png | Bin 0 -> 1742 bytes
.../Resources/special-skin/skin.ini | 5 +-
.../TestSceneHitMarker.cs | 134 ++++++++++++++++++
5 files changed, 138 insertions(+), 1 deletion(-)
create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/aimmarker@2x.png
create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-left@2x.png
create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-right@2x.png
create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/aimmarker@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/aimmarker@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..0b2a554193f08f7984568980956a3db8a6b39800
GIT binary patch
literal 648
zcmV;30(bq1P)EX>4Tx04R}tkv&MmKpe$iQ>7vm2a6PO$WR@`E-K4rtTK|H%@
z>74h8L#!+*#OK7523?T&k?XR{Z=6dG3p_JqWYhD+A!4!A#c~(3vY`^s5JwbMqkJLf
zvch?bvs$gQ_C5Ivg9U9R!*!aYNMH#`q#!~@9TikzAxf)8iitGs$36Tbjz2{%nOqex
zax9<*6_Voz|AXJ%n#JiUHz^ngdS7h&V+;uF0jdyW16NwdUuyz$pQJZB
zTI2{A+y*YLJDR))TI00006VoOIv00000008+zyMF)x010qNS#tmY
zE+YT{E+YYWr9XB6000McNliru=mHiD95ZRmtwR6+02y>eSad^gZEa<4bO1wgWnpw>
zWFU8GbZ8()Nlj2!fese{003Y~L_t&-(~Xd^4Zt7_1kYC1UL}8=Py}}=u;}y?!uSxX)0000EX>4Tx04R}tkv&MmKpe$iQ?)8p2Rn#3WT;NoK}8%(6^me@v=v%)FuC+YXws0R
zxHt-~1qVMCs}3&Cx;nTDg5U>;vxAeOiUKFoAxFnR+6_CX>@2HM@dakSAh-}000IiNkls_o@u5n{w3H!5jp#I_s}mSL6nvbfkWc#_vuLYo^#J%=bYz0&vPDu>`?~P
z0Nc!V3E&1C=JO;l4U7YWzyQ!=wjUbdA^U#|Xawp!3a-6gDOj^!(!4I&S*VDQsaC;d
zkpu@oS~HUIlo5?2^x2VUX1*t+Nq-yB9v@8*1=@kX09PKhkXqoU8}iuo%6H`9I*-*=
zs7QtWsq|QHIFq^%*3_$0#@g!%TuB`Tz#)<-Rfv`s2s4$%QoO2IwpJ8aHbxR!qW7trJguJ@1?U45bE-`_uOK{-b-%3G@PfU<~*e
zh?{*?U>&d#r~s;f+WnivkpmT$8`bNrWoNG~1b*F}+HfgG2XGX)$`bIrTV}Ye*3uC>
zq=ruJv2<)!2?D2qeSqx}JNi2t
zZQa#wVF)+@__FvNA8?}DEev%w+PVYHj{b&HVE||~=kU;$=+xmIQrE;mC2+(ioi{B_
z14~B(&~wBmou4?U1PDHZc=jd~eOHCfJ4>$%CvGf!H$C^B1-{^CW
zORYQQPIC*FJ;-)C)w)yeyz;EQuf9aM2(<9%X{j#}E?#-o-e-zAb+2tE-D7|^5ATq?
zKPt;x^IFGEMO1bn`Y0b*rGQ)vpN56TnkFjz%cEl&04>UXaP19WdWiP+eR%_|#
zY_xTo)~RR(2`K`4IvZ`>ZMBvT;GKKA9b5Amiycx!=6|Arl}AIhTNsKRQbSww88cm_
z&$%e?{q<>UQ8Hr~O=r?UpqZ7)iIaOQk2_>R_~GAElfeGZc(EJun2f*VoHpGKA1fE%
zW|d(4CFk^pJSI&K{I>Z$^s8!FUC@l#qnEW&;P)$7NN6_2le@kqBsYFlnE6LgSAh=E
zd{|fKvAT}?({|u}RzB|^_owZ39;*weyX}g26oT_FIwQc`1A4KK8XGV-|DrSEQ3wKM
zB2cr}D+T>i=`k~&xS0b&ZUX20Q|Yn2UMUFFh`_d*^^(>b&ZNwsC|Bt14QEm{>m?1?
zCIV$%m+ZU{)>JdH%N6_=!kX%J$xfh521*JQQMx*17-o2yD~w&8GS(IB2Y0l0XL0O!
zQb(~!i_VG2DnSO4Y0bbPVkC7`;L~hoUhdplS)RM<5J{vpdqa)piM1;R`uq0a*2A}}}-&CFL8OK}!6
zfVo0NWw8_=iDu@3K@k|}329PQv20~AjhQP{RTazDo{%Q7nAu-FPUGNcf@mb6MfPtJ
zM}Ybq5K_N?lQfP9prGz^zLl@R8mKqoXdRswQBoUra#pR{83{q(7nteTADoK?pG`A`9D8*
zL+g+7N8rOR69RB?D8@HbQD5`&4x1ww%{(zYhq8?E{44b(!oD|l*)8v0UWm1Qq+bAj
zbN5U4x*zH93LD?uTB69FHx-%Cyv%2>X8fJ)3^^T6(_aU)m*Br_F
z6_Nx3XU28k6Kk-%u-)ePB(8b=QDY16?Y-
z$_egQ2*3<*<;=LgJzv(Xzwp`DthMUSb0Oo$z$d`aQkhub=n{^MKyPm^JPw7#IhEi{TDHR{#GTxr2YT^002ovPDHLk
FV1jr`?veli
literal 0
HcmV?d00001
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-right@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-right@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..66be9a80ee2e2ae4c6dc6915d2b7ca5cf8ab589f
GIT binary patch
literal 1742
zcmV;<1~K`GP)EX>4Tx04R}tkv&MmKpe$iQ?)8p2Rn#3WT;NoK}8%(6^me@v=v%)FuC+YXws0R
zxHt-~1qVMCs}3&Cx;nTDg5U>;vxAeOiUKFoAxFnR+6_CX>@2HM@dakSAh-}000D~Nkl1L9Yb|fH_ftE7RjjO?hFdLVhHsD6;Mi3NQZON`8V+<3BQA3EPF^Lyy
zBFH5ssJYD~PVPNjt#956lhOc~_xg%spW#euk$w}6}R9gK=vhuEEC9o1$c~`Rf
z(Ry2wlf>QYeKL21@Rg4>x`?ys*smS!IAyn7e8E7rV7}_2!Kl|YJi?#x1%84Syp!In
zkY2Z>ZC|ASeH8V&R&~)}wqQQKV8CuUoJ!|IpZh27F_`+c=GVn>f!toVMTXssZ0rPq-2F
z7)4*YhF6Y6=fW|AnK={TxGQO->3NOv?ZHqu?n-9PL^x&;=hC%on8f+eXCO0UEIMoi
zC$QrUyo)<T*3ZV=R?f-~Sw)q8_7}ITNXzj(Ynz8XY#0nKO|Ffd3lJ+SQw?
zo^T_5u}|1KI1!G!Qa#~D8k*bxlSMfdpVSn!s$M%F!q0GjixAm?KUED#k}2O6e=Ub!
zzT9@~mdieLFWo14Y(4bY?{@Z~d#M9B__8N*HojnoWl8a{S^UD*#Oe5q57df^KXo6y
zFS^)9_p=4_sqNTj>tdf)v)O`qjqaxoT%0!kCVZ*Rs)f?;&EU6Nn8-Z~eiR+BtjUAq
zuj+95Y2#Stj`7sR9`G*v)Lv2)QZ=X0g)O!$}Y)kHjB1^&ZygL
zaa;~xW2Fp;tl;b7xLnk27M%fmLZ@PB*b@vZ9}Jg6605gAXe
zstYZ)p{)uZh6ZtROM+Y(5y>UDycZfo_&zMvtXfj*ar#}oX-JZ!IZ2XB>92W{iM*rM
zPniAbKjTyQRE^sNDlJm64K~qIM5Tc?-B3Fj<2yzt(TD^gFHQ4B!vebI{%dk!P-X
kiyU)`Hc@tO_2Ah*0YN2jCf7K0Z2$lO07*qoM6N<$f~iM7hyVZp
literal 0
HcmV?d00001
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini
index 9d16267d73..2952948f45 100644
--- a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini
+++ b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini
@@ -1,4 +1,7 @@
[General]
Version: latest
HitCircleOverlayAboveNumber: 0
-HitCirclePrefix: display
\ No newline at end of file
+HitCirclePrefix: display
+
+[Colours]
+ReplayAimLine: 0,0,255
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs
new file mode 100644
index 0000000000..a0748e31af
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs
@@ -0,0 +1,134 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Input.Events;
+using osu.Framework.Input.States;
+using osu.Framework.Logging;
+using osu.Framework.Testing.Input;
+using osu.Game.Rulesets.Osu.UI;
+using osu.Game.Rulesets.UI;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public partial class TestSceneHitMarker : OsuSkinnableTestScene
+ {
+ [Test]
+ public void TestHitMarkers()
+ {
+ var markerContainers = new List();
+
+ AddStep("Create hit markers", () =>
+ {
+ markerContainers.Clear();
+ SetContents(_ => {
+ markerContainers.Add(new TestHitMarkerContainer(new HitObjectContainer())
+ {
+ HitMarkerEnabled = { Value = true },
+ AimMarkersEnabled = { Value = true },
+ AimLinesEnabled = { Value = true },
+ RelativeSizeAxes = Axes.Both
+ });
+
+ return new HitMarkerInputManager
+ {
+ RelativeSizeAxes = Axes.Both,
+ Size = new Vector2(0.95f),
+ Child = markerContainers[^1],
+ };
+ });
+ });
+
+ AddUntilStep("Until skinnable expires", () =>
+ {
+ if (markerContainers.Count == 0)
+ return false;
+
+ Logger.Log("How many: " + markerContainers.Count);
+
+ foreach (var markerContainer in markerContainers)
+ {
+ if (markerContainer.Children.Count != 0)
+ return false;
+ }
+
+ return true;
+ });
+ }
+
+ private partial class HitMarkerInputManager : ManualInputManager
+ {
+ private double? startTime;
+
+ public HitMarkerInputManager()
+ {
+ UseParentInput = false;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ MoveMouseTo(ToScreenSpace(DrawSize / 2));
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ const float spin_angle = 4 * MathF.PI;
+
+ startTime ??= Time.Current;
+
+ float fraction = (float)((Time.Current - startTime) / 5_000);
+
+ float angle = fraction * spin_angle;
+ float radius = fraction * Math.Min(DrawSize.X, DrawSize.Y) / 2;
+
+ Vector2 pos = radius * new Vector2(MathF.Cos(angle), MathF.Sin(angle)) + DrawSize / 2;
+ MoveMouseTo(ToScreenSpace(pos));
+ }
+ }
+
+ private partial class TestHitMarkerContainer : HitMarkerContainer
+ {
+ private double? lastClick;
+ private double? startTime;
+ private bool finishedDrawing = false;
+ private bool leftOrRight = false;
+
+ public TestHitMarkerContainer(HitObjectContainer hitObjectContainer)
+ : base(hitObjectContainer)
+ {
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (finishedDrawing)
+ return;
+
+ startTime ??= lastClick ??= Time.Current;
+
+ if (startTime + 5_000 <= Time.Current)
+ {
+ finishedDrawing = true;
+ HitMarkerEnabled.Value = AimMarkersEnabled.Value = AimLinesEnabled.Value = false;
+ return;
+ }
+
+ if (lastClick + 400 <= Time.Current)
+ {
+ OnPressed(new KeyBindingPressEvent(new InputState(), leftOrRight ? OsuAction.LeftButton : OsuAction.RightButton));
+ leftOrRight = !leftOrRight;
+ lastClick = Time.Current;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
From af13389a9e8fc58ef549cb8e9920375c22dc99ef Mon Sep 17 00:00:00 2001
From: Sheepposu
Date: Thu, 22 Feb 2024 07:31:56 -0500
Subject: [PATCH 0059/1450] fix hit marker skinnables
---
.../Default/OsuTrianglesSkinTransformer.cs | 22 -------------------
.../Legacy/OsuLegacySkinTransformer.cs | 19 ++++++++++++++++
2 files changed, 19 insertions(+), 22 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs
index 69ef1d9dc0..7a4c768aa2 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/OsuTrianglesSkinTransformer.cs
@@ -29,28 +29,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
return new DefaultJudgementPieceSliderTickMiss(result);
}
- break;
- case OsuSkinComponentLookup osuComponent:
- switch (osuComponent.Component)
- {
- case OsuSkinComponents.HitMarkerLeft:
- if (GetTexture(@"hitmarker-left") != null)
- return new HitMarker(OsuAction.LeftButton);
-
- return null;
-
- case OsuSkinComponents.HitMarkerRight:
- if (GetTexture(@"hitmarker-right") != null)
- return new HitMarker(OsuAction.RightButton);
-
- return null;
-
- case OsuSkinComponents.AimMarker:
- if (GetTexture(@"aimmarker") != null)
- return new HitMarker();
-
- return null;
- }
break;
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
index a931d3ecbf..097f1732e2 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
@@ -6,6 +6,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Skinning;
+using osu.Game.Rulesets.Osu.Skinning.Default;
using osuTK;
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
@@ -167,6 +168,24 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
return new LegacyApproachCircle();
return null;
+
+ case OsuSkinComponents.HitMarkerLeft:
+ if (GetTexture(@"hitmarker-left") != null)
+ return new HitMarker(OsuAction.LeftButton);
+
+ return null;
+
+ case OsuSkinComponents.HitMarkerRight:
+ if (GetTexture(@"hitmarker-right") != null)
+ return new HitMarker(OsuAction.RightButton);
+
+ return null;
+
+ case OsuSkinComponents.AimMarker:
+ if (GetTexture(@"aimmarker") != null)
+ return new HitMarker();
+
+ return null;
}
}
From 45444b39d461ca9c497cc7ffdac8402b98e26e03 Mon Sep 17 00:00:00 2001
From: Sheepposu
Date: Thu, 22 Feb 2024 19:01:52 -0500
Subject: [PATCH 0060/1450] fix formatting issues
---
.../TestSceneHitMarker.cs | 11 ++++---
osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 2 +-
osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +-
.../Skinning/Default/DefaultHitMarker.cs | 7 ++++-
.../Skinning/Default/HitMarker.cs | 7 ++++-
.../Legacy/OsuLegacySkinTransformer.cs | 4 +--
.../UI/HitMarkerContainer.cs | 31 +++++++++----------
.../UI/OsuAnalysisSettings.cs | 22 ++++++-------
osu.Game/Rulesets/Ruleset.cs | 2 +-
osu.Game/Screens/Play/Player.cs | 2 +-
.../Play/PlayerSettings/AnalysisSettings.cs | 6 ++--
osu.Game/Screens/Play/ReplayPlayer.cs | 5 ++-
12 files changed, 53 insertions(+), 48 deletions(-)
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs
index a0748e31af..7ba681b50f 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs
@@ -25,8 +25,9 @@ namespace osu.Game.Rulesets.Osu.Tests
AddStep("Create hit markers", () =>
{
markerContainers.Clear();
- SetContents(_ => {
- markerContainers.Add(new TestHitMarkerContainer(new HitObjectContainer())
+ SetContents(_ =>
+ {
+ markerContainers.Add(new TestHitMarkerContainer(new HitObjectContainer())
{
HitMarkerEnabled = { Value = true },
AimMarkersEnabled = { Value = true },
@@ -98,8 +99,8 @@ namespace osu.Game.Rulesets.Osu.Tests
{
private double? lastClick;
private double? startTime;
- private bool finishedDrawing = false;
- private bool leftOrRight = false;
+ private bool finishedDrawing;
+ private bool leftOrRight;
public TestHitMarkerContainer(HitObjectContainer hitObjectContainer)
: base(hitObjectContainer)
@@ -131,4 +132,4 @@ namespace osu.Game.Rulesets.Osu.Tests
}
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
index e45daed919..6dc0d5d522 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
@@ -193,4 +193,4 @@ namespace osu.Game.Rulesets.Osu.Mods
approachCircle.Hide();
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index 358553ac59..224d08cbd5 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -358,6 +358,6 @@ namespace osu.Game.Rulesets.Osu
return adjustedDifficulty;
}
- public override AnalysisSettings? CreateAnalysisSettings(DrawableRuleset drawableRuleset) => new OsuAnalysisSettings(drawableRuleset);
+ public override AnalysisSettings CreateAnalysisSettings(DrawableRuleset drawableRuleset) => new OsuAnalysisSettings(drawableRuleset);
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs
index f4c11e6c8a..7dabb5182f 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs
@@ -1,3 +1,6 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -61,11 +64,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
{
case OsuAction.LeftButton:
return (Colour4.Orange, 20, true);
+
case OsuAction.RightButton:
return (Colour4.LightGreen, 20, true);
+
default:
return (Colour4.Gray.Opacity(0.3F), 8, false);
}
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs
index d86e3d4f79..28877345d0 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs
@@ -1,3 +1,6 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
using osu.Framework.Allocation;
using osu.Framework.Graphics.Sprites;
using osu.Game.Skinning;
@@ -21,13 +24,15 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
case OsuAction.LeftButton:
Texture = skin.GetTexture(@"hitmarker-left");
break;
+
case OsuAction.RightButton:
Texture = skin.GetTexture(@"hitmarker-right");
break;
+
default:
Texture = skin.GetTexture(@"aimmarker");
break;
}
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
index 097f1732e2..61f9eebd86 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
@@ -168,7 +168,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
return new LegacyApproachCircle();
return null;
-
+
case OsuSkinComponents.HitMarkerLeft:
if (GetTexture(@"hitmarker-left") != null)
return new HitMarker(OsuAction.LeftButton);
@@ -184,7 +184,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
case OsuSkinComponents.AimMarker:
if (GetTexture(@"aimmarker") != null)
return new HitMarker();
-
+
return null;
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs b/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs
index 01877a9185..e8916ea545 100644
--- a/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs
+++ b/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs
@@ -25,12 +25,7 @@ namespace osu.Game.Rulesets.Osu.UI
{
private Vector2 lastMousePosition;
private Vector2? lastlastMousePosition;
- private double? timePreempt;
- private double TimePreempt
- {
- get => timePreempt ?? default_time_preempt;
- set => timePreempt = value;
- }
+ private double timePreempt;
public Bindable HitMarkerEnabled = new BindableBool();
public Bindable AimMarkersEnabled = new BindableBool();
@@ -45,6 +40,7 @@ namespace osu.Game.Rulesets.Osu.UI
public HitMarkerContainer(HitObjectContainer hitObjectContainer)
{
this.hitObjectContainer = hitObjectContainer;
+ timePreempt = default_time_preempt;
}
public bool OnPressed(KeyBindingPressEvent e)
@@ -52,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.UI
if (HitMarkerEnabled.Value && (e.Action == OsuAction.LeftButton || e.Action == OsuAction.RightButton))
{
updateTimePreempt();
- AddMarker(e.Action);
+ addMarker(e.Action);
}
return false;
@@ -70,33 +66,35 @@ namespace osu.Game.Rulesets.Osu.UI
if (AimMarkersEnabled.Value)
{
updateTimePreempt();
- AddMarker(null);
+ addMarker(null);
}
if (AimLinesEnabled.Value && lastlastMousePosition != null && lastlastMousePosition != lastMousePosition)
{
if (!AimMarkersEnabled.Value)
updateTimePreempt();
- Add(new AimLineDrawable((Vector2)lastlastMousePosition, lastMousePosition, TimePreempt));
+ Add(new AimLineDrawable((Vector2)lastlastMousePosition, lastMousePosition, timePreempt));
}
return base.OnMouseMove(e);
}
- private void AddMarker(OsuAction? action)
+ private void addMarker(OsuAction? action)
{
var component = OsuSkinComponents.AimMarker;
- switch(action)
+
+ switch (action)
{
case OsuAction.LeftButton:
component = OsuSkinComponents.HitMarkerLeft;
break;
+
case OsuAction.RightButton:
component = OsuSkinComponents.HitMarkerRight;
break;
}
- Add(new HitMarkerDrawable(action, component, TimePreempt)
+ Add(new HitMarkerDrawable(action, component, timePreempt)
{
Position = lastMousePosition,
Origin = Anchor.Centre,
@@ -110,13 +108,14 @@ namespace osu.Game.Rulesets.Osu.UI
if (hitObject == null)
return;
- TimePreempt = hitObject.TimePreempt;
+ timePreempt = hitObject.TimePreempt;
}
private OsuHitObject? getHitObject()
{
foreach (var dho in hitObjectContainer.Objects)
return (dho as DrawableOsuHitObject)?.HitObject;
+
return null;
}
@@ -141,7 +140,7 @@ namespace osu.Game.Rulesets.Osu.UI
LifetimeStart = Time.Current;
LifetimeEnd = LifetimeStart + lifetimeDuration;
- Scheduler.AddDelayed(() =>
+ Scheduler.AddDelayed(() =>
{
this.FadeOut(fadeOutTime);
}, lifetimeDuration - fadeOutTime);
@@ -186,11 +185,11 @@ namespace osu.Game.Rulesets.Osu.UI
LifetimeStart = Time.Current;
LifetimeEnd = LifetimeStart + lifetimeDuration;
- Scheduler.AddDelayed(() =>
+ Scheduler.AddDelayed(() =>
{
this.FadeOut(fadeOutTime);
}, lifetimeDuration - fadeOutTime);
}
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
index 319ae3e1f5..51fd835dc5 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
@@ -1,14 +1,9 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Input.Events;
-using osu.Game.Graphics.Containers;
using osu.Game.Localisation;
-using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play.PlayerSettings;
@@ -16,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.UI
{
public partial class OsuAnalysisSettings : AnalysisSettings
{
- protected new DrawableOsuRuleset drawableRuleset => (DrawableOsuRuleset)base.drawableRuleset;
+ protected new DrawableOsuRuleset DrawableRuleset => (DrawableOsuRuleset)base.DrawableRuleset;
private readonly PlayerCheckbox hitMarkerToggle;
private readonly PlayerCheckbox aimMarkerToggle;
@@ -37,9 +32,9 @@ namespace osu.Game.Rulesets.Osu.UI
protected override void LoadComplete()
{
- drawableRuleset.Playfield.MarkersContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current);
- drawableRuleset.Playfield.MarkersContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current);
- drawableRuleset.Playfield.MarkersContainer.AimLinesEnabled.BindTo(aimLinesToggle.Current);
+ DrawableRuleset.Playfield.MarkersContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current);
+ DrawableRuleset.Playfield.MarkersContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current);
+ DrawableRuleset.Playfield.MarkersContainer.AimLinesEnabled.BindTo(aimLinesToggle.Current);
hideCursorToggle.Current.BindValueChanged(onCursorToggle);
}
@@ -48,11 +43,12 @@ namespace osu.Game.Rulesets.Osu.UI
// this only hides half the cursor
if (hide.NewValue)
{
- drawableRuleset.Playfield.Cursor.FadeOut();
- } else
+ DrawableRuleset.Playfield.Cursor.FadeOut();
+ }
+ else
{
- drawableRuleset.Playfield.Cursor.FadeIn();
+ DrawableRuleset.Playfield.Cursor.FadeIn();
}
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs
index 5fbb23f094..84177f26b0 100644
--- a/osu.Game/Rulesets/Ruleset.cs
+++ b/osu.Game/Rulesets/Ruleset.cs
@@ -403,7 +403,7 @@ namespace osu.Game.Rulesets
/// Can be overridden to alter the difficulty section to the editor beatmap setup screen.
///
public virtual DifficultySection? CreateEditorDifficultySection() => null;
-
+
public virtual AnalysisSettings? CreateAnalysisSettings(DrawableRuleset drawableRuleset) => null;
}
}
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 0fa7143693..ad1f9ec897 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -115,7 +115,7 @@ namespace osu.Game.Screens.Play
public GameplayState GameplayState { get; private set; }
- protected Ruleset ruleset;
+ private Ruleset ruleset;
public BreakOverlay BreakOverlay;
diff --git a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs
index 122ca29142..e30d2b2d42 100644
--- a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs
+++ b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs
@@ -7,12 +7,12 @@ namespace osu.Game.Screens.Play.PlayerSettings
{
public partial class AnalysisSettings : PlayerSettingsGroup
{
- protected DrawableRuleset drawableRuleset;
+ protected DrawableRuleset DrawableRuleset;
public AnalysisSettings(DrawableRuleset drawableRuleset)
: base("Analysis Settings")
{
- this.drawableRuleset = drawableRuleset;
+ DrawableRuleset = drawableRuleset;
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs
index 0d877785e7..65e99731dc 100644
--- a/osu.Game/Screens/Play/ReplayPlayer.cs
+++ b/osu.Game/Screens/Play/ReplayPlayer.cs
@@ -72,10 +72,9 @@ namespace osu.Game.Screens.Play
HUDOverlay.PlayerSettingsOverlay.AddAtStart(playbackSettings);
- var analysisSettings = ruleset.CreateAnalysisSettings(DrawableRuleset);
- if (analysisSettings != null) {
+ var analysisSettings = DrawableRuleset.Ruleset.CreateAnalysisSettings(DrawableRuleset);
+ if (analysisSettings != null)
HUDOverlay.PlayerSettingsOverlay.AddAtStart(analysisSettings);
- }
}
protected override void PrepareReplay()
From 2a1fa8ccacd0ece5f6a098bd4951cefcd9a862c7 Mon Sep 17 00:00:00 2001
From: Sheepposu
Date: Thu, 22 Feb 2024 22:34:38 -0500
Subject: [PATCH 0061/1450] fix OsuAnalysisSettings text not updating
---
osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
index 51fd835dc5..0411882d2a 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Localisation;
@@ -30,7 +31,8 @@ namespace osu.Game.Rulesets.Osu.UI
};
}
- protected override void LoadComplete()
+ [BackgroundDependencyLoader]
+ private void load()
{
DrawableRuleset.Playfield.MarkersContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current);
DrawableRuleset.Playfield.MarkersContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current);
From 4d669c56a2adb102031e1ea0c63538409ace3ba8 Mon Sep 17 00:00:00 2001
From: Sheepposu
Date: Fri, 23 Feb 2024 23:32:35 -0500
Subject: [PATCH 0062/1450] implement pooling
Uses pooling for all analysis objects and creates the lifetime entries from replay data when the analysis container is constructed.
---
.../UI/OsuAnalysisContainer.cs | 242 ++++++++++++++++++
.../UI/OsuAnalysisSettings.cs | 18 +-
osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 5 +-
osu.Game/Rulesets/UI/AnalysisContainer.cs | 18 ++
osu.Game/Rulesets/UI/Playfield.cs | 6 +
.../Play/PlayerSettings/AnalysisSettings.cs | 5 +-
osu.Game/Screens/Play/ReplayPlayer.cs | 3 +
7 files changed, 284 insertions(+), 13 deletions(-)
create mode 100644 osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs
create mode 100644 osu.Game/Rulesets/UI/AnalysisContainer.cs
diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs
new file mode 100644
index 0000000000..a637eddd05
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs
@@ -0,0 +1,242 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Lines;
+using osu.Framework.Graphics.Performance;
+using osu.Framework.Graphics.Pooling;
+using osu.Game.Replays;
+using osu.Game.Rulesets.Objects.Pooling;
+using osu.Game.Rulesets.Osu.Replays;
+using osu.Game.Rulesets.Osu.Skinning;
+using osu.Game.Rulesets.Osu.Skinning.Default;
+using osu.Game.Rulesets.UI;
+using osu.Game.Skinning;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Osu.UI
+{
+ public partial class OsuAnalysisContainer : AnalysisContainer
+ {
+ public Bindable HitMarkerEnabled = new BindableBool();
+ public Bindable AimMarkersEnabled = new BindableBool();
+ public Bindable AimLinesEnabled = new BindableBool();
+
+ private HitMarkersContainer hitMarkersContainer;
+ private AimMarkersContainer aimMarkersContainer;
+ private AimLinesContainer aimLinesContainer;
+
+ public OsuAnalysisContainer(Replay replay)
+ : base(replay)
+ {
+ InternalChildren = new Drawable[]
+ {
+ hitMarkersContainer = new HitMarkersContainer(),
+ aimMarkersContainer = new AimMarkersContainer() { Depth = float.MinValue },
+ aimLinesContainer = new AimLinesContainer() { Depth = float.MaxValue }
+ };
+
+ HitMarkerEnabled.ValueChanged += e => hitMarkersContainer.FadeTo(e.NewValue ? 1 : 0);
+ AimMarkersEnabled.ValueChanged += e => aimMarkersContainer.FadeTo(e.NewValue ? 1 : 0);
+ AimLinesEnabled.ValueChanged += e => aimLinesContainer.FadeTo(e.NewValue ? 1 : 0);
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin)
+ {
+ var aimLineColor = skin.GetConfig(OsuSkinColour.ReplayAimLine)?.Value ?? Color4.White;
+ aimLineColor.A = 127;
+
+ hitMarkersContainer.Hide();
+ aimMarkersContainer.Hide();
+ aimLinesContainer.Hide();
+
+ bool leftHeld = false;
+ bool rightHeld = false;
+ foreach (var frame in Replay.Frames)
+ {
+ var osuFrame = (OsuReplayFrame)frame;
+
+ aimMarkersContainer.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position));
+ aimLinesContainer.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position));
+
+ bool leftButton = osuFrame.Actions.Contains(OsuAction.LeftButton);
+ bool rightButton = osuFrame.Actions.Contains(OsuAction.RightButton);
+
+ if (leftHeld && !leftButton)
+ leftHeld = false;
+ else if (!leftHeld && leftButton)
+ {
+ hitMarkersContainer.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, true));
+ leftHeld = true;
+ }
+
+ if (rightHeld && !rightButton)
+ rightHeld = false;
+ else if (!rightHeld && rightButton)
+ {
+ hitMarkersContainer.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, false));
+ rightHeld = true;
+ }
+ }
+ }
+
+ private partial class HitMarkersContainer : PooledDrawableWithLifetimeContainer
+ {
+ private readonly HitMarkerPool leftPool;
+ private readonly HitMarkerPool rightPool;
+
+ public HitMarkersContainer()
+ {
+ AddInternal(leftPool = new HitMarkerPool(OsuSkinComponents.HitMarkerLeft, OsuAction.LeftButton, 15));
+ AddInternal(rightPool = new HitMarkerPool(OsuSkinComponents.HitMarkerRight, OsuAction.RightButton, 15));
+ }
+
+ protected override HitMarkerDrawable GetDrawable(HitMarkerEntry entry) => (entry.IsLeftMarker ? leftPool : rightPool).Get(d => d.Apply(entry));
+ }
+
+ private partial class AimMarkersContainer : PooledDrawableWithLifetimeContainer
+ {
+ private readonly HitMarkerPool pool;
+
+ public AimMarkersContainer()
+ {
+ AddInternal(pool = new HitMarkerPool(OsuSkinComponents.AimMarker, null, 80));
+ }
+
+ protected override HitMarkerDrawable GetDrawable(AimPointEntry entry) => pool.Get(d => d.Apply(entry));
+ }
+
+ private partial class AimLinesContainer : Path
+ {
+ private LifetimeEntryManager lifetimeManager = new LifetimeEntryManager();
+ private SortedSet aliveEntries = new SortedSet(new AimLinePointComparator());
+
+ public AimLinesContainer()
+ {
+ lifetimeManager.EntryBecameAlive += entryBecameAlive;
+ lifetimeManager.EntryBecameDead += entryBecameDead;
+ lifetimeManager.EntryCrossedBoundary += entryCrossedBoundary;
+
+ PathRadius = 1f;
+ Colour = new Color4(255, 255, 255, 127);
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ lifetimeManager.Update(Time.Current);
+ }
+
+ public void Add(AimPointEntry entry) => lifetimeManager.AddEntry(entry);
+
+ private void entryBecameAlive(LifetimeEntry entry)
+ {
+ aliveEntries.Add((AimPointEntry)entry);
+ updateVertices();
+ }
+
+ private void entryBecameDead(LifetimeEntry entry)
+ {
+ aliveEntries.Remove((AimPointEntry)entry);
+ updateVertices();
+ }
+
+ private void updateVertices()
+ {
+ ClearVertices();
+ foreach (var entry in aliveEntries)
+ {
+ AddVertex(entry.Position);
+ }
+ }
+
+ private void entryCrossedBoundary(LifetimeEntry entry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction)
+ {
+
+ }
+
+ private sealed class AimLinePointComparator : IComparer
+ {
+ public int Compare(AimPointEntry? x, AimPointEntry? y)
+ {
+ ArgumentNullException.ThrowIfNull(x);
+ ArgumentNullException.ThrowIfNull(y);
+
+ return x.LifetimeStart.CompareTo(y.LifetimeStart);
+ }
+ }
+ }
+
+ private partial class HitMarkerDrawable : PoolableDrawableWithLifetime
+ {
+ ///
+ /// This constructor only exists to meet the new() type constraint of .
+ ///
+ public HitMarkerDrawable()
+ {
+ }
+
+ public HitMarkerDrawable(OsuSkinComponents component, OsuAction? action)
+ {
+ Origin = Anchor.Centre;
+ InternalChild = new SkinnableDrawable(new OsuSkinComponentLookup(component), _ => new DefaultHitMarker(action));
+ }
+
+ protected override void OnApply(AimPointEntry entry)
+ {
+ Position = entry.Position;
+
+ using (BeginAbsoluteSequence(LifetimeStart))
+ Show();
+
+ using (BeginAbsoluteSequence(LifetimeEnd - 200))
+ this.FadeOut(200);
+ }
+ }
+
+ private partial class HitMarkerPool : DrawablePool
+ {
+ private readonly OsuSkinComponents component;
+ private readonly OsuAction? action;
+
+ public HitMarkerPool(OsuSkinComponents component, OsuAction? action, int initialSize)
+ : base(initialSize)
+ {
+ this.component = component;
+ this.action = action;
+ }
+
+ protected override HitMarkerDrawable CreateNewDrawable() => new HitMarkerDrawable(component, action);
+ }
+
+ private partial class AimPointEntry : LifetimeEntry
+ {
+ public Vector2 Position { get; }
+
+ public AimPointEntry(double time, Vector2 position)
+ {
+ LifetimeStart = time;
+ LifetimeEnd = time + 1_000;
+ Position = position;
+ }
+ }
+
+ private partial class HitMarkerEntry : AimPointEntry
+ {
+ public bool IsLeftMarker { get; }
+
+ public HitMarkerEntry(double lifetimeStart, Vector2 position, bool isLeftMarker)
+ : base(lifetimeStart, position)
+ {
+ IsLeftMarker = isLeftMarker;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
index 0411882d2a..fd9cb67995 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
@@ -1,10 +1,10 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Localisation;
+using osu.Game.Replays;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play.PlayerSettings;
@@ -29,14 +29,7 @@ namespace osu.Game.Rulesets.Osu.UI
aimLinesToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.AimLines },
hideCursorToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HideCursor }
};
- }
- [BackgroundDependencyLoader]
- private void load()
- {
- DrawableRuleset.Playfield.MarkersContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current);
- DrawableRuleset.Playfield.MarkersContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current);
- DrawableRuleset.Playfield.MarkersContainer.AimLinesEnabled.BindTo(aimLinesToggle.Current);
hideCursorToggle.Current.BindValueChanged(onCursorToggle);
}
@@ -52,5 +45,14 @@ namespace osu.Game.Rulesets.Osu.UI
DrawableRuleset.Playfield.Cursor.FadeIn();
}
}
+
+ public override AnalysisContainer CreateAnalysisContainer(Replay replay)
+ {
+ var analysisContainer = new OsuAnalysisContainer(replay);
+ analysisContainer.HitMarkerEnabled.BindTo(hitMarkerToggle.Current);
+ analysisContainer.AimMarkersEnabled.BindTo(aimMarkerToggle.Current);
+ analysisContainer.AimLinesEnabled.BindTo(aimLinesToggle.Current);
+ return analysisContainer;
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index 3cb3c50ef7..ea336e6067 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -37,8 +37,6 @@ namespace osu.Game.Rulesets.Osu.UI
public SmokeContainer Smoke { get; }
- public HitMarkerContainer MarkersContainer { get; }
-
public FollowPointRenderer FollowPoints { get; }
public static readonly Vector2 BASE_SIZE = new Vector2(512, 384);
@@ -61,8 +59,7 @@ namespace osu.Game.Rulesets.Osu.UI
judgementLayer = new JudgementContainer { RelativeSizeAxes = Axes.Both },
HitObjectContainer,
judgementAboveHitObjectLayer = new Container { RelativeSizeAxes = Axes.Both },
- approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both },
- MarkersContainer = new HitMarkerContainer(HitObjectContainer) { RelativeSizeAxes = Axes.Both }
+ approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both }
};
HitPolicy = new StartTimeOrderedHitPolicy();
diff --git a/osu.Game/Rulesets/UI/AnalysisContainer.cs b/osu.Game/Rulesets/UI/AnalysisContainer.cs
new file mode 100644
index 0000000000..62d54374e7
--- /dev/null
+++ b/osu.Game/Rulesets/UI/AnalysisContainer.cs
@@ -0,0 +1,18 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics.Containers;
+using osu.Game.Replays;
+
+namespace osu.Game.Rulesets.UI
+{
+ public partial class AnalysisContainer : Container
+ {
+ protected Replay Replay;
+
+ public AnalysisContainer(Replay replay)
+ {
+ Replay = replay;
+ }
+ }
+}
diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs
index 90a2f63faa..e116acdc19 100644
--- a/osu.Game/Rulesets/UI/Playfield.cs
+++ b/osu.Game/Rulesets/UI/Playfield.cs
@@ -291,6 +291,12 @@ namespace osu.Game.Rulesets.UI
///
protected virtual HitObjectContainer CreateHitObjectContainer() => new HitObjectContainer();
+ ///
+ /// Adds an analysis container to internal children for replays.
+ ///
+ ///
+ public virtual void AddAnalysisContainer(AnalysisContainer analysisContainer) => AddInternal(analysisContainer);
+
#region Pooling support
private readonly Dictionary pools = new Dictionary();
diff --git a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs
index e30d2b2d42..3752b2b900 100644
--- a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs
+++ b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs
@@ -1,11 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Game.Replays;
using osu.Game.Rulesets.UI;
namespace osu.Game.Screens.Play.PlayerSettings
{
- public partial class AnalysisSettings : PlayerSettingsGroup
+ public abstract partial class AnalysisSettings : PlayerSettingsGroup
{
protected DrawableRuleset DrawableRuleset;
@@ -14,5 +15,7 @@ namespace osu.Game.Screens.Play.PlayerSettings
{
DrawableRuleset = drawableRuleset;
}
+
+ public abstract AnalysisContainer CreateAnalysisContainer(Replay replay);
}
}
diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs
index 65e99731dc..ce6cb5124a 100644
--- a/osu.Game/Screens/Play/ReplayPlayer.cs
+++ b/osu.Game/Screens/Play/ReplayPlayer.cs
@@ -74,7 +74,10 @@ namespace osu.Game.Screens.Play
var analysisSettings = DrawableRuleset.Ruleset.CreateAnalysisSettings(DrawableRuleset);
if (analysisSettings != null)
+ {
HUDOverlay.PlayerSettingsOverlay.AddAtStart(analysisSettings);
+ DrawableRuleset.Playfield.AddAnalysisContainer(analysisSettings.CreateAnalysisContainer(GameplayState.Score.Replay));
+ }
}
protected override void PrepareReplay()
From c95e85368fab71e8c38e10f3dcf809f47f0986e3 Mon Sep 17 00:00:00 2001
From: Sheepposu
Date: Fri, 23 Feb 2024 23:45:58 -0500
Subject: [PATCH 0063/1450] remove old files
---
.../TestSceneHitMarker.cs | 135 ------------
.../UI/HitMarkerContainer.cs | 195 ------------------
2 files changed, 330 deletions(-)
delete mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs
delete mode 100644 osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs
deleted file mode 100644
index 7ba681b50f..0000000000
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs
+++ /dev/null
@@ -1,135 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System;
-using System.Collections.Generic;
-using NUnit.Framework;
-using osu.Framework.Graphics;
-using osu.Framework.Input.Events;
-using osu.Framework.Input.States;
-using osu.Framework.Logging;
-using osu.Framework.Testing.Input;
-using osu.Game.Rulesets.Osu.UI;
-using osu.Game.Rulesets.UI;
-using osuTK;
-
-namespace osu.Game.Rulesets.Osu.Tests
-{
- public partial class TestSceneHitMarker : OsuSkinnableTestScene
- {
- [Test]
- public void TestHitMarkers()
- {
- var markerContainers = new List();
-
- AddStep("Create hit markers", () =>
- {
- markerContainers.Clear();
- SetContents(_ =>
- {
- markerContainers.Add(new TestHitMarkerContainer(new HitObjectContainer())
- {
- HitMarkerEnabled = { Value = true },
- AimMarkersEnabled = { Value = true },
- AimLinesEnabled = { Value = true },
- RelativeSizeAxes = Axes.Both
- });
-
- return new HitMarkerInputManager
- {
- RelativeSizeAxes = Axes.Both,
- Size = new Vector2(0.95f),
- Child = markerContainers[^1],
- };
- });
- });
-
- AddUntilStep("Until skinnable expires", () =>
- {
- if (markerContainers.Count == 0)
- return false;
-
- Logger.Log("How many: " + markerContainers.Count);
-
- foreach (var markerContainer in markerContainers)
- {
- if (markerContainer.Children.Count != 0)
- return false;
- }
-
- return true;
- });
- }
-
- private partial class HitMarkerInputManager : ManualInputManager
- {
- private double? startTime;
-
- public HitMarkerInputManager()
- {
- UseParentInput = false;
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- MoveMouseTo(ToScreenSpace(DrawSize / 2));
- }
-
- protected override void Update()
- {
- base.Update();
-
- const float spin_angle = 4 * MathF.PI;
-
- startTime ??= Time.Current;
-
- float fraction = (float)((Time.Current - startTime) / 5_000);
-
- float angle = fraction * spin_angle;
- float radius = fraction * Math.Min(DrawSize.X, DrawSize.Y) / 2;
-
- Vector2 pos = radius * new Vector2(MathF.Cos(angle), MathF.Sin(angle)) + DrawSize / 2;
- MoveMouseTo(ToScreenSpace(pos));
- }
- }
-
- private partial class TestHitMarkerContainer : HitMarkerContainer
- {
- private double? lastClick;
- private double? startTime;
- private bool finishedDrawing;
- private bool leftOrRight;
-
- public TestHitMarkerContainer(HitObjectContainer hitObjectContainer)
- : base(hitObjectContainer)
- {
- }
-
- protected override void Update()
- {
- base.Update();
-
- if (finishedDrawing)
- return;
-
- startTime ??= lastClick ??= Time.Current;
-
- if (startTime + 5_000 <= Time.Current)
- {
- finishedDrawing = true;
- HitMarkerEnabled.Value = AimMarkersEnabled.Value = AimLinesEnabled.Value = false;
- return;
- }
-
- if (lastClick + 400 <= Time.Current)
- {
- OnPressed(new KeyBindingPressEvent(new InputState(), leftOrRight ? OsuAction.LeftButton : OsuAction.RightButton));
- leftOrRight = !leftOrRight;
- lastClick = Time.Current;
- }
- }
- }
- }
-}
diff --git a/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs b/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs
deleted file mode 100644
index e8916ea545..0000000000
--- a/osu.Game.Rulesets.Osu/UI/HitMarkerContainer.cs
+++ /dev/null
@@ -1,195 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System;
-using osu.Framework.Allocation;
-using osu.Framework.Bindables;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Input;
-using osu.Framework.Input.Bindings;
-using osu.Framework.Input.Events;
-using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Rulesets.Osu.Objects.Drawables;
-using osu.Game.Rulesets.Osu.Skinning;
-using osu.Game.Rulesets.Osu.Skinning.Default;
-using osu.Game.Rulesets.UI;
-using osu.Game.Skinning;
-using osuTK;
-using osuTK.Graphics;
-
-namespace osu.Game.Rulesets.Osu.UI
-{
- public partial class HitMarkerContainer : Container, IRequireHighFrequencyMousePosition, IKeyBindingHandler
- {
- private Vector2 lastMousePosition;
- private Vector2? lastlastMousePosition;
- private double timePreempt;
-
- public Bindable HitMarkerEnabled = new BindableBool();
- public Bindable AimMarkersEnabled = new BindableBool();
- public Bindable AimLinesEnabled = new BindableBool();
-
- private const double default_time_preempt = 1000;
-
- private readonly HitObjectContainer hitObjectContainer;
-
- public override bool ReceivePositionalInputAt(Vector2 _) => true;
-
- public HitMarkerContainer(HitObjectContainer hitObjectContainer)
- {
- this.hitObjectContainer = hitObjectContainer;
- timePreempt = default_time_preempt;
- }
-
- public bool OnPressed(KeyBindingPressEvent e)
- {
- if (HitMarkerEnabled.Value && (e.Action == OsuAction.LeftButton || e.Action == OsuAction.RightButton))
- {
- updateTimePreempt();
- addMarker(e.Action);
- }
-
- return false;
- }
-
- public void OnReleased(KeyBindingReleaseEvent e)
- {
- }
-
- protected override bool OnMouseMove(MouseMoveEvent e)
- {
- lastlastMousePosition = lastMousePosition;
- lastMousePosition = e.MousePosition;
-
- if (AimMarkersEnabled.Value)
- {
- updateTimePreempt();
- addMarker(null);
- }
-
- if (AimLinesEnabled.Value && lastlastMousePosition != null && lastlastMousePosition != lastMousePosition)
- {
- if (!AimMarkersEnabled.Value)
- updateTimePreempt();
- Add(new AimLineDrawable((Vector2)lastlastMousePosition, lastMousePosition, timePreempt));
- }
-
- return base.OnMouseMove(e);
- }
-
- private void addMarker(OsuAction? action)
- {
- var component = OsuSkinComponents.AimMarker;
-
- switch (action)
- {
- case OsuAction.LeftButton:
- component = OsuSkinComponents.HitMarkerLeft;
- break;
-
- case OsuAction.RightButton:
- component = OsuSkinComponents.HitMarkerRight;
- break;
- }
-
- Add(new HitMarkerDrawable(action, component, timePreempt)
- {
- Position = lastMousePosition,
- Origin = Anchor.Centre,
- Depth = action == null ? float.MaxValue : float.MinValue
- });
- }
-
- private void updateTimePreempt()
- {
- var hitObject = getHitObject();
- if (hitObject == null)
- return;
-
- timePreempt = hitObject.TimePreempt;
- }
-
- private OsuHitObject? getHitObject()
- {
- foreach (var dho in hitObjectContainer.Objects)
- return (dho as DrawableOsuHitObject)?.HitObject;
-
- return null;
- }
-
- private partial class HitMarkerDrawable : SkinnableDrawable
- {
- private readonly double lifetimeDuration;
- private readonly double fadeOutTime;
-
- public override bool RemoveWhenNotAlive => true;
-
- public HitMarkerDrawable(OsuAction? action, OsuSkinComponents componenet, double timePreempt)
- : base(new OsuSkinComponentLookup(componenet), _ => new DefaultHitMarker(action))
- {
- fadeOutTime = timePreempt / 2;
- lifetimeDuration = timePreempt + fadeOutTime;
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- LifetimeStart = Time.Current;
- LifetimeEnd = LifetimeStart + lifetimeDuration;
-
- Scheduler.AddDelayed(() =>
- {
- this.FadeOut(fadeOutTime);
- }, lifetimeDuration - fadeOutTime);
- }
- }
-
- private partial class AimLineDrawable : CompositeDrawable
- {
- private readonly double lifetimeDuration;
- private readonly double fadeOutTime;
-
- public override bool RemoveWhenNotAlive => true;
-
- public AimLineDrawable(Vector2 fromP, Vector2 toP, double timePreempt)
- {
- fadeOutTime = timePreempt / 2;
- lifetimeDuration = timePreempt + fadeOutTime;
-
- float distance = Vector2.Distance(fromP, toP);
- Vector2 direction = (toP - fromP);
- InternalChild = new Box
- {
- Position = fromP + (direction / 2),
- Size = new Vector2(distance, 1),
- Rotation = (float)(Math.Atan(direction.Y / direction.X) * (180 / Math.PI)),
- Origin = Anchor.Centre
- };
- }
-
- [BackgroundDependencyLoader]
- private void load(ISkinSource skin)
- {
- var color = skin.GetConfig(OsuSkinColour.ReplayAimLine)?.Value ?? Color4.White;
- color.A = 127;
- InternalChild.Colour = color;
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- LifetimeStart = Time.Current;
- LifetimeEnd = LifetimeStart + lifetimeDuration;
-
- Scheduler.AddDelayed(() =>
- {
- this.FadeOut(fadeOutTime);
- }, lifetimeDuration - fadeOutTime);
- }
- }
- }
-}
From 8cdd9c9ddcec526dcf4866f2c0b0ce1b98dbffd4 Mon Sep 17 00:00:00 2001
From: Sheepposu
Date: Fri, 23 Feb 2024 23:46:50 -0500
Subject: [PATCH 0064/1450] skinning changes
improve default design
---
.../Skinning/Default/DefaultHitMarker.cs | 8 +++-----
osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs | 1 -
osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs | 6 +-----
3 files changed, 4 insertions(+), 11 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs
index 7dabb5182f..dc890d4d63 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs
@@ -23,7 +23,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(3, length),
- Rotation = 45,
Colour = Colour4.Black.Opacity(0.5F)
},
new Box
@@ -31,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(3, length),
- Rotation = 135,
+ Rotation = 90,
Colour = Colour4.Black.Opacity(0.5F)
}
};
@@ -44,7 +43,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(1, length),
- Rotation = 45,
Colour = colour
},
new Box
@@ -52,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(1, length),
- Rotation = 135,
+ Rotation = 90,
Colour = colour
}
});
@@ -69,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
return (Colour4.LightGreen, 20, true);
default:
- return (Colour4.Gray.Opacity(0.3F), 8, false);
+ return (Colour4.Gray.Opacity(0.5F), 8, false);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs
index 5c864fb6c2..24f9217a5f 100644
--- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs
@@ -10,6 +10,5 @@ namespace osu.Game.Rulesets.Osu.Skinning
SliderBall,
SpinnerBackground,
StarBreakAdditive,
- ReplayAimLine,
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs
index a637eddd05..68686f835d 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs
@@ -12,7 +12,6 @@ using osu.Framework.Graphics.Pooling;
using osu.Game.Replays;
using osu.Game.Rulesets.Objects.Pooling;
using osu.Game.Rulesets.Osu.Replays;
-using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Rulesets.Osu.Skinning.Default;
using osu.Game.Rulesets.UI;
using osu.Game.Skinning;
@@ -47,11 +46,8 @@ namespace osu.Game.Rulesets.Osu.UI
}
[BackgroundDependencyLoader]
- private void load(ISkinSource skin)
+ private void load()
{
- var aimLineColor = skin.GetConfig(OsuSkinColour.ReplayAimLine)?.Value ?? Color4.White;
- aimLineColor.A = 127;
-
hitMarkersContainer.Hide();
aimMarkersContainer.Hide();
aimLinesContainer.Hide();
From 822ecb71068b77e071fb590983ff2834b0f9b2e3 Mon Sep 17 00:00:00 2001
From: Sheepposu
Date: Fri, 23 Feb 2024 23:52:17 -0500
Subject: [PATCH 0065/1450] remove unnecessary changes
---
osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini | 5 +----
osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs | 6 ------
osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 +-
3 files changed, 2 insertions(+), 11 deletions(-)
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini
index 2952948f45..9d16267d73 100644
--- a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini
+++ b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini
@@ -1,7 +1,4 @@
[General]
Version: latest
HitCircleOverlayAboveNumber: 0
-HitCirclePrefix: display
-
-[Colours]
-ReplayAimLine: 0,0,255
\ No newline at end of file
+HitCirclePrefix: display
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs
index 68686f835d..c4b5135ca2 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs
@@ -117,7 +117,6 @@ namespace osu.Game.Rulesets.Osu.UI
{
lifetimeManager.EntryBecameAlive += entryBecameAlive;
lifetimeManager.EntryBecameDead += entryBecameDead;
- lifetimeManager.EntryCrossedBoundary += entryCrossedBoundary;
PathRadius = 1f;
Colour = new Color4(255, 255, 255, 127);
@@ -153,11 +152,6 @@ namespace osu.Game.Rulesets.Osu.UI
}
}
- private void entryCrossedBoundary(LifetimeEntry entry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction)
- {
-
- }
-
private sealed class AimLinePointComparator : IComparer
{
public int Compare(AimPointEntry? x, AimPointEntry? y)
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index ea336e6067..a801e3d2f7 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.UI
judgementLayer = new JudgementContainer { RelativeSizeAxes = Axes.Both },
HitObjectContainer,
judgementAboveHitObjectLayer = new Container { RelativeSizeAxes = Axes.Both },
- approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both }
+ approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both },
};
HitPolicy = new StartTimeOrderedHitPolicy();
From 29e5f409bac928bc49e1940a2e44b8ee4b8f2a70 Mon Sep 17 00:00:00 2001
From: Sheepposu
Date: Tue, 27 Feb 2024 21:51:54 -0500
Subject: [PATCH 0066/1450] remove skinnable
better to add skinnable elements later
---
.../Resources/special-skin/aimmarker@2x.png | Bin 648 -> 0 bytes
.../special-skin/hitmarker-left@2x.png | Bin 2127 -> 0 bytes
.../special-skin/hitmarker-right@2x.png | Bin 1742 -> 0 bytes
osu.Game.Rulesets.Osu/OsuSkinComponents.cs | 3 -
.../Skinning/Default/DefaultHitMarker.cs | 74 ------------------
.../Skinning/Default/HitMarker.cs | 68 ++++++++++++----
.../Legacy/OsuLegacySkinTransformer.cs | 20 +----
.../UI/OsuAnalysisContainer.cs | 69 ++++++++--------
8 files changed, 88 insertions(+), 146 deletions(-)
delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/aimmarker@2x.png
delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-left@2x.png
delete mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-right@2x.png
delete mode 100644 osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/aimmarker@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/aimmarker@2x.png
deleted file mode 100644
index 0b2a554193f08f7984568980956a3db8a6b39800..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 648
zcmV;30(bq1P)EX>4Tx04R}tkv&MmKpe$iQ>7vm2a6PO$WR@`E-K4rtTK|H%@
z>74h8L#!+*#OK7523?T&k?XR{Z=6dG3p_JqWYhD+A!4!A#c~(3vY`^s5JwbMqkJLf
zvch?bvs$gQ_C5Ivg9U9R!*!aYNMH#`q#!~@9TikzAxf)8iitGs$36Tbjz2{%nOqex
zax9<*6_Voz|AXJ%n#JiUHz^ngdS7h&V+;uF0jdyW16NwdUuyz$pQJZB
zTI2{A+y*YLJDR))TI00006VoOIv00000008+zyMF)x010qNS#tmY
zE+YT{E+YYWr9XB6000McNliru=mHiD95ZRmtwR6+02y>eSad^gZEa<4bO1wgWnpw>
zWFU8GbZ8()Nlj2!fese{003Y~L_t&-(~Xd^4Zt7_1kYC1UL}8=Py}}=u;}y?!uSxX)0000EX>4Tx04R}tkv&MmKpe$iQ?)8p2Rn#3WT;NoK}8%(6^me@v=v%)FuC+YXws0R
zxHt-~1qVMCs}3&Cx;nTDg5U>;vxAeOiUKFoAxFnR+6_CX>@2HM@dakSAh-}000IiNkls_o@u5n{w3H!5jp#I_s}mSL6nvbfkWc#_vuLYo^#J%=bYz0&vPDu>`?~P
z0Nc!V3E&1C=JO;l4U7YWzyQ!=wjUbdA^U#|Xawp!3a-6gDOj^!(!4I&S*VDQsaC;d
zkpu@oS~HUIlo5?2^x2VUX1*t+Nq-yB9v@8*1=@kX09PKhkXqoU8}iuo%6H`9I*-*=
zs7QtWsq|QHIFq^%*3_$0#@g!%TuB`Tz#)<-Rfv`s2s4$%QoO2IwpJ8aHbxR!qW7trJguJ@1?U45bE-`_uOK{-b-%3G@PfU<~*e
zh?{*?U>&d#r~s;f+WnivkpmT$8`bNrWoNG~1b*F}+HfgG2XGX)$`bIrTV}Ye*3uC>
zq=ruJv2<)!2?D2qeSqx}JNi2t
zZQa#wVF)+@__FvNA8?}DEev%w+PVYHj{b&HVE||~=kU;$=+xmIQrE;mC2+(ioi{B_
z14~B(&~wBmou4?U1PDHZc=jd~eOHCfJ4>$%CvGf!H$C^B1-{^CW
zORYQQPIC*FJ;-)C)w)yeyz;EQuf9aM2(<9%X{j#}E?#-o-e-zAb+2tE-D7|^5ATq?
zKPt;x^IFGEMO1bn`Y0b*rGQ)vpN56TnkFjz%cEl&04>UXaP19WdWiP+eR%_|#
zY_xTo)~RR(2`K`4IvZ`>ZMBvT;GKKA9b5Amiycx!=6|Arl}AIhTNsKRQbSww88cm_
z&$%e?{q<>UQ8Hr~O=r?UpqZ7)iIaOQk2_>R_~GAElfeGZc(EJun2f*VoHpGKA1fE%
zW|d(4CFk^pJSI&K{I>Z$^s8!FUC@l#qnEW&;P)$7NN6_2le@kqBsYFlnE6LgSAh=E
zd{|fKvAT}?({|u}RzB|^_owZ39;*weyX}g26oT_FIwQc`1A4KK8XGV-|DrSEQ3wKM
zB2cr}D+T>i=`k~&xS0b&ZUX20Q|Yn2UMUFFh`_d*^^(>b&ZNwsC|Bt14QEm{>m?1?
zCIV$%m+ZU{)>JdH%N6_=!kX%J$xfh521*JQQMx*17-o2yD~w&8GS(IB2Y0l0XL0O!
zQb(~!i_VG2DnSO4Y0bbPVkC7`;L~hoUhdplS)RM<5J{vpdqa)piM1;R`uq0a*2A}}}-&CFL8OK}!6
zfVo0NWw8_=iDu@3K@k|}329PQv20~AjhQP{RTazDo{%Q7nAu-FPUGNcf@mb6MfPtJ
zM}Ybq5K_N?lQfP9prGz^zLl@R8mKqoXdRswQBoUra#pR{83{q(7nteTADoK?pG`A`9D8*
zL+g+7N8rOR69RB?D8@HbQD5`&4x1ww%{(zYhq8?E{44b(!oD|l*)8v0UWm1Qq+bAj
zbN5U4x*zH93LD?uTB69FHx-%Cyv%2>X8fJ)3^^T6(_aU)m*Br_F
z6_Nx3XU28k6Kk-%u-)ePB(8b=QDY16?Y-
z$_egQ2*3<*<;=LgJzv(Xzwp`DthMUSb0Oo$z$d`aQkhub=n{^MKyPm^JPw7#IhEi{TDHR{#GTxr2YT^002ovPDHLk
FV1jr`?veli
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-right@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitmarker-right@2x.png
deleted file mode 100644
index 66be9a80ee2e2ae4c6dc6915d2b7ca5cf8ab589f..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 1742
zcmV;<1~K`GP)EX>4Tx04R}tkv&MmKpe$iQ?)8p2Rn#3WT;NoK}8%(6^me@v=v%)FuC+YXws0R
zxHt-~1qVMCs}3&Cx;nTDg5U>;vxAeOiUKFoAxFnR+6_CX>@2HM@dakSAh-}000D~Nkl1L9Yb|fH_ftE7RjjO?hFdLVhHsD6;Mi3NQZON`8V+<3BQA3EPF^Lyy
zBFH5ssJYD~PVPNjt#956lhOc~_xg%spW#euk$w}6}R9gK=vhuEEC9o1$c~`Rf
z(Ry2wlf>QYeKL21@Rg4>x`?ys*smS!IAyn7e8E7rV7}_2!Kl|YJi?#x1%84Syp!In
zkY2Z>ZC|ASeH8V&R&~)}wqQQKV8CuUoJ!|IpZh27F_`+c=GVn>f!toVMTXssZ0rPq-2F
z7)4*YhF6Y6=fW|AnK={TxGQO->3NOv?ZHqu?n-9PL^x&;=hC%on8f+eXCO0UEIMoi
zC$QrUyo)<T*3ZV=R?f-~Sw)q8_7}ITNXzj(Ynz8XY#0nKO|Ffd3lJ+SQw?
zo^T_5u}|1KI1!G!Qa#~D8k*bxlSMfdpVSn!s$M%F!q0GjixAm?KUED#k}2O6e=Ub!
zzT9@~mdieLFWo14Y(4bY?{@Z~d#M9B__8N*HojnoWl8a{S^UD*#Oe5q57df^KXo6y
zFS^)9_p=4_sqNTj>tdf)v)O`qjqaxoT%0!kCVZ*Rs)f?;&EU6Nn8-Z~eiR+BtjUAq
zuj+95Y2#Stj`7sR9`G*v)Lv2)QZ=X0g)O!$}Y)kHjB1^&ZygL
zaa;~xW2Fp;tl;b7xLnk27M%fmLZ@PB*b@vZ9}Jg6605gAXe
zstYZ)p{)uZh6ZtROM+Y(5y>UDycZfo_&zMvtXfj*ar#}oX-JZ!IZ2XB>92W{iM*rM
zPniAbKjTyQRE^sNDlJm64K~qIM5Tc?-B3Fj<2yzt(TD^gFHQ4B!vebI{%dk!P-X
kiyU)`Hc@tO_2Ah*0YN2jCf7K0Z2$lO07*qoM6N<$f~iM7hyVZp
diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
index 75db18d345..52fdfea95f 100644
--- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
+++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
@@ -22,8 +22,5 @@ namespace osu.Game.Rulesets.Osu
SpinnerBody,
CursorSmoke,
ApproachCircle,
- HitMarkerLeft,
- HitMarkerRight,
- AimMarker
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs
deleted file mode 100644
index dc890d4d63..0000000000
--- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultHitMarker.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osuTK;
-
-namespace osu.Game.Rulesets.Osu.Skinning.Default
-{
- public partial class DefaultHitMarker : CompositeDrawable
- {
- public DefaultHitMarker(OsuAction? action)
- {
- var (colour, length, hasBorder) = getConfig(action);
-
- if (hasBorder)
- {
- InternalChildren = new Drawable[]
- {
- new Box
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Size = new Vector2(3, length),
- Colour = Colour4.Black.Opacity(0.5F)
- },
- new Box
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Size = new Vector2(3, length),
- Rotation = 90,
- Colour = Colour4.Black.Opacity(0.5F)
- }
- };
- }
-
- AddRangeInternal(new Drawable[]
- {
- new Box
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Size = new Vector2(1, length),
- Colour = colour
- },
- new Box
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Size = new Vector2(1, length),
- Rotation = 90,
- Colour = colour
- }
- });
- }
-
- private (Colour4 colour, float length, bool hasBorder) getConfig(OsuAction? action)
- {
- switch (action)
- {
- case OsuAction.LeftButton:
- return (Colour4.Orange, 20, true);
-
- case OsuAction.RightButton:
- return (Colour4.LightGreen, 20, true);
-
- default:
- return (Colour4.Gray.Opacity(0.5F), 8, false);
- }
- }
- }
-}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs
index 28877345d0..fe662470bc 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/HitMarker.cs
@@ -1,37 +1,73 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework.Allocation;
-using osu.Framework.Graphics.Sprites;
-using osu.Game.Skinning;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osuTK;
namespace osu.Game.Rulesets.Osu.Skinning.Default
{
- public partial class HitMarker : Sprite
+ public partial class HitMarker : CompositeDrawable
{
- private readonly OsuAction? action;
-
- public HitMarker(OsuAction? action = null)
+ public HitMarker(OsuAction? action)
{
- this.action = action;
+ var (colour, length, hasBorder) = getConfig(action);
+
+ if (hasBorder)
+ {
+ InternalChildren = new Drawable[]
+ {
+ new Box
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(3, length),
+ Colour = Colour4.Black.Opacity(0.5F)
+ },
+ new Box
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(3, length),
+ Rotation = 90,
+ Colour = Colour4.Black.Opacity(0.5F)
+ }
+ };
+ }
+
+ AddRangeInternal(new Drawable[]
+ {
+ new Box
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(1, length),
+ Colour = colour
+ },
+ new Box
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(1, length),
+ Rotation = 90,
+ Colour = colour
+ }
+ });
}
- [BackgroundDependencyLoader]
- private void load(ISkinSource skin)
+ private (Colour4 colour, float length, bool hasBorder) getConfig(OsuAction? action)
{
switch (action)
{
case OsuAction.LeftButton:
- Texture = skin.GetTexture(@"hitmarker-left");
- break;
+ return (Colour4.Orange, 20, true);
case OsuAction.RightButton:
- Texture = skin.GetTexture(@"hitmarker-right");
- break;
+ return (Colour4.LightGreen, 20, true);
default:
- Texture = skin.GetTexture(@"aimmarker");
- break;
+ return (Colour4.Gray.Opacity(0.5F), 8, false);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
index 61f9eebd86..d2ebc68c52 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
@@ -6,7 +6,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Skinning;
-using osu.Game.Rulesets.Osu.Skinning.Default;
using osuTK;
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
@@ -169,23 +168,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
return null;
- case OsuSkinComponents.HitMarkerLeft:
- if (GetTexture(@"hitmarker-left") != null)
- return new HitMarker(OsuAction.LeftButton);
-
- return null;
-
- case OsuSkinComponents.HitMarkerRight:
- if (GetTexture(@"hitmarker-right") != null)
- return new HitMarker(OsuAction.RightButton);
-
- return null;
-
- case OsuSkinComponents.AimMarker:
- if (GetTexture(@"aimmarker") != null)
- return new HitMarker();
-
- return null;
+ default:
+ throw new UnsupportedSkinComponentException(lookup);
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs
index c4b5135ca2..7d8ae6980c 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisContainer.cs
@@ -14,7 +14,6 @@ using osu.Game.Rulesets.Objects.Pooling;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Osu.Skinning.Default;
using osu.Game.Rulesets.UI;
-using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
@@ -26,40 +25,41 @@ namespace osu.Game.Rulesets.Osu.UI
public Bindable AimMarkersEnabled = new BindableBool();
public Bindable AimLinesEnabled = new BindableBool();
- private HitMarkersContainer hitMarkersContainer;
- private AimMarkersContainer aimMarkersContainer;
- private AimLinesContainer aimLinesContainer;
+ protected HitMarkersContainer HitMarkers;
+ protected AimMarkersContainer AimMarkers;
+ protected AimLinesContainer AimLines;
public OsuAnalysisContainer(Replay replay)
: base(replay)
{
InternalChildren = new Drawable[]
{
- hitMarkersContainer = new HitMarkersContainer(),
- aimMarkersContainer = new AimMarkersContainer() { Depth = float.MinValue },
- aimLinesContainer = new AimLinesContainer() { Depth = float.MaxValue }
+ HitMarkers = new HitMarkersContainer(),
+ AimMarkers = new AimMarkersContainer { Depth = float.MinValue },
+ AimLines = new AimLinesContainer { Depth = float.MaxValue }
};
- HitMarkerEnabled.ValueChanged += e => hitMarkersContainer.FadeTo(e.NewValue ? 1 : 0);
- AimMarkersEnabled.ValueChanged += e => aimMarkersContainer.FadeTo(e.NewValue ? 1 : 0);
- AimLinesEnabled.ValueChanged += e => aimLinesContainer.FadeTo(e.NewValue ? 1 : 0);
+ HitMarkerEnabled.ValueChanged += e => HitMarkers.FadeTo(e.NewValue ? 1 : 0);
+ AimMarkersEnabled.ValueChanged += e => AimMarkers.FadeTo(e.NewValue ? 1 : 0);
+ AimLinesEnabled.ValueChanged += e => AimLines.FadeTo(e.NewValue ? 1 : 0);
}
[BackgroundDependencyLoader]
private void load()
{
- hitMarkersContainer.Hide();
- aimMarkersContainer.Hide();
- aimLinesContainer.Hide();
+ HitMarkers.Hide();
+ AimMarkers.Hide();
+ AimLines.Hide();
bool leftHeld = false;
bool rightHeld = false;
+
foreach (var frame in Replay.Frames)
{
var osuFrame = (OsuReplayFrame)frame;
- aimMarkersContainer.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position));
- aimLinesContainer.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position));
+ AimMarkers.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position));
+ AimLines.Add(new AimPointEntry(osuFrame.Time, osuFrame.Position));
bool leftButton = osuFrame.Actions.Contains(OsuAction.LeftButton);
bool rightButton = osuFrame.Actions.Contains(OsuAction.RightButton);
@@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.UI
leftHeld = false;
else if (!leftHeld && leftButton)
{
- hitMarkersContainer.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, true));
+ HitMarkers.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, true));
leftHeld = true;
}
@@ -76,42 +76,42 @@ namespace osu.Game.Rulesets.Osu.UI
rightHeld = false;
else if (!rightHeld && rightButton)
{
- hitMarkersContainer.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, false));
+ HitMarkers.Add(new HitMarkerEntry(osuFrame.Time, osuFrame.Position, false));
rightHeld = true;
}
}
}
- private partial class HitMarkersContainer : PooledDrawableWithLifetimeContainer
+ protected partial class HitMarkersContainer : PooledDrawableWithLifetimeContainer
{
private readonly HitMarkerPool leftPool;
private readonly HitMarkerPool rightPool;
public HitMarkersContainer()
{
- AddInternal(leftPool = new HitMarkerPool(OsuSkinComponents.HitMarkerLeft, OsuAction.LeftButton, 15));
- AddInternal(rightPool = new HitMarkerPool(OsuSkinComponents.HitMarkerRight, OsuAction.RightButton, 15));
+ AddInternal(leftPool = new HitMarkerPool(OsuAction.LeftButton, 15));
+ AddInternal(rightPool = new HitMarkerPool(OsuAction.RightButton, 15));
}
protected override HitMarkerDrawable GetDrawable(HitMarkerEntry entry) => (entry.IsLeftMarker ? leftPool : rightPool).Get(d => d.Apply(entry));
}
- private partial class AimMarkersContainer : PooledDrawableWithLifetimeContainer
+ protected partial class AimMarkersContainer : PooledDrawableWithLifetimeContainer
{
private readonly HitMarkerPool pool;
public AimMarkersContainer()
{
- AddInternal(pool = new HitMarkerPool(OsuSkinComponents.AimMarker, null, 80));
+ AddInternal(pool = new HitMarkerPool(null, 80));
}
protected override HitMarkerDrawable GetDrawable(AimPointEntry entry) => pool.Get(d => d.Apply(entry));
}
- private partial class AimLinesContainer : Path
+ protected partial class AimLinesContainer : Path
{
- private LifetimeEntryManager lifetimeManager = new LifetimeEntryManager();
- private SortedSet aliveEntries = new SortedSet(new AimLinePointComparator());
+ private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager();
+ private readonly SortedSet aliveEntries = new SortedSet(new AimLinePointComparator());
public AimLinesContainer()
{
@@ -146,6 +146,7 @@ namespace osu.Game.Rulesets.Osu.UI
private void updateVertices()
{
ClearVertices();
+
foreach (var entry in aliveEntries)
{
AddVertex(entry.Position);
@@ -164,7 +165,7 @@ namespace osu.Game.Rulesets.Osu.UI
}
}
- private partial class HitMarkerDrawable : PoolableDrawableWithLifetime
+ protected partial class HitMarkerDrawable : PoolableDrawableWithLifetime
{
///
/// This constructor only exists to meet the new() type constraint of .
@@ -173,10 +174,10 @@ namespace osu.Game.Rulesets.Osu.UI
{
}
- public HitMarkerDrawable(OsuSkinComponents component, OsuAction? action)
+ public HitMarkerDrawable(OsuAction? action)
{
Origin = Anchor.Centre;
- InternalChild = new SkinnableDrawable(new OsuSkinComponentLookup(component), _ => new DefaultHitMarker(action));
+ InternalChild = new HitMarker(action);
}
protected override void OnApply(AimPointEntry entry)
@@ -191,22 +192,20 @@ namespace osu.Game.Rulesets.Osu.UI
}
}
- private partial class HitMarkerPool : DrawablePool
+ protected partial class HitMarkerPool : DrawablePool
{
- private readonly OsuSkinComponents component;
private readonly OsuAction? action;
- public HitMarkerPool(OsuSkinComponents component, OsuAction? action, int initialSize)
+ public HitMarkerPool(OsuAction? action, int initialSize)
: base(initialSize)
{
- this.component = component;
this.action = action;
}
- protected override HitMarkerDrawable CreateNewDrawable() => new HitMarkerDrawable(component, action);
+ protected override HitMarkerDrawable CreateNewDrawable() => new HitMarkerDrawable(action);
}
- private partial class AimPointEntry : LifetimeEntry
+ protected partial class AimPointEntry : LifetimeEntry
{
public Vector2 Position { get; }
@@ -218,7 +217,7 @@ namespace osu.Game.Rulesets.Osu.UI
}
}
- private partial class HitMarkerEntry : AimPointEntry
+ protected partial class HitMarkerEntry : AimPointEntry
{
public bool IsLeftMarker { get; }
From cefc8357bbdb732f805e848b065afcf40619b9c0 Mon Sep 17 00:00:00 2001
From: Sheepposu
Date: Tue, 27 Feb 2024 21:52:10 -0500
Subject: [PATCH 0067/1450] test scene for OsuAnalysisContainer
---
.../TestSceneHitMarker.cs | 91 +++++++++++++++++++
1 file changed, 91 insertions(+)
create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs
new file mode 100644
index 0000000000..e4c48f96b8
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitMarker.cs
@@ -0,0 +1,91 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+#nullable disable
+
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Game.Replays;
+using osu.Game.Rulesets.Osu.Replays;
+using osu.Game.Rulesets.Osu.UI;
+using osu.Game.Rulesets.Replays;
+using osu.Game.Tests.Visual;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public partial class TestSceneHitMarker : OsuTestScene
+ {
+ private TestOsuAnalysisContainer analysisContainer;
+
+ [Test]
+ public void TestHitMarkers()
+ {
+ createAnalysisContainer();
+ AddStep("enable hit markers", () => analysisContainer.HitMarkerEnabled.Value = true);
+ AddAssert("hit markers visible", () => analysisContainer.HitMarkersVisible);
+ AddStep("disable hit markers", () => analysisContainer.HitMarkerEnabled.Value = false);
+ AddAssert("hit markers not visible", () => !analysisContainer.HitMarkersVisible);
+ }
+
+ [Test]
+ public void TestAimMarker()
+ {
+ createAnalysisContainer();
+ AddStep("enable aim markers", () => analysisContainer.AimMarkersEnabled.Value = true);
+ AddAssert("aim markers visible", () => analysisContainer.AimMarkersVisible);
+ AddStep("disable aim markers", () => analysisContainer.AimMarkersEnabled.Value = false);
+ AddAssert("aim markers not visible", () => !analysisContainer.AimMarkersVisible);
+ }
+
+ [Test]
+ public void TestAimLines()
+ {
+ createAnalysisContainer();
+ AddStep("enable aim lines", () => analysisContainer.AimLinesEnabled.Value = true);
+ AddAssert("aim lines visible", () => analysisContainer.AimLinesVisible);
+ AddStep("disable aim lines", () => analysisContainer.AimLinesEnabled.Value = false);
+ AddAssert("aim lines not visible", () => !analysisContainer.AimLinesVisible);
+ }
+
+ private void createAnalysisContainer()
+ {
+ AddStep("create new analysis container", () => Child = analysisContainer = new TestOsuAnalysisContainer(fabricateReplay()));
+ }
+
+ private Replay fabricateReplay()
+ {
+ var frames = new List();
+
+ for (int i = 0; i < 50; i++)
+ {
+ frames.Add(new OsuReplayFrame
+ {
+ Time = Time.Current + i * 15,
+ Position = new Vector2(20 + i * 10, 20),
+ Actions =
+ {
+ i % 2 == 0 ? OsuAction.LeftButton : OsuAction.RightButton
+ }
+ });
+ }
+
+ return new Replay { Frames = frames };
+ }
+
+ private partial class TestOsuAnalysisContainer : OsuAnalysisContainer
+ {
+ public TestOsuAnalysisContainer(Replay replay)
+ : base(replay)
+ {
+ }
+
+ public bool HitMarkersVisible => HitMarkers.Alpha > 0 && HitMarkers.Entries.Any();
+
+ public bool AimMarkersVisible => AimMarkers.Alpha > 0 && AimMarkers.Entries.Any();
+
+ public bool AimLinesVisible => AimLines.Alpha > 0 && AimLines.Vertices.Count > 1;
+ }
+ }
+}
From 7687ab63edd2b49b4e1a5a2c30be2b9e2b8ca328 Mon Sep 17 00:00:00 2001
From: Sheepposu
Date: Tue, 27 Feb 2024 22:03:45 -0500
Subject: [PATCH 0068/1450] fix code formatting
---
osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs | 3 ++-
osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 1 -
osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs | 2 +-
osu.Game/Screens/Play/ReplayPlayer.cs | 1 +
4 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
index fd9cb67995..b3d5231ade 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuAnalysisSettings.cs
@@ -16,12 +16,13 @@ namespace osu.Game.Rulesets.Osu.UI
private readonly PlayerCheckbox hitMarkerToggle;
private readonly PlayerCheckbox aimMarkerToggle;
- private readonly PlayerCheckbox hideCursorToggle;
private readonly PlayerCheckbox aimLinesToggle;
public OsuAnalysisSettings(DrawableRuleset drawableRuleset)
: base(drawableRuleset)
{
+ PlayerCheckbox hideCursorToggle;
+
Children = new Drawable[]
{
hitMarkerToggle = new PlayerCheckbox { LabelText = PlayerSettingsOverlayStrings.HitMarkers },
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index a801e3d2f7..411a02c5af 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -36,7 +36,6 @@ namespace osu.Game.Rulesets.Osu.UI
private readonly JudgementPooler judgementPooler;
public SmokeContainer Smoke { get; }
-
public FollowPointRenderer FollowPoints { get; }
public static readonly Vector2 BASE_SIZE = new Vector2(512, 384);
diff --git a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs
index 3752b2b900..91531b28b4 100644
--- a/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs
+++ b/osu.Game/Screens/Play/PlayerSettings/AnalysisSettings.cs
@@ -10,7 +10,7 @@ namespace osu.Game.Screens.Play.PlayerSettings
{
protected DrawableRuleset DrawableRuleset;
- public AnalysisSettings(DrawableRuleset drawableRuleset)
+ protected AnalysisSettings(DrawableRuleset drawableRuleset)
: base("Analysis Settings")
{
DrawableRuleset = drawableRuleset;
diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs
index ce6cb5124a..81cc44d889 100644
--- a/osu.Game/Screens/Play/ReplayPlayer.cs
+++ b/osu.Game/Screens/Play/ReplayPlayer.cs
@@ -73,6 +73,7 @@ namespace osu.Game.Screens.Play
HUDOverlay.PlayerSettingsOverlay.AddAtStart(playbackSettings);
var analysisSettings = DrawableRuleset.Ruleset.CreateAnalysisSettings(DrawableRuleset);
+
if (analysisSettings != null)
{
HUDOverlay.PlayerSettingsOverlay.AddAtStart(analysisSettings);
From 9f5f6b5d37405787a4308db875331060f54572b4 Mon Sep 17 00:00:00 2001
From: TextAdventurer12
Date: Sat, 6 Apr 2024 21:39:27 +1300
Subject: [PATCH 0069/1450] 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 0070/1450] 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 0071/1450] 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 0072/1450] 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 0073/1450] 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 0074/1450] 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 0075/1450] Use correct operation for 0 difficulty case
---
osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
index af97d90e53..ec132237db 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
@@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
///
public double CountDifficultStrains()
{
- if (double.IsNaN(Difficulty))
+ if (Difficulty == 0)
return 0.0;
double consistentTopStrain = Difficulty / 10; // What would the top strain be if all strain values were identical
From 203e9284eb5eb54d464e7a290e106b605b7cf66e Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Tue, 28 May 2024 19:01:53 +0200
Subject: [PATCH 0076/1450] End circle only gets brighter once shift is held
---
.../Sliders/Components/SliderTailPiece.cs | 29 ++++++++++++++++++-
1 file changed, 28 insertions(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
index 5cf9346f2e..dc57d6d7ca 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
@@ -61,11 +61,38 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
updateCirclePieceColour();
}
+ protected override bool OnKeyDown(KeyDownEvent e)
+ {
+ if (e.Repeat)
+ return false;
+
+ handleDragToggle(e);
+ return base.OnKeyDown(e);
+ }
+
+ protected override void OnKeyUp(KeyUpEvent e)
+ {
+ handleDragToggle(e);
+ base.OnKeyUp(e);
+ }
+
+ private bool lastShiftPressed;
+
+ private void handleDragToggle(KeyboardEvent key)
+ {
+ bool shiftPressed = key.ShiftPressed;
+
+ if (shiftPressed == lastShiftPressed) return;
+
+ lastShiftPressed = shiftPressed;
+ updateCirclePieceColour();
+ }
+
private void updateCirclePieceColour()
{
Color4 colour = colours.Yellow;
- if (IsHovered)
+ if (IsHovered && inputManager.CurrentState.Keyboard.ShiftPressed)
colour = colour.Lighten(1);
CirclePiece.Colour = colour;
From d2c86b0813d41ddd01ce4cbc00906f94271bfbf2 Mon Sep 17 00:00:00 2001
From: Dean Herbert
Date: Wed, 29 May 2024 21:32:52 +0900
Subject: [PATCH 0077/1450] Avoid passing beatmap in from editor when it's
already present
---
osu.Game/Screens/Edit/Editor.cs | 2 +-
.../Screens/Edit/GameplayTest/EditorPlayer.cs | 32 +++++++++++--------
.../Edit/GameplayTest/EditorPlayerLoader.cs | 5 ++-
3 files changed, 21 insertions(+), 18 deletions(-)
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index 2954d7dcaa..07c32983f5 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -452,7 +452,7 @@ namespace osu.Game.Screens.Edit
pushEditorPlayer();
}
- void pushEditorPlayer() => this.Push(new EditorPlayerLoader(this, playableBeatmap));
+ void pushEditorPlayer() => this.Push(new EditorPlayerLoader(this));
}
///
diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs
index 42eb57c253..c327ae185d 100644
--- a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs
+++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs
@@ -21,19 +21,17 @@ namespace osu.Game.Screens.Edit.GameplayTest
{
private readonly Editor editor;
private readonly EditorState editorState;
- private readonly IBeatmap playableBeatmap;
protected override UserActivity InitialActivity => new UserActivity.TestingBeatmap(Beatmap.Value.BeatmapInfo);
[Resolved]
private MusicController musicController { get; set; } = null!;
- public EditorPlayer(Editor editor, IBeatmap playableBeatmap)
+ public EditorPlayer(Editor editor)
: base(new PlayerConfiguration { ShowResults = false })
{
this.editor = editor;
editorState = editor.GetState();
- this.playableBeatmap = playableBeatmap;
}
protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart)
@@ -52,17 +50,7 @@ namespace osu.Game.Screens.Edit.GameplayTest
{
base.LoadComplete();
- var frame = new ReplayFrame { Header = new FrameHeader(new ScoreInfo(), new ScoreProcessorStatistics()) };
-
- foreach (var hitObject in enumerateHitObjects(playableBeatmap.HitObjects.Where(h => h.StartTime < editorState.Time)))
- {
- var judgement = hitObject.CreateJudgement();
- frame.Header.Statistics.TryAdd(judgement.MaxResult, 0);
- frame.Header.Statistics[judgement.MaxResult]++;
- }
-
- HealthProcessor.ResetFromReplayFrame(frame);
- ScoreProcessor.ResetFromReplayFrame(frame);
+ markPreviousObjectsHit();
ScoreProcessor.HasCompleted.BindValueChanged(completed =>
{
@@ -75,6 +63,22 @@ namespace osu.Game.Screens.Edit.GameplayTest
}, RESULTS_DISPLAY_DELAY);
}
});
+ }
+
+ private void markPreviousObjectsHit()
+ {
+ var frame = new ReplayFrame { Header = new FrameHeader(new ScoreInfo(), new ScoreProcessorStatistics()) };
+
+ foreach (var hitObject in enumerateHitObjects(DrawableRuleset.Objects.Where(h => h.StartTime < editorState.Time)))
+ {
+ var judgement = hitObject.CreateJudgement();
+
+ frame.Header.Statistics.TryAdd(judgement.MaxResult, 0);
+ frame.Header.Statistics[judgement.MaxResult]++;
+ }
+
+ HealthProcessor.ResetFromReplayFrame(frame);
+ ScoreProcessor.ResetFromReplayFrame(frame);
static IEnumerable enumerateHitObjects(IEnumerable hitObjects)
{
diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs
index c62b8cafb8..bb151e4a45 100644
--- a/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs
+++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs
@@ -4,7 +4,6 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Screens;
-using osu.Game.Beatmaps;
using osu.Game.Screens.Menu;
using osu.Game.Screens.Play;
@@ -15,8 +14,8 @@ namespace osu.Game.Screens.Edit.GameplayTest
[Resolved]
private OsuLogo osuLogo { get; set; } = null!;
- public EditorPlayerLoader(Editor editor, IBeatmap playableBeatmap)
- : base(() => new EditorPlayer(editor, playableBeatmap))
+ public EditorPlayerLoader(Editor editor)
+ : base(() => new EditorPlayer(editor))
{
}
From 3b5b7b2f880152dcd5d4bc2e99fc61e79830a6f1 Mon Sep 17 00:00:00 2001
From: Dean Herbert
Date: Wed, 29 May 2024 22:57:17 +0900
Subject: [PATCH 0078/1450] Fix the majority of cases where gameplay stil
doesn't end due to judgement count mismatch
---
osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs
index c327ae185d..836f718f81 100644
--- a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs
+++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs
@@ -69,9 +69,9 @@ namespace osu.Game.Screens.Edit.GameplayTest
{
var frame = new ReplayFrame { Header = new FrameHeader(new ScoreInfo(), new ScoreProcessorStatistics()) };
- foreach (var hitObject in enumerateHitObjects(DrawableRuleset.Objects.Where(h => h.StartTime < editorState.Time)))
+ foreach (var hitObject in enumerateHitObjects(DrawableRuleset.Objects, editorState.Time))
{
- var judgement = hitObject.CreateJudgement();
+ var judgement = hitObject.Judgement;
frame.Header.Statistics.TryAdd(judgement.MaxResult, 0);
frame.Header.Statistics[judgement.MaxResult]++;
@@ -80,11 +80,11 @@ namespace osu.Game.Screens.Edit.GameplayTest
HealthProcessor.ResetFromReplayFrame(frame);
ScoreProcessor.ResetFromReplayFrame(frame);
- static IEnumerable enumerateHitObjects(IEnumerable hitObjects)
+ static IEnumerable enumerateHitObjects(IEnumerable hitObjects, double cutoffTime)
{
- foreach (var hitObject in hitObjects)
+ foreach (var hitObject in hitObjects.Where(h => h.GetEndTime() < cutoffTime))
{
- foreach (var nested in enumerateHitObjects(hitObject.NestedHitObjects))
+ foreach (var nested in enumerateHitObjects(hitObject.NestedHitObjects, cutoffTime))
yield return nested;
yield return hitObject;
From 126837fadd02156990f479d7b02f161cb1e053cd Mon Sep 17 00:00:00 2001
From: Dean Herbert
Date: Wed, 29 May 2024 23:28:37 +0900
Subject: [PATCH 0079/1450] Apply results rather than fake a replay frame
---
osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs | 14 +++-----------
1 file changed, 3 insertions(+), 11 deletions(-)
diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs
index 836f718f81..7d637fcb09 100644
--- a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs
+++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs
@@ -6,12 +6,9 @@ using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
-using osu.Game.Online.Spectator;
using osu.Game.Overlays;
+using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Replays;
-using osu.Game.Rulesets.Scoring;
-using osu.Game.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Users;
@@ -67,19 +64,14 @@ namespace osu.Game.Screens.Edit.GameplayTest
private void markPreviousObjectsHit()
{
- var frame = new ReplayFrame { Header = new FrameHeader(new ScoreInfo(), new ScoreProcessorStatistics()) };
-
foreach (var hitObject in enumerateHitObjects(DrawableRuleset.Objects, editorState.Time))
{
var judgement = hitObject.Judgement;
- frame.Header.Statistics.TryAdd(judgement.MaxResult, 0);
- frame.Header.Statistics[judgement.MaxResult]++;
+ HealthProcessor.ApplyResult(new JudgementResult(hitObject, judgement) { Type = judgement.MaxResult });
+ ScoreProcessor.ApplyResult(new JudgementResult(hitObject, judgement) { Type = judgement.MaxResult });
}
- HealthProcessor.ResetFromReplayFrame(frame);
- ScoreProcessor.ResetFromReplayFrame(frame);
-
static IEnumerable enumerateHitObjects(IEnumerable hitObjects, double cutoffTime)
{
foreach (var hitObject in hitObjects.Where(h => h.GetEndTime() < cutoffTime))
From fdb47f8dfaa7c7f90624b853839111052b4beb29 Mon Sep 17 00:00:00 2001
From: Dean Herbert
Date: Wed, 29 May 2024 23:30:47 +0900
Subject: [PATCH 0080/1450] Fix incorrect handling of nested objects when
inside parent object's duration
---
osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs
index 7d637fcb09..2028094964 100644
--- a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs
+++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs
@@ -74,12 +74,16 @@ namespace osu.Game.Screens.Edit.GameplayTest
static IEnumerable enumerateHitObjects(IEnumerable hitObjects, double cutoffTime)
{
- foreach (var hitObject in hitObjects.Where(h => h.GetEndTime() < cutoffTime))
+ foreach (var hitObject in hitObjects)
{
foreach (var nested in enumerateHitObjects(hitObject.NestedHitObjects, cutoffTime))
- yield return nested;
+ {
+ if (nested.GetEndTime() < cutoffTime)
+ yield return nested;
+ }
- yield return hitObject;
+ if (hitObject.GetEndTime() < cutoffTime)
+ yield return hitObject;
}
}
}
From 7254096c9011132e68f9e1ef109a6f13c076b986 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sat, 1 Jun 2024 20:28:39 +0200
Subject: [PATCH 0081/1450] fix isDragging being always true
---
.../Edit/Blueprints/Sliders/Components/SliderTailPiece.cs | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
index dc57d6d7ca..b5894eb84f 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
@@ -129,10 +129,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
protected override void OnDragEnd(DragEndEvent e)
{
- if (isDragging)
- {
- editorBeatmap?.EndChange();
- }
+ if (!isDragging) return;
+
+ isDragging = false;
+ editorBeatmap?.EndChange();
}
///
From ca41c84ba21ab50aea0e9c1fef406c7bc26293a5 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Sat, 1 Jun 2024 21:15:54 +0200
Subject: [PATCH 0082/1450] trim excess control points on drag end
---
.../Sliders/Components/SliderTailPiece.cs | 29 +++++++++++++++++++
1 file changed, 29 insertions(+)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
index b5894eb84f..a0f4401ca7 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
@@ -131,10 +131,39 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
if (!isDragging) return;
+ trimExcessControlPoints(Slider.Path);
+
isDragging = false;
editorBeatmap?.EndChange();
}
+ ///
+ /// Trims control points from the end of the slider path which are not required to reach the expected end of the slider.
+ ///
+ /// The slider path to trim control points of.
+ private void trimExcessControlPoints(SliderPath sliderPath)
+ {
+ if (!sliderPath.ExpectedDistance.Value.HasValue)
+ return;
+
+ double[] segmentEnds = sliderPath.GetSegmentEnds().ToArray();
+ int segmentIndex = 0;
+
+ for (int i = 1; i < sliderPath.ControlPoints.Count - 1; i++)
+ {
+ if (!sliderPath.ControlPoints[i].Type.HasValue) continue;
+
+ if (Precision.AlmostBigger(segmentEnds[segmentIndex], 1, 1E-3))
+ {
+ sliderPath.ControlPoints.RemoveRange(i + 1, sliderPath.ControlPoints.Count - i - 1);
+ sliderPath.ControlPoints[^1].Type = null;
+ break;
+ }
+
+ segmentIndex++;
+ }
+ }
+
///
/// Finds the expected distance value for which the slider end is closest to the mouse position.
///
From bb38cb4137b6bbfa87f152bcc43b5b470736c806 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Mon, 3 Jun 2024 13:18:36 +0200
Subject: [PATCH 0083/1450] simplify tracking changes in shift key status
---
.../Sliders/Components/SliderTailPiece.cs | 21 +++++--------------
1 file changed, 5 insertions(+), 16 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
index a0f4401ca7..f28a8fafda 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
@@ -63,31 +63,20 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
protected override bool OnKeyDown(KeyDownEvent e)
{
- if (e.Repeat)
- return false;
+ if (!e.Repeat && (e.Key == Key.ShiftLeft || e.Key == Key.ShiftRight))
+ updateCirclePieceColour();
- handleDragToggle(e);
return base.OnKeyDown(e);
}
protected override void OnKeyUp(KeyUpEvent e)
{
- handleDragToggle(e);
+ if (e.Key == Key.ShiftLeft || e.Key == Key.ShiftRight)
+ updateCirclePieceColour();
+
base.OnKeyUp(e);
}
- private bool lastShiftPressed;
-
- private void handleDragToggle(KeyboardEvent key)
- {
- bool shiftPressed = key.ShiftPressed;
-
- if (shiftPressed == lastShiftPressed) return;
-
- lastShiftPressed = shiftPressed;
- updateCirclePieceColour();
- }
-
private void updateCirclePieceColour()
{
Color4 colour = colours.Yellow;
From 77b47ad2b4a3ea402017c48cb04b095dc1bd1b43 Mon Sep 17 00:00:00 2001
From: Olivier Schipper
Date: Mon, 3 Jun 2024 13:23:39 +0200
Subject: [PATCH 0084/1450] simplify nullability annotation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Bartłomiej Dach
---
.../Edit/Blueprints/Sliders/Components/SliderTailPiece.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
index a0f4401ca7..23ebd92482 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
@@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
private readonly Cached fullPathCache = new Cached();
- [Resolved(CanBeNull = true)]
+ [Resolved]
private EditorBeatmap? editorBeatmap { get; set; }
[Resolved]
From 34c4ee7de8ea1b2228221c46aea2acb2fac56afd Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Mon, 3 Jun 2024 13:38:42 +0200
Subject: [PATCH 0085/1450] add CanBeNull attribute to LastRepeat
---
.../Edit/Blueprints/Sliders/SliderCircleOverlay.cs | 2 +-
osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 ++
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs
index 2bf5118039..55ea131dab 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs
@@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
base.Update();
var circle = position == SliderPosition.Start ? (HitCircle)Slider.HeadCircle :
- Slider.RepeatCount % 2 == 0 ? Slider.TailCircle : Slider.LastRepeat;
+ Slider.RepeatCount % 2 == 0 ? Slider.TailCircle : Slider.LastRepeat!;
CirclePiece.UpdateFrom(circle);
marker.UpdateFrom(circle);
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index 041907d790..57f277e1ce 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -9,6 +9,7 @@ using System.Collections.Generic;
using osu.Game.Rulesets.Objects;
using System.Linq;
using System.Threading;
+using JetBrains.Annotations;
using Newtonsoft.Json;
using osu.Framework.Bindables;
using osu.Framework.Caching;
@@ -163,6 +164,7 @@ namespace osu.Game.Rulesets.Osu.Objects
public SliderTailCircle TailCircle { get; protected set; }
[JsonIgnore]
+ [CanBeNull]
public SliderRepeat LastRepeat { get; protected set; }
public Slider()
From 82919998dac6d38de78a4c2041e068e2a09461c0 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Tue, 4 Jun 2024 18:26:32 +0200
Subject: [PATCH 0086/1450] dont light up tail piece when hovering anchor
---
.../Sliders/Components/SliderTailPiece.cs | 28 +++----------------
1 file changed, 4 insertions(+), 24 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
index cf8f909ff6..7d39f04596 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderTailPiece.cs
@@ -50,38 +50,18 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => CirclePiece.ReceivePositionalInputAt(screenSpacePos);
- protected override bool OnHover(HoverEvent e)
+ protected override void Update()
{
updateCirclePieceColour();
- return false;
- }
-
- protected override void OnHoverLost(HoverLostEvent e)
- {
- updateCirclePieceColour();
- }
-
- protected override bool OnKeyDown(KeyDownEvent e)
- {
- if (!e.Repeat && (e.Key == Key.ShiftLeft || e.Key == Key.ShiftRight))
- updateCirclePieceColour();
-
- return base.OnKeyDown(e);
- }
-
- protected override void OnKeyUp(KeyUpEvent e)
- {
- if (e.Key == Key.ShiftLeft || e.Key == Key.ShiftRight)
- updateCirclePieceColour();
-
- base.OnKeyUp(e);
+ base.Update();
}
private void updateCirclePieceColour()
{
Color4 colour = colours.Yellow;
- if (IsHovered && inputManager.CurrentState.Keyboard.ShiftPressed)
+ if (IsHovered && inputManager.CurrentState.Keyboard.ShiftPressed
+ && !inputManager.HoveredDrawables.Any(o => o is PathControlPointPiece))
colour = colour.Lighten(1);
CirclePiece.Colour = colour;
From fe738a09517d7638781efffbd1efde61e05cb861 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Wed, 5 Jun 2024 18:12:02 +0200
Subject: [PATCH 0087/1450] Fix missing container inject
---
osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
index b7bc533296..76e735449a 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
@@ -10,6 +10,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
+using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets.Edit;
@@ -26,6 +27,9 @@ namespace osu.Game.Rulesets.Osu.Edit
[Resolved]
private EditorBeatmap editorBeatmap { get; set; } = null!;
+ [Resolved]
+ private IExpandingContainer? expandingContainer { get; set; }
+
///
/// X position of the grid's origin.
///
From 4f8c167cf96bb9732abd50788f5f003aa5ec38c9 Mon Sep 17 00:00:00 2001
From: OliBomby
Date: Wed, 5 Jun 2024 18:56:18 +0200
Subject: [PATCH 0088/1450] clean up to match logic in CircularDistanceSnapGrid
---
.../Components/CircularPositionSnapGrid.cs | 40 ++++++++-----------
1 file changed, 16 insertions(+), 24 deletions(-)
diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs
index 791cb33439..8e63d6bcc0 100644
--- a/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs
@@ -7,7 +7,7 @@ using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.UserInterface;
using osu.Framework.Utils;
using osuTK;
@@ -32,14 +32,14 @@ namespace osu.Game.Screens.Edit.Compose.Components
{
var drawSize = DrawSize;
- // Calculate the maximum distance from the origin to the edge of the grid.
- float maxDist = MathF.Max(
- MathF.Max(StartPosition.Value.Length, (StartPosition.Value - drawSize).Length),
- MathF.Max((StartPosition.Value - new Vector2(drawSize.X, 0)).Length, (StartPosition.Value - new Vector2(0, drawSize.Y)).Length)
- );
-
- generateCircles((int)(maxDist / Spacing.Value) + 1);
+ // Calculate the required number of circles based on the maximum distance from the origin to the edge of the grid.
+ float dx = Math.Max(StartPosition.Value.X, DrawWidth - StartPosition.Value.X);
+ float dy = Math.Max(StartPosition.Value.Y, DrawHeight - StartPosition.Value.Y);
+ float maxDistance = new Vector2(dx, dy).Length;
+ // We need to add one because the first circle starts at zero radius.
+ int requiredCircles = (int)(maxDistance / Spacing.Value) + 1;
+ generateCircles(requiredCircles);
GenerateOutline(drawSize);
}
@@ -48,30 +48,22 @@ namespace osu.Game.Screens.Edit.Compose.Components
// Make lines the same width independent of display resolution.
float lineWidth = 2 * DrawWidth / ScreenSpaceDrawQuad.Width;
- List generatedCircles = new List();
+ List generatedCircles = new List();
for (int i = 0; i < count; i++)
{
// Add a minimum diameter so the center circle is clearly visible.
float diameter = MathF.Max(lineWidth * 1.5f, i * Spacing.Value * 2);
- var gridCircle = new CircularContainer
+ var gridCircle = new CircularProgress
{
- BorderColour = Colour4.White,
- BorderThickness = lineWidth,
- Alpha = 0.2f,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.None,
- Width = diameter,
- Height = diameter,
Position = StartPosition.Value,
- Masking = true,
- Child = new Box
- {
- RelativeSizeAxes = Axes.Both,
- AlwaysPresent = true,
- Alpha = 0f,
- }
+ Origin = Anchor.Centre,
+ Size = new Vector2(diameter),
+ InnerRadius = lineWidth * 1f / diameter,
+ Colour = Colour4.White,
+ Alpha = 0.2f,
+ Progress = 1,
};
generatedCircles.Add(gridCircle);
From b5f0e585245b1ec4993a1cb208e09bbeabccdf1a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bart=C5=82omiej=20Dach?=
Date: Mon, 17 Jun 2024 15:06:17 +0200
Subject: [PATCH 0089/1450] Add ability to better control slider control point
type during placement via `Tab`
---
.../Sliders/SliderPlacementBlueprint.cs | 83 +++++++++++++++----
1 file changed, 69 insertions(+), 14 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
index 0fa84c91fc..f21a1279e5 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
@@ -40,6 +40,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
private PathControlPoint segmentStart;
private PathControlPoint cursor;
private int currentSegmentLength;
+ private bool usingCustomSegmentType;
[Resolved(CanBeNull = true)]
[CanBeNull]
@@ -149,21 +150,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
case SliderPlacementState.ControlPoints:
if (canPlaceNewControlPoint(out var lastPoint))
- {
- // Place a new point by detatching the current cursor.
- updateCursor();
- cursor = null;
- }
+ placeNewControlPoint();
else
- {
- // Transform the last point into a new segment.
- Debug.Assert(lastPoint != null);
-
- segmentStart = lastPoint;
- segmentStart.Type = PathType.LINEAR;
-
- currentSegmentLength = 1;
- }
+ beginNewSegment(lastPoint);
break;
}
@@ -171,6 +160,18 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
return true;
}
+ private void beginNewSegment(PathControlPoint lastPoint)
+ {
+ // Transform the last point into a new segment.
+ Debug.Assert(lastPoint != null);
+
+ segmentStart = lastPoint;
+ segmentStart.Type = PathType.LINEAR;
+
+ currentSegmentLength = 1;
+ usingCustomSegmentType = false;
+ }
+
protected override bool OnDragStart(DragStartEvent e)
{
if (e.Button != MouseButton.Left)
@@ -223,6 +224,47 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
base.OnMouseUp(e);
}
+ private static readonly PathType[] path_types =
+ [
+ PathType.LINEAR,
+ PathType.BEZIER,
+ PathType.PERFECT_CURVE,
+ PathType.BSpline(4),
+ ];
+
+ protected override bool OnKeyDown(KeyDownEvent e)
+ {
+ if (e.Repeat)
+ return false;
+
+ if (state != SliderPlacementState.ControlPoints)
+ return false;
+
+ switch (e.Key)
+ {
+ case Key.Tab:
+ {
+ usingCustomSegmentType = true;
+
+ int currentTypeIndex = segmentStart.Type.HasValue ? Array.IndexOf(path_types, segmentStart.Type.Value) : -1;
+
+ if (currentTypeIndex < 0 && e.ShiftPressed)
+ currentTypeIndex = 0;
+
+ do
+ {
+ currentTypeIndex = (path_types.Length + currentTypeIndex + (e.ShiftPressed ? -1 : 1)) % path_types.Length;
+ segmentStart.Type = path_types[currentTypeIndex];
+ controlPointVisualiser.EnsureValidPathTypes();
+ } while (segmentStart.Type != path_types[currentTypeIndex]);
+
+ return true;
+ }
+ }
+
+ return true;
+ }
+
protected override void Update()
{
base.Update();
@@ -246,6 +288,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
private void updatePathType()
{
+ if (usingCustomSegmentType)
+ {
+ controlPointVisualiser.EnsureValidPathTypes();
+ return;
+ }
+
if (state == SliderPlacementState.Drawing)
{
segmentStart.Type = PathType.BSpline(4);
@@ -316,6 +364,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
return lastPiece.IsHovered != true;
}
+ private void placeNewControlPoint()
+ {
+ // Place a new point by detatching the current cursor.
+ updateCursor();
+ cursor = null;
+ }
+
private void updateSlider()
{
if (state == SliderPlacementState.Drawing)
From 16ea8f67b00b88fd714c3aa87b42987836a8a5cc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bart=C5=82omiej=20Dach?=
Date: Mon, 17 Jun 2024 15:06:27 +0200
Subject: [PATCH 0090/1450] Add ability to start a new segment during placement
via `S` key
---
.../Blueprints/Sliders/SliderPlacementBlueprint.cs | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
index f21a1279e5..7fac95ab91 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
@@ -242,6 +242,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
switch (e.Key)
{
+ case Key.S:
+ {
+ if (!canPlaceNewControlPoint(out _))
+ return false;
+
+ placeNewControlPoint();
+ var last = HitObject.Path.ControlPoints.Last(p => p != cursor);
+ beginNewSegment(last);
+ return true;
+ }
+
case Key.Tab:
{
usingCustomSegmentType = true;
From 88bdc12022b3bb90e4b70877450c8dc5a0b57d5c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bart=C5=82omiej=20Dach?=
Date: Mon, 17 Jun 2024 15:28:52 +0200
Subject: [PATCH 0091/1450] Add ability to cycle through available types when
selecting single control point on a slider
---
.../Components/PathControlPointVisualiser.cs | 37 +++++++++++++++++++
1 file changed, 37 insertions(+)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
index 47af16ffa6..2bdef4afe8 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
@@ -245,6 +245,43 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
}
+ protected override bool OnKeyDown(KeyDownEvent e)
+ {
+ if (e.Repeat || e.Key != Key.Tab)
+ return false;
+
+ var selectedPieces = Pieces.Where(p => p.IsSelected.Value).ToArray();
+ if (selectedPieces.Length != 1)
+ return false;
+
+ var selectedPoint = selectedPieces.Single().ControlPoint;
+ var validTypes = getValidPathTypes(selectedPoint).ToArray();
+ int currentTypeIndex = Array.IndexOf(validTypes, selectedPoint.Type);
+
+ if (currentTypeIndex < 0 && e.ShiftPressed)
+ currentTypeIndex = 0;
+
+ do
+ {
+ currentTypeIndex = (validTypes.Length + currentTypeIndex + (e.ShiftPressed ? -1 : 1)) % validTypes.Length;
+ selectedPoint.Type = validTypes[currentTypeIndex];
+ EnsureValidPathTypes();
+ } while (selectedPoint.Type != validTypes[currentTypeIndex]);
+
+ return true;
+
+ IEnumerable getValidPathTypes(PathControlPoint pathControlPoint)
+ {
+ if (pathControlPoint != controlPoints[0])
+ yield return null;
+
+ yield return PathType.LINEAR;
+ yield return PathType.BEZIER;
+ yield return PathType.PERFECT_CURVE;
+ yield return PathType.BSpline(4);
+ }
+ }
+
private void selectionRequested(PathControlPointPiece piece, MouseButtonEvent e)
{
if (e.Button == MouseButton.Left && inputManager.CurrentState.Keyboard.ControlPressed)
From 1e137271abcb020276ec12020301fdd5a487b4cb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bart=C5=82omiej=20Dach?=
Date: Mon, 17 Jun 2024 15:47:22 +0200
Subject: [PATCH 0092/1450] Add testing for keyboard control of path during
placement
---
.../TestSceneSliderPlacementBlueprint.cs | 106 +++++++++++++-----
1 file changed, 75 insertions(+), 31 deletions(-)
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs
index bbded55732..bc1e4f9864 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs
@@ -2,13 +2,16 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Linq;
using NUnit.Framework;
+using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
+using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual;
@@ -57,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertPlaced(true);
assertLength(200);
assertControlPointCount(2);
- assertControlPointType(0, PathType.LINEAR);
+ assertFinalControlPointType(0, PathType.LINEAR);
}
[Test]
@@ -71,7 +74,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertPlaced(true);
assertControlPointCount(2);
- assertControlPointType(0, PathType.LINEAR);
+ assertFinalControlPointType(0, PathType.LINEAR);
}
[Test]
@@ -89,7 +92,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertPlaced(true);
assertControlPointCount(3);
assertControlPointPosition(1, new Vector2(100, 0));
- assertControlPointType(0, PathType.PERFECT_CURVE);
+ assertFinalControlPointType(0, PathType.PERFECT_CURVE);
}
[Test]
@@ -111,7 +114,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertControlPointCount(4);
assertControlPointPosition(1, new Vector2(100, 0));
assertControlPointPosition(2, new Vector2(100, 100));
- assertControlPointType(0, PathType.BEZIER);
+ assertFinalControlPointType(0, PathType.BEZIER);
}
[Test]
@@ -130,8 +133,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertPlaced(true);
assertControlPointCount(3);
assertControlPointPosition(1, new Vector2(100, 0));
- assertControlPointType(0, PathType.LINEAR);
- assertControlPointType(1, PathType.LINEAR);
+ assertFinalControlPointType(0, PathType.LINEAR);
+ assertFinalControlPointType(1, PathType.LINEAR);
}
[Test]
@@ -149,7 +152,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertPlaced(true);
assertControlPointCount(2);
- assertControlPointType(0, PathType.LINEAR);
+ assertFinalControlPointType(0, PathType.LINEAR);
assertLength(100);
}
@@ -171,7 +174,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertPlaced(true);
assertControlPointCount(3);
- assertControlPointType(0, PathType.PERFECT_CURVE);
+ assertFinalControlPointType(0, PathType.PERFECT_CURVE);
}
[Test]
@@ -195,7 +198,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertPlaced(true);
assertControlPointCount(4);
- assertControlPointType(0, PathType.BEZIER);
+ assertFinalControlPointType(0, PathType.BEZIER);
}
[Test]
@@ -215,8 +218,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertControlPointCount(3);
assertControlPointPosition(1, new Vector2(100, 0));
assertControlPointPosition(2, new Vector2(100));
- assertControlPointType(0, PathType.LINEAR);
- assertControlPointType(1, PathType.LINEAR);
+ assertFinalControlPointType(0, PathType.LINEAR);
+ assertFinalControlPointType(1, PathType.LINEAR);
}
[Test]
@@ -239,8 +242,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertControlPointCount(4);
assertControlPointPosition(1, new Vector2(100, 0));
assertControlPointPosition(2, new Vector2(100));
- assertControlPointType(0, PathType.LINEAR);
- assertControlPointType(1, PathType.PERFECT_CURVE);
+ assertFinalControlPointType(0, PathType.LINEAR);
+ assertFinalControlPointType(1, PathType.PERFECT_CURVE);
}
[Test]
@@ -268,8 +271,46 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertControlPointPosition(2, new Vector2(100));
assertControlPointPosition(3, new Vector2(200, 100));
assertControlPointPosition(4, new Vector2(200));
- assertControlPointType(0, PathType.PERFECT_CURVE);
- assertControlPointType(2, PathType.PERFECT_CURVE);
+ assertFinalControlPointType(0, PathType.PERFECT_CURVE);
+ assertFinalControlPointType(2, PathType.PERFECT_CURVE);
+ }
+
+ [Test]
+ public void TestManualPathTypeControlViaKeyboard()
+ {
+ addMovementStep(new Vector2(200));
+ addClickStep(MouseButton.Left);
+
+ addMovementStep(new Vector2(300, 200));
+ addClickStep(MouseButton.Left);
+
+ addMovementStep(new Vector2(300));
+
+ assertControlPointTypeDuringPlacement(0, PathType.PERFECT_CURVE);
+
+ AddRepeatStep("press tab", () => InputManager.Key(Key.Tab), 2);
+ assertControlPointTypeDuringPlacement(0, PathType.LINEAR);
+
+ AddStep("press shift-tab", () =>
+ {
+ InputManager.PressKey(Key.ShiftLeft);
+ InputManager.Key(Key.Tab);
+ InputManager.ReleaseKey(Key.ShiftLeft);
+ });
+ assertControlPointTypeDuringPlacement(0, PathType.BSpline(4));
+
+ AddStep("start new segment via S", () => InputManager.Key(Key.S));
+ assertControlPointTypeDuringPlacement(2, PathType.LINEAR);
+
+ addMovementStep(new Vector2(400, 300));
+ addClickStep(MouseButton.Left);
+
+ addMovementStep(new Vector2(400));
+ addClickStep(MouseButton.Right);
+
+ assertPlaced(true);
+ assertFinalControlPointType(0, PathType.BSpline(4));
+ assertFinalControlPointType(2, PathType.PERFECT_CURVE);
}
[Test]
@@ -293,7 +334,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
addClickStep(MouseButton.Right);
assertPlaced(true);
- assertControlPointType(0, PathType.BEZIER);
+ assertFinalControlPointType(0, PathType.BEZIER);
}
[Test]
@@ -312,11 +353,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertPlaced(true);
assertLength(808, tolerance: 10);
assertControlPointCount(5);
- assertControlPointType(0, PathType.BSpline(4));
- assertControlPointType(1, null);
- assertControlPointType(2, null);
- assertControlPointType(3, null);
- assertControlPointType(4, null);
+ assertFinalControlPointType(0, PathType.BSpline(4));
+ assertFinalControlPointType(1, null);
+ assertFinalControlPointType(2, null);
+ assertFinalControlPointType(3, null);
+ assertFinalControlPointType(4, null);
}
[Test]
@@ -337,10 +378,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertPlaced(true);
assertLength(600, tolerance: 10);
assertControlPointCount(4);
- assertControlPointType(0, PathType.BSpline(4));
- assertControlPointType(1, PathType.BSpline(4));
- assertControlPointType(2, PathType.BSpline(4));
- assertControlPointType(3, null);
+ assertFinalControlPointType(0, PathType.BSpline(4));
+ assertFinalControlPointType(1, PathType.BSpline(4));
+ assertFinalControlPointType(2, PathType.BSpline(4));
+ assertFinalControlPointType(3, null);
}
[Test]
@@ -359,7 +400,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertPlaced(true);
assertControlPointCount(3);
- assertControlPointType(0, PathType.BEZIER);
+ assertFinalControlPointType(0, PathType.BEZIER);
}
[Test]
@@ -379,7 +420,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertPlaced(true);
assertControlPointCount(3);
- assertControlPointType(0, PathType.PERFECT_CURVE);
+ assertFinalControlPointType(0, PathType.PERFECT_CURVE);
}
[Test]
@@ -400,7 +441,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertPlaced(true);
assertControlPointCount(3);
- assertControlPointType(0, PathType.PERFECT_CURVE);
+ assertFinalControlPointType(0, PathType.PERFECT_CURVE);
}
[Test]
@@ -421,7 +462,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertPlaced(true);
assertControlPointCount(3);
- assertControlPointType(0, PathType.BEZIER);
+ assertFinalControlPointType(0, PathType.BEZIER);
}
[Test]
@@ -438,7 +479,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertPlaced(true);
assertControlPointCount(3);
- assertControlPointType(0, PathType.PERFECT_CURVE);
+ assertFinalControlPointType(0, PathType.PERFECT_CURVE);
}
private void addMovementStep(Vector2 position) => AddStep($"move mouse to {position}", () => InputManager.MoveMouseTo(InputManager.ToScreenSpace(position)));
@@ -454,7 +495,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
private void assertControlPointCount(int expected) => AddAssert($"has {expected} control points", () => getSlider()!.Path.ControlPoints.Count, () => Is.EqualTo(expected));
- private void assertControlPointType(int index, PathType? type) => AddAssert($"control point {index} is {type?.ToString() ?? "inherit"}", () => getSlider()!.Path.ControlPoints[index].Type, () => Is.EqualTo(type));
+ private void assertControlPointTypeDuringPlacement(int index, PathType? type) => AddAssert($"control point {index} is {type?.ToString() ?? "inherit"}",
+ () => this.ChildrenOfType>().ElementAt(index).ControlPoint.Type, () => Is.EqualTo(type));
+
+ private void assertFinalControlPointType(int index, PathType? type) => AddAssert($"control point {index} is {type?.ToString() ?? "inherit"}", () => getSlider()!.Path.ControlPoints[index].Type, () => Is.EqualTo(type));
private void assertControlPointPosition(int index, Vector2 position) =>
AddAssert($"control point {index} at {position}", () => Precision.AlmostEquals(position, getSlider()!.Path.ControlPoints[index].Position, 1));
From 789810069858456380b52403a3de6fde6d065b28 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bart=C5=82omiej=20Dach?=
Date: Mon, 17 Jun 2024 15:54:45 +0200
Subject: [PATCH 0093/1450] Add testing for keyboard control of path during
selection
---
.../TestScenePathControlPointVisualiser.cs | 55 +++++++++++++++++++
1 file changed, 55 insertions(+)
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs
index 9af028fd8c..4813cc089c 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs
@@ -15,6 +15,7 @@ using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Tests.Visual;
using osuTK;
+using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Tests.Editor
{
@@ -177,6 +178,60 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
addAssertPointPositionChanged(points, i);
}
+ [Test]
+ public void TestChangingControlPointTypeViaTab()
+ {
+ createVisualiser(true);
+
+ addControlPointStep(new Vector2(200), PathType.LINEAR);
+ addControlPointStep(new Vector2(300));
+ addControlPointStep(new Vector2(500, 300));
+ addControlPointStep(new Vector2(700, 200));
+ addControlPointStep(new Vector2(500, 100));
+
+ AddStep("select first control point", () => visualiser.Pieces[0].IsSelected.Value = true);
+ AddStep("press tab", () => InputManager.Key(Key.Tab));
+ assertControlPointPathType(0, PathType.BEZIER);
+
+ AddStep("press shift-tab", () =>
+ {
+ InputManager.PressKey(Key.LShift);
+ InputManager.Key(Key.Tab);
+ InputManager.ReleaseKey(Key.LShift);
+ });
+ assertControlPointPathType(0, PathType.LINEAR);
+
+ AddStep("press shift-tab", () =>
+ {
+ InputManager.PressKey(Key.LShift);
+ InputManager.Key(Key.Tab);
+ InputManager.ReleaseKey(Key.LShift);
+ });
+ assertControlPointPathType(0, PathType.BSpline(4));
+
+ AddStep("press shift-tab", () =>
+ {
+ InputManager.PressKey(Key.LShift);
+ InputManager.Key(Key.Tab);
+ InputManager.ReleaseKey(Key.LShift);
+ });
+ assertControlPointPathType(0, PathType.BEZIER);
+
+ AddStep("select third last control point", () =>
+ {
+ visualiser.Pieces[0].IsSelected.Value = false;
+ visualiser.Pieces[2].IsSelected.Value = true;
+ });
+ AddRepeatStep("press tab", () => InputManager.Key(Key.Tab), 3);
+ assertControlPointPathType(2, PathType.PERFECT_CURVE);
+
+ AddStep("press tab", () => InputManager.Key(Key.Tab));
+ assertControlPointPathType(2, PathType.BSpline(4));
+
+ AddStep("press tab", () => InputManager.Key(Key.Tab));
+ assertControlPointPathType(2, null);
+ }
+
private void addAssertPointPositionChanged(Vector2[] points, int index)
{
AddAssert($"Point at {points.ElementAt(index)} changed",
From 5652a558f98d2eca210901303bcb190f7e1eccd4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bart=C5=82omiej=20Dach?=
Date: Mon, 17 Jun 2024 17:07:47 +0200
Subject: [PATCH 0094/1450] Allow to jump to a specific timestamp via bottom
bar in editor
Apparently this is a stable feature and is helpful for modding.
---
.../Edit/Components/TimeInfoContainer.cs | 116 +++++++++++++++---
1 file changed, 100 insertions(+), 16 deletions(-)
diff --git a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs
index 4747828bca..b0e0d95132 100644
--- a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs
+++ b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs
@@ -1,11 +1,17 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
+using System.Globalization;
using osu.Framework.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Framework.Allocation;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
using osu.Game.Extensions;
using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
+using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osuTK;
@@ -13,7 +19,6 @@ namespace osu.Game.Screens.Edit.Components
{
public partial class TimeInfoContainer : BottomBarContainer
{
- private OsuSpriteText trackTimer = null!;
private OsuSpriteText bpm = null!;
[Resolved]
@@ -29,14 +34,7 @@ namespace osu.Game.Screens.Edit.Components
Children = new Drawable[]
{
- trackTimer = new OsuSpriteText
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- Spacing = new Vector2(-2, 0),
- Font = OsuFont.Torus.With(size: 36, fixedWidth: true, weight: FontWeight.Light),
- Y = -10,
- },
+ new TimestampControl(),
bpm = new OsuSpriteText
{
Colour = colours.Orange1,
@@ -47,19 +45,12 @@ namespace osu.Game.Screens.Edit.Components
};
}
- private double? lastTime;
private double? lastBPM;
protected override void Update()
{
base.Update();
- if (lastTime != editorClock.CurrentTime)
- {
- lastTime = editorClock.CurrentTime;
- trackTimer.Text = editorClock.CurrentTime.ToEditorFormattedString();
- }
-
double newBPM = editorBeatmap.ControlPointInfo.TimingPointAt(editorClock.CurrentTime).BPM;
if (lastBPM != newBPM)
@@ -68,5 +59,98 @@ namespace osu.Game.Screens.Edit.Components
bpm.Text = @$"{newBPM:0} BPM";
}
}
+
+ private partial class TimestampControl : OsuClickableContainer
+ {
+ private Container hoverLayer = null!;
+ private OsuSpriteText trackTimer = null!;
+ private OsuTextBox inputTextBox = null!;
+
+ [Resolved]
+ private EditorClock editorClock { get; set; } = null!;
+
+ public TimestampControl()
+ : base(HoverSampleSet.Button)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ AutoSizeAxes = Axes.Both;
+
+ AddRangeInternal(new Drawable[]
+ {
+ hoverLayer = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding
+ {
+ Top = 5,
+ Horizontal = -5
+ },
+ Child = new Box { RelativeSizeAxes = Axes.Both, },
+ Alpha = 0,
+ },
+ trackTimer = new OsuSpriteText
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Spacing = new Vector2(-2, 0),
+ Font = OsuFont.Torus.With(size: 36, fixedWidth: true, weight: FontWeight.Light),
+ },
+ inputTextBox = new OsuTextBox
+ {
+ Width = 150,
+ Height = 36,
+ Alpha = 0,
+ CommitOnFocusLost = true,
+ },
+ });
+
+ Action = () =>
+ {
+ trackTimer.Alpha = 0;
+ inputTextBox.Alpha = 1;
+ inputTextBox.Text = editorClock.CurrentTime.ToEditorFormattedString();
+ Schedule(() =>
+ {
+ GetContainingFocusManager().ChangeFocus(inputTextBox);
+ inputTextBox.SelectAll();
+ });
+ };
+
+ inputTextBox.OnCommit += (_, __) =>
+ {
+ if (TimeSpan.TryParseExact(inputTextBox.Text, @"mm\:ss\:fff", CultureInfo.InvariantCulture, out var timestamp))
+ editorClock.SeekSmoothlyTo(timestamp.TotalMilliseconds);
+
+ trackTimer.Alpha = 1;
+ inputTextBox.Alpha = 0;
+ };
+ }
+
+ private double? lastTime;
+ private bool showingHoverLayer;
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (lastTime != editorClock.CurrentTime)
+ {
+ lastTime = editorClock.CurrentTime;
+ trackTimer.Text = editorClock.CurrentTime.ToEditorFormattedString();
+ }
+
+ bool shouldShowHoverLayer = IsHovered && inputTextBox.Alpha == 0;
+
+ if (shouldShowHoverLayer != showingHoverLayer)
+ {
+ hoverLayer.FadeTo(shouldShowHoverLayer ? 0.2f : 0, 400, Easing.OutQuint);
+ showingHoverLayer = shouldShowHoverLayer;
+ }
+ }
+ }
}
}
From a631d245daa18d0cc2e6cf239ad9e0f36e9a8864 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bart=C5=82omiej=20Dach?=
Date: Mon, 17 Jun 2024 18:14:33 +0200
Subject: [PATCH 0095/1450] Fix test failure
---
.../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
index 7fac95ab91..fdfb52008c 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
@@ -273,7 +273,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
}
}
- return true;
+ return false;
}
protected override void Update()
From 683d5310b14c6b366b8b29ceaf20dffd1bcc775b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bart=C5=82omiej=20Dach?=
Date: Mon, 17 Jun 2024 18:33:36 +0200
Subject: [PATCH 0096/1450] Implement direct choice of slider control point
path type via `Alt`-number
---
.../Components/PathControlPointVisualiser.cs | 123 +++++++++++-------
.../Sliders/SliderPlacementBlueprint.cs | 14 ++
2 files changed, 91 insertions(+), 46 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
index 2bdef4afe8..3d6e529afa 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
@@ -245,40 +245,73 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
}
+ // ReSharper disable once StaticMemberInGenericType
+ private static readonly PathType?[] path_types =
+ [
+ null,
+ PathType.LINEAR,
+ PathType.BEZIER,
+ PathType.PERFECT_CURVE,
+ PathType.BSpline(4),
+ ];
+
protected override bool OnKeyDown(KeyDownEvent e)
{
- if (e.Repeat || e.Key != Key.Tab)
+ if (e.Repeat)
return false;
var selectedPieces = Pieces.Where(p => p.IsSelected.Value).ToArray();
if (selectedPieces.Length != 1)
return false;
- var selectedPoint = selectedPieces.Single().ControlPoint;
- var validTypes = getValidPathTypes(selectedPoint).ToArray();
- int currentTypeIndex = Array.IndexOf(validTypes, selectedPoint.Type);
+ var selectedPiece = selectedPieces.Single();
+ var selectedPoint = selectedPiece.ControlPoint;
- if (currentTypeIndex < 0 && e.ShiftPressed)
- currentTypeIndex = 0;
-
- do
+ switch (e.Key)
{
- currentTypeIndex = (validTypes.Length + currentTypeIndex + (e.ShiftPressed ? -1 : 1)) % validTypes.Length;
- selectedPoint.Type = validTypes[currentTypeIndex];
- EnsureValidPathTypes();
- } while (selectedPoint.Type != validTypes[currentTypeIndex]);
+ case Key.Tab:
+ {
+ var validTypes = path_types;
- return true;
+ if (selectedPoint == controlPoints[0])
+ validTypes = validTypes.Where(t => t != null).ToArray();
- IEnumerable getValidPathTypes(PathControlPoint pathControlPoint)
- {
- if (pathControlPoint != controlPoints[0])
- yield return null;
+ int currentTypeIndex = Array.IndexOf(validTypes, selectedPoint.Type);
- yield return PathType.LINEAR;
- yield return PathType.BEZIER;
- yield return PathType.PERFECT_CURVE;
- yield return PathType.BSpline(4);
+ if (currentTypeIndex < 0 && e.ShiftPressed)
+ currentTypeIndex = 0;
+
+ changeHandler?.BeginChange();
+
+ do
+ {
+ currentTypeIndex = (validTypes.Length + currentTypeIndex + (e.ShiftPressed ? -1 : 1)) % validTypes.Length;
+
+ updatePathTypeOfSelectedPieces(validTypes[currentTypeIndex]);
+ } while (selectedPoint.Type != validTypes[currentTypeIndex]);
+
+ changeHandler?.EndChange();
+
+ return true;
+ }
+
+ case Key.Number0:
+ case Key.Number1:
+ case Key.Number2:
+ case Key.Number3:
+ case Key.Number4:
+ {
+ var type = path_types[e.Key - Key.Number0];
+
+ if (selectedPoint == controlPoints[0] && type == null)
+ return false;
+
+ updatePathTypeOfSelectedPieces(type);
+ return true;
+ }
+
+ default:
+ return false;
}
}
@@ -291,30 +324,38 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
}
///
- /// Attempts to set the given control point piece to the given path type.
+ /// Attempts to set all selected control point pieces to the given path type.
/// If that would fail, try to change the path such that it instead succeeds
/// in a UX-friendly way.
///
- /// The control point piece that we want to change the path type of.
/// The path type we want to assign to the given control point piece.
- private void updatePathType(PathControlPointPiece piece, PathType? type)
+ private void updatePathTypeOfSelectedPieces(PathType? type)
{
- var pointsInSegment = hitObject.Path.PointsInSegment(piece.ControlPoint);
- int indexInSegment = pointsInSegment.IndexOf(piece.ControlPoint);
+ changeHandler?.BeginChange();
- if (type?.Type == SplineType.PerfectCurve)
+ foreach (var p in Pieces.Where(p => p.IsSelected.Value))
{
- // Can't always create a circular arc out of 4 or more points,
- // so we split the segment into one 3-point circular arc segment
- // and one segment of the previous type.
- int thirdPointIndex = indexInSegment + 2;
+ var pointsInSegment = hitObject.Path.PointsInSegment(p.ControlPoint);
+ int indexInSegment = pointsInSegment.IndexOf(p.ControlPoint);
- if (pointsInSegment.Count > thirdPointIndex + 1)
- pointsInSegment[thirdPointIndex].Type = pointsInSegment[0].Type;
+ if (type?.Type == SplineType.PerfectCurve)
+ {
+ // Can't always create a circular arc out of 4 or more points,
+ // so we split the segment into one 3-point circular arc segment
+ // and one segment of the previous type.
+ int thirdPointIndex = indexInSegment + 2;
+
+ if (pointsInSegment.Count > thirdPointIndex + 1)
+ pointsInSegment[thirdPointIndex].Type = pointsInSegment[0].Type;
+ }
+
+ hitObject.Path.ExpectedDistance.Value = null;
+ p.ControlPoint.Type = type;
}
- hitObject.Path.ExpectedDistance.Value = null;
- piece.ControlPoint.Type = type;
+ EnsureValidPathTypes();
+
+ changeHandler?.EndChange();
}
[Resolved(CanBeNull = true)]
@@ -470,17 +511,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
int totalCount = Pieces.Count(p => p.IsSelected.Value);
int countOfState = Pieces.Where(p => p.IsSelected.Value).Count(p => p.ControlPoint.Type == type);
- var item = new TernaryStateRadioMenuItem(type?.Description ?? "Inherit", MenuItemType.Standard, _ =>
- {
- changeHandler?.BeginChange();
-
- foreach (var p in Pieces.Where(p => p.IsSelected.Value))
- updatePathType(p, type);
-
- EnsureValidPathTypes();
-
- changeHandler?.EndChange();
- });
+ var item = new TernaryStateRadioMenuItem(type?.Description ?? "Inherit", MenuItemType.Standard, _ => updatePathTypeOfSelectedPieces(type));
if (countOfState == totalCount)
item.State.Value = TernaryState.True;
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
index fdfb52008c..91cd270af6 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
@@ -253,6 +253,20 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
return true;
}
+ case Key.Number1:
+ case Key.Number2:
+ case Key.Number3:
+ case Key.Number4:
+ {
+ if (!e.AltPressed)
+ return false;
+
+ usingCustomSegmentType = true;
+ segmentStart.Type = path_types[e.Key - Key.Number1];
+ controlPointVisualiser.EnsureValidPathTypes();
+ return true;
+ }
+
case Key.Tab:
{
usingCustomSegmentType = true;
From 310265c43fa95458e61899d60b72d32366c1dcbe Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bart=C5=82omiej=20Dach?=
Date: Tue, 18 Jun 2024 08:16:22 +0200
Subject: [PATCH 0097/1450] Add slider placement binding description in tooltip
---
.../Edit/SliderCompositionTool.cs | 7 ++++++
osu.Game/Rulesets/Edit/HitObjectComposer.cs | 4 ++--
.../Edit/HitObjectCompositionToolButton.cs | 22 +++++++++++++++++++
.../Edit/Tools/HitObjectCompositionTool.cs | 7 +++---
.../Components/RadioButtons/RadioButton.cs | 4 ++--
5 files changed, 37 insertions(+), 7 deletions(-)
create mode 100644 osu.Game/Rulesets/Edit/HitObjectCompositionToolButton.cs
diff --git a/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs b/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs
index 676205c8d7..617cc1c19b 100644
--- a/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs
+++ b/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs
@@ -15,6 +15,13 @@ namespace osu.Game.Rulesets.Osu.Edit
public SliderCompositionTool()
: base(nameof(Slider))
{
+ TooltipText = """
+ Left click for new point.
+ Left click twice or S key for new segment.
+ Tab, Shift-Tab, or Alt-1~4 to change current segment type.
+ Right click to finish.
+ Click and drag for drawing mode.
+ """;
}
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
index d0c6078c9d..a34717e7ae 100644
--- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs
+++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
@@ -215,14 +215,14 @@ namespace osu.Game.Rulesets.Edit
toolboxCollection.Items = CompositionTools
.Prepend(new SelectTool())
- .Select(t => new RadioButton(t.Name, () => toolSelected(t), t.CreateIcon))
+ .Select(t => new HitObjectCompositionToolButton(t, () => toolSelected(t)))
.ToList();
foreach (var item in toolboxCollection.Items)
{
item.Selected.DisabledChanged += isDisabled =>
{
- item.TooltipText = isDisabled ? "Add at least one timing point first!" : string.Empty;
+ item.TooltipText = isDisabled ? "Add at least one timing point first!" : ((HitObjectCompositionToolButton)item).TooltipText;
};
}
diff --git a/osu.Game/Rulesets/Edit/HitObjectCompositionToolButton.cs b/osu.Game/Rulesets/Edit/HitObjectCompositionToolButton.cs
new file mode 100644
index 0000000000..ba566ff5c0
--- /dev/null
+++ b/osu.Game/Rulesets/Edit/HitObjectCompositionToolButton.cs
@@ -0,0 +1,22 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Game.Rulesets.Edit.Tools;
+using osu.Game.Screens.Edit.Components.RadioButtons;
+
+namespace osu.Game.Rulesets.Edit
+{
+ public class HitObjectCompositionToolButton : RadioButton
+ {
+ public HitObjectCompositionTool Tool { get; }
+
+ public HitObjectCompositionToolButton(HitObjectCompositionTool tool, Action? action)
+ : base(tool.Name, action, tool.CreateIcon)
+ {
+ Tool = tool;
+
+ TooltipText = tool.TooltipText;
+ }
+ }
+}
diff --git a/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs b/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs
index 707645edeb..26e88aa530 100644
--- a/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs
+++ b/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs
@@ -1,9 +1,8 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Graphics;
+using osu.Framework.Localisation;
namespace osu.Game.Rulesets.Edit.Tools
{
@@ -11,6 +10,8 @@ namespace osu.Game.Rulesets.Edit.Tools
{
public readonly string Name;
+ public LocalisableString TooltipText { get; init; } = default;
+
protected HitObjectCompositionTool(string name)
{
Name = name;
@@ -18,7 +19,7 @@ namespace osu.Game.Rulesets.Edit.Tools
public abstract PlacementBlueprint CreatePlacementBlueprint();
- public virtual Drawable CreateIcon() => null;
+ public virtual Drawable? CreateIcon() => null;
public override string ToString() => Name;
}
diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs
index f49fc6f6ab..26022aa746 100644
--- a/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs
+++ b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs
@@ -24,11 +24,11 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons
///
/// A function which creates a drawable icon to represent this item. If null, a sane default should be used.
///
- public readonly Func? CreateIcon;
+ public readonly Func? CreateIcon;
private readonly Action? action;
- public RadioButton(string label, Action? action, Func? createIcon = null)
+ public RadioButton(string label, Action? action, Func? createIcon = null)
{
Label = label;
CreateIcon = createIcon;
From a3326086f79513cdfe613e3304b1ca55c6558f49 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bart=C5=82omiej=20Dach?=
Date: Tue, 18 Jun 2024 08:25:05 +0200
Subject: [PATCH 0098/1450] Adjust hotkeys to address feedback
---
.../Components/PathControlPointVisualiser.cs | 22 +++++++++----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
index 3d6e529afa..775604174b 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
@@ -248,11 +248,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
// ReSharper disable once StaticMemberInGenericType
private static readonly PathType?[] path_types =
[
- null,
PathType.LINEAR,
PathType.BEZIER,
PathType.PERFECT_CURVE,
PathType.BSpline(4),
+ null,
];
protected override bool OnKeyDown(KeyDownEvent e)
@@ -260,17 +260,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
if (e.Repeat)
return false;
- var selectedPieces = Pieces.Where(p => p.IsSelected.Value).ToArray();
- if (selectedPieces.Length != 1)
- return false;
-
- var selectedPiece = selectedPieces.Single();
- var selectedPoint = selectedPiece.ControlPoint;
-
switch (e.Key)
{
case Key.Tab:
{
+ var selectedPieces = Pieces.Where(p => p.IsSelected.Value).ToArray();
+ if (selectedPieces.Length != 1)
+ return false;
+
+ var selectedPiece = selectedPieces.Single();
+ var selectedPoint = selectedPiece.ControlPoint;
+
var validTypes = path_types;
if (selectedPoint == controlPoints[0])
@@ -295,15 +295,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
return true;
}
- case Key.Number0:
case Key.Number1:
case Key.Number2:
case Key.Number3:
case Key.Number4:
+ case Key.Number5:
{
- var type = path_types[e.Key - Key.Number0];
+ var type = path_types[e.Key - Key.Number1];
- if (selectedPoint == controlPoints[0] && type == null)
+ if (Pieces[0].IsSelected.Value && type == null)
return false;
updatePathTypeOfSelectedPieces(type);
From 73786a6f9f1039b7c64587db8bb22a402ebe7c61 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bart=C5=82omiej=20Dach?=
Date: Tue, 18 Jun 2024 08:32:12 +0200
Subject: [PATCH 0099/1450] Adjust & expand test coverage
---
.../TestScenePathControlPointVisualiser.cs | 31 +++++++++++++++----
1 file changed, 25 insertions(+), 6 deletions(-)
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs
index 4813cc089c..93eb76aba6 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs
@@ -215,21 +215,40 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
InputManager.Key(Key.Tab);
InputManager.ReleaseKey(Key.LShift);
});
- assertControlPointPathType(0, PathType.BEZIER);
+ assertControlPointPathType(0, PathType.PERFECT_CURVE);
+ assertControlPointPathType(2, PathType.BSpline(4));
AddStep("select third last control point", () =>
{
visualiser.Pieces[0].IsSelected.Value = false;
visualiser.Pieces[2].IsSelected.Value = true;
});
- AddRepeatStep("press tab", () => InputManager.Key(Key.Tab), 3);
+
+ AddStep("press shift-tab", () =>
+ {
+ InputManager.PressKey(Key.LShift);
+ InputManager.Key(Key.Tab);
+ InputManager.ReleaseKey(Key.LShift);
+ });
assertControlPointPathType(2, PathType.PERFECT_CURVE);
- AddStep("press tab", () => InputManager.Key(Key.Tab));
- assertControlPointPathType(2, PathType.BSpline(4));
-
- AddStep("press tab", () => InputManager.Key(Key.Tab));
+ AddRepeatStep("press tab", () => InputManager.Key(Key.Tab), 2);
+ assertControlPointPathType(0, PathType.BEZIER);
assertControlPointPathType(2, null);
+
+ AddStep("select first and third control points", () =>
+ {
+ visualiser.Pieces[0].IsSelected.Value = true;
+ visualiser.Pieces[2].IsSelected.Value = true;
+ });
+ AddStep("press alt-1", () =>
+ {
+ InputManager.PressKey(Key.AltLeft);
+ InputManager.Key(Key.Number1);
+ InputManager.ReleaseKey(Key.AltLeft);
+ });
+ assertControlPointPathType(0, PathType.LINEAR);
+ assertControlPointPathType(2, PathType.LINEAR);
}
private void addAssertPointPositionChanged(Vector2[] points, int index)
From 24217514192a8ef58384c9a93e2a8b74cf91444a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bart=C5=82omiej=20Dach?=
Date: Tue, 18 Jun 2024 08:33:02 +0200
Subject: [PATCH 0100/1450] Fix code quality inspections
---
osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs b/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs
index 26e88aa530..ba1dc817bb 100644
--- a/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs
+++ b/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs
@@ -10,14 +10,14 @@ namespace osu.Game.Rulesets.Edit.Tools
{
public readonly string Name;
- public LocalisableString TooltipText { get; init; } = default;
+ public LocalisableString TooltipText { get; init; }
protected HitObjectCompositionTool(string name)
{
Name = name;
}
- public abstract PlacementBlueprint CreatePlacementBlueprint();
+ public abstract PlacementBlueprint? CreatePlacementBlueprint();
public virtual Drawable? CreateIcon() => null;
From 1b4a3b0e2ebcba788fccdc353613f170c883551c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bart=C5=82omiej=20Dach?=
Date: Tue, 18 Jun 2024 09:37:43 +0200
Subject: [PATCH 0101/1450] Change editor speed adjustment back to adjusting
tempo
- Partially reverts https://github.com/ppy/osu/pull/12080
- Addresses https://github.com/ppy/osu/discussions/27830,
https://github.com/ppy/osu/discussions/23789,
https://github.com/ppy/osu/discussions/15368, et al.
The important distinction here is that to prevent misuse when timing,
the control will revert to 1.0x speed and disable when moving to timing
screen, with a tooltip explaining why.
---
.../Edit/Components/PlaybackControl.cs | 79 ++++++++++++++-----
1 file changed, 59 insertions(+), 20 deletions(-)
diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs
index 9e27f0e57d..0546878788 100644
--- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs
+++ b/osu.Game/Screens/Edit/Components/PlaybackControl.cs
@@ -10,9 +10,11 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
+using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
@@ -25,14 +27,16 @@ namespace osu.Game.Screens.Edit.Components
public partial class PlaybackControl : BottomBarContainer
{
private IconButton playButton = null!;
+ private PlaybackSpeedControl playbackSpeedControl = null!;
[Resolved]
private EditorClock editorClock { get; set; } = null!;
- private readonly BindableNumber freqAdjust = new BindableDouble(1);
+ private readonly Bindable currentScreenMode = new Bindable();
+ private readonly BindableNumber tempoAdjustment = new BindableDouble(1);
[BackgroundDependencyLoader]
- private void load(OverlayColourProvider colourProvider)
+ private void load(OverlayColourProvider colourProvider, Editor? editor)
{
Background.Colour = colourProvider.Background4;
@@ -47,31 +51,61 @@ namespace osu.Game.Screens.Edit.Components
Icon = FontAwesome.Regular.PlayCircle,
Action = togglePause,
},
- new OsuSpriteText
+ playbackSpeedControl = new PlaybackSpeedControl
{
- Origin = Anchor.BottomLeft,
- Text = EditorStrings.PlaybackSpeed,
- RelativePositionAxes = Axes.Y,
- Y = 0.5f,
- Padding = new MarginPadding { Left = 45 }
- },
- new Container
- {
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- RelativeSizeAxes = Axes.Both,
- Height = 0.5f,
- Padding = new MarginPadding { Left = 45 },
- Child = new PlaybackTabControl { Current = freqAdjust },
+ AutoSizeAxes = Axes.Y,
+ RelativeSizeAxes = Axes.X,
+ Padding = new MarginPadding { Left = 45, },
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ Direction = FillDirection.Vertical,
+ Children = new Drawable[]
+ {
+ new OsuSpriteText
+ {
+ Text = EditorStrings.PlaybackSpeed,
+ },
+ new PlaybackTabControl
+ {
+ Current = tempoAdjustment,
+ RelativeSizeAxes = Axes.X,
+ Height = 16,
+ },
+ }
}
};
- Track.BindValueChanged(tr => tr.NewValue?.AddAdjustment(AdjustableProperty.Frequency, freqAdjust), true);
+ Track.BindValueChanged(tr => tr.NewValue?.AddAdjustment(AdjustableProperty.Tempo, tempoAdjustment), true);
+
+ if (editor != null)
+ currentScreenMode.BindTo(editor.Mode);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ currentScreenMode.BindValueChanged(_ =>
+ {
+ if (currentScreenMode.Value == EditorScreenMode.Timing)
+ {
+ tempoAdjustment.Value = 1;
+ tempoAdjustment.Disabled = true;
+ playbackSpeedControl.FadeTo(0.5f, 400, Easing.OutQuint);
+ playbackSpeedControl.TooltipText = "Speed adjustment is unavailable in timing mode. Timing at slower speeds is inaccurate due to resampling artifacts.";
+ }
+ else
+ {
+ tempoAdjustment.Disabled = false;
+ playbackSpeedControl.FadeTo(1, 400, Easing.OutQuint);
+ playbackSpeedControl.TooltipText = default;
+ }
+ });
}
protected override void Dispose(bool isDisposing)
{
- Track.Value?.RemoveAdjustment(AdjustableProperty.Frequency, freqAdjust);
+ Track.Value?.RemoveAdjustment(AdjustableProperty.Frequency, tempoAdjustment);
base.Dispose(isDisposing);
}
@@ -109,6 +143,11 @@ namespace osu.Game.Screens.Edit.Components
playButton.Icon = editorClock.IsRunning ? pause_icon : play_icon;
}
+ private partial class PlaybackSpeedControl : FillFlowContainer, IHasTooltip
+ {
+ public LocalisableString TooltipText { get; set; }
+ }
+
private partial class PlaybackTabControl : OsuTabControl
{
private static readonly double[] tempo_values = { 0.25, 0.5, 0.75, 1 };
@@ -174,7 +213,7 @@ namespace osu.Game.Screens.Edit.Components
protected override bool OnHover(HoverEvent e)
{
updateState();
- return true;
+ return false;
}
protected override void OnHoverLost(HoverLostEvent e) => updateState();
From 87888ff0bbca43e94a5383939f37e01aec1d7419 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bart=C5=82omiej=20Dach?=
Date: Tue, 18 Jun 2024 10:28:36 +0200
Subject: [PATCH 0102/1450] Extend slider selection box bounds to contain all
control points inside
Previously, the selection box was only guaranteed to contain the actual
body of the slider itself, the control point nodes were allowed to exit
it. This lead to a lot of weird interactions with the selection box
controls (rotation/drag handles, also the buttons under/over it) as the
slider anchors could overlap with them.
To bypass this issue entirely just ensure that the selection box's size
does include the control point nodes at all times.
---
.../Sliders/SliderSelectionBlueprint.cs | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
index 49fdf12d60..2f1e2a9fdd 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
@@ -54,7 +54,21 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
[Resolved(CanBeNull = true)]
private BindableBeatDivisor beatDivisor { get; set; }
- public override Quad SelectionQuad => BodyPiece.ScreenSpaceDrawQuad;
+ public override Quad SelectionQuad
+ {
+ get
+ {
+ var result = BodyPiece.ScreenSpaceDrawQuad.AABBFloat;
+
+ if (ControlPointVisualiser != null)
+ {
+ foreach (var piece in ControlPointVisualiser.Pieces)
+ result = RectangleF.Union(result, piece.ScreenSpaceDrawQuad.AABBFloat);
+ }
+
+ return result;
+ }
+ }
private readonly BindableList controlPoints = new BindableList();
private readonly IBindable pathVersion = new Bindable();
From 5fe21f16b994d0c7e040d9e012b92ff977099487 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bart=C5=82omiej=20Dach?=
Date: Tue, 18 Jun 2024 10:41:26 +0200
Subject: [PATCH 0103/1450] Fix test failures
---
.../Sliders/Components/PathControlPointVisualiser.cs | 3 +++
1 file changed, 3 insertions(+)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
index 775604174b..ddf6cd0f57 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
@@ -301,6 +301,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
case Key.Number4:
case Key.Number5:
{
+ if (!e.AltPressed)
+ return false;
+
var type = path_types[e.Key - Key.Number1];
if (Pieces[0].IsSelected.Value && type == null)
From e1827ac28d7c344deea1764a7ab8f1cb796fb0d5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bart=C5=82omiej=20Dach?=
Date: Tue, 18 Jun 2024 12:33:12 +0200
Subject: [PATCH 0104/1450] Address review feedback
---
osu.Game/OsuGame.cs | 2 +-
.../Edit/Components/TimeInfoContainer.cs | 10 +++++-----
osu.Game/Screens/Edit/Editor.cs | 19 ++++++++++++-------
3 files changed, 18 insertions(+), 13 deletions(-)
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 667c3ecb99..63aa4564bf 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -595,7 +595,7 @@ namespace osu.Game
return;
}
- editor.HandleTimestamp(timestamp);
+ editor.HandleTimestamp(timestamp, notifyOnError: true);
}
///
diff --git a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs
index b0e0d95132..9e14ec851b 100644
--- a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs
+++ b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System;
-using System.Globalization;
using osu.Framework.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Framework.Allocation;
@@ -66,6 +64,9 @@ namespace osu.Game.Screens.Edit.Components
private OsuSpriteText trackTimer = null!;
private OsuTextBox inputTextBox = null!;
+ [Resolved]
+ private Editor? editor { get; set; }
+
[Resolved]
private EditorClock editorClock { get; set; } = null!;
@@ -120,11 +121,10 @@ namespace osu.Game.Screens.Edit.Components
});
};
+ inputTextBox.Current.BindValueChanged(val => editor?.HandleTimestamp(val.NewValue));
+
inputTextBox.OnCommit += (_, __) =>
{
- if (TimeSpan.TryParseExact(inputTextBox.Text, @"mm\:ss\:fff", CultureInfo.InvariantCulture, out var timestamp))
- editorClock.SeekSmoothlyTo(timestamp.TotalMilliseconds);
-
trackTimer.Alpha = 1;
inputTextBox.Alpha = 0;
};
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index 02dcad46f7..a37d3763a5 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -1275,16 +1275,20 @@ namespace osu.Game.Screens.Edit
return tcs.Task;
}
- public void HandleTimestamp(string timestamp)
+ public bool HandleTimestamp(string timestamp, bool notifyOnError = false)
{
if (!EditorTimestampParser.TryParse(timestamp, out var timeSpan, out string selection))
{
- Schedule(() => notifications?.Post(new SimpleErrorNotification
+ if (notifyOnError)
{
- Icon = FontAwesome.Solid.ExclamationTriangle,
- Text = EditorStrings.FailedToParseEditorLink
- }));
- return;
+ Schedule(() => notifications?.Post(new SimpleErrorNotification
+ {
+ Icon = FontAwesome.Solid.ExclamationTriangle,
+ Text = EditorStrings.FailedToParseEditorLink
+ }));
+ }
+
+ return false;
}
editorBeatmap.SelectedHitObjects.Clear();
@@ -1297,7 +1301,7 @@ namespace osu.Game.Screens.Edit
if (string.IsNullOrEmpty(selection))
{
clock.SeekSmoothlyTo(position);
- return;
+ return true;
}
// Seek to the next closest HitObject instead
@@ -1312,6 +1316,7 @@ namespace osu.Game.Screens.Edit
// Delegate handling the selection to the ruleset.
currentScreen.Dependencies.Get().SelectFromTimestamp(position, selection);
+ return true;
}
public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime);
From 44b9a066393d8468ae5728140ce592caaca0d565 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bart=C5=82omiej=20Dach?=
Date: Tue, 18 Jun 2024 13:00:43 +0200
Subject: [PATCH 0105/1450] Allow more lenient parsing of incoming timestamps
---
.../Editing/EditorTimestampParserTest.cs | 43 ++++++++++++++++++
osu.Game/Online/Chat/MessageFormatter.cs | 2 +-
.../Rulesets/Edit/EditorTimestampParser.cs | 44 +++++++++++++------
3 files changed, 75 insertions(+), 14 deletions(-)
create mode 100644 osu.Game.Tests/Editing/EditorTimestampParserTest.cs
diff --git a/osu.Game.Tests/Editing/EditorTimestampParserTest.cs b/osu.Game.Tests/Editing/EditorTimestampParserTest.cs
new file mode 100644
index 0000000000..24ac8e32a4
--- /dev/null
+++ b/osu.Game.Tests/Editing/EditorTimestampParserTest.cs
@@ -0,0 +1,43 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using NUnit.Framework;
+using osu.Game.Rulesets.Edit;
+
+namespace osu.Game.Tests.Editing
+{
+ [TestFixture]
+ public class EditorTimestampParserTest
+ {
+ public static readonly object?[][] test_cases =
+ {
+ new object?[] { ":", false, null, null },
+ new object?[] { "1", true, new TimeSpan(0, 0, 1, 0), null },
+ new object?[] { "99", true, new TimeSpan(0, 0, 99, 0), null },
+ new object?[] { "300", false, null, null },
+ new object?[] { "1:2", true, new TimeSpan(0, 0, 1, 2), null },
+ new object?[] { "1:02", true, new TimeSpan(0, 0, 1, 2), null },
+ new object?[] { "1:92", false, null, null },
+ new object?[] { "1:002", false, null, null },
+ new object?[] { "1:02:3", true, new TimeSpan(0, 0, 1, 2, 3), null },
+ new object?[] { "1:02:300", true, new TimeSpan(0, 0, 1, 2, 300), null },
+ new object?[] { "1:02:3000", false, null, null },
+ new object?[] { "1:02:300 ()", false, null, null },
+ new object?[] { "1:02:300 (1,2,3)", true, new TimeSpan(0, 0, 1, 2, 300), "1,2,3" },
+ };
+
+ [TestCaseSource(nameof(test_cases))]
+ public void TestTryParse(string timestamp, bool expectedSuccess, TimeSpan? expectedParsedTime, string? expectedSelection)
+ {
+ bool actualSuccess = EditorTimestampParser.TryParse(timestamp, out var actualParsedTime, out string? actualSelection);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(actualSuccess, Is.EqualTo(expectedSuccess));
+ Assert.That(actualParsedTime, Is.EqualTo(expectedParsedTime));
+ Assert.That(actualSelection, Is.EqualTo(expectedSelection));
+ });
+ }
+ }
+}
diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs
index f055633d64..77454c4775 100644
--- a/osu.Game/Online/Chat/MessageFormatter.cs
+++ b/osu.Game/Online/Chat/MessageFormatter.cs
@@ -271,7 +271,7 @@ namespace osu.Game.Online.Chat
handleAdvanced(advanced_link_regex, result, startIndex);
// handle editor times
- handleMatches(EditorTimestampParser.TIME_REGEX, "{0}", $@"{OsuGameBase.OSU_PROTOCOL}edit/{{0}}", result, startIndex, LinkAction.OpenEditorTimestamp);
+ handleMatches(EditorTimestampParser.TIME_REGEX_STRICT, "{0}", $@"{OsuGameBase.OSU_PROTOCOL}edit/{{0}}", result, startIndex, LinkAction.OpenEditorTimestamp);
// handle channels
handleMatches(channel_regex, "{0}", $@"{OsuGameBase.OSU_PROTOCOL}chan/{{0}}", result, startIndex, LinkAction.OpenChannel);
diff --git a/osu.Game/Rulesets/Edit/EditorTimestampParser.cs b/osu.Game/Rulesets/Edit/EditorTimestampParser.cs
index bdfdce432e..9c3119d8f4 100644
--- a/osu.Game/Rulesets/Edit/EditorTimestampParser.cs
+++ b/osu.Game/Rulesets/Edit/EditorTimestampParser.cs
@@ -9,13 +9,34 @@ namespace osu.Game.Rulesets.Edit
{
public static class EditorTimestampParser
{
- // 00:00:000 (...) - test
- // original osu-web regex: https://github.com/ppy/osu-web/blob/3b1698639244cfdaf0b41c68bfd651ea729ec2e3/resources/js/utils/beatmapset-discussion-helper.ts#L78
- public static readonly Regex TIME_REGEX = new Regex(@"\b(((?\d{2,}):(?