mirror of
https://github.com/ppy/osu.git
synced 2025-01-27 03:23:03 +08:00
Rework GetScore() method signatures + implementations
Rename legacy-facing overload to mention as much
This commit is contained in:
parent
a8e99f1a95
commit
f1c40bd9ed
@ -6,11 +6,15 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Judgements;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
namespace osu.Game.Tests.Rulesets.Scoring
|
||||
@ -300,7 +304,26 @@ namespace osu.Game.Tests.Rulesets.Scoring
|
||||
HitObjects = { new TestHitObject(result) }
|
||||
});
|
||||
|
||||
Assert.That(scoreProcessor.GetScore(ScoringMode.Standardised, result.AffectsCombo() ? 1 : 0, statistic), Is.EqualTo(expectedScore).Within(0.5d));
|
||||
Assert.That(scoreProcessor.ComputeFinalScore(ScoringMode.Standardised, new ScoreInfo
|
||||
{
|
||||
Ruleset = new TestRuleset().RulesetInfo,
|
||||
MaxCombo = result.AffectsCombo() ? 1 : 0,
|
||||
Statistics = statistic
|
||||
}), Is.EqualTo(expectedScore).Within(0.5d));
|
||||
}
|
||||
|
||||
private class TestRuleset : Ruleset
|
||||
{
|
||||
public override IEnumerable<Mod> GetModsFor(ModType type) => throw new System.NotImplementedException();
|
||||
|
||||
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => throw new System.NotImplementedException();
|
||||
|
||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new System.NotImplementedException();
|
||||
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => throw new System.NotImplementedException();
|
||||
|
||||
public override string Description { get; }
|
||||
public override string ShortName { get; }
|
||||
}
|
||||
|
||||
private class TestJudgement : Judgement
|
||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddUntilStep("wait for multiple judgements", () => ((FailPlayer)Player).ScoreProcessor.JudgedHits > 1);
|
||||
AddAssert("total number of results == 1", () =>
|
||||
{
|
||||
var score = new ScoreInfo();
|
||||
var score = new ScoreInfo { Ruleset = Ruleset.Value };
|
||||
|
||||
((FailPlayer)Player).ScoreProcessor.PopulateScore(score);
|
||||
|
||||
|
@ -46,7 +46,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
var scoreProcessor = new OsuScoreProcessor();
|
||||
scoreProcessor.ApplyBeatmap(playable);
|
||||
|
||||
LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, clocks.Keys.Select(id => new MultiplayerRoomUser(id)).ToArray()) { Expanded = { Value = true } }, Add);
|
||||
LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(Ruleset.Value, scoreProcessor, clocks.Keys.Select(id => new MultiplayerRoomUser(id)).ToArray())
|
||||
{
|
||||
Expanded = { Value = true }
|
||||
}, Add);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for load", () => leaderboard.IsLoaded);
|
||||
|
@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
scoreProcessor.ApplyBeatmap(playableBeatmap);
|
||||
|
||||
LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, multiplayerUsers.ToArray())
|
||||
LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(Ruleset.Value, scoreProcessor, multiplayerUsers.ToArray())
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
scoreProcessor.ApplyBeatmap(playableBeatmap);
|
||||
|
||||
LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, multiplayerUsers.ToArray())
|
||||
LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(Ruleset.Value, scoreProcessor, multiplayerUsers.ToArray())
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -63,9 +63,8 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
|
||||
// calculate total score
|
||||
ScoreProcessor scoreProcessor = ruleset.CreateScoreProcessor();
|
||||
scoreProcessor.HighestCombo.Value = perfectPlay.MaxCombo;
|
||||
scoreProcessor.Mods.Value = perfectPlay.Mods;
|
||||
perfectPlay.TotalScore = (long)scoreProcessor.GetScore(ScoringMode.Standardised, perfectPlay.MaxCombo, statistics);
|
||||
perfectPlay.TotalScore = (long)scoreProcessor.ComputeFinalScore(ScoringMode.Standardised, perfectPlay);
|
||||
|
||||
// compute rank achieved
|
||||
// default to SS, then adjust the rank with mods
|
||||
|
@ -7,7 +7,6 @@ using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -93,10 +92,10 @@ namespace osu.Game.Rulesets.Scoring
|
||||
private int maxBasicHitObjects;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum <see cref="HitResult"/> for a normal hit (i.e. not tick/bonus) for this ruleset.
|
||||
/// Only populated via <see cref="ResetFromReplayFrame"/>.
|
||||
/// The maximum <see cref="HitResult"/> of a basic (non-tick and non-bonus) hitobject.
|
||||
/// Only populated via <see cref="ComputeFinalScore"/> or <see cref="ResetFromReplayFrame"/>.
|
||||
/// </summary>
|
||||
private HitResult? maxBasicHitResult;
|
||||
private HitResult? maxBasicResult;
|
||||
|
||||
private double rollingMaxBaseScore;
|
||||
private double baseScore;
|
||||
@ -222,81 +221,110 @@ namespace osu.Game.Rulesets.Scoring
|
||||
|
||||
private void updateScore()
|
||||
{
|
||||
if (rollingMaxBaseScore != 0)
|
||||
Accuracy.Value = calculateAccuracyRatio(baseScore, true);
|
||||
double rollingAccuracyRatio = rollingMaxBaseScore > 0 ? baseScore / rollingMaxBaseScore : 1;
|
||||
double accuracyRatio = maxBaseScore > 0 ? baseScore / maxBaseScore : 1;
|
||||
double comboRatio = maxAchievableCombo > 0 ? (double)HighestCombo.Value / maxAchievableCombo : 1;
|
||||
|
||||
TotalScore.Value = GetScore(Mode.Value);
|
||||
Accuracy.Value = rollingAccuracyRatio;
|
||||
TotalScore.Value = ComputeScore(Mode.Value, accuracyRatio, comboRatio, getBonusScore(scoreResultCounts), maxBasicHitObjects);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the total score from judgements that have been applied to this <see cref="ScoreProcessor"/>
|
||||
/// through <see cref="JudgementProcessor.ApplyResult"/> and <see cref="JudgementProcessor.RevertResult"/>.
|
||||
/// Computes the total score of a given finalised <see cref="ScoreInfo"/>. This should be used when a score is known to be complete.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Requires an <see cref="IBeatmap"/> to have been applied via <see cref="JudgementProcessor.ApplyBeatmap"/> before use.
|
||||
/// Does not require <see cref="JudgementProcessor.ApplyBeatmap"/> to have been called before use.
|
||||
/// </remarks>
|
||||
/// <param name="mode">The <see cref="ScoringMode"/> to represent the score as.</param>
|
||||
/// <param name="scoreInfo">The <see cref="ScoreInfo"/> to compute the total score of.</param>
|
||||
/// <returns>The total score in the given <see cref="ScoringMode"/>.</returns>
|
||||
public double GetScore(ScoringMode mode)
|
||||
public double ComputeFinalScore(ScoringMode mode, ScoreInfo scoreInfo)
|
||||
{
|
||||
return GetScore(mode, calculateAccuracyRatio(baseScore), calculateComboRatio(HighestCombo.Value), scoreResultCounts);
|
||||
extractFromStatistics(scoreInfo.Ruleset.CreateInstance(),
|
||||
scoreInfo.Statistics,
|
||||
out double extractedBaseScore,
|
||||
out double extractedMaxBaseScore,
|
||||
out int extractedMaxCombo,
|
||||
out int extractedBasicHitObjects);
|
||||
|
||||
double accuracyRatio = extractedMaxBaseScore > 0 ? extractedBaseScore / extractedMaxBaseScore : 1;
|
||||
double comboRatio = extractedMaxCombo > 0 ? (double)scoreInfo.MaxCombo / extractedMaxCombo : 1;
|
||||
|
||||
return ComputeScore(mode, accuracyRatio, comboRatio, getBonusScore(scoreInfo.Statistics), extractedBasicHitObjects);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the total score from judgements counts in a statistics dictionary.
|
||||
/// Computes the total score of a partially-completed <see cref="ScoreInfo"/>. This should be used when it is unknown whether a score is complete.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Requires an <see cref="IBeatmap"/> to have been applied via <see cref="JudgementProcessor.ApplyBeatmap"/> before use.
|
||||
/// Requires <see cref="JudgementProcessor.ApplyBeatmap"/> to have been called before use.
|
||||
/// </remarks>
|
||||
/// <param name="mode">The <see cref="ScoringMode"/> to represent the score as.</param>
|
||||
/// <param name="maxCombo">The maximum combo achievable in the beatmap.</param>
|
||||
/// <param name="statistics">The statistics to compute the score for.</param>
|
||||
/// <returns>The total score computed from judgements in the statistics dictionary.</returns>
|
||||
public double GetScore(ScoringMode mode, int maxCombo, Dictionary<HitResult, int> statistics)
|
||||
/// <param name="scoreInfo">The <see cref="ScoreInfo"/> to compute the total score of.</param>
|
||||
/// <returns>The total score in the given <see cref="ScoringMode"/>.</returns>
|
||||
public double ComputePartialScore(ScoringMode mode, ScoreInfo scoreInfo)
|
||||
{
|
||||
// calculate base score from statistics pairs
|
||||
int computedBaseScore = 0;
|
||||
extractFromStatistics(scoreInfo.Ruleset.CreateInstance(),
|
||||
scoreInfo.Statistics,
|
||||
out double extractedBaseScore,
|
||||
out _,
|
||||
out _,
|
||||
out _);
|
||||
|
||||
foreach (var pair in statistics)
|
||||
double accuracyRatio = maxBaseScore > 0 ? extractedBaseScore / maxBaseScore : 1;
|
||||
double comboRatio = maxAchievableCombo > 0 ? (double)scoreInfo.MaxCombo / maxAchievableCombo : 1;
|
||||
|
||||
return ComputeScore(mode, accuracyRatio, comboRatio, getBonusScore(scoreInfo.Statistics), maxBasicHitObjects);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the total score of a given <see cref="ScoreInfo"/> with a given custom max achievable combo.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is useful for processing legacy scores in which the maximum achievable combo can be more accurately determined via external means (e.g. database values or difficulty calculation).
|
||||
/// <p>Does not require <see cref="JudgementProcessor.ApplyBeatmap"/> to have been called before use.</p>
|
||||
/// </remarks>
|
||||
/// <param name="mode">The <see cref="ScoringMode"/> to represent the score as.</param>
|
||||
/// <param name="scoreInfo">The <see cref="ScoreInfo"/> to compute the total score of.</param>
|
||||
/// <param name="maxAchievableCombo">The maximum achievable combo for the provided beatmap.</param>
|
||||
/// <returns>The total score in the given <see cref="ScoringMode"/>.</returns>
|
||||
public double ComputeFinalLegacyScore(ScoringMode mode, ScoreInfo scoreInfo, int maxAchievableCombo)
|
||||
{
|
||||
double accuracyRatio = scoreInfo.Accuracy;
|
||||
double comboRatio = maxAchievableCombo > 0 ? (double)scoreInfo.MaxCombo / maxAchievableCombo : 1;
|
||||
|
||||
// For legacy osu!mania scores, a full-GREAT score has 100% accuracy. If combined with a full-combo, the score becomes indistinguishable from a full-PERFECT score.
|
||||
// To get around this, the accuracy ratio is always recalculated based on the hit statistics rather than trusting the score.
|
||||
// Note: This cannot be applied universally to all legacy scores, as some rulesets (e.g. catch) group multiple judgements together.
|
||||
if (scoreInfo.IsLegacyScore && scoreInfo.Ruleset.OnlineID == 3)
|
||||
{
|
||||
if (!pair.Key.AffectsAccuracy())
|
||||
continue;
|
||||
extractFromStatistics(
|
||||
scoreInfo.Ruleset.CreateInstance(),
|
||||
scoreInfo.Statistics,
|
||||
out double computedBaseScore,
|
||||
out double computedMaxBaseScore,
|
||||
out _,
|
||||
out _);
|
||||
|
||||
computedBaseScore += Judgement.ToNumericResult(pair.Key) * pair.Value;
|
||||
if (computedMaxBaseScore > 0)
|
||||
accuracyRatio = computedBaseScore / computedMaxBaseScore;
|
||||
}
|
||||
|
||||
return GetScore(mode, calculateAccuracyRatio(computedBaseScore), calculateComboRatio(maxCombo), statistics);
|
||||
int computedBasicHitObjects = scoreInfo.Statistics.Where(kvp => kvp.Key.IsBasic()).Select(kvp => kvp.Value).Sum();
|
||||
|
||||
return ComputeScore(mode, accuracyRatio, comboRatio, getBonusScore(scoreInfo.Statistics), computedBasicHitObjects);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the total score from given scoring component ratios.
|
||||
/// Computes the total score from individual scoring components.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Requires an <see cref="IBeatmap"/> to have been applied via <see cref="JudgementProcessor.ApplyBeatmap"/> before use.
|
||||
/// </remarks>
|
||||
/// <param name="mode">The <see cref="ScoringMode"/> to represent the score as.</param>
|
||||
/// <param name="accuracyRatio">The accuracy percentage achieved by the player.</param>
|
||||
/// <param name="comboRatio">The portion of the max combo achieved by the player.</param>
|
||||
/// <param name="statistics">Any additional statistics to be factored in.</param>
|
||||
/// <returns>The total score computed from the given scoring component ratios.</returns>
|
||||
public double GetScore(ScoringMode mode, double accuracyRatio, double comboRatio, Dictionary<HitResult, int> statistics)
|
||||
{
|
||||
return GetScore(mode, accuracyRatio, comboRatio, maxBasicHitObjects, statistics);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the total score from given scoring component ratios.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Does not require an <see cref="IBeatmap"/> to have been applied via <see cref="JudgementProcessor.ApplyBeatmap"/> before use.
|
||||
/// </remarks>
|
||||
/// <param name="mode">The <see cref="ScoringMode"/> to represent the score as.</param>
|
||||
/// <param name="accuracyRatio">The accuracy percentage achieved by the player.</param>
|
||||
/// <param name="comboRatio">The portion of the max combo achieved by the player.</param>
|
||||
/// <param name="statistics">Any additional statistics to be factored in.</param>
|
||||
/// <param name="bonusScore">The total bonus score.</param>
|
||||
/// <param name="totalBasicHitObjects">The total number of basic (non-tick and non-bonus) hitobjects in the beatmap.</param>
|
||||
/// <returns>The total score computed from the given scoring component ratios.</returns>
|
||||
public double GetScore(ScoringMode mode, double accuracyRatio, double comboRatio, int totalBasicHitObjects, Dictionary<HitResult, int> statistics)
|
||||
public double ComputeScore(ScoringMode mode, double accuracyRatio, double comboRatio, double bonusScore, int totalBasicHitObjects)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
@ -304,33 +332,22 @@ namespace osu.Game.Rulesets.Scoring
|
||||
case ScoringMode.Standardised:
|
||||
double accuracyScore = accuracyPortion * accuracyRatio;
|
||||
double comboScore = comboPortion * comboRatio;
|
||||
return (max_score * (accuracyScore + comboScore) + getBonusScore(statistics)) * scoreMultiplier;
|
||||
return (max_score * (accuracyScore + comboScore) + bonusScore) * scoreMultiplier;
|
||||
|
||||
case ScoringMode.Classic:
|
||||
// This gives a similar feeling to osu!stable scoring (ScoreV1) while keeping classic scoring as only a constant multiple of standardised scoring.
|
||||
// The invariant is important to ensure that scores don't get re-ordered on leaderboards between the two scoring modes.
|
||||
double scaledStandardised = GetScore(ScoringMode.Standardised, accuracyRatio, comboRatio, statistics) / max_score;
|
||||
return Math.Pow(scaledStandardised * totalBasicHitObjects, 2) * 36;
|
||||
double scaledStandardised = ComputeScore(ScoringMode.Standardised, accuracyRatio, comboRatio, bonusScore, totalBasicHitObjects) / max_score;
|
||||
return Math.Pow(scaledStandardised * Math.Max(1, totalBasicHitObjects), 2) * 36;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the accuracy fraction for the provided base score.
|
||||
/// Calculates the total bonus score from score statistics.
|
||||
/// </summary>
|
||||
/// <param name="baseScore">The score to be used for accuracy calculation.</param>
|
||||
/// <param name="preferRolling">Whether the rolling base score should be used (ie. for the current point in time based on Apply/Reverted results).</param>
|
||||
/// <returns>The computed accuracy.</returns>
|
||||
private double calculateAccuracyRatio(double baseScore, bool preferRolling = false)
|
||||
{
|
||||
if (preferRolling && rollingMaxBaseScore != 0)
|
||||
return baseScore / rollingMaxBaseScore;
|
||||
|
||||
return maxBaseScore > 0 ? baseScore / maxBaseScore : 1;
|
||||
}
|
||||
|
||||
private double calculateComboRatio(int maxCombo) => maxAchievableCombo > 0 ? (double)maxCombo / maxAchievableCombo : 1;
|
||||
|
||||
private double getBonusScore(Dictionary<HitResult, int> statistics)
|
||||
/// <param name="statistics">The score statistics.</param>
|
||||
/// <returns>The total bonus score.</returns>
|
||||
private double getBonusScore(IReadOnlyDictionary<HitResult, int> statistics)
|
||||
=> statistics.GetValueOrDefault(HitResult.SmallBonus) * Judgement.SMALL_BONUS_SCORE
|
||||
+ statistics.GetValueOrDefault(HitResult.LargeBonus) * Judgement.LARGE_BONUS_SCORE;
|
||||
|
||||
@ -387,16 +404,17 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// </summary>
|
||||
public virtual void PopulateScore(ScoreInfo score)
|
||||
{
|
||||
score.TotalScore = (long)Math.Round(GetScore(ScoringMode.Standardised));
|
||||
score.Combo = Combo.Value;
|
||||
score.MaxCombo = HighestCombo.Value;
|
||||
score.Accuracy = Accuracy.Value;
|
||||
score.Rank = Rank.Value;
|
||||
score.HitEvents = hitEvents;
|
||||
|
||||
foreach (var result in HitResultExtensions.ALL_TYPES)
|
||||
score.Statistics[result] = GetStatistic(result);
|
||||
|
||||
score.HitEvents = hitEvents;
|
||||
// Populate total score after everything else.
|
||||
score.TotalScore = (long)Math.Round(ComputeFinalScore(ScoringMode.Standardised, score));
|
||||
}
|
||||
|
||||
public override void ResetFromReplayFrame(Ruleset ruleset, ReplayFrame frame)
|
||||
@ -406,11 +424,26 @@ namespace osu.Game.Rulesets.Scoring
|
||||
if (frame.Header == null)
|
||||
return;
|
||||
|
||||
baseScore = 0;
|
||||
rollingMaxBaseScore = 0;
|
||||
extractFromStatistics(ruleset, frame.Header.Statistics, out baseScore, out rollingMaxBaseScore, out _, out _);
|
||||
HighestCombo.Value = frame.Header.MaxCombo;
|
||||
|
||||
foreach ((HitResult result, int count) in frame.Header.Statistics)
|
||||
scoreResultCounts.Clear();
|
||||
scoreResultCounts.AddRange(frame.Header.Statistics);
|
||||
|
||||
updateScore();
|
||||
|
||||
OnResetFromReplayFrame?.Invoke();
|
||||
}
|
||||
|
||||
private void extractFromStatistics(Ruleset ruleset, IReadOnlyDictionary<HitResult, int> statistics, out double baseScore, out double maxBaseScore, out int maxCombo,
|
||||
out int basicHitObjects)
|
||||
{
|
||||
baseScore = 0;
|
||||
maxBaseScore = 0;
|
||||
maxCombo = 0;
|
||||
basicHitObjects = 0;
|
||||
|
||||
foreach ((HitResult result, int count) in statistics)
|
||||
{
|
||||
// Bonus scores are counted separately directly from the statistics dictionary later on.
|
||||
if (!result.IsScorable() || result.IsBonus())
|
||||
@ -433,20 +466,19 @@ namespace osu.Game.Rulesets.Scoring
|
||||
break;
|
||||
|
||||
default:
|
||||
maxResult = maxBasicHitResult ??= ruleset.GetHitResults().OrderByDescending(kvp => Judgement.ToNumericResult(kvp.result)).First().result;
|
||||
maxResult = maxBasicResult ??= ruleset.GetHitResults().OrderByDescending(kvp => Judgement.ToNumericResult(kvp.result)).First().result;
|
||||
break;
|
||||
}
|
||||
|
||||
baseScore += count * Judgement.ToNumericResult(result);
|
||||
rollingMaxBaseScore += count * Judgement.ToNumericResult(maxResult);
|
||||
maxBaseScore += count * Judgement.ToNumericResult(maxResult);
|
||||
|
||||
if (result.AffectsCombo())
|
||||
maxCombo += count;
|
||||
|
||||
if (result.IsBasic())
|
||||
basicHitObjects += count;
|
||||
}
|
||||
|
||||
scoreResultCounts.Clear();
|
||||
scoreResultCounts.AddRange(frame.Header.Statistics);
|
||||
|
||||
updateScore();
|
||||
|
||||
OnResetFromReplayFrame?.Invoke();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
|
@ -18,7 +18,6 @@ using osu.Game.Database;
|
||||
using osu.Game.IO.Archives;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Scoring
|
||||
@ -136,21 +135,9 @@ namespace osu.Game.Scoring
|
||||
return score.TotalScore;
|
||||
|
||||
int beatmapMaxCombo;
|
||||
double accuracy = score.Accuracy;
|
||||
|
||||
if (score.IsLegacyScore)
|
||||
{
|
||||
if (score.RulesetID == 3)
|
||||
{
|
||||
// In osu!stable, a full-GREAT score has 100% accuracy in mania. Along with a full combo, the score becomes indistinguishable from a full-PERFECT score.
|
||||
// To get around this, recalculate accuracy based on the hit statistics.
|
||||
// Note: This cannot be applied universally to all legacy scores, as some rulesets (e.g. catch) group multiple judgements together.
|
||||
double maxBaseScore = score.Statistics.Select(kvp => kvp.Value).Sum() * Judgement.ToNumericResult(HitResult.Perfect);
|
||||
double baseScore = score.Statistics.Select(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value).Sum();
|
||||
if (maxBaseScore > 0)
|
||||
accuracy = baseScore / maxBaseScore;
|
||||
}
|
||||
|
||||
// This score is guaranteed to be an osu!stable score.
|
||||
// The combo must be determined through either the beatmap's max combo value or the difficulty calculator, as lazer's scoring has changed and the score statistics cannot be used.
|
||||
if (score.BeatmapInfo.MaxCombo != null)
|
||||
@ -184,7 +171,7 @@ namespace osu.Game.Scoring
|
||||
var scoreProcessor = ruleset.CreateScoreProcessor();
|
||||
scoreProcessor.Mods.Value = score.Mods;
|
||||
|
||||
return (long)Math.Round(scoreProcessor.GetScore(mode, accuracy, (double)score.MaxCombo / beatmapMaxCombo, score.Statistics));
|
||||
return (long)Math.Round(scoreProcessor.ComputeFinalLegacyScore(mode, score, beatmapMaxCombo));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -76,7 +76,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
});
|
||||
|
||||
// todo: this should be implemented via a custom HUD implementation, and correctly masked to the main content area.
|
||||
LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(ScoreProcessor, users), l =>
|
||||
LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(GameplayState.Ruleset.RulesetInfo, ScoreProcessor, users), l =>
|
||||
{
|
||||
if (!LoadedBeatmapSuccessfully)
|
||||
return;
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
|
||||
@ -12,8 +13,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
{
|
||||
public class MultiSpectatorLeaderboard : MultiplayerGameplayLeaderboard
|
||||
{
|
||||
public MultiSpectatorLeaderboard([NotNull] ScoreProcessor scoreProcessor, MultiplayerRoomUser[] users)
|
||||
: base(scoreProcessor, users)
|
||||
public MultiSpectatorLeaderboard(RulesetInfo ruleset, [NotNull] ScoreProcessor scoreProcessor, MultiplayerRoomUser[] users)
|
||||
: base(ruleset, scoreProcessor, users)
|
||||
{
|
||||
}
|
||||
|
||||
@ -33,7 +34,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
((SpectatingTrackedUserData)data).Clock = null;
|
||||
}
|
||||
|
||||
protected override TrackedUserData CreateUserData(MultiplayerRoomUser user, ScoreProcessor scoreProcessor) => new SpectatingTrackedUserData(user, scoreProcessor);
|
||||
protected override TrackedUserData CreateUserData(MultiplayerRoomUser user, RulesetInfo ruleset, ScoreProcessor scoreProcessor) => new SpectatingTrackedUserData(user, ruleset, scoreProcessor);
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
@ -48,8 +49,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
[CanBeNull]
|
||||
public IClock Clock;
|
||||
|
||||
public SpectatingTrackedUserData(MultiplayerRoomUser user, ScoreProcessor scoreProcessor)
|
||||
: base(user, scoreProcessor)
|
||||
public SpectatingTrackedUserData(MultiplayerRoomUser user, RulesetInfo ruleset, ScoreProcessor scoreProcessor)
|
||||
: base(user, ruleset, scoreProcessor)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -133,7 +133,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
var scoreProcessor = Ruleset.Value.CreateInstance().CreateScoreProcessor();
|
||||
scoreProcessor.ApplyBeatmap(playableBeatmap);
|
||||
|
||||
LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, users)
|
||||
LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(Ruleset.Value, scoreProcessor, users)
|
||||
{
|
||||
Expanded = { Value = true },
|
||||
}, l =>
|
||||
|
@ -65,7 +65,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
{
|
||||
await base.PrepareScoreForResultsAsync(score).ConfigureAwait(false);
|
||||
|
||||
Score.ScoreInfo.TotalScore = (int)Math.Round(ScoreProcessor.GetScore(ScoringMode.Standardised));
|
||||
Score.ScoreInfo.TotalScore = (int)Math.Round(ScoreProcessor.ComputeFinalScore(ScoringMode.Standardised, Score.ScoreInfo));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
|
@ -17,7 +17,9 @@ using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
|
||||
using osu.Game.Online.Spectator;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD
|
||||
@ -41,6 +43,7 @@ namespace osu.Game.Screens.Play.HUD
|
||||
[Resolved]
|
||||
private UserLookupCache userLookupCache { get; set; }
|
||||
|
||||
private readonly RulesetInfo ruleset;
|
||||
private readonly ScoreProcessor scoreProcessor;
|
||||
private readonly MultiplayerRoomUser[] playingUsers;
|
||||
private Bindable<ScoringMode> scoringMode;
|
||||
@ -52,11 +55,13 @@ namespace osu.Game.Screens.Play.HUD
|
||||
/// <summary>
|
||||
/// Construct a new leaderboard.
|
||||
/// </summary>
|
||||
/// <param name="ruleset">The ruleset.</param>
|
||||
/// <param name="scoreProcessor">A score processor instance to handle score calculation for scores of users in the match.</param>
|
||||
/// <param name="users">IDs of all users in this match.</param>
|
||||
public MultiplayerGameplayLeaderboard(ScoreProcessor scoreProcessor, MultiplayerRoomUser[] users)
|
||||
public MultiplayerGameplayLeaderboard(RulesetInfo ruleset, ScoreProcessor scoreProcessor, MultiplayerRoomUser[] users)
|
||||
{
|
||||
// todo: this will eventually need to be created per user to support different mod combinations.
|
||||
this.ruleset = ruleset;
|
||||
this.scoreProcessor = scoreProcessor;
|
||||
|
||||
playingUsers = users;
|
||||
@ -69,7 +74,7 @@ namespace osu.Game.Screens.Play.HUD
|
||||
|
||||
foreach (var user in playingUsers)
|
||||
{
|
||||
var trackedUser = CreateUserData(user, scoreProcessor);
|
||||
var trackedUser = CreateUserData(user, ruleset, scoreProcessor);
|
||||
trackedUser.ScoringMode.BindTo(scoringMode);
|
||||
UserScores[user.UserID] = trackedUser;
|
||||
|
||||
@ -119,7 +124,7 @@ namespace osu.Game.Screens.Play.HUD
|
||||
spectatorClient.OnNewFrames += handleIncomingFrames;
|
||||
}
|
||||
|
||||
protected virtual TrackedUserData CreateUserData(MultiplayerRoomUser user, ScoreProcessor scoreProcessor) => new TrackedUserData(user, scoreProcessor);
|
||||
protected virtual TrackedUserData CreateUserData(MultiplayerRoomUser user, RulesetInfo ruleset, ScoreProcessor scoreProcessor) => new TrackedUserData(user, ruleset, scoreProcessor);
|
||||
|
||||
protected override GameplayLeaderboardScore CreateLeaderboardScoreDrawable(APIUser user, bool isTracked)
|
||||
{
|
||||
@ -222,8 +227,12 @@ namespace osu.Game.Screens.Play.HUD
|
||||
|
||||
public int? Team => (User.MatchState as TeamVersusUserState)?.TeamID;
|
||||
|
||||
public TrackedUserData(MultiplayerRoomUser user, ScoreProcessor scoreProcessor)
|
||||
private readonly RulesetInfo ruleset;
|
||||
|
||||
public TrackedUserData(MultiplayerRoomUser user, RulesetInfo ruleset, ScoreProcessor scoreProcessor)
|
||||
{
|
||||
this.ruleset = ruleset;
|
||||
|
||||
User = user;
|
||||
ScoreProcessor = scoreProcessor;
|
||||
|
||||
@ -244,7 +253,13 @@ namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
var header = frame.Header;
|
||||
|
||||
Score.Value = ScoreProcessor.GetScore(ScoringMode.Value, header.MaxCombo, header.Statistics);
|
||||
Score.Value = ScoreProcessor.ComputePartialScore(ScoringMode.Value, new ScoreInfo
|
||||
{
|
||||
Ruleset = ruleset,
|
||||
MaxCombo = header.MaxCombo,
|
||||
Statistics = header.Statistics
|
||||
});
|
||||
|
||||
Accuracy.Value = header.Accuracy;
|
||||
CurrentCombo.Value = header.Combo;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user