2019-02-18 13:46:32 +08:00
// 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.Rulesets.Catch.Difficulty.Preprocessing ;
using osu.Game.Rulesets.Difficulty.Preprocessing ;
using osu.Game.Rulesets.Difficulty.Skills ;
2021-02-06 12:06:16 +08:00
using osu.Game.Rulesets.Mods ;
2019-02-18 13:46:32 +08:00
namespace osu.Game.Rulesets.Catch.Difficulty.Skills
{
2021-08-17 06:14:29 +08:00
public class Movement : StrainDecaySkill
2019-02-18 13:46:32 +08:00
{
private const float absolute_player_positioning_error = 16f ;
private const float normalized_hitobject_radius = 41.0f ;
2019-04-01 10:00:26 +08:00
private const double direction_change_bonus = 21.0 ;
2019-02-18 13:46:32 +08:00
2024-08-05 21:33:42 +08:00
protected override double SkillMultiplier = > 1 ;
2019-02-18 13:46:32 +08:00
protected override double StrainDecayBase = > 0.2 ;
protected override double DecayWeight = > 0.94 ;
2021-04-03 17:47:39 +08:00
protected override int SectionLength = > 750 ;
2020-03-13 11:43:01 +08:00
protected readonly float HalfCatcherWidth ;
2019-02-20 13:13:54 +08:00
private float? lastPlayerPosition ;
2019-02-16 10:11:31 +08:00
private float lastDistanceMoved ;
2019-03-06 13:36:30 +08:00
private double lastStrainTime ;
2019-02-16 10:11:31 +08:00
2021-02-21 15:24:27 +08:00
/// <summary>
/// The speed multiplier applied to the player's catcher.
/// </summary>
private readonly double catcherSpeedMultiplier ;
2021-06-03 14:09:37 +08:00
public Movement ( Mod [ ] mods , float halfCatcherWidth , double clockRate )
2021-02-06 12:06:16 +08:00
: base ( mods )
2020-03-13 11:43:01 +08:00
{
HalfCatcherWidth = halfCatcherWidth ;
2021-02-21 15:24:27 +08:00
2021-06-03 14:09:37 +08:00
// In catch, clockrate adjustments do not only affect the timings of hitobjects,
2021-02-21 15:24:27 +08:00
// but also the speed of the player's catcher, which has an impact on difficulty
2021-06-03 13:44:28 +08:00
// TODO: Support variable clockrates caused by mods such as ModTimeRamp
// (perhaps by using IApplicableToRate within the CatchDifficultyHitObject constructor to set a catcher speed for each object before processing)
2021-06-03 14:09:37 +08:00
catcherSpeedMultiplier = clockRate ;
2020-03-13 11:43:01 +08:00
}
2019-02-18 13:46:32 +08:00
protected override double StrainValueOf ( DifficultyHitObject current )
{
var catchCurrent = ( CatchDifficultyHitObject ) current ;
2020-06-03 15:48:44 +08:00
lastPlayerPosition ? ? = catchCurrent . LastNormalizedPosition ;
2019-02-20 13:13:54 +08:00
2019-11-20 20:19:49 +08:00
float playerPosition = Math . Clamp (
2019-02-20 13:13:54 +08:00
lastPlayerPosition . Value ,
2019-02-16 10:11:31 +08:00
catchCurrent . NormalizedPosition - ( normalized_hitobject_radius - absolute_player_positioning_error ) ,
catchCurrent . NormalizedPosition + ( normalized_hitobject_radius - absolute_player_positioning_error )
) ;
2019-02-20 13:13:54 +08:00
float distanceMoved = playerPosition - lastPlayerPosition . Value ;
2019-02-18 13:46:32 +08:00
2021-02-21 15:24:27 +08:00
double weightedStrainTime = catchCurrent . StrainTime + 13 + ( 3 / catcherSpeedMultiplier ) ;
2019-03-06 13:36:30 +08:00
2019-04-01 10:00:26 +08:00
double distanceAddition = ( Math . Pow ( Math . Abs ( distanceMoved ) , 1.3 ) / 510 ) ;
2019-03-06 13:36:30 +08:00
double sqrtStrain = Math . Sqrt ( weightedStrainTime ) ;
2019-02-18 13:46:32 +08:00
2019-04-02 06:28:04 +08:00
double edgeDashBonus = 0 ;
2019-02-18 13:46:32 +08:00
2020-04-08 11:20:46 +08:00
// Direction change bonus.
2019-02-16 10:11:31 +08:00
if ( Math . Abs ( distanceMoved ) > 0.1 )
2019-02-18 13:46:32 +08:00
{
2019-02-16 10:11:31 +08:00
if ( Math . Abs ( lastDistanceMoved ) > 0.1 & & Math . Sign ( distanceMoved ) ! = Math . Sign ( lastDistanceMoved ) )
2019-02-18 13:46:32 +08:00
{
2019-04-02 06:28:04 +08:00
double bonusFactor = Math . Min ( 50 , Math . Abs ( distanceMoved ) ) / 50 ;
2020-04-08 11:20:46 +08:00
double antiflowFactor = Math . Max ( Math . Min ( 70 , Math . Abs ( lastDistanceMoved ) ) / 70 , 0.38 ) ;
2019-02-18 13:46:32 +08:00
2020-04-08 11:20:46 +08:00
distanceAddition + = direction_change_bonus / Math . Sqrt ( lastStrainTime + 16 ) * bonusFactor * antiflowFactor * Math . Max ( 1 - Math . Pow ( weightedStrainTime / 1000 , 3 ) , 0 ) ;
2019-02-18 13:46:32 +08:00
}
// Base bonus for every movement, giving some weight to streams.
2019-04-01 10:00:26 +08:00
distanceAddition + = 12.5 * Math . Min ( Math . Abs ( distanceMoved ) , normalized_hitobject_radius * 2 ) / ( normalized_hitobject_radius * 6 ) / sqrtStrain ;
2019-02-18 13:46:32 +08:00
}
2020-04-08 11:20:46 +08:00
// Bonus for edge dashes.
2020-07-01 23:21:45 +08:00
if ( catchCurrent . LastObject . DistanceToHyperDash < = 20.0f )
2019-02-18 13:46:32 +08:00
{
2019-02-16 10:11:31 +08:00
if ( ! catchCurrent . LastObject . HyperDash )
2019-04-02 06:28:04 +08:00
edgeDashBonus + = 5.7 ;
2019-02-16 10:11:31 +08:00
else
{
// After a hyperdash we ARE in the correct position. Always!
playerPosition = catchCurrent . NormalizedPosition ;
}
2019-02-18 13:46:32 +08:00
2021-02-21 15:24:27 +08:00
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
2019-03-06 13:36:30 +08:00
}
2019-02-16 10:11:31 +08:00
lastPlayerPosition = playerPosition ;
lastDistanceMoved = distanceMoved ;
2019-03-06 13:36:30 +08:00
lastStrainTime = catchCurrent . StrainTime ;
2019-02-16 10:11:31 +08:00
2019-03-06 13:36:30 +08:00
return distanceAddition / weightedStrainTime ;
2019-02-18 13:46:32 +08:00
}
}
}