diff --git a/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs b/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs index 048506d111..38042a2c3d 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs @@ -31,7 +31,7 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mania.UI { - public class ManiaHitRenderer : HitRenderer + public class ManiaHitRenderer : SpeedAdjustedHitRenderer { /// /// Preferred column count. This will only have an effect during the initialization of the play field. @@ -50,8 +50,6 @@ namespace osu.Game.Rulesets.Mania.UI /// private readonly List barlineTimingChanges = new List(); - private readonly SortedList defaultControlPoints = new SortedList(Comparer.Default); - public ManiaHitRenderer(WorkingBeatmap beatmap, bool isForCurrentRuleset) : base(beatmap, isForCurrentRuleset) { @@ -116,7 +114,7 @@ namespace osu.Game.Rulesets.Mania.UI private void generateDefaultSpeedAdjustments() { - defaultControlPoints.ForEach(c => + DefaultControlPoints.ForEach(c => { foreach (List t in hitObjectTimingChanges) t.Add(new ManiaSpeedAdjustmentContainer(c, ScrollingAlgorithm.Basic)); @@ -124,93 +122,20 @@ namespace osu.Game.Rulesets.Mania.UI }); } - /// - /// Generates a control point at a point in time with the relevant timing change/difficulty change from the beatmap. - /// - /// The time to create the control point at. - /// The at . - public MultiplierControlPoint CreateControlPointAt(double time) - { - if (defaultControlPoints.Count == 0) - return new MultiplierControlPoint(time); - - int index = defaultControlPoints.BinarySearch(new MultiplierControlPoint(time)); - if (index < 0) - return new MultiplierControlPoint(time); - - return new MultiplierControlPoint(time, defaultControlPoints[index].DeepClone()); - } - protected override void ApplyBeatmap() { base.ApplyBeatmap(); PreferredColumns = (int)Math.Round(Beatmap.BeatmapInfo.Difficulty.CircleSize); - - // Calculate default multiplier control points - var lastTimingPoint = new TimingControlPoint(); - var lastDifficultyPoint = new DifficultyControlPoint(); - - // 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 = allPoints.Select(c => - { - var timingPoint = c as TimingControlPoint; - var difficultyPoint = c as DifficultyControlPoint; - - if (timingPoint != null) - lastTimingPoint = timingPoint; - - if (difficultyPoint != null) - lastDifficultyPoint = difficultyPoint; - - return new MultiplierControlPoint(c.Time) - { - TimingPoint = lastTimingPoint, - DifficultyPoint = lastDifficultyPoint - }; - }); - - double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue; - - // Perform some post processing of the timing changes - timingChanges = timingChanges - // Collapse sections after the last hit object - .Where(s => s.StartTime <= lastObjectTime) - // Collapse sections with the same start time - .GroupBy(s => s.StartTime).Select(g => g.Last()).OrderBy(s => s.StartTime) - // Collapse sections with the same beat length - .GroupBy(s => s.TimingPoint.BeatLength * s.DifficultyPoint.SpeedMultiplier).Select(g => g.First()) - .ToList(); - - defaultControlPoints.AddRange(timingChanges); } - protected override Playfield CreatePlayfield() + protected override Playfield CreatePlayfield() => new ManiaPlayfield(PreferredColumns) { - var playfield = new ManiaPlayfield(PreferredColumns) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - // Invert by default for now (should be moved to config/skin later) - Scale = new Vector2(1, -1) - }; - - for (int i = 0; i < PreferredColumns; i++) - { - foreach (var change in hitObjectTimingChanges[i]) - playfield.Columns.ElementAt(i).Add(change); - } - - foreach (var change in barlineTimingChanges) - playfield.Add(change); - - return playfield; - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + // Invert by default for now (should be moved to config/skin later) + Scale = new Vector2(1, -1) + }; public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this); @@ -236,5 +161,21 @@ namespace osu.Game.Rulesets.Mania.UI } protected override Vector2 GetPlayfieldAspectAdjust() => new Vector2(1, 0.8f); + + protected override void ApplySpeedAdjustments() + { + var maniaPlayfield = Playfield as ManiaPlayfield; + if (maniaPlayfield == null) + return; + + for (int i = 0; i < PreferredColumns; i++) + { + foreach (var change in hitObjectTimingChanges[i]) + maniaPlayfield.Columns.ElementAt(i).Add(change); + } + + foreach (var change in barlineTimingChanges) + maniaPlayfield.Add(change); + } } } diff --git a/osu.Game/Rulesets/UI/HitRenderer.cs b/osu.Game/Rulesets/UI/HitRenderer.cs index 056ee2044f..ea5f912b02 100644 --- a/osu.Game/Rulesets/UI/HitRenderer.cs +++ b/osu.Game/Rulesets/UI/HitRenderer.cs @@ -250,13 +250,16 @@ namespace osu.Game.Rulesets.UI { KeyConversionInputManager.Add(Playfield = CreatePlayfield()); - loadObjects(); + LoadObjects(); if (InputManager?.ReplayInputHandler != null) InputManager.ReplayInputHandler.ToScreenSpace = Playfield.ScaledContent.ToScreenSpace; } - private void loadObjects() + /// + /// Creates and adds drawable representations of hit objects to the play field. + /// + protected virtual void LoadObjects() { drawableObjects.Capacity = Beatmap.HitObjects.Count; diff --git a/osu.Game/Rulesets/UI/SpeedAdjustedHitRenderer.cs b/osu.Game/Rulesets/UI/SpeedAdjustedHitRenderer.cs new file mode 100644 index 0000000000..ced109fba4 --- /dev/null +++ b/osu.Game/Rulesets/UI/SpeedAdjustedHitRenderer.cs @@ -0,0 +1,99 @@ +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Lists; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.IO.Serialization; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Timing; + +namespace osu.Game.Rulesets.UI +{ + /// + /// A type of that supports speed adjustments in some capacity. + /// + public abstract class SpeedAdjustedHitRenderer : HitRenderer + where TObject : HitObject + where TJudgement : Judgement + { + protected readonly SortedList DefaultControlPoints = new SortedList(Comparer.Default); + + public SpeedAdjustedHitRenderer(WorkingBeatmap beatmap, bool isForCurrentRuleset) + : base(beatmap, isForCurrentRuleset) + { + } + + protected override void ApplyBeatmap() + { + // Calculate default multiplier control points + var lastTimingPoint = new TimingControlPoint(); + var lastDifficultyPoint = new DifficultyControlPoint(); + + // 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 = allPoints.Select(c => + { + var timingPoint = c as TimingControlPoint; + var difficultyPoint = c as DifficultyControlPoint; + + if (timingPoint != null) + lastTimingPoint = timingPoint; + + if (difficultyPoint != null) + lastDifficultyPoint = difficultyPoint; + + return new MultiplierControlPoint(c.Time) + { + TimingPoint = lastTimingPoint, + DifficultyPoint = lastDifficultyPoint + }; + }); + + double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue; + + // Perform some post processing of the timing changes + timingChanges = timingChanges + // Collapse sections after the last hit object + .Where(s => s.StartTime <= lastObjectTime) + // Collapse sections with the same start time + .GroupBy(s => s.StartTime).Select(g => g.Last()).OrderBy(s => s.StartTime) + // Collapse sections with the same beat length + .GroupBy(s => s.TimingPoint.BeatLength * s.DifficultyPoint.SpeedMultiplier).Select(g => g.First()); + + DefaultControlPoints.AddRange(timingChanges); + } + + /// + /// Generates a control point with the default timing change/difficulty change from the beatmap at a time. + /// + /// The time to create the control point at. + /// The at . + public MultiplierControlPoint CreateControlPointAt(double time) + { + if (DefaultControlPoints.Count == 0) + return new MultiplierControlPoint(time); + + int index = DefaultControlPoints.BinarySearch(new MultiplierControlPoint(time)); + if (index < 0) + return new MultiplierControlPoint(time); + + return new MultiplierControlPoint(time, DefaultControlPoints[index].DeepClone()); + } + + protected override void LoadObjects() + { + // We need to add speed adjustments before hit objects are loaded + ApplySpeedAdjustments(); + + base.LoadObjects(); + } + + protected abstract void ApplySpeedAdjustments(); + } +} \ No newline at end of file diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index f91810d346..36f358801e 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -314,6 +314,7 @@ +