// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. 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.Bindables; 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.Replays; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; 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; /// /// Place to put drawables above hit objects but below UI. /// public Container Overlays { get; protected set; } /// /// The cursor provided by this . May be null if no cursor is provided. /// public readonly CursorContainer Cursor; public 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); IsPaused.ValueChanged += paused => { if (HasReplayLoaded.Value) return; KeyBindingInputManager.UseParentInput = !paused.NewValue; }; 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 Score ReplayScore { 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 SetReplayScore(Score replayScore) { if (ReplayInputManager == null) throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports replay loading is not available"); ReplayScore = replayScore; ReplayInputManager.ReplayInputHandler = replayScore != null ? CreateReplayInputHandler(replayScore.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 { /// /// Invoked when a has been applied by a . /// public event Action OnNewResult; /// /// Invoked when a is being reverted by a . /// public event Action OnRevertResult; /// /// 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; /// /// 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; applyBeatmapMods(Mods); } [BackgroundDependencyLoader] private void load(OsuConfigManager config) { KeyBindingInputManager.AddRange(new Drawable[] { content = new Container { RelativeSizeAxes = Axes.Both, }, Playfield }); if (Cursor != null) KeyBindingInputManager.Add(Cursor); InternalChildren = new Drawable[] { KeyBindingInputManager, Overlays = new Container { RelativeSizeAxes = Axes.Both } }; // Apply mods applyRulesetMods(Mods, config); loadObjects(); } /// /// Applies the active mods to the Beatmap. /// /// private void applyBeatmapMods(IEnumerable mods) { if (mods == null) return; foreach (var mod in mods.OfType>()) mod.ApplyToBeatmap(Beatmap); } /// /// Applies the active mods to this RulesetContainer. /// /// private void applyRulesetMods(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 SetReplayScore(Score replayScore) { base.SetReplayScore(replayScore); 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) AddRepresentation(h); Playfield.PostProcess(); foreach (var mod in Mods.OfType()) mod.ApplyToDrawableHitObjects(Playfield.HitObjectContainer.Objects); } /// /// Creates and adds the visual representation of a to this . /// /// The to add the visual representation for. internal void AddRepresentation(TObject hitObject) { var drawableObject = GetVisualRepresentation(hitObject); if (drawableObject == null) return; drawableObject.OnNewResult += (_, r) => OnNewResult?.Invoke(r); drawableObject.OnRevertResult += (_, r) => OnRevertResult?.Invoke(r); Playfield.Add(drawableObject); } /// /// Creates a DrawableHitObject from a HitObject. /// /// The HitObject to make drawable. /// The DrawableHitObject. public 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) { } } }