diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 18785d65ea..f67ca1213e 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -2,23 +2,21 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; -using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Catch.Scoring { - public class CatchScoreProcessor : ScoreProcessor + public class CatchScoreProcessor : ScoreProcessor { - public CatchScoreProcessor(DrawableRuleset drawableRuleset) - : base(drawableRuleset) + public CatchScoreProcessor(IBeatmap beatmap) + : base(beatmap) { } private float hpDrainRate; - protected override void ApplyBeatmap(Beatmap beatmap) + protected override void ApplyBeatmap(IBeatmap beatmap) { base.ApplyBeatmap(beatmap); diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index 6b7f00c5d0..f5bddeb2cb 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Catch.UI TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450); } - public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this); + public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(Beatmap); protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 49894a644c..a678ef60e7 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -3,13 +3,11 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mania.Scoring { - internal class ManiaScoreProcessor : ScoreProcessor + internal class ManiaScoreProcessor : ScoreProcessor { /// /// The hit HP multiplier at OD = 0. @@ -51,12 +49,12 @@ namespace osu.Game.Rulesets.Mania.Scoring /// private double hpMultiplier = 1; - public ManiaScoreProcessor(DrawableRuleset drawableRuleset) - : base(drawableRuleset) + public ManiaScoreProcessor(IBeatmap beatmap) + : base(beatmap) { } - protected override void ApplyBeatmap(Beatmap beatmap) + protected override void ApplyBeatmap(IBeatmap beatmap) { base.ApplyBeatmap(beatmap); @@ -65,7 +63,7 @@ namespace osu.Game.Rulesets.Mania.Scoring hpMissMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_miss_min, hp_multiplier_miss_mid, hp_multiplier_miss_max); } - protected override void SimulateAutoplay(Beatmap beatmap) + protected override void SimulateAutoplay(IBeatmap beatmap) { while (true) { diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index d371c1f7a8..0607bf0abd 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Mania.UI protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages); - public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this); + public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(Beatmap); public override int Variant => (int)(Beatmap.Stages.Count == 1 ? PlayfieldType.Single : PlayfieldType.Dual) + Beatmap.TotalColumns; diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index affe18a30d..6779271cb3 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -5,22 +5,20 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; 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; namespace osu.Game.Rulesets.Osu.Scoring { - internal class OsuScoreProcessor : ScoreProcessor + internal class OsuScoreProcessor : ScoreProcessor { - public OsuScoreProcessor(DrawableRuleset drawableRuleset) - : base(drawableRuleset) + public OsuScoreProcessor(IBeatmap beatmap) + : base(beatmap) { } private float hpDrainRate; - protected override void ApplyBeatmap(Beatmap beatmap) + protected override void ApplyBeatmap(IBeatmap beatmap) { base.ApplyBeatmap(beatmap); diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index 49aea52902..5bb728a9b0 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.UI public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // always show the gameplay cursor - public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(this); + public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(Beatmap); protected override Playfield CreatePlayfield() => new OsuPlayfield(); diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index 75a27ff639..ae593d2e3a 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -1,15 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Taiko.Scoring { - internal class TaikoScoreProcessor : ScoreProcessor + internal class TaikoScoreProcessor : ScoreProcessor { /// /// A value used for calculating . @@ -31,16 +31,16 @@ namespace osu.Game.Rulesets.Taiko.Scoring /// private double hpMissMultiplier; - public TaikoScoreProcessor(DrawableRuleset drawableRuleset) - : base(drawableRuleset) + public TaikoScoreProcessor(IBeatmap beatmap) + : base(beatmap) { } - protected override void ApplyBeatmap(Beatmap beatmap) + protected override void ApplyBeatmap(IBeatmap beatmap) { base.ApplyBeatmap(beatmap); - hpMultiplier = 1 / (object_count_factor * beatmap.HitObjects.FindAll(o => o is Hit).Count * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98)); + hpMultiplier = 1 / (object_count_factor * beatmap.HitObjects.OfType().Count() * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98)); hpMissMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.0018, 0.0075, 0.0120); } diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index fc109bf6a6..d4ea9a043a 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.UI new BarLineGenerator(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar.Major ? new DrawableBarLineMajor(bar) : new DrawableBarLine(bar))); } - public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this); + public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(Beatmap); public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer(); diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 18c2a2ca01..a8a2294498 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -13,13 +13,16 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.UI; using osu.Game.Scoring; namespace osu.Game.Rulesets.Scoring { - public abstract class ScoreProcessor + public class ScoreProcessor { + private const double base_portion = 0.3; + private const double combo_portion = 0.7; + private const double max_score = 1000000; + /// /// Invoked when the is in a failed state. /// This may occur regardless of whether an event is invoked. @@ -67,11 +70,6 @@ namespace osu.Game.Rulesets.Scoring /// public readonly Bindable> Mods = new Bindable>(Array.Empty()); - /// - /// Create a for this processor. - /// - public virtual HitWindows CreateHitWindows() => new HitWindows(); - /// /// The current rank. /// @@ -90,132 +88,23 @@ namespace osu.Game.Rulesets.Scoring /// /// Whether all s have been processed. /// - public virtual bool HasCompleted => false; - - /// - /// The total number of judged s at the current point in time. - /// - public int JudgedHits { get; protected set; } + public bool HasCompleted => JudgedHits == MaxHits; /// /// Whether this ScoreProcessor has already triggered the failed state. /// - public virtual bool HasFailed { get; private set; } + public bool HasFailed { get; private set; } /// - /// The default conditions for failing. + /// The maximum number of hits that can be judged. /// - protected virtual bool DefaultFailCondition => Precision.AlmostBigger(Health.MinValue, Health.Value); - - protected ScoreProcessor() - { - Combo.ValueChanged += delegate { HighestCombo.Value = Math.Max(HighestCombo.Value, Combo.Value); }; - Accuracy.ValueChanged += delegate - { - Rank.Value = rankFrom(Accuracy.Value); - foreach (var mod in Mods.Value.OfType()) - Rank.Value = mod.AdjustRank(Rank.Value, Accuracy.Value); - }; - } - - private ScoreRank rankFrom(double acc) - { - if (acc == 1) - return ScoreRank.X; - if (acc > 0.95) - return ScoreRank.S; - if (acc > 0.9) - return ScoreRank.A; - if (acc > 0.8) - return ScoreRank.B; - if (acc > 0.7) - return ScoreRank.C; - - return ScoreRank.D; - } - - /// - /// Resets this ScoreProcessor to a default state. - /// - /// Whether to store the current state of the for future use. - protected virtual void Reset(bool storeResults) - { - TotalScore.Value = 0; - Accuracy.Value = 1; - Health.Value = 1; - Combo.Value = 0; - Rank.Value = ScoreRank.X; - HighestCombo.Value = 0; - - JudgedHits = 0; - - HasFailed = false; - } - - /// - /// Checks if the score is in a failed state and notifies subscribers. - /// - /// This can only ever notify subscribers once. - /// - /// - protected void UpdateFailed(JudgementResult result) - { - if (HasFailed) - return; - - if (!DefaultFailCondition && FailConditions?.Invoke(this, result) != true) - return; - - if (Failed?.Invoke() != false) - HasFailed = true; - } - - /// - /// Notifies subscribers of that a new judgement has occurred. - /// - /// The judgement scoring result to notify subscribers of. - protected void NotifyNewJudgement(JudgementResult result) - { - NewJudgement?.Invoke(result); - - if (HasCompleted) - AllJudged?.Invoke(); - } - - /// - /// Retrieve a score populated with data for the current play this processor is responsible for. - /// - public virtual void PopulateScore(ScoreInfo score) - { - score.TotalScore = (long)Math.Round(TotalScore.Value); - score.Combo = Combo.Value; - score.MaxCombo = HighestCombo.Value; - score.Accuracy = Math.Round(Accuracy.Value, 4); - score.Rank = Rank.Value; - score.Date = DateTimeOffset.Now; - - var hitWindows = CreateHitWindows(); - - foreach (var result in Enum.GetValues(typeof(HitResult)).OfType().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r))) - score.Statistics[result] = GetStatistic(result); - } - - public abstract int GetStatistic(HitResult result); - - public abstract double GetStandardisedScore(); - } - - public class ScoreProcessor : ScoreProcessor - where TObject : HitObject - { - private const double base_portion = 0.3; - private const double combo_portion = 0.7; - private const double max_score = 1000000; - - public sealed override bool HasCompleted => JudgedHits == MaxHits; - protected int MaxHits { get; private set; } + /// + /// The total number of judged s at the current point in time. + /// + public int JudgedHits { get; private set; } + private double maxHighestCombo; private double maxBaseScore; @@ -225,17 +114,22 @@ namespace osu.Game.Rulesets.Scoring private double scoreMultiplier = 1; - public ScoreProcessor(DrawableRuleset drawableRuleset) + public ScoreProcessor(IBeatmap beatmap) { Debug.Assert(base_portion + combo_portion == 1.0); - drawableRuleset.OnNewResult += applyResult; - drawableRuleset.OnRevertResult += revertResult; + Combo.ValueChanged += combo => HighestCombo.Value = Math.Max(HighestCombo.Value, combo.NewValue); + Accuracy.ValueChanged += accuracy => + { + Rank.Value = rankFrom(accuracy.NewValue); + foreach (var mod in Mods.Value.OfType()) + Rank.Value = mod.AdjustRank(Rank.Value, accuracy.NewValue); + }; - ApplyBeatmap(drawableRuleset.Beatmap); + ApplyBeatmap(beatmap); Reset(false); - SimulateAutoplay(drawableRuleset.Beatmap); + SimulateAutoplay(beatmap); Reset(true); if (maxBaseScore == 0 || maxHighestCombo == 0) @@ -257,19 +151,19 @@ namespace osu.Game.Rulesets.Scoring } /// - /// Applies any properties of the which affect scoring to this . + /// Applies any properties of the which affect scoring to this . /// - /// The to read properties from. - protected virtual void ApplyBeatmap(Beatmap beatmap) + /// The to read properties from. + protected virtual void ApplyBeatmap(IBeatmap beatmap) { } /// - /// Simulates an autoplay of the to determine scoring values. + /// Simulates an autoplay of the to determine scoring values. /// /// This provided temporarily. DO NOT USE. - /// The to simulate. - protected virtual void SimulateAutoplay(Beatmap beatmap) + /// The to simulate. + protected virtual void SimulateAutoplay(IBeatmap beatmap) { foreach (var obj in beatmap.HitObjects) simulate(obj); @@ -289,7 +183,7 @@ namespace osu.Game.Rulesets.Scoring result.Type = judgement.MaxResult; - applyResult(result); + ApplyResult(result); } } @@ -297,22 +191,26 @@ namespace osu.Game.Rulesets.Scoring /// Applies the score change of a to this . /// /// The to apply. - private void applyResult(JudgementResult result) + public void ApplyResult(JudgementResult result) { - ApplyResult(result); - updateScore(); + ApplyResultInternal(result); - UpdateFailed(result); - NotifyNewJudgement(result); + updateScore(); + updateFailed(result); + + NewJudgement?.Invoke(result); + + if (HasCompleted) + AllJudged?.Invoke(); } /// /// Reverts the score change of a that was applied to this . /// /// The judgement scoring result. - private void revertResult(JudgementResult result) + public void RevertResult(JudgementResult result) { - RevertResult(result); + RevertResultInternal(result); updateScore(); } @@ -322,10 +220,10 @@ namespace osu.Game.Rulesets.Scoring /// Applies the score change of a to this . /// /// - /// Any changes applied via this method can be reverted via . + /// Any changes applied via this method can be reverted via . /// /// The to apply. - protected virtual void ApplyResult(JudgementResult result) + protected virtual void ApplyResultInternal(JudgementResult result) { result.ComboAtJudgement = Combo.Value; result.HighestComboAtJudgement = HighestCombo.Value; @@ -372,10 +270,10 @@ namespace osu.Game.Rulesets.Scoring } /// - /// Reverts the score change of a that was applied to this via . + /// Reverts the score change of a that was applied to this via . /// /// The judgement scoring result. - protected virtual void RevertResult(JudgementResult result) + protected virtual void RevertResultInternal(JudgementResult result) { Combo.Value = result.ComboAtJudgement; HighestCombo.Value = result.HighestComboAtJudgement; @@ -432,11 +330,49 @@ namespace osu.Game.Rulesets.Scoring } } - public override int GetStatistic(HitResult result) => scoreResultCounts.GetOrDefault(result); + /// + /// Checks if the score is in a failed state and notifies subscribers. + /// + /// This can only ever notify subscribers once. + /// + /// + private void updateFailed(JudgementResult result) + { + if (HasFailed) + return; - public override double GetStandardisedScore() => getScore(ScoringMode.Standardised); + if (!DefaultFailCondition && FailConditions?.Invoke(this, result) != true) + return; - protected override void Reset(bool storeResults) + if (Failed?.Invoke() != false) + HasFailed = true; + } + + private ScoreRank rankFrom(double acc) + { + if (acc == 1) + return ScoreRank.X; + if (acc > 0.95) + return ScoreRank.S; + if (acc > 0.9) + return ScoreRank.A; + if (acc > 0.8) + return ScoreRank.B; + if (acc > 0.7) + return ScoreRank.C; + + return ScoreRank.D; + } + + public int GetStatistic(HitResult result) => scoreResultCounts.GetOrDefault(result); + + public double GetStandardisedScore() => getScore(ScoringMode.Standardised); + + /// + /// Resets this ScoreProcessor to a default state. + /// + /// Whether to store the current state of the for future use. + protected virtual void Reset(bool storeResults) { scoreResultCounts.Clear(); @@ -447,13 +383,49 @@ namespace osu.Game.Rulesets.Scoring maxBaseScore = baseScore; } - base.Reset(storeResults); - + JudgedHits = 0; baseScore = 0; rollingMaxBaseScore = 0; bonusScore = 0; + + TotalScore.Value = 0; + Accuracy.Value = 1; + Health.Value = 1; + Combo.Value = 0; + Rank.Value = ScoreRank.X; + HighestCombo.Value = 0; + + HasFailed = false; } + /// + /// Retrieve a score populated with data for the current play this processor is responsible for. + /// + public virtual void PopulateScore(ScoreInfo score) + { + score.TotalScore = (long)Math.Round(TotalScore.Value); + score.Combo = Combo.Value; + score.MaxCombo = HighestCombo.Value; + score.Accuracy = Math.Round(Accuracy.Value, 4); + score.Rank = Rank.Value; + score.Date = DateTimeOffset.Now; + + var hitWindows = CreateHitWindows(); + + foreach (var result in Enum.GetValues(typeof(HitResult)).OfType().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r))) + score.Statistics[result] = GetStatistic(result); + } + + /// + /// The default conditions for failing. + /// + protected virtual bool DefaultFailCondition => Precision.AlmostBigger(Health.MinValue, Health.Value); + + /// + /// Create a for this processor. + /// + public virtual HitWindows CreateHitWindows() => new HitWindows(); + /// /// Creates the that represents the scoring result for a . /// diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index a856974292..10657f6b39 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -45,6 +45,10 @@ namespace osu.Game.Rulesets.UI public abstract class DrawableRuleset : DrawableRuleset, IProvideCursor, ICanAttachKeyCounter where TObject : HitObject { + public override event Action OnNewResult; + + public override event Action OnRevertResult; + /// /// The selected variant. /// @@ -91,16 +95,6 @@ namespace osu.Game.Rulesets.UI } } - /// - /// Invoked when a has been applied by a . - /// - public event Action OnNewResult; - - /// - /// Invoked when a is being reverted by a . - /// - public event Action OnRevertResult; - /// /// The beatmap. /// @@ -309,7 +303,7 @@ namespace osu.Game.Rulesets.UI /// The Playfield. protected abstract Playfield CreatePlayfield(); - public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(this); + public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(Beatmap); /// /// Applies the active mods to this DrawableRuleset. @@ -366,6 +360,16 @@ namespace osu.Game.Rulesets.UI /// public abstract class DrawableRuleset : CompositeDrawable { + /// + /// Invoked when a has been applied by a . + /// + public abstract event Action OnNewResult; + + /// + /// Invoked when a is being reverted by a . + /// + public abstract event Action OnRevertResult; + /// /// Whether a replay is currently loaded. /// diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 9feee82989..9f4ca7d817 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -140,6 +140,9 @@ namespace osu.Game.Screens.Play // bind clock into components that require it DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused); + DrawableRuleset.OnNewResult += ScoreProcessor.ApplyResult; + DrawableRuleset.OnRevertResult += ScoreProcessor.RevertResult; + // Bind ScoreProcessor to ourselves ScoreProcessor.AllJudged += onCompletion; ScoreProcessor.Failed += onFail;