mirror of
https://github.com/ppy/osu.git
synced 2025-01-26 18:03:11 +08:00
Merge pull request #28033 from peppy/preserve-storyboard
Preserve storyboard events when saving a beatmap in the editor
This commit is contained in:
commit
d443a9bc96
@ -25,6 +25,7 @@ using osu.Game.Rulesets.Osu;
|
|||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Taiko;
|
using osu.Game.Rulesets.Taiko;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osu.Game.Storyboards;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -37,6 +38,22 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
|
|
||||||
private static IEnumerable<string> allBeatmaps = beatmaps_resource_store.GetAvailableResources().Where(res => res.EndsWith(".osu", StringComparison.Ordinal));
|
private static IEnumerable<string> allBeatmaps = beatmaps_resource_store.GetAvailableResources().Where(res => res.EndsWith(".osu", StringComparison.Ordinal));
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUnsupportedStoryboardEvents()
|
||||||
|
{
|
||||||
|
const string name = "Resources/storyboard_only_video.osu";
|
||||||
|
|
||||||
|
var decoded = decodeFromLegacy(beatmaps_resource_store.GetStream(name), name);
|
||||||
|
Assert.That(decoded.beatmap.UnhandledEventLines.Count, Is.EqualTo(1));
|
||||||
|
Assert.That(decoded.beatmap.UnhandledEventLines.Single(), Is.EqualTo("Video,0,\"video.avi\""));
|
||||||
|
|
||||||
|
var memoryStream = encodeToLegacy(decoded);
|
||||||
|
|
||||||
|
var storyboard = new LegacyStoryboardDecoder().Decode(new LineBufferedReader(memoryStream));
|
||||||
|
StoryboardLayer video = storyboard.Layers.Single(l => l.Name == "Video");
|
||||||
|
Assert.That(video.Elements.Count, Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
[TestCaseSource(nameof(allBeatmaps))]
|
[TestCaseSource(nameof(allBeatmaps))]
|
||||||
public void TestEncodeDecodeStability(string name)
|
public void TestEncodeDecodeStability(string name)
|
||||||
{
|
{
|
||||||
|
@ -63,6 +63,8 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
public List<BreakPeriod> Breaks { get; set; } = new List<BreakPeriod>();
|
public List<BreakPeriod> Breaks { get; set; } = new List<BreakPeriod>();
|
||||||
|
|
||||||
|
public List<string> UnhandledEventLines { get; set; } = new List<string>();
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public double TotalBreakTime => Breaks.Sum(b => b.Duration);
|
public double TotalBreakTime => Breaks.Sum(b => b.Duration);
|
||||||
|
|
||||||
|
@ -66,6 +66,7 @@ namespace osu.Game.Beatmaps
|
|||||||
beatmap.ControlPointInfo = original.ControlPointInfo;
|
beatmap.ControlPointInfo = original.ControlPointInfo;
|
||||||
beatmap.HitObjects = convertHitObjects(original.HitObjects, original, cancellationToken).OrderBy(s => s.StartTime).ToList();
|
beatmap.HitObjects = convertHitObjects(original.HitObjects, original, cancellationToken).OrderBy(s => s.StartTime).ToList();
|
||||||
beatmap.Breaks = original.Breaks;
|
beatmap.Breaks = original.Breaks;
|
||||||
|
beatmap.UnhandledEventLines = original.UnhandledEventLines;
|
||||||
|
|
||||||
return beatmap;
|
return beatmap;
|
||||||
}
|
}
|
||||||
|
@ -167,8 +167,6 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
beatmapInfo.SamplesMatchPlaybackRate = false;
|
beatmapInfo.SamplesMatchPlaybackRate = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool ShouldSkipLine(string line) => base.ShouldSkipLine(line) || line.StartsWith(' ') || line.StartsWith('_');
|
|
||||||
|
|
||||||
protected override void ParseLine(Beatmap beatmap, Section section, string line)
|
protected override void ParseLine(Beatmap beatmap, Section section, string line)
|
||||||
{
|
{
|
||||||
switch (section)
|
switch (section)
|
||||||
@ -417,43 +415,57 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
{
|
{
|
||||||
string[] split = line.Split(',');
|
string[] split = line.Split(',');
|
||||||
|
|
||||||
if (!Enum.TryParse(split[0], out LegacyEventType type))
|
// Until we have full storyboard encoder coverage, let's track any lines which aren't handled
|
||||||
throw new InvalidDataException($@"Unknown event type: {split[0]}");
|
// and store them to a temporary location such that they aren't lost on editor save / export.
|
||||||
|
bool lineSupportedByEncoder = false;
|
||||||
|
|
||||||
switch (type)
|
if (Enum.TryParse(split[0], out LegacyEventType type))
|
||||||
{
|
{
|
||||||
case LegacyEventType.Sprite:
|
switch (type)
|
||||||
// Generally, the background is the first thing defined in a beatmap file.
|
{
|
||||||
// In some older beatmaps, it is not present and replaced by a storyboard-level background instead.
|
case LegacyEventType.Sprite:
|
||||||
// Allow the first sprite (by file order) to act as the background in such cases.
|
// Generally, the background is the first thing defined in a beatmap file.
|
||||||
if (string.IsNullOrEmpty(beatmap.BeatmapInfo.Metadata.BackgroundFile))
|
// In some older beatmaps, it is not present and replaced by a storyboard-level background instead.
|
||||||
beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[3]);
|
// Allow the first sprite (by file order) to act as the background in such cases.
|
||||||
break;
|
if (string.IsNullOrEmpty(beatmap.BeatmapInfo.Metadata.BackgroundFile))
|
||||||
|
{
|
||||||
|
beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[3]);
|
||||||
|
lineSupportedByEncoder = true;
|
||||||
|
}
|
||||||
|
|
||||||
case LegacyEventType.Video:
|
break;
|
||||||
string filename = CleanFilename(split[2]);
|
|
||||||
|
|
||||||
// Some very old beatmaps had incorrect type specifications for their backgrounds (ie. using 1 for VIDEO
|
case LegacyEventType.Video:
|
||||||
// instead of 0 for BACKGROUND). To handle this gracefully, check the file extension against known supported
|
string filename = CleanFilename(split[2]);
|
||||||
// video extensions and handle similar to a background if it doesn't match.
|
|
||||||
if (!OsuGameBase.VIDEO_EXTENSIONS.Contains(Path.GetExtension(filename).ToLowerInvariant()))
|
|
||||||
{
|
|
||||||
beatmap.BeatmapInfo.Metadata.BackgroundFile = filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
// Some very old beatmaps had incorrect type specifications for their backgrounds (ie. using 1 for VIDEO
|
||||||
|
// instead of 0 for BACKGROUND). To handle this gracefully, check the file extension against known supported
|
||||||
|
// video extensions and handle similar to a background if it doesn't match.
|
||||||
|
if (!OsuGameBase.VIDEO_EXTENSIONS.Contains(Path.GetExtension(filename).ToLowerInvariant()))
|
||||||
|
{
|
||||||
|
beatmap.BeatmapInfo.Metadata.BackgroundFile = filename;
|
||||||
|
lineSupportedByEncoder = true;
|
||||||
|
}
|
||||||
|
|
||||||
case LegacyEventType.Background:
|
break;
|
||||||
beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[2]);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LegacyEventType.Break:
|
case LegacyEventType.Background:
|
||||||
double start = getOffsetTime(Parsing.ParseDouble(split[1]));
|
beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[2]);
|
||||||
double end = Math.Max(start, getOffsetTime(Parsing.ParseDouble(split[2])));
|
lineSupportedByEncoder = true;
|
||||||
|
break;
|
||||||
|
|
||||||
beatmap.Breaks.Add(new BreakPeriod(start, end));
|
case LegacyEventType.Break:
|
||||||
break;
|
double start = getOffsetTime(Parsing.ParseDouble(split[1]));
|
||||||
|
double end = Math.Max(start, getOffsetTime(Parsing.ParseDouble(split[2])));
|
||||||
|
|
||||||
|
beatmap.Breaks.Add(new BreakPeriod(start, end));
|
||||||
|
lineSupportedByEncoder = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!lineSupportedByEncoder)
|
||||||
|
beatmap.UnhandledEventLines.Add(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleTimingPoint(string line)
|
private void handleTimingPoint(string line)
|
||||||
|
@ -156,6 +156,9 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
|
|
||||||
foreach (var b in beatmap.Breaks)
|
foreach (var b in beatmap.Breaks)
|
||||||
writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Break},{b.StartTime},{b.EndTime}"));
|
writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Break},{b.StartTime},{b.EndTime}"));
|
||||||
|
|
||||||
|
foreach (string l in beatmap.UnhandledEventLines)
|
||||||
|
writer.WriteLine(l);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleControlPoints(TextWriter writer)
|
private void handleControlPoints(TextWriter writer)
|
||||||
|
@ -42,6 +42,12 @@ namespace osu.Game.Beatmaps
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
List<BreakPeriod> Breaks { get; }
|
List<BreakPeriod> Breaks { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// All lines from the [Events] section which aren't handled in the encoding process yet.
|
||||||
|
/// These lines shoule be written out to the beatmap file on save or export.
|
||||||
|
/// </summary>
|
||||||
|
List<string> UnhandledEventLines { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Total amount of break time in the beatmap.
|
/// Total amount of break time in the beatmap.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -330,6 +330,8 @@ namespace osu.Game.Rulesets.Difficulty
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<BreakPeriod> Breaks => baseBeatmap.Breaks;
|
public List<BreakPeriod> Breaks => baseBeatmap.Breaks;
|
||||||
|
public List<string> UnhandledEventLines => baseBeatmap.UnhandledEventLines;
|
||||||
|
|
||||||
public double TotalBreakTime => baseBeatmap.TotalBreakTime;
|
public double TotalBreakTime => baseBeatmap.TotalBreakTime;
|
||||||
public IEnumerable<BeatmapStatistic> GetStatistics() => baseBeatmap.GetStatistics();
|
public IEnumerable<BeatmapStatistic> GetStatistics() => baseBeatmap.GetStatistics();
|
||||||
public double GetMostCommonBeatLength() => baseBeatmap.GetMostCommonBeatLength();
|
public double GetMostCommonBeatLength() => baseBeatmap.GetMostCommonBeatLength();
|
||||||
|
@ -174,6 +174,8 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
public List<BreakPeriod> Breaks => PlayableBeatmap.Breaks;
|
public List<BreakPeriod> Breaks => PlayableBeatmap.Breaks;
|
||||||
|
|
||||||
|
public List<string> UnhandledEventLines => PlayableBeatmap.UnhandledEventLines;
|
||||||
|
|
||||||
public double TotalBreakTime => PlayableBeatmap.TotalBreakTime;
|
public double TotalBreakTime => PlayableBeatmap.TotalBreakTime;
|
||||||
|
|
||||||
public IReadOnlyList<HitObject> HitObjects => PlayableBeatmap.HitObjects;
|
public IReadOnlyList<HitObject> HitObjects => PlayableBeatmap.HitObjects;
|
||||||
|
@ -27,6 +27,7 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
BeatmapInfo = baseBeatmap.BeatmapInfo;
|
BeatmapInfo = baseBeatmap.BeatmapInfo;
|
||||||
ControlPointInfo = baseBeatmap.ControlPointInfo;
|
ControlPointInfo = baseBeatmap.ControlPointInfo;
|
||||||
Breaks = baseBeatmap.Breaks;
|
Breaks = baseBeatmap.Breaks;
|
||||||
|
UnhandledEventLines = baseBeatmap.UnhandledEventLines;
|
||||||
|
|
||||||
if (withHitObjects)
|
if (withHitObjects)
|
||||||
HitObjects = baseBeatmap.HitObjects;
|
HitObjects = baseBeatmap.HitObjects;
|
||||||
|
Loading…
Reference in New Issue
Block a user