1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-22 20:52:54 +08:00

Combine fail blocking and forcing logic into a singular mod interface

This commit is contained in:
Dean Herbert 2024-08-06 22:40:29 +09:00
parent 950b001533
commit 9ed8528a40
No known key found for this signature in database
15 changed files with 88 additions and 72 deletions

View File

@ -9,16 +9,19 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModPerfect : ModPerfect
{
public override bool FailCondition(JudgementResult result)
public override FailState CheckFail(JudgementResult? result)
{
if (result == null)
return FailState.Allow;
if (!isRelevantResult(result.Judgement.MinResult) && !isRelevantResult(result.Judgement.MaxResult) && !isRelevantResult(result.Type))
return false;
return FailState.Allow;
// Mania allows imperfect "Great" hits without failing.
if (result.Judgement.MaxResult == HitResult.Perfect)
return result.Type < HitResult.Great;
return result.Type >= HitResult.Great ? FailState.Allow : FailState.Force;
return result.Type != result.Judgement.MaxResult;
return result.Type != result.Judgement.MaxResult ? FailState.Force : FailState.Allow;
}
private bool isRelevantResult(HitResult result) => result.AffectsAccuracy() || result.AffectsCombo();

View File

@ -33,7 +33,7 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModTargetPractice : ModWithVisibilityAdjustment, IApplicableToDrawableRuleset<OsuHitObject>,
IApplicableToDifficulty, IHasSeed, IHidesApproachCircles, IHasFailCondition
IApplicableToDifficulty, IHasSeed, IHidesApproachCircles, IApplicableFailOverride
{
public override string Name => "Target Practice";
public override string Acronym => "TP";
@ -100,14 +100,15 @@ namespace osu.Game.Rulesets.Osu.Mods
#region Sudden Death (IApplicableFailOverride)
public bool PerformFail() => true;
public bool RestartOnFail => false;
// Sudden death
public bool FailCondition(JudgementResult result)
=> result.Type.AffectsCombo()
&& !result.IsHit;
public FailState CheckFail(JudgementResult? result)
{
if (result == null)
return FailState.Allow;
return result.Type.AffectsCombo() && !result.IsHit ? FailState.Force : FailState.Allow;
}
#endregion

View File

@ -440,7 +440,7 @@ namespace osu.Game.Tests.Gameplay
this.type = type;
}
public override bool FailCondition(JudgementResult result) => result.Type == type;
public override FailState CheckFail(JudgementResult result) => result?.Type == type ? FailState.Force : FailState.Allow;
}
}
}

View File

@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public override double ScoreMultiplier => 1.0;
public override string Acronym => "";
public override bool FailCondition(JudgementResult result) => true;
public override FailState CheckFail(JudgementResult? result) => FailState.Force;
}
}
}

View File

@ -1,22 +1,43 @@
// 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 osu.Game.Rulesets.Judgements;
namespace osu.Game.Rulesets.Mods
{
/// <summary>
/// Represents a mod which can override (and block) a fail.
/// Represents a mod which can override failure, by either hard-blocking it, or forcing it immediately.
/// </summary>
public interface IApplicableFailOverride : IApplicableMod
{
/// <summary>
/// Whether we should allow failing at the current point in time.
/// </summary>
/// <returns>Whether the fail should be allowed to proceed. Return false to block.</returns>
bool PerformFail();
/// <summary>
/// Whether we want to restart on fail. Only used if <see cref="PerformFail"/> returns true.
/// Whether we want to restart on fail.
/// </summary>
bool RestartOnFail { get; }
/// <summary>
/// Check the current failure allowance for this mod.
/// </summary>
/// <param name="result">The judgement result which should be considered. Importantly, will be <c>null</c> if a failure has already being triggered.</param>
/// <returns>The current failure allowance (see <see cref="FailState"/>).</returns>
FailState CheckFail(JudgementResult? result);
}
public enum FailState
{
/// <summary>
/// Failure is being blocked by this mod.
/// </summary>
Block,
/// <summary>
/// Failure is allowed by this mod (but may be triggered by another mod or base behaviour).
/// </summary>
Allow,
/// <summary>
/// Failure should be forced immediately.
/// </summary>
Force
}
}

View File

@ -1,26 +0,0 @@
// 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 osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mods
{
/// <summary>
/// Interface for a <see cref="Mod"/> that specifies its own conditions for failure.
/// </summary>
// todo: maybe IHasFailCondition and IApplicableFailOverride should be combined into a single interface.
public interface IHasFailCondition : IApplicableFailOverride
{
/// <summary>
/// Determines whether <paramref name="result"/> should trigger a failure. Called every time a
/// judgement is applied to <see cref="HealthProcessor"/>.
/// </summary>
/// <param name="result">The latest <see cref="JudgementResult"/>.</param>
/// <returns>Whether the fail condition has been met.</returns>
/// <remarks>
/// This method should only be used to trigger failures based on <paramref name="result"/>
/// </remarks>
bool FailCondition(JudgementResult result);
}
}

View File

@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Mods
public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
public override bool FailCondition(JudgementResult result) => false;
public override FailState CheckFail(JudgementResult? result) => FailState.Allow;
public enum AccuracyMode
{

View File

@ -6,6 +6,7 @@ using System.Linq;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
@ -48,8 +49,8 @@ namespace osu.Game.Rulesets.Mods
player.BreakOverlay.Hide();
}
public bool PerformFail() => false;
public bool RestartOnFail => false;
public FailState CheckFail(JudgementResult? result) => FailState.Block;
}
}

