From 293ea6fbd7490c19cf671de639a7468f1aa82387 Mon Sep 17 00:00:00 2001 From: smoogipooo Date: Mon, 3 Apr 2017 15:24:30 +0900 Subject: [PATCH 01/11] Fix up beatmap converter error. --- osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 97acdeb3e6..805666d5cf 100644 --- a/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -19,6 +19,11 @@ namespace osu.Game.Modes.Taiko.Beatmaps private const float legacy_velocity_scale = 1.4f; private const float bash_convert_factor = 1.65f; + /// + /// Drum roll distance that results in a duration of 1 speed-adjusted beat length. + /// + private const float base_distance = 100; + public Beatmap Convert(Beatmap original) { if (original is LegacyBeatmap) @@ -48,7 +53,7 @@ namespace osu.Game.Modes.Taiko.Beatmaps if (distanceData != null) { - double sv = beatmap.TimingInfo.SliderVelocityAt(obj.StartTime) * beatmap.BeatmapInfo.Difficulty.SliderMultiplier; + double sv = base_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier * beatmap.TimingInfo.BeatLengthAt(obj.StartTime) / 1000; double l = distanceData.Distance * legacy_velocity_scale; double v = sv * legacy_velocity_scale; From aad88514605095ca1c0f55afa7a4f20d7bd01595 Mon Sep 17 00:00:00 2001 From: smoogipooo Date: Mon, 3 Apr 2017 15:32:38 +0900 Subject: [PATCH 02/11] Define TickRate to adjust rate of ticks externally, removing todo. --- .../Beatmaps/TaikoBeatmapConverter.cs | 3 ++- osu.Game.Modes.Taiko/Objects/DrumRoll.cs | 25 +++++++++---------- osu.Game.Modes.Taiko/Objects/DrumRollTick.cs | 6 ++--- .../Replays/TaikoAutoReplay.cs | 9 ++----- 4 files changed, 19 insertions(+), 24 deletions(-) diff --git a/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 805666d5cf..0c356ebdc9 100644 --- a/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -84,7 +84,8 @@ namespace osu.Game.Modes.Taiko.Beatmaps StartTime = obj.StartTime, Sample = obj.Sample, IsStrong = strong, - Distance = distanceData.Distance * (repeatsData?.RepeatCount ?? 1) * legacy_velocity_scale + Distance = distanceData.Distance * (repeatsData?.RepeatCount ?? 1) * legacy_velocity_scale, + TickRate = beatmap.BeatmapInfo.Difficulty.SliderTickRate == 3 ? 3 : 4 }; } } diff --git a/osu.Game.Modes.Taiko/Objects/DrumRoll.cs b/osu.Game.Modes.Taiko/Objects/DrumRoll.cs index ccf86654b5..ed8d332010 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,18 @@ 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); - - //TODO: move this to legacy conversion code to allow for direct division without special case. - if (difficulty.SliderTickRate == 3) - TickTimeDistance /= 3; - else - TickTimeDistance /= 4; + 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 +80,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/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; } } From d7ed392f2749ac8bf9be980a64b171bed359808b Mon Sep 17 00:00:00 2001 From: smoogipooo Date: Mon, 3 Apr 2017 17:19:46 +0900 Subject: [PATCH 03/11] Add a velocity multiplier to taiko hit objects. This will be usable from the editor moving forward also - where every hit object can have its own velocity multiplier on top of the control point one. --- .../Beatmaps/TaikoBeatmapConverter.cs | 16 +++++++++------- osu.Game.Modes.Taiko/Objects/DrumRoll.cs | 2 +- osu.Game.Modes.Taiko/Objects/TaikoHitObject.cs | 7 ++++++- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 0c356ebdc9..7749b5177a 100644 --- a/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -26,8 +26,6 @@ namespace osu.Game.Modes.Taiko.Beatmaps public Beatmap Convert(Beatmap original) { - if (original is LegacyBeatmap) - original.TimingInfo.ControlPoints.ForEach(c => c.VelocityAdjustment /= legacy_velocity_scale); return new Beatmap(original) { @@ -73,7 +71,8 @@ namespace osu.Game.Modes.Taiko.Beatmaps { StartTime = obj.StartTime, Sample = obj.Sample, - IsStrong = strong + IsStrong = strong, + VelocityMultiplier = legacy_velocity_scale }; } } @@ -85,7 +84,8 @@ namespace osu.Game.Modes.Taiko.Beatmaps Sample = obj.Sample, IsStrong = strong, Distance = distanceData.Distance * (repeatsData?.RepeatCount ?? 1) * legacy_velocity_scale, - TickRate = beatmap.BeatmapInfo.Difficulty.SliderTickRate == 3 ? 3 : 4 + TickRate = beatmap.BeatmapInfo.Difficulty.SliderTickRate == 3 ? 3 : 4, + VelocityMultiplier = legacy_velocity_scale }; } } @@ -98,9 +98,9 @@ namespace osu.Game.Modes.Taiko.Beatmaps 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_scale }; } else @@ -113,7 +113,8 @@ namespace osu.Game.Modes.Taiko.Beatmaps { StartTime = obj.StartTime, Sample = obj.Sample, - IsStrong = strong + IsStrong = strong, + VelocityMultiplier = legacy_velocity_scale }; } else @@ -123,6 +124,7 @@ namespace osu.Game.Modes.Taiko.Beatmaps StartTime = obj.StartTime, Sample = obj.Sample, IsStrong = strong, + VelocityMultiplier = legacy_velocity_scale }; } } diff --git a/osu.Game.Modes.Taiko/Objects/DrumRoll.cs b/osu.Game.Modes.Taiko/Objects/DrumRoll.cs index ed8d332010..5c57ee77de 100644 --- a/osu.Game.Modes.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Modes.Taiko/Objects/DrumRoll.cs @@ -69,7 +69,7 @@ namespace osu.Game.Modes.Taiko.Objects { base.ApplyDefaults(timing, difficulty); - Velocity = base_distance * difficulty.SliderMultiplier * difficulty.SliderTickRate * timing.BeatLengthAt(StartTime) * timing.SpeedMultiplierAt(StartTime); + Velocity = base_distance * difficulty.SliderMultiplier * VelocityMultiplier / timing.BeatLengthAt(StartTime); tickSpacing = timing.BeatLengthAt(StartTime) / TickRate; RequiredGoodHits = TotalTicks * Math.Min(0.15, 0.05 + 0.10 / 6 * difficulty.OverallDifficulty); 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; From 0c572990669d0fe5120c40f758b8568bcf6d0b9e Mon Sep 17 00:00:00 2001 From: smoogipooo Date: Mon, 3 Apr 2017 17:20:46 +0900 Subject: [PATCH 04/11] Rewrite drum roll -> hit conversion to match osu-stable. --- .../Beatmaps/TaikoBeatmapConverter.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 7749b5177a..377ee37e2f 100644 --- a/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -18,6 +18,7 @@ namespace osu.Game.Modes.Taiko.Beatmaps { private const float legacy_velocity_scale = 1.4f; private const float bash_convert_factor = 1.65f; + private const float base_scoring_distance = 100; /// /// Drum roll distance that results in a duration of 1 speed-adjusted beat length. @@ -26,7 +27,6 @@ namespace osu.Game.Modes.Taiko.Beatmaps public Beatmap Convert(Beatmap original) { - return new Beatmap(original) { HitObjects = original.HitObjects.SelectMany(h => convertHitObject(h, original)).ToList() @@ -51,17 +51,20 @@ namespace osu.Game.Modes.Taiko.Beatmaps if (distanceData != null) { - double sv = base_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier * beatmap.TimingInfo.BeatLengthAt(obj.StartTime) / 1000; - - 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 speedAdjustedBeatLength = beatmap.TimingInfo.BeatLengthAt(obj.StartTime) * beatmap.TimingInfo.SpeedMultiplierAt(obj.StartTime); + double distance = distanceData.Distance * repeats * legacy_velocity_scale; - if (skipPeriod > 0 && l / v * 1000 < 2 * bl) + double taikoVelocity = base_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier / speedAdjustedBeatLength * legacy_velocity_scale; + double taikoDuration = distance / taikoVelocity; + + double osuVelocity = base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier / speedAdjustedBeatLength * legacy_velocity_scale; + double osuDuration = distance / osuVelocity; + + double skipPeriod = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.Difficulty.SliderTickRate, taikoDuration / repeats); + + if (skipPeriod > 0 && osuDuration < 2 * speedAdjustedBeatLength) { for (double j = obj.StartTime; j <= distanceData.EndTime + skipPeriod / 8; j += skipPeriod) { From a32eb665381c6700af3c359992151a640740f4bd Mon Sep 17 00:00:00 2001 From: smoogipooo Date: Mon, 3 Apr 2017 17:31:08 +0900 Subject: [PATCH 05/11] Fix missing speed multiplier + split onto multiple lines. --- osu.Game.Modes.Taiko/Objects/DrumRoll.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Modes.Taiko/Objects/DrumRoll.cs b/osu.Game.Modes.Taiko/Objects/DrumRoll.cs index 5c57ee77de..ede576835c 100644 --- a/osu.Game.Modes.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Modes.Taiko/Objects/DrumRoll.cs @@ -69,7 +69,9 @@ namespace osu.Game.Modes.Taiko.Objects { base.ApplyDefaults(timing, difficulty); - Velocity = base_distance * difficulty.SliderMultiplier * VelocityMultiplier / timing.BeatLengthAt(StartTime); + double speedAdjutedBeatLength = timing.SpeedMultiplierAt(StartTime) * timing.BeatLengthAt(StartTime); + + 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); From 91eec9e8fcf2f9f0dbeada7bba7be42a29da431d Mon Sep 17 00:00:00 2001 From: smoogipooo Date: Mon, 3 Apr 2017 17:54:48 +0900 Subject: [PATCH 06/11] Fix incorrect split hit circle start time. --- osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 377ee37e2f..c5843e84d9 100644 --- a/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -72,7 +72,7 @@ namespace osu.Game.Modes.Taiko.Beatmaps // depending on hitobject sound additions (not implemented fully yet) yield return new CentreHit { - StartTime = obj.StartTime, + StartTime = j, Sample = obj.Sample, IsStrong = strong, VelocityMultiplier = legacy_velocity_scale From 19b5555ef2de5b339f0eaed25ac2821f44dd8fdf Mon Sep 17 00:00:00 2001 From: smoogipooo Date: Mon, 3 Apr 2017 20:26:46 +0900 Subject: [PATCH 07/11] Slightly clean up archive readers + decoders. Read beatmap version into BeatmapInfo. --- .../Beatmaps/IO/LegacyFilesystemReader.cs | 17 +++------- .../Beatmaps/IO/OszArchiveReaderTest.cs | 9 +++++- osu.Game/Beatmaps/Formats/BeatmapDecoder.cs | 16 +++++----- .../Formats/ConstructableBeatmapDecoder.cs | 16 ---------- osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs | 31 ++++++++++++++----- osu.Game/Beatmaps/IO/ArchiveReader.cs | 6 ---- osu.Game/Beatmaps/IO/OszArchiveReader.cs | 21 +++---------- osu.Game/Database/BeatmapDatabase.cs | 5 ++- osu.Game/Database/BeatmapInfo.cs | 2 ++ osu.Game/Database/DatabaseWorkingBeatmap.cs | 10 +++--- osu.Game/osu.Game.csproj | 1 - 11 files changed, 61 insertions(+), 73 deletions(-) delete mode 100644 osu.Game/Beatmaps/Formats/ConstructableBeatmapDecoder.cs diff --git a/osu.Desktop/Beatmaps/IO/LegacyFilesystemReader.cs b/osu.Desktop/Beatmaps/IO/LegacyFilesystemReader.cs index 0ef448cafe..ffa37c29b7 100644 --- a/osu.Desktop/Beatmaps/IO/LegacyFilesystemReader.cs +++ b/osu.Desktop/Beatmaps/IO/LegacyFilesystemReader.cs @@ -3,10 +3,8 @@ 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 +16,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,14 +34,10 @@ 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.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs index 2a69be92ca..8c44e6c954 100644 --- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs @@ -7,6 +7,9 @@ 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.Beatmaps; +using osu.Game.Database; namespace osu.Game.Tests.Beatmaps.IO { @@ -53,7 +56,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..6d03205ca2 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; bool hasCustomColours = false; - var section = Section.None; - while (true) + Section section = Section.None; + string line = null; + 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 @@ - From 2be745853203470a5642c00f3579b1b3a11b071a Mon Sep 17 00:00:00 2001 From: smoogipooo Date: Mon, 3 Apr 2017 20:27:11 +0900 Subject: [PATCH 08/11] Commenting (+ version check for speed adjustment). --- .../Beatmaps/TaikoBeatmapConverter.cs | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs index c5843e84d9..21b75070df 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; @@ -53,20 +52,33 @@ namespace osu.Game.Modes.Taiko.Beatmaps { int repeats = repeatsData?.RepeatCount ?? 1; - double speedAdjustedBeatLength = beatmap.TimingInfo.BeatLengthAt(obj.StartTime) * beatmap.TimingInfo.SpeedMultiplierAt(obj.StartTime); + 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_scale; + // The velocity of the taiko hit object - calculated as the velocity of a drum roll double taikoVelocity = base_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier / speedAdjustedBeatLength * legacy_velocity_scale; + // 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 = base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier / speedAdjustedBeatLength * legacy_velocity_scale; + // The duration of the osu! hit object double osuDuration = distance / osuVelocity; - double skipPeriod = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.Difficulty.SliderTickRate, taikoDuration / repeats); + // 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 (skipPeriod > 0 && osuDuration < 2 * speedAdjustedBeatLength) + 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) @@ -86,7 +98,7 @@ 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_scale }; From 15db37d9e0e8be5df134f631320813138ea80a8e Mon Sep 17 00:00:00 2001 From: smoogipooo Date: Mon, 3 Apr 2017 20:27:25 +0900 Subject: [PATCH 09/11] Cleanup. --- osu.Desktop/Beatmaps/IO/LegacyFilesystemReader.cs | 2 -- osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs | 1 - 2 files changed, 3 deletions(-) diff --git a/osu.Desktop/Beatmaps/IO/LegacyFilesystemReader.cs b/osu.Desktop/Beatmaps/IO/LegacyFilesystemReader.cs index ffa37c29b7..abc45d82ec 100644 --- a/osu.Desktop/Beatmaps/IO/LegacyFilesystemReader.cs +++ b/osu.Desktop/Beatmaps/IO/LegacyFilesystemReader.cs @@ -4,7 +4,6 @@ using System.IO; using System.Linq; using osu.Game.Beatmaps.IO; -using osu.Game.Beatmaps; namespace osu.Desktop.Beatmaps.IO { @@ -38,6 +37,5 @@ namespace osu.Desktop.Beatmaps.IO { // no-op } - } } diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs index 8c44e6c954..b9c4cf780a 100644 --- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs @@ -8,7 +8,6 @@ using osu.Game.Modes; using osu.Game.Modes.Osu; using osu.Game.Tests.Resources; using osu.Game.Beatmaps.Formats; -using osu.Game.Beatmaps; using osu.Game.Database; namespace osu.Game.Tests.Beatmaps.IO From 5cb16f6e7c42c6dbce3e1f62cc17201f2c01e080 Mon Sep 17 00:00:00 2001 From: smoogipooo Date: Mon, 3 Apr 2017 20:32:03 +0900 Subject: [PATCH 10/11] Renamings + comments. --- .../Beatmaps/TaikoBeatmapConverter.cs | 39 ++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 21b75070df..594ed5f309 100644 --- a/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Modes.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -15,14 +15,27 @@ 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; - private const float base_scoring_distance = 100; + /// + /// 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 base_distance = 100; + private const float taiko_base_distance = 100; public Beatmap Convert(Beatmap original) { @@ -56,10 +69,10 @@ namespace osu.Game.Modes.Taiko.Beatmaps 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_scale; + double distance = distanceData.Distance * repeats * legacy_velocity_multiplier; // The velocity of the taiko hit object - calculated as the velocity of a drum roll - double taikoVelocity = base_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier / speedAdjustedBeatLength * legacy_velocity_scale; + double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier / speedAdjustedBeatLength * legacy_velocity_multiplier; // The duration of the taiko hit object double taikoDuration = distance / taikoVelocity; @@ -69,7 +82,7 @@ namespace osu.Game.Modes.Taiko.Beatmaps speedAdjustedBeatLength /= speedAdjustment; // The velocity of the osu! hit object - calculated as the velocity of a slider - double osuVelocity = base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier / speedAdjustedBeatLength * legacy_velocity_scale; + 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; @@ -87,7 +100,7 @@ namespace osu.Game.Modes.Taiko.Beatmaps StartTime = j, Sample = obj.Sample, IsStrong = strong, - VelocityMultiplier = legacy_velocity_scale + VelocityMultiplier = legacy_velocity_multiplier }; } } @@ -100,13 +113,13 @@ namespace osu.Game.Modes.Taiko.Beatmaps IsStrong = strong, Distance = distance, TickRate = beatmap.BeatmapInfo.Difficulty.SliderTickRate == 3 ? 3 : 4, - VelocityMultiplier = legacy_velocity_scale + 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 { @@ -115,7 +128,7 @@ namespace osu.Game.Modes.Taiko.Beatmaps IsStrong = strong, EndTime = endTimeData.EndTime, RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier), - VelocityMultiplier = legacy_velocity_scale + VelocityMultiplier = legacy_velocity_multiplier }; } else @@ -129,7 +142,7 @@ namespace osu.Game.Modes.Taiko.Beatmaps StartTime = obj.StartTime, Sample = obj.Sample, IsStrong = strong, - VelocityMultiplier = legacy_velocity_scale + VelocityMultiplier = legacy_velocity_multiplier }; } else @@ -139,7 +152,7 @@ namespace osu.Game.Modes.Taiko.Beatmaps StartTime = obj.StartTime, Sample = obj.Sample, IsStrong = strong, - VelocityMultiplier = legacy_velocity_scale + VelocityMultiplier = legacy_velocity_multiplier }; } } From 2e80ecfda845945c526f91fc7b282175815fb9db Mon Sep 17 00:00:00 2001 From: smoogipooo Date: Mon, 3 Apr 2017 20:33:10 +0900 Subject: [PATCH 11/11] Don't need explicit null value. --- osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs index 6d03205ca2..748583606b 100644 --- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs @@ -273,10 +273,10 @@ namespace osu.Game.Beatmaps.Formats HitObjectParser parser = null; + Section section = Section.None; bool hasCustomColours = false; - Section section = Section.None; - string line = null; + string line; while ((line = stream.ReadLine()) != null) { if (string.IsNullOrEmpty(line))