mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 16:52:54 +08:00
Implement automatic break period generation
This commit is contained in:
parent
1f692f5fc7
commit
4022a8b06c
@ -6,22 +6,39 @@ using osu.Game.Screens.Play;
|
|||||||
|
|
||||||
namespace osu.Game.Beatmaps.Timing
|
namespace osu.Game.Beatmaps.Timing
|
||||||
{
|
{
|
||||||
public readonly struct BreakPeriod : IEquatable<BreakPeriod>
|
public record BreakPeriod
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The minimum gap between the start of the break and the previous object.
|
||||||
|
/// </summary>
|
||||||
|
public const double GAP_BEFORE_BREAK = 200;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The minimum gap between the end of the break and the next object.
|
||||||
|
/// Based on osu! preempt time at AR=10.
|
||||||
|
/// See also: https://github.com/ppy/osu/issues/14330#issuecomment-1002158551
|
||||||
|
/// </summary>
|
||||||
|
public const double GAP_AFTER_BREAK = 450;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The minimum duration required for a break to have any effect.
|
/// The minimum duration required for a break to have any effect.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const double MIN_BREAK_DURATION = 650;
|
public const double MIN_BREAK_DURATION = 650;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The minimum required duration of a gap between two objects such that a break can be placed between them.
|
||||||
|
/// </summary>
|
||||||
|
public const double MIN_GAP_DURATION = GAP_BEFORE_BREAK + MIN_BREAK_DURATION + GAP_AFTER_BREAK;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The break start time.
|
/// The break start time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double StartTime { get; init; }
|
public double StartTime { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The break end time.
|
/// The break end time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double EndTime { get; init; }
|
public double EndTime { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The break duration.
|
/// The break duration.
|
||||||
@ -51,8 +68,13 @@ namespace osu.Game.Beatmaps.Timing
|
|||||||
/// <returns>Whether the time falls within this <see cref="BreakPeriod"/>.</returns>
|
/// <returns>Whether the time falls within this <see cref="BreakPeriod"/>.</returns>
|
||||||
public bool Contains(double time) => time >= StartTime && time <= EndTime - BreakOverlay.BREAK_FADE_DURATION;
|
public bool Contains(double time) => time >= StartTime && time <= EndTime - BreakOverlay.BREAK_FADE_DURATION;
|
||||||
|
|
||||||
public bool Equals(BreakPeriod other) => StartTime.Equals(other.StartTime) && EndTime.Equals(other.EndTime);
|
public bool Intersects(BreakPeriod other) => StartTime <= other.EndTime && EndTime >= other.StartTime;
|
||||||
public override bool Equals(object? obj) => obj is BreakPeriod other && Equals(other);
|
|
||||||
|
public virtual bool Equals(BreakPeriod? other) =>
|
||||||
|
other != null
|
||||||
|
&& StartTime == other.StartTime
|
||||||
|
&& EndTime == other.EndTime;
|
||||||
|
|
||||||
public override int GetHashCode() => HashCode.Combine(StartTime, EndTime);
|
public override int GetHashCode() => HashCode.Combine(StartTime, EndTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,13 +13,7 @@ namespace osu.Game.Rulesets.Edit.Checks
|
|||||||
{
|
{
|
||||||
// Breaks may be off by 1 ms.
|
// Breaks may be off by 1 ms.
|
||||||
private const int leniency_threshold = 1;
|
private const int leniency_threshold = 1;
|
||||||
private const double minimum_gap_before_break = 200;
|
|
||||||
|
|
||||||
// Break end time depends on the upcoming object's pre-empt time.
|
|
||||||
// As things stand, "pre-empt time" is only defined for osu! standard
|
|
||||||
// This is a generic value representing AR=10
|
|
||||||
// Relevant: https://github.com/ppy/osu/issues/14330#issuecomment-1002158551
|
|
||||||
private const double min_end_threshold = 450;
|
|
||||||
public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Events, "Breaks not achievable using the editor");
|
public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Events, "Breaks not achievable using the editor");
|
||||||
|
|
||||||
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
|
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
|
||||||
@ -45,8 +39,8 @@ namespace osu.Game.Rulesets.Edit.Checks
|
|||||||
if (previousObjectEndTimeIndex >= 0)
|
if (previousObjectEndTimeIndex >= 0)
|
||||||
{
|
{
|
||||||
double gapBeforeBreak = breakPeriod.StartTime - endTimes[previousObjectEndTimeIndex];
|
double gapBeforeBreak = breakPeriod.StartTime - endTimes[previousObjectEndTimeIndex];
|
||||||
if (gapBeforeBreak < minimum_gap_before_break - leniency_threshold)
|
if (gapBeforeBreak < BreakPeriod.GAP_BEFORE_BREAK - leniency_threshold)
|
||||||
yield return new IssueTemplateEarlyStart(this).Create(breakPeriod.StartTime, minimum_gap_before_break - gapBeforeBreak);
|
yield return new IssueTemplateEarlyStart(this).Create(breakPeriod.StartTime, BreakPeriod.GAP_BEFORE_BREAK - gapBeforeBreak);
|
||||||
}
|
}
|
||||||
|
|
||||||
int nextObjectStartTimeIndex = startTimes.BinarySearch(breakPeriod.EndTime);
|
int nextObjectStartTimeIndex = startTimes.BinarySearch(breakPeriod.EndTime);
|
||||||
@ -55,8 +49,8 @@ namespace osu.Game.Rulesets.Edit.Checks
|
|||||||
if (nextObjectStartTimeIndex < startTimes.Count)
|
if (nextObjectStartTimeIndex < startTimes.Count)
|
||||||
{
|
{
|
||||||
double gapAfterBreak = startTimes[nextObjectStartTimeIndex] - breakPeriod.EndTime;
|
double gapAfterBreak = startTimes[nextObjectStartTimeIndex] - breakPeriod.EndTime;
|
||||||
if (gapAfterBreak < min_end_threshold - leniency_threshold)
|
if (gapAfterBreak < BreakPeriod.GAP_AFTER_BREAK - leniency_threshold)
|
||||||
yield return new IssueTemplateLateEnd(this).Create(breakPeriod.StartTime, min_end_threshold - gapAfterBreak);
|
yield return new IssueTemplateLateEnd(this).Create(breakPeriod.StartTime, BreakPeriod.GAP_AFTER_BREAK - gapAfterBreak);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,14 +54,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
Break = { BindTarget = Break },
|
Break = { BindTarget = Break },
|
||||||
Anchor = Anchor.TopLeft,
|
Anchor = Anchor.TopLeft,
|
||||||
Origin = Anchor.TopLeft,
|
Origin = Anchor.TopLeft,
|
||||||
Action = (time, breakPeriod) => breakPeriod with { StartTime = time },
|
Action = (time, breakPeriod) => new ManualBreakPeriod(time, breakPeriod.EndTime),
|
||||||
},
|
},
|
||||||
new DragHandle(isStartHandle: false)
|
new DragHandle(isStartHandle: false)
|
||||||
{
|
{
|
||||||
Break = { BindTarget = Break },
|
Break = { BindTarget = Break },
|
||||||
Anchor = Anchor.TopRight,
|
Anchor = Anchor.TopRight,
|
||||||
Origin = Anchor.TopRight,
|
Origin = Anchor.TopRight,
|
||||||
Action = (time, breakPeriod) => breakPeriod with { EndTime = time },
|
Action = (time, breakPeriod) => new ManualBreakPeriod(breakPeriod.StartTime, time),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -105,7 +105,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
BeatmapSkin.BeatmapSkinChanged += SaveState;
|
BeatmapSkin.BeatmapSkinChanged += SaveState;
|
||||||
}
|
}
|
||||||
|
|
||||||
beatmapProcessor = playableBeatmap.BeatmapInfo.Ruleset.CreateInstance().CreateBeatmapProcessor(this);
|
beatmapProcessor = new EditorBeatmapProcessor(this, playableBeatmap.BeatmapInfo.Ruleset.CreateInstance());
|
||||||
|
|
||||||
foreach (var obj in HitObjects)
|
foreach (var obj in HitObjects)
|
||||||
trackStartTime(obj);
|
trackStartTime(obj);
|
||||||
|
64
osu.Game/Screens/Edit/EditorBeatmapProcessor.cs
Normal file
64
osu.Game/Screens/Edit/EditorBeatmapProcessor.cs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.Timing;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit
|
||||||
|
{
|
||||||
|
public class EditorBeatmapProcessor : IBeatmapProcessor
|
||||||
|
{
|
||||||
|
public IBeatmap Beatmap { get; }
|
||||||
|
|
||||||
|
private readonly IBeatmapProcessor? rulesetBeatmapProcessor;
|
||||||
|
|
||||||
|
public EditorBeatmapProcessor(IBeatmap beatmap, Ruleset ruleset)
|
||||||
|
{
|
||||||
|
Beatmap = beatmap;
|
||||||
|
rulesetBeatmapProcessor = ruleset.CreateBeatmapProcessor(beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PreProcess()
|
||||||
|
{
|
||||||
|
rulesetBeatmapProcessor?.PreProcess();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PostProcess()
|
||||||
|
{
|
||||||
|
rulesetBeatmapProcessor?.PostProcess();
|
||||||
|
|
||||||
|
autoGenerateBreaks();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void autoGenerateBreaks()
|
||||||
|
{
|
||||||
|
Beatmap.Breaks.RemoveAll(b => b is not ManualBreakPeriod);
|
||||||
|
|
||||||
|
for (int i = 1; i < Beatmap.HitObjects.Count; ++i)
|
||||||
|
{
|
||||||
|
double previousObjectEndTime = Beatmap.HitObjects[i - 1].GetEndTime();
|
||||||
|
double nextObjectStartTime = Beatmap.HitObjects[i].StartTime;
|
||||||
|
|
||||||
|
if (nextObjectStartTime - previousObjectEndTime < BreakPeriod.MIN_GAP_DURATION)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
double breakStartTime = previousObjectEndTime + BreakPeriod.GAP_BEFORE_BREAK;
|
||||||
|
double breakEndTime = nextObjectStartTime - Math.Max(BreakPeriod.GAP_AFTER_BREAK, Beatmap.ControlPointInfo.TimingPointAt(nextObjectStartTime).BeatLength * 2);
|
||||||
|
|
||||||
|
if (breakEndTime - breakStartTime < BreakPeriod.MIN_BREAK_DURATION)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var breakPeriod = new BreakPeriod(breakStartTime, breakEndTime);
|
||||||
|
|
||||||
|
if (Beatmap.Breaks.Any(b => b.Intersects(breakPeriod)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Beatmap.Breaks.Add(breakPeriod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
osu.Game/Screens/Edit/ManualBreakPeriod.cs
Normal file
15
osu.Game/Screens/Edit/ManualBreakPeriod.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
using osu.Game.Beatmaps.Timing;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit
|
||||||
|
{
|
||||||
|
public record ManualBreakPeriod : BreakPeriod
|
||||||
|
{
|
||||||
|
public ManualBreakPeriod(double startTime, double endTime)
|
||||||
|
: base(startTime, endTime)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user