mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 14:25:05 +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
|
||||
{
|
||||
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>
|
||||
/// The minimum duration required for a break to have any effect.
|
||||
/// </summary>
|
||||
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>
|
||||
/// The break start time.
|
||||
/// </summary>
|
||||
public double StartTime { get; init; }
|
||||
public double StartTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The break end time.
|
||||
/// </summary>
|
||||
public double EndTime { get; init; }
|
||||
public double EndTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The break duration.
|
||||
@ -51,8 +68,13 @@ namespace osu.Game.Beatmaps.Timing
|
||||
/// <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 Equals(BreakPeriod other) => StartTime.Equals(other.StartTime) && EndTime.Equals(other.EndTime);
|
||||
public override bool Equals(object? obj) => obj is BreakPeriod other && Equals(other);
|
||||
public bool Intersects(BreakPeriod other) => StartTime <= other.EndTime && EndTime >= other.StartTime;
|
||||
|
||||
public virtual bool Equals(BreakPeriod? other) =>
|
||||
other != null
|
||||
&& StartTime == other.StartTime
|
||||
&& EndTime == other.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.
|
||||
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 IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
|
||||
@ -45,8 +39,8 @@ namespace osu.Game.Rulesets.Edit.Checks
|
||||
if (previousObjectEndTimeIndex >= 0)
|
||||
{
|
||||
double gapBeforeBreak = breakPeriod.StartTime - endTimes[previousObjectEndTimeIndex];
|
||||
if (gapBeforeBreak < minimum_gap_before_break - leniency_threshold)
|
||||
yield return new IssueTemplateEarlyStart(this).Create(breakPeriod.StartTime, minimum_gap_before_break - gapBeforeBreak);
|
||||
if (gapBeforeBreak < BreakPeriod.GAP_BEFORE_BREAK - leniency_threshold)
|
||||
yield return new IssueTemplateEarlyStart(this).Create(breakPeriod.StartTime, BreakPeriod.GAP_BEFORE_BREAK - gapBeforeBreak);
|
||||
}
|
||||
|
||||
int nextObjectStartTimeIndex = startTimes.BinarySearch(breakPeriod.EndTime);
|
||||
@ -55,8 +49,8 @@ namespace osu.Game.Rulesets.Edit.Checks
|
||||
if (nextObjectStartTimeIndex < startTimes.Count)
|
||||
{
|
||||
double gapAfterBreak = startTimes[nextObjectStartTimeIndex] - breakPeriod.EndTime;
|
||||
if (gapAfterBreak < min_end_threshold - leniency_threshold)
|
||||
yield return new IssueTemplateLateEnd(this).Create(breakPeriod.StartTime, min_end_threshold - gapAfterBreak);
|
||||
if (gapAfterBreak < BreakPeriod.GAP_AFTER_BREAK - leniency_threshold)
|
||||
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 },
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
Action = (time, breakPeriod) => breakPeriod with { StartTime = time },
|
||||
Action = (time, breakPeriod) => new ManualBreakPeriod(time, breakPeriod.EndTime),
|
||||
},
|
||||
new DragHandle(isStartHandle: false)
|
||||
{
|
||||
Break = { BindTarget = Break },
|
||||
Anchor = 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;
|
||||
}
|
||||
|
||||
beatmapProcessor = playableBeatmap.BeatmapInfo.Ruleset.CreateInstance().CreateBeatmapProcessor(this);
|
||||
beatmapProcessor = new EditorBeatmapProcessor(this, playableBeatmap.BeatmapInfo.Ruleset.CreateInstance());
|
||||
|
||||
foreach (var obj in HitObjects)
|
||||
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