diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs
index c00847b7d8..69daedb7a7 100644
--- a/osu.Game/Rulesets/Mods/Mod.cs
+++ b/osu.Game/Rulesets/Mods/Mod.cs
@@ -45,5 +45,10 @@ public abstract class Mod
/// The mods this mod cannot be enabled with.
///
public virtual Type[] IncompatibleMods => new Type[] { };
+
+ ///
+ /// Whether we should allow fails at the
+ ///
+ public virtual bool AllowFail => true;
}
}
diff --git a/osu.Game/Rulesets/Mods/ModNoFail.cs b/osu.Game/Rulesets/Mods/ModNoFail.cs
index 613e1e1d4d..d41c4e3956 100644
--- a/osu.Game/Rulesets/Mods/ModNoFail.cs
+++ b/osu.Game/Rulesets/Mods/ModNoFail.cs
@@ -15,5 +15,10 @@ public abstract class ModNoFail : Mod
public override double ScoreMultiplier => 0.5;
public override bool Ranked => true;
public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModAutoplay) };
+
+ ///
+ /// We never fail, 'yo.
+ ///
+ public override bool AllowFail => false;
}
}
\ No newline at end of file
diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
index 79fb34a523..cd9089c859 100644
--- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
+++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
@@ -16,8 +16,9 @@ public abstract class ScoreProcessor
{
///
/// Invoked when the ScoreProcessor is in a failed state.
+ /// Return true if the fail was permitted.
///
- public event Action Failed;
+ public event Func Failed;
///
/// Invoked when a new judgement has occurred. This occurs after the judgement has been processed by the .
@@ -106,8 +107,8 @@ protected void UpdateFailed()
if (alreadyFailed || !HasFailed)
return;
- alreadyFailed = true;
- Failed?.Invoke();
+ if (Failed?.Invoke() != false)
+ alreadyFailed = true;
}
///
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 39128eb85e..345ad60d47 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -238,13 +238,17 @@ private void onCompletion()
}
}
- private void onFail()
+ private bool onFail()
{
+ if (Beatmap.Value.Mods.Value.Any(m => !m.AllowFail))
+ return false;
+
decoupledClock.Stop();
HasFailed = true;
failOverlay.Retries = RestartCount;
failOverlay.Show();
+ return true;
}
protected override void OnEntering(Screen last)