// 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; } private readonly Lazy playfield; /// /// The playfield. /// public Playfield Playfield => playfield.Value; /// /// The cursor provided by this . May be null if no cursor is provided. /// public readonly CursorContainer Cursor; protected readonly Ruleset Ruleset; private IRulesetConfigManager rulesetConfig; private OnScreenDisplay onScreenDisplay; private DependencyContainer dependencies; protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); /// /// A visual representation of a . /// /// The ruleset being repesented. protected RulesetContainer(Ruleset ruleset) { Ruleset = ruleset; playfield = new Lazy(CreatePlayfield); Cursor = CreateCursor(); } [BackgroundDependencyLoader(true)] private void load(OnScreenDisplay onScreenDisplay, SettingsStore settings) { this.onScreenDisplay = onScreenDisplay; rulesetConfig = CreateConfig(Ruleset, settings); if (rulesetConfig != null) { dependencies.Cache(rulesetConfig); onScreenDisplay?.BeginTracking(this, rulesetConfig); } } 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; } /// /// 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; protected virtual IRulesetConfigManager CreateConfig(Ruleset ruleset, SettingsStore settings) => null; /// /// Creates a Playfield. /// /// The Playfield. protected abstract Playfield CreatePlayfield(); protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); if (rulesetConfig != null) { onScreenDisplay?.StopTracking(this, rulesetConfig); rulesetConfig = 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; /// /// Whether the specified beatmap is assumed to be specific to the current ruleset. /// public readonly bool IsForCurrentRuleset; 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. /// Whether to assume the beatmap is for the current ruleset. protected RulesetContainer(Ruleset ruleset, WorkingBeatmap workingBeatmap, bool isForCurrentRuleset) : base(ruleset) { Debug.Assert(workingBeatmap != null, "RulesetContainer initialized with a null beatmap."); WorkingBeatmap = workingBeatmap; IsForCurrentRuleset = isForCurrentRuleset; Mods = workingBeatmap.Mods.Value; RelativeSizeAxes = Axes.Both; BeatmapConverter converter = CreateBeatmapConverter(); BeatmapProcessor processor = CreateBeatmapProcessor(); // Check if the beatmap can be converted if (!converter.CanConvert(workingBeatmap.Beatmap)) throw new BeatmapInvalidForRulesetException($"{nameof(Beatmap)} can not be converted for the current ruleset (converter: {converter})."); // Apply conversion adjustments before converting foreach (var mod in Mods.OfType>()) mod.ApplyToBeatmapConverter(converter); // Convert the beatmap Beatmap = converter.Convert(workingBeatmap.Beatmap); // Apply difficulty adjustments from mods before using Difficulty. foreach (var mod in Mods.OfType()) mod.ApplyToDifficulty(Beatmap.BeatmapInfo.BaseDifficulty); // Post-process the beatmap processor.PostProcess(Beatmap); // Apply defaults foreach (var h in Beatmap.HitObjects) h.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.BaseDifficulty); KeyBindingInputManager = CreateInputManager(); KeyBindingInputManager.RelativeSizeAxes = Axes.Both; // Add mods, should always be the last thing applied to give full control to mods applyMods(Mods); } [BackgroundDependencyLoader] private void load() { KeyBindingInputManager.Add(content = new Container { RelativeSizeAxes = Axes.Both, }); AddInternal(KeyBindingInputManager); KeyBindingInputManager.Add(Playfield); if (Cursor != null) KeyBindingInputManager.Add(Cursor); loadObjects(); } /// /// Applies the active mods to this RulesetContainer. /// /// private void applyMods(IEnumerable mods) { if (mods == null) return; foreach (var mod in mods.OfType>()) foreach (var obj in Beatmap.HitObjects) mod.ApplyToHitObject(obj); foreach (var mod in mods.OfType>()) mod.ApplyToRulesetContainer(this); } public override void SetReplay(Replay replay) { base.SetReplay(replay); if (ReplayInputManager?.ReplayInputHandler != null) ReplayInputManager.ReplayInputHandler.ToScreenSpace = input => Playfield.ScaledContent.ToScreenSpace(input); } /// /// 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; } /// /// Creates a processor to perform post-processing operations /// on HitObjects in converted Beatmaps. /// /// The Beatmap processor. protected virtual BeatmapProcessor CreateBeatmapProcessor() => new BeatmapProcessor(); /// /// 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 converter to convert Beatmap to a specific mode. /// /// The Beatmap converter. protected abstract BeatmapConverter CreateBeatmapConverter(); /// /// 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. /// Whether to assume the beatmap is for the current ruleset. protected RulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset) : base(ruleset, beatmap, isForCurrentRuleset) { } } public class BeatmapInvalidForRulesetException : ArgumentException { public BeatmapInvalidForRulesetException(string text) : base(text) { } } }