Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code refactoring Aim and Speed #15485

Closed
wants to merge 30 commits into from
Closed
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
7bb3f06
preskills basecode
GoldenMine0502 Nov 5, 2021
e954294
deleted prestrainskill and changed to skill
GoldenMine0502 Nov 16, 2021
a219974
changed foreach to for
GoldenMine0502 Nov 16, 2021
282f533
minor
GoldenMine0502 Nov 16, 2021
e061a05
add parameter preskills
GoldenMine0502 Nov 21, 2021
152dce0
refactor aim and speed
GoldenMine0502 Dec 11, 2021
74c7cd4
stashed
GoldenMine0502 Dec 11, 2021
5e8674e
merged
GoldenMine0502 Dec 11, 2021
3f41b52
codefactor
GoldenMine0502 Dec 11, 2021
67ab322
fix historylength doesn't work
GoldenMine0502 Dec 11, 2021
b0195f5
fix complexity bonus problem
GoldenMine0502 Dec 11, 2021
31c7cb8
fix unused value
GoldenMine0502 Dec 11, 2021
5080da1
fix unused value
GoldenMine0502 Dec 11, 2021
9ceeee7
codefactor
GoldenMine0502 Dec 11, 2021
457b956
codefactor
GoldenMine0502 Dec 11, 2021
b7d8a00
aim consider first note velocity
GoldenMine0502 Dec 12, 2021
40e208e
license comment
GoldenMine0502 Dec 12, 2021
430bf94
if previous object is spinner cancel calculation
GoldenMine0502 Dec 12, 2021
7e85cf2
changed inheritance of PreStrainSkill from StrainDecaySkill to Strain…
GoldenMine0502 Dec 12, 2021
fdda075
processinternal publicity to protected
GoldenMine0502 Dec 12, 2021
65d19af
codefactor
GoldenMine0502 Dec 12, 2021
3b7f5cb
processinternal changed to internal
GoldenMine0502 Dec 12, 2021
56cfbe3
code quality
GoldenMine0502 Dec 12, 2021
0a6cf86
test case diffcalc-test and zero-length-sliders
GoldenMine0502 Dec 12, 2021
ae49661
code quality
GoldenMine0502 Dec 12, 2021
d8f770f
visiblity processinternal public
GoldenMine0502 Dec 12, 2021
471079d
a
GoldenMine0502 Dec 13, 2021
254a6b3
review
GoldenMine0502 Dec 13, 2021
a840034
code quality
GoldenMine0502 Dec 13, 2021
cfc2a68
code quality
GoldenMine0502 Dec 13, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public Movement(Mod[] mods, float halfCatcherWidth, double clockRate)
catcherSpeedMultiplier = clockRate;
}

protected override double StrainValueOf(DifficultyHitObject current)
protected override double StrainValueOf(int index, DifficultyHitObject current)
{
var catchCurrent = (CatchDifficultyHitObject)current;

Expand Down
2 changes: 1 addition & 1 deletion osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public Strain(Mod[] mods, int totalColumns)
overallStrain = 1;
}

