mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 21:52:55 +08:00
Merge pull request #8934 from smoogipoo/sorcerer-catch-changes
Implement Sorcerer's osu!catch difficulty calculation adjustments
This commit is contained in:
commit
c8134162b5
@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
{
|
||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
|
||||
|
||||
[TestCase(4.2058561036909863d, "diffcalc-test")]
|
||||
[TestCase(4.050601681491468d, "diffcalc-test")]
|
||||
public void Test(double expected, string name)
|
||||
=> base.Test(expected, name);
|
||||
|
||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
{
|
||||
public class CatchDifficultyCalculator : DifficultyCalculator
|
||||
{
|
||||
private const double star_scaling_factor = 0.145;
|
||||
private const double star_scaling_factor = 0.153;
|
||||
|
||||
protected override int SectionLength => 750;
|
||||
|
||||
@ -73,6 +73,9 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
{
|
||||
halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) * 0.5f;
|
||||
|
||||
// For circle sizes above 5.5, reduce the catcher width further to simulate imperfect gameplay.
|
||||
halfCatcherWidth *= 1 - (Math.Max(0, beatmap.BeatmapInfo.BaseDifficulty.CircleSize - 5.5f) * 0.0625f);
|
||||
|
||||
return new Skill[]
|
||||
{
|
||||
new Movement(halfCatcherWidth),
|
||||
|
@ -52,8 +52,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
|
||||
// Longer maps are worth more
|
||||
double lengthBonus =
|
||||
0.95 + 0.4 * Math.Min(1.0, numTotalHits / 3000.0) +
|
||||
(numTotalHits > 3000 ? Math.Log10(numTotalHits / 3000.0) * 0.5 : 0.0);
|
||||
0.95f + 0.3f * Math.Min(1.0f, numTotalHits / 2500.0f) +
|
||||
(numTotalHits > 2500 ? (float)Math.Log10(numTotalHits / 2500.0f) * 0.475f : 0.0f);
|
||||
|
||||
// Longer maps are worth more
|
||||
value *= lengthBonus;
|
||||
@ -63,19 +63,28 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
|
||||
// Combo scaling
|
||||
if (Attributes.MaxCombo > 0)
|
||||
value *= Math.Min(Math.Pow(Attributes.MaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
|
||||
value *= Math.Min(Math.Pow(Score.MaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
|
||||
|
||||
double approachRateFactor = 1.0;
|
||||
if (Attributes.ApproachRate > 9.0)
|
||||
approachRateFactor += 0.1 * (Attributes.ApproachRate - 9.0); // 10% for each AR above 9
|
||||
else if (Attributes.ApproachRate < 8.0)
|
||||
approachRateFactor += 0.025 * (8.0 - Attributes.ApproachRate); // 2.5% for each AR below 8
|
||||
float approachRate = (float)Attributes.ApproachRate;
|
||||
float approachRateFactor = 1.0f;
|
||||
if (approachRate > 9.0f)
|
||||
approachRateFactor += 0.1f * (approachRate - 9.0f); // 10% for each AR above 9
|
||||
if (approachRate > 10.0f)
|
||||
approachRateFactor += 0.1f * (approachRate - 10.0f); // Additional 10% at AR 11, 30% total
|
||||
else if (approachRate < 8.0f)
|
||||
approachRateFactor += 0.025f * (8.0f - approachRate); // 2.5% for each AR below 8
|
||||
|
||||
value *= approachRateFactor;
|
||||
|
||||
if (mods.Any(m => m is ModHidden))
|
||||
// Hiddens gives nothing on max approach rate, and more the lower it is
|
||||
{
|
||||
value *= 1.05 + 0.075 * (10.0 - Math.Min(10.0, Attributes.ApproachRate)); // 7.5% for each AR below 10
|
||||
// Hiddens gives almost nothing on max approach rate, and more the lower it is
|
||||
if (approachRate <= 10.0f)
|
||||
value *= 1.05f + 0.075f * (10.0f - approachRate); // 7.5% for each AR below 10
|
||||
else if (approachRate > 10.0f)
|
||||
value *= 1.01f + 0.04f * (11.0f - Math.Min(11.0f, approachRate)); // 5% at AR 10, 1% at AR 11
|
||||
}
|
||||
|
||||
if (mods.Any(m => m is ModFlashlight))
|
||||
// Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps.
|
||||
|
@ -21,10 +21,12 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing
|
||||
public readonly float LastNormalizedPosition;
|
||||
|
||||
/// <summary>
|
||||
/// Milliseconds elapsed since the start time of the previous <see cref="CatchDifficultyHitObject"/>, with a minimum of 25ms.
|
||||
/// Milliseconds elapsed since the start time of the previous <see cref="CatchDifficultyHitObject"/>, with a minimum of 40ms.
|
||||
/// </summary>
|
||||
public readonly double StrainTime;
|
||||
|
||||
public readonly double ClockRate;
|
||||
|
||||
public CatchDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate, float halfCatcherWidth)
|
||||
: base(hitObject, lastObject, clockRate)
|
||||
{
|
||||
@ -34,8 +36,9 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing
|
||||
NormalizedPosition = BaseObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor;
|
||||
LastNormalizedPosition = LastObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor;
|
||||
|
||||
// Every strain interval is hard capped at the equivalent of 600 BPM streaming speed as a safety measure
|
||||
StrainTime = Math.Max(25, DeltaTime);
|
||||
// Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure
|
||||
StrainTime = Math.Max(40, DeltaTime);
|
||||
ClockRate = clockRate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,9 +13,9 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
|
||||
{
|
||||
private const float absolute_player_positioning_error = 16f;
|
||||
private const float normalized_hitobject_radius = 41.0f;
|
||||
private const double direction_change_bonus = 12.5;
|
||||
private const double direction_change_bonus = 21.0;
|
||||
|
||||
protected override double SkillMultiplier => 850;
|
||||
protected override double SkillMultiplier => 900;
|
||||
protected override double StrainDecayBase => 0.2;
|
||||
|
||||
protected override double DecayWeight => 0.94;
|
||||
@ -24,6 +24,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
|
||||
|
||||
private float? lastPlayerPosition;
|
||||
private float lastDistanceMoved;
|
||||
private double lastStrainTime;
|
||||
|
||||
public Movement(float halfCatcherWidth)
|
||||
{
|
||||
@ -45,47 +46,47 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
|
||||
|
||||
float distanceMoved = playerPosition - lastPlayerPosition.Value;
|
||||
|
||||
double distanceAddition = Math.Pow(Math.Abs(distanceMoved), 1.3) / 500;
|
||||
double sqrtStrain = Math.Sqrt(catchCurrent.StrainTime);
|
||||
double weightedStrainTime = catchCurrent.StrainTime + 13 + (3 / catchCurrent.ClockRate);
|
||||
|
||||
double bonus = 0;
|
||||
double distanceAddition = (Math.Pow(Math.Abs(distanceMoved), 1.3) / 510);
|
||||
double sqrtStrain = Math.Sqrt(weightedStrainTime);
|
||||
|
||||
// Direction changes give an extra point!
|
||||
double edgeDashBonus = 0;
|
||||
|
||||
// Direction change bonus.
|
||||
if (Math.Abs(distanceMoved) > 0.1)
|
||||
{
|
||||
if (Math.Abs(lastDistanceMoved) > 0.1 && Math.Sign(distanceMoved) != Math.Sign(lastDistanceMoved))
|
||||
{
|
||||
double bonusFactor = Math.Min(absolute_player_positioning_error, Math.Abs(distanceMoved)) / absolute_player_positioning_error;
|
||||
double bonusFactor = Math.Min(50, Math.Abs(distanceMoved)) / 50;
|
||||
double antiflowFactor = Math.Max(Math.Min(70, Math.Abs(lastDistanceMoved)) / 70, 0.38);
|
||||
|
||||
distanceAddition += direction_change_bonus / sqrtStrain * bonusFactor;
|
||||
|
||||
// Bonus for tougher direction switches and "almost" hyperdashes at this point
|
||||
if (catchCurrent.LastObject.DistanceToHyperDash <= 10 / CatchPlayfield.BASE_WIDTH)
|
||||
bonus = 0.3 * bonusFactor;
|
||||
distanceAddition += direction_change_bonus / Math.Sqrt(lastStrainTime + 16) * bonusFactor * antiflowFactor * Math.Max(1 - Math.Pow(weightedStrainTime / 1000, 3), 0);
|
||||
}
|
||||
|
||||
// Base bonus for every movement, giving some weight to streams.
|
||||
distanceAddition += 7.5 * Math.Min(Math.Abs(distanceMoved), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtStrain;
|
||||
distanceAddition += 12.5 * Math.Min(Math.Abs(distanceMoved), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtStrain;
|
||||
}
|
||||
|
||||
// Bonus for "almost" hyperdashes at corner points
|
||||
if (catchCurrent.LastObject.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH)
|
||||
// Bonus for edge dashes.
|
||||
if (catchCurrent.LastObject.DistanceToHyperDash <= 20.0f / CatchPlayfield.BASE_WIDTH)
|
||||
{
|
||||
if (!catchCurrent.LastObject.HyperDash)
|
||||
bonus += 1.0;
|
||||
edgeDashBonus += 5.7;
|
||||
else
|
||||
{
|
||||
// After a hyperdash we ARE in the correct position. Always!
|
||||
playerPosition = catchCurrent.NormalizedPosition;
|
||||
}
|
||||
|
||||
distanceAddition *= 1.0 + bonus * ((10 - catchCurrent.LastObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 10);
|
||||
distanceAddition *= 1.0 + edgeDashBonus * ((20 - catchCurrent.LastObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 20) * Math.Pow((Math.Min(catchCurrent.StrainTime * catchCurrent.ClockRate, 265) / 265), 1.5); // Edge Dashes are easier at lower ms values
|
||||
}
|
||||
|
||||
lastPlayerPosition = playerPosition;
|
||||
lastDistanceMoved = distanceMoved;
|
||||
lastStrainTime = catchCurrent.StrainTime;
|
||||
|
||||
return distanceAddition / catchCurrent.StrainTime;
|
||||
return distanceAddition / weightedStrainTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -43,5 +43,25 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// </summary>
|
||||
[Description(@"Perfect")]
|
||||
Perfect,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates small tick miss.
|
||||
/// </summary>
|
||||
SmallTickMiss,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates a small tick hit.
|
||||
/// </summary>
|
||||
SmallTickHit,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates a large tick miss.
|
||||
/// </summary>
|
||||
LargeTickMiss,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates a large tick hit.
|
||||
/// </summary>
|
||||
LargeTickHit
|
||||
}
|
||||
}
|
||||
|
@ -66,6 +66,9 @@ namespace osu.Game.Scoring.Legacy
|
||||
{
|
||||
case 3:
|
||||
return scoreInfo.Statistics[HitResult.Good];
|
||||
|
||||
case 2:
|
||||
return scoreInfo.Statistics[HitResult.SmallTickMiss];
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -78,6 +81,10 @@ namespace osu.Game.Scoring.Legacy
|
||||
case 3:
|
||||
scoreInfo.Statistics[HitResult.Good] = value;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
scoreInfo.Statistics[HitResult.SmallTickMiss] = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,6 +98,9 @@ namespace osu.Game.Scoring.Legacy
|
||||
|
||||
case 3:
|
||||
return scoreInfo.Statistics[HitResult.Ok];
|
||||
|
||||
case 2:
|
||||
return scoreInfo.Statistics[HitResult.LargeTickHit];
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -108,6 +118,10 @@ namespace osu.Game.Scoring.Legacy
|
||||
case 3:
|
||||
scoreInfo.Statistics[HitResult.Ok] = value;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
scoreInfo.Statistics[HitResult.LargeTickHit] = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,6 +132,9 @@ namespace osu.Game.Scoring.Legacy
|
||||
case 0:
|
||||
case 3:
|
||||
return scoreInfo.Statistics[HitResult.Meh];
|
||||
|
||||
case 2:
|
||||
return scoreInfo.Statistics[HitResult.SmallTickHit];
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -131,6 +148,10 @@ namespace osu.Game.Scoring.Legacy
|
||||
case 3:
|
||||
scoreInfo.Statistics[HitResult.Meh] = value;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
scoreInfo.Statistics[HitResult.SmallTickHit] = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user