Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Respect pre-empt time when auto-generating breaks #29021

Merged
merged 4 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

namespace osu.Game.Rulesets.Catch.Objects
{
public abstract class CatchHitObject : HitObject, IHasPosition, IHasComboInformation
public abstract class CatchHitObject : HitObject, IHasPosition, IHasComboInformation, IHasTimePreempt
{
public const float OBJECT_RADIUS = 64;

Expand Down
4 changes: 2 additions & 2 deletions osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

namespace osu.Game.Rulesets.Osu.Objects
{
public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition
public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition, IHasTimePreempt
{
/// <summary>
/// The radius of hit objects (ie. the radius of a <see cref="HitCircle"/>).
Expand Down Expand Up @@ -46,7 +46,7 @@ public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPositi
/// </summary>
public const double PREEMPT_MAX = 1800;

public double TimePreempt = 600;
public double TimePreempt { get; set; } = 600;
public double TimeFadeIn = 400;

private HitObjectProperty<Vector2> position;
Expand Down
104 changes: 77 additions & 27 deletions osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public void TestSingleObjectBeatmap()
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
HitObjects =
{
new HitCircle { StartTime = 1000 },
new Note { StartTime = 1000 },
}
});

Expand All @@ -67,8 +67,8 @@ public void TestTwoObjectsCloseTogether()
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
HitObjects =
{
new HitCircle { StartTime = 1000 },
new HitCircle { StartTime = 2000 },
new Note { StartTime = 1000 },
new Note { StartTime = 2000 },
}
});

Expand Down Expand Up @@ -136,8 +136,8 @@ public void TestTwoObjectsFarApart()
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
HitObjects =
{
new HitCircle { StartTime = 1000 },
new HitCircle { StartTime = 5000 },
new Note { StartTime = 1000 },
new Note { StartTime = 5000 },
}
});

Expand All @@ -164,8 +164,8 @@ public void TestBreaksAreFused()
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
HitObjects =
{
new HitCircle { StartTime = 1000 },
new HitCircle { StartTime = 9000 },
new Note { StartTime = 1000 },
new Note { StartTime = 9000 },
},
Breaks =
{
Expand Down Expand Up @@ -197,9 +197,9 @@ public void TestBreaksAreSplit()
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
HitObjects =
{
new HitCircle { StartTime = 1000 },
new HitCircle { StartTime = 5000 },
new HitCircle { StartTime = 9000 },
new Note { StartTime = 1000 },
new Note { StartTime = 5000 },
new Note { StartTime = 9000 },
},
Breaks =
{
Expand Down Expand Up @@ -232,8 +232,8 @@ public void TestBreaksAreNudged()
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
HitObjects =
{
new HitCircle { StartTime = 1100 },
new HitCircle { StartTime = 9000 },
new Note { StartTime = 1100 },
new Note { StartTime = 9000 },
},
Breaks =
{
Expand Down Expand Up @@ -264,8 +264,8 @@ public void TestManualBreaksAreNotFused()
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
HitObjects =
{
new HitCircle { StartTime = 1000 },
new HitCircle { StartTime = 9000 },
new Note { StartTime = 1000 },
new Note { StartTime = 9000 },
},
Breaks =
{
Expand Down Expand Up @@ -299,9 +299,9 @@ public void TestManualBreaksAreSplit()
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
HitObjects =
{
new HitCircle { StartTime = 1000 },
new HitCircle { StartTime = 5000 },
new HitCircle { StartTime = 9000 },
new Note { StartTime = 1000 },
new Note { StartTime = 5000 },
new Note { StartTime = 9000 },
},
Breaks =
{
Expand Down Expand Up @@ -334,8 +334,8 @@ public void TestManualBreaksAreNotNudged()
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
HitObjects =
{
new HitCircle { StartTime = 1000 },
new HitCircle { StartTime = 9000 },
new Note { StartTime = 1000 },
new Note { StartTime = 9000 },
},
Breaks =
{
Expand Down Expand Up @@ -366,8 +366,8 @@ public void TestBreaksAtEndOfBeatmapAreRemoved()
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
HitObjects =
{
new HitCircle { StartTime = 1000 },
new HitCircle { StartTime = 2000 },
new Note { StartTime = 1000 },
new Note { StartTime = 2000 },
},
Breaks =
{
Expand All @@ -393,8 +393,8 @@ public void TestManualBreaksAtEndOfBeatmapAreRemoved()
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
HitObjects =
{
new HitCircle { StartTime = 1000 },
new HitCircle { StartTime = 2000 },
new Note { StartTime = 1000 },
new Note { StartTime = 2000 },
},
Breaks =
{
Expand Down Expand Up @@ -447,8 +447,8 @@ public void TestBreaksAtStartOfBeatmapAreRemoved()
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
HitObjects =
{
new HitCircle { StartTime = 10000 },
new HitCircle { StartTime = 11000 },
new Note { StartTime = 10000 },
new Note { StartTime = 11000 },
},
Breaks =
{
Expand All @@ -474,8 +474,8 @@ public void TestManualBreaksAtStartOfBeatmapAreRemoved()
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
HitObjects =
{
new HitCircle { StartTime = 10000 },
new HitCircle { StartTime = 11000 },
new Note { StartTime = 10000 },
new Note { StartTime = 11000 },
},
Breaks =
{
Expand All @@ -489,5 +489,55 @@ public void TestManualBreaksAtStartOfBeatmapAreRemoved()

Assert.That(beatmap.Breaks, Is.Empty);
}

[Test]
public void TestTimePreemptIsRespected()
{
var controlPoints = new ControlPointInfo();
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
var beatmap = new EditorBeatmap(new Beatmap
{
ControlPointInfo = controlPoints,
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
Difficulty =
{
ApproachRate = 10,
},
HitObjects =
{
new HitCircle { StartTime = 1000 },
new HitCircle { StartTime = 5000 },
}
});

foreach (var ho in beatmap.HitObjects)
ho.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty);

var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
beatmapProcessor.PreProcess();
beatmapProcessor.PostProcess();

Assert.Multiple(() =>
{
Assert.That(beatmap.Breaks, Has.Count.EqualTo(1));
Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200));
Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(5000 - OsuHitObject.PREEMPT_MIN));
});

beatmap.Difficulty.ApproachRate = 0;

foreach (var ho in beatmap.HitObjects)
ho.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty);

beatmapProcessor.PreProcess();
beatmapProcessor.PostProcess();

Assert.Multiple(() =>
{
Assert.That(beatmap.Breaks, Has.Count.EqualTo(1));
Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200));
Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(5000 - OsuHitObject.PREEMPT_MAX));
});
}
}
}
13 changes: 13 additions & 0 deletions osu.Game/Rulesets/Objects/Types/IHasTimePreempt.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

