1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-21 21:40:56 +08:00
Files
osu-lazer/osu.Game/Rulesets/Scoring/HealthProcessor.cs
T
Bartłomiej Dach 30857de55e Add rudimentary support of rewinding failed state to health processor
As indicated in the inline comment this is very best effort, just to
make the HP bar not very obviously stuck in a very obviously incorrect
state after the replay is rewound from a failure. There's likely to be a
bunch of replay accuracy issues related to this, but I'm just making the
minimum effort to get this to work semi-acceptably for now.
2025-08-12 14:26:00 +02:00

118 lines
4.0 KiB
C#

// 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.
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)
{
// TODO: this is rudimentary as to make rewinding failed replays work,
// but it also acts up (sometimes rewinding a replay several times around the fail boundary moves the point of fail forward).
// needs further investigation.
if (result.FailedAtJudgement)
HasFailed = false;
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;
}
}
}