Skip to content

Commit e3eeb76

Browse files
author
Bartłomiej Dach
authored
Fix client not sending data relevant to replay to spectator server (ppy#37919)
- Related to ppy#37818, but of no material help to it at this point (too late for that) As noted in ppy#37845 (comment). Upon comparison of replays recorded by the client and by the server the affected fields are: total score without mods, and the list of user pauses. Additionally, the date of setting the score may differ - server-side it seems to be written with UTC+0 while client-side it's written using the local timezone offset. Not really interested in fixing that last issue at this time. Also included is an intentionally loud disclaimer in `LegacyScoreEncoder` to tread with caution when treating the class. Not sure it'll help, and it's a bit late for it as pretty much every single versioning primitive has been ravaged to the brink of unusability, but maybe it'll help someone in the future. This also cleans up an unnecessary nullable on `FrameHeader.Mods` (added in ppy#30137). This change can be only done if users on releases earlier than 2024.1023.0 can no longer connect to spectator server. I leave it to reviewers to determine this as I have no visibility over current spectator server configuration. Inspecting the `osu_builds` table may help confirm this. If it provokes unease, I can back this change out.
1 parent 9727d95 commit e3eeb76

4 files changed

Lines changed: 58 additions & 8 deletions

File tree

osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public void TestResetFromReplayFrame()
8181
AccuracyJudgementCount = 1,
8282
ComboPortion = 0,
8383
BonusPortion = 0
84-
}, DateTimeOffset.Now)
84+
}, DateTimeOffset.Now, [], 0, [])
8585
});
8686

8787
Assert.That(scoreProcessor.TotalScore.Value, Is.Zero);
@@ -99,7 +99,7 @@ public void TestResetFromReplayFrame()
9999
AccuracyJudgementCount = 0,
100100
ComboPortion = 0,
101101
BonusPortion = 0
102-
}, DateTimeOffset.Now)
102+
}, DateTimeOffset.Now, [], 0, [])
103103
});
104104

105105
Assert.That(scoreProcessor.TotalScore.Value, Is.Zero);

osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ protected void UpdateUserStatesRandomly()
221221
[HitResult.Miss] = 0,
222222
[HitResult.Meh] = 0,
223223
[HitResult.Great] = 0
224-
}, new ScoreProcessorStatistics(), DateTimeOffset.Now);
224+
}, new ScoreProcessorStatistics(), DateTimeOffset.Now, [], 0, []);
225225
}
226226

227227
switch (RNG.Next(0, 3))

osu.Game/Online/Spectator/FrameHeader.cs

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,33 @@ public class FrameHeader
6262
/// The set of mods currently active.
6363
/// </summary>
6464
/// <remarks>
65-
/// Nullable for backwards compatibility with older clients
66-
/// (these structures are also used server-side, and <see langword="null"/> will be used as marker that the data isn't there).
67-
/// can be made non-nullable 20250407
65+
/// This is sent to spectator as mods can change during a play - one relevant circumstance
66+
/// is the automatic activation of Touch Device mod when usage of touch devices is detected.
6867
/// </remarks>
6968
[Key(7)]
70-
public APIMod[]? Mods { get; set; }
69+
public APIMod[] Mods { get; set; } = [];
70+
71+
/// <summary>
72+
/// The current total score without mod multipliers active.
73+
/// </summary>
74+
/// <remarks>
75+
/// Nullable for backwards compatibility with older clients that don't send this
76+
/// (server-side <see langword="null"/> is used to distinguish the lack of this data).
77+
/// can be made non-nullable 20261126
78+
/// </remarks>
79+
[Key(8)]
80+
public long? TotalScoreWithoutMods { get; set; }
81+
82+
/// <summary>
83+
/// The list of time instants in the play at which the player paused the game.
84+
/// </summary>
85+
/// <remarks>
86+
/// Nullable for backwards compatibility with older clients that don't send this
87+
/// (server-side <see langword="null"/> is used to distinguish the lack of this data).
88+
/// can be made non-nullable 20261126
89+
/// </remarks>
90+
[Key(9)]
91+
public int[]? Pauses { get; set; }
7192

7293
/// <summary>
7394
/// Construct header summary information from a point-in-time reference to a score which is actively being played.
@@ -83,13 +104,25 @@ public FrameHeader(ScoreInfo score, ScoreProcessorStatistics statistics)
83104
// copy for safety
84105
Statistics = new Dictionary<HitResult, int>(score.Statistics);
85106
Mods = score.APIMods.ToArray();
107+
TotalScoreWithoutMods = score.TotalScoreWithoutMods;
108+
Pauses = score.Pauses.ToArray();
86109

87110
ScoreProcessorStatistics = statistics;
88111
}
89112

90113
[JsonConstructor]
91114
[SerializationConstructor]
92-
public FrameHeader(long totalScore, double accuracy, int combo, int maxCombo, Dictionary<HitResult, int> statistics, ScoreProcessorStatistics scoreProcessorStatistics, DateTimeOffset receivedTime)
115+
public FrameHeader(
116+
long totalScore,
117+
double accuracy,
118+
int combo,
119+
int maxCombo,
120+
Dictionary<HitResult, int> statistics,
121+
ScoreProcessorStatistics scoreProcessorStatistics,
122+
DateTimeOffset receivedTime,
123+
APIMod[] mods,
124+
long? totalScoreWithoutMods,
125+
int[]? pauses)
93126
{
94127
TotalScore = totalScore;
95128
Accuracy = accuracy;
@@ -98,6 +131,9 @@ public FrameHeader(long totalScore, double accuracy, int combo, int maxCombo, Di
98131
Statistics = statistics;
99132
ScoreProcessorStatistics = scoreProcessorStatistics;
100133
ReceivedTime = receivedTime;
134+
Mods = mods;
135+
TotalScoreWithoutMods = totalScoreWithoutMods;
136+
Pauses = pauses;
101137
}
102138
}
103139
}

osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using osu.Game.Extensions;
1313
using osu.Game.IO.Legacy;
1414
using osu.Game.IO.Serialization;
15+
using osu.Game.Online.Spectator;
1516
using osu.Game.Replays.Legacy;
1617
using osu.Game.Rulesets.Objects.Legacy;
1718
using osu.Game.Rulesets.Replays;
@@ -20,6 +21,19 @@
2021

2122
namespace osu.Game.Scoring.Legacy
2223
{
24+
/// <summary>
25+
/// Encodes replays.
26+
/// </summary>
27+
/// <remarks>
28+
/// <b>When making <i>ANY</i> changes to the replay format to add new data, consider if:</b>
29+
/// <list type="bullet">
30+
/// <item><see cref="LATEST_VERSION"/> should be bumped accordingly,</item>
31+
/// <item>
32+
/// changes need to be made to <see cref="SpectatorClient"/> so that spectator server receives the new data being stored,
33+
/// as <b><i>spectator server</i> is responsible for the content of server-stored replays, <i>NOT</i> the client</b>.
34+
/// </item>
35+
/// </list>
36+
/// </remarks>
2337
public class LegacyScoreEncoder
2438
{
2539
/// <summary>

0 commit comments

Comments
 (0)