1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-12 17:23:22 +08:00

Merge pull request #27912 from bdach/pre-mod-multiplier-score

Store total score without mod multipliers to local database and to replays, and send it on score submission
This commit is contained in:
Dean Herbert 2024-05-10 22:46:34 +08:00 committed by GitHub
commit a780abb0b4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 130 additions and 2 deletions

View File

@ -422,6 +422,80 @@ namespace osu.Game.Tests.Beatmaps.Formats
});
}
[Test]
public void TestTotalScoreWithoutModsReadIfPresent()
{
var ruleset = new OsuRuleset().RulesetInfo;
var scoreInfo = TestResources.CreateTestScoreInfo(ruleset);
scoreInfo.Mods = new Mod[]
{
new OsuModDoubleTime { SpeedChange = { Value = 1.1 } }
};
scoreInfo.OnlineID = 123123;
scoreInfo.ClientVersion = "2023.1221.0";
scoreInfo.TotalScoreWithoutMods = 1_000_000;
scoreInfo.TotalScore = 1_020_000;
var beatmap = new TestBeatmap(ruleset);
var score = new Score
{
ScoreInfo = scoreInfo,
Replay = new Replay
{
Frames = new List<ReplayFrame>
{
new OsuReplayFrame(2000, OsuPlayfield.BASE_SIZE / 2, OsuAction.LeftButton)
}
}
};
var decodedAfterEncode = encodeThenDecode(LegacyBeatmapDecoder.LATEST_VERSION, score, beatmap);
Assert.Multiple(() =>
{
Assert.That(decodedAfterEncode.ScoreInfo.TotalScoreWithoutMods, Is.EqualTo(1_000_000));
Assert.That(decodedAfterEncode.ScoreInfo.TotalScore, Is.EqualTo(1_020_000));
});
}
[Test]
public void TestTotalScoreWithoutModsBackwardsPopulatedIfMissing()
{
var ruleset = new OsuRuleset().RulesetInfo;
var scoreInfo = TestResources.CreateTestScoreInfo(ruleset);
scoreInfo.Mods = new Mod[]
{
new OsuModDoubleTime { SpeedChange = { Value = 1.1 } }
};
scoreInfo.OnlineID = 123123;
scoreInfo.ClientVersion = "2023.1221.0";
scoreInfo.TotalScoreWithoutMods = 0;
scoreInfo.TotalScore = 1_020_000;
var beatmap = new TestBeatmap(ruleset);
var score = new Score
{
ScoreInfo = scoreInfo,
Replay = new Replay
{
Frames = new List<ReplayFrame>
{
new OsuReplayFrame(2000, OsuPlayfield.BASE_SIZE / 2, OsuAction.LeftButton)
}
}
};
var decodedAfterEncode = encodeThenDecode(LegacyBeatmapDecoder.LATEST_VERSION, score, beatmap);
Assert.Multiple(() =>
{
Assert.That(decodedAfterEncode.ScoreInfo.TotalScoreWithoutMods, Is.EqualTo(1_000_000));
Assert.That(decodedAfterEncode.ScoreInfo.TotalScore, Is.EqualTo(1_020_000));
});
}
private static Score encodeThenDecode(int beatmapVersion, Score score, TestBeatmap beatmap)
{
var encodeStream = new MemoryStream();

View File

@ -92,8 +92,9 @@ namespace osu.Game.Database
/// 38 2023-12-10 Add EndTimeObjectCount and TotalObjectCount to BeatmapInfo.
/// 39 2023-12-19 Migrate any EndTimeObjectCount and TotalObjectCount values of 0 to -1 to better identify non-calculated values.
/// 40 2023-12-21 Add ScoreInfo.Version to keep track of which build scores were set on.
/// 41 2024-04-17 Add ScoreInfo.TotalScoreWithoutMods for future mod multiplier rebalances.
/// </summary>
private const int schema_version = 40;
private const int schema_version = 41;
/// <summary>
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
@ -1130,6 +1131,12 @@ namespace osu.Game.Database
}
break;
case 41:
foreach (var score in migration.NewRealm.All<ScoreInfo>())
LegacyScoreDecoder.PopulateTotalScoreWithoutMods(score);
break;
}
Logger.Log($"Migration completed in {stopwatch.ElapsedMilliseconds}ms");

View File

@ -16,6 +16,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Scoring.Legacy;
using osu.Game.Scoring;
using osu.Game.Scoring.Legacy;
namespace osu.Game.Database
{
@ -248,6 +249,7 @@ namespace osu.Game.Database
score.Accuracy = computeAccuracy(score, scoreProcessor);
score.Rank = computeRank(score, scoreProcessor);
score.TotalScore = convertFromLegacyTotalScore(score, ruleset, beatmap);
LegacyScoreDecoder.PopulateTotalScoreWithoutMods(score);
}
/// <summary>

View File

