diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs b/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs
index f2c77d6a05..f40d2bb45e 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Localisation;
+using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Mods
@@ -9,5 +10,12 @@ namespace osu.Game.Rulesets.Catch.Mods
public class CatchModEasy : ModEasyWithExtraLives
{
public override LocalisableString Description => @"Larger fruits, more forgiving HP drain, less accuracy required, and extra lives!";
+
+ public override void ApplyToDifficulty(BeatmapDifficulty difficulty)
+ {
+ base.ApplyToDifficulty(difficulty);
+
+ difficulty.OverallDifficulty *= ADJUST_RATIO;
+ }
}
}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs
index 62fded0980..f7d64dc57b 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs
@@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Catch.Mods
{
base.ApplyToDifficulty(difficulty);
+ difficulty.OverallDifficulty = Math.Min(difficulty.OverallDifficulty * ADJUST_RATIO, 10.0f);
difficulty.CircleSize = Math.Min(difficulty.CircleSize * 1.3f, 10.0f); // CS uses a custom 1.3 ratio.
difficulty.ApproachRate = Math.Min(difficulty.ApproachRate * ADJUST_RATIO, 10.0f);
}
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs
index cb2abc1595..2ffc1ee0ef 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs
@@ -5,7 +5,6 @@ using System;
using NUnit.Framework;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Mania.Mods;
-using osu.Game.Rulesets.Mods;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Mania.Tests
@@ -38,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Tests
new object[] { LegacyMods.Key2, new[] { typeof(ManiaModKey2) } },
new object[] { LegacyMods.Mirror, new[] { typeof(ManiaModMirror) } },
new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(ManiaModHardRock), typeof(ManiaModDoubleTime) } },
- new object[] { LegacyMods.ScoreV2, new[] { typeof(ModScoreV2) } },
+ new object[] { LegacyMods.ScoreV2, new[] { typeof(ManiaModScoreV2) } },
};
[TestCaseSource(nameof(mania_mod_mapping))]
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneLegacyReplayPlayback.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneLegacyReplayPlayback.cs
index 2c17cd8015..f95c0c186f 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneLegacyReplayPlayback.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneLegacyReplayPlayback.cs
@@ -9,7 +9,6 @@ using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Replays;
-using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
@@ -521,14 +520,13 @@ namespace osu.Game.Rulesets.Mania.Tests
ScoreInfo = new ScoreInfo
{
Ruleset = CreateRuleset().RulesetInfo,
- Mods = [new ModScoreV2()]
+ Mods = [new ManiaModScoreV2()]
}
};
RunTest($@"SV2 single note @ OD{overallDifficulty}", beatmap, $@"SV2 {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
}
- [Ignore("Tests expected to fail until stable's detailed treatment of hit windows in mania is reproduced.")]
[TestCaseSource(nameof(score_v1_non_convert_test_cases))]
public void TestHitWindowTreatmentWithScoreV1NonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult)
{
@@ -556,7 +554,6 @@ namespace osu.Game.Rulesets.Mania.Tests
RunTest($@"SV1 single note @ OD{overallDifficulty}", beatmap, $@"SV1 {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
}
- [Ignore("Tests expected to fail until stable's detailed treatment of hit windows in mania is reproduced.")]
[TestCaseSource(nameof(score_v1_convert_test_cases))]
public void TestHitWindowTreatmentWithScoreV1Convert(float overallDifficulty, double hitOffset, HitResult expectedResult)
{
@@ -585,7 +582,6 @@ namespace osu.Game.Rulesets.Mania.Tests
RunTest($@"SV1 convert single note @ OD{overallDifficulty}", beatmap, $@"SV1 convert {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
}
- [Ignore("Tests expected to fail until stable's detailed treatment of hit windows in mania is reproduced.")]
[TestCaseSource(nameof(score_v1_non_convert_hard_rock_test_cases))]
public void TestHitWindowTreatmentWithScoreV1AndHardRockNonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult)
{
@@ -614,7 +610,6 @@ namespace osu.Game.Rulesets.Mania.Tests
RunTest($@"SV1+HR single note @ OD{overallDifficulty}", beatmap, $@"SV1+HR {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
}
- [Ignore("Tests expected to fail until stable's detailed treatment of hit windows in mania is reproduced.")]
[TestCaseSource(nameof(score_v1_non_convert_easy_test_cases))]
public void TestHitWindowTreatmentWithScoreV1AndEasyNonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult)
{
@@ -643,7 +638,6 @@ namespace osu.Game.Rulesets.Mania.Tests
RunTest($@"SV1+EZ single note @ OD{overallDifficulty}", beatmap, $@"SV1+EZ {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
}
- [Ignore("Tests expected to fail until stable's detailed treatment of hit windows in mania is reproduced.")]
[TestCaseSource(nameof(score_v1_non_convert_double_time_test_cases))]
public void TestHitWindowTreatmentWithScoreV1AndDoubleTimeNonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult)
{
@@ -672,7 +666,6 @@ namespace osu.Game.Rulesets.Mania.Tests
RunTest($@"SV1+DT single note @ OD{overallDifficulty}", beatmap, $@"SV1+DT {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
}
- [Ignore("Tests expected to fail until stable's detailed treatment of hit windows in mania is reproduced.")]
[TestCaseSource(nameof(score_v1_non_convert_half_time_test_cases))]
public void TestHitWindowTreatmentWithScoreV1AndHalfTimeNonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult)
{
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index cdc7b0a951..c2bcba38ab 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -161,7 +161,7 @@ namespace osu.Game.Rulesets.Mania
yield return new ManiaModMirror();
if (mods.HasFlag(LegacyMods.ScoreV2))
- yield return new ModScoreV2();
+ yield return new ManiaModScoreV2();
}
public override LegacyMods ConvertToLegacyMods(Mod[] mods)
@@ -296,7 +296,7 @@ namespace osu.Game.Rulesets.Mania
case ModType.System:
return new Mod[]
{
- new ModScoreV2(),
+ new ManiaModScoreV2(),
};
default:
diff --git a/osu.Game.Rulesets.Mania/Mods/IManiaRateAdjustmentMod.cs b/osu.Game.Rulesets.Mania/Mods/IManiaRateAdjustmentMod.cs
index ea01bd4436..ca364a1ec8 100644
--- a/osu.Game.Rulesets.Mania/Mods/IManiaRateAdjustmentMod.cs
+++ b/osu.Game.Rulesets.Mania/Mods/IManiaRateAdjustmentMod.cs
@@ -2,12 +2,10 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Bindables;
-using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Mods
{
@@ -17,29 +15,21 @@ namespace osu.Game.Rulesets.Mania.Mods
///
/// Historically, in osu!mania, hit windows are expected to adjust relative to the gameplay rate such that the real-world hit window remains the same.
///
- public interface IManiaRateAdjustmentMod : IApplicableToDifficulty, IApplicableToHitObject
+ public interface IManiaRateAdjustmentMod : IApplicableToHitObject
{
BindableNumber SpeedChange { get; }
- HitWindows HitWindows { get; set; }
-
- void IApplicableToDifficulty.ApplyToDifficulty(BeatmapDifficulty difficulty)
- {
- HitWindows = new ManiaHitWindows(SpeedChange.Value);
- HitWindows.SetDifficulty(difficulty.OverallDifficulty);
- }
-
void IApplicableToHitObject.ApplyToHitObject(HitObject hitObject)
{
switch (hitObject)
{
case Note:
- hitObject.HitWindows = HitWindows;
+ ((ManiaHitWindows)hitObject.HitWindows).SpeedMultiplier = SpeedChange.Value;
break;
case HoldNote hold:
- hold.Head.HitWindows = HitWindows;
- hold.Tail.HitWindows = HitWindows;
+ ((ManiaHitWindows)hold.Head.HitWindows).SpeedMultiplier = SpeedChange.Value;
+ ((ManiaHitWindows)hold.Tail.HitWindows).SpeedMultiplier = SpeedChange.Value;
break;
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModClassic.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModClassic.cs
index 073dda9de8..5e46250dd2 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModClassic.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModClassic.cs
@@ -1,11 +1,41 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods
{
- public class ManiaModClassic : ModClassic
+ public class ManiaModClassic : ModClassic, IApplicableToBeatmap
{
+ public void ApplyToBeatmap(IBeatmap beatmap)
+ {
+ bool isConvert = !beatmap.BeatmapInfo.Ruleset.Equals(new ManiaRuleset().RulesetInfo);
+
+ foreach (var ho in beatmap.HitObjects)
+ {
+ switch (ho)
+ {
+ case Note note:
+ {
+ var hitWindows = (ManiaHitWindows)note.HitWindows;
+ hitWindows.IsConvert = isConvert;
+ hitWindows.ClassicModActive = true;
+ break;
+ }
+
+ case HoldNote hold:
+ {
+ var headWindows = (ManiaHitWindows)hold.Head.HitWindows;
+ var tailWindows = (ManiaHitWindows)hold.Tail.HitWindows;
+ headWindows.IsConvert = tailWindows.IsConvert = isConvert;
+ headWindows.ClassicModActive = tailWindows.ClassicModActive = true;
+ break;
+ }
+ }
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs
index dbe2a9a9fc..9e9d671006 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs
@@ -1,14 +1,11 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mods;
-using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModDaycore : ModDaycore, IManiaRateAdjustmentMod
{
- public HitWindows HitWindows { get; set; } = new ManiaHitWindows();
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs
index bea1a14110..043fa1c40c 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs
@@ -1,16 +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.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mods;
-using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModDoubleTime : ModDoubleTime, IManiaRateAdjustmentMod
{
- public HitWindows HitWindows { get; set; } = new ManiaHitWindows();
-
// For now, all rate-increasing mods should be given a 1x multiplier in mania because it doesn't always
// make the map harder and is more of a personal preference.
// In the future, we can consider adjusting this by experimenting with not applying the hitwindow leniency.
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs
index 275643ca44..c9a84051d5 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs
@@ -2,12 +2,32 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Localisation;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mania.Mods
{
- public class ManiaModEasy : ModEasyWithExtraLives
+ public class ManiaModEasy : ModEasyWithExtraLives, IApplicableToHitObject
{
public override LocalisableString Description => @"More forgiving HP drain, less accuracy required, and extra lives!";
+
+ void IApplicableToHitObject.ApplyToHitObject(HitObject hitObject)
+ {
+ const double multiplier = 1 / 1.4;
+
+ switch (hitObject)
+ {
+ case Note:
+ ((ManiaHitWindows)hitObject.HitWindows).DifficultyMultiplier = multiplier;
+ break;
+
+ case HoldNote hold:
+ ((ManiaHitWindows)hold.Head.HitWindows).DifficultyMultiplier = multiplier;
+ ((ManiaHitWindows)hold.Tail.HitWindows).DifficultyMultiplier = multiplier;
+ break;
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs
index b0fbb11396..f8d2758914 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs
@@ -1,14 +1,11 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mods;
-using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModHalfTime : ModHalfTime, IManiaRateAdjustmentMod
{
- public HitWindows HitWindows { get; set; } = new ManiaHitWindows();
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs
index 189c4b3a5f..a73bd94566 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs
@@ -1,13 +1,33 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mania.Mods
{
- public class ManiaModHardRock : ModHardRock
+ public class ManiaModHardRock : ModHardRock, IApplicableToHitObject
{
public override double ScoreMultiplier => 1;
public override bool Ranked => false;
+
+ void IApplicableToHitObject.ApplyToHitObject(HitObject hitObject)
+ {
+ const double multiplier = 1.4;
+
+ switch (hitObject)
+ {
+ case Note:
+ ((ManiaHitWindows)hitObject.HitWindows).DifficultyMultiplier = multiplier;
+ break;
+
+ case HoldNote hold:
+ ((ManiaHitWindows)hold.Head.HitWindows).DifficultyMultiplier = multiplier;
+ ((ManiaHitWindows)hold.Tail.HitWindows).DifficultyMultiplier = multiplier;
+ break;
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs
index 7e5e80db6c..0eb4ddc7d0 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs
@@ -2,16 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Mania.Objects;
-using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mods;
-using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModNightcore : ModNightcore, IManiaRateAdjustmentMod
{
- public HitWindows HitWindows { get; set; } = new ManiaHitWindows();
-
// For now, all rate-increasing mods should be given a 1x multiplier in mania because it doesn't always
// make the map any harder and is more of a personal preference.
// In the future, we can consider adjusting this by experimenting with not applying the hitwindow leniency.
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModScoreV2.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModScoreV2.cs
new file mode 100644
index 0000000000..46bb75a480
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModScoreV2.cs
@@ -0,0 +1,37 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.Scoring;
+using osu.Game.Rulesets.Mods;
+
+namespace osu.Game.Rulesets.Mania.Mods
+{
+ public class ManiaModScoreV2 : ModScoreV2, IApplicableToBeatmap
+ {
+ public void ApplyToBeatmap(IBeatmap beatmap)
+ {
+ foreach (var ho in beatmap.HitObjects)
+ {
+ switch (ho)
+ {
+ case Note note:
+ {
+ var hitWindows = (ManiaHitWindows)note.HitWindows;
+ hitWindows.ScoreV2Active = true;
+ break;
+ }
+
+ case HoldNote hold:
+ {
+ var headWindows = (ManiaHitWindows)hold.Head.HitWindows;
+ var tailWindows = (ManiaHitWindows)hold.Tail.HitWindows;
+ headWindows.ScoreV2Active = tailWindows.ScoreV2Active = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs
index 96dbd957ae..fe47b297dd 100644
--- a/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs
+++ b/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs
@@ -16,7 +16,84 @@ namespace osu.Game.Rulesets.Mania.Scoring
private static readonly DifficultyRange meh_window_range = new DifficultyRange(151, 136, 121);
private static readonly DifficultyRange miss_window_range = new DifficultyRange(188, 173, 158);
- private readonly double multiplier;
+ private double speedMultiplier = 1;
+
+ ///
+ /// Multiplier used to compensate for the playback speed of the track speeding up or slowing down.
+ /// The goal of this multiplier is to keep hit windows independent of track speed.
+ ///
+ /// - When the track speed is above 1, the hit window ranges are multiplied by , because the time elapses faster.
+ /// - When the track speed is below 1, the hit window ranges are also multiplied by , because the time elapses slower.
+ ///
+ ///
+ public double SpeedMultiplier
+ {
+ get => speedMultiplier;
+ set
+ {
+ speedMultiplier = value;
+ updateWindows();
+ }
+ }
+
+ private double difficultyMultiplier = 1;
+
+ ///
+ /// Multiplier used to make the gameplay more or less difficult.
+ ///
+ /// - When the is above 1, the hit windows decrease to make the gameplay harder.
+ /// - When the is below 1, the hit windows increase to make the gameplay easier.
+ ///
+ ///
+ public double DifficultyMultiplier
+ {
+ get => difficultyMultiplier;
+ set
+ {
+ difficultyMultiplier = value;
+ updateWindows();
+ }
+ }
+
+ private double totalMultiplier => speedMultiplier / difficultyMultiplier;
+
+ private double overallDifficulty;
+
+ private bool classicModActive;
+
+ public bool ClassicModActive
+ {
+ get => classicModActive;
+ set
+ {
+ classicModActive = value;
+ updateWindows();
+ }
+ }
+
+ private bool scoreV2Active;
+
+ public bool ScoreV2Active
+ {
+ get => scoreV2Active;
+ set
+ {
+ scoreV2Active = value;
+ updateWindows();
+ }
+ }
+
+ private bool isConvert;
+
+ public bool IsConvert
+ {
+ get => isConvert;
+ set
+ {
+ isConvert = value;
+ updateWindows();
+ }
+ }
private double perfect;
private double great;
@@ -25,16 +102,6 @@ namespace osu.Game.Rulesets.Mania.Scoring
private double meh;
private double miss;
- public ManiaHitWindows()
- : this(1)
- {
- }
-
- public ManiaHitWindows(double multiplier)
- {
- this.multiplier = multiplier;
- }
-
public override bool IsHitResultAllowed(HitResult result)
{
switch (result)
@@ -53,12 +120,44 @@ namespace osu.Game.Rulesets.Mania.Scoring
public override void SetDifficulty(double difficulty)
{
- perfect = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, perfect_window_range) * multiplier) + 0.5;
- great = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, great_window_range) * multiplier) + 0.5;
- good = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, good_window_range) * multiplier) + 0.5;
- ok = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, ok_window_range) * multiplier) + 0.5;
- meh = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, meh_window_range) * multiplier) + 0.5;
- miss = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, miss_window_range) * multiplier) + 0.5;
+ overallDifficulty = difficulty;
+ updateWindows();
+ }
+
+ private void updateWindows()
+ {
+ if (ClassicModActive && !ScoreV2Active)
+ {
+ if (IsConvert)
+ {
+ perfect = Math.Floor(16 * totalMultiplier) + 0.5;
+ great = Math.Floor((Math.Round(overallDifficulty) > 4 ? 34 : 47) * totalMultiplier) + 0.5;
+ good = Math.Floor((Math.Round(overallDifficulty) > 4 ? 67 : 77) * totalMultiplier) + 0.5;
+ ok = Math.Floor(97 * totalMultiplier) + 0.5;
+ meh = Math.Floor(121 * totalMultiplier) + 0.5;
+ miss = Math.Floor(158 * totalMultiplier) + 0.5;
+ }
+ else
+ {
+ double invertedOd = Math.Clamp(10 - overallDifficulty, 0, 10);
+
+ perfect = Math.Floor(16 * totalMultiplier) + 0.5;
+ great = Math.Floor((34 + 3 * invertedOd) * totalMultiplier) + 0.5;
+ good = Math.Floor((67 + 3 * invertedOd) * totalMultiplier) + 0.5;
+ ok = Math.Floor((97 + 3 * invertedOd) * totalMultiplier) + 0.5;
+ meh = Math.Floor((121 + 3 * invertedOd) * totalMultiplier) + 0.5;
+ miss = Math.Floor((158 + 3 * invertedOd) * totalMultiplier) + 0.5;
+ }
+ }
+ else
+ {
+ perfect = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(overallDifficulty, perfect_window_range) * totalMultiplier) + 0.5;
+ great = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(overallDifficulty, great_window_range) * totalMultiplier) + 0.5;
+ good = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(overallDifficulty, good_window_range) * totalMultiplier) + 0.5;
+ ok = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(overallDifficulty, ok_window_range) * totalMultiplier) + 0.5;
+ meh = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(overallDifficulty, meh_window_range) * totalMultiplier) + 0.5;
+ miss = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(overallDifficulty, miss_window_range) * totalMultiplier) + 0.5;
+ }
}
public override double WindowFor(HitResult result)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 5c593422fc..966f8da261 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double preempt = IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450) / clockRate;
- overallDifficulty = (80 - greatHitWindow) / 6;
+ overallDifficulty = (79.5 - greatHitWindow) / 6;
approachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5;
double comboBasedEstimatedMissCount = calculateComboBasedEstimatedMissCount(osuAttributes);
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs b/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs
index 97fe0d0bf2..9725a42674 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Localisation;
+using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Osu.Mods
@@ -9,5 +10,12 @@ namespace osu.Game.Rulesets.Osu.Mods
public class OsuModEasy : ModEasyWithExtraLives
{
public override LocalisableString Description => @"Larger circles, more forgiving HP drain, less accuracy required, and extra lives!";
+
+ public override void ApplyToDifficulty(BeatmapDifficulty difficulty)
+ {
+ base.ApplyToDifficulty(difficulty);
+
+ difficulty.OverallDifficulty *= ADJUST_RATIO;
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
index d24597eeed..e7ac63599d 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
@@ -28,6 +28,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
base.ApplyToDifficulty(difficulty);
+ difficulty.OverallDifficulty = Math.Min(difficulty.OverallDifficulty * ADJUST_RATIO, 10.0f);
difficulty.CircleSize = Math.Min(difficulty.CircleSize * 1.3f, 10.0f); // CS uses a custom 1.3 ratio.
difficulty.ApproachRate = Math.Min(difficulty.ApproachRate * ADJUST_RATIO, 10.0f);
}
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs
index 009f2854f8..1bc9277210 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs
@@ -19,6 +19,8 @@ namespace osu.Game.Rulesets.Taiko.Mods
public override void ApplyToDifficulty(BeatmapDifficulty difficulty)
{
base.ApplyToDifficulty(difficulty);
+
+ difficulty.OverallDifficulty *= ADJUST_RATIO;
difficulty.SliderMultiplier *= slider_multiplier;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs
index ba41175461..8f01c21894 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
@@ -23,6 +24,8 @@ namespace osu.Game.Rulesets.Taiko.Mods
public override void ApplyToDifficulty(BeatmapDifficulty difficulty)
{
base.ApplyToDifficulty(difficulty);
+
+ difficulty.OverallDifficulty = Math.Min(difficulty.OverallDifficulty * ADJUST_RATIO, 10.0f);
difficulty.SliderMultiplier *= slider_multiplier;
}
}
diff --git a/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselFilterGroupingTest.cs b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselFilterGroupingTest.cs
index 29b4955d02..2874384c4d 100644
--- a/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselFilterGroupingTest.cs
+++ b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselFilterGroupingTest.cs
@@ -333,22 +333,32 @@ namespace osu.Game.Tests.Visual.SongSelectV2
#endregion
+ #region Source grouping
+
+ [Test]
+ public async Task TestGroupingBySource()
+ {
+ int total = 0;
+
+ var beatmapSets = new List();
+ addBeatmapSet(s => s.Beatmaps[0].Metadata.Source = "Cool Game", beatmapSets, out var beatmapCoolGame);
+ addBeatmapSet(s => s.Beatmaps[0].Metadata.Source = "Cool game", beatmapSets, out var beatmapCoolGameB);
+ addBeatmapSet(s => s.Beatmaps[0].Metadata.Source = "Nice Movie", beatmapSets, out var beatmapNiceMovie);
+ addBeatmapSet(s => s.Beatmaps[0].Metadata.Source = string.Empty, beatmapSets, out var beatmapUnsourced);
+
+ var results = await runGrouping(GroupMode.Source, beatmapSets);
+ assertGroup(results, 0, "Cool Game", new[] { beatmapCoolGame, beatmapCoolGameB }, ref total);
+ assertGroup(results, 1, "Nice Movie", new[] { beatmapNiceMovie }, ref total);
+ assertGroup(results, 2, "Unsourced", new[] { beatmapUnsourced }, ref total);
+ assertTotal(results, total);
+ }
+
+ #endregion
+
private static async Task> runGrouping(GroupMode group, List beatmapSets)
{
var groupingFilter = new BeatmapCarouselFilterGrouping(() => new FilterCriteria { Group = group });
- var carouselItems = await groupingFilter.Run(beatmapSets.SelectMany(s => s.Beatmaps.Select(b => new CarouselItem(b))).ToList(), CancellationToken.None);
-
- // sanity check to ensure no detection of two group items with equal order value.
- var groups = carouselItems.Select(i => i.Model).OfType();
-
- foreach (var header in groups)
- {
- var sameOrder = groups.FirstOrDefault(g => g != header && g.Order == header.Order);
- if (sameOrder != null)
- Assert.Fail($"Detected two groups with equal order number: \"{header.Title}\" vs. \"{sameOrder.Title}\"");
- }
-
- return carouselItems;
+ return await groupingFilter.Run(beatmapSets.SelectMany(s => s.Beatmaps.Select(b => new CarouselItem(b))).ToList(), CancellationToken.None);
}
private static void assertGroup(List items, int index, string expectedTitle, IEnumerable expectedBeatmapSets, ref int totalItems)
diff --git a/osu.Game/Localisation/MenuTipStrings.cs b/osu.Game/Localisation/MenuTipStrings.cs
index 977c0928b2..4d1f2ceaa6 100644
--- a/osu.Game/Localisation/MenuTipStrings.cs
+++ b/osu.Game/Localisation/MenuTipStrings.cs
@@ -10,14 +10,14 @@ namespace osu.Game.Localisation
private const string prefix = @"osu.Game.Resources.Localisation.MenuTip";
///
- /// "Press Ctrl-T anywhere in the game to toggle the toolbar!"
+ /// "Press {0} anywhere in the game to toggle the toolbar!"
///
- public static LocalisableString ToggleToolbarShortcut => new TranslatableString(getKey(@"toggle_toolbar_shortcut"), @"Press Ctrl-T anywhere in the game to toggle the toolbar!");
+ public static LocalisableString ToggleToolbarShortcut(LocalisableString keybind) => new TranslatableString(getKey(@"toggle_toolbar_shortcut"), @"Press {0} anywhere in the game to toggle the toolbar!", keybind);
///
- /// "Press Ctrl-O anywhere in the game to access settings!"
+ /// "Press {0} anywhere in the game to access settings!"
///
- public static LocalisableString GameSettingsShortcut => new TranslatableString(getKey(@"game_settings_shortcut"), @"Press Ctrl-O anywhere in the game to access settings!");
+ public static LocalisableString GameSettingsShortcut(LocalisableString keybind) => new TranslatableString(getKey(@"game_settings_shortcut"), @"Press {0} anywhere in the game to access settings!", keybind);
///
/// "All settings are dynamic and take effect in real-time. Try changing the skin while watching autoplay!"
@@ -40,9 +40,9 @@ namespace osu.Game.Localisation
public static LocalisableString ScreenScalingSettings => new TranslatableString(getKey(@"screen_scaling_settings"), @"Try adjusting the ""Screen Scaling"" mode to change your gameplay or UI area, even in fullscreen!");
///
- /// "What used to be "osu!direct" is available to all users just like on the website. You can access it anywhere using Ctrl-B!"
+ /// "What used to be "osu!direct" is available to all users just like on the website. You can access it anywhere using {0}!"
///
- public static LocalisableString FreeOsuDirect => new TranslatableString(getKey(@"free_osu_direct"), @"What used to be ""osu!direct"" is available to all users just like on the website. You can access it anywhere using Ctrl-B!");
+ public static LocalisableString FreeOsuDirect(LocalisableString keybind) => new TranslatableString(getKey(@"free_osu_direct"), @"What used to be ""osu!direct"" is available to all users just like on the website. You can access it anywhere using {0}!", keybind);
///
/// "Seeking in replays is available by dragging on the progress bar at the bottom of the screen or by using the left and right arrow keys!"
@@ -75,9 +75,9 @@ namespace osu.Game.Localisation
public static LocalisableString ToggleAdvancedFPSCounter => new TranslatableString(getKey(@"toggle_advanced_fps_counter"), @"Toggle advanced frame / thread statistics with Ctrl-F11!");
///
- /// "You can pause during a replay by pressing Space!"
+ /// "You can pause during a replay by pressing {0}!"
///
- public static LocalisableString ReplayPausing => new TranslatableString(getKey(@"replay_pausing"), @"You can pause during a replay by pressing Space!");
+ public static LocalisableString ReplayPausing(LocalisableString keybind) => new TranslatableString(getKey(@"replay_pausing"), @"You can pause during a replay by pressing {0}!", keybind);
///
/// "Most of the hotkeys in the game are configurable and can be changed to anything you want. Check the bindings panel under input settings!"
@@ -85,9 +85,9 @@ namespace osu.Game.Localisation
public static LocalisableString ConfigurableHotkeys => new TranslatableString(getKey(@"configurable_hotkeys"), @"Most of the hotkeys in the game are configurable and can be changed to anything you want. Check the bindings panel under input settings!");
///
- /// "Your gameplay HUD can be customised by using the skin layout editor. Open it at any time via Ctrl-Shift-S!"
+ /// "Your gameplay HUD can be customised by using the skin layout editor. Open it at any time via {0}!"
///
- public static LocalisableString SkinEditor => new TranslatableString(getKey(@"skin_editor"), @"Your gameplay HUD can be customised by using the skin layout editor. Open it at any time via Ctrl-Shift-S!");
+ public static LocalisableString SkinEditor(LocalisableString keybind) => new TranslatableString(getKey(@"skin_editor"), @"Your gameplay HUD can be customised by using the skin layout editor. Open it at any time via {0}!", keybind);
///
/// "You can create mod presets to make toggling your favourite mod combinations easier!"
@@ -100,14 +100,14 @@ namespace osu.Game.Localisation
public static LocalisableString ModCustomisationSettings => new TranslatableString(getKey(@"mod_customisation_settings"), @"Many mods have customisation settings that drastically change how they function. Click the Customise button in mod select to view settings!");
///
- /// "Press Ctrl-Shift-R to switch to a random skin!"
+ /// "Press {0} to switch to a random skin!"
///
- public static LocalisableString RandomSkinShortcut => new TranslatableString(getKey(@"random_skin_shortcut"), @"Press Ctrl-Shift-R to switch to a random skin!");
+ public static LocalisableString RandomSkinShortcut(LocalisableString keybind) => new TranslatableString(getKey(@"random_skin_shortcut"), @"Press {0} to switch to a random skin!", keybind);
///
- /// "While watching a replay, press Ctrl-H to toggle replay settings!"
+ /// "While watching a replay, press {0} to toggle replay settings!"
///
- public static LocalisableString ToggleReplaySettingsShortcut => new TranslatableString(getKey(@"toggle_replay_settings_shortcut"), @"While watching a replay, press Ctrl-H to toggle replay settings!");
+ public static LocalisableString ToggleReplaySettingsShortcut(LocalisableString keybind) => new TranslatableString(getKey(@"toggle_replay_settings_shortcut"), @"While watching a replay, press {0} to toggle replay settings!", keybind);
///
/// "You can easily copy the mods from scores on a leaderboard by right-clicking on them!"
@@ -140,9 +140,9 @@ namespace osu.Game.Localisation
public static LocalisableString GlobalStatisticsShortcut => new TranslatableString(getKey(@"global_statistics_shortcut"), @"Take a look under the hood at performance counters and enable verbose performance logging with Ctrl-F2!");
///
- /// "When your gameplay HUD is hidden, you can press and hold Ctrl to view it temporarily!"
+ /// "When your gameplay HUD is hidden, you can press and hold {0} to view it temporarily!"
///
- public static LocalisableString PeekHUDWhenHidden => new TranslatableString(getKey(@"peek_hud_when_hidden"), @"When your gameplay HUD is hidden, you can press and hold Ctrl to view it temporarily!");
+ public static LocalisableString PeekHUDWhenHidden(LocalisableString keybind) => new TranslatableString(getKey(@"peek_hud_when_hidden"), @"When your gameplay HUD is hidden, you can press and hold {0} to view it temporarily!", keybind);
///
/// "Drag and drop any image into the skin editor to load it in quickly!"
diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs
index 3ee4d7846e..0ee384c0f7 100644
--- a/osu.Game/Rulesets/Mods/ModEasy.cs
+++ b/osu.Game/Rulesets/Mods/ModEasy.cs
@@ -19,13 +19,13 @@ namespace osu.Game.Rulesets.Mods
public override bool Ranked => UsesDefaultConfiguration;
public override bool ValidForFreestyleAsRequiredMod => true;
+ protected const float ADJUST_RATIO = 0.5f;
+
public virtual void ApplyToDifficulty(BeatmapDifficulty difficulty)
{
- const float ratio = 0.5f;
- difficulty.CircleSize *= ratio;
- difficulty.ApproachRate *= ratio;
- difficulty.DrainRate *= ratio;
- difficulty.OverallDifficulty *= ratio;
+ difficulty.CircleSize *= ADJUST_RATIO;
+ difficulty.ApproachRate *= ADJUST_RATIO;
+ difficulty.DrainRate *= ADJUST_RATIO;
}
}
}
diff --git a/osu.Game/Rulesets/Mods/ModHardRock.cs b/osu.Game/Rulesets/Mods/ModHardRock.cs
index 6149a9c712..713bfe0623 100644
--- a/osu.Game/Rulesets/Mods/ModHardRock.cs
+++ b/osu.Game/Rulesets/Mods/ModHardRock.cs
@@ -25,7 +25,6 @@ namespace osu.Game.Rulesets.Mods
public virtual void ApplyToDifficulty(BeatmapDifficulty difficulty)
{
difficulty.DrainRate = Math.Min(difficulty.DrainRate * ADJUST_RATIO, 10.0f);
- difficulty.OverallDifficulty = Math.Min(difficulty.OverallDifficulty * ADJUST_RATIO, 10.0f);
}
}
}
diff --git a/osu.Game/Rulesets/Mods/ModScoreV2.cs b/osu.Game/Rulesets/Mods/ModScoreV2.cs
index 6a77cafa30..854f3916a1 100644
--- a/osu.Game/Rulesets/Mods/ModScoreV2.cs
+++ b/osu.Game/Rulesets/Mods/ModScoreV2.cs
@@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Mods
/// This mod is used strictly to mark osu!stable scores set with the "Score V2" mod active.
/// It should not be used in any real capacity going forward.
///
- public sealed class ModScoreV2 : Mod
+ public class ModScoreV2 : Mod
{
public override string Name => "Score V2";
public override string Acronym => @"SV2";
diff --git a/osu.Game/Screens/Menu/MenuTipDisplay.cs b/osu.Game/Screens/Menu/MenuTipDisplay.cs
index 9430d65433..d9e38e8aa0 100644
--- a/osu.Game/Screens/Menu/MenuTipDisplay.cs
+++ b/osu.Game/Screens/Menu/MenuTipDisplay.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -12,6 +13,8 @@ using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
+using osu.Game.Input;
+using osu.Game.Input.Bindings;
using osuTK;
using osuTK.Graphics;
using osu.Game.Localisation;
@@ -27,6 +30,9 @@ namespace osu.Game.Screens.Menu
private Bindable showMenuTips = null!;
+ [Resolved]
+ private RealmKeyBindingStore keyBindingStore { get; set; } = null!;
+
[BackgroundDependencyLoader]
private void load()
{
@@ -97,42 +103,103 @@ namespace osu.Game.Screens.Menu
.FadeOutFromOne(2000, Easing.OutQuint);
}
+ private const int available_tips = 29;
+
private LocalisableString getRandomTip()
{
- LocalisableString[] tips =
- {
- MenuTipStrings.ToggleToolbarShortcut,
- MenuTipStrings.GameSettingsShortcut,
- MenuTipStrings.DynamicSettings,
- MenuTipStrings.NewFeaturesAreComingOnline,
- MenuTipStrings.UIScalingSettings,
- MenuTipStrings.ScreenScalingSettings,
- MenuTipStrings.FreeOsuDirect,
- MenuTipStrings.ReplaySeeking,
- MenuTipStrings.MultithreadingSupport,
- MenuTipStrings.TryNewMods,
- MenuTipStrings.EmbeddedWebContent,
- MenuTipStrings.BeatmapRightClick,
- MenuTipStrings.TemporaryDeleteOperations,
- MenuTipStrings.DiscoverPlaylists,
- MenuTipStrings.ToggleAdvancedFPSCounter,
- MenuTipStrings.GlobalStatisticsShortcut,
- MenuTipStrings.ReplayPausing,
- MenuTipStrings.ConfigurableHotkeys,
- MenuTipStrings.PeekHUDWhenHidden,
- MenuTipStrings.SkinEditor,
- MenuTipStrings.DragAndDropImageInSkinEditor,
- MenuTipStrings.ModPresets,
- MenuTipStrings.ModCustomisationSettings,
- MenuTipStrings.RandomSkinShortcut,
- MenuTipStrings.ToggleReplaySettingsShortcut,
- MenuTipStrings.CopyModsFromScore,
- MenuTipStrings.AutoplayBeatmapShortcut,
- MenuTipStrings.LazerIsNotAWord,
- MenuTipStrings.RightMouseAbsoluteScroll,
- };
+ int tipIndex = RNG.Next(0, available_tips);
- return tips[RNG.Next(0, tips.Length)];
+ switch (tipIndex)
+ {
+ case 0:
+ return MenuTipStrings.ToggleToolbarShortcut(keyBindingStore.GetReadableKeyCombinationsFor(GlobalAction.ToggleToolbar).FirstOrDefault() ?? InputSettingsStrings.ActionHasNoKeyBinding);
+
+ case 1:
+ return MenuTipStrings.GameSettingsShortcut(keyBindingStore.GetReadableKeyCombinationsFor(GlobalAction.ToggleSettings).FirstOrDefault() ?? InputSettingsStrings.ActionHasNoKeyBinding);
+
+ case 2:
+ return MenuTipStrings.DynamicSettings;
+
+ case 3:
+ return MenuTipStrings.NewFeaturesAreComingOnline;
+
+ case 4:
+ return MenuTipStrings.UIScalingSettings;
+
+ case 5:
+ return MenuTipStrings.ScreenScalingSettings;
+
+ case 6:
+ return MenuTipStrings.FreeOsuDirect(keyBindingStore.GetReadableKeyCombinationsFor(GlobalAction.ToggleBeatmapListing).FirstOrDefault() ?? InputSettingsStrings.ActionHasNoKeyBinding);
+
+ case 7:
+ return MenuTipStrings.ReplaySeeking;
+
+ case 8:
+ return MenuTipStrings.MultithreadingSupport;
+
+ case 9:
+ return MenuTipStrings.TryNewMods;
+
+ case 10:
+ return MenuTipStrings.EmbeddedWebContent;
+
+ case 11:
+ return MenuTipStrings.BeatmapRightClick;
+
+ case 12:
+ return MenuTipStrings.TemporaryDeleteOperations;
+
+ case 13:
+ return MenuTipStrings.DiscoverPlaylists;
+
+ case 14:
+ return MenuTipStrings.ToggleAdvancedFPSCounter;
+
+ case 15:
+ return MenuTipStrings.GlobalStatisticsShortcut;
+
+ case 16:
+ return MenuTipStrings.ReplayPausing(keyBindingStore.GetReadableKeyCombinationsFor(GlobalAction.TogglePauseReplay).FirstOrDefault() ?? InputSettingsStrings.ActionHasNoKeyBinding);
+
+ case 17:
+ return MenuTipStrings.ConfigurableHotkeys;
+
+ case 18:
+ return MenuTipStrings.PeekHUDWhenHidden(keyBindingStore.GetReadableKeyCombinationsFor(GlobalAction.HoldForHUD).FirstOrDefault() ?? InputSettingsStrings.ActionHasNoKeyBinding);
+
+ case 19:
+ return MenuTipStrings.SkinEditor(keyBindingStore.GetReadableKeyCombinationsFor(GlobalAction.ToggleSkinEditor).FirstOrDefault() ?? InputSettingsStrings.ActionHasNoKeyBinding);
+
+ case 20:
+ return MenuTipStrings.DragAndDropImageInSkinEditor;
+
+ case 21:
+ return MenuTipStrings.ModPresets;
+
+ case 22:
+ return MenuTipStrings.ModCustomisationSettings;
+
+ case 23:
+ return MenuTipStrings.RandomSkinShortcut(keyBindingStore.GetReadableKeyCombinationsFor(GlobalAction.RandomSkin).FirstOrDefault() ?? InputSettingsStrings.ActionHasNoKeyBinding);
+
+ case 24:
+ return MenuTipStrings.ToggleReplaySettingsShortcut(keyBindingStore.GetReadableKeyCombinationsFor(GlobalAction.ToggleReplaySettings).FirstOrDefault() ?? InputSettingsStrings.ActionHasNoKeyBinding);
+
+ case 25:
+ return MenuTipStrings.CopyModsFromScore;
+
+ case 26:
+ return MenuTipStrings.AutoplayBeatmapShortcut;
+
+ case 27:
+ return MenuTipStrings.LazerIsNotAWord;
+
+ case 28:
+ return MenuTipStrings.RightMouseAbsoluteScroll;
+ }
+
+ return string.Empty;
}
}
}
diff --git a/osu.Game/Screens/Select/Filter/GroupMode.cs b/osu.Game/Screens/Select/Filter/GroupMode.cs
index b3a4f36c91..04aef2fe18 100644
--- a/osu.Game/Screens/Select/Filter/GroupMode.cs
+++ b/osu.Game/Screens/Select/Filter/GroupMode.cs
@@ -34,6 +34,9 @@ namespace osu.Game.Screens.Select.Filter
// [Description("Favourites")]
// Favourites,
+ [Description("Last Played")]
+ LastPlayed,
+
[Description("Length")]
Length,
@@ -46,8 +49,8 @@ namespace osu.Game.Screens.Select.Filter
[Description("Ranked Status")]
RankedStatus,
- [Description("Last Played")]
- LastPlayed,
+ [Description("Source")]
+ Source,
[Description("Title")]
Title,
diff --git a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs
index 4119807692..1b8d8b506d 100644
--- a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs
+++ b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs
@@ -822,9 +822,31 @@ namespace osu.Game.Screens.SelectV2
///
/// Defines a grouping header for a set of carousel items.
///
- /// The order of this group in the carousel, sorted using ascending order.
- /// The title of this group.
- public record GroupDefinition(int Order, string Title);
+ public record GroupDefinition
+ {
+ ///
+ /// The order of this group in the carousel, sorted using ascending order.
+ ///
+ public int Order { get; }
+
+ ///
+ /// The title of this group.
+ ///
+ public string Title { get; }
+
+ private readonly string uncasedTitle;
+
+ public GroupDefinition(int order, string title)
+ {
+ Order = order;
+ Title = title;
+ uncasedTitle = title.ToLowerInvariant();
+ }
+
+ public virtual bool Equals(GroupDefinition? other) => uncasedTitle == other?.uncasedTitle;
+
+ public override int GetHashCode() => HashCode.Combine(uncasedTitle);
+ }
///
/// Defines a grouping header for a set of carousel items grouped by star difficulty.
diff --git a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs
index 772d4123c2..eb55e03d6b 100644
--- a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs
+++ b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs
@@ -202,6 +202,9 @@ namespace osu.Game.Screens.SelectV2
return defineGroupByLength(length);
}, items);
+ case GroupMode.Source:
+ return getGroupsBy(b => defineGroupBySource(b.BeatmapSet!.Metadata.Source), items);
+
// TODO: need implementation
//
// case GroupMode.Collections:
@@ -225,6 +228,7 @@ namespace osu.Game.Screens.SelectV2
{
return items.GroupBy(i => getGroup((BeatmapInfo)i.Model))
.OrderBy(s => s.Key.Order)
+ .ThenBy(s => s.Key.Title)
.Select(g => new GroupMapping(g.Key, g.ToList()))
.ToList();
}
@@ -355,6 +359,14 @@ namespace osu.Game.Screens.SelectV2
return new GroupDefinition(11, "Over 10 minutes");
}
+ private GroupDefinition defineGroupBySource(string source)
+ {
+ if (string.IsNullOrEmpty(source))
+ return new GroupDefinition(1, "Unsourced");
+
+ return new GroupDefinition(0, source);
+ }
+
private static T? aggregateMax(BeatmapInfo b, Func func)
{
var beatmaps = b.BeatmapSet!.Beatmaps.Where(bb => !bb.Hidden);