diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs index c6aac3bb71..4c540fa8cf 100644 --- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; using System.Globalization; using System.IO; using OpenTK.Graphics; @@ -31,6 +32,8 @@ namespace osu.Game.Beatmaps.Formats private ConvertHitObjectParser parser; + private readonly Dictionary variables = new Dictionary(); + private LegacySampleBank defaultSampleBank; private int defaultSampleVolume = 100; @@ -56,36 +59,39 @@ namespace osu.Game.Beatmaps.Formats TimingPoints, Colours, HitObjects, + Variables, } - private void handleGeneral(Beatmap beatmap, string key, string val) + private void handleGeneral(Beatmap beatmap, string line) { + var pair = splitKeyVal(line, ':'); + var metadata = beatmap.BeatmapInfo.Metadata; - switch (key) + switch (pair.Key) { case @"AudioFilename": - metadata.AudioFile = val; + metadata.AudioFile = pair.Value; break; case @"AudioLeadIn": - beatmap.BeatmapInfo.AudioLeadIn = int.Parse(val); + beatmap.BeatmapInfo.AudioLeadIn = int.Parse(pair.Value); break; case @"PreviewTime": - metadata.PreviewTime = int.Parse(val); + metadata.PreviewTime = int.Parse(pair.Value); break; case @"Countdown": - beatmap.BeatmapInfo.Countdown = int.Parse(val) == 1; + beatmap.BeatmapInfo.Countdown = int.Parse(pair.Value) == 1; break; case @"SampleSet": - defaultSampleBank = (LegacySampleBank)Enum.Parse(typeof(LegacySampleBank), val); + defaultSampleBank = (LegacySampleBank)Enum.Parse(typeof(LegacySampleBank), pair.Value); break; case @"SampleVolume": - defaultSampleVolume = int.Parse(val); + defaultSampleVolume = int.Parse(pair.Value); break; case @"StackLeniency": - beatmap.BeatmapInfo.StackLeniency = float.Parse(val, NumberFormatInfo.InvariantInfo); + beatmap.BeatmapInfo.StackLeniency = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); break; case @"Mode": - beatmap.BeatmapInfo.RulesetID = int.Parse(val); + beatmap.BeatmapInfo.RulesetID = int.Parse(pair.Value); switch (beatmap.BeatmapInfo.RulesetID) { @@ -104,107 +110,135 @@ namespace osu.Game.Beatmaps.Formats } break; case @"LetterboxInBreaks": - beatmap.BeatmapInfo.LetterboxInBreaks = int.Parse(val) == 1; + beatmap.BeatmapInfo.LetterboxInBreaks = int.Parse(pair.Value) == 1; break; case @"SpecialStyle": - beatmap.BeatmapInfo.SpecialStyle = int.Parse(val) == 1; + beatmap.BeatmapInfo.SpecialStyle = int.Parse(pair.Value) == 1; break; case @"WidescreenStoryboard": - beatmap.BeatmapInfo.WidescreenStoryboard = int.Parse(val) == 1; + beatmap.BeatmapInfo.WidescreenStoryboard = int.Parse(pair.Value) == 1; break; } } - private void handleEditor(Beatmap beatmap, string key, string val) + private void handleEditor(Beatmap beatmap, string line) { - switch (key) + var pair = splitKeyVal(line, ':'); + + switch (pair.Key) { case @"Bookmarks": - beatmap.BeatmapInfo.StoredBookmarks = val; + beatmap.BeatmapInfo.StoredBookmarks = pair.Value; break; case @"DistanceSpacing": - beatmap.BeatmapInfo.DistanceSpacing = double.Parse(val, NumberFormatInfo.InvariantInfo); + beatmap.BeatmapInfo.DistanceSpacing = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo); break; case @"BeatDivisor": - beatmap.BeatmapInfo.BeatDivisor = int.Parse(val); + beatmap.BeatmapInfo.BeatDivisor = int.Parse(pair.Value); break; case @"GridSize": - beatmap.BeatmapInfo.GridSize = int.Parse(val); + beatmap.BeatmapInfo.GridSize = int.Parse(pair.Value); break; case @"TimelineZoom": - beatmap.BeatmapInfo.TimelineZoom = double.Parse(val, NumberFormatInfo.InvariantInfo); + beatmap.BeatmapInfo.TimelineZoom = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo); break; } } - private void handleMetadata(Beatmap beatmap, string key, string val) + private void handleMetadata(Beatmap beatmap, string line) { + var pair = splitKeyVal(line, ':'); + var metadata = beatmap.BeatmapInfo.Metadata; - switch (key) + switch (pair.Key) { case @"Title": - metadata.Title = val; + metadata.Title = pair.Value; break; case @"TitleUnicode": - metadata.TitleUnicode = val; + metadata.TitleUnicode = pair.Value; break; case @"Artist": - metadata.Artist = val; + metadata.Artist = pair.Value; break; case @"ArtistUnicode": - metadata.ArtistUnicode = val; + metadata.ArtistUnicode = pair.Value; break; case @"Creator": - metadata.Author = val; + metadata.Author = pair.Value; break; case @"Version": - beatmap.BeatmapInfo.Version = val; + beatmap.BeatmapInfo.Version = pair.Value; break; case @"Source": - beatmap.BeatmapInfo.Metadata.Source = val; + beatmap.BeatmapInfo.Metadata.Source = pair.Value; break; case @"Tags": - beatmap.BeatmapInfo.Metadata.Tags = val; + beatmap.BeatmapInfo.Metadata.Tags = pair.Value; break; case @"BeatmapID": - beatmap.BeatmapInfo.OnlineBeatmapID = int.Parse(val); + beatmap.BeatmapInfo.OnlineBeatmapID = int.Parse(pair.Value); break; case @"BeatmapSetID": - beatmap.BeatmapInfo.OnlineBeatmapSetID = int.Parse(val); - metadata.OnlineBeatmapSetID = int.Parse(val); + beatmap.BeatmapInfo.OnlineBeatmapSetID = int.Parse(pair.Value); + metadata.OnlineBeatmapSetID = int.Parse(pair.Value); break; } } - private void handleDifficulty(Beatmap beatmap, string key, string val) + private void handleDifficulty(Beatmap beatmap, string line) { + var pair = splitKeyVal(line, ':'); + var difficulty = beatmap.BeatmapInfo.Difficulty; - switch (key) + switch (pair.Key) { case @"HPDrainRate": - difficulty.DrainRate = float.Parse(val, NumberFormatInfo.InvariantInfo); + difficulty.DrainRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); break; case @"CircleSize": - difficulty.CircleSize = float.Parse(val, NumberFormatInfo.InvariantInfo); + difficulty.CircleSize = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); break; case @"OverallDifficulty": - difficulty.OverallDifficulty = float.Parse(val, NumberFormatInfo.InvariantInfo); + difficulty.OverallDifficulty = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); break; case @"ApproachRate": - difficulty.ApproachRate = float.Parse(val, NumberFormatInfo.InvariantInfo); + difficulty.ApproachRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); break; case @"SliderMultiplier": - difficulty.SliderMultiplier = float.Parse(val, NumberFormatInfo.InvariantInfo); + difficulty.SliderMultiplier = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); break; case @"SliderTickRate": - difficulty.SliderTickRate = float.Parse(val, NumberFormatInfo.InvariantInfo); + difficulty.SliderTickRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); break; } } - private void handleEvents(Beatmap beatmap, string val) + /// + /// Decodes any beatmap variables present in a line into their real values. + /// + /// The line which may contains variables. + private void decodeVariables(ref string line) { - string[] split = val.Split(','); + while (line.IndexOf('$') >= 0) + { + string[] split = line.Split(','); + for (int i = 0; i < split.Length; i++) + { + var item = split[i]; + if (item.StartsWith("$") && variables.ContainsKey(item)) + split[i] = variables[item]; + } + + line = string.Join(",", split); + } + } + + private void handleEvents(Beatmap beatmap, string line) + { + decodeVariables(ref line); + + string[] split = line.Split(','); EventType type; if (!Enum.TryParse(split[0], out type)) @@ -236,9 +270,9 @@ namespace osu.Game.Beatmaps.Formats } } - private void handleTimingPoints(Beatmap beatmap, string val) + private void handleTimingPoints(Beatmap beatmap, string line) { - string[] split = val.Split(','); + string[] split = line.Split(','); double time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo); double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo); @@ -321,12 +355,14 @@ namespace osu.Game.Beatmaps.Formats } } - private void handleColours(Beatmap beatmap, string key, string val, ref bool hasCustomColours) + private void handleColours(Beatmap beatmap, string line, ref bool hasCustomColours) { - string[] split = val.Split(','); + var pair = splitKeyVal(line, ':'); + + string[] split = pair.Value.Split(','); if (split.Length != 3) - throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B): {val}"); + throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B): {pair.Value}"); byte r, g, b; if (!byte.TryParse(split[0], out r) || !byte.TryParse(split[1], out g) || !byte.TryParse(split[2], out b)) @@ -339,7 +375,7 @@ namespace osu.Game.Beatmaps.Formats } // Note: the combo index specified in the beatmap is discarded - if (key.StartsWith(@"Combo")) + if (pair.Key.StartsWith(@"Combo")) { beatmap.ComboColors.Add(new Color4 { @@ -351,6 +387,12 @@ namespace osu.Game.Beatmaps.Formats } } + private void handleVariables(string line) + { + var pair = splitKeyVal(line, '='); + variables[pair.Key] = pair.Value; + } + protected override Beatmap ParseFile(StreamReader stream) { return new LegacyBeatmap(base.ParseFile(stream)); @@ -390,42 +432,39 @@ namespace osu.Game.Beatmaps.Formats continue; } - string val = line, key = null; - if (section != Section.Events && section != Section.TimingPoints && section != Section.HitObjects) - { - key = val.Remove(val.IndexOf(':')).Trim(); - val = val.Substring(val.IndexOf(':') + 1).Trim(); - } switch (section) { case Section.General: - handleGeneral(beatmap, key, val); + handleGeneral(beatmap, line); break; case Section.Editor: - handleEditor(beatmap, key, val); + handleEditor(beatmap, line); break; case Section.Metadata: - handleMetadata(beatmap, key, val); + handleMetadata(beatmap, line); break; case Section.Difficulty: - handleDifficulty(beatmap, key, val); + handleDifficulty(beatmap, line); break; case Section.Events: - handleEvents(beatmap, val); + handleEvents(beatmap, line); break; case Section.TimingPoints: - handleTimingPoints(beatmap, val); + handleTimingPoints(beatmap, line); break; case Section.Colours: - handleColours(beatmap, key, val, ref hasCustomColours); + handleColours(beatmap, line, ref hasCustomColours); break; case Section.HitObjects: - var obj = parser.Parse(val); + var obj = parser.Parse(line); if (obj != null) beatmap.HitObjects.Add(obj); break; + case Section.Variables: + handleVariables(line); + break; } } @@ -433,6 +472,15 @@ namespace osu.Game.Beatmaps.Formats hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.Difficulty); } + private KeyValuePair splitKeyVal(string line, char separator) + { + return new KeyValuePair + ( + line.Remove(line.IndexOf(separator)).Trim(), + line.Substring(line.IndexOf(separator) + 1).Trim() + ); + } + internal enum LegacySampleBank { None = 0,