// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Bindables; using osu.Framework.Utils; using osu.Game.Rulesets.Judgements; namespace osu.Game.Rulesets.Scoring { public abstract partial class HealthProcessor : JudgementProcessor { /// <summary> /// Invoked when the <see cref="ScoreProcessor"/> is in a failed state. /// Return true if the fail was permitted. /// </summary> public event Func<bool>? Failed; /// <summary> /// Additional conditions on top of <see cref="CheckDefaultFailCondition"/> that cause a failing state. /// </summary> public event Func<HealthProcessor, JudgementResult, bool>? FailConditions; /// <summary> /// The current health. /// </summary> public readonly BindableDouble Health = new BindableDouble(1) { MinValue = 0, MaxValue = 1 }; /// <summary> /// Whether this ScoreProcessor has already triggered the failed state. /// </summary> public bool HasFailed { get; private set; } /// <summary> /// Immediately triggers a failure for this HealthProcessor. /// </summary> public void TriggerFailure() { if (HasFailed) return; if (Failed?.Invoke() != false) HasFailed = true; } protected override void ApplyResultInternal(JudgementResult result) { result.HealthAtJudgement = Health.Value; result.FailedAtJudgement = HasFailed; if (HasFailed) return; Health.Value += GetHealthIncreaseFor(result); if (meetsAnyFailCondition(result)) TriggerFailure(); } protected override void RevertResultInternal(JudgementResult result) { HasFailed = result.FailedAtJudgement; if (HasFailed) return; Health.Value = result.HealthAtJudgement; } /// <summary> /// Retrieves the health increase for a <see cref="JudgementResult"/>. /// </summary> /// <param name="result">The <see cref="JudgementResult"/>.</param> /// <returns>The health increase.</returns> protected virtual double GetHealthIncreaseFor(JudgementResult result) => result.HealthIncrease; /// <summary> /// Checks whether the default conditions for failing are met. /// </summary> /// <returns><see langword="true"/> if failure should be invoked.</returns> protected virtual bool CheckDefaultFailCondition(JudgementResult result) => Precision.AlmostBigger(Health.MinValue, Health.Value); /// <summary> /// Whether the current state of <see cref="HealthProcessor"/> or the provided <paramref name="result"/> meets any fail condition. /// </summary> /// <param name="result">The judgement result.</param> private bool meetsAnyFailCondition(JudgementResult result) { if (CheckDefaultFailCondition(result)) return true; if (FailConditions != null) { foreach (var condition in FailConditions.GetInvocationList()) { bool conditionResult = (bool)condition.Method.Invoke(condition.Target, new object[] { this, result })!; if (conditionResult) return true; } } return false; } protected override void Reset(bool storeResults) { base.Reset(storeResults); Health.Value = 1; HasFailed = false; } } }