-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Move osu!catch movement diffcalc to an evaluator #32655
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
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
57ab8b3
Move osu!catch movement state into `CatchDifficultyHitObject`
wulpine 81c48a3
Move osu!catch movement difficulty calculation to an evaluator
wulpine 3510a7f
Add documentation for `CatchDifficultyHitObject` fields
wulpine 6ff3494
Merge branch 'pp-dev' into movement-evaluator
tsunyoku File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
65 changes: 65 additions & 0 deletions
65
osu.Game.Rulesets.Catch/Difficulty/Evaluators/MovementEvaluator.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| // Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence. | ||
| // See the LICENCE file in the repository root for full licence text. | ||
|
|
||
| using System; | ||
| using osu.Game.Rulesets.Catch.Difficulty.Preprocessing; | ||
| using osu.Game.Rulesets.Difficulty.Preprocessing; | ||
|
|
||
| namespace osu.Game.Rulesets.Catch.Difficulty.Evaluators | ||
| { | ||
| public static class MovementEvaluator | ||
| { | ||
| private const double direction_change_bonus = 21.0; | ||
|
|
||
| public static double EvaluateDifficultyOf(DifficultyHitObject current, double catcherSpeedMultiplier) | ||
| { | ||
| var catchCurrent = (CatchDifficultyHitObject)current; | ||
| var catchLast = (CatchDifficultyHitObject)current.Previous(0); | ||
| var catchLastLast = (CatchDifficultyHitObject)current.Previous(1); | ||
|
|
||
| double weightedStrainTime = catchCurrent.StrainTime + 13 + (3 / catcherSpeedMultiplier); | ||
|
|
||
| double distanceAddition = (Math.Pow(Math.Abs(catchCurrent.DistanceMoved), 1.3) / 510); | ||
| double sqrtStrain = Math.Sqrt(weightedStrainTime); | ||
|
|
||
| double edgeDashBonus = 0; | ||
|
|
||
| // Direction change bonus. | ||
| if (Math.Abs(catchCurrent.DistanceMoved) > 0.1) | ||
| { | ||
| if (current.Index >= 1 && Math.Abs(catchLast.DistanceMoved) > 0.1 && Math.Sign(catchCurrent.DistanceMoved) != Math.Sign(catchLast.DistanceMoved)) | ||
| { | ||
| double bonusFactor = Math.Min(50, Math.Abs(catchCurrent.DistanceMoved)) / 50; | ||
| double antiflowFactor = Math.Max(Math.Min(70, Math.Abs(catchLast.DistanceMoved)) / 70, 0.38); | ||
|
|
||
| distanceAddition += direction_change_bonus / Math.Sqrt(catchLast.StrainTime + 16) * bonusFactor * antiflowFactor * Math.Max(1 - Math.Pow(weightedStrainTime / 1000, 3), 0); | ||
| } | ||
|
|
||
| // Base bonus for every movement, giving some weight to streams. | ||
| distanceAddition += 12.5 * Math.Min(Math.Abs(catchCurrent.DistanceMoved), CatchDifficultyHitObject.NORMALIZED_HALF_CATCHER_WIDTH * 2) | ||
| / (CatchDifficultyHitObject.NORMALIZED_HALF_CATCHER_WIDTH * 6) / sqrtStrain; | ||
| } | ||
|
|
||
| // Bonus for edge dashes. | ||
| if (catchCurrent.LastObject.DistanceToHyperDash <= 20.0f) | ||
| { | ||
| if (!catchCurrent.LastObject.HyperDash) | ||
| edgeDashBonus += 5.7; | ||
|
|
||
| distanceAddition *= 1.0 + edgeDashBonus * ((20 - catchCurrent.LastObject.DistanceToHyperDash) / 20) | ||
| * Math.Pow((Math.Min(catchCurrent.StrainTime * catcherSpeedMultiplier, 265) / 265), 1.5); // Edge Dashes are easier at lower ms values | ||
| } | ||
|
|
||
| // There is an edge case where horizontal back and forth sliders create "buzz" patterns which are repeated "movements" with a distance lower than | ||
| // the platter's width but high enough to be considered a movement due to the absolute_player_positioning_error and NORMALIZED_HALF_CATCHER_WIDTH offsets | ||
| // We are detecting this exact scenario. The first back and forth is counted but all subsequent ones are nullified. | ||
| // To achieve that, we need to store the exact distances (distance ignoring absolute_player_positioning_error and NORMALIZED_HALF_CATCHER_WIDTH) | ||
| if (current.Index >= 2 && Math.Abs(catchCurrent.ExactDistanceMoved) <= CatchDifficultyHitObject.NORMALIZED_HALF_CATCHER_WIDTH * 2 | ||
| && catchCurrent.ExactDistanceMoved == -catchLast.ExactDistanceMoved && catchLast.ExactDistanceMoved == -catchLastLast.ExactDistanceMoved | ||
| && catchCurrent.StrainTime == catchLast.StrainTime && catchLast.StrainTime == catchLastLast.StrainTime) | ||
| distanceAddition = 0; | ||
|
|
||
| return distanceAddition / weightedStrainTime; | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,7 @@ | ||
| // Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence. | ||
| // See the LICENCE file in the repository root for full licence text. | ||
|
|
||
| using System; | ||
| using osu.Game.Rulesets.Catch.Difficulty.Preprocessing; | ||
| using osu.Game.Rulesets.Catch.Difficulty.Evaluators; | ||
| using osu.Game.Rulesets.Difficulty.Preprocessing; | ||
| using osu.Game.Rulesets.Difficulty.Skills; | ||
| using osu.Game.Rulesets.Mods; | ||
|
|
@@ -11,9 +10,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills | |
| { | ||
| public class Movement : StrainDecaySkill | ||
| { | ||
| private const float absolute_player_positioning_error = 16f; | ||
| private const double direction_change_bonus = 21.0; | ||
|
|
||
| protected override double SkillMultiplier => 1; | ||
| protected override double StrainDecayBase => 0.2; | ||
|
|
||
|
|
@@ -23,12 +19,6 @@ public class Movement : StrainDecaySkill | |
|
|
||
| protected readonly float HalfCatcherWidth; | ||
|
|
||
| private float? lastPlayerPosition; | ||
| private float lastDistanceMoved; | ||
| private float lastExactDistanceMoved; | ||
| private double lastStrainTime; | ||
| private bool isInBuzzSection; | ||
|
|
||
| /// <summary> | ||
| /// The speed multiplier applied to the player's catcher. | ||
| /// </summary> | ||
|
|
@@ -48,82 +38,7 @@ public Movement(Mod[] mods, float halfCatcherWidth, double clockRate) | |
|
|
||
| protected override double StrainValueOf(DifficultyHitObject current) | ||
| { | ||
| var catchCurrent = (CatchDifficultyHitObject)current; | ||
|
|
||
| lastPlayerPosition ??= catchCurrent.LastNormalizedPosition; | ||
|
|
||
| float playerPosition = Math.Clamp( | ||
| lastPlayerPosition.Value, | ||
| catchCurrent.NormalizedPosition - (CatchDifficultyHitObject.NORMALIZED_HALF_CATCHER_WIDTH - absolute_player_positioning_error), | ||
| catchCurrent.NormalizedPosition + (CatchDifficultyHitObject.NORMALIZED_HALF_CATCHER_WIDTH - absolute_player_positioning_error) | ||
| ); | ||
|
|
||
| float distanceMoved = playerPosition - lastPlayerPosition.Value; | ||
|
|
||
| // For the exact position we consider that the catcher is in the correct position for both objects | ||
| float exactDistanceMoved = catchCurrent.NormalizedPosition - lastPlayerPosition.Value; | ||
|
|
||
| double weightedStrainTime = catchCurrent.StrainTime + 13 + (3 / catcherSpeedMultiplier); | ||
|
|
||
| double distanceAddition = (Math.Pow(Math.Abs(distanceMoved), 1.3) / 510); | ||
| double sqrtStrain = Math.Sqrt(weightedStrainTime); | ||
|
|
||
| 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(50, Math.Abs(distanceMoved)) / 50; | ||
| double antiflowFactor = Math.Max(Math.Min(70, Math.Abs(lastDistanceMoved)) / 70, 0.38); | ||
|
|
||
| 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 += 12.5 * Math.Min(Math.Abs(distanceMoved), CatchDifficultyHitObject.NORMALIZED_HALF_CATCHER_WIDTH * 2) / (CatchDifficultyHitObject.NORMALIZED_HALF_CATCHER_WIDTH * 6) | ||
| / sqrtStrain; | ||
| } | ||
|
|
||
| // Bonus for edge dashes. | ||
| if (catchCurrent.LastObject.DistanceToHyperDash <= 20.0f) | ||
| { | ||
| if (!catchCurrent.LastObject.HyperDash) | ||
| edgeDashBonus += 5.7; | ||
| else | ||
| { | ||
| // After a hyperdash we ARE in the correct position. Always! | ||
| playerPosition = catchCurrent.NormalizedPosition; | ||
| } | ||
|
|
||
| distanceAddition *= 1.0 + edgeDashBonus * ((20 - catchCurrent.LastObject.DistanceToHyperDash) / 20) | ||
| * Math.Pow((Math.Min(catchCurrent.StrainTime * catcherSpeedMultiplier, 265) / 265), 1.5); // Edge Dashes are easier at lower ms values | ||
| } | ||
|
|
||
| // There is an edge case where horizontal back and forth sliders create "buzz" patterns which are repeated "movements" with a distance lower than | ||
| // the platter's width but high enough to be considered a movement due to the absolute_player_positioning_error and NORMALIZED_HALF_CATCHER_WIDTH offsets | ||
| // We are detecting this exact scenario. The first back and forth is counted but all subsequent ones are nullified. | ||
| // To achieve that, we need to store the exact distances (distance ignoring absolute_player_positioning_error and NORMALIZED_HALF_CATCHER_WIDTH) | ||
| if (Math.Abs(exactDistanceMoved) <= CatchDifficultyHitObject.NORMALIZED_HALF_CATCHER_WIDTH * 2 && exactDistanceMoved == -lastExactDistanceMoved | ||
| && catchCurrent.StrainTime == lastStrainTime) | ||
| { | ||
| if (isInBuzzSection) | ||
| distanceAddition = 0; | ||
| else | ||
| isInBuzzSection = true; | ||
| } | ||
| else | ||
| { | ||
| isInBuzzSection = false; | ||
| } | ||
|
|
||
| lastPlayerPosition = playerPosition; | ||
| lastDistanceMoved = distanceMoved; | ||
| lastStrainTime = catchCurrent.StrainTime; | ||
| lastExactDistanceMoved = exactDistanceMoved; | ||
|
|
||
| return distanceAddition / weightedStrainTime; | ||
| return MovementEvaluator.EvaluateDifficultyOf(current, catcherSpeedMultiplier); | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did you change the buzz slider logic? I understand that you may want to have a stateless logic (removing the buzz section variable) but that's not the point of this PR. I don't mind that you changed variable names but logic changes unrelated to the PR should be kept separate imo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fyi evaluators shouldn't hold any state, so if you want it to retain the logic the variable would have to be moved to the
CatchDifficultyHitObjectprobablyUh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not that I just want stateless logic, it's required, no? The buzz section variable won't work cause it's stateful, so I had to add more conditions.
current.Index >= 2is a guard—in the very beginning of the mapcatchLastandcatchLastLastwill be null. Everything else has the same logic, but an extra fruit gets checked to account for lack of the buzz section variable. The smoogisheet showed no SR changes.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also recreating the variable in
CatchDifficultyHitObjectwould require moving the buzz slider logic there too. I just don't see much point in it just for the sake of making the conditions look the same as before.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay thanks for the explanations, I don't mind the changes if stateless is required 👍