1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-30 01:32:55 +08:00

Use integer calculations to replicate stable's slider conversion

This commit is contained in:
smoogipoo 2020-10-09 21:22:13 +09:00
parent 5f19081db6
commit 08f3481b59

View File

@ -3,8 +3,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using osu.Framework.Utils;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Rulesets.Mania.MathUtils;
@ -12,6 +12,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Formats;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{ {
@ -25,8 +26,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// </summary> /// </summary>
private const float osu_base_scoring_distance = 100; private const float osu_base_scoring_distance = 100;
public readonly double EndTime; public readonly int StartTime;
public readonly double SegmentDuration; public readonly int EndTime;
public readonly int SegmentDuration;
public readonly int SpanCount; public readonly int SpanCount;
private PatternType convertType; private PatternType convertType;
@ -41,20 +43,25 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
var distanceData = hitObject as IHasDistance; var distanceData = hitObject as IHasDistance;
var repeatsData = hitObject as IHasRepeats; var repeatsData = hitObject as IHasRepeats;
SpanCount = repeatsData?.SpanCount() ?? 1; Debug.Assert(distanceData != null);
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime); DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime);
// The true distance, accounting for any repeats double beatLength;
double distance = (distanceData?.Distance ?? 0) * SpanCount; #pragma warning disable 618
// The velocity of the osu! hit object - calculated as the velocity of a slider if (difficultyPoint is LegacyBeatmapDecoder.LegacyDifficultyControlPoint legacyDifficultyPoint)
double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / timingPoint.BeatLength; #pragma warning restore 618
// The duration of the osu! hit object beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier;
double osuDuration = distance / osuVelocity; else
beatLength = timingPoint.BeatLength / difficultyPoint.SpeedMultiplier;
EndTime = hitObject.StartTime + osuDuration; SpanCount = repeatsData?.SpanCount() ?? 1;
SegmentDuration = (EndTime - HitObject.StartTime) / SpanCount;
StartTime = (int)Math.Round(hitObject.StartTime);
EndTime = (int)Math.Floor(StartTime + distanceData.Distance * beatLength * SpanCount * 0.01 / beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier);
SegmentDuration = (EndTime - StartTime) / SpanCount;
} }
public override IEnumerable<Pattern> Generate() public override IEnumerable<Pattern> Generate()
@ -76,7 +83,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
foreach (var obj in originalPattern.HitObjects) foreach (var obj in originalPattern.HitObjects)
{ {
if (!Precision.AlmostEquals(EndTime, obj.GetEndTime())) if (EndTime != (int)Math.Round(obj.GetEndTime()))
intermediatePattern.Add(obj); intermediatePattern.Add(obj);
else else
endTimePattern.Add(obj); endTimePattern.Add(obj);
@ -91,35 +98,35 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (TotalColumns == 1) if (TotalColumns == 1)
{ {
var pattern = new Pattern(); var pattern = new Pattern();
addToPattern(pattern, 0, HitObject.StartTime, EndTime); addToPattern(pattern, 0, StartTime, EndTime);
return pattern; return pattern;
} }
if (SpanCount > 1) if (SpanCount > 1)
{ {
if (SegmentDuration <= 90) if (SegmentDuration <= 90)
return generateRandomHoldNotes(HitObject.StartTime, 1); return generateRandomHoldNotes(StartTime, 1);
if (SegmentDuration <= 120) if (SegmentDuration <= 120)
{ {
convertType |= PatternType.ForceNotStack; convertType |= PatternType.ForceNotStack;
return generateRandomNotes(HitObject.StartTime, SpanCount + 1); return generateRandomNotes(StartTime, SpanCount + 1);
} }
if (SegmentDuration <= 160) if (SegmentDuration <= 160)
return generateStair(HitObject.StartTime); return generateStair(StartTime);
if (SegmentDuration <= 200 && ConversionDifficulty > 3) if (SegmentDuration <= 200 && ConversionDifficulty > 3)
return generateRandomMultipleNotes(HitObject.StartTime); return generateRandomMultipleNotes(StartTime);
double duration = EndTime - HitObject.StartTime; double duration = EndTime - StartTime;
if (duration >= 4000) if (duration >= 4000)
return generateNRandomNotes(HitObject.StartTime, 0.23, 0, 0); return generateNRandomNotes(StartTime, 0.23, 0, 0);
if (SegmentDuration > 400 && SpanCount < TotalColumns - 1 - RandomStart) if (SegmentDuration > 400 && SpanCount < TotalColumns - 1 - RandomStart)
return generateTiledHoldNotes(HitObject.StartTime); return generateTiledHoldNotes(StartTime);
return generateHoldAndNormalNotes(HitObject.StartTime); return generateHoldAndNormalNotes(StartTime);
} }
if (SegmentDuration <= 110) if (SegmentDuration <= 110)
@ -128,37 +135,37 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
convertType |= PatternType.ForceNotStack; convertType |= PatternType.ForceNotStack;
else else
convertType &= ~PatternType.ForceNotStack; convertType &= ~PatternType.ForceNotStack;
return generateRandomNotes(HitObject.StartTime, SegmentDuration < 80 ? 1 : 2); return generateRandomNotes(StartTime, SegmentDuration < 80 ? 1 : 2);
} }
if (ConversionDifficulty > 6.5) if (ConversionDifficulty > 6.5)
{ {
if (convertType.HasFlag(PatternType.LowProbability)) if (convertType.HasFlag(PatternType.LowProbability))
return generateNRandomNotes(HitObject.StartTime, 0.78, 0.3, 0); return generateNRandomNotes(StartTime, 0.78, 0.3, 0);
return generateNRandomNotes(HitObject.StartTime, 0.85, 0.36, 0.03); return generateNRandomNotes(StartTime, 0.85, 0.36, 0.03);
} }
if (ConversionDifficulty > 4) if (ConversionDifficulty > 4)
{ {
if (convertType.HasFlag(PatternType.LowProbability)) if (convertType.HasFlag(PatternType.LowProbability))
return generateNRandomNotes(HitObject.StartTime, 0.43, 0.08, 0); return generateNRandomNotes(StartTime, 0.43, 0.08, 0);
return generateNRandomNotes(HitObject.StartTime, 0.56, 0.18, 0); return generateNRandomNotes(StartTime, 0.56, 0.18, 0);
} }
if (ConversionDifficulty > 2.5) if (ConversionDifficulty > 2.5)
{ {
if (convertType.HasFlag(PatternType.LowProbability)) if (convertType.HasFlag(PatternType.LowProbability))
return generateNRandomNotes(HitObject.StartTime, 0.3, 0, 0); return generateNRandomNotes(StartTime, 0.3, 0, 0);
return generateNRandomNotes(HitObject.StartTime, 0.37, 0.08, 0); return generateNRandomNotes(StartTime, 0.37, 0.08, 0);
} }
if (convertType.HasFlag(PatternType.LowProbability)) if (convertType.HasFlag(PatternType.LowProbability))
return generateNRandomNotes(HitObject.StartTime, 0.17, 0, 0); return generateNRandomNotes(StartTime, 0.17, 0, 0);
return generateNRandomNotes(HitObject.StartTime, 0.27, 0, 0); return generateNRandomNotes(StartTime, 0.27, 0, 0);
} }
/// <summary> /// <summary>
@ -167,7 +174,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// <param name="startTime">Start time of each hold note.</param> /// <param name="startTime">Start time of each hold note.</param>
/// <param name="noteCount">Number of hold notes.</param> /// <param name="noteCount">Number of hold notes.</param>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns> /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
private Pattern generateRandomHoldNotes(double startTime, int noteCount) private Pattern generateRandomHoldNotes(int startTime, int noteCount)
{ {
// - - - - // - - - -
// ■ - ■ ■ // ■ - ■ ■
@ -202,7 +209,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// <param name="startTime">The start time.</param> /// <param name="startTime">The start time.</param>
/// <param name="noteCount">The number of notes.</param> /// <param name="noteCount">The number of notes.</param>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns> /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
private Pattern generateRandomNotes(double startTime, int noteCount) private Pattern generateRandomNotes(int startTime, int noteCount)
{ {
// - - - - // - - - -
// x - - - // x - - -
@ -234,7 +241,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// </summary> /// </summary>
/// <param name="startTime">The start time.</param> /// <param name="startTime">The start time.</param>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns> /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
private Pattern generateStair(double startTime) private Pattern generateStair(int startTime)
{ {
// - - - - // - - - -
// x - - - // x - - -
@ -286,7 +293,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// </summary> /// </summary>
/// <param name="startTime">The start time.</param> /// <param name="startTime">The start time.</param>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns> /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
private Pattern generateRandomMultipleNotes(double startTime) private Pattern generateRandomMultipleNotes(int startTime)
{ {
// - - - - // - - - -
// x - - - // x - - -
@ -329,7 +336,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// <param name="p3">The probability required for 3 hold notes to be generated.</param> /// <param name="p3">The probability required for 3 hold notes to be generated.</param>
/// <param name="p4">The probability required for 4 hold notes to be generated.</param> /// <param name="p4">The probability required for 4 hold notes to be generated.</param>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns> /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
private Pattern generateNRandomNotes(double startTime, double p2, double p3, double p4) private Pattern generateNRandomNotes(int startTime, double p2, double p3, double p4)
{ {
// - - - - // - - - -
// ■ - ■ ■ // ■ - ■ ■
@ -366,7 +373,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
static bool isDoubleSample(HitSampleInfo sample) => sample.Name == HitSampleInfo.HIT_CLAP || sample.Name == HitSampleInfo.HIT_FINISH; static bool isDoubleSample(HitSampleInfo sample) => sample.Name == HitSampleInfo.HIT_CLAP || sample.Name == HitSampleInfo.HIT_FINISH;
bool canGenerateTwoNotes = !convertType.HasFlag(PatternType.LowProbability); bool canGenerateTwoNotes = !convertType.HasFlag(PatternType.LowProbability);
canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(HitObject.StartTime).Any(isDoubleSample); canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(StartTime).Any(isDoubleSample);
if (canGenerateTwoNotes) if (canGenerateTwoNotes)
p2 = 1; p2 = 1;
@ -379,7 +386,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// </summary> /// </summary>
/// <param name="startTime">The first hold note start time.</param> /// <param name="startTime">The first hold note start time.</param>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns> /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
private Pattern generateTiledHoldNotes(double startTime) private Pattern generateTiledHoldNotes(int startTime)
{ {
// - - - - // - - - -
// ■ ■ ■ ■ // ■ ■ ■ ■
@ -394,6 +401,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
int columnRepeat = Math.Min(SpanCount, TotalColumns); int columnRepeat = Math.Min(SpanCount, TotalColumns);
// Due to integer rounding, this is not guaranteed to be the same as EndTime (the class-level variable).
int endTime = startTime + SegmentDuration * SpanCount;
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns) if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
nextColumn = FindAvailableColumn(nextColumn, PreviousPattern); nextColumn = FindAvailableColumn(nextColumn, PreviousPattern);
@ -401,7 +411,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
for (int i = 0; i < columnRepeat; i++) for (int i = 0; i < columnRepeat; i++)
{ {
nextColumn = FindAvailableColumn(nextColumn, pattern); nextColumn = FindAvailableColumn(nextColumn, pattern);
addToPattern(pattern, nextColumn, startTime, EndTime); addToPattern(pattern, nextColumn, startTime, endTime);
startTime += SegmentDuration; startTime += SegmentDuration;
} }
@ -413,7 +423,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// </summary> /// </summary>
/// <param name="startTime">The start time of notes.</param> /// <param name="startTime">The start time of notes.</param>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns> /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
private Pattern generateHoldAndNormalNotes(double startTime) private Pattern generateHoldAndNormalNotes(int startTime)
{ {
// - - - - // - - - -
// ■ x x - // ■ x x -
@ -448,7 +458,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
for (int i = 0; i <= SpanCount; i++) for (int i = 0; i <= SpanCount; i++)
{ {
if (!(ignoreHead && startTime == HitObject.StartTime)) if (!(ignoreHead && startTime == StartTime))
{ {
for (int j = 0; j < noteCount; j++) for (int j = 0; j < noteCount; j++)
{ {
@ -471,19 +481,18 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// </summary> /// </summary>
/// <param name="time">The time to retrieve the sample info list from.</param> /// <param name="time">The time to retrieve the sample info list from.</param>
/// <returns></returns> /// <returns></returns>
private IList<HitSampleInfo> sampleInfoListAt(double time) => nodeSamplesAt(time)?.First() ?? HitObject.Samples; private IList<HitSampleInfo> sampleInfoListAt(int time) => nodeSamplesAt(time)?.First() ?? HitObject.Samples;
/// <summary> /// <summary>
/// Retrieves the list of node samples that occur at time greater than or equal to <paramref name="time"/>. /// Retrieves the list of node samples that occur at time greater than or equal to <paramref name="time"/>.
/// </summary> /// </summary>
/// <param name="time">The time to retrieve node samples at.</param> /// <param name="time">The time to retrieve node samples at.</param>
private List<IList<HitSampleInfo>> nodeSamplesAt(double time) private List<IList<HitSampleInfo>> nodeSamplesAt(int time)
{ {
if (!(HitObject is IHasPathWithRepeats curveData)) if (!(HitObject is IHasPathWithRepeats curveData))
return null; return null;
// mathematically speaking this should be a whole number always, but floating-point arithmetic is not so kind var index = SegmentDuration == 0 ? 0 : (time - StartTime) / SegmentDuration;
var index = (int)Math.Round(SegmentDuration == 0 ? 0 : (time - HitObject.StartTime) / SegmentDuration, MidpointRounding.AwayFromZero);
// avoid slicing the list & creating copies, if at all possible. // avoid slicing the list & creating copies, if at all possible.
return index == 0 ? curveData.NodeSamples : curveData.NodeSamples.Skip(index).ToList(); return index == 0 ? curveData.NodeSamples : curveData.NodeSamples.Skip(index).ToList();
@ -496,7 +505,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// <param name="column">The column to add the note to.</param> /// <param name="column">The column to add the note to.</param>
/// <param name="startTime">The start time of the note.</param> /// <param name="startTime">The start time of the note.</param>
/// <param name="endTime">The end time of the note (set to <paramref name="startTime"/> for a non-hold note).</param> /// <param name="endTime">The end time of the note (set to <paramref name="startTime"/> for a non-hold note).</param>
private void addToPattern(Pattern pattern, int column, double startTime, double endTime) private void addToPattern(Pattern pattern, int column, int startTime, int endTime)
{ {
ManiaHitObject newObject; ManiaHitObject newObject;