1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 20:22:55 +08:00

Merge pull request #3839 from smoogipoo/xexxar-angles

Implement xexxar’s aim assessment
This commit is contained in:
Dean Herbert 2019-01-29 16:43:15 +09:00 committed by GitHub
commit 432bc60db6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 107 additions and 25 deletions

View File

@ -38,7 +38,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
// The first jump is formed by the first two hitobjects of the map.
// If the map has less than two OsuHitObjects, the enumerator will not return anything.
for (int i = 1; i < objects.Count; i++)
yield return new OsuDifficultyHitObject(objects[i], objects[i - 1], timeRate);
{
var lastLast = i > 1 ? objects[i - 2] : null;
var last = objects[i - 1];
var current = objects[i];
yield return new OsuDifficultyHitObject(lastLast, last, current, timeRate);
}
}
}
}

View File

@ -40,14 +40,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
/// </summary>
public double StrainTime { get; private set; }
/// <summary>
/// Angle the player has to take to hit this <see cref="OsuDifficultyHitObject"/>.
/// Calculated as the angle between the circles (current-2, current-1, current).
/// </summary>
public double? Angle { get; private set; }
private readonly OsuHitObject lastLastObject;
private readonly OsuHitObject lastObject;
private readonly double timeRate;
/// <summary>
/// Initializes the object calculating extra data required for difficulty calculation.
/// </summary>
public OsuDifficultyHitObject(OsuHitObject currentObject, OsuHitObject lastObject, double timeRate)
public OsuDifficultyHitObject(OsuHitObject lastLastObject, OsuHitObject lastObject, OsuHitObject currentObject, double timeRate)
{
this.lastLastObject = lastLastObject;
this.lastObject = lastObject;
this.timeRate = timeRate;
@ -68,20 +76,30 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
scalingFactor *= 1 + smallCircleBonus;
}
Vector2 lastCursorPosition = lastObject.StackedPosition;
var lastSlider = lastObject as Slider;
if (lastSlider != null)
if (lastObject is Slider lastSlider)
{
computeSliderCursorPosition(lastSlider);
lastCursorPosition = lastSlider.LazyEndPosition ?? lastCursorPosition;
TravelDistance = lastSlider.LazyTravelDistance * scalingFactor;
}
Vector2 lastCursorPosition = getEndCursorPosition(lastObject);
// Don't need to jump to reach spinners
if (!(BaseObject is Spinner))
JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length;
if (lastLastObject != null)
{
Vector2 lastLastCursorPosition = getEndCursorPosition(lastLastObject);
Vector2 v1 = lastLastCursorPosition - lastObject.StackedPosition;
Vector2 v2 = BaseObject.StackedPosition - lastCursorPosition;
float dot = Vector2.Dot(v1, v2);
float det = v1.X * v2.Y - v1.Y * v2.X;
Angle = Math.Abs(Math.Atan2(det, dot));
}
}
private void setTimingValues()
@ -127,5 +145,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
computeVertex(time);
computeVertex(slider.EndTime);
}
private Vector2 getEndCursorPosition(OsuHitObject hitObject)
{
Vector2 pos = hitObject.StackedPosition;
var slider = hitObject as Slider;
if (slider != null)
{
computeSliderCursorPosition(slider);
pos = slider.LazyEndPosition ?? pos;
}
return pos;
}
}
}

View File

