Skip to content

Commit bd4723d

Browse files
authored
Merge pull request #20200 from smoogipoo/lazer-maximum-statistics
Populate `MaximumStatistics` for scores imported into lazer
2 parents 1b0edae + 280b1dd commit bd4723d

File tree

3 files changed

+128
-1
lines changed

3 files changed

+128
-1
lines changed

osu.Game/BackgroundBeatmapProcessor.cs

+53
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77
using System.Linq;
88
using System.Threading;
99
using System.Threading.Tasks;
10+
using Newtonsoft.Json;
1011
using osu.Framework.Allocation;
1112
using osu.Framework.Bindables;
1213
using osu.Framework.Graphics;
1314
using osu.Framework.Logging;
1415
using osu.Game.Beatmaps;
1516
using osu.Game.Database;
1617
using osu.Game.Rulesets;
18+
using osu.Game.Scoring;
1719
using osu.Game.Screens.Play;
1820

1921
namespace osu.Game
@@ -23,6 +25,9 @@ public class BackgroundBeatmapProcessor : Component
2325
[Resolved]
2426
private RulesetStore rulesetStore { get; set; } = null!;
2527

28+
[Resolved]
29+
private ScoreManager scoreManager { get; set; } = null!;
30+
2631
[Resolved]
2732
private RealmAccess realmAccess { get; set; } = null!;
2833

@@ -46,6 +51,7 @@ protected override void LoadComplete()
4651
Logger.Log("Beginning background beatmap processing..");
4752
checkForOutdatedStarRatings();
4853
processBeatmapSetsWithMissingMetrics();
54+
processScoresWithMissingStatistics();
4955
}).ContinueWith(t =>
5056
{
5157
if (t.Exception?.InnerException is ObjectDisposedException)
@@ -140,5 +146,52 @@ private void processBeatmapSetsWithMissingMetrics()
140146
});
141147
}
142148
}
149+
150+
private void processScoresWithMissingStatistics()
151+
{
152+
HashSet<Guid> scoreIds = new HashSet<Guid>();
153+
154+
Logger.Log("Querying for scores to reprocess...");
155+
156+
realmAccess.Run(r =>
157+
{
158+
foreach (var score in r.All<ScoreInfo>())
159+
{
160+
if (score.Statistics.Sum(kvp => kvp.Value) > 0 && score.MaximumStatistics.Sum(kvp => kvp.Value) == 0)
161+
scoreIds.Add(score.ID);
162+
}
163+
});
164+
165+
Logger.Log($"Found {scoreIds.Count} scores which require reprocessing.");
166+
167+
foreach (var id in scoreIds)
168+
{
169+
while (localUserPlayInfo?.IsPlaying.Value == true)
170+
{
171+
Logger.Log("Background processing sleeping due to active gameplay...");
172+
Thread.Sleep(TimeToSleepDuringGameplay);
173+
}
174+
175+
try
176+
{
177+
var score = scoreManager.Query(s => s.ID == id);
178+
179+
scoreManager.PopulateMaximumStatistics(score);
180+
181+
// Can't use async overload because we're not on the update thread.
182+
// ReSharper disable once MethodHasAsyncOverload
183+
realmAccess.Write(r =>
184+
{
185+
r.Find<ScoreInfo>(id).MaximumStatisticsJson = JsonConvert.SerializeObject(score.MaximumStatistics);
186+
});
187+
188+
Logger.Log($"Populated maximum statistics for score {id}");
189+
}
190+
catch (Exception e)
191+
{
192+
Logger.Log(@$"Failed to populate maximum statistics for {id}: {e}");
193+
}
194+
}
195+
}
143196
}
144197
}

osu.Game/Scoring/ScoreImporter.cs

+67
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Diagnostics;
67
using System.Linq;
78
using System.Threading;
89
using Newtonsoft.Json;
@@ -16,6 +17,8 @@
1617
using osu.Game.Online.API;
1718
using osu.Game.Online.API.Requests;
1819
using osu.Game.Online.API.Requests.Responses;
20+
using osu.Game.Rulesets.Judgements;
21+
using osu.Game.Rulesets.Scoring;
1922
using Realms;
2023

2124
namespace osu.Game.Scoring
@@ -71,13 +74,77 @@ protected override void Populate(ScoreInfo model, ArchiveReader? archive, Realm
7174
if (model.BeatmapInfo == null) throw new ArgumentNullException(nameof(model.BeatmapInfo));
7275
if (model.Ruleset == null) throw new ArgumentNullException(nameof(model.Ruleset));
7376

77+
PopulateMaximumStatistics(model);
78+
7479
if (string.IsNullOrEmpty(model.StatisticsJson))
7580
model.StatisticsJson = JsonConvert.SerializeObject(model.Statistics);
7681

7782
if (string.IsNullOrEmpty(model.MaximumStatisticsJson))
7883
model.MaximumStatisticsJson = JsonConvert.SerializeObject(model.MaximumStatistics);
7984
}
8085

