// Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; using System.Collections.Generic; using osu.Framework.Configuration; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Scoring { public abstract class ScoreProcessor { /// /// Invoked when the ScoreProcessor is in a failed state. /// Return true if the fail was permitted. /// public event Func Failed; /// /// Invoked when a new judgement has occurred. This occurs after the judgement has been processed by the . /// public event Action NewJudgement; /// /// The current total score. /// public readonly BindableDouble TotalScore = new BindableDouble { MinValue = 0 }; /// /// The current accuracy. /// public readonly BindableDouble Accuracy = new BindableDouble { MinValue = 0, MaxValue = 1 }; /// /// The current health. /// public readonly BindableDouble Health = new BindableDouble { MinValue = 0, MaxValue = 1 }; /// /// The current combo. /// public readonly BindableInt Combo = new BindableInt(); /// /// THe highest combo achieved by this score. /// public readonly BindableInt HighestCombo = new BindableInt(); /// /// Whether the score is in a failed state. /// public virtual bool HasFailed => Health.Value == Health.MinValue; /// /// Whether this ScoreProcessor has already triggered the failed state. /// private bool alreadyFailed; protected ScoreProcessor() { Combo.ValueChanged += delegate { HighestCombo.Value = Math.Max(HighestCombo.Value, Combo.Value); }; Reset(); } 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. /// protected virtual void Reset() { TotalScore.Value = 0; Accuracy.Value = 0; Health.Value = 0; Combo.Value = 0; HighestCombo.Value = 0; alreadyFailed = false; } /// /// Checks if the score is in a failed state and notifies subscribers. /// /// This can only ever notify subscribers once. /// /// protected void UpdateFailed() { if (alreadyFailed || !HasFailed) return; if (Failed?.Invoke() != false) alreadyFailed = true; } /// /// Notifies subscribers of that a new judgement has occurred. /// /// The judgement to notify subscribers of. protected void NotifyNewJudgement(Judgement judgement) { NewJudgement?.Invoke(judgement); } /// /// Retrieve a score populated with data for the current play this processor is responsible for. /// public virtual void PopulateScore(Score score) { score.TotalScore = TotalScore; score.Combo = Combo; score.MaxCombo = HighestCombo; score.Accuracy = Accuracy; score.Rank = rankFrom(Accuracy); score.Date = DateTimeOffset.Now; score.Health = Health; } } public abstract class ScoreProcessor : ScoreProcessor where TObject : HitObject { /// /// All judgements held by this ScoreProcessor. /// protected readonly List Judgements = new List(); protected ScoreProcessor() { } protected ScoreProcessor(RulesetContainer rulesetContainer) { Judgements.Capacity = rulesetContainer.Beatmap.HitObjects.Count; rulesetContainer.OnJudgement += AddJudgement; ComputeTargets(rulesetContainer.Beatmap); Reset(); } /// /// Computes target scoring values for this ScoreProcessor. This is equivalent to performing an auto-play of the score to find the values. /// /// The Beatmap containing the objects that will be judged by this ScoreProcessor. protected virtual void ComputeTargets(Beatmap beatmap) { } /// /// Adds a judgement to this ScoreProcessor. /// /// The judgement to add. protected void AddJudgement(Judgement judgement) { if (judgement.AffectsCombo) { switch (judgement.Result) { case HitResult.None: break; case HitResult.Miss: Combo.Value = 0; break; default: Combo.Value++; break; } } Judgements.Add(judgement); OnNewJudgement(judgement); NotifyNewJudgement(judgement); UpdateFailed(); } protected override void Reset() { base.Reset(); Judgements.Clear(); } /// /// Updates any values that need post-processing. Invoked when a new judgement has occurred. /// /// The judgement that triggered this calculation. protected abstract void OnNewJudgement(Judgement judgement); } }