mirror of
https://github.com/ppy/osu.git
synced 2024-11-13 16:13:34 +08:00
Merge pull request #28528 from bdach/break-autogeneration
This commit is contained in:
commit
9eb6752033
@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods
|
||||
StartTime = 5000,
|
||||
}
|
||||
},
|
||||
Breaks = new List<BreakPeriod>
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(2000, 4000),
|
||||
}
|
||||
|
@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
Breaks = new List<BreakPeriod>
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(500, 2000),
|
||||
},
|
||||
|
@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
StartTime = 5000,
|
||||
}
|
||||
},
|
||||
Breaks = new List<BreakPeriod>
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(2000, 4000),
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
Breaks = new List<BreakPeriod>
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(500, 2000),
|
||||
},
|
||||
|
@ -177,7 +177,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
Breaks = new List<BreakPeriod>
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(100, 1600),
|
||||
},
|
||||
|
@ -1,7 +1,6 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -29,7 +28,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
{
|
||||
var beatmap = new Beatmap<HitObject>
|
||||
{
|
||||
Breaks = new List<BreakPeriod>
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(0, 649)
|
||||
}
|
||||
@ -52,7 +51,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
new HitCircle { StartTime = 0 },
|
||||
new HitCircle { StartTime = 1_200 }
|
||||
},
|
||||
Breaks = new List<BreakPeriod>
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(100, 751)
|
||||
}
|
||||
@ -75,7 +74,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
new HitCircle { StartTime = 0 },
|
||||
new HitCircle { StartTime = 1_298 }
|
||||
},
|
||||
Breaks = new List<BreakPeriod>
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(200, 850)
|
||||
}
|
||||
@ -98,7 +97,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
new HitCircle { StartTime = 0 },
|
||||
new HitCircle { StartTime = 1200 }
|
||||
},
|
||||
Breaks = new List<BreakPeriod>
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(1398, 2300)
|
||||
}
|
||||
@ -121,7 +120,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
new HitCircle { StartTime = 1100 },
|
||||
new HitCircle { StartTime = 1500 }
|
||||
},
|
||||
Breaks = new List<BreakPeriod>
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(0, 652)
|
||||
}
|
||||
@ -145,7 +144,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
new HitCircle { StartTime = 1_297 },
|
||||
new HitCircle { StartTime = 1_298 }
|
||||
},
|
||||
Breaks = new List<BreakPeriod>
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(200, 850)
|
||||
}
|
||||
@ -168,7 +167,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
new HitCircle { StartTime = 0 },
|
||||
new HitCircle { StartTime = 1_300 }
|
||||
},
|
||||
Breaks = new List<BreakPeriod>
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(200, 850)
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
new HitCircle { StartTime = 0 },
|
||||
new HitCircle { StartTime = 40_000 }
|
||||
},
|
||||
Breaks = new List<BreakPeriod>
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(10_000, 21_000)
|
||||
}
|
||||
|
300
osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs
Normal file
300
osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs
Normal file
@ -0,0 +1,300 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Screens.Edit;
|
||||
|
||||
namespace osu.Game.Tests.Editing
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestSceneEditorBeatmapProcessor
|
||||
{
|
||||
[Test]
|
||||
public void TestEmptyBeatmap()
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
};
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
beatmapProcessor.PostProcess();
|
||||
|
||||
Assert.That(beatmap.Breaks, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSingleObjectBeatmap()
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
}
|
||||
};
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
beatmapProcessor.PostProcess();
|
||||
|
||||
Assert.That(beatmap.Breaks, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTwoObjectsCloseTogether()
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 2000 },
|
||||
}
|
||||
};
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
beatmapProcessor.PostProcess();
|
||||
|
||||
Assert.That(beatmap.Breaks, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTwoObjectsFarApart()
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 5000 },
|
||||
}
|
||||
};
|
||||
|
||||
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(4000));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBreaksAreFused()
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 9000 },
|
||||
},
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(1200, 4000),
|
||||
new BreakPeriod(5200, 8000),
|
||||
}
|
||||
};
|
||||
|
||||
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(8000));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBreaksAreSplit()
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 5000 },
|
||||
new HitCircle { StartTime = 9000 },
|
||||
},
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(1200, 8000),
|
||||
}
|
||||
};
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
beatmapProcessor.PostProcess();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(beatmap.Breaks, Has.Count.EqualTo(2));
|
||||
Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200));
|
||||
Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(4000));
|
||||
Assert.That(beatmap.Breaks[1].StartTime, Is.EqualTo(5200));
|
||||
Assert.That(beatmap.Breaks[1].EndTime, Is.EqualTo(8000));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBreaksAreNudged()
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1100 },
|
||||
new HitCircle { StartTime = 9000 },
|
||||
},
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(1200, 8000),
|
||||
}
|
||||
};
|
||||
|
||||
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(1300));
|
||||
Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(8000));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestManualBreaksAreNotFused()
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 9000 },
|
||||
},
|
||||
Breaks =
|
||||
{
|
||||
new ManualBreakPeriod(1200, 4000),
|
||||
new ManualBreakPeriod(5200, 8000),
|
||||
}
|
||||
};
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
beatmapProcessor.PostProcess();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(beatmap.Breaks, Has.Count.EqualTo(2));
|
||||
Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200));
|
||||
Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(4000));
|
||||
Assert.That(beatmap.Breaks[1].StartTime, Is.EqualTo(5200));
|
||||
Assert.That(beatmap.Breaks[1].EndTime, Is.EqualTo(8000));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestManualBreaksAreSplit()
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 5000 },
|
||||
new HitCircle { StartTime = 9000 },
|
||||
},
|
||||
Breaks =
|
||||
{
|
||||
new ManualBreakPeriod(1200, 8000),
|
||||
}
|
||||
};
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
beatmapProcessor.PostProcess();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(beatmap.Breaks, Has.Count.EqualTo(2));
|
||||
Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200));
|
||||
Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(4000));
|
||||
Assert.That(beatmap.Breaks[1].StartTime, Is.EqualTo(5200));
|
||||
Assert.That(beatmap.Breaks[1].EndTime, Is.EqualTo(8000));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestManualBreaksAreNotNudged()
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 9000 },
|
||||
},
|
||||
Breaks =
|
||||
{
|
||||
new ManualBreakPeriod(1200, 8800),
|
||||
}
|
||||
};
|
||||
|
||||
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(8800));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -4,10 +4,12 @@
|
||||
#nullable disable
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
@ -15,6 +17,8 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
|
||||
|
||||
[Test]
|
||||
public void TestSelectedObjects()
|
||||
{
|
||||
|
@ -8,6 +8,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.IO.Serialization.Converters;
|
||||
|
||||
namespace osu.Game.Beatmaps
|
||||
@ -61,7 +62,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public ControlPointInfo ControlPointInfo { get; set; } = new ControlPointInfo();
|
||||
|
||||
public List<BreakPeriod> Breaks { get; set; } = new List<BreakPeriod>();
|
||||
public BindableList<BreakPeriod> Breaks { get; set; } = new BindableList<BreakPeriod>();
|
||||
|
||||
public List<string> UnhandledEventLines { get; set; } = new List<string>();
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@ -40,7 +41,7 @@ namespace osu.Game.Beatmaps
|
||||
/// <summary>
|
||||
/// The breaks in this beatmap.
|
||||
/// </summary>
|
||||
List<BreakPeriod> Breaks { get; }
|
||||
BindableList<BreakPeriod> Breaks { get; }
|
||||
|
||||
/// <summary>
|
||||
/// All lines from the [Events] section which aren't handled in the encoding process yet.
|
||||
|
@ -1,26 +1,44 @@
|
||||
// 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 osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.Beatmaps.Timing
|
||||
{
|
||||
public class BreakPeriod
|
||||
public class BreakPeriod : IEquatable<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;
|
||||
public double StartTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The break end time.
|
||||
/// </summary>
|
||||
public double EndTime;
|
||||
public double EndTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The break duration.
|
||||
@ -49,5 +67,14 @@ namespace osu.Game.Beatmaps.Timing
|
||||
/// <param name="time">The time to check in milliseconds.</param>
|
||||
/// <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 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);
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
@ -329,7 +330,7 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
set => baseBeatmap.Difficulty = value;
|
||||
}
|
||||
|
||||
public List<BreakPeriod> Breaks => baseBeatmap.Breaks;
|
||||
public BindableList<BreakPeriod> Breaks => baseBeatmap.Breaks;
|
||||
public List<string> UnhandledEventLines => baseBeatmap.UnhandledEventLines;
|
||||
|
||||
public double TotalBreakTime => baseBeatmap.TotalBreakTime;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
@ -14,35 +15,33 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
||||
/// </summary>
|
||||
public partial class BreakPart : TimelinePart
|
||||
{
|
||||
private readonly BindableList<BreakPeriod> breaks = new BindableList<BreakPeriod>();
|
||||
|
||||
protected override void LoadBeatmap(EditorBeatmap beatmap)
|
||||
{
|
||||
base.LoadBeatmap(beatmap);
|
||||
foreach (var breakPeriod in beatmap.Breaks)
|
||||
Add(new BreakVisualisation(breakPeriod));
|
||||
|
||||
breaks.UnbindAll();
|
||||
breaks.BindTo(beatmap.Breaks);
|
||||
breaks.BindCollectionChanged((_, _) =>
|
||||
{
|
||||
foreach (var breakPeriod in beatmap.Breaks)
|
||||
Add(new BreakVisualisation(breakPeriod));
|
||||
}, true);
|
||||
}
|
||||
|
||||
private partial class BreakVisualisation : Circle
|
||||
{
|
||||
private readonly BreakPeriod breakPeriod;
|
||||
|
||||
public BreakVisualisation(BreakPeriod breakPeriod)
|
||||
{
|
||||
this.breakPeriod = breakPeriod;
|
||||
|
||||
RelativePositionAxes = Axes.X;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
X = (float)breakPeriod.StartTime;
|
||||
Width = (float)breakPeriod.Duration;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours) => Colour = colours.GreyCarmineLight;
|
||||
private void load(OsuColour colours) => Colour = colours.Gray7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -20,11 +21,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
{
|
||||
public partial class TimelineBreak : CompositeDrawable
|
||||
{
|
||||
public BreakPeriod Break { get; }
|
||||
public Bindable<BreakPeriod> Break { get; } = new Bindable<BreakPeriod>();
|
||||
|
||||
public TimelineBreak(BreakPeriod b)
|
||||
{
|
||||
Break = b;
|
||||
Break.Value = b;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -44,44 +45,50 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
Child = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colours.PurpleLight,
|
||||
Alpha = 0.4f,
|
||||
Colour = colours.Gray5,
|
||||
Alpha = 0.7f,
|
||||
},
|
||||
},
|
||||
new DragHandle(Break, isStartHandle: true)
|
||||
new DragHandle(isStartHandle: true)
|
||||
{
|
||||
Break = { BindTarget = Break },
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
Action = (time, breakPeriod) => breakPeriod.StartTime = time,
|
||||
Action = (time, breakPeriod) => new ManualBreakPeriod(time, breakPeriod.EndTime),
|
||||
},
|
||||
new DragHandle(Break, isStartHandle: false)
|
||||
new DragHandle(isStartHandle: false)
|
||||
{
|
||||
Break = { BindTarget = Break },
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Action = (time, breakPeriod) => breakPeriod.EndTime = time,
|
||||
Action = (time, breakPeriod) => new ManualBreakPeriod(breakPeriod.StartTime, time),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.Update();
|
||||
base.LoadComplete();
|
||||
|
||||
X = (float)Break.StartTime;
|
||||
Width = (float)Break.Duration;
|
||||
Break.BindValueChanged(_ =>
|
||||
{
|
||||
X = (float)Break.Value.StartTime;
|
||||
Width = (float)Break.Value.Duration;
|
||||
}, true);
|
||||
}
|
||||
|
||||
private partial class DragHandle : FillFlowContainer
|
||||
{
|
||||
public Bindable<BreakPeriod> Break { get; } = new Bindable<BreakPeriod>();
|
||||
|
||||
public new Anchor Anchor
|
||||
{
|
||||
get => base.Anchor;
|
||||
init => base.Anchor = value;
|
||||
}
|
||||
|
||||
public Action<double, BreakPeriod>? Action { get; init; }
|
||||
public Func<double, BreakPeriod, BreakPeriod>? Action { get; init; }
|
||||
|
||||
private readonly BreakPeriod breakPeriod;
|
||||
private readonly bool isStartHandle;
|
||||
|
||||
private Container handle = null!;
|
||||
@ -99,9 +106,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
public DragHandle(BreakPeriod breakPeriod, bool isStartHandle)
|
||||
public DragHandle(bool isStartHandle)
|
||||
{
|
||||
this.breakPeriod = breakPeriod;
|
||||
this.isStartHandle = isStartHandle;
|
||||
}
|
||||
|
||||
@ -164,13 +170,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
changeHandler?.BeginChange();
|
||||
updateState();
|
||||
|
||||
double min = beatmap.HitObjects.Last(ho => ho.GetEndTime() <= breakPeriod.StartTime).GetEndTime();
|
||||
double max = beatmap.HitObjects.First(ho => ho.StartTime >= breakPeriod.EndTime).StartTime;
|
||||
double min = beatmap.HitObjects.Last(ho => ho.GetEndTime() <= Break.Value.StartTime).GetEndTime();
|
||||
double max = beatmap.HitObjects.First(ho => ho.StartTime >= Break.Value.EndTime).StartTime;
|
||||
|
||||
if (isStartHandle)
|
||||
max = Math.Min(max, breakPeriod.EndTime - BreakPeriod.MIN_BREAK_DURATION);
|
||||
max = Math.Min(max, Break.Value.EndTime - BreakPeriod.MIN_BREAK_DURATION);
|
||||
else
|
||||
min = Math.Max(min, breakPeriod.StartTime + BreakPeriod.MIN_BREAK_DURATION);
|
||||
min = Math.Max(min, Break.Value.StartTime + BreakPeriod.MIN_BREAK_DURATION);
|
||||
|
||||
allowedDragRange = (min, max);
|
||||
|
||||
@ -183,11 +189,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
|
||||
Debug.Assert(allowedDragRange != null);
|
||||
|
||||
if (timeline.FindSnappedPositionAndTime(e.ScreenSpaceMousePosition).Time is double time
|
||||
if (Action != null
|
||||
&& timeline.FindSnappedPositionAndTime(e.ScreenSpaceMousePosition).Time is double time
|
||||
&& time > allowedDragRange.Value.min
|
||||
&& time < allowedDragRange.Value.max)
|
||||
{
|
||||
Action?.Invoke(time, breakPeriod);
|
||||
int index = beatmap.Breaks.IndexOf(Break.Value);
|
||||
beatmap.Breaks[index] = Break.Value = Action.Invoke(time, Break.Value);
|
||||
}
|
||||
|
||||
updateState();
|
||||
@ -204,7 +212,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
{
|
||||
bool active = IsHovered || IsDragged;
|
||||
|
||||
var colour = colours.PurpleLighter;
|
||||
var colour = colours.Gray8;
|
||||
if (active)
|
||||
colour = colour.Lighten(0.3f);
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
// 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.Collections.Specialized;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Caching;
|
||||
@ -27,8 +28,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
{
|
||||
base.LoadBeatmap(beatmap);
|
||||
|
||||
// TODO: this will have to be mutable soon enough
|
||||
breaks.AddRange(beatmap.Breaks);
|
||||
breaks.UnbindAll();
|
||||
breaks.BindTo(beatmap.Breaks);
|
||||
breaks.BindCollectionChanged((_, e) =>
|
||||
{
|
||||
if (e.Action != NotifyCollectionChangedAction.Replace)
|
||||
breakCache.Invalidate();
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
@ -56,14 +62,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
|
||||
private void recreateBreaks()
|
||||
{
|
||||
// Remove groups outside the visible range
|
||||
foreach (TimelineBreak drawableBreak in this)
|
||||
{
|
||||
if (!shouldBeVisible(drawableBreak.Break))
|
||||
drawableBreak.Expire();
|
||||
}
|
||||
Clear();
|
||||
|
||||
// Add remaining ones
|
||||
for (int i = 0; i < breaks.Count; i++)
|
||||
{
|
||||
var breakPeriod = breaks[i];
|
||||
@ -71,20 +71,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
if (!shouldBeVisible(breakPeriod))
|
||||
continue;
|
||||
|
||||
bool alreadyVisible = false;
|
||||
|
||||
foreach (var b in this)
|
||||
{
|
||||
if (ReferenceEquals(b.Break, breakPeriod))
|
||||
{
|
||||
alreadyVisible = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (alreadyVisible)
|
||||
continue;
|
||||
|
||||
Add(new TimelineBreak(breakPeriod));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
@ -172,7 +172,7 @@ namespace osu.Game.Screens.Edit
|
||||
set => PlayableBeatmap.ControlPointInfo = value;
|
||||
}
|
||||
|
||||
public List<BreakPeriod> Breaks => PlayableBeatmap.Breaks;
|
||||
public BindableList<BreakPeriod> Breaks => PlayableBeatmap.Breaks;
|
||||
|
||||
public List<string> UnhandledEventLines => PlayableBeatmap.UnhandledEventLines;
|
||||
|
||||
@ -349,13 +349,13 @@ namespace osu.Game.Screens.Edit
|
||||
if (batchPendingUpdates.Count == 0 && batchPendingDeletes.Count == 0 && batchPendingInserts.Count == 0)
|
||||
return;
|
||||
|
||||
beatmapProcessor?.PreProcess();
|
||||
beatmapProcessor.PreProcess();
|
||||
|
||||
foreach (var h in batchPendingDeletes) processHitObject(h);
|
||||
foreach (var h in batchPendingInserts) processHitObject(h);
|
||||
foreach (var h in batchPendingUpdates) processHitObject(h);
|
||||
|
||||
beatmapProcessor?.PostProcess();
|
||||
beatmapProcessor.PostProcess();
|
||||
|
||||
BeatmapReprocessed?.Invoke();
|
||||
|
||||
|
70
osu.Game/Screens/Edit/EditorBeatmapProcessor.cs
Normal file
70
osu.Game/Screens/Edit/EditorBeatmapProcessor.cs
Normal file
@ -0,0 +1,70 @@
|
||||
// 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);
|
||||
|
||||
foreach (var manualBreak in Beatmap.Breaks.ToList())
|
||||
{
|
||||
if (Beatmap.HitObjects.Any(ho => ho.StartTime <= manualBreak.EndTime && ho.GetEndTime() >= manualBreak.StartTime))
|
||||
Beatmap.Breaks.Remove(manualBreak);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -45,6 +45,7 @@ namespace osu.Game.Screens.Edit
|
||||
editorBeatmap.BeginChange();
|
||||
processHitObjects(result, () => newBeatmap ??= readBeatmap(newState));
|
||||
processTimingPoints(() => newBeatmap ??= readBeatmap(newState));
|
||||
processBreaks(() => newBeatmap ??= readBeatmap(newState));
|
||||
processHitObjectLocalData(() => newBeatmap ??= readBeatmap(newState));
|
||||
editorBeatmap.EndChange();
|
||||
}
|
||||
@ -75,6 +76,27 @@ namespace osu.Game.Screens.Edit
|
||||
}
|
||||
}
|
||||
|
||||
private void processBreaks(Func<IBeatmap> getNewBeatmap)
|
||||
{
|
||||
var newBreaks = getNewBeatmap().Breaks.ToArray();
|
||||
|
||||
foreach (var oldBreak in editorBeatmap.Breaks.ToArray())
|
||||
{
|
||||
if (newBreaks.Any(b => b.Equals(oldBreak)))
|
||||
continue;
|
||||
|
||||
editorBeatmap.Breaks.Remove(oldBreak);
|
||||
}
|
||||
|
||||
foreach (var newBreak in newBreaks)
|
||||
{
|
||||
if (editorBeatmap.Breaks.Any(b => b.Equals(newBreak)))
|
||||
continue;
|
||||
|
||||
editorBeatmap.Breaks.Add(newBreak);
|
||||
}
|
||||
}
|
||||
|
||||
private void processHitObjects(DiffResult result, Func<IBeatmap> getNewBeatmap)
|
||||
{
|
||||
findChangedIndices(result, LegacyDecoder<Beatmap>.Section.HitObjects, out var removedIndices, out var addedIndices);
|
||||
|
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 class ManualBreakPeriod : BreakPeriod
|
||||
{
|
||||
public ManualBreakPeriod(double startTime, double endTime)
|
||||
: base(startTime, endTime)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -26,11 +26,13 @@ namespace osu.Game.Tests.Beatmaps
|
||||
|
||||
BeatmapInfo = baseBeatmap.BeatmapInfo;
|
||||
ControlPointInfo = baseBeatmap.ControlPointInfo;
|
||||
Breaks = baseBeatmap.Breaks;
|
||||
UnhandledEventLines = baseBeatmap.UnhandledEventLines;
|
||||
|
||||
if (withHitObjects)
|
||||
{
|
||||
HitObjects = baseBeatmap.HitObjects;
|
||||
Breaks = baseBeatmap.Breaks;
|
||||
}
|
||||
|
||||
BeatmapInfo.Ruleset = ruleset;
|
||||
BeatmapInfo.Length = 75000;
|
||||
|
Loading…
Reference in New Issue
Block a user