diff --git a/osu.Desktop/Beatmaps/IO/LegacyFilesystemReader.cs b/osu.Desktop/Beatmaps/IO/LegacyFilesystemReader.cs index 0ef448cafe..abc45d82ec 100644 --- a/osu.Desktop/Beatmaps/IO/LegacyFilesystemReader.cs +++ b/osu.Desktop/Beatmaps/IO/LegacyFilesystemReader.cs @@ -3,10 +3,7 @@ using System.IO; using System.Linq; -using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.IO; -using osu.Game.Beatmaps; -using osu.Game.Database; namespace osu.Desktop.Beatmaps.IO { @@ -18,20 +15,17 @@ namespace osu.Desktop.Beatmaps.IO public static void Register() => AddReader((storage, path) => Directory.Exists(path)); private string basePath { get; } - private Beatmap firstMap { get; } public LegacyFilesystemReader(string path) { basePath = path; + BeatmapFilenames = Directory.GetFiles(basePath, @"*.osu").Select(Path.GetFileName).ToArray(); + if (BeatmapFilenames.Length == 0) throw new FileNotFoundException(@"This directory contains no beatmaps"); + StoryboardFilename = Directory.GetFiles(basePath, @"*.osb").Select(Path.GetFileName).FirstOrDefault(); - using (var stream = new StreamReader(GetStream(BeatmapFilenames[0]))) - { - var decoder = BeatmapDecoder.GetDecoder(stream); - firstMap = decoder.Decode(stream); - } } public override Stream GetStream(string name) @@ -39,11 +33,6 @@ namespace osu.Desktop.Beatmaps.IO return File.OpenRead(Path.Combine(basePath, name)); } - public override BeatmapMetadata ReadMetadata() - { - return firstMap.BeatmapInfo.Metadata; - } - public override void Dispose() { // no-op diff --git a/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 97acdeb3e6..594ed5f309 100644 --- a/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Samples; using osu.Game.Modes.Objects; using osu.Game.Modes.Objects.Types; @@ -16,14 +15,30 @@ namespace osu.Game.Modes.Taiko.Beatmaps { internal class TaikoBeatmapConverter : IBeatmapConverter { - private const float legacy_velocity_scale = 1.4f; - private const float bash_convert_factor = 1.65f; + /// + /// osu! is generally slower than taiko, so a factor is added to increase + /// speed. This must be used everywhere slider length or beat length is used. + /// + private const float legacy_velocity_multiplier = 1.4f; + + /// + /// Because swells are easier in taiko than spinners are in osu!, + /// legacy taiko multiplies a factor when converting the number of required hits. + /// + private const float swell_hit_multiplier = 1.65f; + + /// + /// Base osu! slider scoring distance. + /// + private const float osu_base_scoring_distance = 100; + + /// + /// Drum roll distance that results in a duration of 1 speed-adjusted beat length. + /// + private const float taiko_base_distance = 100; public Beatmap Convert(Beatmap original) { - if (original is LegacyBeatmap) - original.TimingInfo.ControlPoints.ForEach(c => c.VelocityAdjustment /= legacy_velocity_scale); - return new Beatmap(original) { HitObjects = original.HitObjects.SelectMany(h => convertHitObject(h, original)).ToList() @@ -48,27 +63,44 @@ namespace osu.Game.Modes.Taiko.Beatmaps if (distanceData != null) { - double sv = beatmap.TimingInfo.SliderVelocityAt(obj.StartTime) * beatmap.BeatmapInfo.Difficulty.SliderMultiplier; - - double l = distanceData.Distance * legacy_velocity_scale; - double v = sv * legacy_velocity_scale; - double bl = beatmap.TimingInfo.BeatLengthAt(obj.StartTime); - int repeats = repeatsData?.RepeatCount ?? 1; - double skipPeriod = Math.Min(bl / beatmap.BeatmapInfo.Difficulty.SliderTickRate, distanceData.Duration / repeats); + double speedAdjustment = beatmap.TimingInfo.SpeedMultiplierAt(obj.StartTime); + double speedAdjustedBeatLength = beatmap.TimingInfo.BeatLengthAt(obj.StartTime) * speedAdjustment; + + // The true distance, accounting for any repeats. This ends up being the drum roll distance later + double distance = distanceData.Distance * repeats * legacy_velocity_multiplier; - if (skipPeriod > 0 && l / v * 1000 < 2 * bl) + // The velocity of the taiko hit object - calculated as the velocity of a drum roll + double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier / speedAdjustedBeatLength * legacy_velocity_multiplier; + // The duration of the taiko hit object + double taikoDuration = distance / taikoVelocity; + + // For some reason, old osu! always uses speedAdjustment to determine the taiko velocity, but + // only uses it to determine osu! velocity if beatmap version < 8. Let's account for that here. + if (beatmap.BeatmapInfo.BeatmapVersion >= 8) + speedAdjustedBeatLength /= speedAdjustment; + + // The velocity of the osu! hit object - calculated as the velocity of a slider + double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier / speedAdjustedBeatLength * legacy_velocity_multiplier; + // The duration of the osu! hit object + double osuDuration = distance / osuVelocity; + + // If the drum roll is to be split into hit circles, assume the ticks are 1/8 spaced within the duration of one beat + double tickSpacing = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.Difficulty.SliderTickRate, taikoDuration / repeats) / 8; + + if (tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength) { - for (double j = obj.StartTime; j <= distanceData.EndTime + skipPeriod / 8; j += skipPeriod) + for (double j = obj.StartTime; j <= distanceData.EndTime + tickSpacing; j += tickSpacing) { // Todo: This should generate different type of hits (including strongs) // depending on hitobject sound additions (not implemented fully yet) yield return new CentreHit { - StartTime = obj.StartTime, + StartTime = j, Sample = obj.Sample, - IsStrong = strong + IsStrong = strong, + VelocityMultiplier = legacy_velocity_multiplier }; } } @@ -79,22 +111,24 @@ namespace osu.Game.Modes.Taiko.Beatmaps StartTime = obj.StartTime, Sample = obj.Sample, IsStrong = strong, - Distance = distanceData.Distance * (repeatsData?.RepeatCount ?? 1) * legacy_velocity_scale + Distance = distance, + TickRate = beatmap.BeatmapInfo.Difficulty.SliderTickRate == 3 ? 3 : 4, + VelocityMultiplier = legacy_velocity_multiplier }; } } else if (endTimeData != null) { - double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.Difficulty.OverallDifficulty, 3, 5, 7.5) * bash_convert_factor; + double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.Difficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier; yield return new Swell { StartTime = obj.StartTime, Sample = obj.Sample, IsStrong = strong, - EndTime = endTimeData.EndTime, - RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier) + RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier), + VelocityMultiplier = legacy_velocity_multiplier }; } else @@ -107,7 +141,8 @@ namespace osu.Game.Modes.Taiko.Beatmaps { StartTime = obj.StartTime, Sample = obj.Sample, - IsStrong = strong + IsStrong = strong, + VelocityMultiplier = legacy_velocity_multiplier }; } else @@ -117,6 +152,7 @@ namespace osu.Game.Modes.Taiko.Beatmaps StartTime = obj.StartTime, Sample = obj.Sample, IsStrong = strong, + VelocityMultiplier = legacy_velocity_multiplier }; } } diff --git a/osu.Game.Modes.Taiko/Objects/DrumRoll.cs b/osu.Game.Modes.Taiko/Objects/DrumRoll.cs index ccf86654b5..ede576835c 100644 --- a/osu.Game.Modes.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Modes.Taiko/Objects/DrumRoll.cs @@ -33,10 +33,9 @@ namespace osu.Game.Modes.Taiko.Objects public double Velocity { get; protected set; } = 5; /// - /// The distance between ticks of this drumroll. - /// Half of this value is the hit window of the ticks. + /// Numer of ticks per beat length. /// - public double TickTimeDistance { get; protected set; } = 100; + public int TickRate = 1; /// /// Number of drum roll ticks required for a "Good" hit. @@ -60,18 +59,20 @@ namespace osu.Game.Modes.Taiko.Objects private List ticks; + /// + /// The length (in milliseconds) between ticks of this drumroll. + /// Half of this value is the hit window of the ticks. + /// + private double tickSpacing = 100; + public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty) { base.ApplyDefaults(timing, difficulty); - Velocity = base_distance * difficulty.SliderMultiplier * difficulty.SliderTickRate * timing.BeatLengthAt(StartTime) * timing.SpeedMultiplierAt(StartTime); - TickTimeDistance = timing.BeatLengthAt(StartTime); + double speedAdjutedBeatLength = timing.SpeedMultiplierAt(StartTime) * timing.BeatLengthAt(StartTime); - //TODO: move this to legacy conversion code to allow for direct division without special case. - if (difficulty.SliderTickRate == 3) - TickTimeDistance /= 3; - else - TickTimeDistance /= 4; + Velocity = base_distance * difficulty.SliderMultiplier / speedAdjutedBeatLength * VelocityMultiplier; + tickSpacing = timing.BeatLengthAt(StartTime) / TickRate; RequiredGoodHits = TotalTicks * Math.Min(0.15, 0.05 + 0.10 / 6 * difficulty.OverallDifficulty); RequiredGreatHits = TotalTicks * Math.Min(0.30, 0.10 + 0.20 / 6 * difficulty.OverallDifficulty); @@ -81,17 +82,17 @@ namespace osu.Game.Modes.Taiko.Objects { var ret = new List(); - if (TickTimeDistance == 0) + if (tickSpacing == 0) return ret; bool first = true; - for (double t = StartTime; t < EndTime + (int)TickTimeDistance; t += TickTimeDistance) + for (double t = StartTime; t < EndTime + (int)tickSpacing; t += tickSpacing) { ret.Add(new DrumRollTick { FirstTick = first, PreEmpt = PreEmpt, - TickTimeDistance = TickTimeDistance, + TickSpacing = tickSpacing, StartTime = t, IsStrong = IsStrong, Sample = new HitSampleInfo diff --git a/osu.Game.Modes.Taiko/Objects/DrumRollTick.cs b/osu.Game.Modes.Taiko/Objects/DrumRollTick.cs index 2ca0d71fc1..32e8851b66 100644 --- a/osu.Game.Modes.Taiko/Objects/DrumRollTick.cs +++ b/osu.Game.Modes.Taiko/Objects/DrumRollTick.cs @@ -11,14 +11,14 @@ namespace osu.Game.Modes.Taiko.Objects public bool FirstTick; /// - /// The distance between this tick and the next in milliseconds. + /// The length (in milliseconds) between this tick and the next. /// Half of this value is the hit window of the tick. /// - public double TickTimeDistance; + public double TickSpacing; /// /// The time allowed to hit this tick. /// - public double HitWindow => TickTimeDistance / 2; + public double HitWindow => TickSpacing / 2; } } \ No newline at end of file diff --git a/osu.Game.Modes.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Modes.Taiko/Objects/TaikoHitObject.cs index 327c0402ab..5de7e20b67 100644 --- a/osu.Game.Modes.Taiko/Objects/TaikoHitObject.cs +++ b/osu.Game.Modes.Taiko/Objects/TaikoHitObject.cs @@ -19,6 +19,11 @@ namespace osu.Game.Modes.Taiko.Objects /// private const double base_scroll_time = 6000; + /// + /// The velocity multiplier applied to this hit object. + /// + public float VelocityMultiplier = 1; + /// /// The time to scroll in the HitObject. /// @@ -39,7 +44,7 @@ namespace osu.Game.Modes.Taiko.Objects { base.ApplyDefaults(timing, difficulty); - PreEmpt = base_scroll_time / difficulty.SliderMultiplier * timing.BeatLengthAt(StartTime) * timing.SpeedMultiplierAt(StartTime) / 1000; + PreEmpt = base_scroll_time / difficulty.SliderMultiplier * timing.BeatLengthAt(StartTime) * timing.SpeedMultiplierAt(StartTime) / VelocityMultiplier / 1000; ControlPoint overridePoint; Kiai = timing.TimingPointAt(StartTime, out overridePoint).KiaiMode; diff --git a/osu.Game.Modes.Taiko/Replays/TaikoAutoReplay.cs b/osu.Game.Modes.Taiko/Replays/TaikoAutoReplay.cs index c8a93c9068..89d974baf9 100644 --- a/osu.Game.Modes.Taiko/Replays/TaikoAutoReplay.cs +++ b/osu.Game.Modes.Taiko/Replays/TaikoAutoReplay.cs @@ -72,14 +72,9 @@ namespace osu.Game.Modes.Taiko.Replays } else if (drumRoll != null) { - double delay = drumRoll.TickTimeDistance; - - double time = drumRoll.StartTime; - - for (int j = 0; j < drumRoll.TotalTicks; j++) + foreach (var tick in drumRoll.Ticks) { - Frames.Add(new ReplayFrame((int)time, 0, 0, hitButton ? ReplayButtonState.Left1 : ReplayButtonState.Left2)); - time += delay; + Frames.Add(new ReplayFrame(tick.StartTime, 0, 0, hitButton ? ReplayButtonState.Left1 : ReplayButtonState.Left2)); hitButton = !hitButton; } } diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs index 2a69be92ca..b9c4cf780a 100644 --- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs @@ -7,6 +7,8 @@ using osu.Game.Beatmaps.IO; using osu.Game.Modes; using osu.Game.Modes.Osu; using osu.Game.Tests.Resources; +using osu.Game.Beatmaps.Formats; +using osu.Game.Database; namespace osu.Game.Tests.Beatmaps.IO { @@ -53,7 +55,11 @@ namespace osu.Game.Tests.Beatmaps.IO using (var osz = Resource.OpenResource("Beatmaps.241526 Soleily - Renatus.osz")) { var reader = new OszArchiveReader(osz); - var meta = reader.ReadMetadata(); + + BeatmapMetadata meta; + using (var stream = new StreamReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu"))) + meta = BeatmapDecoder.GetDecoder(stream).Decode(stream).Metadata; + Assert.AreEqual(241526, meta.OnlineBeatmapSetID); Assert.AreEqual("Soleily", meta.Artist); Assert.AreEqual("Soleily", meta.ArtistUnicode); diff --git a/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs index 425c6cc5dc..452bd595c7 100644 --- a/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs @@ -13,13 +13,13 @@ namespace osu.Game.Beatmaps.Formats { private static Dictionary decoders { get; } = new Dictionary(); - public static BeatmapDecoder GetDecoder(TextReader stream) + public static BeatmapDecoder GetDecoder(StreamReader stream) { - var line = stream.ReadLine()?.Trim(); + string line = stream.ReadLine()?.Trim(); if (line == null || !decoders.ContainsKey(line)) throw new IOException(@"Unknown file format"); - return (BeatmapDecoder)Activator.CreateInstance(decoders[line]); + return (BeatmapDecoder)Activator.CreateInstance(decoders[line], line); } protected static void AddDecoder(string magic) where T : BeatmapDecoder @@ -27,17 +27,17 @@ namespace osu.Game.Beatmaps.Formats decoders[magic] = typeof(T); } - public virtual Beatmap Decode(TextReader stream) + public virtual Beatmap Decode(StreamReader stream) { return ParseFile(stream); } - public virtual void Decode(TextReader stream, Beatmap beatmap) + public virtual void Decode(StreamReader stream, Beatmap beatmap) { ParseFile(stream, beatmap); } - protected virtual Beatmap ParseFile(TextReader stream) + protected virtual Beatmap ParseFile(StreamReader stream) { var beatmap = new Beatmap { @@ -48,9 +48,11 @@ namespace osu.Game.Beatmaps.Formats Difficulty = new BeatmapDifficulty(), }, }; + ParseFile(stream, beatmap); return beatmap; } - protected abstract void ParseFile(TextReader stream, Beatmap beatmap); + + protected abstract void ParseFile(StreamReader stream, Beatmap beatmap); } } diff --git a/osu.Game/Beatmaps/Formats/ConstructableBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/ConstructableBeatmapDecoder.cs deleted file mode 100644 index 3e7dbb4d1b..0000000000 --- a/osu.Game/Beatmaps/Formats/ConstructableBeatmapDecoder.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.IO; - -namespace osu.Game.Beatmaps.Formats -{ - public class ConstructableBeatmapDecoder : BeatmapDecoder - { - protected override void ParseFile(TextReader stream, Beatmap beatmap) - { - throw new NotImplementedException(); - } - } -} diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs index 20b977499e..748583606b 100644 --- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs @@ -31,6 +31,17 @@ namespace osu.Game.Beatmaps.Formats // TODO: Not sure how far back to go, or differences between versions } + private readonly int beatmapVersion; + + public OsuLegacyDecoder() + { + } + + public OsuLegacyDecoder(string header) + { + beatmapVersion = int.Parse(header.Substring(17)); + } + private enum Section { None, @@ -246,32 +257,36 @@ namespace osu.Game.Beatmaps.Formats } } - protected override Beatmap ParseFile(TextReader stream) + protected override Beatmap ParseFile(StreamReader stream) { return new LegacyBeatmap(base.ParseFile(stream)); } - public override Beatmap Decode(TextReader stream) + public override Beatmap Decode(StreamReader stream) { return new LegacyBeatmap(base.Decode(stream)); } - protected override void ParseFile(TextReader stream, Beatmap beatmap) + protected override void ParseFile(StreamReader stream, Beatmap beatmap) { + beatmap.BeatmapInfo.BeatmapVersion = beatmapVersion; + HitObjectParser parser = null; + Section section = Section.None; bool hasCustomColours = false; - var section = Section.None; - while (true) + string line; + while ((line = stream.ReadLine()) != null) { - var line = stream.ReadLine(); - if (line == null) - break; if (string.IsNullOrEmpty(line)) continue; + if (line.StartsWith(@"osu file format v")) + { + beatmap.BeatmapInfo.BeatmapVersion = int.Parse(line.Substring(17)); continue; + } if (line.StartsWith(@"[") && line.EndsWith(@"]")) { diff --git a/osu.Game/Beatmaps/IO/ArchiveReader.cs b/osu.Game/Beatmaps/IO/ArchiveReader.cs index bbf4de20f5..6c6b6be23c 100644 --- a/osu.Game/Beatmaps/IO/ArchiveReader.cs +++ b/osu.Game/Beatmaps/IO/ArchiveReader.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.IO; using osu.Framework.IO.Stores; using osu.Framework.Platform; -using osu.Game.Database; namespace osu.Game.Beatmaps.IO { @@ -35,11 +34,6 @@ namespace osu.Game.Beatmaps.IO readers.Add(new Reader { Test = test, Type = typeof(T) }); } - /// - /// Reads the beatmap metadata from this archive. - /// - public abstract BeatmapMetadata ReadMetadata(); - /// /// Gets a list of beatmap file names. /// diff --git a/osu.Game/Beatmaps/IO/OszArchiveReader.cs b/osu.Game/Beatmaps/IO/OszArchiveReader.cs index 5c0f29fb86..6c550def8d 100644 --- a/osu.Game/Beatmaps/IO/OszArchiveReader.cs +++ b/osu.Game/Beatmaps/IO/OszArchiveReader.cs @@ -5,7 +5,6 @@ using System.IO; using System.Linq; using Ionic.Zip; using osu.Game.Beatmaps.Formats; -using osu.Game.Database; namespace osu.Game.Beatmaps.IO { @@ -23,23 +22,18 @@ namespace osu.Game.Beatmaps.IO private readonly Stream archiveStream; private readonly ZipFile archive; - private readonly Beatmap firstMap; public OszArchiveReader(Stream archiveStream) { this.archiveStream = archiveStream; archive = ZipFile.Read(archiveStream); - BeatmapFilenames = archive.Entries.Where(e => e.FileName.EndsWith(@".osu")) - .Select(e => e.FileName).ToArray(); + + BeatmapFilenames = archive.Entries.Where(e => e.FileName.EndsWith(@".osu")).Select(e => e.FileName).ToArray(); + if (BeatmapFilenames.Length == 0) throw new FileNotFoundException(@"This directory contains no beatmaps"); - StoryboardFilename = archive.Entries.Where(e => e.FileName.EndsWith(@".osb")) - .Select(e => e.FileName).FirstOrDefault(); - using (var stream = new StreamReader(GetStream(BeatmapFilenames[0]))) - { - var decoder = BeatmapDecoder.GetDecoder(stream); - firstMap = decoder.Decode(stream); - } + + StoryboardFilename = archive.Entries.Where(e => e.FileName.EndsWith(@".osb")).Select(e => e.FileName).FirstOrDefault(); } public override Stream GetStream(string name) @@ -50,11 +44,6 @@ namespace osu.Game.Beatmaps.IO return entry.OpenReader(); } - public override BeatmapMetadata ReadMetadata() - { - return firstMap.BeatmapInfo.Metadata; - } - public override void Dispose() { archive.Dispose(); diff --git a/osu.Game/Database/BeatmapDatabase.cs b/osu.Game/Database/BeatmapDatabase.cs index dfc916a136..41ddd8df39 100644 --- a/osu.Game/Database/BeatmapDatabase.cs +++ b/osu.Game/Database/BeatmapDatabase.cs @@ -175,7 +175,10 @@ namespace osu.Game.Database BeatmapMetadata metadata; using (var reader = ArchiveReader.GetReader(storage, path)) - metadata = reader.ReadMetadata(); + { + using (var stream = new StreamReader(reader.GetStream(reader.BeatmapFilenames[0]))) + metadata = BeatmapDecoder.GetDecoder(stream).Decode(stream).Metadata; + } if (File.Exists(path)) // Not always the case, i.e. for LegacyFilesystemReader { diff --git a/osu.Game/Database/BeatmapInfo.cs b/osu.Game/Database/BeatmapInfo.cs index cda9cba70c..890623091d 100644 --- a/osu.Game/Database/BeatmapInfo.cs +++ b/osu.Game/Database/BeatmapInfo.cs @@ -15,6 +15,8 @@ namespace osu.Game.Database [PrimaryKey, AutoIncrement] public int ID { get; set; } + public int BeatmapVersion; + public int? OnlineBeatmapID { get; set; } public int? OnlineBeatmapSetID { get; set; } diff --git a/osu.Game/Database/DatabaseWorkingBeatmap.cs b/osu.Game/Database/DatabaseWorkingBeatmap.cs index 1b37cf2fa0..9fb3bed1e7 100644 --- a/osu.Game/Database/DatabaseWorkingBeatmap.cs +++ b/osu.Game/Database/DatabaseWorkingBeatmap.cs @@ -34,12 +34,14 @@ namespace osu.Game.Database using (var stream = new StreamReader(reader.GetStream(BeatmapInfo.Path))) { decoder = BeatmapDecoder.GetDecoder(stream); - beatmap = decoder?.Decode(stream); + beatmap = decoder.Decode(stream); } - if (WithStoryboard && beatmap != null && BeatmapSetInfo.StoryboardFile != null) - using (var stream = new StreamReader(reader.GetStream(BeatmapSetInfo.StoryboardFile))) - decoder.Decode(stream, beatmap); + if (beatmap == null || !WithStoryboard || BeatmapSetInfo.StoryboardFile == null) + return beatmap; + + using (var stream = new StreamReader(reader.GetStream(BeatmapSetInfo.StoryboardFile))) + decoder.Decode(stream, beatmap); } return beatmap; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ecb3f5084c..a9e8dfb5bb 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -142,7 +142,6 @@ -