// 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.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; 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 a . /// s inside this will scroll within the playfield. /// public abstract class ScrollingHitRenderer : HitRenderer where TObject : HitObject where TJudgement : Judgement where TPlayfield : ScrollingPlayfield { /// /// Provides the default s that adjust the scrolling rate of s /// inside this . /// /// protected readonly SortedList DefaultControlPoints = new SortedList(Comparer.Default); protected ScrollingHitRenderer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset) : base(ruleset, beatmap, isForCurrentRuleset) { } [BackgroundDependencyLoader] private void load() { DefaultControlPoints.ForEach(c => applySpeedAdjustment(c, Playfield)); } private void applySpeedAdjustment(MultiplierControlPoint controlPoint, ScrollingPlayfield playfield) { playfield.HitObjects.AddSpeedAdjustment(CreateSpeedAdjustmentContainer(controlPoint)); playfield.NestedPlayfields.ForEach(p => applySpeedAdjustment(controlPoint, p)); } 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); // If we have no control points, add a default one if (DefaultControlPoints.Count == 0) DefaultControlPoints.Add(new MultiplierControlPoint()); } /// /// Generates a with the default timing change/difficulty change from the beatmap at a time. /// /// The time to create the control point at. /// The default 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()); } /// /// Creates a that facilitates the movement of hit objects. /// /// The that provides the speed adjustments for the hitobjects. /// The . protected virtual SpeedAdjustmentContainer CreateSpeedAdjustmentContainer(MultiplierControlPoint controlPoint) => new SpeedAdjustmentContainer(controlPoint); } }