diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs index 77f77487627b..deda615ffb34 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs @@ -67,11 +67,9 @@ current.BaseObject is Spinner || // Snap Stuff // Reduce strain time by 25ms to account for stopping time. - double snapDifficulty = Math.Max(linearDifficulty * (2 * osuCurrObj.Radius / Math.Max(10, osuCurrObj.StrainTime - 40) + currMovement.Length / (osuCurrObj.StrainTime - 25)), + double snapDifficulty = Math.Max(linearDifficulty * (2 * osuCurrObj.Radius / Math.Max(10, Math.Max(osuCurrObj.StrainTime, osuLastObj0.StrainTime) - 50) + currMovement.Length / (osuCurrObj.StrainTime - 0)), linearDifficulty * currMovement.Length / currTime); - double snapBuff = Math.Max(osuCurrObj.StrainTime, osuLastObj0.StrainTime) / (Math.Max(osuCurrObj.StrainTime, osuLastObj0.StrainTime) - 25); - // Begin angle and weird rewards. double currVelocity = currMovement.Length / osuCurrObj.StrainTime; double prevVelocity = prevMovement.Length / osuLastObj0.StrainTime; @@ -85,24 +83,24 @@ current.BaseObject is Spinner || double lastAngle = osuLastObj0.Angle.Value; // We reward wide angles on snap. - snapAngle = snapBuff * linearDifficulty * calculateAngleSpline(Math.Abs(currAngle), false) * Math.Min(Math.Min(currVelocity, prevVelocity), (currMovement + prevMovement).Length / Math.Max(osuCurrObj.StrainTime, osuLastObj0.StrainTime)); + snapAngle = linearDifficulty * calculateAngleSpline(Math.Abs(currAngle), false) * Math.Min(Math.Min(currVelocity, prevVelocity), (currMovement + prevMovement).Length / Math.Max(osuCurrObj.StrainTime, osuLastObj0.StrainTime)); // We reward for angle changes or the acuteness of the angle, whichever is higher. Possibly a case out there to reward both. - flowAngle = linearDifficulty * Math.Pow(Math.Sin((currAngle - lastAngle) / 2), 2) * Math.Min(currVelocity, prevVelocity) - + linearDifficulty * calculateAngleSpline(Math.Abs(currAngle), true) * Math.Min(Math.Min(currVelocity, prevVelocity), (currMovement - prevMovement).Length / Math.Max(osuCurrObj.StrainTime, osuLastObj0.StrainTime)); + flowAngle = linearDifficulty * calculateAngleSpline(Math.Abs(currAngle), true) * Math.Min(Math.Min(currVelocity, prevVelocity), (currMovement - prevMovement).Length / Math.Max(osuCurrObj.StrainTime, osuLastObj0.StrainTime)); + + flowAngle += Math.Pow(Math.Sin((currAngle - lastAngle) / 2), 2) * Math.Min(currVelocity * currMovement.Length / (osuCurrObj.Radius * 2), prevVelocity * prevMovement.Length / (osuCurrObj.Radius * 2)); } - double flowVelChange = linearDifficulty * Math.Max(0, Math.Min(Math.Abs(currVelocity - prevVelocity) - Math.Min(currVelocity, prevVelocity), Math.Max(osuCurrObj.Radius / Math.Max(osuCurrObj.StrainTime, osuLastObj0.StrainTime), Math.Min(currVelocity, prevVelocity)))); + // double flowVelChange = linearDifficulty * Math.Max(0, Math.Min(Math.Abs(currVelocity - prevVelocity) - Math.Min(currVelocity, prevVelocity), Math.Max(osuCurrObj.Radius / Math.Max(osuCurrObj.StrainTime, osuLastObj0.StrainTime), Math.Min(currVelocity, prevVelocity)))); - // double flowVelChange = linearDifficulty * Math.Abs(prevVelocity - currVelocity) * rhythmRatio; + double flowVelChange = linearDifficulty * Math.Abs(prevVelocity - currVelocity); double snapVelChange = linearDifficulty * Math.Max(0, Math.Min(Math.Abs(prevVelocity - currVelocity) - Math.Min(currVelocity, prevVelocity), Math.Max(osuCurrObj.Radius / Math.Max(osuCurrObj.StrainTime, osuLastObj0.StrainTime), Math.Min(currVelocity, prevVelocity)))); - snapDifficulty += snapBuff * snapVelChange + snapAngle; - flowDifficulty += flowVelChange + flowAngle; + snapDifficulty += Math.Max(snapVelChange, snapAngle); + flowDifficulty += Math.Max(flowVelChange, flowAngle); - // Apply balancing parameters. - flowDifficulty = flowDifficulty * 1.4125; - snapDifficulty = snapDifficulty * 0.79; + snapDifficulty *= 0.655; + flowDifficulty *= 0.85; // Apply small CS buff. snapDifficulty *= Math.Sqrt(linearDifficulty); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs index 3091e3a583e6..5c1970caacc0 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators public static class RhythmEvaluator { private const int history_time_max = 5000; // 5 seconds of calculatingRhythmBonus max. - private const double rhythm_multiplier = 1.5; + private const double rhythm_multiplier = 1.0; /// /// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current . @@ -50,7 +50,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current) double prevDelta = prevObj.StrainTime; double lastDelta = lastObj.StrainTime; double currRatio = Math.PI * Math.Min(5, Math.Max(prevDelta, currDelta) / Math.Min(prevDelta, currDelta)); - currRatio = 1.0 + 8 * (-0.25 + Math.Min(0.75, Math.Max(0.25, Math.Pow(Math.Sin(currRatio), 2.0) + 0.2 * Math.Pow(Math.Sin(1.5 * currRatio), 2.0)))); + currRatio = 1.0 + 12 * (-0.25 + Math.Min(0.75, Math.Max(0.25, Math.Pow(Math.Sin(currRatio), 2.0) + 0.2 * Math.Pow(Math.Sin(1.5 * currRatio), 2.0)))); double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - currObj.HitWindowGreat * 0.3) / (currObj.HitWindowGreat * 0.3)); diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 8347d36c8c89..0d40566099b3 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -38,9 +38,24 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; double aimRatingNoSliders = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; - double speedRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; - double speedNotes = ((Speed)skills[2]).RelevantNoteCount(); - double flashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * difficulty_multiplier; + double aimFlowRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; + double aimSnapRating = Math.Sqrt(skills[3].DifficultyValue()) * difficulty_multiplier; + double speedRating = Math.Sqrt(skills[4].DifficultyValue()) * difficulty_multiplier; + double speedNotes = ((Speed)skills[4]).RelevantNoteCount(); + double flashlightRating = Math.Sqrt(skills[5].DifficultyValue()) * difficulty_multiplier; + + double p = 5.0; + + if (aimSnapRating > aimFlowRating) + { + aimRating = Math.Pow(Math.Pow(aimRating, p) + Math.Pow(aimFlowRating, p), 1.0 / p); + aimRatingNoSliders = Math.Pow(Math.Pow(aimRatingNoSliders, p) + Math.Pow(aimFlowRating, p), 1.0 / p); + } + else + { + aimRating = Math.Pow(Math.Pow(aimRating, p) + Math.Pow(aimSnapRating, p), 1.0 / p); + aimRatingNoSliders = Math.Pow(Math.Pow(aimRatingNoSliders, p) + Math.Pow(aimSnapRating, p), 1.0 / p); + } if (mods.Any(m => m is OsuModTouchDevice)) { @@ -57,6 +72,8 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat flashlightRating *= 0.7; } + + double baseAimPerformance = Math.Pow(5 * Math.Max(1, aimRating / 0.0675) - 4, 3) / 100000; double baseAimNoSlidersPerformance = Math.Pow(5 * Math.Max(1, aimRatingNoSliders / 0.0675) - 4, 3) / 100000; double baseSpeedPerformance = Math.Pow(5 * Math.Max(1, speedRating / 0.0675) - 4, 3) / 100000; @@ -133,6 +150,8 @@ protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clo { new Aim(mods, true), new Aim(mods, false), + new AimFlow(mods), + new AimSnap(mods), new Speed(mods), new Flashlight(mods) }; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 2bdbce6d2894..9331cef83356 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -106,7 +106,7 @@ private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attribut double approachRateFactor = 0.0; if (attributes.ApproachRate > 10.33) - approachRateFactor = 0.12 * (attributes.ApproachRate - 10.33); + approachRateFactor = 0.15 * (attributes.ApproachRate - 10.33); else if (attributes.ApproachRate < 9.0) approachRateFactor = 0.05 * (9.0 - attributes.ApproachRate); @@ -154,7 +154,7 @@ private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attrib double approachRateFactor = 0.0; if (attributes.ApproachRate > 10.33) - approachRateFactor = 0.24 * (attributes.ApproachRate - 10.33); + approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33); speedValue *= 1.0 + approachRateFactor; @@ -175,7 +175,7 @@ private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attrib // Scale the speed value with speed deviation. if (deviation != null) - speedValue *= 1.0 / (1.0 + Math.Pow((double)speedDeviation / (12.0 + Math.Pow(hitWindow300, .75)), 4.0)); + speedValue *= 1.0 / (1.0 + Math.Pow((double)deviation / (10.0 + Math.Pow(hitWindow300, .75)), 4.0)); return speedValue; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index e7a9a2a5cc2d..d687bf0b7c05 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -26,7 +26,7 @@ public Aim(Mod[] mods, bool withSliders) private double currentSnapStrain; private double realStrain; - private double skillMultiplier => 32;//38.75; + private double skillMultiplier => 27.27;//38.75; // private double skillMultiplier => 23.55; private double strainDecayBase => 0.15; @@ -39,8 +39,11 @@ public Aim(Mod[] mods, bool withSliders) protected override double StrainValueAt(DifficultyHitObject current) { - currentFlowStrain *= strainDecay(current.DeltaTime); - currentSnapStrain *= strainDecay(current.DeltaTime); + // currentFlowStrain *= strainDecay(current.DeltaTime); + // currentSnapStrain *= strainDecay(current.DeltaTime); + + currentFlowStrain *= Math.Min(1.0, Math.Max(0, (1000 - current.DeltaTime) / 1000)); + currentSnapStrain *= Math.Min(1.0, Math.Max(0, (1000 - current.DeltaTime) / 1000)); double currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current); @@ -54,8 +57,7 @@ protected override double StrainValueAt(DifficultyHitObject current) else currentSnapStrain += snapStrain; - double p = 3; - realStrain = currentFlowStrain + currentSnapStrain + (Math.Pow(Math.Pow(currentFlowStrain, p) + Math.Pow(currentSnapStrain, p), 1.0 / p) - Math.Max(currentFlowStrain, currentSnapStrain)); + realStrain = currentFlowStrain + currentSnapStrain; return realStrain; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/AimFlow.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/AimFlow.cs new file mode 100644 index 000000000000..f2f9795f0b46 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/AimFlow.cs @@ -0,0 +1,62 @@ +// Copyright (c) ppy Pty Ltd . 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.Mods; +using osu.Game.Rulesets.Osu.Difficulty.Evaluators; +using System.Collections.Generic; + +namespace osu.Game.Rulesets.Osu.Difficulty.Skills +{ + /// + /// Represents the skill required to correctly aim at every object in the map with a uniform CircleSize and normalized distances. + /// + public class AimFlow : OsuStrainSkill + { + public AimFlow(Mod[] mods) + : base(mods) + { + } + + private double currentFlowStrain; + private double currentSnapStrain; + private double realStrain; + + private double skillMultiplier => 27.27;//38.75; + // private double skillMultiplier => 23.55; + private double strainDecayBase => 0.15; + + private readonly List flowStrains = new List(); + private readonly List snapStrains = new List(); + + private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); + + protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => realStrain * strainDecay(time - current.Previous(0).StartTime); + + protected override double StrainValueAt(DifficultyHitObject current) + { + // currentFlowStrain *= strainDecay(current.DeltaTime); + // currentSnapStrain *= strainDecay(current.DeltaTime); + + currentFlowStrain *= Math.Min(1.0, Math.Max(0, (1000 - current.DeltaTime) / 1000)); + currentSnapStrain *= Math.Min(1.0, Math.Max(0, (1000 - current.DeltaTime) / 1000)); + + double currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current); + + (double, double) aimResult = AimEvaluator.EvaluateDifficultyOf(current, true, strainDecayBase, currentRhythm); + + double flowStrain = aimResult.Item1 * skillMultiplier; + double snapStrain = aimResult.Item2 * skillMultiplier; + + if (flowStrain < snapStrain) + currentFlowStrain += flowStrain; + else + currentSnapStrain += snapStrain; + + realStrain = currentFlowStrain; + + return realStrain; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/AimSnap.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/AimSnap.cs new file mode 100644 index 000000000000..482a600306e3 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/AimSnap.cs @@ -0,0 +1,62 @@ +// Copyright (c) ppy Pty Ltd . 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.Mods; +using osu.Game.Rulesets.Osu.Difficulty.Evaluators; +using System.Collections.Generic; + +namespace osu.Game.Rulesets.Osu.Difficulty.Skills +{ + /// + /// Represents the skill required to correctly aim at every object in the map with a uniform CircleSize and normalized distances. + /// + public class AimSnap : OsuStrainSkill + { + public AimSnap(Mod[] mods) + : base(mods) + { + } + + private double currentFlowStrain; + private double currentSnapStrain; + private double realStrain; + + private double skillMultiplier => 27.27;//38.75; + // private double skillMultiplier => 23.55; + private double strainDecayBase => 0.15; + + private readonly List flowStrains = new List(); + private readonly List snapStrains = new List(); + + private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); + + protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => realStrain * strainDecay(time - current.Previous(0).StartTime); + + protected override double StrainValueAt(DifficultyHitObject current) + { + // currentFlowStrain *= strainDecay(current.DeltaTime); + // currentSnapStrain *= strainDecay(current.DeltaTime); + + currentFlowStrain *= Math.Min(1.0, Math.Max(0, (1000 - current.DeltaTime) / 1000)); + currentSnapStrain *= Math.Min(1.0, Math.Max(0, (1000 - current.DeltaTime) / 1000)); + + double currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current); + + (double, double) aimResult = AimEvaluator.EvaluateDifficultyOf(current, true, strainDecayBase, currentRhythm); + + double flowStrain = aimResult.Item1 * skillMultiplier; + double snapStrain = aimResult.Item2 * skillMultiplier; + + if (flowStrain < snapStrain) + currentFlowStrain += flowStrain; + else + currentSnapStrain += snapStrain; + + realStrain = currentSnapStrain; + + return realStrain; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 9bef7f9640f7..1ac54533e2b1 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -37,8 +37,11 @@ public abstract class OsuStrainSkill : StrainSkill protected OsuStrainSkill(Mod[] mods) : base(mods) { + strains = new List(); } + private List strains; + public override double DifficultyValue() { double difficulty = 0; @@ -47,14 +50,14 @@ public override double DifficultyValue() // These sections will not contribute to the difficulty. var peaks = GetCurrentStrainPeaks().Where(p => p > 0); - List strains = peaks.OrderByDescending(d => d).ToList(); + strains = peaks.OrderByDescending(d => d).ToList(); // We are reducing the highest strains first to account for extreme difficulty spikes - for (int i = 0; i < Math.Min(strains.Count, ReducedSectionCount); i++) - { - double scale = Math.Log10(Interpolation.Lerp(1, 10, Math.Clamp((float)i / ReducedSectionCount, 0, 1))); - strains[i] *= Interpolation.Lerp(ReducedStrainBaseline, 1.0, scale); - } + // for (int i = 0; i < Math.Min(strains.Count, ReducedSectionCount); i++) + // { + // double scale = Math.Log10(Interpolation.Lerp(1, 10, Math.Clamp((float)i / ReducedSectionCount, 0, 1))); + // strains[i] *= Interpolation.Lerp(ReducedStrainBaseline, 1.0, scale); + // } int index = 0; @@ -75,5 +78,37 @@ public override double DifficultyValue() return difficulty * DifficultyMultiplier; } + + // public double difficultyValueWithThreshold(double startIndex) + // { + // // threshold = 1.0 - threshold; + // // int startIndex = (int)(threshold * strains.Count); + + // int index = 0; + // double difficulty = 0.0; + + // // Difficulty is the weighted sum of the highest strains from every section. + // // We're sorting from highest to lowest strain. + // foreach (double strain in strains.OrderByDescending(d => d)) + // { + // // Below uses harmonic sum scaling which makes the resulting summation logarithmic rather than geometric. + // // Good for properly weighting difficulty across full map instead of using object count for LengthBonus. + // // 1.44 and 7.5 are arbitrary constants that worked well. + // // double weight = 1.44 * ((1 + (7.5 / (1 + index))) / (index + 1 + (7.5 / (1 + index)))); + // // double weight = 1.42 * ((1 + (7.5 / (1 + index))) / (Math.Pow(index, Math.Max(0.85, 1.0 - 0.15 * Math.Pow(index / 1500.0, 1))) + 1 + (7.5 / (1 + index)))); + // if (startIndex > index) + // { + // index += 1; + // continue; + // } + + // double weight = (1.0 + (20.0 / (1.0 + (index - startIndex)))) / (index + 1.0 + (20.0 / (1.0 + (index - startIndex)))); + + // difficulty += strain * weight; + // index += 1; + // } + + // return difficulty; + // } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index bd05e1368585..f62e6c68be0f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// public class Speed : OsuStrainSkill { - private double skillMultiplier => 1050; + private double skillMultiplier => 1000; private double strainDecayBase => 0.3; private double currentStrain;