2019-01-24 08:43:03 +00: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.
2018-04-13 09:19:50 +00:00
using System ;
2019-02-12 07:03:28 +00:00
using osu.Game.Rulesets.Difficulty.Preprocessing ;
2021-02-06 04:06:16 +00:00
using osu.Game.Rulesets.Mods ;
2018-05-15 08:36:29 +00:00
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing ;
2019-02-18 05:58:33 +00:00
using osu.Game.Rulesets.Osu.Objects ;
2018-04-13 09:19:50 +00:00
2018-05-15 08:36:29 +00:00
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
2018-04-13 09:19:50 +00:00
{
/// <summary>
/// Represents the skill required to correctly aim at every object in the map with a uniform CircleSize and normalized distances.
/// </summary>
2021-06-14 17:18:49 +00:00
public class Aim : OsuStrainSkill
2018-04-13 09:19:50 +00:00
{
2021-02-06 04:06:16 +00:00
public Aim ( Mod [ ] mods )
: base ( mods )
{
}
2021-09-25 03:02:33 +00:00
protected override int HistoryLength = > 2 ;
2021-10-13 15:41:24 +00:00
private const double wide_angle_multiplier = 1.5 ;
2021-10-27 16:30:17 +00:00
private const double acute_angle_multiplier = 2.0 ;
2021-10-22 17:21:34 +00:00
private const double slider_multiplier = 1.5 ;
2021-11-06 21:55:47 +00:00
private const double vel_change_multiplier = 0.75 ;
2018-04-13 09:19:50 +00:00
2021-08-17 13:39:18 +00:00
private double currentStrain = 1 ;
2018-04-13 09:19:50 +00:00
2021-10-13 15:41:24 +00:00
private double skillMultiplier = > 23.25 ;
2021-08-17 13:39:18 +00:00
private double strainDecayBase = > 0.15 ;
2021-09-25 03:02:33 +00:00
2021-10-03 17:36:34 +00:00
private double strainValueOf ( DifficultyHitObject current )
2018-12-08 06:01:26 +00:00
{
2021-10-21 17:21:34 +00:00
if ( current . BaseObject is Spinner | | Previous . Count < = 1 | | Previous [ 0 ] . BaseObject is Spinner )
2019-02-18 05:58:33 +00:00
return 0 ;
2021-09-25 03:02:33 +00:00
var osuCurrObj = ( OsuDifficultyHitObject ) current ;
2021-11-02 14:51:09 +00:00
var osuLastObj = ( OsuDifficultyHitObject ) Previous [ 0 ] ;
var osuLastLastObj = ( OsuDifficultyHitObject ) Previous [ 1 ] ;
2018-12-24 03:41:04 +00:00
2021-11-02 15:04:19 +00:00
// Calculate the velocity to the current hitobject, which starts with a base distance / time assuming the last object is a hitcircle.
double currVelocity = osuCurrObj . JumpDistance / osuCurrObj . StrainTime ;
2019-01-29 07:35:20 +00:00
2021-11-02 15:04:19 +00:00
// But if the last object is a slider, then we extend the travel velocity through the slider into the current object.
if ( osuLastObj . BaseObject is Slider )
2021-10-13 15:41:24 +00:00
{
2021-11-02 15:04:19 +00:00
double movementVelocity = osuCurrObj . MovementDistance / osuCurrObj . MovementTime ; // calculate the movement velocity from slider end to current object
double travelVelocity = osuCurrObj . TravelDistance / osuCurrObj . TravelTime ; // calculate the slider velocity from slider head to slider end.
2021-10-13 15:41:24 +00:00
currVelocity = Math . Max ( currVelocity , movementVelocity + travelVelocity ) ; // take the larger total combined velocity.
}
2019-02-12 07:03:28 +00:00
2021-11-02 15:04:19 +00:00
// As above, do the same for the previous hitobject.
double prevVelocity = osuLastObj . JumpDistance / osuLastObj . StrainTime ;
2019-01-29 07:35:20 +00:00
2021-11-02 14:51:09 +00:00
if ( osuLastLastObj . BaseObject is Slider )
2021-09-25 03:02:33 +00:00
{
2021-11-02 14:51:09 +00:00
double movementVelocity = osuLastObj . MovementDistance / osuLastObj . MovementTime ;
double travelVelocity = osuLastObj . TravelDistance / osuLastObj . TravelTime ;
2021-10-13 15:41:24 +00:00
prevVelocity = Math . Max ( prevVelocity , movementVelocity + travelVelocity ) ;
}
2019-02-12 07:03:28 +00:00
2021-11-06 22:27:58 +00:00
double wideAngleBonus = 0 ;
double acuteAngleBonus = 0 ;
2021-10-13 16:25:16 +00:00
double sliderBonus = 0 ;
2021-10-21 17:07:56 +00:00
double velChangeBonus = 0 ;
2019-01-29 07:35:20 +00:00
2021-10-13 15:41:24 +00:00
double aimStrain = currVelocity ; // Start strain with regular velocity.
2019-02-12 07:03:28 +00:00
2021-11-02 14:51:09 +00:00
if ( Math . Max ( osuCurrObj . StrainTime , osuLastObj . StrainTime ) < 1.25 * Math . Min ( osuCurrObj . StrainTime , osuLastObj . StrainTime ) ) // If rhythms are the same.
2021-09-25 03:02:33 +00:00
{
2021-11-02 14:51:09 +00:00
if ( osuCurrObj . Angle ! = null & & osuLastObj . Angle ! = null & & osuLastLastObj . Angle ! = null )
2018-12-21 13:52:27 +00:00
{
2021-10-13 15:41:24 +00:00
double currAngle = osuCurrObj . Angle . Value ;
2021-10-27 16:30:17 +00:00
double lastAngle = osuLastObj . Angle . Value ;
2021-11-02 15:16:33 +00:00
double lastLastAngle = osuLastLastObj . Angle . Value ;
2021-09-25 03:02:33 +00:00
// Rewarding angles, take the smaller velocity as base.
2021-11-06 22:27:58 +00:00
double angleBonus = Math . Min ( currVelocity , prevVelocity ) ;
2019-02-12 07:03:28 +00:00
2021-11-06 22:27:58 +00:00
wideAngleBonus = calcWideAngleBonus ( currAngle ) ;
acuteAngleBonus = calcAcuteAngleBonus ( currAngle ) ;
2021-09-25 03:02:33 +00:00
2021-10-13 15:41:24 +00:00
if ( osuCurrObj . StrainTime > 100 ) // Only buff deltaTime exceeding 300 bpm 1/2.
2021-09-25 03:02:33 +00:00
acuteAngleBonus = 0 ;
else
2021-11-02 14:33:51 +00:00
{
2021-11-02 15:16:33 +00:00
acuteAngleBonus * = calcAcuteAngleBonus ( lastAngle ) // Multiply by previous angle, we don't want to buff unless this is a wiggle type pattern.
2021-11-02 14:33:51 +00:00
* Math . Min ( angleBonus , 125 / osuCurrObj . StrainTime ) // The maximum velocity we buff is equal to 125 / strainTime
* Math . Pow ( Math . Sin ( Math . PI / 2 * Math . Min ( 1 , ( 100 - osuCurrObj . StrainTime ) / 25 ) ) , 2 ) // scale buff from 150 bpm 1/4 to 200 bpm 1/4
* Math . Pow ( Math . Sin ( Math . PI / 2 * ( Math . Clamp ( osuCurrObj . JumpDistance , 50 , 100 ) - 50 ) / 50 ) , 2 ) ; // Buff distance exceeding 50 (radius) up to 100 (diameter).
}
2021-09-25 03:02:33 +00:00
2021-11-02 15:16:33 +00:00
wideAngleBonus * = angleBonus * ( 1 - Math . Min ( wideAngleBonus , Math . Pow ( calcWideAngleBonus ( lastAngle ) , 3 ) ) ) ; // Penalize wide angles if they're repeated, reducing the penalty as the lastAngle gets more acute.
acuteAngleBonus * = 0.5 + 0.5 * ( 1 - Math . Min ( acuteAngleBonus , Math . Pow ( calcAcuteAngleBonus ( lastLastAngle ) , 3 ) ) ) ; // Penalize acute angles if they're repeated, reducing the penalty as the lastLastAngle gets more obtuse.
2021-09-25 03:27:07 +00:00
}
2018-12-21 05:52:43 +00:00
}
2021-09-25 03:02:33 +00:00
2021-10-21 17:07:56 +00:00
if ( Math . Max ( prevVelocity , currVelocity ) ! = 0 )
2021-09-25 03:02:33 +00:00
{
2021-11-06 22:27:58 +00:00
prevVelocity = ( osuLastObj . JumpDistance + osuLastObj . TravelDistance ) / osuLastObj . StrainTime ; // We want to use the average velocity over the whole object when awarding differences, not the individual jump and slider path velocities.
2021-10-22 17:18:34 +00:00
currVelocity = ( osuCurrObj . JumpDistance + osuCurrObj . TravelDistance ) / osuCurrObj . StrainTime ;
2021-11-07 01:08:51 +00:00
double distRatio = Math . Pow ( Math . Sin ( Math . PI / 2 * Math . Abs ( prevVelocity - currVelocity ) / Math . Max ( prevVelocity , currVelocity ) ) , 2 ) ; // scale with ratio of difference compared to 0.5 * max dist.
2021-11-06 22:27:58 +00:00
double overlapVelocityBuff = Math . Min ( 125 / Math . Min ( osuCurrObj . StrainTime , osuLastObj . StrainTime ) , Math . Abs ( prevVelocity - currVelocity ) ) ; // reward for % distance up to 125 / strainTime for overlaps where velocity is still changing.
double nonOverlapVelocityBuff = Math . Abs ( prevVelocity - currVelocity ) // reward for % distance slowed down compared to previous, paying attention to not award overlap
2021-11-07 00:13:13 +00:00
* Math . Pow ( Math . Sin ( Math . PI / 2 * Math . Min ( 1 , Math . Min ( osuCurrObj . JumpDistance , osuLastObj . JumpDistance ) / 100 ) ) , 2 ) ; // do not award overlap
2021-11-06 22:27:58 +00:00
velChangeBonus = Math . Max ( overlapVelocityBuff , nonOverlapVelocityBuff ) * distRatio ; // choose larger distance, multiplied by ratio.
2018-12-21 05:52:43 +00:00
2021-11-03 15:54:49 +00:00
velChangeBonus * = Math . Pow ( Math . Min ( osuCurrObj . StrainTime , osuLastObj . StrainTime ) / Math . Max ( osuCurrObj . StrainTime , osuLastObj . StrainTime ) , 2 ) ; // penalize for rhythm changes.
2021-10-21 17:07:56 +00:00
}
2019-01-29 07:35:20 +00:00
2021-10-21 16:08:35 +00:00
if ( osuCurrObj . TravelTime ! = 0 )
2021-09-25 03:04:22 +00:00
{
2021-10-13 16:17:49 +00:00
sliderBonus = osuCurrObj . TravelDistance / osuCurrObj . TravelTime ; // add some slider rewards
2021-09-25 03:02:33 +00:00
}
2021-11-06 22:27:58 +00:00
aimStrain + = Math . Max ( acuteAngleBonus * acute_angle_multiplier , wideAngleBonus * wide_angle_multiplier + velChangeBonus * vel_change_multiplier ) ; // Add in acute angle bonus or wide angle bonus + velchange bonus, whichever is larger.
2021-10-13 16:17:49 +00:00
aimStrain + = sliderBonus * slider_multiplier ; // Add in additional slider velocity.
2021-09-25 03:02:33 +00:00
return aimStrain ;
}
2021-10-13 15:41:24 +00:00
private double calcWideAngleBonus ( double angle ) = > Math . Pow ( Math . Sin ( 3.0 / 4 * ( Math . Min ( 5.0 / 6 * Math . PI , Math . Max ( Math . PI / 6 , angle ) ) - Math . PI / 6 ) ) , 2 ) ;
2021-09-25 03:02:33 +00:00
2021-10-13 15:41:24 +00:00
private double calcAcuteAngleBonus ( double angle ) = > 1 - calcWideAngleBonus ( angle ) ;
2019-02-12 07:03:28 +00:00
private double applyDiminishingExp ( double val ) = > Math . Pow ( val , 0.99 ) ;
2021-08-17 13:39:18 +00:00
private double strainDecay ( double ms ) = > Math . Pow ( strainDecayBase , ms / 1000 ) ;
protected override double CalculateInitialStrain ( double time ) = > currentStrain * strainDecay ( time - Previous [ 0 ] . StartTime ) ;
2019-02-12 07:03:28 +00:00
2021-08-17 13:39:18 +00:00
protected override double StrainValueAt ( DifficultyHitObject current )
2021-09-25 03:02:33 +00:00
{
2021-08-17 13:39:18 +00:00
currentStrain * = strainDecay ( current . DeltaTime ) ;
2021-10-03 17:36:34 +00:00
currentStrain + = strainValueOf ( current ) * skillMultiplier ;
2021-09-25 03:02:33 +00:00
2021-08-17 13:39:18 +00:00
return currentStrain ;
2021-09-25 03:02:33 +00:00
}
2018-04-13 09:19:50 +00:00
}
}