mirror of
https://github.com/ppy/osu.git
synced 2026-05-17 01:03:15 +08:00
131f828e6a
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
191 lines
6.9 KiB
C#
191 lines
6.9 KiB
C#
// 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 System;
|
|
using osu.Game.Beatmaps;
|
|
using osu.Game.Rulesets.Scoring;
|
|
|
|
namespace osu.Game.Rulesets.Mania.Scoring
|
|
{
|
|
public class ManiaHitWindows : HitWindows
|
|
{
|
|
public static readonly DifficultyRange PERFECT_WINDOW_RANGE = new DifficultyRange(22.4D, 19.4D, 13.9D);
|
|
private static readonly DifficultyRange great_window_range = new DifficultyRange(64, 49, 34);
|
|
private static readonly DifficultyRange good_window_range = new DifficultyRange(97, 82, 67);
|
|
private static readonly DifficultyRange ok_window_range = new DifficultyRange(127, 112, 97);
|
|
private static readonly DifficultyRange meh_window_range = new DifficultyRange(151, 136, 121);
|
|
private static readonly DifficultyRange miss_window_range = new DifficultyRange(188, 173, 158);
|
|
|
|
private double speedMultiplier = 1;
|
|
|
|
/// <summary>
|
|
/// Multiplier used to compensate for the playback speed of the track speeding up or slowing down.
|
|
/// The goal of this multiplier is to keep hit windows independent of track speed.
|
|
/// <list type="bullet">
|
|
/// <item>When the track speed is above 1, the hit window ranges are multiplied by <see cref="SpeedMultiplier"/>, because the time elapses faster.</item>
|
|
/// <item>When the track speed is below 1, the hit window ranges are also multiplied by <see cref="SpeedMultiplier"/>, because the time elapses slower.</item>
|
|
/// </list>
|
|
/// </summary>
|
|
public double SpeedMultiplier
|
|
{
|
|
get => speedMultiplier;
|
|
set
|
|
{
|
|
speedMultiplier = value;
|
|
updateWindows();
|
|
}
|
|
}
|
|
|
|
private double difficultyMultiplier = 1;
|
|
|
|
/// <summary>
|
|
/// Multiplier used to make the gameplay more or less difficult.
|
|
/// <list type="bullet">
|
|
/// <item>When the <see cref="DifficultyMultiplier"/> is above 1, the hit windows decrease to make the gameplay harder.</item>
|
|
/// <item>When the <see cref="DifficultyMultiplier"/> is below 1, the hit windows increase to make the gameplay easier.</item>
|
|
/// </list>
|
|
/// </summary>
|
|
public double DifficultyMultiplier
|
|
{
|
|
get => difficultyMultiplier;
|
|
set
|
|
{
|
|
difficultyMultiplier = value;
|
|
updateWindows();
|
|
}
|
|
}
|
|
|
|
private double totalMultiplier => speedMultiplier / difficultyMultiplier;
|
|
|
|
private double overallDifficulty;
|
|
|
|
private bool classicModActive;
|
|
|
|
public bool ClassicModActive
|
|
{
|
|
get => classicModActive;
|
|
set
|
|
{
|
|
classicModActive = value;
|
|
updateWindows();
|
|
}
|
|
}
|
|
|
|
private bool scoreV2Active;
|
|
|
|
public bool ScoreV2Active
|
|
{
|
|
get => scoreV2Active;
|
|
set
|
|
{
|
|
scoreV2Active = value;
|
|
updateWindows();
|
|
}
|
|
}
|
|
|
|
private bool isConvert;
|
|
|
|
public bool IsConvert
|
|
{
|
|
get => isConvert;
|
|
set
|
|
{
|
|
isConvert = value;
|
|
updateWindows();
|
|
}
|
|
}
|
|
|
|
private double perfect;
|
|
private double great;
|
|
private double good;
|
|
private double ok;
|
|
private double meh;
|
|
private double miss;
|
|
|
|
public override bool IsHitResultAllowed(HitResult result)
|
|
{
|
|
switch (result)
|
|
{
|
|
case HitResult.Perfect:
|
|
case HitResult.Great:
|
|
case HitResult.Good:
|
|
case HitResult.Ok:
|
|
case HitResult.Meh:
|
|
case HitResult.Miss:
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public override void SetDifficulty(double difficulty)
|
|
{
|
|
overallDifficulty = difficulty;
|
|
updateWindows();
|
|
}
|
|
|
|
private void updateWindows()
|
|
{
|
|
if (ClassicModActive && !ScoreV2Active)
|
|
{
|
|
if (IsConvert)
|
|
{
|
|
perfect = Math.Floor(16 * totalMultiplier) + 0.5;
|
|
great = Math.Floor((Math.Round(overallDifficulty) > 4 ? 34 : 47) * totalMultiplier) + 0.5;
|
|
good = Math.Floor((Math.Round(overallDifficulty) > 4 ? 67 : 77) * totalMultiplier) + 0.5;
|
|
ok = Math.Floor(97 * totalMultiplier) + 0.5;
|
|
meh = Math.Floor(121 * totalMultiplier) + 0.5;
|
|
miss = Math.Floor(158 * totalMultiplier) + 0.5;
|
|
}
|
|
else
|
|
{
|
|
double invertedOd = Math.Clamp(10 - overallDifficulty, 0, 10);
|
|
|
|
perfect = Math.Floor(16 * totalMultiplier) + 0.5;
|
|
great = Math.Floor((34 + 3 * invertedOd) * totalMultiplier) + 0.5;
|
|
good = Math.Floor((67 + 3 * invertedOd) * totalMultiplier) + 0.5;
|
|
ok = Math.Floor((97 + 3 * invertedOd) * totalMultiplier) + 0.5;
|
|
meh = Math.Floor((121 + 3 * invertedOd) * totalMultiplier) + 0.5;
|
|
miss = Math.Floor((158 + 3 * invertedOd) * totalMultiplier) + 0.5;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
perfect = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(overallDifficulty, PERFECT_WINDOW_RANGE) * totalMultiplier) + 0.5;
|
|
great = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(overallDifficulty, great_window_range) * totalMultiplier) + 0.5;
|
|
good = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(overallDifficulty, good_window_range) * totalMultiplier) + 0.5;
|
|
ok = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(overallDifficulty, ok_window_range) * totalMultiplier) + 0.5;
|
|
meh = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(overallDifficulty, meh_window_range) * totalMultiplier) + 0.5;
|
|
miss = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(overallDifficulty, miss_window_range) * totalMultiplier) + 0.5;
|
|
}
|
|
}
|
|
|
|
public override double WindowFor(HitResult result)
|
|
{
|
|
switch (result)
|
|
{
|
|
case HitResult.Perfect:
|
|
return perfect;
|
|
|
|
case HitResult.Great:
|
|
return great;
|
|
|
|
case HitResult.Good:
|
|
return good;
|
|
|
|
case HitResult.Ok:
|
|
return ok;
|
|
|
|
case HitResult.Meh:
|
|
return meh;
|
|
|
|
case HitResult.Miss:
|
|
return miss;
|
|
|
|
default:
|
|
throw new ArgumentOutOfRangeException(nameof(result), result, null);
|
|
}
|
|
}
|
|
}
|
|
}
|