mirror of
https://github.com/ppy/osu.git
synced 2025-01-14 00:42:55 +08:00
Merge pull request #3564 from smoogipoo/tighten-diffcalc-tolerances
Make difficulty calculation closer match osu!stable
This commit is contained in:
commit
a1032a94bf
@ -10,6 +10,8 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
{
|
||||
public class OsuBeatmapProcessor : BeatmapProcessor
|
||||
{
|
||||
private const int stack_distance = 3;
|
||||
|
||||
public OsuBeatmapProcessor(IBeatmap beatmap)
|
||||
: base(beatmap)
|
||||
{
|
||||
@ -18,17 +20,21 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
public override void PostProcess()
|
||||
{
|
||||
base.PostProcess();
|
||||
applyStacking((Beatmap<OsuHitObject>)Beatmap);
|
||||
|
||||
var osuBeatmap = (Beatmap<OsuHitObject>)Beatmap;
|
||||
|
||||
// Reset stacking
|
||||
foreach (var h in osuBeatmap.HitObjects)
|
||||
h.StackHeight = 0;
|
||||
|
||||
if (Beatmap.BeatmapInfo.BeatmapVersion >= 6)
|
||||
applyStacking(osuBeatmap);
|
||||
else
|
||||
applyStackingOld(osuBeatmap);
|
||||
}
|
||||
|
||||
private void applyStacking(Beatmap<OsuHitObject> beatmap)
|
||||
{
|
||||
const int stack_distance = 3;
|
||||
|
||||
// Reset stacking
|
||||
for (int i = 0; i <= beatmap.HitObjects.Count - 1; i++)
|
||||
beatmap.HitObjects[i].StackHeight = 0;
|
||||
|
||||
// Extend the end index to include objects they are stacked on
|
||||
int extendedEndIndex = beatmap.HitObjects.Count - 1;
|
||||
for (int i = beatmap.HitObjects.Count - 1; i >= 0; i--)
|
||||
@ -167,5 +173,40 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applyStackingOld(Beatmap<OsuHitObject> beatmap)
|
||||
{
|
||||
for (int i = 0; i < beatmap.HitObjects.Count; i++)
|
||||
{
|
||||
OsuHitObject currHitObject = beatmap.HitObjects[i];
|
||||
|
||||
if (currHitObject.StackHeight != 0 && !(currHitObject is Slider))
|
||||
continue;
|
||||
|
||||
double startTime = (currHitObject as IHasEndTime)?.EndTime ?? currHitObject.StartTime;
|
||||
int sliderStack = 0;
|
||||
|
||||
for (int j = i + 1; j < beatmap.HitObjects.Count; j++)
|
||||
{
|
||||
double stackThreshold = beatmap.HitObjects[i].TimePreempt * beatmap.BeatmapInfo.StackLeniency;
|
||||
|
||||
if (beatmap.HitObjects[j].StartTime - stackThreshold > startTime)
|
||||
break;
|
||||
|
||||
if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, currHitObject.Position) < stack_distance)
|
||||
{
|
||||
currHitObject.StackHeight++;
|
||||
startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[i].StartTime;
|
||||
}
|
||||
else if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, currHitObject.EndPosition) < stack_distance)
|
||||
{
|
||||
//Case for sliders - bump notes down and right, rather than up and left.
|
||||
sliderStack++;
|
||||
beatmap.HitObjects[j].StackHeight -= sliderStack;
|
||||
startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[i].StartTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
double sectionLength = section_length * timeRate;
|
||||
|
||||
// The first object doesn't generate a strain, so we begin with an incremented section end
|
||||
double currentSectionEnd = 2 * sectionLength;
|
||||
double currentSectionEnd = Math.Ceiling(beatmap.HitObjects.First().StartTime / sectionLength) * sectionLength;
|
||||
|
||||
foreach (OsuDifficultyHitObject h in difficultyBeatmap)
|
||||
{
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
@ -23,8 +24,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
{
|
||||
// Sort OsuHitObjects by StartTime - they are not correctly ordered in some cases.
|
||||
// This should probably happen before the objects reach the difficulty calculator.
|
||||
objects.Sort((a, b) => a.StartTime.CompareTo(b.StartTime));
|
||||
difficultyObjects = createDifficultyObjectEnumerator(objects, timeRate);
|
||||
difficultyObjects = createDifficultyObjectEnumerator(objects.OrderBy(h => h.StartTime).ToList(), timeRate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -21,15 +21,25 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
public OsuHitObject BaseObject { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Normalized distance from the <see cref="OsuHitObject.StackedPosition"/> of the previous <see cref="OsuDifficultyHitObject"/>.
|
||||
/// Normalized distance from the end position of the previous <see cref="OsuDifficultyHitObject"/> to the start position of this <see cref="OsuDifficultyHitObject"/>.
|
||||
/// </summary>
|
||||
public double Distance { get; private set; }
|
||||
public double JumpDistance { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Normalized distance between the start and end position of the previous <see cref="OsuDifficultyHitObject"/>.
|
||||
/// </summary>
|
||||
public double TravelDistance { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Milliseconds elapsed since the StartTime of the previous <see cref="OsuDifficultyHitObject"/>.
|
||||
/// </summary>
|
||||
public double DeltaTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Milliseconds elapsed since the start time of the previous <see cref="OsuDifficultyHitObject"/>, with a minimum of 50ms.
|
||||
/// </summary>
|
||||
public double StrainTime { get; private set; }
|
||||
|
||||
private readonly OsuHitObject lastObject;
|
||||
private readonly double timeRate;
|
||||
|
||||
@ -51,31 +61,37 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
private void setDistances()
|
||||
{
|
||||
// We will scale distances by this factor, so we can assume a uniform CircleSize among beatmaps.
|
||||
double scalingFactor = normalized_radius / BaseObject.Radius;
|
||||
float scalingFactor = normalized_radius / (float)BaseObject.Radius;
|
||||
if (BaseObject.Radius < 30)
|
||||
{
|
||||
double smallCircleBonus = Math.Min(30 - BaseObject.Radius, 5) / 50;
|
||||
float smallCircleBonus = Math.Min(30 - (float)BaseObject.Radius, 5) / 50;
|
||||
scalingFactor *= 1 + smallCircleBonus;
|
||||
}
|
||||
|
||||
Vector2 lastCursorPosition = lastObject.StackedPosition;
|
||||
float lastTravelDistance = 0;
|
||||
|
||||
var lastSlider = lastObject as Slider;
|
||||
if (lastSlider != null)
|
||||
{
|
||||
computeSliderCursorPosition(lastSlider);
|
||||
lastCursorPosition = lastSlider.LazyEndPosition ?? lastCursorPosition;
|
||||
lastTravelDistance = lastSlider.LazyTravelDistance;
|
||||
}
|
||||
|
||||
Distance = (lastTravelDistance + (BaseObject.StackedPosition - lastCursorPosition).Length) * scalingFactor;
|
||||
// Don't need to jump to reach spinners
|
||||
if (!(BaseObject is Spinner))
|
||||
JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length;
|
||||
|
||||
// Todo: BUG!!! Last slider's travel distance is considered ONLY IF we ourselves are also a slider!
|
||||
if (BaseObject is Slider)
|
||||
TravelDistance = (lastSlider?.LazyTravelDistance ?? 0) * scalingFactor;
|
||||
}
|
||||
|
||||
private void setTimingValues()
|
||||
{
|
||||
// Every timing inverval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure.
|
||||
DeltaTime = Math.Max(50, (BaseObject.StartTime - lastObject.StartTime) / timeRate);
|
||||
DeltaTime = (BaseObject.StartTime - lastObject.StartTime) / timeRate;
|
||||
|
||||
// Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure
|
||||
StrainTime = Math.Max(50, DeltaTime);
|
||||
}
|
||||
|
||||
private void computeSliderCursorPosition(Slider slider)
|
||||
@ -87,8 +103,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
float approxFollowCircleRadius = (float)(slider.Radius * 3);
|
||||
var computeVertex = new Action<double>(t =>
|
||||
{
|
||||
double progress = ((int)t - (int)slider.StartTime) / (float)(int)slider.SpanDuration;
|
||||
if (progress % 2 > 1)
|
||||
progress = 1 - progress % 1;
|
||||
else
|
||||
progress = progress % 1;
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException (bugged in current r# version)
|
||||
var diff = slider.StackedPositionAt(t) - slider.LazyEndPosition.Value;
|
||||
var diff = slider.StackedPosition + slider.Curve.PositionAt(progress) - slider.LazyEndPosition.Value;
|
||||
float dist = diff.Length;
|
||||
|
||||
if (dist > approxFollowCircleRadius)
|
||||
|
@ -14,6 +14,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
protected override double SkillMultiplier => 26.25;
|
||||
protected override double StrainDecayBase => 0.15;
|
||||
|
||||
protected override double StrainValueOf(OsuDifficultyHitObject current) => Math.Pow(current.Distance, 0.99) / current.DeltaTime;
|
||||
protected override double StrainValueOf(OsuDifficultyHitObject current)
|
||||
=> (Math.Pow(current.TravelDistance, 0.99) + Math.Pow(current.JumpDistance, 0.99)) / current.StrainTime;
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
|
||||
protected override double StrainValueOf(OsuDifficultyHitObject current)
|
||||
{
|
||||
double distance = current.Distance;
|
||||
double distance = current.TravelDistance + current.JumpDistance;
|
||||
|
||||
double speedValue;
|
||||
if (distance > single_spacing_threshold)
|
||||
@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
else
|
||||
speedValue = 0.95;
|
||||
|
||||
return speedValue / current.DeltaTime;
|
||||
return speedValue / current.StrainTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
/// <summary>
|
||||
/// The beat length at this control point.
|
||||
/// </summary>
|
||||
public double BeatLength
|
||||
public virtual double BeatLength
|
||||
{
|
||||
get => beatLength;
|
||||
set => beatLength = MathHelper.Clamp(value, 6, 60000);
|
||||
|
@ -318,12 +318,12 @@ namespace osu.Game.Beatmaps.Formats
|
||||
|
||||
if (timingChange)
|
||||
{
|
||||
handleTimingControlPoint(new TimingControlPoint
|
||||
{
|
||||
Time = time,
|
||||
BeatLength = beatLength,
|
||||
TimeSignature = timeSignature
|
||||
});
|
||||
var controlPoint = CreateTimingControlPoint();
|
||||
controlPoint.Time = time;
|
||||
controlPoint.BeatLength = beatLength;
|
||||
controlPoint.TimeSignature = timeSignature;
|
||||
|
||||
handleTimingControlPoint(controlPoint);
|
||||
}
|
||||
|
||||
handleDifficultyControlPoint(new DifficultyControlPoint
|
||||
@ -418,6 +418,8 @@ namespace osu.Game.Beatmaps.Formats
|
||||
|
||||
private double getOffsetTime(double time) => time + (ApplyOffsets ? offset : 0);
|
||||
|
||||
protected virtual TimingControlPoint CreateTimingControlPoint() => new TimingControlPoint();
|
||||
|
||||
[Flags]
|
||||
internal enum EffectFlags
|
||||
{
|
||||
|
@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
|
||||
namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="LegacyBeatmapDecoder"/> built for difficulty calculation of legacy <see cref="Beatmap"/>s
|
||||
/// <remarks>
|
||||
/// To use this, the decoder must be registered by the application through <see cref="LegacyDifficultyCalculatorBeatmapDecoder.Register"/>.
|
||||
/// Doing so will override any existing <see cref="Beatmap"/> decoders.
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public class LegacyDifficultyCalculatorBeatmapDecoder : LegacyBeatmapDecoder
|
||||
{
|
||||
public LegacyDifficultyCalculatorBeatmapDecoder(int version = LATEST_VERSION)
|
||||
: base(version)
|
||||
{
|
||||
ApplyOffsets = false;
|
||||
}
|
||||
|
||||
public new static void Register()
|
||||
{
|
||||
AddDecoder<Beatmap>(@"osu file format v", m => new LegacyDifficultyCalculatorBeatmapDecoder(int.Parse(m.Split('v').Last())));
|
||||
}
|
||||
|
||||
protected override TimingControlPoint CreateTimingControlPoint()
|
||||
=> new LegacyDifficultyCalculatorControlPoint();
|
||||
|
||||
private class LegacyDifficultyCalculatorControlPoint : TimingControlPoint
|
||||
{
|
||||
public override double BeatLength { get; set; } = 1000;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user