1
0
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:
Bartłomiej Dach 2024-05-01 15:48:21 +02:00 committed by GitHub
commit d443a9bc96
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 76 additions and 30 deletions

View File

@ -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)
{ {

View File

@ -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);

View File

@ -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;
} }

View File

@ -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)

View File

@ -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)

View File

@ -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>

View File

@ -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();

View File

@ -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;

View File

@ -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;