// Copyright (c) 2007-2018 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.Configuration; using osu.Framework.Lists; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Timing; using osu.Game.Rulesets.UI.Scrolling.Algorithms; namespace osu.Game.Rulesets.UI.Scrolling { /// /// A type of that supports a . /// s inside this will scroll within the playfield. /// public abstract class ScrollingRulesetContainer : RulesetContainer where TObject : HitObject where TPlayfield : ScrollingPlayfield { protected readonly Bindable Direction = new Bindable(); protected virtual ScrollAlgorithm ScrollAlgorithm => ScrollAlgorithm.Sequential; /// /// Provides the default s that adjust the scrolling rate of s /// inside this . /// /// private readonly SortedList controlPoints = new SortedList(Comparer.Default); private IScrollingInfo scrollingInfo; [Cached(Type = typeof(IScrollAlgorithm))] private readonly IScrollAlgorithm algorithm; protected ScrollingRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { switch (ScrollAlgorithm) { case ScrollAlgorithm.Sequential: algorithm = new SequentialScrollAlgorithm(controlPoints); break; case ScrollAlgorithm.Overlapping: algorithm = new OverlappingScrollAlgorithm(controlPoints); break; case ScrollAlgorithm.Constant: algorithm = new ConstantScrollAlgorithm(); break; } } [BackgroundDependencyLoader] private void load() { scrollingInfo.Direction.BindTo(Direction); // 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) { Velocity = Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier, 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); controlPoints.AddRange(timingChanges); // If we have no control points, add a default one if (controlPoints.Count == 0) controlPoints.Add(new MultiplierControlPoint { Velocity = Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier }); } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); if ((scrollingInfo = dependencies.Get()) == null) dependencies.CacheAs(scrollingInfo = CreateScrollingInfo()); return dependencies; } protected virtual IScrollingInfo CreateScrollingInfo() => new ScrollingInfo(); private class ScrollingInfo : IScrollingInfo { public IBindable Direction { get; } = new Bindable(); } } }