-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rework the way mod fails are done #29379
base: master
Are you sure you want to change the base?
Conversation
Is this supposed to compile? |
Game will compile, yes. Tests no because I want feedback on the direction before spending time dealing with them. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm probably not that deep into this code as the others are but I do prefer this over #28825. I share the concern about introducing ordering to determining failure, as well as the concern about mod code being less readable in that other PR in comparison to this.
A couple of sidebar comments but in general I do prefer this.
/// <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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure on the naming here, I'd probably adjust to something like PreventFail / NoEffect / ForceFail
or something. But the idea is sound, so no major objections here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd agree with the proposed renaming. The current naming was very hard to work with when I was looking at this.
case FailState.Allow: | ||
restartOnFail |= failMod.RestartOnFail; | ||
break; | ||
|
||
case FailState.Force: | ||
forcedRestartOnFail |= failMod.RestartOnFail; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only part I'm not sure is this thing, because it's dropping the information identifying the mod that called for a restart.
That said, I think it is fine? My thinking is that this handles cases where two mods would allow or force a failure at the same instant, so at that point it's probably fine to decide to do whatever, and allowing the mod that forces restart to take precedence is probably a better idea.
osu.Game/Rulesets/Mods/ModNoFail.cs
Outdated
@@ -24,12 +25,12 @@ public abstract class ModNoFail : Mod, IApplicableFailOverride, IApplicableToHUD | |||
|
|||
private readonly Bindable<bool> showHealthBar = new Bindable<bool>(); | |||
|
|||
public bool RestartOnFail => false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This being here is a tad weird... In the large scale of things it's probably fine, but I wouldn't expect to see this here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do you imagine this working? I'm struggling to understand what your suggestions are.
I pondered RestartOnFail
quite a bit because the semantics are somewhat up in the air. My thinking is that if a mod forces fail then it should have the say as to whether to restart or not, leading to the above logic (including your comment above).
Are you seeing:
- Making this an interface-implemented property (implemented as
=> false
by default). (I'm not sure how to feel on these in general right now). - Making the enum a bitwise flag?
Otherwise, please describe the suggestions / your perspective a bit more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(1) would work, but in general I'm not super sure into trying to force ModNoFail
and similar mods into the IApplicableFailOverride
hole. The slightly-left-of-field off-the-cuff suggestion here would be to give fail-blocking mods a separate interface (IBlockFail
?) instead. That would probably make FailState
disappear again too as it could be a bool again on IApplicableFailOverride
(which would probably become IForceFail
or something).
Based on above comments, I've split
Curious on the naming/thoughts. Also, this PR is still not ready because there's another issue to be resolved:
|
I applied one of my proposals from the above - to check |
I'm not sure I would have gone with that. It seems rather arbitrary and dependent on specific ordering, and avoiding specific ordering was initially the point of this PR. |
I didn't see the most recent comments, but came here to review as a whole and also look into test failures. Something like this should fix failures: diff --git a/osu.Game/Rulesets/Scoring/HealthProcessor.cs b/osu.Game/Rulesets/Scoring/HealthProcessor.cs
index b6bb3a98d8..09aad81f94 100644
--- a/osu.Game/Rulesets/Scoring/HealthProcessor.cs
+++ b/osu.Game/Rulesets/Scoring/HealthProcessor.cs
@@ -26,14 +26,14 @@ protected override void ApplyResultInternal(JudgementResult result)
if (CheckDefaultFailCondition(result))
{
- bool allowFail = false;
+ bool allowFail = true;
for (int i = 0; i < Mods.Value.Count; i++)
{
if (Mods.Value[i] is IBlockFail blockMod)
{
// Intentionally does not early return so that all mods have a chance to update internal states (e.g. ModEasyWithExtraLives).
- allowFail |= blockMod.AllowFail();
+ allowFail &= blockMod.AllowFail();
break;
}
}
diff --git a/osu.Game/Tests/Visual/ModForceFailTestScene.cs b/osu.Game/Tests/Visual/ModForceFailTestScene.cs
index 4c6988f5ed..86b5527a95 100644
--- a/osu.Game/Tests/Visual/ModForceFailTestScene.cs
+++ b/osu.Game/Tests/Visual/ModForceFailTestScene.cs
@@ -40,12 +40,12 @@ public ModFailConditionTestPlayer(ModTestData data, bool allowFail)
protected override bool CheckModsAllowFailure() => true;
- public bool CheckFailed(bool failed)
+ public bool CheckFailed(bool shouldHaveFailed)
{
- if (!failed)
+ if (!shouldHaveFailed)
return ScoreProcessor.HasCompleted.Value && !HealthProcessor.HasFailed;
- return HealthProcessor.HasFailed;
+ return HealthProcessor.HasFailed || ScoreProcessor.HasFailed;
}
}
But without reading the above, I was going to bring up the fact that now Apart from this, the code does seem quite legible. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As commented
If you have a preference I'm all ears. I took a path to keep the ball rolling in one of the possible directions as I would like to try things out to see what looks good and sticks without engaging in bureaucratic criticism of 4 diffs (one for each suggestion) at once. This is still avoiding the ordering to me. Here's my reasoning:
Perhaps the way we've split the processors out is a bad idea. I say this because of two reasons. First, I understand the above concern, and it would be nice to have everything in one place. Second, I really don't like that the fail state is split between the two - it means one has An alternative solution may be to add a third processor, like a |
Splitting the two out felt good for keep the classes small, but I agree that maybe they should be combined. I'm fine with that being done elsewhere / later. Curious on @bdach's thoughts. |
If we must enforce ordering somehow then I'd prefer this. It feels much less shoddy to me than arbitrarily deciding that one processor does half of the fail processing and the other processor does the other half. I have vague unsubstantiated reservations to combining the two existing processors, because I think it will result in a class that is in totality too large to live. |
Moving things into a Sure, to simplify things I could try to move the judgement-based conditions inside Is the solution that This change looks kind of weird to me: diff --git a/osu.Game.Rulesets.Catch.Tests/CatchHealthProcessorTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchHealthProcessorTest.cs
index 1b46be01fb..c086ac8f1f 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchHealthProcessorTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchHealthProcessorTest.cs
@@ -6,6 +6,7 @@
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Scoring;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Tests
{
@@ -25,18 +26,20 @@ public class CatchHealthProcessorTest
[TestCaseSource(nameof(test_cases))]
public void TestFailAfterMinResult(CatchHitObject hitObject, double startingHealth, bool failExpected)
{
- var healthProcessor = new CatchHealthProcessor(0);
- healthProcessor.ApplyBeatmap(new CatchBeatmap
- {
- HitObjects = { hitObject }
- });
- healthProcessor.Health.Value = startingHealth;
-
+ var beatmap = new CatchBeatmap { HitObjects = { hitObject } };
var result = new CatchJudgementResult(hitObject, hitObject.CreateJudgement());
result.Type = result.Judgement.MinResult;
+
+ var healthProcessor = new CatchHealthProcessor(0);
+ healthProcessor.ApplyBeatmap(beatmap);
+ healthProcessor.Health.Value = startingHealth;
healthProcessor.ApplyResult(result);
- Assert.That(healthProcessor.HasFailed, Is.EqualTo(failExpected));
+ var failProcessor = new FailProcessor(healthProcessor);
+ failProcessor.ApplyBeatmap(beatmap);
+ failProcessor.ApplyResult(result);
+
+ Assert.That(failProcessor.HasFailed, Is.EqualTo(failExpected));
}
[TestCaseSource(nameof(test_cases))]
|
I'm not super opposed to this mind. If it stops there being a god That said, in the provided diff, why does |
RFC. I'm PRing this for easier team review and to make sure that we're all on-board before I continue working on it. The intention is to replace #28825, following the commentary that I provided in that PR.
Please do not review this commit-by-commit as it is not in a final or clean state, and instead look at the full diff.
The
HasFailed
property has been moved toJudgementProcessor
, and any such class can cause the fail. The immediate reason for this is becauseModAccuracyChallenge
triggers fail based onScoreProcessor
parameters, which are updated afterHealthProcessor
.One way around this may be to provide helper methods that compute certain values (in this case Accuracy + MaximumAccuracy) given a result, without actually changing said values inside
ScoreProcessor
. I backed out of this path because I'm not sure what the scope would be - just accuracy, or score too? combo? highestcombo? maximum remaining achievable combo? The scope seems endless.As in #28825,
IApplicableFailOverride
has a new method providing an enum fail state -Block
,Allow
andForce
. Unlike that PR, the result parameter is not nullable (the method is only triggered on a result byJudgementProcessor
) and whether a fail is triggered at the end of the day is instead somewhat of a decision tree (seeJudgementProcessor.meetsAnyFailCondition()
).