86+
/// <summary>
87+
/// Populates the <see cref="ScoreInfo.MaximumStatistics"/> for a given <see cref="ScoreInfo"/>.
88+
/// </summary>
89+
/// <param name="score">The score to populate the statistics of.</param>
90+
public void PopulateMaximumStatistics(ScoreInfo score)
91+
{
92+
if (score.MaximumStatistics.Select(kvp => kvp.Value).Sum() > 0)
93+
return;
94+
95+
var beatmap = score.BeatmapInfo.Detach();
96+
var ruleset = score.Ruleset.Detach();
97+
var rulesetInstance = ruleset.CreateInstance();
98+
99+
Debug.Assert(rulesetInstance != null);
100+
101+
// Populate the maximum statistics.
102+
HitResult maxBasicResult = rulesetInstance.GetHitResults()
103+
.Select(h => h.result)
104+
.Where(h => h.IsBasic())
105+
.OrderByDescending(Judgement.ToNumericResult).First();
106+
107+
foreach ((HitResult result, int count) in score.Statistics)
108+
{
109+
switch (result)
110+
{
111+
case HitResult.LargeTickHit:
112+
case HitResult.LargeTickMiss:
113+
score.MaximumStatistics[HitResult.LargeTickHit] = score.MaximumStatistics.GetValueOrDefault(HitResult.LargeTickHit) + count;
114+
break;
115+
116+
case HitResult.SmallTickHit:
117+
case HitResult.SmallTickMiss:
118+
score.MaximumStatistics[HitResult.SmallTickHit] = score.MaximumStatistics.GetValueOrDefault(HitResult.SmallTickHit) + count;
119+
break;
120+
121+
case HitResult.IgnoreHit:
122+
case HitResult.IgnoreMiss:
123+
case HitResult.SmallBonus:
124+
case HitResult.LargeBonus:
125+
break;
126+
127+
default:
128+
score.MaximumStatistics[maxBasicResult] = score.MaximumStatistics.GetValueOrDefault(maxBasicResult) + count;
129+
break;
130+
}
131+
}
132+
133+
if (!score.IsLegacyScore)
134+
return;
135+
136+
#pragma warning disable CS0618
137+
// In osu! and osu!mania, some judgements affect combo but aren't stored to scores.
138+
// A special hit result is used to pad out the combo value to match, based on the max combo from the difficulty attributes.
139+
var calculator = rulesetInstance.CreateDifficultyCalculator(beatmaps().GetWorkingBeatmap(beatmap));
140+
var attributes = calculator.Calculate(score.Mods);
141+
142+
int maxComboFromStatistics = score.MaximumStatistics.Where(kvp => kvp.Key.AffectsCombo()).Select(kvp => kvp.Value).DefaultIfEmpty(0).Sum();
143+
if (attributes.MaxCombo > maxComboFromStatistics)
144+
score.MaximumStatistics[HitResult.LegacyComboIncrease] = attributes.MaxCombo - maxComboFromStatistics;
145+
#pragma warning restore CS0618
146+
}
147+
81148
protected override void PostImport(ScoreInfo model, Realm realm, bool batchImport)
82149
{
83150
base.PostImport(model, realm, batchImport);

osu.Game/Scoring/ScoreManager.cs

+8-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ public class ScoreManager : ModelManager<ScoreInfo>, IModelImporter<ScoreInfo>
2828
private readonly OsuConfigManager configManager;
2929
private readonly ScoreImporter scoreImporter;
3030

31-
public ScoreManager(RulesetStore rulesets, Func<BeatmapManager> beatmaps, Storage storage, RealmAccess realm, IAPIProvider api, OsuConfigManager configManager = null)
31+
public ScoreManager(RulesetStore rulesets, Func<BeatmapManager> beatmaps, Storage storage, RealmAccess realm, IAPIProvider api,
32+
OsuConfigManager configManager = null)
3233
: base(storage, realm)
3334
{
3435
this.configManager = configManager;
@@ -178,6 +179,12 @@ public void Delete(BeatmapInfo beatmap, bool silent = false)
178179
public Live<ScoreInfo> Import(ScoreInfo item, ArchiveReader archive = null, bool batchImport = false, CancellationToken cancellationToken = default) =>
179180
scoreImporter.ImportModel(item, archive, batchImport, cancellationToken);
180181

182+
/// <summary>
183+
/// Populates the <see cref="ScoreInfo.MaximumStatistics"/> for a given <see cref="ScoreInfo"/>.
184+
/// </summary>
185+
/// <param name="score">The score to populate the statistics of.</param>
186+
public void PopulateMaximumStatistics(ScoreInfo score) => scoreImporter.PopulateMaximumStatistics(score);
187+
181188
#region Implementation of IPresentImports<ScoreInfo>
182189

183190
public Action<IEnumerable<Live<ScoreInfo>>> PresentImport

0 commit comments

Comments
 (0)