mirror of
https://github.com/ppy/osu.git
synced 2024-12-05 09:42:54 +08:00
Merge b56c86d6f7
into aa0ee5cf3a
This commit is contained in:
commit
90a2280809
@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.EmptyFreeform
|
||||
return new DifficultyAttributes(mods, 0);
|
||||
}
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, Mod[] mods, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
|
||||
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => Array.Empty<Skill>();
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.EmptyScrolling
|
||||
return new DifficultyAttributes(mods, 0);
|
||||
}
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, Mod[] mods, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
|
||||
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => Array.Empty<Skill>();
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Pippidon
|
||||
return new DifficultyAttributes(mods, 0);
|
||||
}
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, Mod[] mods, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
|
||||
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => Array.Empty<Skill>();
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
return attributes;
|
||||
}
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, Mod[] mods, double clockRate)
|
||||
{
|
||||
CatchHitObject? lastObject = null;
|
||||
|
||||
|
@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
return 1;
|
||||
}
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, Mod[] mods, double clockRate)
|
||||
{
|
||||
var sortedObjects = beatmap.HitObjects.ToArray();
|
||||
|
||||
|
@ -117,7 +117,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
return attributes;
|
||||
}
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, Mod[] mods, double clockRate)
|
||||
{
|
||||
List<DifficultyHitObject> objects = new List<DifficultyHitObject>();
|
||||
|
||||
@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
for (int i = 1; i < beatmap.HitObjects.Count; i++)
|
||||
{
|
||||
var lastLast = i > 1 ? beatmap.HitObjects[i - 2] : null;
|
||||
objects.Add(new OsuDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], lastLast, clockRate, objects, objects.Count));
|
||||
objects.Add(new OsuDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], lastLast, clockRate, objects, objects.Count, mods));
|
||||
}
|
||||
|
||||
return objects;
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
@ -89,11 +90,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
private readonly OsuHitObject? lastLastObject;
|
||||
private readonly OsuHitObject lastObject;
|
||||
|
||||
public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject? lastLastObject, double clockRate, List<DifficultyHitObject> objects, int index)
|
||||
private readonly Mod[] mods;
|
||||
|
||||
public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject? lastLastObject, double clockRate, List<DifficultyHitObject> objects, int index, Mod[] mods)
|
||||
: base(hitObject, lastObject, clockRate, objects, index)
|
||||
{
|
||||
this.lastLastObject = lastLastObject as OsuHitObject;
|
||||
this.lastObject = (OsuHitObject)lastObject;
|
||||
this.mods = mods;
|
||||
|
||||
// Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects.
|
||||
StrainTime = Math.Max(DeltaTime, MIN_DELTA_TIME);
|
||||
@ -334,6 +338,110 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
if (i == nestedObjects.Count - 1)
|
||||
slider.LazyEndPosition = currCursorPosition;
|
||||
}
|
||||
|
||||
if (mods.OfType<OsuModStrictTracking>().Any())
|
||||
{
|
||||
double strictPath = calculateStrictSliderPath(slider);
|
||||
|
||||
double normalVelocity = slider.LazyTravelTime > 0 ? slider.LazyTravelDistance / slider.LazyTravelTime : 0;
|
||||
double strictVelocity = slider.LazyTravelTime > 0 ? strictPath / slider.LazyTravelTime : 0;
|
||||
|
||||
if (strictVelocity > normalVelocity)
|
||||
{
|
||||
double deltaVelocity = strictVelocity - normalVelocity;
|
||||
|
||||
// This addiional velocity would require more complex movement so buff it
|
||||
deltaVelocity *= 1.5;
|
||||
|
||||
double multiplier = (normalVelocity + deltaVelocity) / strictVelocity;
|
||||
|
||||
slider.LazyTravelDistance = (float)(strictPath * multiplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Vector2 positionWithRepeats(double relativeTime, Slider slider, int repeat)
|
||||
{
|
||||
relativeTime -= slider.SpanDuration * repeat;
|
||||
|
||||
double progress = relativeTime / slider.SpanDuration;
|
||||
if (repeat % 2 == 1)
|
||||
progress = 1 - progress; // revert if odd number of repeats
|
||||
return slider.Path.PositionAt(progress);
|
||||
}
|
||||
|
||||
private double calculateStrictSliderPath(Slider slider)
|
||||
{
|
||||
// WARNING, this is lazer-correct implementation because stable doesn't have Strict Tracking mod
|
||||
// This means that path of this function can be lower than normal path
|
||||
double lazyEndTime = Math.Max(
|
||||
slider.StartTime + slider.Duration + SliderEventGenerator.TAIL_LENIENCY,
|
||||
slider.NestedHitObjects.LastOrDefault(n => n is not SliderTailCircle)?.StartTime ?? double.MinValue
|
||||
);
|
||||
|
||||
double startTime = slider.HeadCircle.StartTime;
|
||||
double totalTrackingTime = lazyEndTime - startTime;
|
||||
|
||||
if (totalTrackingTime == 0)
|
||||
return 0;
|
||||
|
||||
double numberOfUpdates = Math.Ceiling(slider.Path.Distance * (slider.RepeatCount + 1) / NORMALISED_RADIUS);
|
||||
|
||||
double currentTime;
|
||||
Vector2 currentCursorPosition = new Vector2();
|
||||
|
||||
double totalPath = 0;
|
||||
|
||||
double deltaT = totalTrackingTime / numberOfUpdates;
|
||||
double currentUpdateTime = deltaT;
|
||||
|
||||
double getNestedObjectStartTime(int index) => slider.NestedHitObjects[index].StartTime - startTime;
|
||||
int currentNestedObjectIndex = 1;
|
||||
double nestedObjectTime = getNestedObjectStartTime(1);
|
||||
|
||||
currentTime = Math.Min(currentUpdateTime, nestedObjectTime);
|
||||
|
||||
float scalingFactor = (float)(NORMALISED_RADIUS / slider.Radius);
|
||||
|
||||
// Adjust to be sure that there would be no floating point error
|
||||
double adjustedEndTime = totalTrackingTime + deltaT / 2;
|
||||
while (currentTime <= adjustedEndTime)
|
||||
{
|
||||
currentTime = Math.Min(currentUpdateTime, nestedObjectTime);
|
||||
if (currentTime > adjustedEndTime || currentTime >= slider.TailCircle.StartTime)
|
||||
break;
|
||||
|
||||
float currentRadius = assumed_slider_radius;
|
||||
|
||||
// Handle the scenario where we're doing normal update
|
||||
if (currentUpdateTime < nestedObjectTime)
|
||||
{
|
||||
currentUpdateTime += deltaT;
|
||||
}
|
||||
// Handle the scenario where we're updating for nested object
|
||||
else
|
||||
{
|
||||
// Repeats require more accurate movement
|
||||
currentRadius = slider.NestedHitObjects[currentNestedObjectIndex] is SliderRepeat ? NORMALISED_RADIUS : assumed_slider_radius;
|
||||
|
||||
currentNestedObjectIndex++;
|
||||
nestedObjectTime = currentNestedObjectIndex < slider.NestedHitObjects.Count ? getNestedObjectStartTime(currentNestedObjectIndex) : double.PositiveInfinity;
|
||||
}
|
||||
|
||||
int currentRepeat = (int)(currentTime / slider.SpanDuration);
|
||||
|
||||
Vector2 ballPosition = positionWithRepeats(currentTime, slider, currentRepeat);
|
||||
float distanceToCursor = Vector2.Distance(ballPosition, currentCursorPosition) * scalingFactor;
|
||||
|
||||
if (distanceToCursor <= currentRadius)
|
||||
continue;
|
||||
|
||||
float neededMovement = distanceToCursor - currentRadius;
|
||||
currentCursorPosition = Vector2.Lerp(currentCursorPosition, ballPosition, neededMovement / distanceToCursor);
|
||||
totalPath += neededMovement;
|
||||
}
|
||||
|
||||
return totalPath;
|
||||
}
|
||||
|
||||
private Vector2 getEndCursorPosition(OsuHitObject hitObject)
|
||||
|
@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
new TaikoModHardRock(),
|
||||
};
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, Mod[] mods, double clockRate)
|
||||
{
|
||||
List<DifficultyHitObject> difficultyHitObjects = new List<DifficultyHitObject>();
|
||||
List<TaikoDifficultyHitObject> centreObjects = new List<TaikoDifficultyHitObject>();
|
||||
|
@ -227,7 +227,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, Mod[] mods, double clockRate)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
@ -175,7 +175,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||
=> new TestDifficultyAttributes { Objects = beatmap.HitObjects.ToArray() };
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, Mod[] mods, double clockRate)
|
||||
{
|
||||
List<DifficultyHitObject> objects = new List<DifficultyHitObject>();
|
||||
|
||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
/// </summary>
|
||||
protected IBeatmap Beatmap { get; private set; }
|
||||
|
||||
private Mod[] playableMods;
|
||||
public Mod[] PlayableMods { get; private set; }
|
||||
private double clockRate;
|
||||
|
||||
private readonly IRulesetInfo ruleset;
|
||||
@ -65,10 +65,10 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
preProcess(mods, cancellationToken);
|
||||
|
||||
var skills = CreateSkills(Beatmap, playableMods, clockRate);
|
||||
var skills = CreateSkills(Beatmap, PlayableMods, clockRate);
|
||||
|
||||
if (!Beatmap.HitObjects.Any())
|
||||
return CreateDifficultyAttributes(Beatmap, playableMods, skills, clockRate);
|
||||
return CreateDifficultyAttributes(Beatmap, PlayableMods, skills, clockRate);
|
||||
|
||||
foreach (var hitObject in getDifficultyHitObjects())
|
||||
{
|
||||
@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
}
|
||||
}
|
||||
|
||||
return CreateDifficultyAttributes(Beatmap, playableMods, skills, clockRate);
|
||||
return CreateDifficultyAttributes(Beatmap, PlayableMods, skills, clockRate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -106,7 +106,7 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
if (!Beatmap.HitObjects.Any())
|
||||
return attribs;
|
||||
|
||||
var skills = CreateSkills(Beatmap, playableMods, clockRate);
|
||||
var skills = CreateSkills(Beatmap, PlayableMods, clockRate);
|
||||
var progressiveBeatmap = new ProgressiveCalculationBeatmap(Beatmap);
|
||||
var difficultyObjects = getDifficultyHitObjects().ToArray();
|
||||
|
||||
@ -127,7 +127,7 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
currentIndex++;
|
||||
}
|
||||
|
||||
attribs.Add(new TimedDifficultyAttributes(obj.GetEndTime(), CreateDifficultyAttributes(progressiveBeatmap, playableMods, skills, clockRate)));
|
||||
attribs.Add(new TimedDifficultyAttributes(obj.GetEndTime(), CreateDifficultyAttributes(progressiveBeatmap, PlayableMods, skills, clockRate)));
|
||||
}
|
||||
|
||||
return attribs;
|
||||
@ -159,7 +159,7 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
/// <summary>
|
||||
/// Retrieves the <see cref="DifficultyHitObject"/>s to calculate against.
|
||||
/// </summary>
|
||||
private IEnumerable<DifficultyHitObject> getDifficultyHitObjects() => SortObjects(CreateDifficultyHitObjects(Beatmap, clockRate));
|
||||
private IEnumerable<DifficultyHitObject> getDifficultyHitObjects() => SortObjects(CreateDifficultyHitObjects(Beatmap, PlayableMods, clockRate));
|
||||
|
||||
/// <summary>
|
||||
/// Performs required tasks before every calculation.
|
||||
@ -168,16 +168,16 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
private void preProcess([NotNull] IEnumerable<Mod> mods, CancellationToken cancellationToken = default)
|
||||
{
|
||||
playableMods = mods.Select(m => m.DeepClone()).ToArray();
|
||||
PlayableMods = mods.Select(m => m.DeepClone()).ToArray();
|
||||
|
||||
// Only pass through the cancellation token if it's non-default.
|
||||
// This allows for the default timeout to be applied for playable beatmap construction.
|
||||
Beatmap = cancellationToken == default
|
||||
? beatmap.GetPlayableBeatmap(ruleset, playableMods)
|
||||
: beatmap.GetPlayableBeatmap(ruleset, playableMods, cancellationToken);
|
||||
? beatmap.GetPlayableBeatmap(ruleset, PlayableMods)
|
||||
: beatmap.GetPlayableBeatmap(ruleset, PlayableMods, cancellationToken);
|
||||
|
||||
var track = new TrackVirtual(10000);
|
||||
playableMods.OfType<IApplicableToTrack>().ForEach(m => m.ApplyToTrack(track));
|
||||
PlayableMods.OfType<IApplicableToTrack>().ForEach(m => m.ApplyToTrack(track));
|
||||
clockRate = track.Rate;
|
||||
}
|
||||
|
||||
@ -276,9 +276,10 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
/// Enumerates <see cref="DifficultyHitObject"/>s to be processed from <see cref="HitObject"/>s in the <see cref="IBeatmap"/>.
|
||||
/// </summary>
|
||||
/// <param name="beatmap">The <see cref="IBeatmap"/> providing the <see cref="HitObject"/>s to enumerate.</param>
|
||||
/// <param name="mods"></param>
|
||||
/// <param name="clockRate">The rate at which the gameplay clock is run at.</param>
|
||||
/// <returns>The enumerated <see cref="DifficultyHitObject"/>s.</returns>
|
||||
protected abstract IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate);
|
||||
protected abstract IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, Mod[] mods, double clockRate);
|
||||
|
||||
/// <summary>
|
||||
/// Creates the <see cref="Skill"/>s to calculate the difficulty of an <see cref="IBeatmap"/>.
|
||||
|
Loading…
Reference in New Issue
Block a user