mirror of
https://github.com/ppy/osu
synced 2025-01-09 23:59:44 +00:00
323e4ac26b
In catch, rate adjustment mods do not only affect the timings of hitobjects, but also the speed of the player's catcher. This catcher speed change has an impact on difficulty which is currently accounted for by using the clockrate directly in calculations. Semantically this is a bad idea because clockrate adjustments are supposed to be fully accounted for in DifficultyHitObjects, but passing clockrate here for the purpose of being used as catcher speed doesn't make much sense, especially since it is copied in every DifficultyHitObject despite being the same value. It makes more sense to account for this catch specific impact by handling rate adjustment mods in a catch specific way, or more specifically in a Movement skill specific way.
104 lines
4.3 KiB
C#
104 lines
4.3 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 System.Linq;
|
|
using osu.Game.Rulesets.Catch.Difficulty.Preprocessing;
|
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
|
using osu.Game.Rulesets.Difficulty.Skills;
|
|
using osu.Game.Rulesets.Mods;
|
|
|
|
namespace osu.Game.Rulesets.Catch.Difficulty.Skills
|
|
{
|
|
public class Movement : Skill
|
|
{
|
|
private const float absolute_player_positioning_error = 16f;
|
|
private const float normalized_hitobject_radius = 41.0f;
|
|
private const double direction_change_bonus = 21.0;
|
|
|
|
protected override double SkillMultiplier => 900;
|
|
protected override double StrainDecayBase => 0.2;
|
|
|
|
protected override double DecayWeight => 0.94;
|
|
|
|
protected readonly float HalfCatcherWidth;
|
|
|
|
private float? lastPlayerPosition;
|
|
private float lastDistanceMoved;
|
|
private double lastStrainTime;
|
|
|
|
/// <summary>
|
|
/// The speed multiplier applied to the player's catcher.
|
|
/// </summary>
|
|
private readonly double catcherSpeedMultiplier;
|
|
|
|
public Movement(Mod[] mods, float halfCatcherWidth)
|
|
: base(mods)
|
|
{
|
|
HalfCatcherWidth = halfCatcherWidth;
|
|
|
|
// In catch, rate adjustment mods do not only affect the timings of hitobjects,
|
|
// but also the speed of the player's catcher, which has an impact on difficulty
|
|
var rateAdjustMod = mods.FirstOrDefault(m => m is ModRateAdjust);
|
|
catcherSpeedMultiplier = (rateAdjustMod as ModRateAdjust)?.SpeedChange.Value ?? 1;
|
|
}
|
|
|
|
protected override double StrainValueOf(DifficultyHitObject current)
|
|
{
|
|
var catchCurrent = (CatchDifficultyHitObject)current;
|
|
|
|
lastPlayerPosition ??= catchCurrent.LastNormalizedPosition;
|
|
|
|
float playerPosition = Math.Clamp(
|
|
lastPlayerPosition.Value,
|
|
catchCurrent.NormalizedPosition - (normalized_hitobject_radius - absolute_player_positioning_error),
|
|
catchCurrent.NormalizedPosition + (normalized_hitobject_radius - absolute_player_positioning_error)
|
|
);
|
|
|
|
float distanceMoved = playerPosition - lastPlayerPosition.Value;
|
|
|
|
double weightedStrainTime = catchCurrent.StrainTime + 13 + (3 / catcherSpeedMultiplier);
|
|
|
|
double distanceAddition = (Math.Pow(Math.Abs(distanceMoved), 1.3) / 510);
|
|
double sqrtStrain = Math.Sqrt(weightedStrainTime);
|
|
|
|
double edgeDashBonus = 0;
|
|
|
|
// Direction change bonus.
|
|
if (Math.Abs(distanceMoved) > 0.1)
|
|
{
|
|
if (Math.Abs(lastDistanceMoved) > 0.1 && Math.Sign(distanceMoved) != Math.Sign(lastDistanceMoved))
|
|
{
|
|
double bonusFactor = Math.Min(50, Math.Abs(distanceMoved)) / 50;
|
|
double antiflowFactor = Math.Max(Math.Min(70, Math.Abs(lastDistanceMoved)) / 70, 0.38);
|
|
|
|
distanceAddition += direction_change_bonus / Math.Sqrt(lastStrainTime + 16) * bonusFactor * antiflowFactor * Math.Max(1 - Math.Pow(weightedStrainTime / 1000, 3), 0);
|
|
}
|
|
|
|
// Base bonus for every movement, giving some weight to streams.
|
|
distanceAddition += 12.5 * Math.Min(Math.Abs(distanceMoved), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtStrain;
|
|
}
|
|
|
|
// Bonus for edge dashes.
|
|
if (catchCurrent.LastObject.DistanceToHyperDash <= 20.0f)
|
|
{
|
|
if (!catchCurrent.LastObject.HyperDash)
|
|
edgeDashBonus += 5.7;
|
|
else
|
|
{
|
|
// After a hyperdash we ARE in the correct position. Always!
|
|
playerPosition = catchCurrent.NormalizedPosition;
|
|
}
|
|
|
|
distanceAddition *= 1.0 + edgeDashBonus * ((20 - catchCurrent.LastObject.DistanceToHyperDash) / 20) * Math.Pow((Math.Min(catchCurrent.StrainTime * catcherSpeedMultiplier, 265) / 265), 1.5); // Edge Dashes are easier at lower ms values
|
|
}
|
|
|
|
lastPlayerPosition = playerPosition;
|
|
lastDistanceMoved = distanceMoved;
|
|
lastStrainTime = catchCurrent.StrainTime;
|
|
|
|
return distanceAddition / weightedStrainTime;
|
|
}
|
|
}
|
|
}
|