mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 17:43:05 +08:00
Treat NaN slider velocity timing points as 1.0x but without slider ticks
This commit is contained in:
parent
e8d4bc4497
commit
9f08c474ca
@ -40,6 +40,9 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
[JsonIgnore]
|
||||
public double TickDistance => tickDistanceFactor * DifficultyControlPoint.SliderVelocity;
|
||||
|
||||
[JsonIgnore]
|
||||
public bool GenerateTicks => DifficultyControlPoint.GenerateTicks;
|
||||
|
||||
/// <summary>
|
||||
/// The length of one span of this <see cref="JuiceStream"/>.
|
||||
/// </summary>
|
||||
@ -64,7 +67,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
int nodeIndex = 0;
|
||||
SliderEventDescriptor? lastEvent = null;
|
||||
|
||||
foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken))
|
||||
foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, GenerateTicks, cancellationToken))
|
||||
{
|
||||
// generate tiny droplets since the last point
|
||||
if (lastEvent != null)
|
||||
|
@ -117,7 +117,7 @@ namespace osu.Game.Rulesets.Mania.Objects
|
||||
|
||||
private void createTicks(CancellationToken cancellationToken)
|
||||
{
|
||||
if (tickSpacing == 0)
|
||||
if (tickSpacing == 0 || !DifficultyControlPoint.GenerateTicks)
|
||||
return;
|
||||
|
||||
for (double t = StartTime + tickSpacing; t <= EndTime - tickSpacing; t += tickSpacing)
|
||||
|
@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||
{
|
||||
var sliderEvents = SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken);
|
||||
var sliderEvents = SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, GenerateTicks, cancellationToken);
|
||||
|
||||
foreach (var e in sliderEvents)
|
||||
{
|
||||
|
@ -142,6 +142,11 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
/// </summary>
|
||||
public double TickDistanceMultiplier = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this <see cref="Slider"/> should generate <see cref="SliderTick"/>s.
|
||||
/// </summary>
|
||||
public bool GenerateTicks { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this <see cref="Slider"/>'s judgement is fully handled by its nested <see cref="HitObject"/>s.
|
||||
/// If <c>false</c>, this <see cref="Slider"/> will be judged proportionally to the number of nested <see cref="HitObject"/>s hit.
|
||||
@ -170,13 +175,14 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
|
||||
Velocity = scoringDistance / timingPoint.BeatLength;
|
||||
TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier;
|
||||
GenerateTicks = DifficultyControlPoint.GenerateTicks;
|
||||
}
|
||||
|
||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||
{
|
||||
base.CreateNestedHitObjects(cancellationToken);
|
||||
|
||||
var sliderEvents = SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken);
|
||||
var sliderEvents = SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, GenerateTicks, cancellationToken);
|
||||
|
||||
foreach (var e in sliderEvents)
|
||||
{
|
||||
|
@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
|
||||
private void createTicks(CancellationToken cancellationToken)
|
||||
{
|
||||
if (tickSpacing == 0)
|
||||
if (tickSpacing == 0 || !DifficultyControlPoint.GenerateTicks)
|
||||
return;
|
||||
|
||||
bool first = true;
|
||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Tests.Beatmaps
|
||||
[Test]
|
||||
public void TestSingleSpan()
|
||||
{
|
||||
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null).ToArray();
|
||||
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null, true).ToArray();
|
||||
|
||||
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
|
||||
Assert.That(events[0].Time, Is.EqualTo(start_time));
|
||||
@ -33,7 +33,7 @@ namespace osu.Game.Tests.Beatmaps
|
||||
[Test]
|
||||
public void TestRepeat()
|
||||
{
|
||||
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2, null).ToArray();
|
||||
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2, null, true).ToArray();
|
||||
|
||||
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
|
||||
Assert.That(events[0].Time, Is.EqualTo(start_time));
|
||||
@ -54,7 +54,7 @@ namespace osu.Game.Tests.Beatmaps
|
||||
[Test]
|
||||
public void TestNonEvenTicks()
|
||||
{
|
||||
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2, null).ToArray();
|
||||
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2, null, true).ToArray();
|
||||
|
||||
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
|
||||
Assert.That(events[0].Time, Is.EqualTo(start_time));
|
||||
@ -87,7 +87,7 @@ namespace osu.Game.Tests.Beatmaps
|
||||
[Test]
|
||||
public void TestLegacyLastTickOffset()
|
||||
{
|
||||
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, 100).ToArray();
|
||||
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, 100, true).ToArray();
|
||||
|
||||
Assert.That(events[2].Type, Is.EqualTo(SliderEventType.LegacyLastTick));
|
||||
Assert.That(events[2].Time, Is.EqualTo(900));
|
||||
@ -99,7 +99,7 @@ namespace osu.Game.Tests.Beatmaps
|
||||
const double velocity = 5;
|
||||
const double min_distance = velocity * 10;
|
||||
|
||||
var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2, 0).ToArray();
|
||||
var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2, 0, true).ToArray();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -114,5 +114,12 @@ namespace osu.Game.Tests.Beatmaps
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNoTickGeneration()
|
||||
{
|
||||
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null, false).ToArray();
|
||||
Assert.That(events.Any(e => e.Type == SliderEventType.Tick), Is.False);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,13 +40,21 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
set => SliderVelocityBindable.Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not slider ticks should be generated at this control point.
|
||||
/// This exists for backwards compatibility with maps that abuse NaN slider velocity behavior on osu!stable (e.g. /b/2628991).
|
||||
/// </summary>
|
||||
public bool GenerateTicks { get; set; } = true;
|
||||
|
||||
public override bool IsRedundant(ControlPoint? existing)
|
||||
=> existing is DifficultyControlPoint existingDifficulty
|
||||
&& SliderVelocity == existingDifficulty.SliderVelocity;
|
||||
&& SliderVelocity == existingDifficulty.SliderVelocity
|
||||
&& GenerateTicks == existingDifficulty.GenerateTicks;
|
||||
|
||||
public override void CopyFrom(ControlPoint other)
|
||||
{
|
||||
SliderVelocity = ((DifficultyControlPoint)other).SliderVelocity;
|
||||
GenerateTicks = ((DifficultyControlPoint)other).GenerateTicks;
|
||||
|
||||
base.CopyFrom(other);
|
||||
}
|
||||
@ -57,8 +65,9 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
|
||||
public bool Equals(DifficultyControlPoint? other)
|
||||
=> base.Equals(other)
|
||||
&& SliderVelocity == other.SliderVelocity;
|
||||
&& SliderVelocity == other.SliderVelocity
|
||||
&& GenerateTicks == other.GenerateTicks;
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), SliderVelocity);
|
||||
public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), SliderVelocity, GenerateTicks);
|
||||
}
|
||||
}
|
||||
|
@ -373,7 +373,9 @@ namespace osu.Game.Beatmaps.Formats
|
||||
string[] split = line.Split(',');
|
||||
|
||||
double time = getOffsetTime(Parsing.ParseDouble(split[0].Trim()));
|
||||
double beatLength = Parsing.ParseDouble(split[1].Trim());
|
||||
double beatLength = Parsing.ParseDouble(split[1].Trim(), allowNaN: true);
|
||||
|
||||
// If beatLength is NaN, speedMultiplier should still be 1 because all comparisons against NaN are false.
|
||||
double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1;
|
||||
|
||||
TimeSignature timeSignature = TimeSignature.SimpleQuadruple;
|
||||
@ -412,6 +414,9 @@ namespace osu.Game.Beatmaps.Formats
|
||||
|
||||
if (timingChange)
|
||||
{
|
||||
if (double.IsNaN(beatLength))
|
||||
throw new InvalidDataException("Beat length cannot be NaN in a timing control point");
|
||||
|
||||
var controlPoint = CreateTimingControlPoint();
|
||||
|
||||
controlPoint.BeatLength = beatLength;
|
||||
@ -425,6 +430,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
#pragma warning restore 618
|
||||
{
|
||||
SliderVelocity = speedMultiplier,
|
||||
GenerateTicks = !double.IsNaN(beatLength),
|
||||
}, timingChange);
|
||||
|
||||
var effectPoint = new EffectControlPoint
|
||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Objects
|
||||
{
|
||||
// ReSharper disable once MethodOverloadWithOptionalParameter
|
||||
public static IEnumerable<SliderEventDescriptor> Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount,
|
||||
double? legacyLastTickOffset, CancellationToken cancellationToken = default)
|
||||
double? legacyLastTickOffset, bool shouldGenerateTicks, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// A very lenient maximum length of a slider for ticks to be generated.
|
||||
// This exists for edge cases such as /b/1573664 where the beatmap has been edited by the user, and should never be reached in normal usage.
|
||||
@ -41,16 +41,19 @@ namespace osu.Game.Rulesets.Objects
|
||||
double spanStartTime = startTime + span * spanDuration;
|
||||
bool reversed = span % 2 == 1;
|
||||
|
||||
var ticks = generateTicks(span, spanStartTime, spanDuration, reversed, length, tickDistance, minDistanceFromEnd, cancellationToken);
|
||||
|
||||
if (reversed)
|
||||
if (shouldGenerateTicks)
|
||||
{
|
||||
// For repeat spans, ticks are returned in reverse-StartTime order, which is undesirable for some rulesets
|
||||
ticks = ticks.Reverse();
|
||||
}
|
||||
var ticks = generateTicks(span, spanStartTime, spanDuration, reversed, length, tickDistance, minDistanceFromEnd, cancellationToken);
|
||||
|
||||
foreach (var e in ticks)
|
||||
yield return e;
|
||||
if (reversed)
|
||||
{
|
||||
// For repeat spans, ticks are returned in reverse-StartTime order, which is undesirable for some rulesets
|
||||
ticks = ticks.Reverse();
|
||||
}
|
||||
|
||||
foreach (var e in ticks)
|
||||
yield return e;
|
||||
}
|
||||
|
||||
if (span < spanCount - 1)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user