protected override double StrainValueOf(DifficultyHitObject current)
protected override double StrainValueOf(int index, DifficultyHitObject current)
{
var maniaCurrent = (ManiaDifficultyHitObject)current;
double endTime = maniaCurrent.EndTime;
Expand Down
6 changes: 3 additions & 3 deletions osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ public class OsuDifficultyCalculatorTest : DifficultyCalculatorTest
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";

[TestCase(6.6972307565739273d, "diffcalc-test")]
[TestCase(1.4484754139145539d, "zero-length-sliders")]
[TestCase(1.4804738333333043d, "zero-length-sliders")]
public void Test(double expected, string name)
=> base.Test(expected, name);

[TestCase(8.9382559208689809d, "diffcalc-test")]
[TestCase(1.7548875851757628d, "zero-length-sliders")]
[TestCase(8.9383248067074295d, "diffcalc-test")]
[TestCase(1.806793929165774d, "zero-length-sliders")]
public void TestClockRateAdjusted(double expected, string name)
=> Test(expected, name, new OsuModDoubleTime());

Expand Down
145 changes: 18 additions & 127 deletions osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
using System;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Difficulty.Skills.Pre;

namespace osu.Game.Rulesets.Osu.Difficulty.Skills
{
Expand All @@ -14,150 +13,42 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
/// </summary>
public class Aim : OsuStrainSkill
{
public Aim(Mod[] mods, bool withSliders)
: base(mods)
{
this.withSliders = withSliders;
}

private readonly bool withSliders;

protected override int HistoryLength => 2;

private const double wide_angle_multiplier = 1.5;
private const double acute_angle_multiplier = 2.0;
private const double slider_multiplier = 1.5;
private const double velocity_change_multiplier = 0.75;

private double currentStrain;

private double skillMultiplier => 23.25;
private double strainDecayBase => 0.15;

private double strainValueOf(DifficultyHitObject current)
{
if (current.BaseObject is Spinner || Previous.Count <= 1 || Previous[0].BaseObject is Spinner)
return 0;

var osuCurrObj = (OsuDifficultyHitObject)current;
var osuLastObj = (OsuDifficultyHitObject)Previous[0];
var osuLastLastObj = (OsuDifficultyHitObject)Previous[1];

// 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;

// 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 && withSliders)
{
double travelVelocity = osuLastObj.TravelDistance / osuLastObj.TravelTime; // calculate the slider velocity from slider head to slider end.
double movementVelocity = osuCurrObj.MinimumJumpDistance / osuCurrObj.MinimumJumpTime; // calculate the movement velocity from slider end to current object

currVelocity = Math.Max(currVelocity, movementVelocity + travelVelocity); // take the larger total combined velocity.
}

// As above, do the same for the previous hitobject.
double prevVelocity = osuLastObj.LazyJumpDistance / osuLastObj.StrainTime;

if (osuLastLastObj.BaseObject is Slider && withSliders)
{
double travelVelocity = osuLastLastObj.TravelDistance / osuLastLastObj.TravelTime;
double movementVelocity = osuLastObj.MinimumJumpDistance / osuLastObj.MinimumJumpTime;

prevVelocity = Math.Max(prevVelocity, movementVelocity + travelVelocity);
}

double wideAngleBonus = 0;
double acuteAngleBonus = 0;
double sliderBonus = 0;
double velocityChangeBonus = 0;

double aimStrain = currVelocity; // Start strain with regular velocity.

if (Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime) < 1.25 * Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime)) // If rhythms are the same.
{
if (osuCurrObj.Angle != null && osuLastObj.Angle != null && osuLastLastObj.Angle != null)
{
double currAngle = osuCurrObj.Angle.Value;
double lastAngle = osuLastObj.Angle.Value;
double lastLastAngle = osuLastLastObj.Angle.Value;

// Rewarding angles, take the smaller velocity as base.
double angleBonus = Math.Min(currVelocity, prevVelocity);

wideAngleBonus = calcWideAngleBonus(currAngle);
acuteAngleBonus = calcAcuteAngleBonus(currAngle);

if (osuCurrObj.StrainTime > 100) // Only buff deltaTime exceeding 300 bpm 1/2.
acuteAngleBonus = 0;
else
{
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.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).
}

// Penalize wide angles if they're repeated, reducing the penalty as the lastAngle gets more acute.
wideAngleBonus *= angleBonus * (1 - Math.Min(wideAngleBonus, Math.Pow(calcWideAngleBonus(lastAngle), 3)));
// Penalize acute angles if they're repeated, reducing the penalty as the lastLastAngle gets more obtuse.
acuteAngleBonus *= 0.5 + 0.5 * (1 - Math.Min(acuteAngleBonus, Math.Pow(calcAcuteAngleBonus(lastLastAngle), 3)));
}
}

if (Math.Max(prevVelocity, currVelocity) != 0)
{
// We want to use the average velocity over the whole object when awarding differences, not the individual jump and slider path velocities.
prevVelocity = (osuLastObj.LazyJumpDistance + osuLastLastObj.TravelDistance) / osuLastObj.StrainTime;
currVelocity = (osuCurrObj.LazyJumpDistance + osuLastObj.TravelDistance) / osuCurrObj.StrainTime;

// Scale with ratio of difference compared to 0.5 * max dist.
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.
double overlapVelocityBuff = Math.Min(125 / Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime), Math.Abs(prevVelocity - currVelocity));

// Reward for % distance slowed down compared to previous, paying attention to not award overlap
double nonOverlapVelocityBuff = Math.Abs(prevVelocity - currVelocity)
// do not award overlap
* Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, Math.Min(osuCurrObj.LazyJumpDistance, osuLastObj.LazyJumpDistance) / 100)), 2);
private double currentStrain;

