Skip to content

Commit 9727d95

Browse files
Bartłomiej Dachpeppy
andauthored
Replace usages of Mod.ScoreMultiplier with new score multiplier API (ppy#37845)
- Part of ppy#37818 During review, I would like to direct particular attention to the following changes: ## [Migrate song select to new score multiplier API](ppy@945fd78) This was a confusing change to write because of the way song selects hook their mod overlays up to global bindables. In particular different things happen in different circumstances. - When going through `SongSelect.CreateModOverlay()`, which is called by the base `SongSelect`, the mod overlay is automatically bound to global bindables via `SongSelect.on{ArrivingAt,Leaving}Screen()`. - For multiplayer user mod select overlays, which are bolted on by subclasses of `SongSelect`, manual hook-up is required. - As for free mod select overlays, they don't show mod multipliers at all, and don't have easy access to the ruleset, and thus the hookup is skipped entirely as redundant. ## [Fix score multiplier registrations being shared between implementations via superclass static fields](ppy@ba0a7ad) Revealed by `ScoreMultiplierCalculatorTest` starting to fail due to interference from `OsuScoreMultiplierCalculator`. It's not ideal from a performance standpoint but it's the simplest choice for now. Tricks could be pulled to salvage the static. One is ```csharp public class ScoreMultiplierCalculator<T> where T : ScoreMultiplierCalculator<T> { } ``` This works because of generics internals; static instance members are not shared between different specialisations of a generic class. It is also very unintuitive, so I would rather not. (It trips a ReSharper inspection too, which would have to be silenced.) From a performance standpoint this is not ideal, but a significant chunk of migrated usages already precede the construction of the calculator via the known-expensive `RulesetInfo.CreateInstance()`, and the paths that actually construct the calculator do not appear to be that hot. If need be, this can be handled by actually caching ruleset instances and their derivative subcomponents. ## [Introduce passing of context to score multiplier calculator](ppy@9e9242b) This is required for two reasons: - The upcoming mod rebalance will require out-of-band supplementary information that is not available for reading from the mod instances themselves for calculating the multiplier. - This context, namely passing of `ScoreInfo`, will be used for implementing backwards compatibility with old scores and their score multipliers. This is required because it has turned out under inspection that all server-side lazer replays recorded until now are missing `TotalScoreWithoutMods` due to an omission of not sending it across the wire to spectator server. Because the score import flow uses replays, filtered through `LegacyScoreDecoder`, to populate total score in the realm database, it is basically impossible to ignore scores that are missing `TotalScoreWithoutMods`, because that will result in bug reports that the scores do not have the new score multipliers applied. Thus, passing of `ScoreInfo` will facilitate implementation of versioning score multipliers, which should result in less breakage than not doing so. An example of this is added in 341b2d6, which should handle the case of mania mod multipliers having been changed without any attempt to facilitate for it in ppy#30506. --------- Co-authored-by: Dean Herbert <pe@ppy.sh>
1 parent e831c46 commit 9727d95

35 files changed

Lines changed: 856 additions & 221 deletions

osu.Game.Benchmarks/BenchmarkScoreMultiplierCalculator.cs

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -42,29 +42,7 @@ public record ModTestCase(string Description, IEnumerable<Mod> Mods)
4242
public override void SetUp()
4343
{
4444
base.SetUp();
45-
calculator = new OsuRuleset().CreateScoreMultiplierCalculator();
46-
}
47-
48-
[Benchmark]
49-
public double ViaModScoreMultiplier() => viaModScoreMultiplier(Times, Mods);
50-
51-
[Test]
52-
public void ViaModScoreMultiplier([Values(100)] int times, [ValueSource(nameof(ValuesForMods))] ModTestCase mods)
53-
=> viaModScoreMultiplier(times, mods);
54-
55-
private double viaModScoreMultiplier(int times, ModTestCase mods)
56-
{
57-
double scoreMultiplier = 1;
58-
59-
for (int i = 0; i < times; ++i)
60-
{
61-
scoreMultiplier = 1;
62-
63-
foreach (var mod in mods.Mods)
64-
scoreMultiplier *= mod.ScoreMultiplier;
65-
}
66-
67-
return scoreMultiplier;
45+
calculator = new OsuRuleset().CreateScoreMultiplierCalculator(new ScoreMultiplierContext());
6846
}
6947

7048
[Benchmark]

osu.Game.Rulesets.Catch.Tests/CatchScoreMultiplierTest.cs

Lines changed: 132 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using NUnit.Framework;
55
using osu.Game.Rulesets.Catch.Mods;
6+
using osu.Game.Rulesets.Mods;
67
using osu.Game.Tests.Rulesets;
78

89
namespace osu.Game.Rulesets.Catch.Tests
@@ -14,28 +15,141 @@ public CatchScoreMultiplierTest()
1415
{
1516
}
1617

17-
[Test]
18-
public void TestFlashlightOnNonDefaultSettings()
19-
=> TestModCombination([new CatchModFlashlight { ComboBasedSize = { Value = false } }]);
18+
private static readonly object[][] test_cases =
19+
[
20+
#region Difficulty Reduction
2021

21-
[Test]
22-
public void TestHalfTimeSpeeds([Values(0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 0.99)] double speedChange)
23-
=> TestModCombination([new CatchModHalfTime { SpeedChange = { Value = speedChange } }]);
22+
[new Mod[] { new CatchModEasy() }, 0.5],
23+
[new Mod[] { new CatchModNoFail() }, 0.5],
2424

25-
[Test]
26-
public void TestDaycoreSpeeds([Values(0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 0.99)] double speedChange)
27-
=> TestModCombination([new CatchModDaycore { SpeedChange = { Value = speedChange } }]);
25+
[new Mod[] { new CatchModHalfTime { SpeedChange = { Value = 0.50 } } }, 0.1],
26+
[new Mod[] { new CatchModHalfTime { SpeedChange = { Value = 0.55 } } }, 0.1],
27+
[new Mod[] { new CatchModHalfTime { SpeedChange = { Value = 0.60 } } }, 0.2],
28+
[new Mod[] { new CatchModHalfTime { SpeedChange = { Value = 0.65 } } }, 0.2],
29+
[new Mod[] { new CatchModHalfTime { SpeedChange = { Value = 0.70 } } }, 0.3],
30+
[new Mod[] { new CatchModHalfTime { SpeedChange = { Value = 0.75 } } }, 0.3],
31+
[new Mod[] { new CatchModHalfTime { SpeedChange = { Value = 0.80 } } }, 0.4],
32+
[new Mod[] { new CatchModHalfTime { SpeedChange = { Value = 0.85 } } }, 0.4],
33+
[new Mod[] { new CatchModHalfTime { SpeedChange = { Value = 0.90 } } }, 0.5],
34+
[new Mod[] { new CatchModHalfTime { SpeedChange = { Value = 0.95 } } }, 0.5],
35+
[new Mod[] { new CatchModHalfTime { SpeedChange = { Value = 0.99 } } }, 0.5],
2836

29-
[Test]
30-
public void TestDoubleTimeSpeeds([Values(1.01, 1.05, 1.1, 1.15, 1.2, 1.25, 1.3, 1.35, 1.4, 1.45, 1.5, 1.55, 1.6, 1.65, 1.7, 1.75, 1.8, 1.85, 1.9, 1.95, 2)] double speedChange)
31-
=> TestModCombination([new CatchModDoubleTime { SpeedChange = { Value = speedChange } }]);
37+
[new Mod[] { new CatchModDaycore { SpeedChange = { Value = 0.50 } } }, 0.1],
38+
[new Mod[] { new CatchModDaycore { SpeedChange = { Value = 0.55 } } }, 0.1],
39+
[new Mod[] { new CatchModDaycore { SpeedChange = { Value = 0.60 } } }, 0.2],
40+
[new Mod[] { new CatchModDaycore { SpeedChange = { Value = 0.65 } } }, 0.2],
41+
[new Mod[] { new CatchModDaycore { SpeedChange = { Value = 0.70 } } }, 0.3],
42+
[new Mod[] { new CatchModDaycore { SpeedChange = { Value = 0.75 } } }, 0.3],
43+
[new Mod[] { new CatchModDaycore { SpeedChange = { Value = 0.80 } } }, 0.4],
44+
[new Mod[] { new CatchModDaycore { SpeedChange = { Value = 0.85 } } }, 0.4],
45+
[new Mod[] { new CatchModDaycore { SpeedChange = { Value = 0.90 } } }, 0.5],
46+
[new Mod[] { new CatchModDaycore { SpeedChange = { Value = 0.95 } } }, 0.5],
47+
[new Mod[] { new CatchModDaycore { SpeedChange = { Value = 0.99 } } }, 0.5],
3248

33-
[Test]
34-
public void TestNightcoreSpeeds([Values(1.01, 1.05, 1.1, 1.15, 1.2, 1.25, 1.3, 1.35, 1.4, 1.45, 1.5, 1.55, 1.6, 1.65, 1.7, 1.75, 1.8, 1.85, 1.9, 1.95, 2)] double speedChange)
35-
=> TestModCombination([new CatchModNightcore { SpeedChange = { Value = speedChange } }]);
49+
#endregion
3650

37-
[Test]
38-
public void TestMultiplicativeCombination()
39-
=> TestModCombination([new CatchModHidden(), new CatchModHardRock()]);
51+
#region Difficulty Increase
52+
53+
[new Mod[] { new CatchModHardRock() }, 1.12],
54+
[new Mod[] { new CatchModSuddenDeath() }, 1],
55+
[new Mod[] { new CatchModPerfect() }, 1],
56+
57+
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.01 } } }, 1.00],
58+
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.05 } } }, 1.00],
59+
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.10 } } }, 1.02],
60+
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.15 } } }, 1.02],
61+
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.20 } } }, 1.04],
62+
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.25 } } }, 1.04],
63+
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.30 } } }, 1.06],
64+
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.35 } } }, 1.06],
65+
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.40 } } }, 1.08],
66+
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.45 } } }, 1.08],
67+
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.50 } } }, 1.10],
68+
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.55 } } }, 1.10],
69+
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.60 } } }, 1.12],
70+
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.65 } } }, 1.12],
71+
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.70 } } }, 1.14],
72+
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.75 } } }, 1.14],
73+
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.80 } } }, 1.16],
74+
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.85 } } }, 1.16],
75+
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.90 } } }, 1.18],
76+
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.95 } } }, 1.18],
77+
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 2.00 } } }, 1.20],
78+
79+
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.01 } } }, 1.00],
80+
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.05 } } }, 1.00],
81+
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.10 } } }, 1.02],
82+
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.15 } } }, 1.02],
83+
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.20 } } }, 1.04],
84+
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.25 } } }, 1.04],
85+
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.30 } } }, 1.06],
86+
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.35 } } }, 1.06],
87+
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.40 } } }, 1.08],
88+
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.45 } } }, 1.08],
89+
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.50 } } }, 1.10],
90+
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.55 } } }, 1.10],
91+
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.60 } } }, 1.12],
92+
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.65 } } }, 1.12],
93+
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.70 } } }, 1.14],
94+
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.75 } } }, 1.14],
95+
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.80 } } }, 1.16],
96+
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.85 } } }, 1.16],
97+
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.90 } } }, 1.18],
98+
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.95 } } }, 1.18],
99+
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 2.00 } } }, 1.20],
100+
101+
[new Mod[] { new CatchModHidden() }, 1.06],
102+
103+
[new Mod[] { new CatchModFlashlight() }, 1.12],
104+
[new Mod[] { new CatchModFlashlight { ComboBasedSize = { Value = false } } }, 1],
105+
106+
[new Mod[] { new ModAccuracyChallenge() }, 1],
107+
108+
#endregion
109+
110+
#region Conversion
111+
112+
[new Mod[] { new CatchModDifficultyAdjust() }, 0.5],
113+
[new Mod[] { new CatchModClassic() }, 0.96],
114+
[new Mod[] { new CatchModMirror() }, 1],
115+
116+
#endregion
117+
118+
#region Automation
119+
120+
[new Mod[] { new CatchModAutoplay() }, 1],
121+
[new Mod[] { new CatchModCinema() }, 1],
122+
[new Mod[] { new CatchModRelax() }, 0.1],
123+
124+
#endregion
125+
126+
#region Fun
127+
128+
[new Mod[] { new ModWindUp() }, 0.5],
129+
[new Mod[] { new ModWindDown() }, 0.5],
130+
[new Mod[] { new CatchModFloatingFruits() }, 1],
131+
[new Mod[] { new CatchModMuted() }, 1],
132+
[new Mod[] { new CatchModNoScope() }, 1],
133+
[new Mod[] { new CatchModMovingFast() }, 1],
134+
[new Mod[] { new CatchModSynesthesia() }, 0.8],
135+
136+
#endregion
137+
138+
#region System
139+
140+
[new Mod[] { new ModScoreV2() }, 1],
141+
142+
#endregion
143+
144+
#region Combinations
145+
146+
[new Mod[] { new CatchModHidden(), new CatchModHardRock() }, 1.06 * 1.12]
147+
148+
#endregion
149+
];
150+
151+
[TestCaseSource(nameof(test_cases))]
152+
public void TestMultipliers(Mod[] mods, double expectedMultiplier)
153+
=> TestModCombination(mods, expectedMultiplier);
40154
}
41155
}

osu.Game.Rulesets.Catch/CatchRuleset.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ public override IEnumerable<Mod> GetModsFor(ModType type)
169169
}
170170
}
171171

172-
public override ScoreMultiplierCalculator CreateScoreMultiplierCalculator() => new CatchScoreMultiplierCalculator();
172+
public override ScoreMultiplierCalculator CreateScoreMultiplierCalculator(ScoreMultiplierContext context) => new CatchScoreMultiplierCalculator(context);
173173

174174
public override string Description => "osu!catch";
175175

osu.Game.Rulesets.Catch/Scoring/CatchScoreMultiplierCalculator.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ namespace osu.Game.Rulesets.Catch.Scoring
99
{
1010
public class CatchScoreMultiplierCalculator : ScoreMultiplierCalculator
1111
{
12-
static CatchScoreMultiplierCalculator()
12+
public CatchScoreMultiplierCalculator(ScoreMultiplierContext context)
13+
: base(context)
1314
{
1415
#region Difficulty Reduction
1516

0 commit comments

Comments
 (0)