mirror of
https://github.com/ppy/osu.git
synced 2024-11-15 14:37:30 +08:00
Add DifficultyCalculationUtils
This commit is contained in:
parent
54288c350a
commit
78c97d2cd7
@ -5,6 +5,7 @@ using System;
|
|||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
using osu.Game.Rulesets.Difficulty.Skills;
|
using osu.Game.Rulesets.Difficulty.Skills;
|
||||||
|
using osu.Game.Rulesets.Difficulty.Utils;
|
||||||
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
@ -73,7 +74,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
|
|||||||
// 0.0 +--------+-+---------------> Release Difference / ms
|
// 0.0 +--------+-+---------------> Release Difference / ms
|
||||||
// release_threshold
|
// release_threshold
|
||||||
if (isOverlapping)
|
if (isOverlapping)
|
||||||
holdAddition = 1 / (1 + Math.Exp(0.27 * (release_threshold - closestEndTime)));
|
holdAddition = DifficultyCalculationUtils.Logistic(x: closestEndTime, multiplier: 0.27, midpointOffset: release_threshold);
|
||||||
|
|
||||||
// Decay and increase individualStrains in own column
|
// Decay and increase individualStrains in own column
|
||||||
individualStrains[column] = applyDecay(individualStrains[column], startTime - startTimes[column], individual_decay_base);
|
individualStrains[column] = applyDecay(individualStrains[column], startTime - startTimes[column], individual_decay_base);
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
|
using osu.Game.Rulesets.Difficulty.Utils;
|
||||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
|
||||||
@ -33,6 +34,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
var osuLastObj = (OsuDifficultyHitObject)current.Previous(0);
|
var osuLastObj = (OsuDifficultyHitObject)current.Previous(0);
|
||||||
var osuLastLastObj = (OsuDifficultyHitObject)current.Previous(1);
|
var osuLastLastObj = (OsuDifficultyHitObject)current.Previous(1);
|
||||||
|
|
||||||
|
const int radius = OsuDifficultyHitObject.NORMALISED_RADIUS;
|
||||||
|
const int diameter = OsuDifficultyHitObject.NORMALISED_DIAMETER;
|
||||||
|
|
||||||
// Calculate the velocity to the current hitobject, which starts with a base distance / time assuming the last object is a hitcircle.
|
// Calculate the velocity to the current hitobject, which starts with a base distance / time assuming the last object is a hitcircle.
|
||||||
double currVelocity = osuCurrObj.LazyJumpDistance / osuCurrObj.StrainTime;
|
double currVelocity = osuCurrObj.LazyJumpDistance / osuCurrObj.StrainTime;
|
||||||
|
|
||||||
@ -77,14 +81,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
wideAngleBonus = calcWideAngleBonus(currAngle);
|
wideAngleBonus = calcWideAngleBonus(currAngle);
|
||||||
acuteAngleBonus = calcAcuteAngleBonus(currAngle);
|
acuteAngleBonus = calcAcuteAngleBonus(currAngle);
|
||||||
|
|
||||||
if (osuCurrObj.StrainTime > 100) // Only buff deltaTime exceeding 300 bpm 1/2.
|
if (DifficultyCalculationUtils.MillisecondsToBPM(osuCurrObj.StrainTime, 2) < 300) // Only buff deltaTime exceeding 300 bpm 1/2.
|
||||||
acuteAngleBonus = 0;
|
acuteAngleBonus = 0;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
acuteAngleBonus *= calcAcuteAngleBonus(lastAngle) // Multiply by previous angle, we don't want to buff unless this is a wiggle type pattern.
|
acuteAngleBonus *= calcAcuteAngleBonus(lastAngle) // Multiply by previous angle, we don't want to buff unless this is a wiggle type pattern.
|
||||||
* Math.Min(angleBonus, 125 / osuCurrObj.StrainTime) // The maximum velocity we buff is equal to 125 / strainTime
|
* Math.Min(angleBonus, diameter * 1.25 / 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.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.LazyJumpDistance, 50, 100) - 50) / 50), 2); // Buff distance exceeding 50 (radius) up to 100 (diameter).
|
* Math.Pow(Math.Sin(Math.PI / 2 * (Math.Clamp(osuCurrObj.LazyJumpDistance, radius, diameter) - radius) / radius), 2); // Buff distance exceeding radius up to diameter.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Penalize wide angles if they're repeated, reducing the penalty as the lastAngle gets more acute.
|
// Penalize wide angles if they're repeated, reducing the penalty as the lastAngle gets more acute.
|
||||||
@ -104,7 +108,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
double distRatio = Math.Pow(Math.Sin(Math.PI / 2 * Math.Abs(prevVelocity - currVelocity) / Math.Max(prevVelocity, currVelocity)), 2);
|
double distRatio = Math.Pow(Math.Sin(Math.PI / 2 * Math.Abs(prevVelocity - currVelocity) / Math.Max(prevVelocity, currVelocity)), 2);
|
||||||
|
|
||||||
// Reward for % distance up to 125 / strainTime for overlaps where velocity is still changing.
|
// Reward for % distance up to 125 / strainTime for overlaps where velocity is still changing.
|
||||||
double overlapVelocityBuff = Math.Min(125 / Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime), Math.Abs(prevVelocity - currVelocity));
|
double overlapVelocityBuff = Math.Min(diameter * 1.25 / Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime), Math.Abs(prevVelocity - currVelocity));
|
||||||
|
|
||||||
velocityChangeBonus = overlapVelocityBuff * distRatio;
|
velocityChangeBonus = overlapVelocityBuff * distRatio;
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
|
using osu.Game.Rulesets.Difficulty.Utils;
|
||||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
|
||||||
@ -120,7 +121,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
islandCount.Count++;
|
islandCount.Count++;
|
||||||
|
|
||||||
// repeated island (ex: triplet -> triplet)
|
// repeated island (ex: triplet -> triplet)
|
||||||
double power = logistic(island.Delta, 2.75, 0.24, 14);
|
double power = DifficultyCalculationUtils.Logistic(island.Delta, maxValue: 2.75, multiplier: 0.24, midpointOffset: 58.33);
|
||||||
effectiveRatio *= Math.Min(3.0 / islandCount.Count, Math.Pow(1.0 / islandCount.Count, power));
|
effectiveRatio *= Math.Min(3.0 / islandCount.Count, Math.Pow(1.0 / islandCount.Count, power));
|
||||||
|
|
||||||
islandCounts[countIndex] = (islandCount.Island, islandCount.Count);
|
islandCounts[countIndex] = (islandCount.Island, islandCount.Count);
|
||||||
@ -172,8 +173,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
return Math.Sqrt(4 + rhythmComplexitySum * rhythm_overall_multiplier) / 2.0; // produces multiplier that can be applied to strain. range [1, infinity) (not really though)
|
return Math.Sqrt(4 + rhythmComplexitySum * rhythm_overall_multiplier) / 2.0; // produces multiplier that can be applied to strain. range [1, infinity) (not really though)
|
||||||
}
|
}
|
||||||
|
|
||||||
private static double logistic(double x, double maxValue, double multiplier, double offset) => (maxValue / (1 + Math.Pow(Math.E, offset - (multiplier * x))));
|
|
||||||
|
|
||||||
private class Island : IEquatable<Island>
|
private class Island : IEquatable<Island>
|
||||||
{
|
{
|
||||||
private readonly double deltaDifferenceEpsilon;
|
private readonly double deltaDifferenceEpsilon;
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
|
using osu.Game.Rulesets.Difficulty.Utils;
|
||||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
|
||||||
@ -10,8 +11,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
{
|
{
|
||||||
public static class SpeedEvaluator
|
public static class SpeedEvaluator
|
||||||
{
|
{
|
||||||
private const double single_spacing_threshold = 125; // 1.25 circles distance between centers
|
private const double single_spacing_threshold = OsuDifficultyHitObject.NORMALISED_DIAMETER * 1.25; // 1.25 circles distance between centers
|
||||||
private const double min_speed_bonus = 75; // ~200BPM
|
private const double min_speed_bonus = 200; // 200 BPM 1/4th
|
||||||
private const double speed_balancing_factor = 40;
|
private const double speed_balancing_factor = 40;
|
||||||
private const double distance_multiplier = 0.94;
|
private const double distance_multiplier = 0.94;
|
||||||
|
|
||||||
@ -43,8 +44,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
double speedBonus = 0.0;
|
double speedBonus = 0.0;
|
||||||
|
|
||||||
// Add additional scaling bonus for streams/bursts higher than 200bpm
|
// Add additional scaling bonus for streams/bursts higher than 200bpm
|
||||||
if (strainTime < min_speed_bonus)
|
if (DifficultyCalculationUtils.MillisecondsToBPM(strainTime) > min_speed_bonus)
|
||||||
speedBonus = 0.75 * Math.Pow((min_speed_bonus - strainTime) / speed_balancing_factor, 2);
|
speedBonus = 0.75 * Math.Pow((DifficultyCalculationUtils.BPMToMilliseconds(min_speed_bonus) - strainTime) / speed_balancing_factor, 2);
|
||||||
|
|
||||||
double travelDistance = osuPrevObj?.TravelDistance ?? 0;
|
double travelDistance = osuPrevObj?.TravelDistance ?? 0;
|
||||||
double distance = travelDistance + osuCurrObj.MinimumJumpDistance;
|
double distance = travelDistance + osuCurrObj.MinimumJumpDistance;
|
||||||
|
@ -19,6 +19,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
/// A distance by which all distances should be scaled in order to assume a uniform circle size.
|
/// A distance by which all distances should be scaled in order to assume a uniform circle size.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int NORMALISED_RADIUS = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths.
|
public const int NORMALISED_RADIUS = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths.
|
||||||
|
public const int NORMALISED_DIAMETER = NORMALISED_RADIUS * 2;
|
||||||
|
|
||||||
public const int MIN_DELTA_TIME = 25;
|
public const int MIN_DELTA_TIME = 25;
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
|
using osu.Game.Rulesets.Difficulty.Utils;
|
||||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
|
||||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour;
|
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour;
|
||||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour.Data;
|
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour.Data;
|
||||||
@ -11,26 +12,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
|
|||||||
{
|
{
|
||||||
public class ColourEvaluator
|
public class ColourEvaluator
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// A sigmoid function. It gives a value between (middle - height/2) and (middle + height/2).
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="val">The input value.</param>
|
|
||||||
/// <param name="center">The center of the sigmoid, where the largest gradient occurs and value is equal to middle.</param>
|
|
||||||
/// <param name="width">The radius of the sigmoid, outside of which values are near the minimum/maximum.</param>
|
|
||||||
/// <param name="middle">The middle of the sigmoid output.</param>
|
|
||||||
/// <param name="height">The height of the sigmoid output. This will be equal to max value - min value.</param>
|
|
||||||
private static double sigmoid(double val, double center, double width, double middle, double height)
|
|
||||||
{
|
|
||||||
double sigmoid = Math.Tanh(Math.E * -(val - center) / width);
|
|
||||||
return sigmoid * (height / 2) + middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Evaluate the difficulty of the first note of a <see cref="MonoStreak"/>.
|
/// Evaluate the difficulty of the first note of a <see cref="MonoStreak"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static double EvaluateDifficultyOf(MonoStreak monoStreak)
|
public static double EvaluateDifficultyOf(MonoStreak monoStreak)
|
||||||
{
|
{
|
||||||
return sigmoid(monoStreak.Index, 2, 2, 0.5, 1) * EvaluateDifficultyOf(monoStreak.Parent) * 0.5;
|
return DifficultyCalculationUtils.Logistic(exponent: Math.E * monoStreak.Index - 2 * Math.E) * EvaluateDifficultyOf(monoStreak.Parent) * 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -38,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static double EvaluateDifficultyOf(AlternatingMonoPattern alternatingMonoPattern)
|
public static double EvaluateDifficultyOf(AlternatingMonoPattern alternatingMonoPattern)
|
||||||
{
|
{
|
||||||
return sigmoid(alternatingMonoPattern.Index, 2, 2, 0.5, 1) * EvaluateDifficultyOf(alternatingMonoPattern.Parent);
|
return DifficultyCalculationUtils.Logistic(exponent: Math.E * alternatingMonoPattern.Index - 2 * Math.E) * EvaluateDifficultyOf(alternatingMonoPattern.Parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -46,7 +33,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static double EvaluateDifficultyOf(RepeatingHitPatterns repeatingHitPattern)
|
public static double EvaluateDifficultyOf(RepeatingHitPatterns repeatingHitPattern)
|
||||||
{
|
{
|
||||||
return 2 * (1 - sigmoid(repeatingHitPattern.RepetitionInterval, 2, 2, 0.5, 1));
|
return 2 * (1 - DifficultyCalculationUtils.Logistic(exponent: Math.E * repeatingHitPattern.RepetitionInterval - 2 * Math.E));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static double EvaluateDifficultyOf(DifficultyHitObject hitObject)
|
public static double EvaluateDifficultyOf(DifficultyHitObject hitObject)
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Difficulty.Utils
|
||||||
|
{
|
||||||
|
public static class DifficultyCalculationUtils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Converts BPM value into milliseconds
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bpm">Beats per minute</param>
|
||||||
|
/// <param name="delimiter">Which rhythm delimiter to use, default is 1/4</param>
|
||||||
|
/// <returns>BPM conveted to milliseconds</returns>
|
||||||
|
public static double BPMToMilliseconds(double bpm, int delimiter = 4)
|
||||||
|
{
|
||||||
|
return 60000.0 / delimiter / bpm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts milliseconds value into a BPM value
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ms">Milliseconds</param>
|
||||||
|
/// <param name="delimiter">Which rhythm delimiter to use, default is 1/4</param>
|
||||||
|
/// <returns>Milliseconds conveted to beats per minute</returns>
|
||||||
|
public static double MillisecondsToBPM(double ms, int delimiter = 4)
|
||||||
|
{
|
||||||
|
return 60000.0 / (ms * delimiter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates a S-shaped logistic function (https://en.wikipedia.org/wiki/Logistic_function)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="x">Value to calculate the function for</param>
|
||||||
|
/// <param name="maxValue">Maximum value returnable by the function</param>
|
||||||
|
/// <param name="multiplier">Growth rate of the function</param>
|
||||||
|
/// <param name="midpointOffset">How much the function midpoint is offset from zero <paramref name="x"/></param>
|
||||||
|
/// <returns>The output of logistic function of <paramref name="x"/></returns>
|
||||||
|
public static double Logistic(double x, double maxValue = 1, double multiplier = 1, double midpointOffset = 0) => maxValue / (1 + Math.Exp(multiplier * (midpointOffset - x)));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates a S-shaped logistic function (https://en.wikipedia.org/wiki/Logistic_function)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="maxValue">Maximum value returnable by the function</param>
|
||||||
|
/// <param name="exponent">Exponent</param>
|
||||||
|
/// <returns>The output of logistic function</returns>
|
||||||
|
public static double Logistic(double exponent, double maxValue = 1) => maxValue / (1 + Math.Exp(exponent));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user