This change refactors `GetAdjustedDisplayDifficulty()` and
`GetBeatmapAttributesToDisplay()` in two ways:
- Both methods now accept `IBeatmapInfo` instead of
`IBeatmapDifficultyInfo`. This is done in order to make mania key
count display to work, wherein `IBeatmapDifficultyInfo` is not enough
to calculate the final key count.
- `GetAdjustedDisplayDifficulty()` now applies all
`IApplicableToDifficulty` mods itself. I did this after noticing that
every real consumer of this method had to do that themselves for very
little reason.
In stable mania, Hard Rock and Easy mods do not work the same way as
they do on all of the rulesets. The difference is that mania HR and EZ,
rather than apply a multiplier to the map's original Overall Difficulty,
apply multipliers to *the durations of hit windows themselves*.
Prior to the last release, lazer was oblivious to this reality and just
treated mania HR / EZ as it did every other ruleset. Last release, for
the sake for gameplay parity across rulesets, the mods in question were
adjusted to match stable, but in the process, it started looking like HR
/ EZ did not change OD anymore.
The problem is that they do, but applying a multiplier to the map's OD
and applying a multiplier to the hit window duration is not the same
thing. The second thing is actually *much harsher* in magnitude, to the
point where applying HR to any map is almost guaranteed to exceed "the
effective OD" of 10, and applying EZ to any map is almost guaranteed to
result in "negative effective OD".
This change attempts to convey that reality by displaying "effective
OD", similar to what's already done in other rulesets when rate-changing
mods are active. Note that the values this will display *do not match*
stable *and that is correct*, because stable song select *lies* about
the actual impact on OD by just assuming it can treat all rulesets in
the same way.
---
Would close https://github.com/ppy/osu/issues/34150 I guess.
And yes I would like *all of the above* to land on the changelog if
possible if this is merged.
For further convincing that this makes any semblance of sense please see
the following: https://www.desmos.com/calculator/yigt7jycdv
The quirks in question being that lazer's hit windows in mania preceding
this change are used in stable *if and only if* Score V2 is active. If
Score V2 is *not* active, stable has two disparate other sets of hit
window ranges, dependent on whether the beatmap is a convert or not.
With this commit, those hit windows are used in lazer when the Classic
mod is active.
Open points for discussion would be:
- What does this mean for plays already set on lazer using the Classic
mod? Are there even enough of them to care about? Also, on `master`
the Classic mod does precisely nothing, so maybe such scores should
just have Classic mod stripped from them?
- What does this mean for the mod multiplier of Classic in mania? (I don't
expect an answer to this one.)
This is a "two-birds-with-one-stone" change, which addresses both
https://github.com/ppy/osu/issues/28744 and
https://github.com/ppy/osu/issues/11311 simultaneously.
- The replay stability issue caused by time instants being rounded to
nearest integer is fixed by this, because flooring and
subtracting/adding 0.5 from the hit window threshold makes it
impossible for a judgement to switch to anything else after replay
rounding is applied - all hit windows are always a full integer plus
0.5 milliseconds, which immunizes them to rounding-to-full-ms issues.
- The direction of applying the 0.5 adjustment additionally fixes the
disparity with stable - in osu! and taiko 0.5 is subtracted as
hit window ranges in those rulesets are exclusive on stable, while in
mania 0.5 is added, as the hit window ranges there are *inclusive* on
stable.
As should be obvious, this materially changes hit windows. To what degree
this is a *significant* change is up for discussion; I would say "no"
since hitting half a millisecond changes would require 2000fps input
recording, and we're still timestamping inputs using the update thread's
clock, that gives a 1ms resolution at best.
In the worst case, in osu! and taiko, this can change a hit window range
by 1.5ms (e.g. 300.9ms -> floored to 300ms -> 299.5ms after subtraction
of the half). It's more than the best-case resolution of input
timestamps, but not by much. Considering how cleanly this resolves the
issues in question, I see it as an acceptable tradeoff.
This change pulls back a significant degree of overspecialisation and
rigidity in the class structure of `HitWindows` to make subsequent
changes to hit windows, whose purpose is to improve replay playback
accuracy, possible to do cleanly.
Notably:
- `HitWindows` is full abstract now. In a few use cases, and as a
reference for ruleset implementors, `DefaultHitWindows` is provided as
a separate class instead.
This fixes the weirdness wherein `HitWindows` always declared 6 fields
for result types but some of them would never be set to a non-zero
value or read.
- `HitWindow.GetRanges()` is deleted because it is overspecialised and
prevents being able to adjust hitwindows by ±0.5ms cleanly which will
be required later.
The fallout of this is that the assertion that used `GetRanges()` in
the `HitWindows` ctor must use something else now, and the closest
thing to it was `GetAllAvailableWindows()`, which didn't return
the miss window - so I made it return the miss window and fixed the
one consumer that didn't want it (bar hit error meter) to skip it.
- Diff also contains some clean-up around `DifficultyRange` to unify
handling of it.
Closes https://github.com/ppy/osu/issues/33465 probably.
This reverts the replay frame de-duplication logic to what it was before
https://github.com/ppy/osu/pull/33148#discussion_r2091549388.
I don't have good reproduction steps. I tried to write a test case for
this that isn't just "press and release a key in the same frame",
thinking that maybe there was some loophole in the osu! touch input
mapper that may produce this situation artificially, but I could not in
many configurations. So I have to assume that this just *can happen*
organically.