// Choose the largest bonus, multiplied by ratio.
velocityChangeBonus = Math.Max(overlapVelocityBuff, nonOverlapVelocityBuff) * distRatio;
private readonly AimVelocity aimVelocity;

// Penalize for rhythm changes.
velocityChangeBonus *= Math.Pow(Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime) / Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime), 2);
}
private readonly AimAngleBonus aimAngleBonus;

if (osuLastObj.TravelTime != 0)
{
// Reward sliders based on velocity.
sliderBonus = osuLastObj.TravelDistance / osuLastObj.TravelTime;
}
public Aim(Mod[] mods, bool withSliders)
: base(mods)
{
aimVelocity = new AimVelocity(mods, withSliders);
aimAngleBonus = new AimAngleBonus(mods, withSliders, aimVelocity);
}

// Add in acute angle bonus or wide angle bonus + velocity change bonus, whichever is larger.
aimStrain += Math.Max(acuteAngleBonus * acute_angle_multiplier, wideAngleBonus * wide_angle_multiplier + velocityChangeBonus * velocity_change_multiplier);
private double strainValueOf(int index, DifficultyHitObject current)
{
aimVelocity.ProcessPre(index, current);
aimAngleBonus.ProcessPre(index, current);

// Add in additional slider velocity bonus.
if (withSliders)
aimStrain += sliderBonus * slider_multiplier;
double aimStrain = aimVelocity[index] + aimAngleBonus[index]; // Start strain with regular velocity.

return aimStrain;
}

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);

private double calcAcuteAngleBonus(double angle) => 1 - calcWideAngleBonus(angle);

private double applyDiminishingExp(double val) => Math.Pow(val, 0.99);
//private double applyDiminishingExp(double val) => Math.Pow(val, 0.99);

private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);

protected override double CalculateInitialStrain(double time) => currentStrain * strainDecay(time - Previous[0].StartTime);

protected override double StrainValueAt(DifficultyHitObject current)
protected override double StrainValueAt(int index, DifficultyHitObject current)
{
currentStrain *= strainDecay(current.DeltaTime);
currentStrain += strainValueOf(current) * skillMultiplier;
currentStrain += strainValueOf(index, current) * skillMultiplier;

return currentStrain;
}
Expand Down
6 changes: 3 additions & 3 deletions osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public Flashlight(Mod[] mods)

private double currentStrain;

