1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-15 11:07:25 +08:00
osu-lazer/osu.Game/Database/StandardisedScoreMigrationTools.cs
2023-06-13 02:12:23 +09:00

177 lines
6.9 KiB
C#

// 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.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
namespace osu.Game.Database
{
public static class StandardisedScoreMigrationTools
{
public static long GetNewStandardised(ScoreInfo score)
{
// Avoid retrieving from realm inside loops.
int maxCombo = score.MaxCombo;
var ruleset = score.Ruleset.CreateInstance();
var processor = ruleset.CreateScoreProcessor();
var beatmap = new Beatmap();
HitResult maxRulesetJudgement = ruleset.GetHitResults().First().result;
var maximumJudgements = score.MaximumStatistics
.Where(kvp => kvp.Key.AffectsCombo())
.OrderByDescending(kvp => Judgement.ToNumericResult(kvp.Key))
.SelectMany(kvp => Enumerable.Repeat(new FakeJudgement(kvp.Key), kvp.Value))
.ToList();
// This is a list of all results, ordered from best to worst.
// We are constructing a "best possible" score from the statistics provided because it's the best we can do.
List<HitResult> sortedHits = score.Statistics
.Where(kvp => kvp.Key.AffectsCombo())
.OrderByDescending(kvp => Judgement.ToNumericResult(kvp.Key))
.SelectMany(kvp => Enumerable.Repeat(kvp.Key, kvp.Value))
.ToList();
if (maximumJudgements.Count != sortedHits.Count)
{
// Older scores may not have maximum judgements populated correctly.
// In this case we need to fill them.
maximumJudgements = sortedHits
.Select(r => new FakeJudgement(getMaxJudgementFor(r, maxRulesetJudgement)))
.ToList();
}
foreach (var judgement in maximumJudgements)
beatmap.HitObjects.Add(new FakeHit(judgement));
processor.ApplyBeatmap(beatmap);
Queue<HitResult> misses = new Queue<HitResult>(score.Statistics
.Where(kvp => kvp.Key == HitResult.Miss || kvp.Key == HitResult.LargeTickMiss)
.SelectMany(kvp => Enumerable.Repeat(kvp.Key, kvp.Value)));
int maxJudgementIndex = 0;
foreach (var result in sortedHits)
{
// misses are handled from the queue.
if (result == HitResult.Miss || result == HitResult.LargeTickMiss)
continue;
if (processor.Combo.Value == maxCombo)
{
if (misses.Count > 0)
{
processor.ApplyResult(new JudgementResult(null!, maximumJudgements[maxJudgementIndex++])
{
Type = misses.Dequeue(),
});
}
else
{
// worst case scenario, insert a miss.
processor.ApplyResult(new JudgementResult(null!, new FakeJudgement(getMaxJudgementFor(HitResult.Miss, maxRulesetJudgement)))
{
Type = HitResult.Miss,
});
}
}
processor.ApplyResult(new JudgementResult(null!, maximumJudgements[maxJudgementIndex++])
{
Type = result
});
}
var bonusHits = score.Statistics
.Where(kvp => kvp.Key.IsBonus())
.SelectMany(kvp => Enumerable.Repeat(kvp.Key, kvp.Value));
foreach (var result in bonusHits)
processor.ApplyResult(new JudgementResult(null!, new FakeJudgement(result)) { Type = result });
// Not true for all scores for whatever reason. Oh well.
// Debug.Assert(processor.HighestCombo.Value == score.MaxCombo);
return processor.TotalScore.Value;
}
private static HitResult getMaxJudgementFor(HitResult hitResult, HitResult max)
{
switch (hitResult)
{
case HitResult.Miss:
case HitResult.Meh:
case HitResult.Ok:
case HitResult.Good:
case HitResult.Great:
case HitResult.Perfect:
return max;
case HitResult.SmallTickMiss:
case HitResult.SmallTickHit:
return HitResult.SmallTickHit;
case HitResult.LargeTickMiss:
case HitResult.LargeTickHit:
return HitResult.LargeTickHit;
}
return HitResult.IgnoreHit;
}
public static long GetOldStandardised(ScoreInfo score)
{
double accuracyScore =
(double)score.Statistics.Where(kvp => kvp.Key.AffectsAccuracy()).Sum(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value)
/ score.MaximumStatistics.Where(kvp => kvp.Key.AffectsAccuracy()).Sum(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value);
double comboScore = (double)score.MaxCombo / score.MaximumStatistics.Where(kvp => kvp.Key.AffectsCombo()).Sum(kvp => kvp.Value);
double bonusScore = score.Statistics.Where(kvp => kvp.Key.IsBonus()).Sum(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value);
double accuracyPortion = 0.3;
switch (score.RulesetID)
{
case 1:
accuracyPortion = 0.75;
break;
case 3:
accuracyPortion = 0.99;
break;
}
return (long)(1000000 * (accuracyPortion * accuracyScore + (1 - accuracyPortion) * comboScore) + bonusScore);
}
private class FakeHit : HitObject
{
private readonly Judgement judgement;
public override Judgement CreateJudgement() => judgement;
public FakeHit(Judgement judgement)
{
this.judgement = judgement;
}
}
private class FakeJudgement : Judgement
{
public override HitResult MaxResult { get; }
public FakeJudgement(HitResult result)
{
MaxResult = result;
}
}
}
}