// Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Configuration; using System; using System.Collections.Generic; using osu.Game.Modes.Judgements; using osu.Game.Modes.UI; using osu.Game.Modes.Objects; using osu.Game.Beatmaps; namespace osu.Game.Modes { public abstract class ScoreProcessor { /// /// Invoked when the ScoreProcessor is in a failed state. /// public event Action Failed; /// /// 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 => false; /// /// 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(); } /// /// Creates a Score applicable to the game mode in which this ScoreProcessor resides. /// /// The Score. public virtual Score CreateScore() => new Score { TotalScore = TotalScore, Combo = Combo, MaxCombo = HighestCombo, Accuracy = Accuracy, Health = Health, }; /// /// 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; alreadyFailed = true; Failed?.Invoke(); } } public abstract class ScoreProcessor : ScoreProcessor where TObject : HitObject where TJudgement : JudgementInfo { /// /// All judgements held by this ScoreProcessor. /// protected readonly List Judgements = new List(); public override bool HasFailed => Health.Value == Health.MinValue; protected ScoreProcessor() { } protected ScoreProcessor(HitRenderer hitRenderer) { Judgements.Capacity = hitRenderer.Beatmap.HitObjects.Count; hitRenderer.OnJudgement += addJudgement; ComputeTargets(hitRenderer.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. private void addJudgement(TJudgement judgement) { Judgements.Add(judgement); UpdateCalculations(judgement); judgement.ComboAtHit = (ulong)Combo.Value; UpdateFailed(); } protected override void Reset() { Judgements.Clear(); } /// /// Update any values that potentially need post-processing on a judgement change. /// /// A new JudgementInfo that triggered this calculation. May be null. protected abstract void UpdateCalculations(TJudgement newJudgement); } }