namespace osu.Game.Rulesets.Objects.Types
{
/// <summary>
/// A <see cref="HitObject"/> that appears on screen at a fixed time interval before its <see cref="HitObject.StartTime"/>.
/// </summary>
public interface IHasTimePreempt
{
double TimePreempt { get; }
}
}
20 changes: 14 additions & 6 deletions osu.Game/Screens/Edit/EditorBeatmapProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;

namespace osu.Game.Screens.Edit
{
Expand Down Expand Up @@ -44,7 +45,7 @@ public void PostProcess()

private void autoGenerateBreaks()
{
var objectDuration = Beatmap.HitObjects.Select(ho => (ho.StartTime, ho.GetEndTime())).ToHashSet();
var objectDuration = Beatmap.HitObjects.Select(ho => (ho.StartTime - ((ho as IHasTimePreempt)?.TimePreempt ?? 0), ho.GetEndTime())).ToHashSet();

if (objectDuration.SetEquals(objectDurationCache))
return;
Expand All @@ -67,19 +68,26 @@ private void autoGenerateBreaks()

for (int i = 1; i < Beatmap.HitObjects.Count; ++i)
{
var previousObject = Beatmap.HitObjects[i - 1];
var nextObject = Beatmap.HitObjects[i];

// Keep track of the maximum end time encountered thus far.
// This handles cases like osu!mania's hold notes, which could have concurrent other objects after their start time.
// Note that we're relying on the implicit assumption that objects are sorted by start time,
// which is why similar tracking is not done for start time.
currentMaxEndTime = Math.Max(currentMaxEndTime, Beatmap.HitObjects[i - 1].GetEndTime());

double nextObjectStartTime = Beatmap.HitObjects[i].StartTime;
currentMaxEndTime = Math.Max(currentMaxEndTime, previousObject.GetEndTime());

if (nextObjectStartTime - currentMaxEndTime < BreakPeriod.MIN_GAP_DURATION)
if (nextObject.StartTime - currentMaxEndTime < BreakPeriod.MIN_GAP_DURATION)
continue;

double breakStartTime = currentMaxEndTime + BreakPeriod.GAP_BEFORE_BREAK;
double breakEndTime = nextObjectStartTime - Math.Max(BreakPeriod.GAP_AFTER_BREAK, Beatmap.ControlPointInfo.TimingPointAt(nextObjectStartTime).BeatLength * 2);

double breakEndTime = nextObject.StartTime;

if (nextObject is IHasTimePreempt hasTimePreempt)
breakEndTime -= hasTimePreempt.TimePreempt;
else
breakEndTime -= Math.Max(BreakPeriod.GAP_AFTER_BREAK, Beatmap.ControlPointInfo.TimingPointAt(nextObject.StartTime).BeatLength * 2);

if (breakEndTime - breakStartTime < BreakPeriod.MIN_BREAK_DURATION)
continue;
Expand Down
Loading