@ -33,6 +33,9 @@ namespace osu.Game.Online.API.Requests.Responses
[JsonProperty("total_score")]
public long TotalScore { get; set; }
[JsonProperty("total_score_without_mods")]
public long TotalScoreWithoutMods { get; set; }
[JsonProperty("accuracy")]
public double Accuracy { get; set; }
@ -206,6 +209,7 @@ namespace osu.Game.Online.API.Requests.Responses
Ruleset = new RulesetInfo { OnlineID = RulesetID },
Passed = Passed,
TotalScore = TotalScore,
TotalScoreWithoutMods = TotalScoreWithoutMods,
LegacyTotalScore = LegacyTotalScore,
Accuracy = Accuracy,
MaxCombo = MaxCombo,
@ -239,6 +243,7 @@ namespace osu.Game.Online.API.Requests.Responses
{
Rank = score.Rank,
TotalScore = score.TotalScore,
TotalScoreWithoutMods = score.TotalScoreWithoutMods,
Accuracy = score.Accuracy,
PP = score.PP,
MaxCombo = score.MaxCombo,

View File

@ -56,6 +56,14 @@ namespace osu.Game.Rulesets.Scoring
/// </summary>
public readonly BindableLong TotalScore = new BindableLong { MinValue = 0 };
/// <summary>
/// The total number of points awarded for the score without including mod multipliers.
/// </summary>
/// <remarks>
/// The purpose of this property is to enable future lossless rebalances of mod multipliers.
/// </remarks>
public readonly BindableLong TotalScoreWithoutMods = new BindableLong { MinValue = 0 };
/// <summary>
/// The current accuracy.
/// </summary>
@ -363,7 +371,8 @@ namespace osu.Game.Rulesets.Scoring
double comboProgress = maximumComboPortion > 0 ? currentComboPortion / maximumComboPortion : 1;
double accuracyProcess = maximumAccuracyJudgementCount > 0 ? (double)currentAccuracyJudgementCount / maximumAccuracyJudgementCount : 1;
TotalScore.Value = (long)Math.Round(ComputeTotalScore(comboProgress, accuracyProcess, currentBonusPortion) * scoreMultiplier);
TotalScoreWithoutMods.Value = (long)Math.Round(ComputeTotalScore(comboProgress, accuracyProcess, currentBonusPortion));
TotalScore.Value = (long)Math.Round(TotalScoreWithoutMods.Value * scoreMultiplier);
}
private void updateRank()
@ -446,6 +455,7 @@ namespace osu.Game.Rulesets.Scoring
score.MaximumStatistics[result] = MaximumResultCounts.GetValueOrDefault(result);
// Populate total score after everything else.
score.TotalScoreWithoutMods = TotalScoreWithoutMods.Value;
score.TotalScore = TotalScore.Value;
}

View File

@ -46,6 +46,9 @@ namespace osu.Game.Scoring.Legacy
[JsonProperty("user_id")]
public int UserID = -1;
[JsonProperty("total_score_without_mods")]
public long? TotalScoreWithoutMods { get; set; }
public static LegacyReplaySoloScoreInfo FromScore(ScoreInfo score) => new LegacyReplaySoloScoreInfo
{
OnlineID = score.OnlineID,
@ -55,6 +58,7 @@ namespace osu.Game.Scoring.Legacy
ClientVersion = score.ClientVersion,
Rank = score.Rank,
UserID = score.User.OnlineID,
TotalScoreWithoutMods = score.TotalScoreWithoutMods > 0 ? score.TotalScoreWithoutMods : null,
};
}
}

View File

@ -133,6 +133,11 @@ namespace osu.Game.Scoring.Legacy
decodedRank = readScore.Rank;
if (readScore.UserID > 1)
score.ScoreInfo.RealmUser.OnlineID = readScore.UserID;
if (readScore.TotalScoreWithoutMods is long totalScoreWithoutMods)
score.ScoreInfo.TotalScoreWithoutMods = totalScoreWithoutMods;
else
PopulateTotalScoreWithoutMods(score.ScoreInfo);
});
}
}
@ -244,6 +249,16 @@ namespace osu.Game.Scoring.Legacy
#pragma warning restore CS0618
}
public static void PopulateTotalScoreWithoutMods(ScoreInfo score)
{
double modMultiplier = 1;
foreach (var mod in score.Mods)
modMultiplier *= mod.ScoreMultiplier;
score.TotalScoreWithoutMods = (long)Math.Round(score.TotalScore / modMultiplier);
}
private void readLegacyReplay(Replay replay, StreamReader reader)
{
float lastTime = beatmapOffset;

View File

@ -65,8 +65,19 @@ namespace osu.Game.Scoring
public bool DeletePending { get; set; }
/// <summary>
/// The total number of points awarded for the score.
/// </summary>
public long TotalScore { get; set; }
/// <summary>
/// The total number of points awarded for the score without including mod multipliers.
/// </summary>
/// <remarks>
/// The purpose of this property is to enable future lossless rebalances of mod multipliers.
/// </remarks>
public long TotalScoreWithoutMods { get; set; }
/// <summary>
/// The version of processing applied to calculate total score as stored in the database.
/// If this does not match <see cref="LegacyScoreEncoder.LATEST_VERSION"/>,