// 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.Input; 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 { /// /// Whether to apply adjustments to the child based on our own size. /// public bool AspectAdjust = true; /// /// The input manager for this RulesetContainer. /// internal IHasReplayHandler ReplayInputManager => KeyBindingInputManager as IHasReplayHandler; /// /// The key conversion input manager for this RulesetContainer. /// public PassThroughInputManager KeyBindingInputManager; /// /// Whether we are currently providing the local user a gameplay cursor. /// public virtual bool ProvidingUserCursor => false; /// /// Whether we have a replay loaded currently. /// public bool HasReplayLoaded => ReplayInputManager?.ReplayInputHandler != null; public abstract IEnumerable Objects { get; } private readonly Lazy playfield; /// /// The playfield. /// public Playfield Playfield => playfield.Value; protected readonly Ruleset Ruleset; /// /// A visual representation of a . /// /// The ruleset being repesented. protected RulesetContainer(Ruleset ruleset) { Ruleset = ruleset; playfield = new Lazy(CreatePlayfield); } 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 FramedReplayInputHandler 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; } /// /// Creates a Playfield. /// /// The Playfield. protected abstract Playfield CreatePlayfield(); } /// /// 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. /// protected readonly bool IsForCurrentRuleset; public sealed override bool ProvidingUserCursor => !HasReplayLoaded && Playfield.ProvidingUserCursor; 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); // Apply defaults foreach (var h in Beatmap.HitObjects) h.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.BaseDifficulty); // Post-process the beatmap processor.PostProcess(Beatmap); 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); 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) => { Playfield.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 = AspectAdjust ? GetPlayfieldAspectAdjust() : Vector2.One; } /// /// Creates a processor to perform post-processing operations /// on HitObjects in converted Beatmaps. /// /// The Beatmap processor. protected virtual BeatmapProcessor CreateBeatmapProcessor() => new BeatmapProcessor(); /// /// In some cases we want to apply changes to the relative size of our contained based on custom conditions. /// /// protected virtual Vector2 GetPlayfieldAspectAdjust() => 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) { } } }