From 217dd2ecdc273af42124b89fa2c902828d545c95 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Mar 2018 21:23:03 +0900 Subject: [PATCH 1/2] Initial push for better decoders --- .../Formats/LegacyBeatmapDecoderTest.cs | 16 +- .../Formats/LegacyStoryboardDecoderTest.cs | 4 +- .../Beatmaps/Formats/OsuJsonDecoderTest.cs | 4 +- .../Beatmaps/IO/OszArchiveReaderTest.cs | 2 +- osu.Game/Beatmaps/Beatmap.cs | 7 +- osu.Game/Beatmaps/BeatmapManager.cs | 6 +- .../Beatmaps/BeatmapManager_WorkingBeatmap.cs | 20 +- osu.Game/Beatmaps/Formats/Decoder.cs | 81 +++-- .../Beatmaps/Formats/JsonBeatmapDecoder.cs | 14 +- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 40 +-- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 59 +--- .../Formats/LegacyStoryboardDecoder.cs | 296 +++++++++--------- .../Objects/Legacy/ConvertHitObjectParser.cs | 4 +- .../Tests/Beatmaps/BeatmapConversionTest.cs | 4 +- osu.Game/Tests/Visual/TestCasePlayer.cs | 2 +- 15 files changed, 250 insertions(+), 309 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index ab10da2cd1..b74be134c1 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { - var beatmap = decoder.DecodeBeatmap(stream); + var beatmap = decoder.Decode(stream); var beatmapInfo = beatmap.BeatmapInfo; var metadata = beatmap.Metadata; @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { - var beatmapInfo = decoder.DecodeBeatmap(stream).BeatmapInfo; + var beatmapInfo = decoder.Decode(stream).BeatmapInfo; int[] expectedBookmarks = { @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { - var beatmap = decoder.DecodeBeatmap(stream); + var beatmap = decoder.Decode(stream); var beatmapInfo = beatmap.BeatmapInfo; var metadata = beatmap.Metadata; @@ -96,7 +96,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { - var difficulty = decoder.DecodeBeatmap(stream).BeatmapInfo.BaseDifficulty; + var difficulty = decoder.Decode(stream).BeatmapInfo.BaseDifficulty; Assert.AreEqual(6.5f, difficulty.DrainRate); Assert.AreEqual(4, difficulty.CircleSize); @@ -114,7 +114,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { - var beatmap = decoder.DecodeBeatmap(stream); + var beatmap = decoder.Decode(stream); var metadata = beatmap.Metadata; var breakPoint = beatmap.Breaks[0]; @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { - var beatmap = decoder.DecodeBeatmap(stream); + var beatmap = decoder.Decode(stream); var controlPoints = beatmap.ControlPointInfo; Assert.AreEqual(4, controlPoints.TimingPoints.Count); @@ -167,7 +167,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { - var comboColors = decoder.DecodeBeatmap(stream).ComboColors; + var comboColors = decoder.Decode(stream).ComboColors; Color4[] expectedColors = { @@ -191,7 +191,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { - var hitObjects = decoder.DecodeBeatmap(stream).HitObjects; + var hitObjects = decoder.Decode(stream).HitObjects; var curveData = hitObjects[0] as IHasCurve; var positionData = hitObjects[0] as IHasPosition; diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs index dce6c0f55b..1c0801c634 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs @@ -18,11 +18,11 @@ namespace osu.Game.Tests.Beatmaps.Formats [Test] public void TestDecodeStoryboardEvents() { - var decoder = new LegacyBeatmapDecoder(); + var decoder = new LegacyStoryboardDecoder(); using (var resStream = Resource.OpenResource("Himeringo - Yotsuya-san ni Yoroshiku (RLC) [Winber1's Extreme].osu")) using (var stream = new StreamReader(resStream)) { - var storyboard = decoder.GetStoryboardDecoder().DecodeStoryboard(stream); + var storyboard = decoder.Decode(stream); Assert.IsTrue(storyboard.HasDrawable); Assert.AreEqual(4, storyboard.Layers.Count()); diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 89d96c774e..80dea9d01d 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -159,7 +159,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var sr = new StreamReader(stream)) { - var legacyDecoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.DecodeBeatmap(sr); + var legacyDecoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr); using (var ms = new MemoryStream()) using (var sw = new StreamWriter(ms)) using (var sr2 = new StreamReader(ms)) @@ -168,7 +168,7 @@ namespace osu.Game.Tests.Beatmaps.Formats sw.Flush(); ms.Position = 0; - return (legacyDecoded, new JsonBeatmapDecoder().DecodeBeatmap(sr2)); + return (legacyDecoded, new JsonBeatmapDecoder().Decode(sr2)); } } } diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs index 1f7246a119..29d25accbb 100644 --- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Beatmaps.IO BeatmapMetadata meta; using (var stream = new StreamReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu"))) - meta = Decoder.GetDecoder(stream).DecodeBeatmap(stream).Metadata; + meta = Decoder.GetDecoder(stream).Decode(stream).Metadata; Assert.AreEqual(241526, meta.OnlineBeatmapSetID); Assert.AreEqual("Soleily", meta.Artist); diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 4fd54e4364..9b00993b6e 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -22,6 +22,7 @@ namespace osu.Game.Beatmaps public BeatmapInfo BeatmapInfo = new BeatmapInfo(); public ControlPointInfo ControlPointInfo = new ControlPointInfo(); public List Breaks = new List(); + public List ComboColors = new List { new Color4(17, 136, 170, 255), @@ -85,9 +86,13 @@ namespace osu.Game.Beatmaps /// Constructs a new beatmap. /// /// The original beatmap to use the parameters of. - public Beatmap(Beatmap original = null) + public Beatmap(Beatmap original) : base(original) { } + + public Beatmap() + { + } } } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 1d6d8b6726..817a3388e2 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -301,7 +301,7 @@ namespace osu.Game.Beatmaps BeatmapMetadata metadata; using (var stream = new StreamReader(reader.GetStream(mapName))) - metadata = Decoder.GetDecoder(stream).DecodeBeatmap(stream).Metadata; + metadata = Decoder.GetDecoder(stream).Decode(stream).Metadata; return new BeatmapSetInfo { @@ -328,8 +328,8 @@ namespace osu.Game.Beatmaps raw.CopyTo(ms); ms.Position = 0; - var decoder = Decoder.GetDecoder(sr); - Beatmap beatmap = decoder.DecodeBeatmap(sr); + var decoder = Decoder.GetDecoder(sr); + Beatmap beatmap = decoder.Decode(sr); beatmap.BeatmapInfo.Path = name; beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash(); diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index a72c1adfcd..fb11684309 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; +using osu.Framework.Logging; using osu.Game.Beatmaps.Formats; using osu.Game.Graphics.Textures; using osu.Game.Storyboards; @@ -30,10 +31,7 @@ namespace osu.Game.Beatmaps try { using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo.Path)))) - { - Decoder decoder = Decoder.GetDecoder(stream); - return decoder.DecodeBeatmap(stream); - } + return Decoder.GetDecoder(stream).Decode(stream); } catch { @@ -78,23 +76,23 @@ namespace osu.Game.Beatmaps Storyboard storyboard; try { - using (var beatmap = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo.Path)))) + using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo.Path)))) { - Decoder decoder = Decoder.GetDecoder(beatmap); + var decoder = Decoder.GetDecoder(stream); // todo: support loading from both set-wide storyboard *and* beatmap specific. - if (BeatmapSetInfo?.StoryboardFile == null) - storyboard = decoder.GetStoryboardDecoder().DecodeStoryboard(beatmap); + storyboard = decoder.Decode(stream); else { - using (var reader = new StreamReader(store.GetStream(getPathForFile(BeatmapSetInfo.StoryboardFile)))) - storyboard = decoder.GetStoryboardDecoder().DecodeStoryboard(beatmap, reader); + using (var secondaryStream = new StreamReader(store.GetStream(getPathForFile(BeatmapSetInfo.StoryboardFile)))) + storyboard = decoder.Decode(stream, secondaryStream); } } } - catch + catch (Exception e) { + Logger.Error(e, "Storyboard failed to load"); storyboard = new Storyboard(); } diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs index 1aae52208a..9f10485c5f 100644 --- a/osu.Game/Beatmaps/Formats/Decoder.cs +++ b/osu.Game/Beatmaps/Formats/Decoder.cs @@ -4,38 +4,64 @@ using System; using System.Collections.Generic; using System.IO; -using osu.Game.Storyboards; +using System.Linq; namespace osu.Game.Beatmaps.Formats { + public abstract class Decoder : Decoder + where TOutput : new() + { + protected virtual TOutput CreateTemplateObject() => new TOutput(); + + public TOutput Decode(StreamReader primaryStream, params StreamReader[] otherStreams) + { + var output = CreateTemplateObject(); + foreach (StreamReader stream in new[] { primaryStream }.Concat(otherStreams)) + ParseStreamInto(stream, output); + return output; + } + + protected abstract void ParseStreamInto(StreamReader stream, TOutput beatmap); + } + public abstract class Decoder { - private static readonly Dictionary> decoders = new Dictionary>(); + private static readonly Dictionary>> decoders = new Dictionary>>(); static Decoder() { - LegacyDecoder.Register(); + LegacyBeatmapDecoder.Register(); JsonBeatmapDecoder.Register(); + LegacyStoryboardDecoder.Register(); } /// /// Retrieves a to parse a . /// /// A stream pointing to the . - public static Decoder GetDecoder(StreamReader stream) + public static Decoder GetDecoder(StreamReader stream) + where T : new() { if (stream == null) throw new ArgumentNullException(nameof(stream)); + if (!decoders.TryGetValue(typeof(T), out var typedDecoders)) + throw new IOException(@"Unknown decoder type"); + string line; do - { line = stream.ReadLine()?.Trim(); } - while (line != null && line.Length == 0); + { + line = stream.ReadLine()?.Trim(); + } while (line != null && line.Length == 0); - if (line == null || !decoders.ContainsKey(line)) + if (line == null) throw new IOException(@"Unknown file format"); - return decoders[line](line); + var decoder = typedDecoders.Select(d => line.StartsWith(d.Key) ? d.Value : null).FirstOrDefault(); + if (decoder == null) + throw new IOException(@"Unknown file format"); + + return (Decoder)decoder.Invoke(line); } /// @@ -43,41 +69,12 @@ namespace osu.Game.Beatmaps.Formats /// /// A string in the file which triggers this decoder to be used. /// A function which constructs the given . - protected static void AddDecoder(string magic, Func constructor) + protected static void AddDecoder(string magic, Func constructor) { - decoders[magic] = constructor; + if (!decoders.TryGetValue(typeof(T), out var typedDecoders)) + decoders.Add(typeof(T), typedDecoders = new Dictionary>()); + + typedDecoders[magic] = constructor; } - - /// - /// Retrieves a to parse a - /// - public abstract Decoder GetStoryboardDecoder(); - - public virtual Beatmap DecodeBeatmap(StreamReader stream) - { - var beatmap = new Beatmap - { - BeatmapInfo = new BeatmapInfo - { - Metadata = new BeatmapMetadata(), - BaseDifficulty = new BeatmapDifficulty(), - }, - }; - - ParseBeatmap(stream, beatmap); - return beatmap; - } - - protected abstract void ParseBeatmap(StreamReader stream, Beatmap beatmap); - - public virtual Storyboard DecodeStoryboard(params StreamReader[] streams) - { - var storyboard = new Storyboard(); - foreach (StreamReader stream in streams) - ParseStoryboard(stream, storyboard); - return storyboard; - } - - protected abstract void ParseStoryboard(StreamReader stream, Storyboard storyboard); } } diff --git a/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs index b0798e5a87..add0f39280 100644 --- a/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs @@ -3,20 +3,17 @@ using System.IO; using osu.Game.IO.Serialization; -using osu.Game.Storyboards; namespace osu.Game.Beatmaps.Formats { - public class JsonBeatmapDecoder : Decoder + public class JsonBeatmapDecoder : Decoder { public static void Register() { - AddDecoder("{", m => new JsonBeatmapDecoder()); + AddDecoder("{", m => new JsonBeatmapDecoder()); } - public override Decoder GetStoryboardDecoder() => this; - - protected override void ParseBeatmap(StreamReader stream, Beatmap beatmap) + protected override void ParseStreamInto(StreamReader stream, Beatmap beatmap) { stream.BaseStream.Position = 0; stream.DiscardBufferedData(); @@ -26,10 +23,5 @@ namespace osu.Game.Beatmaps.Formats foreach (var hitObject in beatmap.HitObjects) hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty); } - - protected override void ParseStoryboard(StreamReader stream, Storyboard storyboard) - { - // throw new System.NotImplementedException(); - } } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 7d4f8b5bf5..c54d81aa2b 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -4,6 +4,7 @@ using System; using System.Globalization; using System.IO; +using System.Linq; using OpenTK.Graphics; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Objects.Legacy; @@ -12,8 +13,10 @@ using osu.Framework; namespace osu.Game.Beatmaps.Formats { - public class LegacyBeatmapDecoder : LegacyDecoder + public class LegacyBeatmapDecoder : LegacyDecoder { + public const int LATEST_VERSION = 14; + private Beatmap beatmap; private bool hasCustomColours; @@ -22,6 +25,11 @@ namespace osu.Game.Beatmaps.Formats private LegacySampleBank defaultSampleBank; private int defaultSampleVolume = 100; + public static void Register() + { + AddDecoder(@"osu file format v", m => new LegacyBeatmapDecoder(int.Parse(m.Split('v').Last()))); + } + /// /// lazer's audio timings in general doesn't match stable. this is the result of user testing, albeit limited. /// This only seems to be required on windows. We need to eventually figure out why, with a bit of luck. @@ -35,29 +43,16 @@ namespace osu.Game.Beatmaps.Formats private readonly int offset = UniversalOffset; - public LegacyBeatmapDecoder() + public LegacyBeatmapDecoder(int version = LATEST_VERSION) : base(version) { - } - - public LegacyBeatmapDecoder(string header) - { - BeatmapVersion = int.Parse(header.Substring(17)); - // BeatmapVersion 4 and lower had an incorrect offset (stable has this set as 24ms off) - offset += BeatmapVersion < 5 ? 24 : 0; + offset += FormatVersion < 5 ? 24 : 0; } - protected override void ParseBeatmap(StreamReader stream, Beatmap beatmap) + protected override void ParseStreamInto(StreamReader stream, Beatmap beatmap) { - if (stream == null) - throw new ArgumentNullException(nameof(stream)); - if (beatmap == null) - throw new ArgumentNullException(nameof(beatmap)); - this.beatmap = beatmap; - this.beatmap.BeatmapInfo.BeatmapVersion = BeatmapVersion; - - ParseContent(stream); + base.ParseStreamInto(stream, beatmap); // objects may be out of order *only* if a user has manually edited an .osu file. // unfortunately there are ranked maps in this state (example: https://osu.ppy.sh/s/594828). @@ -67,14 +62,9 @@ namespace osu.Game.Beatmaps.Formats hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.BeatmapInfo.BaseDifficulty); } - protected override bool ShouldSkipLine(string line) - { - if (base.ShouldSkipLine(line) || line.StartsWith(" ") || line.StartsWith("_")) - return true; - return false; - } + protected override bool ShouldSkipLine(string line) => base.ShouldSkipLine(line) || line.StartsWith(" ") || line.StartsWith("_"); - protected override void ProcessSection(Section section, string line) + protected override void ParseLine(Beatmap beatmap, Section section, string line) { switch (section) { diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index e0fc439924..6a3fb82586 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -4,47 +4,20 @@ using System; using System.Collections.Generic; using System.IO; -using osu.Game.Beatmaps.Legacy; -using osu.Game.Storyboards; namespace osu.Game.Beatmaps.Formats { - public abstract class LegacyDecoder : Decoder + public abstract class LegacyDecoder : Decoder + where T : new() { - public static void Register() + protected readonly int FormatVersion; + + protected LegacyDecoder(int version) { - AddDecoder(@"osu file format v14", m => new LegacyBeatmapDecoder(m)); - AddDecoder(@"osu file format v13", m => new LegacyBeatmapDecoder(m)); - AddDecoder(@"osu file format v12", m => new LegacyBeatmapDecoder(m)); - AddDecoder(@"osu file format v11", m => new LegacyBeatmapDecoder(m)); - AddDecoder(@"osu file format v10", m => new LegacyBeatmapDecoder(m)); - AddDecoder(@"osu file format v9", m => new LegacyBeatmapDecoder(m)); - AddDecoder(@"osu file format v8", m => new LegacyBeatmapDecoder(m)); - AddDecoder(@"osu file format v7", m => new LegacyBeatmapDecoder(m)); - AddDecoder(@"osu file format v6", m => new LegacyBeatmapDecoder(m)); - AddDecoder(@"osu file format v5", m => new LegacyBeatmapDecoder(m)); - AddDecoder(@"osu file format v4", m => new LegacyBeatmapDecoder(m)); - AddDecoder(@"osu file format v3", m => new LegacyBeatmapDecoder(m)); - // TODO: differences between versions + FormatVersion = version; } - protected int BeatmapVersion; - - public override Decoder GetStoryboardDecoder() => new LegacyStoryboardDecoder(BeatmapVersion); - - public override Beatmap DecodeBeatmap(StreamReader stream) => new LegacyBeatmap(base.DecodeBeatmap(stream)); - - protected override void ParseBeatmap(StreamReader stream, Beatmap beatmap) - { - throw new NotImplementedException(); - } - - protected override void ParseStoryboard(StreamReader stream, Storyboard storyboard) - { - throw new NotImplementedException(); - } - - protected void ParseContent(StreamReader stream) + protected override void ParseStreamInto(StreamReader stream, T beatmap) { Section section = Section.None; @@ -54,13 +27,6 @@ namespace osu.Game.Beatmaps.Formats if (ShouldSkipLine(line)) continue; - // It's already set in ParseBeatmap... why do it again? - //if (line.StartsWith(@"osu file format v")) - //{ - // Beatmap.BeatmapInfo.BeatmapVersion = int.Parse(line.Substring(17)); - // continue; - //} - if (line.StartsWith(@"[") && line.EndsWith(@"]")) { if (!Enum.TryParse(line.Substring(1, line.Length - 2), out section)) @@ -68,18 +34,13 @@ namespace osu.Game.Beatmaps.Formats continue; } - ProcessSection(section, line); + ParseLine(beatmap, section, line); } } - protected virtual bool ShouldSkipLine(string line) - { - if (string.IsNullOrWhiteSpace(line) || line.StartsWith("//")) - return true; - return false; - } + protected virtual bool ShouldSkipLine(string line) => string.IsNullOrWhiteSpace(line) || line.StartsWith("//"); - protected abstract void ProcessSection(Section section, string line); + protected abstract void ParseLine(T output, Section section, string line); protected KeyValuePair SplitKeyVal(string line, char separator) { diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index a4ff060c83..e35276ae1a 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Globalization; using System.IO; using OpenTK; @@ -13,37 +13,34 @@ using osu.Game.Storyboards; namespace osu.Game.Beatmaps.Formats { - public class LegacyStoryboardDecoder : LegacyDecoder + public class LegacyStoryboardDecoder : LegacyDecoder { - private Storyboard storyboard; - private StoryboardSprite storyboardSprite; private CommandTimelineGroup timelineGroup; + private Storyboard storyboard; + private readonly Dictionary variables = new Dictionary(); public LegacyStoryboardDecoder() + : base(0) { } - public LegacyStoryboardDecoder(int beatmapVersion) + public static void Register() { - BeatmapVersion = beatmapVersion; + // note that this isn't completely correct + AddDecoder(@"osu file format v", m => new LegacyStoryboardDecoder()); + AddDecoder(@"[Events]", m => new LegacyStoryboardDecoder()); } - protected override void ParseStoryboard(StreamReader stream, Storyboard storyboard) + protected override void ParseStreamInto(StreamReader stream, Storyboard storyboard) { - if (stream == null) - throw new ArgumentNullException(nameof(stream)); - if (storyboard == null) - throw new ArgumentNullException(nameof(storyboard)); - this.storyboard = storyboard; - - ParseContent(stream); + base.ParseStreamInto(stream, storyboard); } - protected override void ProcessSection(Section section, string line) + protected override void ParseLine(Storyboard storyboard, Section section, string line) { switch (section) { @@ -80,38 +77,38 @@ namespace osu.Game.Beatmaps.Formats switch (type) { case EventType.Sprite: - { - var layer = parseLayer(split[1]); - var origin = parseOrigin(split[2]); - var path = cleanFilename(split[3]); - var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo); - var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo); - storyboardSprite = new StoryboardSprite(path, origin, new Vector2(x, y)); - storyboard.GetLayer(layer).Add(storyboardSprite); - } + { + var layer = parseLayer(split[1]); + var origin = parseOrigin(split[2]); + var path = cleanFilename(split[3]); + var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo); + var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo); + storyboardSprite = new StoryboardSprite(path, origin, new Vector2(x, y)); + storyboard.GetLayer(layer).Add(storyboardSprite); + } break; case EventType.Animation: - { - var layer = parseLayer(split[1]); - var origin = parseOrigin(split[2]); - var path = cleanFilename(split[3]); - var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo); - var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo); - var frameCount = int.Parse(split[6]); - var frameDelay = double.Parse(split[7], NumberFormatInfo.InvariantInfo); - var loopType = split.Length > 8 ? (AnimationLoopType)Enum.Parse(typeof(AnimationLoopType), split[8]) : AnimationLoopType.LoopForever; - storyboardSprite = new StoryboardAnimation(path, origin, new Vector2(x, y), frameCount, frameDelay, loopType); - storyboard.GetLayer(layer).Add(storyboardSprite); - } + { + var layer = parseLayer(split[1]); + var origin = parseOrigin(split[2]); + var path = cleanFilename(split[3]); + var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo); + var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo); + var frameCount = int.Parse(split[6]); + var frameDelay = double.Parse(split[7], NumberFormatInfo.InvariantInfo); + var loopType = split.Length > 8 ? (AnimationLoopType)Enum.Parse(typeof(AnimationLoopType), split[8]) : AnimationLoopType.LoopForever; + storyboardSprite = new StoryboardAnimation(path, origin, new Vector2(x, y), frameCount, frameDelay, loopType); + storyboard.GetLayer(layer).Add(storyboardSprite); + } break; case EventType.Sample: - { - var time = double.Parse(split[1], CultureInfo.InvariantCulture); - var layer = parseLayer(split[2]); - var path = cleanFilename(split[3]); - var volume = split.Length > 4 ? float.Parse(split[4], CultureInfo.InvariantCulture) : 100; - storyboard.GetLayer(layer).Add(new StoryboardSample(path, time, volume)); - } + { + var time = double.Parse(split[1], CultureInfo.InvariantCulture); + var layer = parseLayer(split[2]); + var path = cleanFilename(split[3]); + var volume = split.Length > 4 ? float.Parse(split[4], CultureInfo.InvariantCulture) : 100; + storyboard.GetLayer(layer).Add(new StoryboardSample(path, time, volume)); + } break; } } @@ -124,120 +121,120 @@ namespace osu.Game.Beatmaps.Formats switch (commandType) { case "T": - { - var triggerName = split[1]; - var startTime = split.Length > 2 ? double.Parse(split[2], CultureInfo.InvariantCulture) : double.MinValue; - var endTime = split.Length > 3 ? double.Parse(split[3], CultureInfo.InvariantCulture) : double.MaxValue; - var groupNumber = split.Length > 4 ? int.Parse(split[4]) : 0; - timelineGroup = storyboardSprite?.AddTrigger(triggerName, startTime, endTime, groupNumber); - } + { + var triggerName = split[1]; + var startTime = split.Length > 2 ? double.Parse(split[2], CultureInfo.InvariantCulture) : double.MinValue; + var endTime = split.Length > 3 ? double.Parse(split[3], CultureInfo.InvariantCulture) : double.MaxValue; + var groupNumber = split.Length > 4 ? int.Parse(split[4]) : 0; + timelineGroup = storyboardSprite?.AddTrigger(triggerName, startTime, endTime, groupNumber); + } break; case "L": - { - var startTime = double.Parse(split[1], CultureInfo.InvariantCulture); - var loopCount = int.Parse(split[2]); - timelineGroup = storyboardSprite?.AddLoop(startTime, loopCount); - } + { + var startTime = double.Parse(split[1], CultureInfo.InvariantCulture); + var loopCount = int.Parse(split[2]); + timelineGroup = storyboardSprite?.AddLoop(startTime, loopCount); + } break; default: + { + if (string.IsNullOrEmpty(split[3])) + split[3] = split[2]; + + var easing = (Easing)int.Parse(split[1]); + var startTime = double.Parse(split[2], CultureInfo.InvariantCulture); + var endTime = double.Parse(split[3], CultureInfo.InvariantCulture); + + switch (commandType) { - if (string.IsNullOrEmpty(split[3])) - split[3] = split[2]; - - var easing = (Easing)int.Parse(split[1]); - var startTime = double.Parse(split[2], CultureInfo.InvariantCulture); - var endTime = double.Parse(split[3], CultureInfo.InvariantCulture); - - switch (commandType) + case "F": { - case "F": - { - var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); - var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; - timelineGroup?.Alpha.Add(easing, startTime, endTime, startValue, endValue); - } - break; - case "S": - { - var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); - var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; - timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startValue), new Vector2(endValue)); - } - break; - case "V": - { - var startX = float.Parse(split[4], CultureInfo.InvariantCulture); - var startY = float.Parse(split[5], CultureInfo.InvariantCulture); - var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX; - var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY; - timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY)); - } - break; - case "R": - { - var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); - var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; - timelineGroup?.Rotation.Add(easing, startTime, endTime, MathHelper.RadiansToDegrees(startValue), MathHelper.RadiansToDegrees(endValue)); - } - break; - case "M": - { - var startX = float.Parse(split[4], CultureInfo.InvariantCulture); - var startY = float.Parse(split[5], CultureInfo.InvariantCulture); - var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX; - var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY; - timelineGroup?.X.Add(easing, startTime, endTime, startX, endX); - timelineGroup?.Y.Add(easing, startTime, endTime, startY, endY); - } - break; - case "MX": - { - var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); - var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; - timelineGroup?.X.Add(easing, startTime, endTime, startValue, endValue); - } - break; - case "MY": - { - var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); - var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; - timelineGroup?.Y.Add(easing, startTime, endTime, startValue, endValue); - } - break; - case "C": - { - var startRed = float.Parse(split[4], CultureInfo.InvariantCulture); - var startGreen = float.Parse(split[5], CultureInfo.InvariantCulture); - var startBlue = float.Parse(split[6], CultureInfo.InvariantCulture); - var endRed = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startRed; - var endGreen = split.Length > 8 ? float.Parse(split[8], CultureInfo.InvariantCulture) : startGreen; - var endBlue = split.Length > 9 ? float.Parse(split[9], CultureInfo.InvariantCulture) : startBlue; - timelineGroup?.Colour.Add(easing, startTime, endTime, - new Color4(startRed / 255f, startGreen / 255f, startBlue / 255f, 1), - new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1)); - } - break; - case "P": - { - var type = split[4]; - switch (type) - { - case "A": - timelineGroup?.BlendingMode.Add(easing, startTime, endTime, BlendingMode.Additive, startTime == endTime ? BlendingMode.Additive : BlendingMode.Inherit); - break; - case "H": - timelineGroup?.FlipH.Add(easing, startTime, endTime, true, startTime == endTime); - break; - case "V": - timelineGroup?.FlipV.Add(easing, startTime, endTime, true, startTime == endTime); - break; - } - } - break; - default: - throw new InvalidDataException($@"Unknown command type: {commandType}"); + var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); + var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; + timelineGroup?.Alpha.Add(easing, startTime, endTime, startValue, endValue); } + break; + case "S": + { + var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); + var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; + timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startValue), new Vector2(endValue)); + } + break; + case "V": + { + var startX = float.Parse(split[4], CultureInfo.InvariantCulture); + var startY = float.Parse(split[5], CultureInfo.InvariantCulture); + var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX; + var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY; + timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY)); + } + break; + case "R": + { + var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); + var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; + timelineGroup?.Rotation.Add(easing, startTime, endTime, MathHelper.RadiansToDegrees(startValue), MathHelper.RadiansToDegrees(endValue)); + } + break; + case "M": + { + var startX = float.Parse(split[4], CultureInfo.InvariantCulture); + var startY = float.Parse(split[5], CultureInfo.InvariantCulture); + var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX; + var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY; + timelineGroup?.X.Add(easing, startTime, endTime, startX, endX); + timelineGroup?.Y.Add(easing, startTime, endTime, startY, endY); + } + break; + case "MX": + { + var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); + var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; + timelineGroup?.X.Add(easing, startTime, endTime, startValue, endValue); + } + break; + case "MY": + { + var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); + var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; + timelineGroup?.Y.Add(easing, startTime, endTime, startValue, endValue); + } + break; + case "C": + { + var startRed = float.Parse(split[4], CultureInfo.InvariantCulture); + var startGreen = float.Parse(split[5], CultureInfo.InvariantCulture); + var startBlue = float.Parse(split[6], CultureInfo.InvariantCulture); + var endRed = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startRed; + var endGreen = split.Length > 8 ? float.Parse(split[8], CultureInfo.InvariantCulture) : startGreen; + var endBlue = split.Length > 9 ? float.Parse(split[9], CultureInfo.InvariantCulture) : startBlue; + timelineGroup?.Colour.Add(easing, startTime, endTime, + new Color4(startRed / 255f, startGreen / 255f, startBlue / 255f, 1), + new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1)); + } + break; + case "P": + { + var type = split[4]; + switch (type) + { + case "A": + timelineGroup?.BlendingMode.Add(easing, startTime, endTime, BlendingMode.Additive, startTime == endTime ? BlendingMode.Additive : BlendingMode.Inherit); + break; + case "H": + timelineGroup?.FlipH.Add(easing, startTime, endTime, true, startTime == endTime); + break; + case "V": + timelineGroup?.FlipV.Add(easing, startTime, endTime, true, startTime == endTime); + break; + } + } + break; + default: + throw new InvalidDataException($@"Unknown command type: {commandType}"); } + } break; } } @@ -269,6 +266,7 @@ namespace osu.Game.Beatmaps.Formats case LegacyOrigins.BottomRight: return Anchor.BottomRight; } + throw new InvalidDataException($@"Unknown origin: {value}"); } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index ce292ef223..5084b28cf2 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -188,8 +188,8 @@ namespace osu.Game.Rulesets.Objects.Legacy string[] split = str.Split(':'); - var bank = (LegacyDecoder.LegacySampleBank)Convert.ToInt32(split[0]); - var addbank = (LegacyDecoder.LegacySampleBank)Convert.ToInt32(split[1]); + var bank = (LegacyBeatmapDecoder.LegacySampleBank)Convert.ToInt32(split[0]); + var addbank = (LegacyBeatmapDecoder.LegacySampleBank)Convert.ToInt32(split[1]); // Let's not implement this for now, because this doesn't fit nicely into the bank structure //string sampleFile = split2.Length > 4 ? split2[4] : string.Empty; diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index 219d805bc1..8505498e4f 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -112,9 +112,9 @@ namespace osu.Game.Tests.Beatmaps using (var resStream = openResource($"{resource_namespace}.{name}.osu")) using (var stream = new StreamReader(resStream)) { - var decoder = Decoder.GetDecoder(stream); + var decoder = Decoder.GetDecoder(stream); ((LegacyBeatmapDecoder)decoder).ApplyOffsets = false; - return decoder.DecodeBeatmap(stream); + return decoder.Decode(stream); } } diff --git a/osu.Game/Tests/Visual/TestCasePlayer.cs b/osu.Game/Tests/Visual/TestCasePlayer.cs index 181ed5e0e6..d835adb54f 100644 --- a/osu.Game/Tests/Visual/TestCasePlayer.cs +++ b/osu.Game/Tests/Visual/TestCasePlayer.cs @@ -65,7 +65,7 @@ namespace osu.Game.Tests.Visual using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(test_beatmap_data))) using (var reader = new StreamReader(stream)) - beatmap = Game.Beatmaps.Formats.Decoder.GetDecoder(reader).DecodeBeatmap(reader); + beatmap = Game.Beatmaps.Formats.Decoder.GetDecoder(reader).Decode(reader); return beatmap; } From 1bcda4930ebc6736a239b4e6e4710525b03d3d4d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Mar 2018 11:33:12 +0900 Subject: [PATCH 2/2] Add back beatmap version set --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index c54d81aa2b..915ea9b587 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -52,6 +52,8 @@ namespace osu.Game.Beatmaps.Formats protected override void ParseStreamInto(StreamReader stream, Beatmap beatmap) { this.beatmap = beatmap; + this.beatmap.BeatmapInfo.BeatmapVersion = FormatVersion; + base.ParseStreamInto(stream, beatmap); // objects may be out of order *only* if a user has manually edited an .osu file.