mirror of
https://github.com/ppy/osu.git
synced 2026-05-18 14:50:54 +08:00
Move speed distance bonus to aim (#36168)
This doesn't solve _flow_ aim in any way, but makes it so that `Speed` doesn't have distance scaling (read as "aim") anymore which fixes some issues related to that like the length bonus behaving incorrectly, and in general `Speed` being an aim+tap skill instead of just a tapping skill --------- Co-authored-by: James Wilson <tsunyoku@gmail.com>
This commit is contained in:
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
private const int history_time_max = 5 * 1000; // 5 seconds
|
||||
private const int history_objects_max = 32;
|
||||
private const double rhythm_overall_multiplier = 1.0;
|
||||
private const double rhythm_ratio_multiplier = 17.0;
|
||||
private const double rhythm_ratio_multiplier = 30.0;
|
||||
|
||||
/// <summary>
|
||||
/// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current <see cref="OsuDifficultyHitObject"/>.
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// 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.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
{
|
||||
public static class SpeedAimEvaluator
|
||||
{
|
||||
private const double single_spacing_threshold = OsuDifficultyHitObject.NORMALISED_DIAMETER * 1.25; // 1.25 circles distance between centers
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the difficulty of aiming the current object, based on:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>distance between the previous and current object</description></item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public static double EvaluateDifficultyOf(DifficultyHitObject current)
|
||||
{
|
||||
if (current.BaseObject is Spinner)
|
||||
return 0;
|
||||
|
||||
var osuCurrObj = (OsuDifficultyHitObject)current;
|
||||
var osuPrevObj = current.Index > 0 ? (OsuDifficultyHitObject)current.Previous(0) : null;
|
||||
|
||||
double travelDistance = osuPrevObj?.LazyTravelDistance ?? 0;
|
||||
double distance = travelDistance + osuCurrObj.LazyJumpDistance;
|
||||
|
||||
// Cap distance at single_spacing_threshold
|
||||
distance = Math.Min(distance, single_spacing_threshold);
|
||||
|
||||
// Max distance bonus is 1 * `distance_multiplier` at single_spacing_threshold
|
||||
double distanceBonus = Math.Pow(distance / single_spacing_threshold, 3.95);
|
||||
|
||||
// Apply reduced small circle bonus because flow aim difficulty on small circles doesn't scale as hard as jumps
|
||||
distanceBonus *= Math.Sqrt(osuCurrObj.SmallCircleBonus);
|
||||
|
||||
double strain = distanceBonus * 1000 / osuCurrObj.AdjustedDeltaTime;
|
||||
|
||||
strain *= highBpmBonus(osuCurrObj.AdjustedDeltaTime);
|
||||
|
||||
return strain;
|
||||
}
|
||||
|
||||
private static double highBpmBonus(double ms) => 1 / (1 - Math.Pow(0.3, ms / 1000));
|
||||
}
|
||||
}
|
||||
@@ -2,40 +2,31 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
{
|
||||
public static class SpeedEvaluator
|
||||
{
|
||||
private const double single_spacing_threshold = OsuDifficultyHitObject.NORMALISED_DIAMETER * 1.25; // 1.25 circles distance between centers
|
||||
private const double min_speed_bonus = 200; // 200 BPM 1/4th
|
||||
private const double speed_balancing_factor = 40;
|
||||
private const double distance_multiplier = 0.8;
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the difficulty of tapping the current object, based on:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>time between pressing the previous and current object,</description></item>
|
||||
/// <item><description>distance between those objects,</description></item>
|
||||
/// <item><description>and how easily they can be cheesed.</description></item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public static double EvaluateDifficultyOf(DifficultyHitObject current, IReadOnlyList<Mod> mods)
|
||||
public static double EvaluateDifficultyOf(DifficultyHitObject current)
|
||||
{
|
||||
if (current.BaseObject is Spinner)
|
||||
return 0;
|
||||
|
||||
// derive strainTime for calculation
|
||||
var osuCurrObj = (OsuDifficultyHitObject)current;
|
||||
var osuPrevObj = current.Index > 0 ? (OsuDifficultyHitObject)current.Previous(0) : null;
|
||||
|
||||
double strainTime = osuCurrObj.AdjustedDeltaTime;
|
||||
double doubletapness = 1.0 - osuCurrObj.GetDoubletapness((OsuDifficultyHitObject?)osuCurrObj.Next(0));
|
||||
@@ -51,23 +42,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
if (DifficultyCalculationUtils.MillisecondsToBPM(strainTime) > min_speed_bonus)
|
||||
speedBonus = 0.75 * Math.Pow((DifficultyCalculationUtils.BPMToMilliseconds(min_speed_bonus) - strainTime) / speed_balancing_factor, 2);
|
||||
|
||||
double travelDistance = osuPrevObj?.LazyTravelDistance ?? 0;
|
||||
double distance = travelDistance + osuCurrObj.LazyJumpDistance;
|
||||
|
||||
// Cap distance at single_spacing_threshold
|
||||
distance = Math.Min(distance, single_spacing_threshold);
|
||||
|
||||
// Max distance bonus is 1 * `distance_multiplier` at single_spacing_threshold
|
||||
double distanceBonus = Math.Pow(distance / single_spacing_threshold, 3.95) * distance_multiplier;
|
||||
|
||||
// Apply reduced small circle bonus because flow aim difficulty on small circles doesn't scale as hard as jumps
|
||||
distanceBonus *= Math.Sqrt(osuCurrObj.SmallCircleBonus);
|
||||
|
||||
if (mods.OfType<OsuModAutopilot>().Any())
|
||||
distanceBonus = 0;
|
||||
|
||||
// Base difficulty with all bonuses
|
||||
double difficulty = (1 + speedBonus + distanceBonus) * 1000 / strainTime;
|
||||
double difficulty = (1 + speedBonus) * 1000 / strainTime;
|
||||
|
||||
difficulty *= highBpmBonus(osuCurrObj.AdjustedDeltaTime);
|
||||
|
||||
|
||||
@@ -197,7 +197,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
double aimValue = OsuStrainSkill.DifficultyToPerformance(aimDifficulty);
|
||||
|
||||
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
|
||||
double lengthBonus = 0.95 + 0.3 * Math.Min(1.0, totalHits / 2000.0) +
|
||||
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
|
||||
aimValue *= lengthBonus;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Evaluators;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||
@@ -26,28 +27,41 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
IncludeSliders = includeSliders;
|
||||
}
|
||||
|
||||
private double currentStrain;
|
||||
private double currentAimStrain;
|
||||
private double currentSpeedStrain;
|
||||
|
||||
private double skillMultiplier => 26.6;
|
||||
private double strainDecayBase => 0.15;
|
||||
private double skillMultiplierAim => 26.0;
|
||||
private double skillMultiplierSpeed => 1.3;
|
||||
private double skillMultiplierTotal => 1.02;
|
||||
private double meanExponent => 1.2;
|
||||
|
||||
private readonly List<double> sliderStrains = new List<double>();
|
||||
|
||||
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
|
||||
private double strainDecayAim(double ms) => Math.Pow(0.15, ms / 1000);
|
||||
private double strainDecaySpeed(double ms) => Math.Pow(0.3, ms / 1000);
|
||||
|
||||
protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => currentStrain * strainDecay(time - current.Previous(0).StartTime);
|
||||
protected override double CalculateInitialStrain(double time, DifficultyHitObject current) =>
|
||||
DifficultyCalculationUtils.Norm(meanExponent,
|
||||
currentAimStrain * strainDecayAim(time - current.Previous(0).StartTime),
|
||||
currentSpeedStrain * strainDecaySpeed(time - current.Previous(0).StartTime));
|
||||
|
||||
protected override double StrainValueAt(DifficultyHitObject current)
|
||||
{
|
||||
double decay = strainDecay(((OsuDifficultyHitObject)current).AdjustedDeltaTime);
|
||||
double decayAim = strainDecayAim(((OsuDifficultyHitObject)current).AdjustedDeltaTime);
|
||||
double decaySpeed = strainDecaySpeed(((OsuDifficultyHitObject)current).AdjustedDeltaTime);
|
||||
|
||||
currentStrain *= decay;
|
||||
currentStrain += AimEvaluator.EvaluateDifficultyOf(current, IncludeSliders) * (1 - decay) * skillMultiplier;
|
||||
currentAimStrain *= decayAim;
|
||||
currentAimStrain += AimEvaluator.EvaluateDifficultyOf(current, IncludeSliders) * (1 - decayAim) * skillMultiplierAim;
|
||||
|
||||
currentSpeedStrain *= decaySpeed;
|
||||
currentSpeedStrain += SpeedAimEvaluator.EvaluateDifficultyOf(current) * (1 - decaySpeed) * skillMultiplierSpeed;
|
||||
|
||||
double totalStrain = DifficultyCalculationUtils.Norm(meanExponent, currentAimStrain, currentSpeedStrain);
|
||||
|
||||
if (current.BaseObject is Slider)
|
||||
sliderStrains.Add(currentStrain);
|
||||
sliderStrains.Add(totalStrain);
|
||||
|
||||
return currentStrain;
|
||||
return totalStrain * skillMultiplierTotal;
|
||||
}
|
||||
|
||||
public double GetDifficultSliders()
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
/// </summary>
|
||||
public class Speed : HarmonicSkill
|
||||
{
|
||||
private double skillMultiplier => 0.93;
|
||||
private double skillMultiplier => 0.95;
|
||||
|
||||
private readonly List<double> sliderStrains = new List<double>();
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
double decay = strainDecay(((OsuDifficultyHitObject)current).AdjustedDeltaTime);
|
||||
|
||||
currentDifficulty *= decay;
|
||||
currentDifficulty += SpeedEvaluator.EvaluateDifficultyOf(current, Mods) * (1 - decay) * skillMultiplier;
|
||||
currentDifficulty += SpeedEvaluator.EvaluateDifficultyOf(current) * (1 - decay) * skillMultiplier;
|
||||
|
||||
double currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user