View File

@ -7,6 +7,7 @@ using Humanizer;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mods
@ -33,18 +34,22 @@ namespace osu.Game.Rulesets.Mods
retries = Retries.Value;
}
public bool PerformFail()
public bool RestartOnFail => false;
public FailState CheckFail(JudgementResult? result)
{
if (retries == 0) return true;
if (result != null)
return FailState.Block;
if (retries == 0)
return FailState.Allow;
health.Value = health.MaxValue;
retries--;
return false;
return FailState.Block;
}
public bool RestartOnFail => false;
public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
{
health.BindTo(healthProcessor.Health);

View File

@ -9,17 +9,16 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mods
{
public abstract class ModFailCondition : Mod, IApplicableToHealthProcessor, IHasFailCondition
public abstract class ModFailCondition : Mod, IApplicableToHealthProcessor, IApplicableFailOverride
{
public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModCinema) };
[SettingSource("Restart on fail", "Automatically restarts when failed.")]
public BindableBool Restart { get; } = new BindableBool();
public virtual bool PerformFail() => true;
public virtual bool AllowFail => true;
public virtual bool RestartOnFail => Restart.Value;
private Action<Mod>? triggerFailureDelegate;
public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
@ -32,6 +31,6 @@ namespace osu.Game.Rulesets.Mods
/// </summary>
protected void TriggerFailure() => triggerFailureDelegate?.Invoke(this);
public abstract bool FailCondition(JudgementResult result);
public abstract FailState CheckFail(JudgementResult? result);
}
}

View File

@ -7,6 +7,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Judgements;
using osu.Game.Screens.Play;
namespace osu.Game.Rulesets.Mods
@ -24,12 +25,12 @@ namespace osu.Game.Rulesets.Mods
private readonly Bindable<bool> showHealthBar = new Bindable<bool>();
public bool RestartOnFail => false;
/// <summary>
/// We never fail, 'yo.
/// </summary>
public bool PerformFail() => false;
public bool RestartOnFail => false;
public FailState CheckFail(JudgementResult? result) => FailState.Block;
public void ReadFromConfig(OsuConfigManager config)
{

View File

@ -28,9 +28,16 @@ namespace osu.Game.Rulesets.Mods
Restart.Value = Restart.Default = true;
}
public override bool FailCondition(JudgementResult result)
=> (isRelevantResult(result.Judgement.MinResult) || isRelevantResult(result.Judgement.MaxResult) || isRelevantResult(result.Type))
&& result.Type != result.Judgement.MaxResult;
public override FailState CheckFail(JudgementResult? result)
{
if (result == null)
return FailState.Allow;
return (isRelevantResult(result.Judgement.MinResult) || isRelevantResult(result.Judgement.MaxResult) || isRelevantResult(result.Type))
&& result.Type != result.Judgement.MaxResult
? FailState.Force
: FailState.Allow;
}
private bool isRelevantResult(HitResult result) => result.AffectsAccuracy() || result.AffectsCombo();
}

View File

@ -23,8 +23,12 @@ namespace osu.Game.Rulesets.Mods
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModPerfect)).ToArray();
public override bool FailCondition(JudgementResult result)
=> result.Type.AffectsCombo()
&& !result.IsHit;
public override FailState CheckFail(JudgementResult? result)
{
if (result == null)
return FailState.Allow;
return result.Type.AffectsCombo() && !result.IsHit ? FailState.Force : FailState.Allow;
}
}
}

View File

@ -102,9 +102,9 @@ namespace osu.Game.Rulesets.Scoring
if (CheckDefaultFailCondition(result))
return true;
foreach (var condition in Mods.Value.OfType<IHasFailCondition>())
foreach (var condition in Mods.Value.OfType<IApplicableFailOverride>())
{
if (condition.FailCondition(result))
if (condition.CheckFail(result) == FailState.Force)
{
ModTriggeringFailure = condition as Mod;
return true;

View File

@ -152,7 +152,7 @@ namespace osu.Game.Screens.Play
/// Whether failing should be allowed.
/// By default, this checks whether all selected mods allow failing.
/// </summary>
protected virtual bool CheckModsAllowFailure() => GameplayState.Mods.OfType<IApplicableFailOverride>().All(m => m.PerformFail());
protected virtual bool CheckModsAllowFailure() => HealthProcessor.Mods.Value.OfType<IApplicableFailOverride>().All(m => m.CheckFail(null) != FailState.Block);
public readonly PlayerConfiguration Configuration;
@ -244,7 +244,7 @@ namespace osu.Game.Screens.Play
HealthProcessor = gameplayMods.OfType<IApplicableHealthProcessor>().FirstOrDefault()?.CreateHealthProcessor(playableBeatmap.HitObjects[0].StartTime);
HealthProcessor ??= ruleset.CreateHealthProcessor(playableBeatmap.HitObjects[0].StartTime);
HealthProcessor.Mods.Value = gameplayMods.OrderByDescending(m => m is IHasFailCondition mod && mod.RestartOnFail).ToArray();
HealthProcessor.Mods.Value = gameplayMods.OrderByDescending(m => m is IApplicableFailOverride mod && mod.RestartOnFail).ToArray();
HealthProcessor.ApplyBeatmap(playableBeatmap);
dependencies.CacheAs(HealthProcessor);