1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-15 15:23:14 +08:00
osu-lazer/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingHighAREvaluator.cs

153 lines
6.8 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.Collections.Generic;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Game.Rulesets.Difficulty.Preprocessing;
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
{
// Main class with some util functions
public static class ReadingHighAREvaluator
{
public static double EvaluateDifficultyOf(DifficultyHitObject current, bool applyAdjust = false)
{
var currObj = (OsuDifficultyHitObject)current;
double result = GetDifficulty(currObj.Preempt);
if (applyAdjust)
{
double inpredictability = ReadingEvaluator.EvaluateInpredictabilityOf(current);
// follow lines make high AR easier, so apply nerf if object isn't new combo
inpredictability *= 1 + 0.1 * (800 - currObj.FollowLineTime) / 800;
result *= 0.9 + 1 * inpredictability;
result *= 1.05 - 0.4 * EvaluateFieryAnglePunishmentOf(current);
}
return result;
}
// Explicitely nerfs edgecased fiery-type jumps for high AR. The difference from Inpredictability is that this is not used in HD calc
public static double EvaluateFieryAnglePunishmentOf(DifficultyHitObject current)
{
if (current.Index <= 2)
return 0;
var currObj = (OsuDifficultyHitObject)current;
var lastObj0 = (OsuDifficultyHitObject)current.Previous(0);
var lastObj1 = (OsuDifficultyHitObject)current.Previous(1);
var lastObj2 = (OsuDifficultyHitObject)current.Previous(2);
if (currObj.Angle.IsNull() || lastObj0.Angle.IsNull() || lastObj1.Angle.IsNull() || lastObj2.Angle.IsNull())
return 0;
// Punishment will be reduced if velocity is changing
double velocityChangeFactor = getVelocityChangeFactor(currObj, lastObj0);
velocityChangeFactor = 1 - Math.Pow(velocityChangeFactor, 2);
double a1 = currObj.Angle.Value / Math.PI;
double a2 = lastObj0.Angle.Value / Math.PI;
double a3 = lastObj1.Angle.Value / Math.PI;
double a4 = lastObj2.Angle.Value / Math.PI;
// - 4 same sharp angles in a row: (0.3 0.3 0.3 0.3) -> max punishment
// Normalized difference
double angleDifference1 = Math.Abs(a1 - a2);
double angleDifference2 = Math.Abs(a1 - a3);
double angleDifference3 = Math.Abs(a1 - a4);
// Will be close to 1 if angleDifference1 and angleDifference2 was both close to 0
double sameAnglePunishment = Math.Pow((1 - angleDifference1) * (1 - angleDifference2) * (1 - angleDifference3), 3);
// Starting from 60 degrees - reduce same angle punishment
double angleSharpnessFactor = Math.Max(0, a1 - 1.0 / 3);
angleSharpnessFactor = 1 - angleSharpnessFactor;
sameAnglePunishment *= angleSharpnessFactor;
sameAnglePunishment *= velocityChangeFactor;
sameAnglePunishment *= 0.75;
// - Alternating angles with 0: (0.3 0 0.3 0) or (0 0.3 0 0.3) -> max punishment, (0.3 0 0.1 0) -> some punishment
double alternateWithZeroAnglePunishment = Math.Max(
getAlternateWithZeroAnglePunishment(a1, a2, a3, a4),
getAlternateWithZeroAnglePunishment(a2, a1, a4, a3));
alternateWithZeroAnglePunishment *= velocityChangeFactor;
return Math.Min(1, sameAnglePunishment + alternateWithZeroAnglePunishment);
}
private static double getVelocityChangeFactor(OsuDifficultyHitObject osuCurrObj, OsuDifficultyHitObject osuLastObj)
{
double currVelocity = osuCurrObj.LazyJumpDistance / osuCurrObj.StrainTime;
double prevVelocity = osuLastObj.LazyJumpDistance / osuLastObj.StrainTime;
double velocityChangeFactor = 0;
// https://www.desmos.com/calculator/kqxmqc8pkg
if (currVelocity > 0 || prevVelocity > 0)
{
double velocityChange = Math.Max(0,
Math.Min(
Math.Abs(prevVelocity - currVelocity) - 0.5 * Math.Min(currVelocity, prevVelocity),
Math.Max(((OsuHitObject)osuCurrObj.BaseObject).Radius / Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime), Math.Min(currVelocity, prevVelocity))
)); // Stealed from xexxar
velocityChangeFactor = velocityChange / Math.Max(currVelocity, prevVelocity); // maxiumum is 0.4
velocityChangeFactor /= 0.4;
}
return velocityChangeFactor;
}
private static double getAlternateWithZeroAnglePunishment(double a1, double a2, double a3, double a4)
{
// We assume that a1 and a3 are 0
double zeroFactor = Math.Pow((1 - a1) * (1 - a3), 8);
zeroFactor *= Math.Pow(1 - Math.Abs(a1 - a3), 2);
double angleSimilarityFactor = 1 - Math.Abs(a2 - a4);
double angleSharpnessFactor = Math.Min(1 - Math.Max(0, a2 - 1.0 / 3), 1 - Math.Max(0, a4 - 1.0 / 3));
return zeroFactor * angleSimilarityFactor * angleSharpnessFactor;
}
// High AR curve
// https://www.desmos.com/calculator/hbj7swzlth
public static double GetDifficulty(double preempt)
{
double value = Math.Pow(3, 3 - 0.01 * preempt); // 1 for 300ms, 0.25 for 400ms, 0.0625 for 500ms
value = softmin(value, 2, 1.7); // use softmin to achieve full-memory cap, 2 times more than AR11 (300ms)
return value;
}
// This is very accurate on preempt > 300ms, breaking starting somewhere around 120ms
public static double GetPreempt(double difficulty)
{
double fixCoef = difficulty / GetDifficulty(highArCurveReversed(difficulty));
return highArCurveReversed(difficulty * fixCoef);
}
// This is an approximation cuz high AR curve is unsolvable
// https://www.desmos.com/calculator/n9vk18bcyh
private static double highArCurveReversed(double value)
{
double helperValue = value / Math.Pow(1 - Math.Pow(1.7, value - 2), 0.45);
double preempt = -(Math.Log(helperValue, 3) - 3) / 0.01;
return preempt;
}
// We are using mutiply and divide instead of add and subtract, so values won't be negative
// https://www.desmos.com/calculator/fv5xerwpd2
private static double softmin(double a, double b, double power = Math.E) => a * b / Math.Log(Math.Pow(power, a) + Math.Pow(power, b), power);
}
}