mirror of
https://github.com/ppy/osu.git
synced 2025-02-15 06:13:03 +08:00
Merge pull request #25993 from smoogipoo/fix-total-score-conversion
Relocate numeric HitResult values, add accuracy conversion
This commit is contained in:
commit
7e9d12e1d2
@ -7,7 +7,7 @@ using System.Linq;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Catch.Mods;
|
using osu.Game.Rulesets.Catch.Mods;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Catch.Scoring;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
{
|
{
|
||||||
internal class CatchLegacyScoreSimulator : ILegacyScoreSimulator
|
internal class CatchLegacyScoreSimulator : ILegacyScoreSimulator
|
||||||
{
|
{
|
||||||
|
private readonly ScoreProcessor scoreProcessor = new CatchScoreProcessor();
|
||||||
|
|
||||||
private int legacyBonusScore;
|
private int legacyBonusScore;
|
||||||
private int standardisedBonusScore;
|
private int standardisedBonusScore;
|
||||||
private int combo;
|
private int combo;
|
||||||
@ -134,7 +136,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
if (isBonus)
|
if (isBonus)
|
||||||
{
|
{
|
||||||
legacyBonusScore += scoreIncrease;
|
legacyBonusScore += scoreIncrease;
|
||||||
standardisedBonusScore += Judgement.ToNumericResult(bonusResult);
|
standardisedBonusScore += scoreProcessor.GetBaseScoreForResult(bonusResult);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
attributes.AccuracyScore += scoreIncrease;
|
attributes.AccuracyScore += scoreIncrease;
|
||||||
|
@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Scoring
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override double GetComboScoreChange(JudgementResult result)
|
protected override double GetComboScoreChange(JudgementResult result)
|
||||||
=> GetNumericResultFor(result) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(combo_cap, combo_base));
|
=> GetBaseScoreForResult(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(combo_cap, combo_base));
|
||||||
|
|
||||||
public override ScoreRank RankFromAccuracy(double accuracy)
|
public override ScoreRank RankFromAccuracy(double accuracy)
|
||||||
{
|
{
|
||||||
|
@ -31,44 +31,31 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
|||||||
+ bonusPortion;
|
+ bonusPortion;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override double GetNumericResultFor(JudgementResult result)
|
|
||||||
{
|
|
||||||
switch (result.Type)
|
|
||||||
{
|
|
||||||
case HitResult.Perfect:
|
|
||||||
return 305;
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.GetNumericResultFor(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double GetMaxNumericResultFor(JudgementResult result)
|
|
||||||
{
|
|
||||||
switch (result.Judgement.MaxResult)
|
|
||||||
{
|
|
||||||
case HitResult.Perfect:
|
|
||||||
return 305;
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.GetMaxNumericResultFor(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double GetComboScoreChange(JudgementResult result)
|
protected override double GetComboScoreChange(JudgementResult result)
|
||||||
{
|
{
|
||||||
double numericResult;
|
return getBaseComboScoreForResult(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(400, combo_base));
|
||||||
|
}
|
||||||
|
|
||||||
switch (result.Type)
|
public override int GetBaseScoreForResult(HitResult result)
|
||||||
|
{
|
||||||
|
switch (result)
|
||||||
{
|
{
|
||||||
case HitResult.Perfect:
|
case HitResult.Perfect:
|
||||||
numericResult = 300;
|
return 305;
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
numericResult = GetNumericResultFor(result);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return numericResult * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(400, combo_base));
|
return base.GetBaseScoreForResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getBaseComboScoreForResult(HitResult result)
|
||||||
|
{
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case HitResult.Perfect:
|
||||||
|
return 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetBaseScoreForResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class JudgementOrderComparer : IComparer<HitObject>
|
private class JudgementOrderComparer : IComparer<HitObject>
|
||||||
|
@ -58,10 +58,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
double trackerRotationTolerance = 0;
|
double trackerRotationTolerance = 0;
|
||||||
|
|
||||||
addSeekStep(5000);
|
addSeekStep(5000);
|
||||||
AddStep("calculate rotation tolerance", () =>
|
AddStep("calculate rotation tolerance", () => { trackerRotationTolerance = Math.Abs(drawableSpinner.RotationTracker.Rotation * 0.1f); });
|
||||||
{
|
|
||||||
trackerRotationTolerance = Math.Abs(drawableSpinner.RotationTracker.Rotation * 0.1f);
|
|
||||||
});
|
|
||||||
AddAssert("is disc rotation not almost 0", () => drawableSpinner.RotationTracker.Rotation, () => Is.Not.EqualTo(0).Within(100));
|
AddAssert("is disc rotation not almost 0", () => drawableSpinner.RotationTracker.Rotation, () => Is.Not.EqualTo(0).Within(100));
|
||||||
AddAssert("is disc rotation absolute not almost 0", () => drawableSpinner.Result.TotalRotation, () => Is.Not.EqualTo(0).Within(100));
|
AddAssert("is disc rotation absolute not almost 0", () => drawableSpinner.Result.TotalRotation, () => Is.Not.EqualTo(0).Within(100));
|
||||||
|
|
||||||
@ -133,9 +130,11 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
AddAssert("player score matching expected bonus score", () =>
|
AddAssert("player score matching expected bonus score", () =>
|
||||||
{
|
{
|
||||||
|
var scoreProcessor = ((ScoreExposedPlayer)Player).ScoreProcessor;
|
||||||
|
|
||||||
// multipled by 2 to nullify the score multiplier. (autoplay mod selected)
|
// multipled by 2 to nullify the score multiplier. (autoplay mod selected)
|
||||||
long totalScore = ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value * 2;
|
long totalScore = scoreProcessor.TotalScore.Value * 2;
|
||||||
return totalScore == (int)(drawableSpinner.Result.TotalRotation / 360) * new SpinnerTick().CreateJudgement().MaxNumericResult;
|
return totalScore == (int)(drawableSpinner.Result.TotalRotation / 360) * scoreProcessor.GetBaseScoreForResult(new SpinnerTick().CreateJudgement().MaxResult);
|
||||||
});
|
});
|
||||||
|
|
||||||
addSeekStep(0);
|
addSeekStep(0);
|
||||||
|
@ -5,12 +5,12 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Judgements;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Scoring;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.Scoring.Legacy;
|
using osu.Game.Rulesets.Scoring.Legacy;
|
||||||
|
|
||||||
@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
{
|
{
|
||||||
internal class OsuLegacyScoreSimulator : ILegacyScoreSimulator
|
internal class OsuLegacyScoreSimulator : ILegacyScoreSimulator
|
||||||
{
|
{
|
||||||
|
private readonly ScoreProcessor scoreProcessor = new OsuScoreProcessor();
|
||||||
|
|
||||||
private int legacyBonusScore;
|
private int legacyBonusScore;
|
||||||
private int standardisedBonusScore;
|
private int standardisedBonusScore;
|
||||||
private int combo;
|
private int combo;
|
||||||
@ -171,7 +173,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
if (isBonus)
|
if (isBonus)
|
||||||
{
|
{
|
||||||
legacyBonusScore += scoreIncrease;
|
legacyBonusScore += scoreIncrease;
|
||||||
standardisedBonusScore += Judgement.ToNumericResult(bonusResult);
|
standardisedBonusScore += scoreProcessor.GetBaseScoreForResult(bonusResult);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
attributes.AccuracyScore += scoreIncrease;
|
attributes.AccuracyScore += scoreIncrease;
|
||||||
|
@ -17,6 +17,7 @@ using osu.Game.Rulesets.Judgements;
|
|||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Judgements;
|
using osu.Game.Rulesets.Osu.Judgements;
|
||||||
|
using osu.Game.Rulesets.Osu.Scoring;
|
||||||
using osu.Game.Rulesets.Osu.Skinning;
|
using osu.Game.Rulesets.Osu.Skinning;
|
||||||
using osu.Game.Rulesets.Osu.Skinning.Default;
|
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
@ -312,7 +313,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
updateBonusScore();
|
updateBonusScore();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly int score_per_tick = new SpinnerBonusTick.OsuSpinnerBonusTickJudgement().MaxNumericResult;
|
private static readonly int score_per_tick = new OsuScoreProcessor().GetBaseScoreForResult(new SpinnerBonusTick.OsuSpinnerBonusTickJudgement().MaxResult);
|
||||||
|
|
||||||
private void updateBonusScore()
|
private void updateBonusScore()
|
||||||
{
|
{
|
||||||
|
@ -5,7 +5,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Judgements;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
@ -13,11 +12,14 @@ using osu.Game.Rulesets.Scoring;
|
|||||||
using osu.Game.Rulesets.Scoring.Legacy;
|
using osu.Game.Rulesets.Scoring.Legacy;
|
||||||
using osu.Game.Rulesets.Taiko.Mods;
|
using osu.Game.Rulesets.Taiko.Mods;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
using osu.Game.Rulesets.Taiko.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Difficulty
|
namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||||
{
|
{
|
||||||
internal class TaikoLegacyScoreSimulator : ILegacyScoreSimulator
|
internal class TaikoLegacyScoreSimulator : ILegacyScoreSimulator
|
||||||
{
|
{
|
||||||
|
private readonly ScoreProcessor scoreProcessor = new TaikoScoreProcessor();
|
||||||
|
|
||||||
private int legacyBonusScore;
|
private int legacyBonusScore;
|
||||||
private int standardisedBonusScore;
|
private int standardisedBonusScore;
|
||||||
private int combo;
|
private int combo;
|
||||||
@ -191,7 +193,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
if (isBonus)
|
if (isBonus)
|
||||||
{
|
{
|
||||||
legacyBonusScore += scoreIncrease;
|
legacyBonusScore += scoreIncrease;
|
||||||
standardisedBonusScore += Judgement.ToNumericResult(bonusResult);
|
standardisedBonusScore += scoreProcessor.GetBaseScoreForResult(bonusResult);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
attributes.AccuracyScore += scoreIncrease;
|
attributes.AccuracyScore += scoreIncrease;
|
||||||
|
@ -28,20 +28,20 @@ namespace osu.Game.Rulesets.Taiko.Scoring
|
|||||||
|
|
||||||
protected override double GetComboScoreChange(JudgementResult result)
|
protected override double GetComboScoreChange(JudgementResult result)
|
||||||
{
|
{
|
||||||
return GetNumericResultFor(result)
|
return GetBaseScoreForResult(result.Type)
|
||||||
* Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(400, combo_base))
|
* Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(400, combo_base))
|
||||||
* strongScaleValue(result);
|
* strongScaleValue(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override double GetNumericResultFor(JudgementResult result)
|
public override int GetBaseScoreForResult(HitResult result)
|
||||||
{
|
{
|
||||||
switch (result.Type)
|
switch (result)
|
||||||
{
|
{
|
||||||
case HitResult.Ok:
|
case HitResult.Ok:
|
||||||
return 150;
|
return 150;
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.GetNumericResultFor(result);
|
return base.GetBaseScoreForResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private double strongScaleValue(JudgementResult result)
|
private double strongScaleValue(JudgementResult result)
|
||||||
|
@ -48,7 +48,7 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
// Apply a judgement
|
// Apply a judgement
|
||||||
scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new TestJudgement(HitResult.LargeBonus)) { Type = HitResult.LargeBonus });
|
scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new TestJudgement(HitResult.LargeBonus)) { Type = HitResult.LargeBonus });
|
||||||
|
|
||||||
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(Judgement.LARGE_BONUS_SCORE));
|
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(scoreProcessor.GetBaseScoreForResult(HitResult.LargeBonus)));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -340,15 +340,12 @@ namespace osu.Game
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var score = scoreManager.Query(s => s.ID == id);
|
|
||||||
long newTotalScore = StandardisedScoreMigrationTools.ConvertFromLegacyTotalScore(score, beatmapManager);
|
|
||||||
|
|
||||||
// Can't use async overload because we're not on the update thread.
|
// Can't use async overload because we're not on the update thread.
|
||||||
// ReSharper disable once MethodHasAsyncOverload
|
// ReSharper disable once MethodHasAsyncOverload
|
||||||
realmAccess.Write(r =>
|
realmAccess.Write(r =>
|
||||||
{
|
{
|
||||||
ScoreInfo s = r.Find<ScoreInfo>(id)!;
|
ScoreInfo s = r.Find<ScoreInfo>(id)!;
|
||||||
s.TotalScore = newTotalScore;
|
StandardisedScoreMigrationTools.UpdateFromLegacy(s, beatmapManager);
|
||||||
s.TotalScoreVersion = LegacyScoreEncoder.LATEST_VERSION;
|
s.TotalScoreVersion = LegacyScoreEncoder.LATEST_VERSION;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -57,14 +57,14 @@ namespace osu.Game.Database
|
|||||||
// We are constructing a "best possible" score from the statistics provided because it's the best we can do.
|
// We are constructing a "best possible" score from the statistics provided because it's the best we can do.
|
||||||
List<HitResult> sortedHits = score.Statistics
|
List<HitResult> sortedHits = score.Statistics
|
||||||
.Where(kvp => kvp.Key.AffectsCombo())
|
.Where(kvp => kvp.Key.AffectsCombo())
|
||||||
.OrderByDescending(kvp => Judgement.ToNumericResult(kvp.Key))
|
.OrderByDescending(kvp => processor.GetBaseScoreForResult(kvp.Key))
|
||||||
.SelectMany(kvp => Enumerable.Repeat(kvp.Key, kvp.Value))
|
.SelectMany(kvp => Enumerable.Repeat(kvp.Key, kvp.Value))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// Attempt to use maximum statistics from the database.
|
// Attempt to use maximum statistics from the database.
|
||||||
var maximumJudgements = score.MaximumStatistics
|
var maximumJudgements = score.MaximumStatistics
|
||||||
.Where(kvp => kvp.Key.AffectsCombo())
|
.Where(kvp => kvp.Key.AffectsCombo())
|
||||||
.OrderByDescending(kvp => Judgement.ToNumericResult(kvp.Key))
|
.OrderByDescending(kvp => processor.GetBaseScoreForResult(kvp.Key))
|
||||||
.SelectMany(kvp => Enumerable.Repeat(new FakeJudgement(kvp.Key), kvp.Value))
|
.SelectMany(kvp => Enumerable.Repeat(new FakeJudgement(kvp.Key), kvp.Value))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
@ -169,10 +169,10 @@ namespace osu.Game.Database
|
|||||||
public static long GetOldStandardised(ScoreInfo score)
|
public static long GetOldStandardised(ScoreInfo score)
|
||||||
{
|
{
|
||||||
double accuracyScore =
|
double accuracyScore =
|
||||||
(double)score.Statistics.Where(kvp => kvp.Key.AffectsAccuracy()).Sum(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value)
|
(double)score.Statistics.Where(kvp => kvp.Key.AffectsAccuracy()).Sum(kvp => numericScoreFor(kvp.Key) * kvp.Value)
|
||||||
/ score.MaximumStatistics.Where(kvp => kvp.Key.AffectsAccuracy()).Sum(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value);
|
/ score.MaximumStatistics.Where(kvp => kvp.Key.AffectsAccuracy()).Sum(kvp => numericScoreFor(kvp.Key) * kvp.Value);
|
||||||
double comboScore = (double)score.MaxCombo / score.MaximumStatistics.Where(kvp => kvp.Key.AffectsCombo()).Sum(kvp => 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 bonusScore = score.Statistics.Where(kvp => kvp.Key.IsBonus()).Sum(kvp => numericScoreFor(kvp.Key) * kvp.Value);
|
||||||
|
|
||||||
double accuracyPortion = 0.3;
|
double accuracyPortion = 0.3;
|
||||||
|
|
||||||
@ -193,6 +193,65 @@ namespace osu.Game.Database
|
|||||||
modMultiplier *= mod.ScoreMultiplier;
|
modMultiplier *= mod.ScoreMultiplier;
|
||||||
|
|
||||||
return (long)Math.Round((1000000 * (accuracyPortion * accuracyScore + (1 - accuracyPortion) * comboScore) + bonusScore) * modMultiplier);
|
return (long)Math.Round((1000000 * (accuracyPortion * accuracyScore + (1 - accuracyPortion) * comboScore) + bonusScore) * modMultiplier);
|
||||||
|
|
||||||
|
static int numericScoreFor(HitResult result)
|
||||||
|
{
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case HitResult.SmallTickHit:
|
||||||
|
return 10;
|
||||||
|
|
||||||
|
case HitResult.LargeTickHit:
|
||||||
|
return 30;
|
||||||
|
|
||||||
|
case HitResult.Meh:
|
||||||
|
return 50;
|
||||||
|
|
||||||
|
case HitResult.Ok:
|
||||||
|
return 100;
|
||||||
|
|
||||||
|
case HitResult.Good:
|
||||||
|
return 200;
|
||||||
|
|
||||||
|
case HitResult.Great:
|
||||||
|
return 300;
|
||||||
|
|
||||||
|
case HitResult.Perfect:
|
||||||
|
return 315;
|
||||||
|
|
||||||
|
case HitResult.SmallBonus:
|
||||||
|
return 10;
|
||||||
|
|
||||||
|
case HitResult.LargeBonus:
|
||||||
|
return 50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates a legacy <see cref="ScoreInfo"/> to standardised scoring.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="score">The score to update.</param>
|
||||||
|
/// <param name="beatmaps">A <see cref="BeatmapManager"/> used for <see cref="WorkingBeatmap"/> lookups.</param>
|
||||||
|
public static void UpdateFromLegacy(ScoreInfo score, BeatmapManager beatmaps)
|
||||||
|
{
|
||||||
|
score.TotalScore = convertFromLegacyTotalScore(score, beatmaps);
|
||||||
|
score.Accuracy = ComputeAccuracy(score);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates a legacy <see cref="ScoreInfo"/> to standardised scoring.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="score">The score to update.</param>
|
||||||
|
/// <param name="difficulty">The beatmap difficulty.</param>
|
||||||
|
/// <param name="attributes">The legacy scoring attributes for the beatmap which the score was set on.</param>
|
||||||
|
public static void UpdateFromLegacy(ScoreInfo score, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes)
|
||||||
|
{
|
||||||
|
score.TotalScore = convertFromLegacyTotalScore(score, difficulty, attributes);
|
||||||
|
score.Accuracy = ComputeAccuracy(score);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -201,7 +260,7 @@ namespace osu.Game.Database
|
|||||||
/// <param name="score">The score to convert the total score of.</param>
|
/// <param name="score">The score to convert the total score of.</param>
|
||||||
/// <param name="beatmaps">A <see cref="BeatmapManager"/> used for <see cref="WorkingBeatmap"/> lookups.</param>
|
/// <param name="beatmaps">A <see cref="BeatmapManager"/> used for <see cref="WorkingBeatmap"/> lookups.</param>
|
||||||
/// <returns>The standardised total score.</returns>
|
/// <returns>The standardised total score.</returns>
|
||||||
public static long ConvertFromLegacyTotalScore(ScoreInfo score, BeatmapManager beatmaps)
|
private static long convertFromLegacyTotalScore(ScoreInfo score, BeatmapManager beatmaps)
|
||||||
{
|
{
|
||||||
if (!score.IsLegacyScore)
|
if (!score.IsLegacyScore)
|
||||||
return score.TotalScore;
|
return score.TotalScore;
|
||||||
@ -224,7 +283,7 @@ namespace osu.Game.Database
|
|||||||
ILegacyScoreSimulator sv1Simulator = legacyRuleset.CreateLegacyScoreSimulator();
|
ILegacyScoreSimulator sv1Simulator = legacyRuleset.CreateLegacyScoreSimulator();
|
||||||
LegacyScoreAttributes attributes = sv1Simulator.Simulate(beatmap, playableBeatmap);
|
LegacyScoreAttributes attributes = sv1Simulator.Simulate(beatmap, playableBeatmap);
|
||||||
|
|
||||||
return ConvertFromLegacyTotalScore(score, LegacyBeatmapConversionDifficultyInfo.FromBeatmap(beatmap.Beatmap), attributes);
|
return convertFromLegacyTotalScore(score, LegacyBeatmapConversionDifficultyInfo.FromBeatmap(beatmap.Beatmap), attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -234,7 +293,7 @@ namespace osu.Game.Database
|
|||||||
/// <param name="difficulty">The beatmap difficulty.</param>
|
/// <param name="difficulty">The beatmap difficulty.</param>
|
||||||
/// <param name="attributes">The legacy scoring attributes for the beatmap which the score was set on.</param>
|
/// <param name="attributes">The legacy scoring attributes for the beatmap which the score was set on.</param>
|
||||||
/// <returns>The standardised total score.</returns>
|
/// <returns>The standardised total score.</returns>
|
||||||
public static long ConvertFromLegacyTotalScore(ScoreInfo score, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes)
|
private static long convertFromLegacyTotalScore(ScoreInfo score, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes)
|
||||||
{
|
{
|
||||||
if (!score.IsLegacyScore)
|
if (!score.IsLegacyScore)
|
||||||
return score.TotalScore;
|
return score.TotalScore;
|
||||||
@ -386,6 +445,19 @@ namespace osu.Game.Database
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static double ComputeAccuracy(ScoreInfo scoreInfo)
|
||||||
|
{
|
||||||
|
Ruleset ruleset = scoreInfo.Ruleset.CreateInstance();
|
||||||
|
ScoreProcessor scoreProcessor = ruleset.CreateScoreProcessor();
|
||||||
|
|
||||||
|
int baseScore = scoreInfo.Statistics.Where(kvp => kvp.Key.AffectsAccuracy())
|
||||||
|
.Sum(kvp => kvp.Value * scoreProcessor.GetBaseScoreForResult(kvp.Key));
|
||||||
|
int maxBaseScore = scoreInfo.MaximumStatistics.Where(kvp => kvp.Key.AffectsAccuracy())
|
||||||
|
.Sum(kvp => kvp.Value * scoreProcessor.GetBaseScoreForResult(kvp.Key));
|
||||||
|
|
||||||
|
return maxBaseScore == 0 ? 1 : baseScore / (double)maxBaseScore;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to populate the <paramref name="score"/> model using data parsed from its corresponding replay file.
|
/// Used to populate the <paramref name="score"/> model using data parsed from its corresponding replay file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -11,16 +11,6 @@ namespace osu.Game.Rulesets.Judgements
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class Judgement
|
public class Judgement
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The score awarded for a small bonus.
|
|
||||||
/// </summary>
|
|
||||||
public const int SMALL_BONUS_SCORE = 10;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The score awarded for a large bonus.
|
|
||||||
/// </summary>
|
|
||||||
public const int LARGE_BONUS_SCORE = 50;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The default health increase for a maximum judgement, as a proportion of total health.
|
/// The default health increase for a maximum judgement, as a proportion of total health.
|
||||||
/// By default, each maximum judgement restores 5% of total health.
|
/// By default, each maximum judgement restores 5% of total health.
|
||||||
@ -91,23 +81,11 @@ namespace osu.Game.Rulesets.Judgements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The numeric score representation for the maximum achievable result.
|
|
||||||
/// </summary>
|
|
||||||
public int MaxNumericResult => ToNumericResult(MaxResult);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The health increase for the maximum achievable result.
|
/// The health increase for the maximum achievable result.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double MaxHealthIncrease => HealthIncreaseFor(MaxResult);
|
public double MaxHealthIncrease => HealthIncreaseFor(MaxResult);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Retrieves the numeric score representation of a <see cref="JudgementResult"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="result">The <see cref="JudgementResult"/> to find the numeric score representation for.</param>
|
|
||||||
/// <returns>The numeric score representation of <paramref name="result"/>.</returns>
|
|
||||||
public int NumericResultFor(JudgementResult result) => ToNumericResult(result.Type);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the numeric health increase of a <see cref="HitResult"/>.
|
/// Retrieves the numeric health increase of a <see cref="HitResult"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -165,41 +143,6 @@ namespace osu.Game.Rulesets.Judgements
|
|||||||
/// <returns>The numeric health increase of <paramref name="result"/>.</returns>
|
/// <returns>The numeric health increase of <paramref name="result"/>.</returns>
|
||||||
public double HealthIncreaseFor(JudgementResult result) => HealthIncreaseFor(result.Type);
|
public double HealthIncreaseFor(JudgementResult result) => HealthIncreaseFor(result.Type);
|
||||||
|
|
||||||
public override string ToString() => $"MaxResult:{MaxResult} MaxScore:{MaxNumericResult}";
|
public override string ToString() => $"MaxResult:{MaxResult}";
|
||||||
|
|
||||||
public static int ToNumericResult(HitResult result)
|
|
||||||
{
|
|
||||||
switch (result)
|
|
||||||
{
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
case HitResult.SmallTickHit:
|
|
||||||
return 10;
|
|
||||||
|
|
||||||
case HitResult.LargeTickHit:
|
|
||||||
return 30;
|
|
||||||
|
|
||||||
case HitResult.Meh:
|
|
||||||
return 50;
|
|
||||||
|
|
||||||
case HitResult.Ok:
|
|
||||||
return 100;
|
|
||||||
|
|
||||||
case HitResult.Good:
|
|
||||||
return 200;
|
|
||||||
|
|
||||||
case HitResult.Great:
|
|
||||||
// Perfect doesn't actually give more score / accuracy directly.
|
|
||||||
case HitResult.Perfect:
|
|
||||||
return 300;
|
|
||||||
|
|
||||||
case HitResult.SmallBonus:
|
|
||||||
return SMALL_BONUS_SCORE;
|
|
||||||
|
|
||||||
case HitResult.LargeBonus:
|
|
||||||
return LARGE_BONUS_SCORE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,6 +112,6 @@ namespace osu.Game.Rulesets.Judgements
|
|||||||
RawTime = null;
|
RawTime = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"{Type} (Score:{Judgement.NumericResultFor(this)} HP:{Judgement.HealthIncreaseFor(this)} {Judgement})";
|
public override string ToString() => $"{Type} ({Judgement})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -227,12 +227,12 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
|
|
||||||
if (result.Judgement.MaxResult.AffectsAccuracy())
|
if (result.Judgement.MaxResult.AffectsAccuracy())
|
||||||
{
|
{
|
||||||
currentMaximumBaseScore += GetMaxNumericResultFor(result);
|
currentMaximumBaseScore += GetBaseScoreForResult(result.Judgement.MaxResult);
|
||||||
currentAccuracyJudgementCount++;
|
currentAccuracyJudgementCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.Type.AffectsAccuracy())
|
if (result.Type.AffectsAccuracy())
|
||||||
currentBaseScore += GetNumericResultFor(result);
|
currentBaseScore += GetBaseScoreForResult(result.Type);
|
||||||
|
|
||||||
if (result.Type.IsBonus())
|
if (result.Type.IsBonus())
|
||||||
currentBonusPortion += GetBonusScoreChange(result);
|
currentBonusPortion += GetBonusScoreChange(result);
|
||||||
@ -276,12 +276,12 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
|
|
||||||
if (result.Judgement.MaxResult.AffectsAccuracy())
|
if (result.Judgement.MaxResult.AffectsAccuracy())
|
||||||
{
|
{
|
||||||
currentMaximumBaseScore -= GetMaxNumericResultFor(result);
|
currentMaximumBaseScore -= GetBaseScoreForResult(result.Judgement.MaxResult);
|
||||||
currentAccuracyJudgementCount--;
|
currentAccuracyJudgementCount--;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.Type.AffectsAccuracy())
|
if (result.Type.AffectsAccuracy())
|
||||||
currentBaseScore -= GetNumericResultFor(result);
|
currentBaseScore -= GetBaseScoreForResult(result.Type);
|
||||||
|
|
||||||
if (result.Type.IsBonus())
|
if (result.Type.IsBonus())
|
||||||
currentBonusPortion -= GetBonusScoreChange(result);
|
currentBonusPortion -= GetBonusScoreChange(result);
|
||||||
@ -297,21 +297,51 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
updateScore();
|
updateScore();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual double GetBonusScoreChange(JudgementResult result) => GetNumericResultFor(result);
|
/// <summary>
|
||||||
|
/// Gets the final score change to be applied to the bonus portion of the score.
|
||||||
protected virtual double GetComboScoreChange(JudgementResult result) => GetMaxNumericResultFor(result) * Math.Pow(result.ComboAfterJudgement, COMBO_EXPONENT);
|
/// </summary>
|
||||||
|
/// <param name="result">The judgement result.</param>
|
||||||
|
protected virtual double GetBonusScoreChange(JudgementResult result) => GetBaseScoreForResult(result.Type);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the numeric score representation for a <see cref="JudgementResult"/>.
|
/// Gets the final score change to be applied to the combo portion of the score.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="result">The <see cref="JudgementResult"/>.</param>
|
/// <param name="result">The judgement result.</param>
|
||||||
protected virtual double GetNumericResultFor(JudgementResult result) => result.Judgement.NumericResultFor(result);
|
protected virtual double GetComboScoreChange(JudgementResult result) => GetBaseScoreForResult(result.Judgement.MaxResult) * Math.Pow(result.ComboAfterJudgement, COMBO_EXPONENT);
|
||||||
|
|
||||||
/// <summary>
|
public virtual int GetBaseScoreForResult(HitResult result)
|
||||||
/// Retrieves the maximum numeric score representation for a <see cref="JudgementResult"/>.
|
{
|
||||||
/// </summary>
|
switch (result)
|
||||||
/// <param name="result">The <see cref="JudgementResult"/>.</param>
|
{
|
||||||
protected virtual double GetMaxNumericResultFor(JudgementResult result) => result.Judgement.MaxNumericResult;
|
default:
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case HitResult.SmallTickHit:
|
||||||
|
return 10;
|
||||||
|
|
||||||
|
case HitResult.LargeTickHit:
|
||||||
|
return 30;
|
||||||
|
|
||||||
|
case HitResult.Meh:
|
||||||
|
return 50;
|
||||||
|
|
||||||
|
case HitResult.Ok:
|
||||||
|
return 100;
|
||||||
|
|
||||||
|
case HitResult.Good:
|
||||||
|
return 200;
|
||||||
|
|
||||||
|
case HitResult.Great:
|
||||||
|
case HitResult.Perfect: // Perfect doesn't actually give more score / accuracy directly.
|
||||||
|
return 300;
|
||||||
|
|
||||||
|
case HitResult.SmallBonus:
|
||||||
|
return 10;
|
||||||
|
|
||||||
|
case HitResult.LargeBonus:
|
||||||
|
return 50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual void ApplyScoreChange(JudgementResult result)
|
protected virtual void ApplyScoreChange(JudgementResult result)
|
||||||
{
|
{
|
||||||
@ -540,7 +570,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Used to compute accuracy.
|
/// Used to compute accuracy.
|
||||||
/// See: <see cref="HitResultExtensions.IsBasic"/> and <see cref="Judgement.ToNumericResult"/>.
|
/// See: <see cref="HitResultExtensions.IsBasic"/> and <see cref="ScoreProcessor.GetBaseScoreForResult"/>.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[Key(0)]
|
[Key(0)]
|
||||||
public double BaseScore { get; set; }
|
public double BaseScore { get; set; }
|
||||||
|
@ -34,9 +34,10 @@ namespace osu.Game.Scoring.Legacy
|
|||||||
/// <item><description>30000005: Introduce combo exponent in the osu! gamemode. Reconvert all scores.</description></item>
|
/// <item><description>30000005: Introduce combo exponent in the osu! gamemode. Reconvert all scores.</description></item>
|
||||||
/// <item><description>30000006: Fix edge cases in conversion after combo exponent introduction that lead to NaNs. Reconvert all scores.</description></item>
|
/// <item><description>30000006: Fix edge cases in conversion after combo exponent introduction that lead to NaNs. Reconvert all scores.</description></item>
|
||||||
/// <item><description>30000007: Adjust osu!mania combo and accuracy portions and judgement scoring values. Reconvert all scores.</description></item>
|
/// <item><description>30000007: Adjust osu!mania combo and accuracy portions and judgement scoring values. Reconvert all scores.</description></item>
|
||||||
|
/// <item><description>30000008: Add accuracy conversion. Reconvert all scores.</description></item>
|
||||||
/// </list>
|
/// </list>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public const int LATEST_VERSION = 30000007;
|
public const int LATEST_VERSION = 30000008;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The first stable-compatible YYYYMMDD format version given to lazer usage of replays.
|
/// The first stable-compatible YYYYMMDD format version given to lazer usage of replays.
|
||||||
|
@ -17,7 +17,6 @@ using osu.Game.Scoring.Legacy;
|
|||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Rulesets.Judgements;
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using Realms;
|
using Realms;
|
||||||
|
|
||||||
@ -107,7 +106,7 @@ namespace osu.Game.Scoring
|
|||||||
else if (model.IsLegacyScore)
|
else if (model.IsLegacyScore)
|
||||||
{
|
{
|
||||||
model.LegacyTotalScore = model.TotalScore;
|
model.LegacyTotalScore = model.TotalScore;
|
||||||
model.TotalScore = StandardisedScoreMigrationTools.ConvertFromLegacyTotalScore(model, beatmaps());
|
StandardisedScoreMigrationTools.UpdateFromLegacy(model, beatmaps());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,13 +124,14 @@ namespace osu.Game.Scoring
|
|||||||
var beatmap = score.BeatmapInfo!.Detach();
|
var beatmap = score.BeatmapInfo!.Detach();
|
||||||
var ruleset = score.Ruleset.Detach();
|
var ruleset = score.Ruleset.Detach();
|
||||||
var rulesetInstance = ruleset.CreateInstance();
|
var rulesetInstance = ruleset.CreateInstance();
|
||||||
|
var scoreProcessor = rulesetInstance.CreateScoreProcessor();
|
||||||
|
|
||||||
Debug.Assert(rulesetInstance != null);
|
Debug.Assert(rulesetInstance != null);
|
||||||
|
|
||||||
// Populate the maximum statistics.
|
// Populate the maximum statistics.
|
||||||
HitResult maxBasicResult = rulesetInstance.GetHitResults()
|
HitResult maxBasicResult = rulesetInstance.GetHitResults()
|
||||||
.Select(h => h.result)
|
.Select(h => h.result)
|
||||||
.Where(h => h.IsBasic()).MaxBy(Judgement.ToNumericResult);
|
.Where(h => h.IsBasic()).MaxBy(scoreProcessor.GetBaseScoreForResult);
|
||||||
|
|
||||||
foreach ((HitResult result, int count) in score.Statistics)
|
foreach ((HitResult result, int count) in score.Statistics)
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user