@ -11,10 +11,39 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
/// </summary>
public class Aim : Skill
{
private const double angle_bonus_begin = Math.PI / 3;
private const double timing_threshold = 107;
protected override double SkillMultiplier => 26.25;
protected override double StrainDecayBase => 0.15;
protected override double StrainValueOf(OsuDifficultyHitObject current)
=> (Math.Pow(current.TravelDistance, 0.99) + Math.Pow(current.JumpDistance, 0.99)) / current.StrainTime;
{
double result = 0;
const double scale = 90;
double applyDiminishingExp(double val) => Math.Pow(val, 0.99);
if (Previous.Count > 0)
{
if (current.Angle != null && current.Angle.Value > angle_bonus_begin)
{
var angleBonus = Math.Sqrt(
Math.Max(Previous[0].JumpDistance - scale, 0)
* Math.Pow(Math.Sin(current.Angle.Value - angle_bonus_begin), 2)
* Math.Max(current.JumpDistance - scale, 0));
result = 1.5 * applyDiminishingExp(Math.Max(0, angleBonus)) / Math.Max(timing_threshold, Previous[0].StrainTime);
}
}
double jumpDistanceExp = applyDiminishingExp(current.JumpDistance);
double travelDistanceExp = applyDiminishingExp(current.TravelDistance);
return Math.Max(
result + (jumpDistanceExp + travelDistanceExp + Math.Sqrt(travelDistanceExp * jumpDistanceExp)) / Math.Max(current.StrainTime, timing_threshold),
(Math.Sqrt(travelDistanceExp * jumpDistanceExp) + jumpDistanceExp + travelDistanceExp) / current.StrainTime
);
}
}
}

View File

@ -15,6 +15,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
/// </summary>
public abstract class Skill
{
protected const double SINGLE_SPACING_THRESHOLD = 125;
protected const double STREAM_SPACING_THRESHOLD = 110;
/// <summary>
/// Strain values are multiplied by this number for the given skill. Used to balance the value of different skills between each other.
/// </summary>

View File

@ -1,6 +1,7 @@
// 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.Osu.Difficulty.Preprocessing;
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
@ -10,30 +11,41 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
/// </summary>
public class Speed : Skill
{
private const double angle_bonus_begin = 5 * Math.PI / 6;
private const double pi_over_4 = Math.PI / 4;
private const double pi_over_2 = Math.PI / 2;
protected override double SkillMultiplier => 1400;
protected override double StrainDecayBase => 0.3;
private const double single_spacing_threshold = 125;
private const double stream_spacing_threshold = 110;
private const double almost_diameter = 90;
private const double min_speed_bonus = 75; // ~200BPM
private const double max_speed_bonus = 45; // ~330BPM
private const double speed_balancing_factor = 40;
protected override double StrainValueOf(OsuDifficultyHitObject current)
{
double distance = current.TravelDistance + current.JumpDistance;
double distance = Math.Min(SINGLE_SPACING_THRESHOLD, current.TravelDistance + current.JumpDistance);
double deltaTime = Math.Max(max_speed_bonus, current.DeltaTime);
double speedValue;
if (distance > single_spacing_threshold)
speedValue = 2.5;
else if (distance > stream_spacing_threshold)
speedValue = 1.6 + 0.9 * (distance - stream_spacing_threshold) / (single_spacing_threshold - stream_spacing_threshold);
else if (distance > almost_diameter)
speedValue = 1.2 + 0.4 * (distance - almost_diameter) / (stream_spacing_threshold - almost_diameter);
else if (distance > almost_diameter / 2)
speedValue = 0.95 + 0.25 * (distance - almost_diameter / 2) / (almost_diameter / 2);
else
speedValue = 0.95;
double speedBonus = 1.0;
if (deltaTime < min_speed_bonus)
speedBonus = 1 + Math.Pow((min_speed_bonus - deltaTime) / speed_balancing_factor, 2);
return speedValue / current.StrainTime;
double angleBonus = 1.0;
if (current.Angle != null && current.Angle.Value < angle_bonus_begin)
{
angleBonus = 1 + Math.Pow(Math.Sin(1.5 * (angle_bonus_begin - current.Angle.Value)), 2) / 3.57;
if (current.Angle.Value < pi_over_2)
{
angleBonus = 1.28;
if (distance < 90 && current.Angle.Value < pi_over_4)
angleBonus += (1 - angleBonus) * Math.Min((90 - distance) / 10, 1);
else if (distance < 90)
angleBonus += (1 - angleBonus) * Math.Min((90 - distance) / 10, 1) * Math.Sin((pi_over_2 - current.Angle.Value) / pi_over_4);
}
}
return (1 + (speedBonus - 1) * 0.75) * angleBonus * (0.95 + speedBonus * Math.Pow(distance / SINGLE_SPACING_THRESHOLD, 3.5)) / current.StrainTime;
}
}
}