private double strainValueOf(DifficultyHitObject current)
private double strainValueOf(int index, DifficultyHitObject current)
{
if (current.BaseObject is Spinner)
return 0;
Expand Down Expand Up @@ -69,10 +69,10 @@ private double strainValueOf(DifficultyHitObject current)

protected override double CalculateInitialStrain(double time) => currentStrain * strainDecay(time - Previous[0].StartTime);

protected override double StrainValueAt(DifficultyHitObject current)
protected override double StrainValueAt(int index, DifficultyHitObject current)
{
currentStrain *= strainDecay(current.DeltaTime);
currentStrain += strainValueOf(current) * skillMultiplier;
currentStrain += strainValueOf(index, current) * skillMultiplier;

return currentStrain;
}
Expand Down
129 changes: 129 additions & 0 deletions osu.Game.Rulesets.Osu/Difficulty/Skills/Pre/AimAngleBonus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// 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.Difficulty.Skills;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Objects;

namespace osu.Game.Rulesets.Osu.Difficulty.Skills.Pre
{
public class AimAngleBonus : PreStrainSkill
{
private const double wide_angle_multiplier = 1.5;
private const double acute_angle_multiplier = 2.0;
private const double velocity_change_multiplier = 0.75;
private const double slider_multiplier = 1.5;

protected override double SkillMultiplier => 1.0;

protected override double StrainDecayBase => 0.0;

protected override int HistoryLength => 2;

private readonly bool withSliders;

private readonly AimVelocity aimVelocity;

public AimAngleBonus(Mod[] mods, bool withSliders, AimVelocity aimVelocity)
: base(mods)
{
this.withSliders = withSliders;
this.aimVelocity = aimVelocity;
}

protected override double StrainValueAt(int index, DifficultyHitObject current)
{
if (current.BaseObject is Spinner || Previous.Count < 2 || Previous[0].BaseObject is Spinner)
return 0;

var osuCurrObj = (OsuDifficultyHitObject)current;
var osuLastObj = (OsuDifficultyHitObject)Previous[0];
var osuLastLastObj = (OsuDifficultyHitObject)Previous[1];

double currVelocity = aimVelocity[index];
double prevVelocity = aimVelocity[index - 1];

double wideAngleBonus = 0;
double acuteAngleBonus = 0;
double sliderBonus = 0;
double velocityChangeBonus = 0;

if (Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime) < 1.25 * Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime)) // If rhythms are the same.
{
if (osuCurrObj.Angle != null && osuLastObj.Angle != null && osuLastLastObj.Angle != null)
{
double currAngle = osuCurrObj.Angle.Value;
double lastAngle = osuLastObj.Angle.Value;
double lastLastAngle = osuLastLastObj.Angle.Value;

// Rewarding angles, take the smaller velocity as base.
double angleBonus = Math.Min(currVelocity, prevVelocity);

wideAngleBonus = calcWideAngleBonus(currAngle);
acuteAngleBonus = calcAcuteAngleBonus(currAngle);

if (osuCurrObj.StrainTime > 100) // Only buff deltaTime exceeding 300 bpm 1/2.
acuteAngleBonus = 0;
else
{
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.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).
}

// Penalize wide angles if they're repeated, reducing the penalty as the lastAngle gets more acute.
wideAngleBonus *= angleBonus * (1 - Math.Min(wideAngleBonus, Math.Pow(calcWideAngleBonus(lastAngle), 3)));
// Penalize acute angles if they're repeated, reducing the penalty as the lastLastAngle gets more obtuse.
acuteAngleBonus *= 0.5 + 0.5 * (1 - Math.Min(acuteAngleBonus, Math.Pow(calcAcuteAngleBonus(lastLastAngle), 3)));
}
}

if (Math.Max(prevVelocity, currVelocity) != 0)
{
// We want to use the average velocity over the whole object when awarding differences, not the individual jump and slider path velocities.
prevVelocity = (osuLastObj.LazyJumpDistance + osuLastLastObj.TravelDistance) / osuLastObj.StrainTime;
currVelocity = (osuCurrObj.LazyJumpDistance + osuLastObj.TravelDistance) / osuCurrObj.StrainTime;

// Scale with ratio of difference compared to 0.5 * max dist.
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.
double overlapVelocityBuff = Math.Min(125 / Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime), Math.Abs(prevVelocity - currVelocity));

// Reward for % distance slowed down compared to previous, paying attention to not award overlap
double nonOverlapVelocityBuff = Math.Abs(prevVelocity - currVelocity)
// do not award overlap
* Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, Math.Min(osuCurrObj.LazyJumpDistance, osuLastObj.LazyJumpDistance) / 100)), 2);

// Choose the largest bonus, multiplied by ratio.
velocityChangeBonus = Math.Max(overlapVelocityBuff, nonOverlapVelocityBuff) * distRatio;

// Penalize for rhythm changes.
velocityChangeBonus *= Math.Pow(Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime) / Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime), 2);
}

if (osuLastObj.TravelTime != 0)
{
// Reward sliders based on velocity.
sliderBonus = osuLastObj.TravelDistance / osuLastObj.TravelTime;
}

// Add in acute angle bonus or wide angle bonus + velocity change bonus, whichever is larger.
double totalBonus = Math.Max(acuteAngleBonus * acute_angle_multiplier, wideAngleBonus * wide_angle_multiplier + velocityChangeBonus * velocity_change_multiplier);

// Add in additional slider velocity bonus.
if (withSliders)
totalBonus += sliderBonus * slider_multiplier;

return totalBonus;
}

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);

private double calcAcuteAngleBonus(double angle) => 1 - calcWideAngleBonus(angle);
}
}
Loading