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:
commit
a780abb0b4
@ -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();
|
||||
|
@ -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");
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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"/>,
|
||||
|
Loading…
Reference in New Issue
Block a user