Merge pull request #21587 from smoogipoo/spectatorstate-maximum-statistics

Store maximum statistics to spectator state
This commit is contained in:
Dean Herbert
2022-12-12 20:00:52 +09:00
committed by GitHub
6 changed files with 42 additions and 90 deletions

View File

@ -117,11 +117,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
BeatmapID = 0, BeatmapID = 0,
RulesetID = 0, RulesetID = 0,
Mods = user.Mods, Mods = user.Mods,
MaximumScoringValues = new ScoringValues MaximumStatistics = new Dictionary<HitResult, int>
{ {
BaseScore = 10000, { HitResult.Perfect, 100 }
MaxCombo = 1000,
CountBasicHitObjects = 1000
} }
}; };
} }

View File

@ -174,7 +174,7 @@ namespace osu.Game.Online.Spectator
currentState.RulesetID = score.ScoreInfo.RulesetID; currentState.RulesetID = score.ScoreInfo.RulesetID;
currentState.Mods = score.ScoreInfo.Mods.Select(m => new APIMod(m)).ToArray(); currentState.Mods = score.ScoreInfo.Mods.Select(m => new APIMod(m)).ToArray();
currentState.State = SpectatedUserState.Playing; currentState.State = SpectatedUserState.Playing;
currentState.MaximumScoringValues = state.ScoreProcessor.MaximumScoringValues; currentState.MaximumStatistics = state.ScoreProcessor.MaximumStatistics;
currentBeatmap = state.Beatmap; currentBeatmap = state.Beatmap;
currentScore = score; currentScore = score;

View File

@ -152,12 +152,12 @@ namespace osu.Game.Online.Spectator
scoreInfo.MaxCombo = frame.Header.MaxCombo; scoreInfo.MaxCombo = frame.Header.MaxCombo;
scoreInfo.Statistics = frame.Header.Statistics; scoreInfo.Statistics = frame.Header.Statistics;
scoreInfo.MaximumStatistics = spectatorState.MaximumStatistics;
Accuracy.Value = frame.Header.Accuracy; Accuracy.Value = frame.Header.Accuracy;
Combo.Value = frame.Header.Combo; Combo.Value = frame.Header.Combo;
scoreProcessor.ExtractScoringValues(frame.Header, out var currentScoringValues, out _); TotalScore.Value = scoreProcessor.ComputeScore(Mode.Value, scoreInfo);
TotalScore.Value = scoreProcessor.ComputeScore(Mode.Value, currentScoringValues, spectatorState.MaximumScoringValues);
} }
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)

View File

