mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 21:02:54 +08:00
Merge pull request #10452 from smoogipoo/fix-mania-conversion
Make mania beatmap conversions match stable
This commit is contained in:
commit
010f86ff34
@ -116,7 +116,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
||||
prevNoteTimes.RemoveAt(0);
|
||||
prevNoteTimes.Add(newNoteTime);
|
||||
|
||||
density = (prevNoteTimes[^1] - prevNoteTimes[0]) / prevNoteTimes.Count;
|
||||
if (prevNoteTimes.Count >= 2)
|
||||
density = (prevNoteTimes[^1] - prevNoteTimes[0]) / prevNoteTimes.Count;
|
||||
}
|
||||
|
||||
private double lastTime;
|
||||
@ -180,7 +181,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
||||
|
||||
case IHasDuration endTimeData:
|
||||
{
|
||||
conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, originalBeatmap);
|
||||
conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap);
|
||||
|
||||
recordNote(endTimeData.EndTime, new Vector2(256, 192));
|
||||
computeDensity(endTimeData.EndTime);
|
||||
|
@ -3,8 +3,8 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
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.Mania.Objects;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
{
|
||||
@ -25,8 +26,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
/// </summary>
|
||||
private const float osu_base_scoring_distance = 100;
|
||||
|
||||
public readonly double EndTime;
|
||||
public readonly double SegmentDuration;
|
||||
public readonly int StartTime;
|
||||
public readonly int EndTime;
|
||||
public readonly int SegmentDuration;
|
||||
public readonly int SpanCount;
|
||||
|
||||
private PatternType convertType;
|
||||
@ -41,20 +43,26 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
var distanceData = hitObject as IHasDistance;
|
||||
var repeatsData = hitObject as IHasRepeats;
|
||||
|
||||
SpanCount = repeatsData?.SpanCount() ?? 1;
|
||||
Debug.Assert(distanceData != null);
|
||||
|
||||
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
|
||||
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime);
|
||||
|
||||
// The true distance, accounting for any repeats
|
||||
double distance = (distanceData?.Distance ?? 0) * SpanCount;
|
||||
// The velocity of the osu! hit object - calculated as the velocity of a slider
|
||||
double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / timingPoint.BeatLength;
|
||||
// The duration of the osu! hit object
|
||||
double osuDuration = distance / osuVelocity;
|
||||
double beatLength;
|
||||
#pragma warning disable 618
|
||||
if (difficultyPoint is LegacyBeatmapDecoder.LegacyDifficultyControlPoint legacyDifficultyPoint)
|
||||
#pragma warning restore 618
|
||||
beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier;
|
||||
else
|
||||
beatLength = timingPoint.BeatLength / difficultyPoint.SpeedMultiplier;
|
||||
|
||||
EndTime = hitObject.StartTime + osuDuration;
|
||||
SegmentDuration = (EndTime - HitObject.StartTime) / SpanCount;
|
||||
SpanCount = repeatsData?.SpanCount() ?? 1;
|
||||
StartTime = (int)Math.Round(hitObject.StartTime);
|
||||
|
||||
// This matches stable's calculation.
|
||||
EndTime = (int)Math.Floor(StartTime + distanceData.Distance * beatLength * SpanCount * 0.01 / beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier);
|
||||
|
||||
SegmentDuration = (EndTime - StartTime) / SpanCount;
|
||||
}
|
||||
|
||||
public override IEnumerable<Pattern> Generate()
|
||||
@ -76,7 +84,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
foreach (var obj in originalPattern.HitObjects)
|
||||
{
|
||||
if (!Precision.AlmostEquals(EndTime, obj.GetEndTime()))
|
||||
if (EndTime != (int)Math.Round(obj.GetEndTime()))
|
||||
intermediatePattern.Add(obj);
|
||||
else
|
||||
endTimePattern.Add(obj);
|
||||
@ -91,35 +99,35 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
if (TotalColumns == 1)
|
||||
{
|
||||
var pattern = new Pattern();
|
||||
addToPattern(pattern, 0, HitObject.StartTime, EndTime);
|
||||
addToPattern(pattern, 0, StartTime, EndTime);
|
||||
return pattern;
|
||||
}
|
||||
|
||||
if (SpanCount > 1)
|
||||
{
|
||||
if (SegmentDuration <= 90)
|
||||
return generateRandomHoldNotes(HitObject.StartTime, 1);
|
||||
return generateRandomHoldNotes(StartTime, 1);
|
||||
|
||||
if (SegmentDuration <= 120)
|
||||
{
|
||||
convertType |= PatternType.ForceNotStack;
|
||||
return generateRandomNotes(HitObject.StartTime, SpanCount + 1);
|
||||
return generateRandomNotes(StartTime, SpanCount + 1);
|
||||
}
|
||||
|
||||
if (SegmentDuration <= 160)
|
||||
return generateStair(HitObject.StartTime);
|
||||
return generateStair(StartTime);
|
||||
|
||||
if (SegmentDuration <= 200 && ConversionDifficulty > 3)
|
||||
return generateRandomMultipleNotes(HitObject.StartTime);
|
||||
return generateRandomMultipleNotes(StartTime);
|
||||
|
||||
double duration = EndTime - HitObject.StartTime;
|
||||
double duration = EndTime - StartTime;
|
||||
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)
|
||||
return generateTiledHoldNotes(HitObject.StartTime);
|
||||
return generateTiledHoldNotes(StartTime);
|
||||
|
||||
return generateHoldAndNormalNotes(HitObject.StartTime);
|
||||
return generateHoldAndNormalNotes(StartTime);
|
||||
}
|
||||
|
||||
if (SegmentDuration <= 110)
|
||||
@ -128,37 +136,37 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
convertType |= PatternType.ForceNotStack;
|
||||
else
|
||||
convertType &= ~PatternType.ForceNotStack;
|
||||
return generateRandomNotes(HitObject.StartTime, SegmentDuration < 80 ? 1 : 2);
|
||||
return generateRandomNotes(StartTime, SegmentDuration < 80 ? 1 : 2);
|
||||
}
|
||||
|
||||
if (ConversionDifficulty > 6.5)
|
||||
{
|
||||
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 (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 (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))
|
||||
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>
|
||||
@ -167,7 +175,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
/// <param name="startTime">Start time of each hold note.</param>
|
||||
/// <param name="noteCount">Number of hold notes.</param>
|
||||
/// <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 +210,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
/// <param name="startTime">The start time.</param>
|
||||
/// <param name="noteCount">The number of notes.</param>
|
||||
/// <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 - - -
|
||||
@ -234,7 +242,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
/// </summary>
|
||||
/// <param name="startTime">The start time.</param>
|
||||
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
||||
private Pattern generateStair(double startTime)
|
||||
private Pattern generateStair(int startTime)
|
||||
{
|
||||
// - - - -
|
||||
// x - - -
|
||||
@ -286,7 +294,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
/// </summary>
|
||||
/// <param name="startTime">The start time.</param>
|
||||
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
||||
private Pattern generateRandomMultipleNotes(double startTime)
|
||||
private Pattern generateRandomMultipleNotes(int startTime)
|
||||
{
|
||||
// - - - -
|
||||
// x - - -
|
||||
@ -329,7 +337,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="p4">The probability required for 4 hold notes to be generated.</param>
|
||||
/// <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 +374,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
static bool isDoubleSample(HitSampleInfo sample) => sample.Name == HitSampleInfo.HIT_CLAP || sample.Name == HitSampleInfo.HIT_FINISH;
|
||||
|
||||
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)
|
||||
p2 = 1;
|
||||
@ -379,7 +387,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
/// </summary>
|
||||
/// <param name="startTime">The first hold note start time.</param>
|
||||
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
||||
private Pattern generateTiledHoldNotes(double startTime)
|
||||
private Pattern generateTiledHoldNotes(int startTime)
|
||||
{
|
||||
// - - - -
|
||||
// ■ ■ ■ ■
|
||||
@ -394,6 +402,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
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);
|
||||
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
||||
nextColumn = FindAvailableColumn(nextColumn, PreviousPattern);
|
||||
@ -401,7 +412,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
for (int i = 0; i < columnRepeat; i++)
|
||||
{
|
||||
nextColumn = FindAvailableColumn(nextColumn, pattern);
|
||||
addToPattern(pattern, nextColumn, startTime, EndTime);
|
||||
addToPattern(pattern, nextColumn, startTime, endTime);
|
||||
startTime += SegmentDuration;
|
||||
}
|
||||
|
||||
@ -413,7 +424,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
/// </summary>
|
||||
/// <param name="startTime">The start time of notes.</param>
|
||||
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
||||
private Pattern generateHoldAndNormalNotes(double startTime)
|
||||
private Pattern generateHoldAndNormalNotes(int startTime)
|
||||
{
|
||||
// - - - -
|
||||
// ■ x x -
|
||||
@ -448,7 +459,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
for (int i = 0; i <= SpanCount; i++)
|
||||
{
|
||||
if (!(ignoreHead && startTime == HitObject.StartTime))
|
||||
if (!(ignoreHead && startTime == StartTime))
|
||||
{
|
||||
for (int j = 0; j < noteCount; j++)
|
||||
{
|
||||
@ -471,19 +482,18 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
/// </summary>
|
||||
/// <param name="time">The time to retrieve the sample info list from.</param>
|
||||
/// <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>
|
||||
/// Retrieves the list of node samples that occur at time greater than or equal to <paramref name="time"/>.
|
||||
/// </summary>
|
||||
/// <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))
|
||||
return null;
|
||||
|
||||
// mathematically speaking this should be a whole number always, but floating-point arithmetic is not so kind
|
||||
var index = (int)Math.Round(SegmentDuration == 0 ? 0 : (time - HitObject.StartTime) / SegmentDuration, MidpointRounding.AwayFromZero);
|
||||
var index = SegmentDuration == 0 ? 0 : (time - StartTime) / SegmentDuration;
|
||||
|
||||
// avoid slicing the list & creating copies, if at all possible.
|
||||
return index == 0 ? curveData.NodeSamples : curveData.NodeSamples.Skip(index).ToList();
|
||||
@ -496,7 +506,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
/// <param name="column">The column to add the note to.</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>
|
||||
private void addToPattern(Pattern pattern, int column, double startTime, double endTime)
|
||||
private void addToPattern(Pattern pattern, int column, int startTime, int endTime)
|
||||
{
|
||||
ManiaHitObject newObject;
|
||||
|
||||
|
@ -14,12 +14,17 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
{
|
||||
internal class EndTimeObjectPatternGenerator : PatternGenerator
|
||||
{
|
||||
private readonly double endTime;
|
||||
private readonly int endTime;
|
||||
private readonly PatternType convertType;
|
||||
|
||||
public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, IBeatmap originalBeatmap)
|
||||
: base(random, hitObject, beatmap, new Pattern(), originalBeatmap)
|
||||
public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap)
|
||||
: base(random, hitObject, beatmap, previousPattern, originalBeatmap)
|
||||
{
|
||||
endTime = (HitObject as IHasDuration)?.EndTime ?? 0;
|
||||
endTime = (int)((HitObject as IHasDuration)?.EndTime ?? 0);
|
||||
|
||||
convertType = PreviousPattern.ColumnWithObjects == TotalColumns
|
||||
? PatternType.None
|
||||
: PatternType.ForceNotStack;
|
||||
}
|
||||
|
||||
public override IEnumerable<Pattern> Generate()
|
||||
@ -40,18 +45,25 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
break;
|
||||
|
||||
case 8:
|
||||
addToPattern(pattern, FindAvailableColumn(GetRandomColumn(), PreviousPattern), generateHold);
|
||||
addToPattern(pattern, getRandomColumn(), generateHold);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (TotalColumns > 0)
|
||||
addToPattern(pattern, GetRandomColumn(), generateHold);
|
||||
addToPattern(pattern, getRandomColumn(0), generateHold);
|
||||
break;
|
||||
}
|
||||
|
||||
return pattern;
|
||||
}
|
||||
|
||||
private int getRandomColumn(int? lowerBound = null)
|
||||
{
|
||||
if ((convertType & PatternType.ForceNotStack) > 0)
|
||||
return FindAvailableColumn(GetRandomColumn(lowerBound), lowerBound, patterns: PreviousPattern);
|
||||
|
||||
return FindAvailableColumn(GetRandomColumn(lowerBound), lowerBound);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs and adds a note to a pattern.
|
||||
/// </summary>
|
||||
|
@ -397,7 +397,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
case 4:
|
||||
centreProbability = 0;
|
||||
p2 = Math.Min(p2 * 2, 0.2);
|
||||
|
||||
// Stable requires rngValue > x, which is an inverse-probability. Lazer uses true probability (1 - x).
|
||||
// But multiplying this value by 2 (stable) is not the same operation as dividing it by 2 (lazer),
|
||||
// so it needs to be converted to from a probability and then back after the multiplication.
|
||||
p2 = 1 - Math.Max((1 - p2) * 2, 0.8);
|
||||
p3 = 0;
|
||||
break;
|
||||
|
||||
@ -408,11 +412,20 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
case 6:
|
||||
centreProbability = 0;
|
||||
p2 = Math.Min(p2 * 2, 0.5);
|
||||
p3 = Math.Min(p3 * 2, 0.15);
|
||||
|
||||
// Stable requires rngValue > x, which is an inverse-probability. Lazer uses true probability (1 - x).
|
||||
// But multiplying this value by 2 (stable) is not the same operation as dividing it by 2 (lazer),
|
||||
// so it needs to be converted to from a probability and then back after the multiplication.
|
||||
p2 = 1 - Math.Max((1 - p2) * 2, 0.5);
|
||||
p3 = 1 - Math.Max((1 - p3) * 2, 0.85);
|
||||
break;
|
||||
}
|
||||
|
||||
// The stable values were allowed to exceed 1, which indicate <0% probability.
|
||||
// These values needs to be clamped otherwise GetRandomNoteCount() will throw an exception.
|
||||
p2 = Math.Clamp(p2, 0, 1);
|
||||
p3 = Math.Clamp(p3, 0, 1);
|
||||
|
||||
double centreVal = Random.NextDouble();
|
||||
int noteCount = GetRandomNoteCount(p2, p3);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user