// Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Configuration; using osu.Framework.Graphics.Cursor; using osu.Framework.Input; using osu.Game.Configuration; using osu.Game.Input.Handlers; using osu.Game.Overlays; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using OpenTK; namespace osu.Game.Rulesets.UI { /// /// Base RulesetContainer. Doesn't hold objects. /// /// Should not be derived - derive instead. /// /// public abstract class RulesetContainer : Container { /// /// The selected variant. /// public virtual int Variant => 0; /// /// The input manager for this RulesetContainer. /// internal IHasReplayHandler ReplayInputManager => KeyBindingInputManager as IHasReplayHandler; /// /// The key conversion input manager for this RulesetContainer. /// public PassThroughInputManager KeyBindingInputManager; /// /// Whether a replay is currently loaded. /// public readonly BindableBool HasReplayLoaded = new BindableBool(); public abstract IEnumerable Objects { get; } /// /// The point in time at which gameplay starts, including any required lead-in for display purposes. /// Defaults to two seconds before the first . Override as necessary. /// public virtual double GameplayStartTime => Objects.First().StartTime - 2000; private readonly Lazy playfield; /// /// The playfield. /// public Playfield Playfield => playfield.Value; private readonly Container overlays; /// /// Place to put drawables above hit objects but below UI. /// public Container Overlays => overlays; /// /// The cursor provided by this . May be null if no cursor is provided. /// public readonly CursorContainer Cursor; protected readonly Ruleset Ruleset; protected IRulesetConfigManager Config { get; private set; } private OnScreenDisplay onScreenDisplay; /// /// A visual representation of a . /// /// The ruleset being repesented. protected RulesetContainer(Ruleset ruleset) { Ruleset = ruleset; playfield = new Lazy(CreatePlayfield); overlays = new Container { RelativeSizeAxes = Axes.Both, Width = 1, Height = 1 }; IsPaused.ValueChanged += paused => { if (HasReplayLoaded) return; KeyBindingInputManager.UseParentInput = !paused; }; Cursor = CreateCursor(); } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); onScreenDisplay = dependencies.Get(); Config = dependencies.Get().GetConfigFor(Ruleset); if (Config != null) { dependencies.Cache(Config); onScreenDisplay?.BeginTracking(this, Config); } return dependencies; } public abstract ScoreProcessor CreateScoreProcessor(); /// /// Creates a key conversion input manager. An exception will be thrown if a valid is not returned. /// /// The input manager. public abstract PassThroughInputManager CreateInputManager(); protected virtual ReplayInputHandler CreateReplayInputHandler(Replay replay) => null; public Replay Replay { get; private set; } /// /// Whether the game is paused. Used to block user input. /// public readonly BindableBool IsPaused = new BindableBool(); /// /// Sets a replay to be used, overriding local input. /// /// The replay, null for local input. public virtual void SetReplay(Replay replay) { if (ReplayInputManager == null) throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports replay loading is not available"); Replay = replay; ReplayInputManager.ReplayInputHandler = replay != null ? CreateReplayInputHandler(replay) : null; HasReplayLoaded.Value = ReplayInputManager.ReplayInputHandler != null; } /// /// Creates the cursor. May be null if the doesn't provide a custom cursor. /// protected virtual CursorContainer CreateCursor() => null; /// /// Creates a Playfield. /// /// The Playfield. protected abstract Playfield CreatePlayfield(); protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); if (Config != null) { onScreenDisplay?.StopTracking(this, Config); Config = null; } } } /// /// RulesetContainer that applies conversion to Beatmaps. Does not contain a Playfield /// and does not load drawable hit objects. /// /// Should not be derived - derive instead. /// /// /// The type of HitObject contained by this RulesetContainer. public abstract class RulesetContainer : RulesetContainer where TObject : HitObject { public event Action OnJudgement; public event Action OnJudgementRemoved; /// /// The Beatmap /// public Beatmap Beatmap; /// /// All the converted hit objects contained by this hit renderer. /// public override IEnumerable Objects => Beatmap.HitObjects; /// /// The mods which are to be applied. /// protected IEnumerable Mods; /// /// The this was created with. /// protected readonly WorkingBeatmap WorkingBeatmap; public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(this); protected override Container Content => content; private Container content; private IEnumerable mods; /// /// Whether to assume the beatmap passed into this is for the current ruleset. /// Creates a hit renderer for a beatmap. /// /// The ruleset being repesented. /// The beatmap to create the hit renderer for. protected RulesetContainer(Ruleset ruleset, WorkingBeatmap workingBeatmap) : base(ruleset) { Debug.Assert(workingBeatmap != null, "RulesetContainer initialized with a null beatmap."); WorkingBeatmap = workingBeatmap; // ReSharper disable once PossibleNullReferenceException Mods = workingBeatmap.Mods.Value; RelativeSizeAxes = Axes.Both; Beatmap = (Beatmap)workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo); KeyBindingInputManager = CreateInputManager(); KeyBindingInputManager.RelativeSizeAxes = Axes.Both; } [BackgroundDependencyLoader] private void load(OsuConfigManager config) { KeyBindingInputManager.Add(content = new Container { RelativeSizeAxes = Axes.Both, }); AddInternal(KeyBindingInputManager); KeyBindingInputManager.Add(Playfield); if (Cursor != null) KeyBindingInputManager.Add(Cursor); // Apply mods applyMods(Mods, config); loadObjects(); } /// /// Applies the active mods to this RulesetContainer. /// /// private void applyMods(IEnumerable mods, OsuConfigManager config) { if (mods == null) return; foreach (var mod in mods.OfType>()) mod.ApplyToRulesetContainer(this); foreach (var mod in mods.OfType()) mod.ReadFromConfig(config); } public override void SetReplay(Replay replay) { base.SetReplay(replay); if (ReplayInputManager?.ReplayInputHandler != null) ReplayInputManager.ReplayInputHandler.GamefieldToScreenSpace = Playfield.GamefieldToScreenSpace; } /// /// Creates and adds drawable representations of hit objects to the play field. /// private void loadObjects() { foreach (TObject h in Beatmap.HitObjects) { var drawableObject = GetVisualRepresentation(h); if (drawableObject == null) continue; drawableObject.OnJudgement += (d, j) => OnJudgement?.Invoke(j); drawableObject.OnJudgementRemoved += (d, j) => OnJudgementRemoved?.Invoke(j); Playfield.Add(drawableObject); } Playfield.PostProcess(); foreach (var mod in Mods.OfType()) mod.ApplyToDrawableHitObjects(Playfield.HitObjects.Objects); } protected override void Update() { base.Update(); Playfield.Size = GetAspectAdjustedSize() * PlayfieldArea; } /// /// Computes the size of the in relative coordinate space after aspect adjustments. /// /// The aspect-adjusted size. protected virtual Vector2 GetAspectAdjustedSize() => Vector2.One; /// /// The area of this that is available for the to use. /// Must be specified in relative coordinate space to this . /// This affects the final size of the but does not affect the 's scale. /// protected virtual Vector2 PlayfieldArea => new Vector2(0.75f); // A sane default /// /// Creates a DrawableHitObject from a HitObject. /// /// The HitObject to make drawable. /// The DrawableHitObject. protected abstract DrawableHitObject GetVisualRepresentation(TObject h); } /// /// A derivable RulesetContainer that manages the Playfield and HitObjects. /// /// The type of Playfield contained by this RulesetContainer. /// The type of HitObject contained by this RulesetContainer. public abstract class RulesetContainer : RulesetContainer where TObject : HitObject where TPlayfield : Playfield { /// /// The playfield. /// protected new TPlayfield Playfield => (TPlayfield)base.Playfield; /// /// Creates a hit renderer for a beatmap. /// /// The ruleset being repesented. /// The beatmap to create the hit renderer for. protected RulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { } } public class BeatmapInvalidForRulesetException : ArgumentException { public BeatmapInvalidForRulesetException(string text) : base(text) { } } }