@ -9,7 +9,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using MessagePack; using MessagePack;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Scoring; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Online.Spectator namespace osu.Game.Online.Spectator
{ {
@ -31,7 +31,7 @@ namespace osu.Game.Online.Spectator
public SpectatedUserState State { get; set; } public SpectatedUserState State { get; set; }
[Key(4)] [Key(4)]
public ScoringValues MaximumScoringValues { get; set; } public Dictionary<HitResult, int> MaximumStatistics { get; set; } = new Dictionary<HitResult, int>();
public bool Equals(SpectatorState other) public bool Equals(SpectatorState other)
{ {

View File

@ -10,7 +10,6 @@ using osu.Framework.Bindables;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Extensions; using osu.Game.Extensions;
using osu.Game.Online.Spectator;
using osu.Game.Rulesets.Judgements; 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;
@ -90,17 +89,14 @@ namespace osu.Game.Rulesets.Scoring
private readonly double accuracyPortion; private readonly double accuracyPortion;
private readonly double comboPortion; private readonly double comboPortion;
/// <summary> public Dictionary<HitResult, int> MaximumStatistics
/// Scoring values for a perfect play.
/// </summary>
public ScoringValues MaximumScoringValues
{ {
get get
{ {
if (!beatmapApplied) if (!beatmapApplied)
throw new InvalidOperationException($"Cannot access maximum scoring values before calling {nameof(ApplyBeatmap)}."); throw new InvalidOperationException($"Cannot access maximum statistics before calling {nameof(ApplyBeatmap)}.");
return maximumScoringValues; return new Dictionary<HitResult, int>(maximumResultCounts);
} }
} }
@ -268,7 +264,7 @@ namespace osu.Game.Rulesets.Scoring
private void updateScore() private void updateScore()
{ {
Accuracy.Value = currentMaximumScoringValues.BaseScore > 0 ? (double)currentScoringValues.BaseScore / currentMaximumScoringValues.BaseScore : 1; Accuracy.Value = currentMaximumScoringValues.BaseScore > 0 ? (double)currentScoringValues.BaseScore / currentMaximumScoringValues.BaseScore : 1;
TotalScore.Value = ComputeScore(Mode.Value, currentScoringValues, maximumScoringValues); TotalScore.Value = computeScore(Mode.Value, currentScoringValues, maximumScoringValues);
} }
/// <summary> /// <summary>
@ -303,9 +299,9 @@ namespace osu.Game.Rulesets.Scoring
if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset)) if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset))
throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\"."); throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\".");
ExtractScoringValues(scoreInfo, out var current, out var maximum); extractScoringValues(scoreInfo, out var current, out var maximum);
return ComputeScore(mode, current, maximum); return computeScore(mode, current, maximum);
} }
/// <summary> /// <summary>
@ -316,7 +312,7 @@ namespace osu.Game.Rulesets.Scoring
/// <param name="maximum">The maximum scoring values.</param> /// <param name="maximum">The maximum scoring values.</param>
/// <returns>The total score computed from the given scoring values.</returns> /// <returns>The total score computed from the given scoring values.</returns>
[Pure] [Pure]
public long ComputeScore(ScoringMode mode, ScoringValues current, ScoringValues maximum) private long computeScore(ScoringMode mode, ScoringValues current, ScoringValues maximum)
{ {
double accuracyRatio = maximum.BaseScore > 0 ? (double)current.BaseScore / maximum.BaseScore : 1; double accuracyRatio = maximum.BaseScore > 0 ? (double)current.BaseScore / maximum.BaseScore : 1;
double comboRatio = maximum.MaxCombo > 0 ? (double)current.MaxCombo / maximum.MaxCombo : 1; double comboRatio = maximum.MaxCombo > 0 ? (double)current.MaxCombo / maximum.MaxCombo : 1;
@ -474,14 +470,14 @@ namespace osu.Game.Rulesets.Scoring
/// Consumers are expected to more accurately fill in the above values through external means. /// Consumers are expected to more accurately fill in the above values through external means.
/// <para> /// <para>
/// <b>Ensure</b> to fill in the maximum <see cref="ScoringValues.CountBasicHitObjects"/> for use in /// <b>Ensure</b> to fill in the maximum <see cref="ScoringValues.CountBasicHitObjects"/> for use in
/// <see cref="ComputeScore(osu.Game.Rulesets.Scoring.ScoringMode,osu.Game.Scoring.ScoringValues,osu.Game.Scoring.ScoringValues)"/>. /// <see cref="computeScore(osu.Game.Rulesets.Scoring.ScoringMode,ScoringValues,ScoringValues)"/>.
/// </para> /// </para>
/// </remarks> /// </remarks>
/// <param name="scoreInfo">The score to extract scoring values from.</param> /// <param name="scoreInfo">The score to extract scoring values from.</param>
/// <param name="current">The "current" scoring values, representing the hit statistics as they appear.</param> /// <param name="current">The "current" scoring values, representing the hit statistics as they appear.</param>
/// <param name="maximum">The "maximum" scoring values, representing the hit statistics as if the maximum hit result was attained each time.</param> /// <param name="maximum">The "maximum" scoring values, representing the hit statistics as if the maximum hit result was attained each time.</param>
[Pure] [Pure]
internal void ExtractScoringValues(ScoreInfo scoreInfo, out ScoringValues current, out ScoringValues maximum) private void extractScoringValues(ScoreInfo scoreInfo, out ScoringValues current, out ScoringValues maximum)
{ {
extractScoringValues(scoreInfo.Statistics, out current, out maximum); extractScoringValues(scoreInfo.Statistics, out current, out maximum);
current.MaxCombo = scoreInfo.MaxCombo; current.MaxCombo = scoreInfo.MaxCombo;
@ -490,31 +486,6 @@ namespace osu.Game.Rulesets.Scoring
extractScoringValues(scoreInfo.MaximumStatistics, out _, out maximum); extractScoringValues(scoreInfo.MaximumStatistics, out _, out maximum);
} }
/// <summary>
/// Applies a best-effort extraction of hit statistics into <see cref="ScoringValues"/>.
/// </summary>
/// <remarks>
/// This method is useful in a variety of situations, with a few drawbacks that need to be considered:
/// <list type="bullet">
/// <item>The maximum <see cref="ScoringValues.BonusScore"/> will always be 0.</item>
/// <item>The current and maximum <see cref="ScoringValues.CountBasicHitObjects"/> will always be the same value.</item>
/// </list>
/// Consumers are expected to more accurately fill in the above values through external means.
/// <para>
/// <b>Ensure</b> to fill in the maximum <see cref="ScoringValues.CountBasicHitObjects"/> for use in
/// <see cref="ComputeScore(osu.Game.Rulesets.Scoring.ScoringMode,osu.Game.Scoring.ScoringValues,osu.Game.Scoring.ScoringValues)"/>.
/// </para>
/// </remarks>
/// <param name="header">The replay frame header to extract scoring values from.</param>
/// <param name="current">The "current" scoring values, representing the hit statistics as they appear.</param>
/// <param name="maximum">The "maximum" scoring values, representing the hit statistics as if the maximum hit result was attained each time.</param>
[Pure]
internal void ExtractScoringValues(FrameHeader header, out ScoringValues current, out ScoringValues maximum)
{
extractScoringValues(header.Statistics, out current, out maximum);
current.MaxCombo = header.MaxCombo;
}
/// <summary> /// <summary>
/// Applies a best-effort extraction of hit statistics into <see cref="ScoringValues"/>. /// Applies a best-effort extraction of hit statistics into <see cref="ScoringValues"/>.
/// </summary> /// </summary>
@ -589,6 +560,32 @@ namespace osu.Game.Rulesets.Scoring
base.Dispose(isDisposing); base.Dispose(isDisposing);
hitEvents.Clear(); hitEvents.Clear();
} }
/// <summary>
/// Stores the required scoring data that fulfils the minimum requirements for a <see cref="ScoreProcessor"/> to calculate score.
/// </summary>
private struct ScoringValues
{
/// <summary>
/// The sum of all "basic" <see cref="HitObject"/> scoring values. See: <see cref="HitResultExtensions.IsBasic"/> and <see cref="Judgement.ToNumericResult"/>.
/// </summary>
public long BaseScore;
/// <summary>
/// The sum of all "bonus" <see cref="HitObject"/> scoring values. See: <see cref="HitResultExtensions.IsBonus"/> and <see cref="Judgement.ToNumericResult"/>.
/// </summary>
public long BonusScore;
/// <summary>
/// The highest achieved combo.
/// </summary>
public int MaxCombo;
/// <summary>
/// The count of "basic" <see cref="HitObject"/>s. See: <see cref="HitResultExtensions.IsBasic"/>.
/// </summary>
public int CountBasicHitObjects;
}
} }
public enum ScoringMode public enum ScoringMode

View File

@ -1,43 +0,0 @@
// 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.
#nullable disable
using MessagePack;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Scoring
{
/// <summary>
/// Stores the required scoring data that fulfils the minimum requirements for a <see cref="ScoreProcessor"/> to calculate score.
/// </summary>
[MessagePackObject]
public struct ScoringValues
{
/// <summary>
/// The sum of all "basic" <see cref="HitObject"/> scoring values. See: <see cref="HitResultExtensions.IsBasic"/> and <see cref="Judgement.ToNumericResult"/>.
/// </summary>
[Key(0)]
public long BaseScore;
/// <summary>
/// The sum of all "bonus" <see cref="HitObject"/> scoring values. See: <see cref="HitResultExtensions.IsBonus"/> and <see cref="Judgement.ToNumericResult"/>.
/// </summary>
[Key(1)]
public long BonusScore;
/// <summary>
/// The highest achieved combo.
/// </summary>
[Key(2)]
public int MaxCombo;
/// <summary>
/// The count of "basic" <see cref="HitObject"/>s. See: <see cref="HitResultExtensions.IsBasic"/>.
/// </summary>
[Key(3)]
public int CountBasicHitObjects;
}
}