diff --git a/osu-framework b/osu-framework index 42e26d49b9..777996fb97 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 42e26d49b9046fcb96c123b0dfb48e06d741e162 +Subproject commit 777996fb9731ba1895a5ab1323cbbc97259ff741 diff --git a/osu-resources b/osu-resources index ffccbeb98d..9f46a456dc 160000 --- a/osu-resources +++ b/osu-resources @@ -1 +1 @@ -Subproject commit ffccbeb98dc9e8f0965520270b5885e63f244c83 +Subproject commit 9f46a456dc3a56dcbff09671a3f588b16a464106 diff --git a/osu.Desktop.VisualTests/Tests/TestCaseGamefield.cs b/osu.Desktop.VisualTests/Tests/TestCaseGamefield.cs index 6bd9d35b80..e2cd2bf67b 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseGamefield.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseGamefield.cs @@ -18,7 +18,7 @@ using osu.Game.Rulesets.Taiko.UI; using System.Collections.Generic; using osu.Desktop.VisualTests.Beatmaps; using osu.Framework.Allocation; -using osu.Game.Beatmaps.Timing; +using osu.Game.Beatmaps.ControlPoints; namespace osu.Desktop.VisualTests.Tests { @@ -53,8 +53,8 @@ namespace osu.Desktop.VisualTests.Tests time += RNG.Next(50, 500); } - TimingInfo timing = new TimingInfo(); - timing.ControlPoints.Add(new ControlPoint + var controlPointInfo = new ControlPointInfo(); + controlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = 200 }); @@ -73,7 +73,7 @@ namespace osu.Desktop.VisualTests.Tests Author = @"peppy", }, }, - TimingInfo = timing + ControlPointInfo = controlPointInfo }); Add(new Drawable[] diff --git a/osu.Desktop.VisualTests/Tests/TestCaseManiaPlayfield.cs b/osu.Desktop.VisualTests/Tests/TestCaseManiaPlayfield.cs index 04fcd8e94a..9dcba02849 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseManiaPlayfield.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseManiaPlayfield.cs @@ -7,10 +7,10 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Mania.UI; using System; using System.Collections.Generic; -using osu.Game.Beatmaps.Timing; using OpenTK; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Timing; namespace osu.Desktop.VisualTests.Tests { @@ -27,7 +27,7 @@ namespace osu.Desktop.VisualTests.Tests Action createPlayfield = (cols, pos) => { Clear(); - Add(new ManiaPlayfield(cols, new List()) + Add(new ManiaPlayfield(cols, new List()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -41,7 +41,7 @@ namespace osu.Desktop.VisualTests.Tests Clear(); ManiaPlayfield playField; - Add(playField = new ManiaPlayfield(cols, new List { new ControlPoint { BeatLength = 200 } }) + Add(playField = new ManiaPlayfield(cols, new List { new TimingChange { BeatLength = 200 } }) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Desktop/Beatmaps/IO/LegacyFilesystemReader.cs b/osu.Desktop/Beatmaps/IO/LegacyFilesystemReader.cs index 8c896646bf..8772fc9f28 100644 --- a/osu.Desktop/Beatmaps/IO/LegacyFilesystemReader.cs +++ b/osu.Desktop/Beatmaps/IO/LegacyFilesystemReader.cs @@ -37,5 +37,7 @@ namespace osu.Desktop.Beatmaps.IO { // no-op } + + public override Stream GetUnderlyingStream() => null; } } diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 0cad23304e..718e0967da 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -5,11 +5,11 @@ using System; using System.Linq; using osu.Game.Audio; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { @@ -32,11 +32,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy public DistanceObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, Pattern previousPattern) : base(random, hitObject, beatmap, previousPattern) { - ControlPoint overridePoint; - ControlPoint controlPoint = Beatmap.TimingInfo.TimingPointAt(hitObject.StartTime, out overridePoint); - convertType = PatternType.None; - if ((overridePoint ?? controlPoint)?.KiaiMode == false) + if (Beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime).KiaiMode) convertType = PatternType.LowProbability; var distanceData = hitObject as IHasDistance; @@ -44,13 +41,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy repeatCount = repeatsData?.RepeatCount ?? 1; - double speedAdjustment = beatmap.TimingInfo.SpeedMultiplierAt(hitObject.StartTime); - double speedAdjustedBeatLength = beatmap.TimingInfo.BeatLengthAt(hitObject.StartTime) * speedAdjustment; + TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); + DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime); // The true distance, accounting for any repeats double distance = (distanceData?.Distance ?? 0) * repeatCount; // 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; + double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier / (timingPoint.BeatLength * difficultyPoint.SpeedMultiplier); // The duration of the osu! hit object double osuDuration = distance / osuVelocity; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs index d044ee8893..b1ba99d98b 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs @@ -6,7 +6,7 @@ using System.Linq; using OpenTK; using osu.Game.Audio; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Timing; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects; @@ -25,17 +25,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { StairType = lastStair; - ControlPoint overridePoint; - ControlPoint controlPoint = beatmap.TimingInfo.TimingPointAt(hitObject.StartTime, out overridePoint); + TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); + EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime); var positionData = hitObject as IHasPosition; float positionSeparation = ((positionData?.Position ?? Vector2.Zero) - previousPosition).Length; double timeSeparation = hitObject.StartTime - previousTime; - double beatLength = controlPoint.BeatLength; - bool kiai = (overridePoint ?? controlPoint).KiaiMode; - if (timeSeparation <= 125) { // More than 120 BPM @@ -72,12 +69,12 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy // More than 100 BPM stream convertType |= PatternType.ForceStack | PatternType.LowProbability; } - else if (positionSeparation < 20 && density >= beatLength / 2.5) + else if (positionSeparation < 20 && density >= timingPoint.BeatLength / 2.5) { // Low density stream convertType |= PatternType.Reverse | PatternType.LowProbability; } - else if (density < beatLength / 2.5 || kiai) + else if (density < timingPoint.BeatLength / 2.5 || effectPoint.KiaiMode) { // High density } diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index 41bbe08d56..30e71aeb5d 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Audio; -using osu.Game.Beatmaps.Timing; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Database; using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Objects.Types; @@ -33,9 +33,9 @@ namespace osu.Game.Rulesets.Mania.Objects /// public HitWindows ReleaseHitWindows { get; protected set; } = new HitWindows(); - public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty) + public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - base.ApplyDefaults(timing, difficulty); + base.ApplyDefaults(controlPointInfo, difficulty); ReleaseHitWindows = HitWindows * release_window_lenience; } diff --git a/osu.Game.Rulesets.Mania/Objects/Note.cs b/osu.Game.Rulesets.Mania/Objects/Note.cs index e955f6658b..6c0cacd277 100644 --- a/osu.Game.Rulesets.Mania/Objects/Note.cs +++ b/osu.Game.Rulesets.Mania/Objects/Note.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Beatmaps.Timing; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Database; using osu.Game.Rulesets.Mania.Judgements; @@ -17,9 +17,9 @@ namespace osu.Game.Rulesets.Mania.Objects /// public HitWindows HitWindows { get; protected set; } = new HitWindows(); - public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty) + public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - base.ApplyDefaults(timing, difficulty); + base.ApplyDefaults(controlPointInfo, difficulty); HitWindows = new HitWindows(difficulty.OverallDifficulty); } diff --git a/osu.Game.Rulesets.Mania/Timing/ControlPointContainer.cs b/osu.Game.Rulesets.Mania/Timing/ControlPointContainer.cs index 2ff97047c0..cc8897840e 100644 --- a/osu.Game.Rulesets.Mania/Timing/ControlPointContainer.cs +++ b/osu.Game.Rulesets.Mania/Timing/ControlPointContainer.cs @@ -7,7 +7,7 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using OpenTK; -using osu.Game.Beatmaps.Timing; +using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Rulesets.Mania.Timing { @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Timing private readonly List drawableControlPoints; - public ControlPointContainer(IEnumerable timingChanges) + public ControlPointContainer(IEnumerable timingChanges) { drawableControlPoints = timingChanges.Select(t => new DrawableControlPoint(t)).ToList(); Children = drawableControlPoints; @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Mania.Timing /// private class DrawableControlPoint : Container { - private readonly ControlPoint timingChange; + private readonly TimingChange timingChange; protected override Container Content => content; private readonly Container content; @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Mania.Timing /// the content container will scroll at twice the normal rate. /// /// The control point to create the drawable control point for. - public DrawableControlPoint(ControlPoint timingChange) + public DrawableControlPoint(TimingChange timingChange) { this.timingChange = timingChange; diff --git a/osu.Game.Rulesets.Mania/Timing/TimingChange.cs b/osu.Game.Rulesets.Mania/Timing/TimingChange.cs new file mode 100644 index 0000000000..9153ba6991 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Timing/TimingChange.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Mania.Timing +{ + public class TimingChange + { + /// + /// The time at which this timing change happened. + /// + public double Time; + + /// + /// The beat length. + /// + public double BeatLength = 500; + + /// + /// The speed multiplier. + /// + public double SpeedMultiplier = 1; + } +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 72c60b28c9..c8cb5f6387 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -17,7 +17,6 @@ using System.Collections.Generic; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Judgements; -using osu.Game.Beatmaps.Timing; using System; using osu.Framework.Configuration; @@ -46,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.UI public readonly ControlPointContainer ControlPointContainer; - public Column(IEnumerable timingChanges) + public Column(IEnumerable timingChanges) { RelativeSizeAxes = Axes.Y; Width = column_width; diff --git a/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs b/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs index 4d734d231f..95b7979e43 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs @@ -2,19 +2,22 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; using System.Linq; using OpenTK; using OpenTK.Input; using osu.Framework.Configuration; using osu.Framework.Graphics; +using osu.Framework.Lists; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Timing; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Scoring; +using osu.Game.Rulesets.Mania.Timing; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; @@ -33,22 +36,32 @@ namespace osu.Game.Rulesets.Mania.UI protected override Playfield CreatePlayfield() { - ControlPoint firstTimingChange = Beatmap.TimingInfo.ControlPoints.FirstOrDefault(t => t.TimingChange); + double lastSpeedMultiplier = 1; + double lastBeatLength = 500; - if (firstTimingChange == null) - throw new InvalidOperationException("The Beatmap contains no timing points!"); + // Merge timing + difficulty points + var allPoints = new SortedList(Comparer.Default); + allPoints.AddRange(Beatmap.ControlPointInfo.TimingPoints); + allPoints.AddRange(Beatmap.ControlPointInfo.DifficultyPoints); // Generate the timing points, making non-timing changes use the previous timing change - var timingChanges = Beatmap.TimingInfo.ControlPoints.Select(c => + var timingChanges = allPoints.Select(c => { - ControlPoint t = c.Clone(); + var timingPoint = c as TimingControlPoint; + var difficultyPoint = c as DifficultyControlPoint; - if (c.TimingChange) - firstTimingChange = c; - else - t.BeatLength = firstTimingChange.BeatLength; + if (timingPoint != null) + lastBeatLength = timingPoint.BeatLength; - return t; + if (difficultyPoint != null) + lastSpeedMultiplier = difficultyPoint.SpeedMultiplier; + + return new TimingChange + { + Time = c.Time, + BeatLength = lastBeatLength, + SpeedMultiplier = lastSpeedMultiplier + }; }); double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue; diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 70bdd3b13c..ff763f87c4 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -19,7 +19,6 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Mania.Timing; using osu.Framework.Input; -using osu.Game.Beatmaps.Timing; using osu.Framework.Graphics.Transforms; using osu.Framework.MathUtils; @@ -65,7 +64,7 @@ namespace osu.Game.Rulesets.Mania.UI private readonly int columnCount; - public ManiaPlayfield(int columnCount, IEnumerable timingChanges) + public ManiaPlayfield(int columnCount, IEnumerable timingChanges) { this.columnCount = columnCount; diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj index a3f30acae0..9442d7cf8f 100644 --- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj +++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj @@ -78,6 +78,7 @@ + diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 723a37ed7b..e6fd82e6c8 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -6,8 +6,8 @@ using OpenTK; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using OpenTK.Graphics; -using osu.Game.Beatmaps.Timing; using osu.Game.Database; +using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Rulesets.Osu.Objects { @@ -68,9 +68,9 @@ namespace osu.Game.Rulesets.Osu.Objects return OsuScoreResult.Miss; } - public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty) + public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - base.ApplyDefaults(timing, difficulty); + base.ApplyDefaults(controlPointInfo, difficulty); Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2; } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 6c0147a3de..3b44e38d5e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using OpenTK; -using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Objects.Types; using System; using System.Collections.Generic; @@ -10,6 +9,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Database; using System.Linq; using osu.Game.Audio; +using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Rulesets.Osu.Objects { @@ -62,13 +62,16 @@ namespace osu.Game.Rulesets.Osu.Objects public double Velocity; public double TickDistance; - public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty) + public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - base.ApplyDefaults(timing, difficulty); + base.ApplyDefaults(controlPointInfo, difficulty); - double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier / timing.SpeedMultiplierAt(StartTime); + TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); + DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); - Velocity = scoringDistance / timing.BeatLengthAt(StartTime); + double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier / difficultyPoint.SpeedMultiplier; + + Velocity = scoringDistance / timingPoint.BeatLength; TickDistance = scoringDistance / difficulty.SliderTickRate; } diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 3761b62b65..eff60ba935 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -2,8 +2,8 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Rulesets.Objects.Types; -using osu.Game.Beatmaps.Timing; using osu.Game.Database; +using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Rulesets.Osu.Objects { @@ -19,9 +19,9 @@ namespace osu.Game.Rulesets.Osu.Objects public override bool NewCombo => true; - public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty) + public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - base.ApplyDefaults(timing, difficulty); + base.ApplyDefaults(controlPointInfo, difficulty); SpinsRequired = (int)(Duration / 1000 * BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5)); } diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index a2dea3731e..5d44da78f9 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -12,6 +12,7 @@ using osu.Game.Database; using osu.Game.IO.Serialization; using osu.Game.Audio; using osu.Game.Rulesets.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Rulesets.Taiko.Beatmaps { @@ -77,8 +78,11 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps { int repeats = repeatsData?.RepeatCount ?? 1; - double speedAdjustment = beatmap.TimingInfo.SpeedMultiplierAt(obj.StartTime); - double speedAdjustedBeatLength = beatmap.TimingInfo.BeatLengthAt(obj.StartTime) * speedAdjustment; + TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime); + DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime); + + double speedAdjustment = difficultyPoint.SpeedMultiplier; + double speedAdjustedBeatLength = timingPoint.BeatLength * 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; diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index f79c01b643..18e3016fc3 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -5,9 +5,9 @@ using osu.Game.Rulesets.Objects.Types; using System; using System.Collections.Generic; using System.Linq; -using osu.Game.Beatmaps.Timing; using osu.Game.Database; using osu.Game.Audio; +using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Rulesets.Taiko.Objects { @@ -55,11 +55,13 @@ namespace osu.Game.Rulesets.Taiko.Objects /// private double tickSpacing = 100; - public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty) + public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - base.ApplyDefaults(timing, difficulty); + base.ApplyDefaults(controlPointInfo, difficulty); - tickSpacing = timing.BeatLengthAt(StartTime) / TickRate; + TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); + + tickSpacing = timingPoint.BeatLength / 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); diff --git a/osu.Game.Rulesets.Taiko/Objects/Hit.cs b/osu.Game.Rulesets.Taiko/Objects/Hit.cs index 136e89124c..f31472d0fd 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Hit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Hit.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Beatmaps.Timing; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Database; namespace osu.Game.Rulesets.Taiko.Objects @@ -23,9 +23,9 @@ namespace osu.Game.Rulesets.Taiko.Objects /// public double HitWindowMiss = 95; - public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty) + public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - base.ApplyDefaults(timing, difficulty); + base.ApplyDefaults(controlPointInfo, difficulty); HitWindowGreat = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 50, 35, 20); HitWindowGood = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 120, 80, 50); diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs index 6a6353fde2..82ceaeed1a 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Beatmaps.Timing; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Database; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Taiko.UI; @@ -31,12 +31,12 @@ namespace osu.Game.Rulesets.Taiko.Objects public const float DEFAULT_STRONG_CIRCLE_DIAMETER = DEFAULT_CIRCLE_DIAMETER * STRONG_CIRCLE_DIAMETER_SCALE; /// - /// The time taken from the initial (off-screen) spawn position to the centre of the hit target for a of 1000ms. + /// The time taken from the initial (off-screen) spawn position to the centre of the hit target for a of 1000ms. /// private const double scroll_time = 6000; /// - /// Our adjusted taking into consideration local and other speed multipliers. + /// Our adjusted taking into consideration local and other speed multipliers. /// public double ScrollTime; @@ -51,17 +51,17 @@ namespace osu.Game.Rulesets.Taiko.Objects /// public bool Kiai { get; protected set; } - public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty) + public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - base.ApplyDefaults(timing, difficulty); + base.ApplyDefaults(controlPointInfo, difficulty); - ScrollTime = scroll_time * (timing.BeatLengthAt(StartTime) * timing.SpeedMultiplierAt(StartTime) / 1000) / difficulty.SliderMultiplier; + TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); + DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); + EffectControlPoint effectPoint = controlPointInfo.EffectPointAt(StartTime); - ControlPoint overridePoint; - Kiai = timing.TimingPointAt(StartTime, out overridePoint).KiaiMode; + ScrollTime = scroll_time * (timingPoint.BeatLength * difficultyPoint.SpeedMultiplier / 1000) / difficulty.SliderMultiplier; - if (overridePoint != null) - Kiai |= overridePoint.KiaiMode; + Kiai |= effectPoint.KiaiMode; } } } \ No newline at end of file diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoHitRenderer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoHitRenderer.cs index 73450d576d..662cace511 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoHitRenderer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoHitRenderer.cs @@ -17,6 +17,7 @@ using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Taiko.Replays; using OpenTK; using osu.Game.Rulesets.Beatmaps; +using System.Linq; namespace osu.Game.Rulesets.Taiko.UI { @@ -43,7 +44,7 @@ namespace osu.Game.Rulesets.Taiko.UI TaikoHitObject lastObject = Beatmap.HitObjects[Beatmap.HitObjects.Count - 1]; double lastHitTime = 1 + (lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime; - var timingPoints = Beatmap.TimingInfo.ControlPoints.FindAll(cp => cp.TimingChange); + var timingPoints = Beatmap.ControlPointInfo.TimingPoints.ToList(); if (timingPoints.Count == 0) return; @@ -68,7 +69,7 @@ namespace osu.Game.Rulesets.Taiko.UI StartTime = time, }; - barLine.ApplyDefaults(Beatmap.TimingInfo, Beatmap.BeatmapInfo.Difficulty); + barLine.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.Difficulty); bool isMajor = currentBeat % (int)currentPoint.TimeSignature == 0; taikoPlayfield.AddBarLine(isMajor ? new DrawableBarLineMajor(barLine) : new DrawableBarLine(barLine)); diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 608b2fcd19..0368455b92 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -7,6 +7,7 @@ using osu.Game.Database; using osu.Game.Rulesets.Objects; using System.Collections.Generic; using System.Linq; +using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Beatmaps { @@ -17,7 +18,7 @@ namespace osu.Game.Beatmaps where T : HitObject { public BeatmapInfo BeatmapInfo; - public TimingInfo TimingInfo = new TimingInfo(); + public ControlPointInfo ControlPointInfo = new ControlPointInfo(); public List Breaks = new List(); public readonly List ComboColors = new List { @@ -46,7 +47,7 @@ namespace osu.Game.Beatmaps public Beatmap(Beatmap original = null) { BeatmapInfo = original?.BeatmapInfo ?? BeatmapInfo; - TimingInfo = original?.TimingInfo ?? TimingInfo; + ControlPointInfo = original?.ControlPointInfo ?? ControlPointInfo; Breaks = original?.Breaks ?? Breaks; ComboColors = original?.ComboColors ?? ComboColors; } diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs new file mode 100644 index 0000000000..0d1dc21e96 --- /dev/null +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; + +namespace osu.Game.Beatmaps.ControlPoints +{ + public class ControlPoint : IComparable + { + /// + /// The time at which the control point takes effect. + /// + public double Time; + + public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time); + } +} diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs new file mode 100644 index 0000000000..acf90931ac --- /dev/null +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -0,0 +1,107 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Lists; + +namespace osu.Game.Beatmaps.ControlPoints +{ + public class ControlPointInfo + { + /// + /// All timing points. + /// + public readonly SortedList TimingPoints = new SortedList(Comparer.Default); + + /// + /// All difficulty points. + /// + public readonly SortedList DifficultyPoints = new SortedList(Comparer.Default); + + /// + /// All sound points. + /// + public readonly SortedList SoundPoints = new SortedList(Comparer.Default); + + /// + /// All effect points. + /// + public readonly SortedList EffectPoints = new SortedList(Comparer.Default); + + /// + /// Finds the difficulty control point that is active at . + /// + /// The time to find the difficulty control point at. + /// The difficulty control point. + public DifficultyControlPoint DifficultyPointAt(double time) => binarySearch(DifficultyPoints, time); + + /// + /// Finds the effect control point that is active at . + /// + /// The time to find the effect control point at. + /// The effect control point. + public EffectControlPoint EffectPointAt(double time) => binarySearch(EffectPoints, time); + + /// + /// Finds the sound control point that is active at . + /// + /// The time to find the sound control point at. + /// The sound control point. + public SoundControlPoint SoundPointAt(double time) => binarySearch(SoundPoints, time); + + /// + /// Finds the timing control point that is active at . + /// + /// The time to find the timing control point at. + /// The timing control point. + public TimingControlPoint TimingPointAt(double time) => binarySearch(TimingPoints, time, TimingPoints.FirstOrDefault()); + + /// + /// Finds the maximum BPM represented by any timing control point. + /// + public double BPMMaximum => + 60000 / (TimingPoints.OrderBy(c => c.BeatLength).FirstOrDefault() ?? new TimingControlPoint()).BeatLength; + + /// + /// Finds the minimum BPM represented by any timing control point. + /// + public double BPMMinimum => + 60000 / (TimingPoints.OrderByDescending(c => c.BeatLength).FirstOrDefault() ?? new TimingControlPoint()).BeatLength; + + /// + /// Finds the mode BPM (most common BPM) represented by the control points. + /// + public double BPMMode => + 60000 / (TimingPoints.GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).FirstOrDefault()?.FirstOrDefault() ?? new TimingControlPoint()).BeatLength; + + /// + /// Binary searches one of the control point lists to find the active control point at . + /// + /// The list to search. + /// The time to find the control point at. + /// The control point to use when is before any control points. If null, a new control point will be constructed. + /// The active control point at . + private T binarySearch(SortedList list, double time, T prePoint = null) + where T : ControlPoint, new() + { + if (list.Count == 0) + return new T(); + + if (time < list[0].Time) + return prePoint ?? new T(); + + int index = list.BinarySearch(new T() { Time = time }); + + // Check if we've found an exact match (t == time) + if (index >= 0) + return list[index]; + + index = ~index; + + // BinarySearch will return the index of the first element _greater_ than the search + // This is the inactive point - the active point is the one before it (index - 1) + return list[index - 1]; + } + } +} \ No newline at end of file diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs new file mode 100644 index 0000000000..30c7884334 --- /dev/null +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Beatmaps.ControlPoints +{ + public class DifficultyControlPoint : ControlPoint + { + /// + /// The speed multiplier at this control point. + /// + public double SpeedMultiplier = 1; + } +} \ No newline at end of file diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs new file mode 100644 index 0000000000..7671739cd9 --- /dev/null +++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Beatmaps.ControlPoints +{ + public class EffectControlPoint : ControlPoint + { + /// + /// Whether this control point enables Kiai mode. + /// + public bool KiaiMode; + + /// + /// Whether the first bar line of this control point is ignored. + /// + public bool OmitFirstBarLine; + } +} \ No newline at end of file diff --git a/osu.Game/Beatmaps/ControlPoints/SoundControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SoundControlPoint.cs new file mode 100644 index 0000000000..8084229382 --- /dev/null +++ b/osu.Game/Beatmaps/ControlPoints/SoundControlPoint.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Beatmaps.ControlPoints +{ + public class SoundControlPoint : ControlPoint + { + /// + /// The default sample bank at this control point. + /// + public string SampleBank; + + /// + /// The default sample volume at this control point. + /// + public int SampleVolume; + } +} \ No newline at end of file diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs new file mode 100644 index 0000000000..6f7e38c163 --- /dev/null +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -0,0 +1,20 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps.Timing; + +namespace osu.Game.Beatmaps.ControlPoints +{ + public class TimingControlPoint : ControlPoint + { + /// + /// The time signature at this control point. + /// + public TimeSignatures TimeSignature = TimeSignatures.SimpleQuadruple; + + /// + /// The beat length at this control point. + /// + public double BeatLength = 500; + } +} \ No newline at end of file diff --git a/osu.Game/Beatmaps/DifficultyCalculator.cs b/osu.Game/Beatmaps/DifficultyCalculator.cs index f483d1e6e3..474d38aa1b 100644 --- a/osu.Game/Beatmaps/DifficultyCalculator.cs +++ b/osu.Game/Beatmaps/DifficultyCalculator.cs @@ -37,7 +37,7 @@ namespace osu.Game.Beatmaps Objects = CreateBeatmapConverter().Convert(beatmap, true).HitObjects; foreach (var h in Objects) - h.ApplyDefaults(beatmap.TimingInfo, beatmap.BeatmapInfo.Difficulty); + h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.Difficulty); PreprocessHitObjects(); } diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs index 04208337c7..cb1d9d2fcd 100644 --- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs @@ -8,6 +8,7 @@ using OpenTK.Graphics; using osu.Game.Beatmaps.Timing; using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Objects.Legacy; +using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Beatmaps.Formats { @@ -241,6 +242,7 @@ namespace osu.Game.Beatmaps.Formats double time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo); double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo); + double speedMultiplier = beatLength < 0 ? -beatLength / 100.0 : 1; TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple; if (split.Length >= 3) @@ -275,18 +277,48 @@ namespace osu.Game.Beatmaps.Formats if (stringSampleSet == @"none") stringSampleSet = @"normal"; - beatmap.TimingInfo.ControlPoints.Add(new ControlPoint + DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(time); + SoundControlPoint soundPoint = beatmap.ControlPointInfo.SoundPointAt(time); + EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(time); + + if (timingChange) { - Time = time, - BeatLength = beatLength, - SpeedMultiplier = beatLength < 0 ? -beatLength / 100.0 : 1, - TimingChange = timingChange, - TimeSignature = timeSignature, - SampleBank = stringSampleSet, - SampleVolume = sampleVolume, - KiaiMode = kiaiMode, - OmitFirstBarLine = omitFirstBarSignature - }); + beatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint + { + Time = time, + BeatLength = beatLength, + TimeSignature = timeSignature + }); + } + + if (speedMultiplier != difficultyPoint.SpeedMultiplier) + { + beatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint + { + Time = time, + SpeedMultiplier = speedMultiplier + }); + } + + if (stringSampleSet != soundPoint.SampleBank || sampleVolume != soundPoint.SampleVolume) + { + beatmap.ControlPointInfo.SoundPoints.Add(new SoundControlPoint + { + Time = time, + SampleBank = stringSampleSet, + SampleVolume = sampleVolume + }); + } + + if (kiaiMode != effectPoint.KiaiMode || omitFirstBarSignature != effectPoint.OmitFirstBarLine) + { + beatmap.ControlPointInfo.EffectPoints.Add(new EffectControlPoint + { + Time = time, + KiaiMode = kiaiMode, + OmitFirstBarLine = omitFirstBarSignature + }); + } } private void handleColours(Beatmap beatmap, string key, string val, ref bool hasCustomColours) diff --git a/osu.Game/Beatmaps/IO/ArchiveReader.cs b/osu.Game/Beatmaps/IO/ArchiveReader.cs index 9d7d67007e..7ff5668b6f 100644 --- a/osu.Game/Beatmaps/IO/ArchiveReader.cs +++ b/osu.Game/Beatmaps/IO/ArchiveReader.cs @@ -63,5 +63,7 @@ namespace osu.Game.Beatmaps.IO return buffer; } } + + public abstract Stream GetUnderlyingStream(); } } \ No newline at end of file diff --git a/osu.Game/Beatmaps/IO/OszArchiveReader.cs b/osu.Game/Beatmaps/IO/OszArchiveReader.cs index 6c550def8d..eb9e740779 100644 --- a/osu.Game/Beatmaps/IO/OszArchiveReader.cs +++ b/osu.Game/Beatmaps/IO/OszArchiveReader.cs @@ -49,5 +49,7 @@ namespace osu.Game.Beatmaps.IO archive.Dispose(); archiveStream.Dispose(); } + + public override Stream GetUnderlyingStream() => archiveStream; } } \ No newline at end of file diff --git a/osu.Game/Beatmaps/Timing/ControlPoint.cs b/osu.Game/Beatmaps/Timing/ControlPoint.cs deleted file mode 100644 index fbae7d9614..0000000000 --- a/osu.Game/Beatmaps/Timing/ControlPoint.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Beatmaps.Timing -{ - public class ControlPoint - { - public string SampleBank; - public int SampleVolume; - public TimeSignatures TimeSignature = TimeSignatures.SimpleQuadruple; - public double Time; - public double BeatLength = 500; - public double SpeedMultiplier = 1; - public bool TimingChange = true; - public bool KiaiMode; - public bool OmitFirstBarLine; - - public ControlPoint Clone() => (ControlPoint)MemberwiseClone(); - } -} diff --git a/osu.Game/Beatmaps/Timing/TimingInfo.cs b/osu.Game/Beatmaps/Timing/TimingInfo.cs deleted file mode 100644 index 19cb0816ba..0000000000 --- a/osu.Game/Beatmaps/Timing/TimingInfo.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using System.Linq; - -namespace osu.Game.Beatmaps.Timing -{ - public class TimingInfo - { - public readonly List ControlPoints = new List(); - - public double BPMMaximum => 60000 / (ControlPoints?.Where(c => c.BeatLength != 0).OrderBy(c => c.BeatLength).FirstOrDefault() ?? new ControlPoint()).BeatLength; - public double BPMMinimum => 60000 / (ControlPoints?.Where(c => c.BeatLength != 0).OrderByDescending(c => c.BeatLength).FirstOrDefault() ?? new ControlPoint()).BeatLength; - public double BPMMode => BPMAt(ControlPoints.Where(c => c.BeatLength != 0).GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).First().First().Time); - - public double BPMAt(double time) - { - return 60000 / BeatLengthAt(time); - } - - /// - /// Finds the speed multiplier at a time. - /// - /// The time to find the speed multiplier at. - /// The speed multiplier. - public double SpeedMultiplierAt(double time) - { - ControlPoint overridePoint; - ControlPoint timingPoint = TimingPointAt(time, out overridePoint); - - return overridePoint?.SpeedMultiplier ?? timingPoint?.SpeedMultiplier ?? 1; - } - - /// - /// Finds the beat length at a time. This is expressed in milliseconds. - /// - /// The time to find the beat length at. - /// The beat length. - public double BeatLengthAt(double time) - { - ControlPoint overridePoint; - ControlPoint timingPoint = TimingPointAt(time, out overridePoint); - - return timingPoint.BeatLength; - } - - /// - /// Finds the timing point at a time. - /// - /// The time to find the timing point at. - /// The timing point containing the velocity change of the returned timing point. - /// The timing point. - public ControlPoint TimingPointAt(double time, out ControlPoint overridePoint) - { - overridePoint = null; - - ControlPoint timingPoint = null; - foreach (var controlPoint in ControlPoints) - { - // Some beatmaps have the first timingPoint (accidentally) start after the first HitObject(s). - // This null check makes it so that the first ControlPoint that makes a timing change is used as - // the timingPoint for those HitObject(s). - if (controlPoint.Time <= time || timingPoint == null) - { - if (controlPoint.TimingChange) - { - timingPoint = controlPoint; - overridePoint = null; - } - else - overridePoint = controlPoint; - } - else break; - } - - return timingPoint ?? new ControlPoint(); - } - } -} \ No newline at end of file diff --git a/osu.Game/Database/BeatmapDatabase.cs b/osu.Game/Database/BeatmapDatabase.cs index bd25ebe8a9..efd5631077 100644 --- a/osu.Game/Database/BeatmapDatabase.cs +++ b/osu.Game/Database/BeatmapDatabase.cs @@ -12,6 +12,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.IO; using osu.Game.IPC; +using osu.Game.Screens.Menu; using SQLite.Net; using SQLiteNetExtensions.Extensions; @@ -38,6 +39,10 @@ namespace osu.Game.Database { foreach (var b in GetAllWithChildren(b => b.DeletePending)) { + if (b.Hash == Intro.MENU_MUSIC_BEATMAP_HASH) + // this is a bit hacky, but will do for now. + continue; + try { Storage.Delete(b.Path); @@ -97,50 +102,49 @@ namespace osu.Game.Database typeof(BeatmapDifficulty), }; + public void Import(string path) + { + try + { + Import(ArchiveReader.GetReader(Storage, path)); + + // We may or may not want to delete the file depending on where it is stored. + // e.g. reconstructing/repairing database with beatmaps from default storage. + // Also, not always a single file, i.e. for LegacyFilesystemReader + // TODO: Add a check to prevent files from storage to be deleted. + try + { + File.Delete(path); + } + catch (Exception e) + { + Logger.Error(e, $@"Could not delete file at {path}"); + } + } + catch (Exception e) + { + e = e.InnerException ?? e; + Logger.Error(e, @"Could not import beatmap set"); + } + } + + public void Import(ArchiveReader archiveReader) + { + BeatmapSetInfo set = getBeatmapSet(archiveReader); + + //If we have an ID then we already exist in the database. + if (set.ID == 0) + Import(new[] { set }); + } + /// /// Import multiple from . /// /// Multiple locations on disk - public void Import(IEnumerable paths) + public void Import(params string[] paths) { foreach (string p in paths) - { - try - { - BeatmapSetInfo set = getBeatmapSet(p); - - //If we have an ID then we already exist in the database. - if (set.ID == 0) - Import(new[] { set }); - - // We may or may not want to delete the file depending on where it is stored. - // e.g. reconstructing/repairing database with beatmaps from default storage. - // Also, not always a single file, i.e. for LegacyFilesystemReader - // TODO: Add a check to prevent files from storage to be deleted. - try - { - File.Delete(p); - } - catch (Exception e) - { - Logger.Error(e, $@"Could not delete file at {p}"); - } - } - catch (Exception e) - { - e = e.InnerException ?? e; - Logger.Error(e, @"Could not import beatmap set"); - } - } - } - - /// - /// Import from . - /// - /// Location on disk - public void Import(string path) - { - Import(new[] { path }); + Import(p); } /// @@ -148,29 +152,26 @@ namespace osu.Game.Database /// /// Content location /// - private BeatmapSetInfo getBeatmapSet(string path) - { - string hash = null; + private BeatmapSetInfo getBeatmapSet(string path) => getBeatmapSet(ArchiveReader.GetReader(Storage, path)); + private BeatmapSetInfo getBeatmapSet(ArchiveReader archiveReader) + { BeatmapMetadata metadata; - using (var reader = ArchiveReader.GetReader(Storage, path)) - { - using (var stream = new StreamReader(reader.GetStream(reader.BeatmapFilenames[0]))) - metadata = BeatmapDecoder.GetDecoder(stream).Decode(stream).Metadata; - } + using (var stream = new StreamReader(archiveReader.GetStream(archiveReader.BeatmapFilenames[0]))) + metadata = BeatmapDecoder.GetDecoder(stream).Decode(stream).Metadata; - if (File.Exists(path)) // Not always the case, i.e. for LegacyFilesystemReader + string hash; + string path; + + using (var input = archiveReader.GetUnderlyingStream()) { - using (var input = Storage.GetStream(path)) - { - hash = input.GetMd5Hash(); - input.Seek(0, SeekOrigin.Begin); - path = Path.Combine(@"beatmaps", hash.Remove(1), hash.Remove(2), hash); - if (!Storage.Exists(path)) - using (var output = Storage.GetStream(path, FileAccess.Write)) - input.CopyTo(output); - } + hash = input.GetMd5Hash(); + input.Seek(0, SeekOrigin.Begin); + path = Path.Combine(@"beatmaps", hash.Remove(1), hash.Remove(2), hash); + if (!Storage.Exists(path)) + using (var output = Storage.GetStream(path, FileAccess.Write)) + input.CopyTo(output); } var existing = Connection.Table().FirstOrDefault(b => b.Hash == hash); diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index dfb742f7d1..c0defceac0 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -2,58 +2,65 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Configuration; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Timing; +using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Graphics.Containers { public class BeatSyncedContainer : Container { - private readonly Bindable beatmap = new Bindable(); + protected readonly Bindable Beatmap = new Bindable(); private int lastBeat; - private ControlPoint lastControlPoint; + private TimingControlPoint lastTimingPoint; + + /// + /// The amount of time before a beat we should fire . + /// This allows for adding easing to animations that may be synchronised to the beat. + /// + protected double EarlyActivationMilliseconds; protected override void Update() { - if (beatmap.Value?.Track == null) + if (Beatmap.Value?.Track == null) return; - double currentTrackTime = beatmap.Value.Track.CurrentTime; - ControlPoint overridePoint; - ControlPoint controlPoint = beatmap.Value.Beatmap.TimingInfo.TimingPointAt(currentTrackTime, out overridePoint); + double currentTrackTime = Beatmap.Value.Track.CurrentTime + EarlyActivationMilliseconds; - if (controlPoint.BeatLength == 0) + TimingControlPoint timingPoint = Beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(currentTrackTime); + EffectControlPoint effectPoint = Beatmap.Value.Beatmap.ControlPointInfo.EffectPointAt(currentTrackTime); + + if (timingPoint.BeatLength == 0) return; - bool kiai = (overridePoint ?? controlPoint).KiaiMode; - int beat = (int)((currentTrackTime - controlPoint.Time) / controlPoint.BeatLength); + int beatIndex = (int)((currentTrackTime - timingPoint.Time) / timingPoint.BeatLength); // The beats before the start of the first control point are off by 1, this should do the trick - if (currentTrackTime < controlPoint.Time) - beat--; + if (currentTrackTime < timingPoint.Time) + beatIndex--; - if (controlPoint == lastControlPoint && beat == lastBeat) + if (timingPoint == lastTimingPoint && beatIndex == lastBeat) return; - double offsetFromBeat = (controlPoint.Time - currentTrackTime) % controlPoint.BeatLength; + double offsetFromBeat = (timingPoint.Time - currentTrackTime) % timingPoint.BeatLength; using (BeginDelayedSequence(offsetFromBeat, true)) - OnNewBeat(beat, controlPoint.BeatLength, controlPoint.TimeSignature, kiai); + OnNewBeat(beatIndex, timingPoint, effectPoint, Beatmap.Value.Track.CurrentAmplitudes); - lastBeat = beat; - lastControlPoint = controlPoint; + lastBeat = beatIndex; + lastTimingPoint = timingPoint; } [BackgroundDependencyLoader] private void load(OsuGameBase game) { - beatmap.BindTo(game.Beatmap); + Beatmap.BindTo(game.Beatmap); } - protected virtual void OnNewBeat(int newBeat, double beatLength, TimeSignatures timeSignature, bool kiai) + protected virtual void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) { } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 33dbe0a12a..96e4910f1e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -86,7 +86,7 @@ namespace osu.Game if (args?.Length > 0) { var paths = args.Where(a => !a.StartsWith(@"-")); - Task.Run(() => BeatmapDatabase.Import(paths)); + Task.Run(() => BeatmapDatabase.Import(paths.ToArray())); } Dependencies.Cache(this); diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 9a5fd769cd..ea9ccd3492 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; namespace osu.Game.Overlays.Settings.Sections.Graphics { @@ -11,11 +12,12 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { protected override string Header => "Layout"; - private SettingsSlider letterboxPositionX; - private SettingsSlider letterboxPositionY; + private FillFlowContainer letterboxSettings; private Bindable letterboxing; + private const int transition_duration = 400; + [BackgroundDependencyLoader] private void load(FrameworkConfigManager config) { @@ -33,34 +35,40 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics LabelText = "Letterboxing", Bindable = letterboxing, }, - letterboxPositionX = new SettingsSlider + letterboxSettings = new FillFlowContainer { - LabelText = "Horizontal position", - Bindable = config.GetBindable(FrameworkSetting.LetterboxPositionX) - }, - letterboxPositionY = new SettingsSlider - { - LabelText = "Vertical position", - Bindable = config.GetBindable(FrameworkSetting.LetterboxPositionY) + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + AutoSizeDuration = transition_duration, + AutoSizeEasing = EasingTypes.OutQuint, + Masking = true, + + Children = new Drawable[] + { + new SettingsSlider + { + LabelText = "Horizontal position", + Bindable = config.GetBindable(FrameworkSetting.LetterboxPositionX) + }, + new SettingsSlider + { + LabelText = "Vertical position", + Bindable = config.GetBindable(FrameworkSetting.LetterboxPositionY) + }, + } }, }; - letterboxing.ValueChanged += visibilityChanged; - letterboxing.TriggerChange(); - } + letterboxing.ValueChanged += isVisible => + { + letterboxSettings.ClearTransforms(); + letterboxSettings.AutoSizeAxes = isVisible ? Axes.Y : Axes.None; - private void visibilityChanged(bool newVisibility) - { - if (newVisibility) - { - letterboxPositionX.Show(); - letterboxPositionY.Show(); - } - else - { - letterboxPositionX.Hide(); - letterboxPositionY.Hide(); - } + if(!isVisible) + letterboxSettings.ResizeHeightTo(0, transition_duration, EasingTypes.OutQuint); + }; + letterboxing.TriggerChange(); } } } diff --git a/osu.Game/Rulesets/Beatmaps/BeatmapConverter.cs b/osu.Game/Rulesets/Beatmaps/BeatmapConverter.cs index 5342686c3f..aafa576d4b 100644 --- a/osu.Game/Rulesets/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Rulesets/Beatmaps/BeatmapConverter.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Beatmaps return new Beatmap { BeatmapInfo = original.BeatmapInfo, - TimingInfo = original.TimingInfo, + ControlPointInfo = original.ControlPointInfo, HitObjects = original.HitObjects.SelectMany(h => convert(h, original)).ToList() }; } diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 46fb5fcf70..5592681cab 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Audio; -using osu.Game.Beatmaps.Timing; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Database; using osu.Game.Rulesets.Objects.Types; @@ -33,31 +33,28 @@ namespace osu.Game.Rulesets.Objects /// /// Applies default values to this HitObject. /// + /// The control points. /// The difficulty settings to use. - /// The timing settings to use. - public virtual void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty) + public virtual void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - ControlPoint overridePoint; - ControlPoint timingPoint = timing.TimingPointAt(StartTime, out overridePoint); - - ControlPoint samplePoint = overridePoint ?? timingPoint; + SoundControlPoint soundPoint = controlPointInfo.SoundPointAt(StartTime); // Initialize first sample - Samples.ForEach(s => initializeSampleInfo(s, samplePoint)); + Samples.ForEach(s => initializeSampleInfo(s, soundPoint)); // Initialize any repeat samples var repeatData = this as IHasRepeats; - repeatData?.RepeatSamples?.ForEach(r => r.ForEach(s => initializeSampleInfo(s, samplePoint))); + repeatData?.RepeatSamples?.ForEach(r => r.ForEach(s => initializeSampleInfo(s, soundPoint))); } - private void initializeSampleInfo(SampleInfo sample, ControlPoint controlPoint) + private void initializeSampleInfo(SampleInfo sample, SoundControlPoint soundPoint) { if (sample.Volume == 0) - sample.Volume = controlPoint?.SampleVolume ?? 0; + sample.Volume = soundPoint?.SampleVolume ?? 0; // If the bank is not assigned a name, assign it from the control point if (string.IsNullOrEmpty(sample.Bank)) - sample.Bank = controlPoint?.SampleBank ?? @"normal"; + sample.Bank = soundPoint?.SampleBank ?? @"normal"; } } } diff --git a/osu.Game/Rulesets/UI/HitRenderer.cs b/osu.Game/Rulesets/UI/HitRenderer.cs index d0cce87e43..f0dc143668 100644 --- a/osu.Game/Rulesets/UI/HitRenderer.cs +++ b/osu.Game/Rulesets/UI/HitRenderer.cs @@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.UI // Apply defaults foreach (var h in Beatmap.HitObjects) - h.ApplyDefaults(Beatmap.TimingInfo, Beatmap.BeatmapInfo.Difficulty); + h.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.Difficulty); // Post-process the beatmap processor.PostProcess(Beatmap); diff --git a/osu.Game/Screens/Menu/Intro.cs b/osu.Game/Screens/Menu/Intro.cs index 01659edd72..5791b7f196 100644 --- a/osu.Game/Screens/Menu/Intro.cs +++ b/osu.Game/Screens/Menu/Intro.cs @@ -8,7 +8,10 @@ using osu.Framework.Audio.Track; using osu.Framework.Configuration; using osu.Framework.Screens; using osu.Framework.Graphics; +using osu.Framework.MathUtils; +using osu.Game.Beatmaps.IO; using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Graphics.Containers; using osu.Game.Screens.Backgrounds; using OpenTK.Graphics; @@ -19,6 +22,8 @@ namespace osu.Game.Screens.Menu { private readonly OsuLogo logo; + public const string MENU_MUSIC_BEATMAP_HASH = "21c1271b91234385978b5418881fdd88"; + /// /// Whether we have loaded the menu previously. /// @@ -27,7 +32,6 @@ namespace osu.Game.Screens.Menu private MainMenu mainMenu; private SampleChannel welcome; private SampleChannel seeya; - private Track bgm; internal override bool HasLocalCursorDisplayed => true; @@ -60,15 +64,49 @@ namespace osu.Game.Screens.Menu private Bindable menuVoice; private Bindable menuMusic; + private Track track; [BackgroundDependencyLoader] - private void load(AudioManager audio, OsuConfigManager config) + private void load(AudioManager audio, OsuConfigManager config, BeatmapDatabase beatmaps, Framework.Game game) { menuVoice = config.GetBindable(OsuSetting.MenuVoice); menuMusic = config.GetBindable(OsuSetting.MenuMusic); - bgm = audio.Track.Get(@"circles"); - bgm.Looping = true; + var trackManager = audio.Track; + + BeatmapSetInfo setInfo = null; + + if (!menuMusic) + { + var query = beatmaps.Query().Where(b => !b.DeletePending); + int count = query.Count(); + if (count > 0) + setInfo = query.ElementAt(RNG.Next(0, count - 1)); + } + + if (setInfo == null) + { + var query = beatmaps.Query().Where(b => b.Hash == MENU_MUSIC_BEATMAP_HASH); + + setInfo = query.FirstOrDefault(); + + if (setInfo == null) + { + // we need to import the default menu background beatmap + beatmaps.Import(new OszArchiveReader(game.Resources.GetStream(@"Tracks/circles.osz"))); + + setInfo = query.First(); + + setInfo.DeletePending = true; + beatmaps.Update(setInfo, false); + } + } + + beatmaps.GetChildren(setInfo); + Beatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); + + track = Beatmap.Track; + trackManager.SetExclusive(track); welcome = audio.Sample.Get(@"welcome"); seeya = audio.Sample.Get(@"seeya"); @@ -83,8 +121,7 @@ namespace osu.Game.Screens.Menu Scheduler.AddDelayed(delegate { - if (menuMusic) - bgm.Start(); + track.Start(); LoadComponentAsync(mainMenu = new MainMenu()); diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 61e1ee0832..1a7d2d4e37 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -1,18 +1,12 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System.Threading.Tasks; using OpenTK; using OpenTK.Input; using osu.Framework.Allocation; -using osu.Framework.Audio.Track; -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Input; -using osu.Framework.MathUtils; using osu.Framework.Screens; -using osu.Game.Configuration; -using osu.Game.Database; using osu.Game.Graphics.Containers; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Charts; @@ -54,36 +48,18 @@ namespace osu.Game.Screens.Menu OnSolo = delegate { Push(consumeSongSelect()); }, OnMulti = delegate { Push(new Lobby()); }, OnExit = delegate { Exit(); }, - } + }, + new MenuSideFlashes(), } } }; } - private Bindable menuMusic; - private TrackManager trackManager; - [BackgroundDependencyLoader] - private void load(OsuGame game, OsuConfigManager config, BeatmapDatabase beatmaps) + private void load(OsuGame game) { - menuMusic = config.GetBindable(OsuSetting.MenuMusic); LoadComponentAsync(background); - if (!menuMusic) - { - trackManager = game.Audio.Track; - - var query = beatmaps.Query().Where(b => !b.DeletePending); - int count = query.Count(); - - if (count > 0) - { - var beatmap = query.ElementAt(RNG.Next(0, count - 1)); - beatmaps.GetChildren(beatmap); - Beatmap = beatmaps.GetWorkingBeatmap(beatmap.Beatmaps[0]); - } - } - buttons.OnSettings = game.ToggleSettings; buttons.OnDirect = game.ToggleDirect; @@ -109,14 +85,13 @@ namespace osu.Game.Screens.Menu buttons.FadeInFromZero(500); if (last is Intro && Beatmap != null) { - Task.Run(() => + if (!Beatmap.Track.IsRunning) { - trackManager.SetExclusive(Beatmap.Track); Beatmap.Track.Seek(Beatmap.Metadata.PreviewTime); if (Beatmap.Metadata.PreviewTime == -1) Beatmap.Track.Seek(Beatmap.Track.Length * 0.4f); Beatmap.Track.Start(); - }); + } } } diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs new file mode 100644 index 0000000000..0cf1fa54fa --- /dev/null +++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs @@ -0,0 +1,96 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Audio.Track; +using osu.Framework.Configuration; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using System; + +namespace osu.Game.Screens.Menu +{ + public class MenuSideFlashes : BeatSyncedContainer + { + public override bool HandleInput => false; + + private readonly Bindable beatmap = new Bindable(); + + private readonly Box leftBox; + private readonly Box rightBox; + + private const float amplitude_dead_zone = 0.25f; + private const float alpha_multiplier = (1 - amplitude_dead_zone) / 0.55f; + private const float kiai_multiplier = (1 - amplitude_dead_zone * 0.95f) / 0.8f; + + private const int box_max_alpha = 200; + private const double box_fade_in_time = 65; + private const int box_width = 200; + + public MenuSideFlashes() + { + RelativeSizeAxes = Axes.Both; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Children = new Drawable[] + { + leftBox = new Box + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Y, + Width = box_width, + Alpha = 0, + BlendingMode = BlendingMode.Additive, + }, + rightBox = new Box + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Y, + Width = box_width, + Alpha = 0, + BlendingMode = BlendingMode.Additive, + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuGameBase game, OsuColour colours) + { + beatmap.BindTo(game.Beatmap); + + // linear colour looks better in this case, so let's use it for now. + Color4 gradientDark = colours.Blue.Opacity(0).ToLinear(); + Color4 gradientLight = colours.Blue.Opacity(0.3f).ToLinear(); + + leftBox.ColourInfo = ColourInfo.GradientHorizontal(gradientLight, gradientDark); + rightBox.ColourInfo = ColourInfo.GradientHorizontal(gradientDark, gradientLight); + } + + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + { + if (beatIndex < 0) + return; + + if (effectPoint.KiaiMode ? beatIndex % 2 == 0 : beatIndex % (int)timingPoint.TimeSignature == 0) + flash(leftBox, timingPoint.BeatLength, effectPoint.KiaiMode, amplitudes); + if (effectPoint.KiaiMode ? beatIndex % 2 == 1 : beatIndex % (int)timingPoint.TimeSignature == 0) + flash(rightBox, timingPoint.BeatLength, effectPoint.KiaiMode, amplitudes); + } + + private void flash(Drawable d, double beatLength, bool kiai, TrackAmplitudes amplitudes) + { + d.FadeTo(Math.Max(0, ((d.Equals(leftBox) ? amplitudes.LeftChannel : amplitudes.RightChannel) - amplitude_dead_zone) / (kiai ? kiai_multiplier : alpha_multiplier)), box_fade_in_time); + using (d.BeginDelayedSequence(box_fade_in_time)) + d.FadeOut(beatLength, EasingTypes.In); + } + } +} diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index e28adeacff..116f7291e2 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -5,13 +5,16 @@ using System; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Audio.Track; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; +using osu.Game.Graphics.Containers; using OpenTK; using OpenTK.Graphics; @@ -20,13 +23,15 @@ namespace osu.Game.Screens.Menu /// /// osu! logo and its attachments (pulsing, visualiser etc.) /// - public class OsuLogo : Container + public class OsuLogo : BeatSyncedContainer { public readonly Color4 OsuPink = OsuColour.FromHex(@"e967a1"); private readonly Sprite logo; private readonly CircularContainer logoContainer; private readonly Container logoBounceContainer; + private readonly Container logoBeatContainer; + private readonly Container logoAmplitudeContainer; private readonly Container logoHoverContainer; private SampleChannel sampleClick; @@ -67,8 +72,12 @@ namespace osu.Game.Screens.Menu private const float default_size = 480; + private const double beat_in_time = 60; + public OsuLogo() { + EarlyActivationMilliseconds = beat_in_time; + Size = new Vector2(default_size); Anchor = Anchor.Centre; @@ -78,67 +87,16 @@ namespace osu.Game.Screens.Menu Children = new Drawable[] { - logoBounceContainer = new Container + logoHoverContainer = new Container { AutoSizeAxes = Axes.Both, Children = new Drawable[] { - logoHoverContainer = new Container + logoBounceContainer = new Container { AutoSizeAxes = Axes.Both, Children = new Drawable[] { - new BufferedContainer - { - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - logoContainer = new CircularContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Scale = new Vector2(0.88f), - Masking = true, - Children = new Drawable[] - { - colourAndTriangles = new Container - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuPink, - }, - new Triangles - { - TriangleScale = 4, - ColourLight = OsuColour.FromHex(@"ff7db7"), - ColourDark = OsuColour.FromHex(@"de5b95"), - RelativeSizeAxes = Axes.Both, - }, - } - }, - flashLayer = new Box - { - RelativeSizeAxes = Axes.Both, - BlendingMode = BlendingMode.Additive, - Colour = Color4.White, - Alpha = 0, - }, - }, - }, - logo = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - } - }, rippleContainer = new Container { Anchor = Anchor.Centre, @@ -151,36 +109,101 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.Centre, Origin = Anchor.Centre, BlendingMode = BlendingMode.Additive, - Alpha = 0.15f + Alpha = 0 } } }, - impactContainer = new CircularContainer + logoAmplitudeContainer = new Container { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Alpha = 0, - BorderColour = Color4.White, - RelativeSizeAxes = Axes.Both, - BorderThickness = 10, - Masking = true, + AutoSizeAxes = Axes.Both, Children = new Drawable[] { - new Box + logoBeatContainer = new Container { - RelativeSizeAxes = Axes.Both, - AlwaysPresent = true, - Alpha = 0, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new BufferedContainer + { + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + logoContainer = new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Scale = new Vector2(0.88f), + Masking = true, + Children = new Drawable[] + { + colourAndTriangles = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuPink, + }, + new Triangles + { + TriangleScale = 4, + ColourLight = OsuColour.FromHex(@"ff7db7"), + ColourDark = OsuColour.FromHex(@"de5b95"), + RelativeSizeAxes = Axes.Both, + }, + } + }, + flashLayer = new Box + { + RelativeSizeAxes = Axes.Both, + BlendingMode = BlendingMode.Additive, + Colour = Color4.White, + Alpha = 0, + }, + }, + }, + logo = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } + }, + impactContainer = new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 0, + BorderColour = Color4.White, + RelativeSizeAxes = Axes.Both, + BorderThickness = 10, + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + AlwaysPresent = true, + Alpha = 0, + } + } + }, + new MenuVisualisation + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + BlendingMode = BlendingMode.Additive, + Alpha = 0.2f, + } + } } } - }, - new MenuVisualisation - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - BlendingMode = BlendingMode.Additive, - Alpha = 0.2f, } } } @@ -197,13 +220,48 @@ namespace osu.Game.Screens.Menu ripple.Texture = textures.Get(@"Menu/logo"); } - protected override void LoadComplete() - { - base.LoadComplete(); + private int lastBeatIndex; - ripple.ScaleTo(ripple.Scale * 1.1f, 500); - ripple.FadeOut(500); - ripple.Loop(300); + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + { + base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); + + lastBeatIndex = beatIndex; + + var beatLength = timingPoint.BeatLength; + + float amplitudeAdjust = Math.Min(1, 0.4f + amplitudes.Maximum); + + if (beatIndex < 0) return; + + logoBeatContainer.ScaleTo(1 - 0.02f * amplitudeAdjust, beat_in_time, EasingTypes.Out); + using (logoBeatContainer.BeginDelayedSequence(beat_in_time)) + logoBeatContainer.ScaleTo(1, beatLength * 2, EasingTypes.OutQuint); + + ripple.ClearTransforms(); + + ripple.ScaleTo(logoAmplitudeContainer.Scale); + ripple.Alpha = 0.15f * amplitudeAdjust; + + ripple.ScaleTo(logoAmplitudeContainer.Scale * (1 + 0.04f * amplitudeAdjust), beatLength, EasingTypes.OutQuint); + ripple.FadeOut(beatLength, EasingTypes.OutQuint); + + if (effectPoint.KiaiMode && flashLayer.Alpha < 0.4f) + { + flashLayer.ClearTransforms(); + + flashLayer.FadeTo(0.2f * amplitudeAdjust, beat_in_time, EasingTypes.Out); + using (flashLayer.BeginDelayedSequence(beat_in_time)) + flashLayer.FadeOut(beatLength); + } + } + + protected override void Update() + { + base.Update(); + + var maxAmplitude = lastBeatIndex >= 0 ? Beatmap.Value?.Track?.CurrentAmplitudes.Maximum ?? 0 : 0; + logoAmplitudeContainer.ScaleTo(1 - maxAmplitude * 0.04f, 50, EasingTypes.OutQuint); } protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) @@ -254,4 +312,4 @@ namespace osu.Game.Screens.Menu impactContainer.ScaleTo(1.12f, 250); } } -} \ No newline at end of file +} diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 1e57c2ba2a..115611c244 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -30,7 +30,6 @@ namespace osu.Game.Screens.Play public readonly SongProgress Progress; public readonly ModDisplay ModDisplay; - private Bindable showKeyCounter; private Bindable showHud; private static bool hasShownNotificationOnce; @@ -67,24 +66,8 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader(true)] private void load(OsuConfigManager config, NotificationManager notificationManager) { - showKeyCounter = config.GetBindable(OsuSetting.KeyOverlay); - showKeyCounter.ValueChanged += keyCounterVisibility => - { - if (keyCounterVisibility) - KeyCounter.FadeIn(duration); - else - KeyCounter.FadeOut(duration); - }; - showKeyCounter.TriggerChange(); - showHud = config.GetBindable(OsuSetting.ShowInterface); - showHud.ValueChanged += hudVisibility => - { - if (hudVisibility) - content.FadeIn(duration); - else - content.FadeOut(duration); - }; + showHud.ValueChanged += hudVisibility => content.FadeTo(hudVisibility ? 1 : 0, duration); showHud.TriggerChange(); if (!showHud && !hasShownNotificationOnce) diff --git a/osu.Game/Screens/Play/KeyCounterCollection.cs b/osu.Game/Screens/Play/KeyCounterCollection.cs index 4a561e6036..6e1c8a34e5 100644 --- a/osu.Game/Screens/Play/KeyCounterCollection.cs +++ b/osu.Game/Screens/Play/KeyCounterCollection.cs @@ -6,11 +6,18 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using OpenTK.Graphics; using osu.Framework.Input; +using osu.Framework.Configuration; +using osu.Framework.Allocation; +using osu.Game.Configuration; namespace osu.Game.Screens.Play { public class KeyCounterCollection : FillFlowContainer { + private const int duration = 100; + + private Bindable showKeyCounter; + public KeyCounterCollection() { AlwaysReceiveInput = true; @@ -34,6 +41,14 @@ namespace osu.Game.Screens.Play counter.ResetCount(); } + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + showKeyCounter = config.GetBindable(OsuSetting.KeyOverlay); + showKeyCounter.ValueChanged += keyCounterVisibility => FadeTo(keyCounterVisibility ? 1 : 0, duration); + showKeyCounter.TriggerChange(); + } + //further: change default values here and in KeyCounter if needed, instead of passing them in every constructor private bool isCounting; public bool IsCounting diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 3efc85d743..a39e7dbab2 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -123,7 +123,7 @@ namespace osu.Game.Screens.Play decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; var firstObjectTime = HitRenderer.Objects.First().StartTime; - decoupledClock.Seek(Math.Min(0, firstObjectTime - Math.Max(Beatmap.Beatmap.TimingInfo.BeatLengthAt(firstObjectTime) * 4, Beatmap.BeatmapInfo.AudioLeadIn))); + decoupledClock.Seek(Math.Min(0, firstObjectTime - Math.Max(Beatmap.Beatmap.ControlPointInfo.TimingPointAt(firstObjectTime).BeatLength * 4, Beatmap.BeatmapInfo.AudioLeadIn))); decoupledClock.ProcessFrame(); offsetClock = new FramedOffsetClock(decoupledClock); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 2d6d212130..130642b9c7 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -170,8 +170,8 @@ namespace osu.Game.Screens.Select List visibleGroups = groups.Where(selectGroup => selectGroup.State != BeatmapGroupState.Hidden).ToList(); if (visibleGroups.Count < 1) return; - BeatmapGroup group = visibleGroups[RNG.Next(visibleGroups.Count)]; + BeatmapGroup group = visibleGroups[RNG.Next(visibleGroups.Count)]; BeatmapPanel panel = group.BeatmapPanels[RNG.Next(group.BeatmapPanels.Count)]; selectGroup(group, panel); diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 2f5b35f92a..2d002597ab 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -238,12 +238,12 @@ namespace osu.Game.Screens.Select private string getBPMRange(Beatmap beatmap) { - double bpmMax = beatmap.TimingInfo.BPMMaximum; - double bpmMin = beatmap.TimingInfo.BPMMinimum; + double bpmMax = beatmap.ControlPointInfo.BPMMaximum; + double bpmMin = beatmap.ControlPointInfo.BPMMinimum; if (Precision.AlmostEquals(bpmMin, bpmMax)) return Math.Round(bpmMin) + "bpm"; - return Math.Round(bpmMin) + "-" + Math.Round(bpmMax) + "bpm (mostly " + Math.Round(beatmap.TimingInfo.BPMMode) + "bpm)"; + return Math.Round(bpmMin) + "-" + Math.Round(bpmMax) + "bpm (mostly " + Math.Round(beatmap.ControlPointInfo.BPMMode) + "bpm)"; } public class InfoLabel : Container diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index e9ead7c9c0..41fa53e8a3 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -316,11 +316,12 @@ namespace osu.Game.Screens.Select /// private void selectionChanged(BeatmapInfo beatmap) { - bool beatmapSetChange = false; + selectionChangedDebounce?.Cancel(); if (beatmap.Equals(Beatmap?.BeatmapInfo)) return; + bool beatmapSetChange = false; if (beatmap.BeatmapSetInfoID == selectionChangeNoBounce?.BeatmapSetInfoID) sampleChangeDifficulty.Play(); else @@ -331,7 +332,6 @@ namespace osu.Game.Screens.Select selectionChangeNoBounce = beatmap; - selectionChangedDebounce?.Cancel(); selectionChangedDebounce = Scheduler.AddDelayed(delegate { Beatmap = database.GetWorkingBeatmap(beatmap, Beatmap); diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 1fb2658679..14215403f6 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -87,10 +87,15 @@ + + + + + + - @@ -184,6 +189,7 @@ + @@ -202,7 +208,6 @@ -