Closes https://github.com/ppy/osu/issues/36490.
While I'm out here already taking heat for deleting mod combinations let
me do more of that.
The main problem with the mod combination here, again, is that the
application of the mods does not commute.
- The current behaviour is that TP is applied first, then DA. This is,
again, "enforced" by the mod select overlay implicitly enforcing order
of the mod instances in the global mod bindable to match the display
order of mods inside it.
Even this doesn't "work" correctly as is, because as the bug reporter
points out, if they throw on DA with no changes expecting the map's
default AR to be applied, it still gets halved. This is because DA works
in a way wherein if you don't touch the AR slider, DA does not touch AR.
Which means that the DA slider should *really* be at *half* of the map's
base AR by default in this case because TP is active. How do you program
this?
- The *alternative* behaviour would be that DA is applied first, then
TP. This in turn would mean that the effective range of AR adjustment
offered by DA when TP is active would be halved to [0, 5] (or [-5, 5.5]
with extended ranges). How do you program this?
The above is just client-side concerns, while leaving out the other
giant concern, which is "how do you get every single place in the game
that may want to apply mods to a beatmap to apply them *in the same
order*?". Then extend that to server-side components, then extend that
to every external library that may want to re-implement SR/PP
calculations, etc. etc.
One additional remark:
What the bug reporter *did not* say however, but I am saying, is that
there's an elephant in the room, and that is the Easy mod, which *also*
changes AR, but also *happens* to apply commutatively with Target
Practice simply because both mods are implemented to halve the AR, which
means that the order of application doesn't matter. If I were *really*
bent on being a bad guy and just deleting mod combinations
indiscriminately, I'd delete that one as well. But I'm not doing that.
Effectively closing https://github.com/ppy/osu/issues/21471 as a
wontfix.
The issue is demonstrated by the following test case:
<details>
<summary>patch</summary>
```diff
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFreezeFrame.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFreezeFrame.cs
index 31498295da..36b4fe5122 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFreezeFrame.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFreezeFrame.cs
@@ -55,5 +55,49 @@ public void TestSkipToFirstSpinnerNotSuppressed()
PassCondition = () => Player.GameplayClockContainer.GameplayStartTime > 0
});
}
+
+ [Test]
+ public void TestFreezeFrameAppliedBeforeHidden()
+ {
+ CreateModTest(new ModTestData
+ {
+ Mods =
+ [
+ new OsuModFreezeFrame(),
+ new OsuModHidden(),
+ ],
+ CreateBeatmap = () => new OsuBeatmap
+ {
+ HitObjects =
+ {
+ new HitCircle { StartTime = 3000, Position = OsuPlayfield.BASE_SIZE / 2, NewCombo = true },
+ new HitCircle { StartTime = 5000, Position = OsuPlayfield.BASE_SIZE / 2 },
+ }
+ },
+ PassCondition = () => ((HitCircle)Player.GameplayState.Beatmap.HitObjects[1]).TimeFadeIn == 480
+ });
+ }
+
+ [Test]
+ public void TestFreezeFrameAppliedAfterHidden()
+ {
+ CreateModTest(new ModTestData
+ {
+ Mods =
+ [
+ new OsuModHidden(),
+ new OsuModFreezeFrame(),
+ ],
+ CreateBeatmap = () => new OsuBeatmap
+ {
+ HitObjects =
+ {
+ new HitCircle { StartTime = 3000, Position = OsuPlayfield.BASE_SIZE / 2, NewCombo = true },
+ new HitCircle { StartTime = 5000, Position = OsuPlayfield.BASE_SIZE / 2 },
+ }
+ },
+ PassCondition = () => ((HitCircle)Player.GameplayState.Beatmap.HitObjects[1]).TimeFadeIn == 480
+ });
+ }
}
}
```
</details>
The reason that this is happening is a data dependency. Freeze Frame
modifies `TimePreempt` of hitobjects:
https://github.com/ppy/osu/blob/54c0b2c20c3209f1f17be81b8883ef66ec8a83bb/osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs#L53
while Hidden uses `TimePreempt` to set `TimeFadeIn`:
https://github.com/ppy/osu/blob/54c0b2c20c3209f1f17be81b8883ef66ec8a83bb/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs#L45
Therefore the final value of `TimeFadeIn` with these two mods active
depends on the order of application.
The reason why I'm bothering to do this is that I was pinged from the PP
development server about this again in the context of some ongoing
'rework' and I wish to get ahead of this before it becomes a bigger
problem than it already is.
The current order of application of these mods as done in `Player` is
constant, but essentially undefined. I'm not even sure what even
enforces the current order. It's currently Hidden, then Freeze Frame. If
I were to guess, the thing "enforcing" (insert 400 tonne solid lead air
quotes) it is probably the mod select overlay with its "I need to own
all of the mod instances in the global bindable" substitution logic.
I'm already getting pushback for this from the PP server crowd who are
attempting to justify the current "behaviour" by saying that the player
base wants this or by saying that it's already broken so it should
remain that way forever. I am however not willing to accept
[stable-taiko-tier stupidity
again](https://github.com/ppy/osu/pull/27136#issuecomment-1957055402)
and am not willing to accept the complexity this would invite everywhere
else.
I do not see any other easy way of fixing this problem at this point in
time. I had hoped that I could inline the `TimePreempt` read in
`OsuModHidden`, but it's not that easy because it's set in multiple
places.
Existing HDFF scores would probably need to be delisted from the
leaderboards. I'm not even sure I can get myself to pretend to care.
TC is a mod that always increases difficulty and is quite similar to HD.
Given we even have diffcalc/pp considerations for it it's time to move
it to the category it belongs.
This doesn't cover any other mods that might need reshuffling too
because TC is the only one that has actual impact on difficulty-based
leaderboards (as in pp) and some people are actively playing it for the
difficulty increase and not just as a fun gimmick.
After a quick search turns out it was difficulty increasing from the
start but was moved to fun in review
https://github.com/ppy/osu/pull/3569#discussion_r300085523 but I don't
really agree with that.
Also as far as I know multimods don't do anything anymore?.. I've put it
into HD multimod for consistency, but can move it to be separate if you
want
Co-authored-by: Dan Balasescu <smoogipoo@smgi.me>
Because the flashlight is made to be scaled by playfield, there are
constant scale factors applied somewhere in the
`PlayfieldAdjustmentContainer` which needs to be reflected in the
flashlight size to keep the size the same.
The factor is specifically 1.6x, computed in {Osu,Catch}PlayfieldAdjustmentContainer.ScalingContainer`.
More generally, I've deduced these factors by logging the difference
between the `flashlightSize` before and after b78abe2f.
Make isSpinnableTime public in SpinnerRotationTracker and use it to set Tracking in OsuModSpunOut.
Tracking was previously set to true, causing the "spins per minute" to appear immediately when the spinner appeared.
See https://github.com/ppy/osu/issues/29720,
https://discord.com/channels/188630481301012481/188630652340404224/1334294048541904906.
This removes the tooltip due to being zero or negative information, and
also changes the description of the setting to not contain the word
"mirror", which will hopefully quash the "this is where I would place a
mirror to my screen to achieve what I want" interpretation which seems
to be a minority interpretation.
The first time this was complained about I figured this was probably a
one guy issue, but now it's happened twice, and I never want to see this
conversation again.