1
0
mirror of https://github.com/ppy/osu.git synced 2026-06-10 04:53:40 +08:00

Compare commits

...

9 Commits

28 changed files with 769 additions and 158 deletions
@@ -2,8 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Tests.Rulesets;
namespace osu.Game.Rulesets.Catch.Tests
@@ -110,7 +114,7 @@ namespace osu.Game.Rulesets.Catch.Tests
#region Conversion
[new Mod[] { new CatchModDifficultyAdjust() }, 0.5],
[new Mod[] { new CatchModClassic() }, 0.96],
[new Mod[] { new CatchModClassic() }, 1],
[new Mod[] { new CatchModMirror() }, 1],
#endregion
@@ -151,5 +155,17 @@ namespace osu.Game.Rulesets.Catch.Tests
[TestCaseSource(nameof(test_cases))]
public void TestMultipliers(Mod[] mods, double expectedMultiplier)
=> TestModCombination(mods, expectedMultiplier);
[TestCase(30000001, 0.96)]
[TestCase(30000009, 0.96)]
[TestCase(30000016, 0.96)]
[TestCase(30000017, 1)]
[TestCase(null, 1)]
public void TestClassicMultiplierVersioning(int? totalScoreVersion, double expectedMultiplier)
{
var scoreInfo = totalScoreVersion != null ? new ScoreInfo { TotalScoreVersion = totalScoreVersion.Value } : null;
var calculator = Ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty(), scoreInfo));
Assert.That(calculator.CalculateFor([new CatchModClassic()]), Is.EqualTo(expectedMultiplier).Within(Precision.DOUBLE_EPSILON));
}
}
}
@@ -4,6 +4,7 @@
using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
namespace osu.Game.Rulesets.Catch.Scoring
{
@@ -37,7 +38,7 @@ namespace osu.Game.Rulesets.Catch.Scoring
#region Conversion
Single<CatchModDifficultyAdjust>(hasMultiplier: 0.5);
Single<CatchModClassic>(hasMultiplier: 0.96);
Single<CatchModClassic>(hasMultiplier: _ => classicMultiplier(context.Score));
// Mirror
#endregion
@@ -82,5 +83,13 @@ namespace osu.Game.Rulesets.Catch.Scoring
else
return 0.6 + value;
}
private static double classicMultiplier(ScoreInfo? score)
{
if (score != null && score.TotalScoreVersion < 30000017)
return 0.96;
return 1;
}
}
}
@@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Mania.Tests
[new Mod[] { new ManiaModDualStages() }, 1],
[new Mod[] { new ManiaModMirror() }, 1],
[new Mod[] { new ManiaModDifficultyAdjust() }, 0.5],
[new Mod[] { new ManiaModClassic() }, 0.96],
[new Mod[] { new ManiaModClassic() }, 1],
[new Mod[] { new ManiaModInvert() }, 1],
[new Mod[] { new ManiaModConstantSpeed() }, 0.9],
[new Mod[] { new ManiaModHoldOff() }, 0.9],
@@ -220,5 +220,17 @@ namespace osu.Game.Rulesets.Mania.Tests
}));
Assert.That(calculator.CalculateFor([new ManiaModKey4()]), Is.EqualTo(expectedMultiplier).Within(Precision.DOUBLE_EPSILON));
}
[TestCase(30000001, 0.96)]
[TestCase(30000009, 0.96)]
[TestCase(30000016, 0.96)]
[TestCase(30000017, 1)]
[TestCase(null, 1)]
public void TestClassicMultiplierVersioning(int? totalScoreVersion, double expectedMultiplier)
{
var scoreInfo = totalScoreVersion != null ? new ScoreInfo { TotalScoreVersion = totalScoreVersion.Value } : null;
var calculator = Ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty(), scoreInfo));
Assert.That(calculator.CalculateFor([new ManiaModClassic()]), Is.EqualTo(expectedMultiplier).Within(Precision.DOUBLE_EPSILON));
}
}
}
@@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.Scoring
// Dual Stages
// Mirror
Single<ManiaModDifficultyAdjust>(hasMultiplier: 0.5);
Single<ManiaModClassic>(hasMultiplier: 0.96);
Single<ManiaModClassic>(hasMultiplier: _ => classicMultiplier(Context.Score));
// Invert
Single<ManiaModConstantSpeed>(hasMultiplier: 0.9);
Single<ManiaModHoldOff>(hasMultiplier: 0.9);
@@ -142,5 +142,13 @@ namespace osu.Game.Rulesets.Mania.Scoring
return new_key_mod_multiplier;
}
private static double classicMultiplier(ScoreInfo? score)
{
if (score != null && score.TotalScoreVersion < 30000017)
return 0.96;
return 1;
}
}
}
@@ -17,6 +17,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
{
public partial class TestSceneOsuModDifficultyAdjust : OsuModTestScene
{
protected override bool AllowFail => true;
[Test]
public void TestNoAdjustment() => CreateModTest(new ModTestData
{
@@ -72,6 +74,88 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
PassCondition = () => checkSomeHit() && checkObjectsPreempt(450)
});
[Test]
public void TestScoreMultiplierCorrectWithNoAdjustment() => CreateModTest(new ModTestData
{
Mod = new OsuModDifficultyAdjust(),
CreateBeatmap = () => new Beatmap
{
BeatmapInfo = new BeatmapInfo
{
Difficulty = new BeatmapDifficulty
{
CircleSize = 8
}
},
HitObjects = new List<HitObject>
{
new HitCircle { StartTime = 1000 },
new HitCircle { StartTime = 2000 }
}
},
Autoplay = true,
PassCondition = () => Player.ScoreProcessor.TotalScore.Value == 1_000_000,
});
[Test]
public void TestScoreMultiplierCorrectWithSingleAdjustment() => CreateModTest(new ModTestData
{
Mod = new OsuModDifficultyAdjust
{
ApproachRate = { Value = 7.3f }
},
CreateBeatmap = () => new Beatmap
{
BeatmapInfo = new BeatmapInfo
{
Difficulty = new BeatmapDifficulty
{
CircleSize = 8,
ApproachRate = 7,
OverallDifficulty = 6,
DrainRate = 5,
}
},
HitObjects = new List<HitObject>
{
new HitCircle { StartTime = 1000 },
new HitCircle { StartTime = 2000 }
}
},
Autoplay = true,
PassCondition = () => Player.ScoreProcessor.TotalScore.Value == 850_000,
});
[Test]
public void TestScoreMultiplierCorrectWithMultipleAdjustments() => CreateModTest(new ModTestData
{
Mod = new OsuModDifficultyAdjust
{
ApproachRate = { Value = 6.8f },
OverallDifficulty = { Value = 6.6f }
},
CreateBeatmap = () => new Beatmap
{
BeatmapInfo = new BeatmapInfo
{
Difficulty = new BeatmapDifficulty
{
CircleSize = 8,
ApproachRate = 7,
OverallDifficulty = 6,
DrainRate = 5,
}
},
HitObjects = new List<HitObject>
{
new HitCircle { StartTime = 1000 },
new HitCircle { StartTime = 2000 }
}
},
Autoplay = true,
PassCondition = () => Player.ScoreProcessor.TotalScore.Value == 630_000,
});
private bool checkObjectsPreempt(double target)
{
var objects = Player.ChildrenOfType<DrawableHitCircle>();
@@ -2,8 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Tests.Rulesets;
namespace osu.Game.Rulesets.Osu.Tests
@@ -19,94 +23,106 @@ namespace osu.Game.Rulesets.Osu.Tests
[
#region Difficulty Reduction
[new Mod[] { new OsuModEasy() }, 0.5],
[new Mod[] { new OsuModEasy() }, 0.8],
[new Mod[] { new OsuModEasy { Retries = { Value = 1 } } }, 0.8],
[new Mod[] { new OsuModEasy { Retries = { Value = 3 } } }, 0.7],
[new Mod[] { new OsuModEasy { Retries = { Value = 5 } } }, 0.5],
[new Mod[] { new OsuModEasy { Retries = { Value = 8 } } }, 0.4],
[new Mod[] { new OsuModNoFail() }, 0.5],
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.50 } } }, 0.1],
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.55 } } }, 0.1],
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.60 } } }, 0.2],
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.65 } } }, 0.2],
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.70 } } }, 0.3],
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.75 } } }, 0.3],
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.80 } } }, 0.4],
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.85 } } }, 0.4],
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.90 } } }, 0.5],
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.95 } } }, 0.5],
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.99 } } }, 0.5],
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.50 } } }, 0.20],
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.55 } } }, 0.27],
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.60 } } }, 0.34],
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.65 } } }, 0.41],
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.70 } } }, 0.48],
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.75 } } }, 0.55],
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.80 } } }, 0.62],
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.85 } } }, 0.69],
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.90 } } }, 0.76],
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.95 } } }, 0.83],
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.99 } } }, 0.83],
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.50 } } }, 0.1],
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.55 } } }, 0.1],
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.60 } } }, 0.2],
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.65 } } }, 0.2],
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.70 } } }, 0.3],
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.75 } } }, 0.3],
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.80 } } }, 0.4],
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.85 } } }, 0.4],
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.90 } } }, 0.5],
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.95 } } }, 0.5],
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.99 } } }, 0.5],
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.50 } } }, 0.20],
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.55 } } }, 0.27],
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.60 } } }, 0.34],
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.65 } } }, 0.41],
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.70 } } }, 0.48],
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.75 } } }, 0.55],
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.80 } } }, 0.62],
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.85 } } }, 0.69],
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.90 } } }, 0.76],
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.95 } } }, 0.83],
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.99 } } }, 0.83],
#endregion
#region Difficulty Increase
[new Mod[] { new OsuModHardRock() }, 1.06],
[new Mod[] { new OsuModHardRock() }, 1.09],
[new Mod[] { new OsuModSuddenDeath() }, 1],
[new Mod[] { new OsuModPerfect() }, 1],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.01 } } }, 1.00],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.05 } } }, 1.00],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.10 } } }, 1.02],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.15 } } }, 1.02],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.20 } } }, 1.04],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } }, 1.04],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.30 } } }, 1.06],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.35 } } }, 1.06],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.40 } } }, 1.08],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.45 } } }, 1.08],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.50 } } }, 1.10],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.55 } } }, 1.10],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.60 } } }, 1.12],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.65 } } }, 1.12],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.70 } } }, 1.14],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.75 } } }, 1.14],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.80 } } }, 1.16],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.85 } } }, 1.16],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.90 } } }, 1.18],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.95 } } }, 1.18],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2.00 } } }, 1.20],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.01 } } }, 1.000],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.05 } } }, 1.000],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.10 } } }, 1.036],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.15 } } }, 1.036],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.20 } } }, 1.082],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } }, 1.082],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.30 } } }, 1.128],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.35 } } }, 1.128],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.40 } } }, 1.174],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.45 } } }, 1.174],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.50 } } }, 1.230],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.55 } } }, 1.230],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.60 } } }, 1.266],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.65 } } }, 1.266],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.70 } } }, 1.312],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.75 } } }, 1.312],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.80 } } }, 1.358],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.85 } } }, 1.358],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.90 } } }, 1.404],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.95 } } }, 1.404],
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2.00 } } }, 1.450],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.01 } } }, 1.00],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.05 } } }, 1.00],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.10 } } }, 1.02],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.15 } } }, 1.02],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.20 } } }, 1.04],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.25 } } }, 1.04],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.30 } } }, 1.06],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.35 } } }, 1.06],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.40 } } }, 1.08],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.45 } } }, 1.08],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.50 } } }, 1.10],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.55 } } }, 1.10],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.60 } } }, 1.12],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.65 } } }, 1.12],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.70 } } }, 1.14],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.75 } } }, 1.14],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.80 } } }, 1.16],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.85 } } }, 1.16],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.90 } } }, 1.18],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.95 } } }, 1.18],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 2.00 } } }, 1.20],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.01 } } }, 1.000],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.05 } } }, 1.000],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.10 } } }, 1.036],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.15 } } }, 1.036],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.20 } } }, 1.082],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.25 } } }, 1.082],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.30 } } }, 1.128],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.35 } } }, 1.128],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.40 } } }, 1.174],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.45 } } }, 1.174],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.50 } } }, 1.230],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.55 } } }, 1.230],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.60 } } }, 1.266],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.65 } } }, 1.266],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.70 } } }, 1.312],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.75 } } }, 1.312],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.80 } } }, 1.358],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.85 } } }, 1.358],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.90 } } }, 1.404],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.95 } } }, 1.404],
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 2.00 } } }, 1.450],
[new Mod[] { new OsuModHidden() }, 1.06],
[new Mod[] { new OsuModHidden { OnlyFadeApproachCircles = { Value = true } } }, 1],
[new Mod[] { new OsuModHidden() }, 1.04],
[new Mod[] { new OsuModHidden { OnlyFadeApproachCircles = { Value = true } } }, 1.02],
[new Mod[] { new OsuModTraceable() }, 1],
[new Mod[] { new OsuModTraceable() }, 1.02],
[new Mod[] { new OsuModFlashlight() }, 1.12],
[new Mod[] { new OsuModFlashlight { ComboBasedSize = { Value = false } } }, 1],
[new Mod[] { new OsuModFlashlight() }, 1.2],
[new Mod[] { new OsuModFlashlight { SizeMultiplier = { Value = 0.5f } } }, 1.2],
[new Mod[] { new OsuModFlashlight { SizeMultiplier = { Value = 0.9f } } }, 1.2],
[new Mod[] { new OsuModFlashlight { SizeMultiplier = { Value = 1.1f } } }, 1.18],
[new Mod[] { new OsuModFlashlight { SizeMultiplier = { Value = 1.5f } } }, 1.1],
[new Mod[] { new OsuModFlashlight { SizeMultiplier = { Value = 1.9f } } }, 1.02],
[new Mod[] { new OsuModFlashlight { SizeMultiplier = { Value = 2f } } }, 1.02],
[new Mod[] { new OsuModFlashlight { ComboBasedSize = { Value = false } } }, 1.04],
[new Mod[] { new OsuModFlashlight { SizeMultiplier = { Value = 1.9f }, ComboBasedSize = { Value = false } } }, 1.004],
[new Mod[] { new OsuModBlinds() }, 1.12],
[new Mod[] { new OsuModBlinds() }, 1.24],
[new Mod[] { new OsuModStrictTracking() }, 1],
[new Mod[] { new OsuModAccuracyChallenge() }, 1],
@@ -114,10 +130,13 @@ namespace osu.Game.Rulesets.Osu.Tests
#region Conversion
[new Mod[] { new OsuModTargetPractice() }, 0.1],
[new Mod[] { new OsuModDifficultyAdjust() }, 0.5],
[new Mod[] { new OsuModClassic() }, 0.96],
[new Mod[] { new OsuModRandom() }, 1],
[new Mod[] { new OsuModTargetPractice() }, 0.01],
[new Mod[] { new OsuModDifficultyAdjust() }, 1],
[new Mod[] { new OsuModClassic() }, 0.985],
[new Mod[] { new OsuModClassic { ClassicNoteLock = { Value = false } } }, 0.96],
[new Mod[] { new OsuModRandom() }, 0.7],
[new Mod[] { new OsuModMirror() }, 1],
[new Mod[] { new OsuModAlternate() }, 1],
[new Mod[] { new OsuModSingleTap() }, 1],
@@ -130,7 +149,7 @@ namespace osu.Game.Rulesets.Osu.Tests
[new Mod[] { new OsuModCinema() }, 1],
[new Mod[] { new OsuModRelax() }, 0.1],
[new Mod[] { new OsuModAutopilot() }, 0.1],
[new Mod[] { new OsuModSpunOut() }, 0.9],
[new Mod[] { new OsuModSpunOut() }, 0.95],
#endregion
@@ -140,19 +159,36 @@ namespace osu.Game.Rulesets.Osu.Tests
[new Mod[] { new OsuModWiggle() }, 1],
[new Mod[] { new OsuModSpinIn() }, 1],
[new Mod[] { new OsuModGrow() }, 1],
[new Mod[] { new OsuModDeflate() }, 1],
[new Mod[] { new ModWindUp() }, 0.5],
[new Mod[] { new ModWindDown() }, 0.5],
[new Mod[] { new OsuModDeflate { StartScale = { Value = 5 } } }, 0.94],
[new Mod[] { new ModWindUp() }, 0.8 * 1 + 0.2 * 1.230],
[new Mod[] { new ModWindUp { InitialRate = { Value = 0.7 }, FinalRate = { Value = 1.2 } } }, 0.8 * 0.48 + 0.2 * 1.082],
[new Mod[] { new ModWindUp { InitialRate = { Value = 0.7 }, FinalRate = { Value = 0.9 } } }, 0.8 * 0.48 + 0.2 * 0.76],
[new Mod[] { new ModWindUp { InitialRate = { Value = 1.1 }, FinalRate = { Value = 1.4 } } }, 0.8 * 1.036 + 0.2 * 1.174],
[new Mod[] { new ModWindDown() }, 0.8 * 0.55 + 0.2 * 1],
[new Mod[] { new ModWindDown { InitialRate = { Value = 1.2 }, FinalRate = { Value = 0.7 } } }, 0.8 * 0.48 + 0.2 * 1.082],
[new Mod[] { new ModWindDown { InitialRate = { Value = 0.9 }, FinalRate = { Value = 0.7 } } }, 0.8 * 0.48 + 0.2 * 0.76],
[new Mod[] { new ModWindDown { InitialRate = { Value = 1.4 }, FinalRate = { Value = 1.1 } } }, 0.8 * 1.036 + 0.2 * 1.174],
[new Mod[] { new OsuModBarrelRoll() }, 1],
[new Mod[] { new OsuModApproachDifferent() }, 1],
[new Mod[] { new OsuModApproachDifferent() }, 0.7],
[new Mod[] { new OsuModMuted() }, 1],
[new Mod[] { new OsuModNoScope() }, 1],
[new Mod[] { new OsuModMagnetised() }, 0.5],
[new Mod[] { new OsuModMagnetised() }, 0.4],
[new Mod[] { new OsuModMagnetised { AttractionStrength = { Value = 0.05f } } }, 0.67],
[new Mod[] { new OsuModMagnetised { AttractionStrength = { Value = 0.2f } } }, 0.58],
[new Mod[] { new OsuModMagnetised { AttractionStrength = { Value = 0.7f } } }, 0.28],
[new Mod[] { new OsuModMagnetised { AttractionStrength = { Value = 1 } } }, 0.1],
[new Mod[] { new OsuModRepel() }, 1],
[new Mod[] { new ModAdaptiveSpeed() }, 0.5],
[new Mod[] { new ModAdaptiveSpeed() }, 0.1],
[new Mod[] { new OsuModFreezeFrame() }, 1],
[new Mod[] { new OsuModBubbles() }, 1],
[new Mod[] { new OsuModSynesthesia() }, 0.8],
[new Mod[] { new OsuModSynesthesia() }, 0.99],
[new Mod[] { new OsuModDepth() }, 1],
[new Mod[] { new OsuModBloom() }, 1],
@@ -167,7 +203,24 @@ namespace osu.Game.Rulesets.Osu.Tests
#region Combinations
[new Mod[] { new OsuModHidden(), new OsuModHardRock() }, 1.06 * 1.06],
[new Mod[] { new OsuModHidden(), new OsuModHardRock() }, 1.04 * 1.09],
[new Mod[] { new OsuModHidden(), new OsuModWiggle() }, 1.02],
[new Mod[] { new OsuModHidden(), new OsuModGrow() }, 1.02],
[new Mod[] { new OsuModHidden(), new OsuModDeflate() }, 1.02],
[new Mod[] { new OsuModHidden(), new OsuModDeflate { StartScale = { Value = 4 } } }, 1.02 * 0.96],
[new Mod[] { new OsuModHidden(), new OsuModRepel() }, 1.02],
[new Mod[] { new OsuModHidden { OnlyFadeApproachCircles = { Value = true } }, new OsuModRepel() }, 1],
[new Mod[] { new OsuModHidden(), new OsuModDepth() }, 1.02],
[new Mod[] { new OsuModHidden(), new OsuModDepth(), new OsuModHardRock() }, 1.02 * 1.09],
[new Mod[] { new OsuModHidden(), new OsuModBlinds() }, 1.24],
[new Mod[] { new OsuModHidden(), new OsuModBlinds(), new OsuModHardRock() }, 1.24 * 1.09],
[new Mod[] { new OsuModTraceable(), new OsuModBlinds() }, 1.24],
[new Mod[] { new OsuModTraceable(), new OsuModBlinds(), new OsuModHardRock() }, 1.24 * 1.09],
[new Mod[] { new OsuModFlashlight(), new OsuModFreezeFrame() }, 1.1],
#endregion
];
@@ -175,5 +228,70 @@ namespace osu.Game.Rulesets.Osu.Tests
[TestCaseSource(nameof(test_cases))]
public void TestMultipliers(Mod[] mods, double expectedMultiplier)
=> TestModCombination(mods, expectedMultiplier);
[TestCase(null, null, null, null, 1)]
[TestCase(2.9f, null, null, null, 0.95)]
[TestCase(3.1f, null, null, null, 0.95)]
[TestCase(null, 3.9f, null, null, 0.95)]
[TestCase(null, 4.1f, null, null, 0.95)]
[TestCase(null, null, 4.9f, null, 0.95)]
[TestCase(null, null, 5.1f, null, 0.95)]
[TestCase(null, null, null, 5.9f, 0.95)]
[TestCase(null, null, null, 6.1f, 0.95)]
[TestCase(2.9f, 3.9f, null, null, 0.95 * 0.95)]
[TestCase(2.9f, 3.9f, 4.9f, null, 0.95 * 0.95 * 0.95)]
[TestCase(2.9f, 3.9f, 4.9f, 5.9f, 0.95 * 0.95 * 0.95 * 0.95)]
[TestCase(0.0f, null, null, null, 0.1)]
[TestCase(0.0f, 0.0f, 0.0f, 0.0f, 0.1)]
public void TestDifficultyAdjust(float? cs, float? ar, float? od, float? hp, double expectedMultiplier)
{
var difficulty = new BeatmapDifficulty
{
CircleSize = 3,
ApproachRate = 4,
OverallDifficulty = 5,
DrainRate = 6,
};
var mod = new OsuModDifficultyAdjust
{
CircleSize = { Value = cs },
ApproachRate = { Value = ar },
OverallDifficulty = { Value = od },
DrainRate = { Value = hp },
};
var calculator = Ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext(difficulty));
Assert.That(calculator.CalculateFor([mod]), Is.EqualTo(expectedMultiplier).Within(Precision.FLOAT_EPSILON));
}
[TestCase(30000001, 0.96)]
[TestCase(30000009, 0.96)]
[TestCase(30000016, 0.96)]
[TestCase(30000017, 0.985)]
[TestCase(null, 0.985)]
public void TestClassicMultiplierVersioning(int? totalScoreVersion, double expectedMultiplier)
{
var scoreInfo = totalScoreVersion != null ? new ScoreInfo { TotalScoreVersion = totalScoreVersion.Value } : null;
var calculator = Ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty(), scoreInfo));
Assert.That(calculator.CalculateFor([new OsuModClassic()]), Is.EqualTo(expectedMultiplier).Within(Precision.DOUBLE_EPSILON));
}
[Test]
public void VerySmallModMultiplier()
{
var mods = new Mod[]
{
new OsuModEasy { Retries = { Value = 10 } },
new OsuModNoFail(),
new OsuModHalfTime { SpeedChange = { Value = 0.5 } },
new OsuModTargetPractice(),
new OsuModClassic { ClassicNoteLock = { Value = false } },
new OsuModDeflate { StartScale = { Value = 25 } },
new OsuModMagnetised { AttractionStrength = { Value = 1 } },
new OsuModSynesthesia(),
};
var calculator = Ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty()));
Assert.That(calculator.CalculateFor(mods), Is.GreaterThan(0));
}
}
}
+7 -1
View File
@@ -234,7 +234,13 @@ namespace osu.Game.Rulesets.Osu
}
}
public override ScoreMultiplierCalculator CreateScoreMultiplierCalculator(ScoreMultiplierContext context) => new OsuScoreMultiplierCalculator(context);
public override ScoreMultiplierCalculator CreateScoreMultiplierCalculator(ScoreMultiplierContext context)
{
if (context.Score != null && context.Score.TotalScoreVersion < 30000017)
return new OsuScoreMultiplierCalculatorV1(context);
return new OsuScoreMultiplierCalculatorV2(context);
}
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetOsu };
@@ -7,9 +7,9 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Scoring
{
public class OsuScoreMultiplierCalculator : ScoreMultiplierCalculator
public class OsuScoreMultiplierCalculatorV1 : ScoreMultiplierCalculator
{
public OsuScoreMultiplierCalculator(ScoreMultiplierContext context)
public OsuScoreMultiplierCalculatorV1(ScoreMultiplierContext context)
: base(context)
{
#region Difficulty Reduction
@@ -0,0 +1,200 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Scoring
{
public class OsuScoreMultiplierCalculatorV2 : ScoreMultiplierCalculator
{
public OsuScoreMultiplierCalculatorV2(ScoreMultiplierContext context)
: base(context)
{
#region Difficulty Reduction
Single<OsuModEasy>(hasMultiplier: easyMultiplier);
Single<OsuModNoFail>(hasMultiplier: 0.5);
Single<OsuModHalfTime>(hasMultiplier: halfTime => halfTimeMultiplier(halfTime.SpeedChange.Value));
Single<OsuModDaycore>(hasMultiplier: daycore => halfTimeMultiplier(daycore.SpeedChange.Value));
#endregion
#region Difficulty Increase
Single<OsuModHardRock>(hasMultiplier: 1.09);
// Sudden Death (1.0x)
// Perfect (1.0x)
Single<OsuModDoubleTime>(hasMultiplier: doubleTime => doubleTimeMultiplier(doubleTime.SpeedChange.Value));
Single<OsuModNightcore>(hasMultiplier: nightcore => doubleTimeMultiplier(nightcore.SpeedChange.Value));
const double blinds_multiplier = 1.24;
Combination<OsuModHidden, OsuModBlinds>(hasMultiplier: (_, _) => blinds_multiplier);
Combination<OsuModHidden, OsuModWiggle>(hasMultiplier: (hidden, _) => hiddenMultiplier(hidden, otherModsProvideTimingInfo: true));
Combination<OsuModHidden, OsuModGrow>(hasMultiplier: (hidden, _) => hiddenMultiplier(hidden, otherModsProvideTimingInfo: true));
Combination<OsuModHidden, OsuModDeflate>(hasMultiplier: (hidden, deflate) => hiddenMultiplier(hidden, otherModsProvideTimingInfo: true) * deflateMultiplier(deflate));
Combination<OsuModHidden, OsuModRepel>(hasMultiplier: (hidden, _) => hiddenMultiplier(hidden, otherModsProvideTimingInfo: true));
Combination<OsuModHidden, OsuModDepth>(hasMultiplier: (hidden, _) => hiddenMultiplier(hidden, otherModsProvideTimingInfo: true));
Single<OsuModHidden>(hasMultiplier: hidden => hiddenMultiplier(hidden, otherModsProvideTimingInfo: false));
Combination<OsuModTraceable, OsuModBlinds>(hasMultiplier: (_, _) => blinds_multiplier);
Single<OsuModTraceable>(hasMultiplier: 1.02);
Combination<OsuModFlashlight, OsuModFreezeFrame>(hasMultiplier: (flashlight, _) => 1 + (flashlightMultiplier(flashlight) - 1) / 2);
Single<OsuModFlashlight>(hasMultiplier: flashlightMultiplier);
Single<OsuModBlinds>(hasMultiplier: blinds_multiplier);
// Strict Tracking (1.0x)
// Accuracy Challenge (1.0x)
#endregion
#region Conversion
Single<OsuModTargetPractice>(hasMultiplier: 0.01);
Single<OsuModDifficultyAdjust>(hasMultiplier: difficultyAdjust => difficultyAdjustMultiplier(difficultyAdjust, Context.BeatmapDifficultyWithoutMods));
Single<OsuModClassic>(hasMultiplier: classic => classic.ClassicNoteLock.Value ? 0.985 : 0.96);
Single<OsuModRandom>(hasMultiplier: 0.7);
// Mirror (1.0x)
// Alternate (1.0x)
// Single Tap (1.0x)
#endregion
#region Automation
// Autoplay (1.0x)
// Cinema (1.0x)
Single<OsuModRelax>(hasMultiplier: 0.1);
Single<OsuModAutopilot>(hasMultiplier: 0.1);
Single<OsuModSpunOut>(hasMultiplier: 0.95);
#endregion
#region Fun
// Transform (1.0x)
// Wiggle (1.0x)
// Spin In (1.0x)
// Grow (1.0x)
Single<OsuModDeflate>(hasMultiplier: deflateMultiplier);
Single<ModWindUp>(hasMultiplier: timeRampMultiplier);
Single<ModWindDown>(hasMultiplier: timeRampMultiplier);
// Barrel Roll (1.0x)
Single<OsuModApproachDifferent>(hasMultiplier: 0.7);
// Muted (1.0x)
// No Scope (1.0x)
Single<OsuModMagnetised>(hasMultiplier: magnetised => 0.7 - magnetised.AttractionStrength.Value * 0.6);
// Repel (1.0x)
Single<ModAdaptiveSpeed>(hasMultiplier: 0.1);
// Freeze Frame (1.0x)
// Bubbles (1.0x)
Single<OsuModSynesthesia>(hasMultiplier: 0.99);
// Depth (1.0x)
// Bloom (1.0x)
#endregion
#region System
// Touch Device (1.0x)
// Score V2 (1.0x)
#endregion
}
private static double easyMultiplier(OsuModEasy easy)
{
// 0.8x base multiplier
// Reduce by 0.1x per extra life
double value = 0.8 - Math.Max(0, 0.1 * (easy.Retries.Value - easy.Retries.Default));
return Math.Max(0.4, value);
}
private static double halfTimeMultiplier(double speedChange)
{
// 0.2x at 0.5x speed, +0.07x per 0.05x speed increment.
// Default HT (0.75x) = 0.55
return (int)(speedChange * 20) / 20.0 * 1.4 - 0.5;
}
private static double doubleTimeMultiplier(double speedChange)
{
// Floor to the nearest multiple of 0.1.
double value = (int)(speedChange * 10) / 10.0;
// 0.01 penalty for non-default rates.
double penalty = value != 1.5 && value != 1.0 ? 0.01 : 0.0;
// Linear from 1.0 to 1.46, minus the penalty.
// Default DT (1.5x) = 1.23
return (value - 1) * 0.46 + 1 - penalty;
}
private static double hiddenMultiplier(OsuModHidden hidden, bool otherModsProvideTimingInfo)
{
double value = 1.04;
if (hidden.OnlyFadeApproachCircles.Value)
value -= 0.02;
if (otherModsProvideTimingInfo)
value -= 0.02;
return value;
}
private static double flashlightMultiplier(OsuModFlashlight flashlight)
{
// Multiplier of 1.2x, reduced by 0.02 per 0.1 increase in flashlight size.
double value = Math.Max(1.02, Math.Min(1.2, 1.2 - 0.2 * (flashlight.SizeMultiplier.Value - 1)));
if (!flashlight.ComboBasedSize.Value)
value = 1 + (value - 1) / 5;
return value;
}
private static double difficultyAdjustMultiplier(OsuModDifficultyAdjust difficultyAdjust, IBeatmapDifficultyInfo beatmapDifficulty)
{
double selectedCircleSize = difficultyAdjust.CircleSize.Value ?? beatmapDifficulty.CircleSize;
double selectedDrainRate = difficultyAdjust.DrainRate.Value ?? beatmapDifficulty.DrainRate;
double selectedOverallDifficulty = difficultyAdjust.OverallDifficulty.Value ?? beatmapDifficulty.OverallDifficulty;
double selectedApproachRate = difficultyAdjust.ApproachRate.Value ?? beatmapDifficulty.ApproachRate;
double csDifference = Math.Abs(selectedCircleSize - beatmapDifficulty.CircleSize);
double hpDifference = Math.Abs(selectedDrainRate - beatmapDifficulty.DrainRate);
double odDifference = Math.Abs(selectedOverallDifficulty - beatmapDifficulty.OverallDifficulty);
double arDifference = Math.Abs(selectedApproachRate - beatmapDifficulty.ApproachRate);
// Per parameter, reduce multiplier by 0.05x per 0.1 change.
double csMultiplier = Math.Max(0.1, 1.0 - csDifference * 0.5);
double hpMultiplier = Math.Max(0.1, 1.0 - hpDifference * 0.5);
double odMultiplier = Math.Max(0.1, 1.0 - odDifference * 0.5);
double arMultiplier = Math.Max(0.1, 1.0 - arDifference * 0.5);
return Math.Max(0.1, csMultiplier * hpMultiplier * odMultiplier * arMultiplier);
}
private static double timeRampMultiplier(ModTimeRamp timeRamp)
{
double minSpeed = Math.Min(timeRamp.InitialRate.Value, timeRamp.FinalRate.Value);
double maxSpeed = Math.Max(timeRamp.InitialRate.Value, timeRamp.FinalRate.Value);
double minMultiplier = minSpeed < 1 ? halfTimeMultiplier(minSpeed) : doubleTimeMultiplier(minSpeed);
double maxMultiplier = maxSpeed < 1 ? halfTimeMultiplier(maxSpeed) : doubleTimeMultiplier(maxSpeed);
return 0.8 * minMultiplier + 0.2 * maxMultiplier;
}
private static double deflateMultiplier(OsuModDeflate deflate)
=> 1.0 - Math.Max(0, 0.02 * (deflate.StartScale.Value - deflate.StartScale.Default));
}
}
@@ -27,10 +27,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
protected override Color4 ColourAt(float position)
{
// https://github.com/peppy/osu-stable-reference/blob/3ea48705eb67172c430371dcfc8a16a002ed0d3d/osu!/Graphics/Renderers/MmSliderRendererGL.cs#L99
// float aaWidth = Math.Min(Math.Max(0.5f / PathRadius, 3.0f / 256.0f), 1.0f / 16.0f);
// applying the aa_width constant from stable makes sliders blurry, especially on CS>5. set to zero for now.
// this might be related to SmoothPath applying AA internally, but disabling that does not seem to have much of an effect.
const float aa_width = 0f;
float aaWidth = Math.Min(Math.Max(0.5f / PathRadius, 3.0f / 256.0f), 1.0f / 16.0f);
Color4 shadow = new Color4(0, 0, 0, 0.25f);
Color4 outerColour = AccentColour.Darken(0.1f);
@@ -40,19 +37,19 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
const float shadow_portion = 1 - (OsuLegacySkinTransformer.LEGACY_CIRCLE_RADIUS / OsuHitObject.OBJECT_RADIUS);
const float border_portion = 0.1875f;
if (position <= shadow_portion - aa_width)
return LegacyUtils.InterpolateNonLinear(position, Color4.Black.Opacity(0f), shadow, 0, shadow_portion - aa_width);
if (position <= shadow_portion - aaWidth)
return LegacyUtils.InterpolateNonLinear(position, Color4.Black.Opacity(0f), shadow, 0, shadow_portion - aaWidth);
if (position <= shadow_portion + aa_width)
return LegacyUtils.InterpolateNonLinear(position, shadow, BorderColour, shadow_portion - aa_width, shadow_portion + aa_width);
if (position <= shadow_portion + aaWidth)
return LegacyUtils.InterpolateNonLinear(position, shadow, BorderColour, shadow_portion - aaWidth, shadow_portion + aaWidth);
if (position <= border_portion - aa_width)
if (position <= border_portion - aaWidth)
return BorderColour;
if (position <= border_portion + aa_width)
return LegacyUtils.InterpolateNonLinear(position, BorderColour, outerColour, border_portion - aa_width, border_portion + aa_width);
if (position <= border_portion + aaWidth)
return LegacyUtils.InterpolateNonLinear(position, BorderColour, outerColour, border_portion - aaWidth, border_portion + aaWidth);
return LegacyUtils.InterpolateNonLinear(position, outerColour, innerColour, border_portion + aa_width, 1);
return LegacyUtils.InterpolateNonLinear(position, outerColour, innerColour, border_portion + aaWidth, 1);
}
/// <summary>
@@ -2,8 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Scoring;
using osu.Game.Tests.Rulesets;
namespace osu.Game.Rulesets.Taiko.Tests
@@ -113,7 +117,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
[new Mod[] { new TaikoModRandom() }, 1],
[new Mod[] { new TaikoModDifficultyAdjust() }, 0.5],
[new Mod[] { new TaikoModClassic() }, 0.96],
[new Mod[] { new TaikoModClassic() }, 1],
[new Mod[] { new TaikoModSwap() }, 1],
[new Mod[] { new TaikoModSingleTap() }, 1],
[new Mod[] { new TaikoModConstantSpeed() }, 0.9],
@@ -153,5 +157,17 @@ namespace osu.Game.Rulesets.Taiko.Tests
[TestCaseSource(nameof(test_cases))]
public void TestMultipliers(Mod[] mods, double expectedMultiplier)
=> TestModCombination(mods, expectedMultiplier);
[TestCase(30000001, 0.96)]
[TestCase(30000009, 0.96)]
[TestCase(30000016, 0.96)]
[TestCase(30000017, 1)]
[TestCase(null, 1)]
public void TestClassicMultiplierVersioning(int? totalScoreVersion, double expectedMultiplier)
{
var scoreInfo = totalScoreVersion != null ? new ScoreInfo { TotalScoreVersion = totalScoreVersion.Value } : null;
var calculator = Ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty(), scoreInfo));
Assert.That(calculator.CalculateFor([new TaikoModClassic()]), Is.EqualTo(expectedMultiplier).Within(Precision.DOUBLE_EPSILON));
}
}
}
@@ -4,6 +4,7 @@
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Scoring;
namespace osu.Game.Rulesets.Taiko.Scoring
{
@@ -39,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring
// Random
Single<TaikoModDifficultyAdjust>(hasMultiplier: 0.5);
Single<TaikoModClassic>(hasMultiplier: 0.96);
Single<TaikoModClassic>(hasMultiplier: _ => classicMultiplier(Context.Score));
// Swap
// Single Tap
Single<TaikoModConstantSpeed>(hasMultiplier: 0.9);
@@ -83,5 +84,13 @@ namespace osu.Game.Rulesets.Taiko.Scoring
else
return 0.6 + value;
}
private static double classicMultiplier(ScoreInfo? score)
{
if (score != null && score.TotalScoreVersion < 30000017)
return 0.96;
return 1;
}
}
}
@@ -552,6 +552,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
}
[Test]
[Ignore("Temporarily ignored pending correct addressing of treatment of `TotalScoreVersion` in replays")]
public void TestTotalScoreWithoutModsBackwardsPopulatedIfMissing()
{
var ruleset = new OsuRuleset().RulesetInfo;
+20 -12
View File
@@ -228,21 +228,29 @@ namespace osu.Game.Tests.Mods
[Test]
public void TestFormatScoreMultiplier()
{
ClassicAssert.AreEqual(ModUtils.FormatScoreMultiplier(0.9999).ToString(), "0.99x");
ClassicAssert.AreEqual(ModUtils.FormatScoreMultiplier(1.0).ToString(), "1.00x");
ClassicAssert.AreEqual(ModUtils.FormatScoreMultiplier(1.0001).ToString(), "1.01x");
Assert.Multiple(() =>
{
Assert.That(ModUtils.FormatScoreMultiplier(0.9999).ToString(), Is.EqualTo("0.99x"));
Assert.That(ModUtils.FormatScoreMultiplier(1.0).ToString(), Is.EqualTo("1.00x"));
Assert.That(ModUtils.FormatScoreMultiplier(1.0001).ToString(), Is.EqualTo("1.01x"));
ClassicAssert.AreEqual(ModUtils.FormatScoreMultiplier(0.899999999999999).ToString(), "0.90x");
ClassicAssert.AreEqual(ModUtils.FormatScoreMultiplier(0.9).ToString(), "0.90x");
ClassicAssert.AreEqual(ModUtils.FormatScoreMultiplier(0.900000000000001).ToString(), "0.90x");
Assert.That(ModUtils.FormatScoreMultiplier(0.899999999999999).ToString(), Is.EqualTo("0.90x"));
Assert.That(ModUtils.FormatScoreMultiplier(0.9).ToString(), Is.EqualTo("0.90x"));
Assert.That(ModUtils.FormatScoreMultiplier(0.900000000000001).ToString(), Is.EqualTo("0.90x"));
ClassicAssert.AreEqual(ModUtils.FormatScoreMultiplier(1.099999999999999).ToString(), "1.10x");
ClassicAssert.AreEqual(ModUtils.FormatScoreMultiplier(1.1).ToString(), "1.10x");
ClassicAssert.AreEqual(ModUtils.FormatScoreMultiplier(1.100000000000001).ToString(), "1.10x");
Assert.That(ModUtils.FormatScoreMultiplier(1.099999999999999).ToString(), Is.EqualTo("1.10x"));
Assert.That(ModUtils.FormatScoreMultiplier(1.1).ToString(), Is.EqualTo("1.10x"));
Assert.That(ModUtils.FormatScoreMultiplier(1.100000000000001).ToString(), Is.EqualTo("1.10x"));
ClassicAssert.AreEqual(ModUtils.FormatScoreMultiplier(1.045).ToString(), "1.05x");
ClassicAssert.AreEqual(ModUtils.FormatScoreMultiplier(1.05).ToString(), "1.05x");
ClassicAssert.AreEqual(ModUtils.FormatScoreMultiplier(1.055).ToString(), "1.06x");
Assert.That(ModUtils.FormatScoreMultiplier(1.045).ToString(), Is.EqualTo("1.05x"));
Assert.That(ModUtils.FormatScoreMultiplier(1.05).ToString(), Is.EqualTo("1.05x"));
Assert.That(ModUtils.FormatScoreMultiplier(1.055).ToString(), Is.EqualTo("1.06x"));
Assert.That(ModUtils.FormatScoreMultiplier(1.1799999952316285).ToString(), Is.EqualTo("1.18x"));
Assert.That(ModUtils.FormatScoreMultiplier(1.1599999904632567).ToString(), Is.EqualTo("1.16x"));
Assert.That(ModUtils.FormatScoreMultiplier(1.1400000095367431).ToString(), Is.EqualTo("1.14x"));
Assert.That(ModUtils.FormatScoreMultiplier(1.1200000047683716).ToString(), Is.EqualTo("1.12x"));
});
}
private static readonly object[] multiplayer_mod_test_scenarios =
@@ -321,13 +321,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
ClickButtonWhenEnabled<UserModSelectButton>();
AddUntilStep("mod select shows unranked", () => this.ChildrenOfType<RankingInformationDisplay>().Single().Ranked.Value == false);
AddAssert("score multiplier = 1.20", () => this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01));
AddAssert("score multiplier = 1.45", () => this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(1.45).Within(0.01));
AddStep("select flashlight", () => this.ChildrenOfType<MultiplayerUserModSelectOverlay>().Single().ChildrenOfType<ModPanel>().Single(m => m.Mod is ModFlashlight).TriggerClick());
AddAssert("score multiplier = 1.35", () => this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(1.35).Within(0.01));
AddAssert("score multiplier = 1.74", () => this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(1.74).Within(0.01));
AddStep("change flashlight setting", () => ((OsuModFlashlight)this.ChildrenOfType<MultiplayerUserModSelectOverlay>().Single().SelectedMods.Value.Single()).FollowDelay.Value = 1200);
AddAssert("score multiplier = 1.20", () => this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01));
AddStep("change flashlight setting", () => ((OsuModFlashlight)this.ChildrenOfType<MultiplayerUserModSelectOverlay>().Single().SelectedMods.Value.Single()).ComboBasedSize.Value = false);
AddAssert("score multiplier = 1.51", () => this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(1.51).Within(0.01));
}
[Test]
@@ -12,11 +12,9 @@ using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Select;
using osu.Game.Utils;
@@ -73,19 +71,19 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("Set ruleset", () => footerButtonMods.Ruleset.Value = ruleset.RulesetInfo);
AddStep(@"Add Hidden", () => changeMods(hiddenMod));
assertModsMultiplier(ruleset, hiddenMod);
assertModsMultiplier(1.04);
var hardRockMod = new Mod[] { new OsuModHardRock() };
AddStep(@"Add HardRock", () => changeMods(hardRockMod));
assertModsMultiplier(ruleset, hardRockMod);
assertModsMultiplier(1.09);
var doubleTimeMod = new Mod[] { new OsuModDoubleTime() };
AddStep(@"Add DoubleTime", () => changeMods(doubleTimeMod));
assertModsMultiplier(ruleset, doubleTimeMod);
assertModsMultiplier(1.23);
var multipleIncrementMods = new Mod[] { new OsuModDoubleTime(), new OsuModHidden(), new OsuModHardRock() };
AddStep(@"Add multiple Mods", () => changeMods(multipleIncrementMods));
assertModsMultiplier(ruleset, multipleIncrementMods);
assertModsMultiplier(1.23 * 1.04 * 1.09);
}
[Test]
@@ -97,15 +95,60 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("Set ruleset", () => footerButtonMods.Ruleset.Value = ruleset.RulesetInfo);
AddStep(@"Add Easy", () => changeMods(easyMod));
assertModsMultiplier(ruleset, easyMod);
assertModsMultiplier(0.8);
var noFailMod = new Mod[] { new OsuModNoFail() };
AddStep(@"Add NoFail", () => changeMods(noFailMod));
assertModsMultiplier(ruleset, noFailMod);
assertModsMultiplier(0.5);
var multipleDecrementMods = new Mod[] { new OsuModEasy(), new OsuModNoFail() };
AddStep(@"Add Multiple Mods", () => changeMods(multipleDecrementMods));
assertModsMultiplier(ruleset, multipleDecrementMods);
assertModsMultiplier(0.8 * 0.5);
}
[Test]
public void TestDifficultyAdjustMultiplier()
{
var ruleset = new OsuRuleset();
var difficultyAdjustMod = new OsuModDifficultyAdjust();
AddStep("Set ruleset", () => footerButtonMods.Ruleset.Value = ruleset.RulesetInfo);
AddStep("Set beatmap", () =>
{
var beatmap = CreateWorkingBeatmap(ruleset.RulesetInfo);
beatmap.BeatmapInfo.Difficulty = new BeatmapDifficulty
{
ApproachRate = 3,
OverallDifficulty = 5,
CircleSize = 5,
DrainRate = 6,
};
Beatmap.Value = beatmap;
});
AddStep(@"Set Difficulty Adjust", () => changeMods([difficultyAdjustMod]));
assertModsMultiplier(1);
AddStep("Adjust AR", () => difficultyAdjustMod.ApproachRate.Value = 3.3f);
assertModsMultiplier(0.85);
AddStep("Adjust HP", () => difficultyAdjustMod.DrainRate.Value = 6.5f);
assertModsMultiplier(0.6375);
AddStep("Change beatmap", () =>
{
var beatmap = CreateWorkingBeatmap(ruleset.RulesetInfo);
beatmap.BeatmapInfo.Difficulty = new BeatmapDifficulty
{
ApproachRate = 3.3f,
OverallDifficulty = 8,
CircleSize = 8,
DrainRate = 6,
};
Beatmap.Value = beatmap;
});
assertModsMultiplier(0.75);
}
[Test]
@@ -119,11 +162,9 @@ namespace osu.Game.Tests.Visual.SongSelect
private void changeMods(IReadOnlyList<Mod> mods) => footerButtonMods.Mods.Value = mods;
private void assertModsMultiplier(Ruleset ruleset, IEnumerable<Mod> mods)
private void assertModsMultiplier(double expectedMultiplier)
{
var scoreMultiplierCalculator = ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty()));
double multiplier = scoreMultiplierCalculator.CalculateFor(mods);
string expectedValue = ModUtils.FormatScoreMultiplier(multiplier).ToString();
string expectedValue = ModUtils.FormatScoreMultiplier(expectedMultiplier).ToString();
AddAssert($"Displayed multiplier is {expectedValue}", () => footerButtonMods.ChildrenOfType<OsuSpriteText>().First(t => t.Text.ToString().Contains('x')).Text.ToString(), () => Is.EqualTo(expectedValue));
}
@@ -67,6 +67,13 @@ namespace osu.Game.Tests.Visual.UserInterface
TabbableContentContainer = this,
},
new FormTextBox
{
Caption = "Length limited text",
PlaceholderText = "I can only hold 10 characters!",
LengthLimit = 10,
TabbableContentContainer = this,
},
new FormTextBox
{
Caption = "Artist",
HintText = "Poot artist here!",
@@ -125,7 +125,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddUntilStep("two panels active", () => modSelectOverlay.ChildrenOfType<ModPanel>().Count(panel => panel.Active.Value) == 2);
AddAssert("mod multiplier correct", () =>
{
double multiplier = new OsuScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty())).CalculateFor(SelectedMods.Value);
double multiplier = new OsuScoreMultiplierCalculatorV2(new ScoreMultiplierContext(new BeatmapDifficulty())).CalculateFor(SelectedMods.Value);
return Precision.AlmostEquals(multiplier, this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value);
});
assertCustomisationToggleState(disabled: false, active: false);
@@ -140,7 +140,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddUntilStep("two panels active", () => modSelectOverlay.ChildrenOfType<ModPanel>().Count(panel => panel.Active.Value) == 2);
AddAssert("mod multiplier correct", () =>
{
double multiplier = new OsuScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty())).CalculateFor(SelectedMods.Value);
double multiplier = new OsuScoreMultiplierCalculatorV2(new ScoreMultiplierContext(new BeatmapDifficulty())).CalculateFor(SelectedMods.Value);
return Precision.AlmostEquals(multiplier, this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value);
});
assertCustomisationToggleState(disabled: false, active: false);
@@ -899,7 +899,7 @@ namespace osu.Game.Tests.Visual.UserInterface
InputManager.Click(MouseButton.Left);
});
AddAssert("difficulty multiplier display shows correct value",
() => this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(0.1).Within(Precision.DOUBLE_EPSILON));
() => this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(0.2).Within(Precision.DOUBLE_EPSILON));
// this is highly unorthodox in a test, but because the `ModSettingChangeTracker` machinery heavily leans on events and object disposal and re-creation,
// it is instrumental in the reproduction of the failure scenario that this test is supposed to cover.
@@ -909,7 +909,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("reset half time speed to default", () => modSelectOverlay.ChildrenOfType<ModCustomisationPanel>().Single()
.ChildrenOfType<RevertToDefaultButton<double>>().Single().TriggerClick());
AddUntilStep("difficulty multiplier display shows correct value",
() => this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(0.3).Within(Precision.DOUBLE_EPSILON));
() => this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(0.55).Within(Precision.DOUBLE_EPSILON));
}
[Test]
@@ -1032,6 +1032,51 @@ namespace osu.Game.Tests.Visual.UserInterface
AddAssert("OsuModPerfect panel active", () => getPanelForMod(typeof(OsuModPerfect)).Active.Value);
}
[Test]
public void TestDifficultyAdjustModMultiplierIsCalculatedCorrectly()
{
createScreen();
AddStep("set mods", () => SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() });
AddUntilStep("one panel active", () => modSelectOverlay.ChildrenOfType<ModPanel>().Count(panel => panel.Active.Value), () => Is.EqualTo(1));
AddAssert("mod multiplier is correct", () => this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value,
() => Is.EqualTo(1).Within(Precision.FLOAT_EPSILON));
assertCustomisationToggleState(disabled: false, active: false);
AddAssert("setting items created", () => modSelectOverlay.ChildrenOfType<ISettingsItem>().Any());
AddStep("modify DA", () => SelectedMods.Value.OfType<OsuModDifficultyAdjust>().Single().CircleSize.Value = 3.9f);
AddAssert("mod multiplier is correct", () => this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value,
() => Is.EqualTo(0.95).Within(Precision.FLOAT_EPSILON));
AddStep("replace DA completely", () =>
{
SelectedMods.Value = new Mod[]
{
new OsuModDifficultyAdjust
{
ApproachRate = { Value = 6.8f },
OverallDifficulty = { Value = 6.2f }
}
};
});
AddAssert("mod multiplier is correct", () => this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value,
() => Is.EqualTo(0.81).Within(Precision.FLOAT_EPSILON));
AddStep("change beatmap", () =>
{
var beatmap = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
beatmap.BeatmapInfo.Difficulty = new BeatmapDifficulty
{
ApproachRate = 6,
OverallDifficulty = 5,
CircleSize = 5,
DrainRate = 5,
};
modSelectOverlay.Beatmap.Value = beatmap;
});
AddAssert("mod multiplier is correct", () => this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value,
() => Is.EqualTo(0.24).Within(Precision.FLOAT_EPSILON));
}
private void waitForColumnLoad() => AddUntilStep("all column content loaded", () =>
modSelectOverlay.ChildrenOfType<ModColumn>().Any()
&& modSelectOverlay.ChildrenOfType<ModColumn>().All(column => column.IsLoaded && column.ItemsLoaded)
+1 -1
View File
@@ -99,7 +99,7 @@ namespace osu.Game.Beatmaps
userAudioOffset.BindValueChanged(offset => userGlobalOffsetClock.Offset = offset.NewValue, true);
experimentalAudio = audioManager.UseExperimentalWasapi.GetBoundCopy();
experimentalAudio.BindValueChanged(_ => updatePlatformOffset());
experimentalAudio.BindValueChanged(_ => updatePlatformOffset(), true);
// TODO: this doesn't update when using ChangeSource() to change beatmap.
beatmapOffsetSubscription = realm.SubscribeToPropertyChanged(
@@ -69,13 +69,15 @@ namespace osu.Game.Graphics.UserInterfaceV2
if (TooltipText != default)
{
// Use a space to pad the icon drawable, so that it does not have
// an awkward left margin if it gets pushed to a new line.
textFlow.AddText(" ", t => t.Width = 5);
textFlow.AddArbitraryDrawable(new SpriteIcon
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Size = new Vector2(10),
Icon = FontAwesome.Solid.QuestionCircle,
Margin = new MarginPadding { Left = 5 },
Y = 1f,
});
}
@@ -77,6 +77,11 @@ namespace osu.Game.Graphics.UserInterfaceV2
/// </summary>
public LocalisableString PlaceholderText { get; init; }
/// <summary>
/// Maximum allowed length of text.
/// </summary>
public int? LengthLimit { get; init; }
private FormControlBackground background = null!;
private Box flashLayer = null!;
private InnerTextBox textBox = null!;
@@ -120,6 +125,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
t.RelativeSizeAxes = Axes.X;
t.Width = 1;
t.PlaceholderText = PlaceholderText;
t.LengthLimit = LengthLimit;
t.Current = Current;
t.CommitOnFocusLost = true;
t.OnCommit += (textBox, newText) =>
+6 -11
View File
@@ -23,18 +23,19 @@ namespace osu.Game.Overlays.BeatmapSet
private readonly Box successRateBackground;
private readonly Box background;
private readonly MetadataSection<string[]?> userTags;
public readonly Bindable<APIBeatmapSet> BeatmapSet = new Bindable<APIBeatmapSet>();
public readonly Bindable<APIBeatmap> Beatmap = new Bindable<APIBeatmap>();
public Info()
{
SuccessRate successRate;
MetadataSectionNominators nominators;
MetadataSection source, mapperTags;
MetadataSectionSource source;
MetadataSectionGenre genre;
MetadataSectionLanguage language;
MetadataSectionUserTags userTags;
MetadataSectionMapperTags mapperTags;
SuccessRate successRate;
RelativeSizeAxes = Axes.X;
Height = base_height;
@@ -115,23 +116,17 @@ namespace osu.Game.Overlays.BeatmapSet
{
nominators.Metadata = (b.NewValue?.CurrentNominations ?? Array.Empty<BeatmapSetOnlineNomination>(), b.NewValue?.RelatedUsers ?? Array.Empty<APIUser>());
source.Metadata = b.NewValue?.Source ?? string.Empty;
mapperTags.Metadata = b.NewValue?.Tags ?? string.Empty;
updateUserTags();
genre.Metadata = b.NewValue?.Genre ?? new BeatmapSetOnlineGenre { Id = (int)SearchGenre.Unspecified };
language.Metadata = b.NewValue?.Language ?? new BeatmapSetOnlineLanguage { Id = (int)SearchLanguage.Unspecified };
mapperTags.Metadata = b.NewValue?.Tags ?? string.Empty;
});
Beatmap.BindValueChanged(b =>
{
userTags.Metadata = b.NewValue?.GetTopUserTags().Select(t => t.Tag.Name).ToArray() ?? Array.Empty<string>();
successRate.Beatmap = b.NewValue;
updateUserTags();
});
}
private void updateUserTags()
{
userTags.Metadata = Beatmap.Value?.GetTopUserTags().Select(t => t.Tag.Name).ToArray();
}
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
@@ -2,17 +2,32 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Online.Chat;
namespace osu.Game.Overlays.BeatmapSet
{
public partial class MetadataSectionUserTags : MetadataSection<string[]?>
public partial class MetadataSectionUserTags : MetadataSection<string[]>
{
private readonly Action<string>? searchAction;
public override string[] Metadata
{
set
{
if (value.Length == 0)
{
this.FadeOut(TRANSITION_DURATION);
return;
}
base.Metadata = value;
}
}
public MetadataSectionUserTags(Action<string>? searchAction = null)
: base(MetadataType.UserTags, null)
: base(MetadataType.UserTags)
{
this.searchAction = searchAction;
}
@@ -61,9 +61,10 @@ namespace osu.Game.Scoring.Legacy
/// <item><description>30000014: Fix edge cases in conversion for osu! scores on selected beatmaps. Reconvert all scores.</description></item>
/// <item><description>30000015: Fix osu! standardised score estimation algorithm violating basic invariants. Reconvert all scores.</description></item>
/// <item><description>30000016: Fix taiko standardised score estimation algorithm not including swell tick score gain into bonus portion. Reconvert all scores.</description></item>
/// <item><description>30000017: Mod score multiplier rebalance. Recalculates the <see cref="ScoreInfo.TotalScore"/> of all scores with <see cref="ScoreInfo.TotalScoreWithoutMods"/> present.</description></item>
/// </list>
/// </remarks>
public const int LATEST_VERSION = 30000016;
public const int LATEST_VERSION = 30000017;
/// <summary>
/// The first stable-compatible YYYYMMDD format version given to lazer usage of replays.
@@ -4,6 +4,7 @@
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
@@ -40,7 +41,13 @@ namespace osu.Game.Screens.Select
AccentColour = colour.Purple1;
Hotkey = GlobalAction.ToggleBeatmapOptions;
Action = this.ShowPopover;
Action = () =>
{
if (this.FindClosestParent<PopoverContainer>()?.CurrentTarget == this)
this.HidePopover();
else
this.ShowPopover();
};
}
protected override void LoadComplete()
@@ -26,6 +26,7 @@ namespace osu.Game.Screens.Select
{
public Bindable<BeatmapInfo?> Beatmap { get; } = new Bindable<BeatmapInfo?>();
public Bindable<StarDifficulty> StarDifficulty { get; } = new Bindable<StarDifficulty>();
public BindableBool Selected { get; } = new BindableBool();
protected override Colour4 DimColour => Colour4.White;
@@ -140,6 +141,8 @@ namespace osu.Game.Screens.Select
StarDifficulty.BindValueChanged(_ => updateBeatmap());
showConvertedBeatmaps.BindValueChanged(_ => updateBeatmap());
scopedBeatmapSet.BindValueChanged(_ => updateBeatmap(), true);
Selected.BindValueChanged(_ => updateEnabled());
scopedBeatmapSet.BindDisabledChanged(_ => updateEnabled(), true);
Enabled.BindValueChanged(_ => updateAppearance(), true);
FinishTransforms(true);
}
@@ -254,6 +257,11 @@ namespace osu.Game.Screens.Select
base.OnHoverLost(e);
}
private void updateEnabled()
{
Enabled.Value = Selected.Value && !scopedBeatmapSet.Disabled;
}
private void updateAppearance()
{
bool isInteractable = Enabled.Value && IsHovered;
@@ -194,6 +194,7 @@ namespace osu.Game.Screens.Select
{
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
Selected = { BindTarget = Selected },
}
},
}
@@ -214,7 +215,6 @@ namespace osu.Game.Screens.Select
Selected.BindValueChanged(s =>
{
Expanded.Value = s.NewValue;
spreadDisplay.Enabled.Value = s.NewValue;
}, true);
}
+2 -2
View File
@@ -273,9 +273,9 @@ namespace osu.Game.Utils
{
// Round multiplier values away from 1.00x to two significant digits.
if (scoreMultiplier > 1)
scoreMultiplier = Math.Ceiling(Math.Round(scoreMultiplier * 100, 12)) / 100;
scoreMultiplier = Math.Ceiling(Math.Round(scoreMultiplier * 100, 4)) / 100;
else
scoreMultiplier = Math.Floor(Math.Round(scoreMultiplier * 100, 12)) / 100;
scoreMultiplier = Math.Floor(Math.Round(scoreMultiplier * 100, 4)) / 100;
return scoreMultiplier.ToLocalisableString("0.00x");
}