// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit; namespace osu.Game.Rulesets.Edit { /// /// A wrapper for a . Handles adding visual representations of s to the underlying . /// internal partial class DrawableEditorRulesetWrapper : CompositeDrawable where TObject : HitObject { public Playfield Playfield => drawableRuleset.Playfield; private readonly DrawableRuleset drawableRuleset; [Resolved] private EditorBeatmap beatmap { get; set; } = null!; [Resolved] private EditorClock editorClock { get; set; } = null!; public DrawableEditorRulesetWrapper(DrawableRuleset drawableRuleset) { this.drawableRuleset = drawableRuleset; RelativeSizeAxes = Axes.Both; InternalChild = drawableRuleset; } [BackgroundDependencyLoader] private void load() { Playfield.DisplayJudgements.Value = false; } [Resolved] private IEditorChangeHandler? changeHandler { get; set; } protected override void LoadComplete() { base.LoadComplete(); beatmap.HitObjectAdded += addHitObject; beatmap.HitObjectRemoved += removeHitObject; if (changeHandler != null) { // for now only regenerate replay on a finalised state change, not HitObjectUpdated. changeHandler.OnStateChange += () => Scheduler.AddOnce(regenerateAutoplay); } else { beatmap.HitObjectUpdated += _ => Scheduler.AddOnce(regenerateAutoplay); } Scheduler.AddOnce(regenerateAutoplay); } protected override void Update() { base.Update(); // Whenever possible, we want to stay in frame stability playback. // Without doing so, we run into bugs with some gameplay elements not behaving as expected. // // Note that this is not using EditorClock.IsSeeking as that would exit frame stability // on all seeks. The intention here is to retain frame stability for small seeks. // // I still think no gameplay elements should require frame stability in the first place, but maybe that ship has sailed already.. bool shouldBypassFrameStability = Math.Abs(drawableRuleset.FrameStableClock.CurrentTime - editorClock.CurrentTime) > 1000; drawableRuleset.FrameStablePlayback = !shouldBypassFrameStability; } private void regenerateAutoplay() { var autoplayMod = drawableRuleset.Mods.OfType().Single(); drawableRuleset.SetReplayScore(autoplayMod.CreateScoreFromReplayData(drawableRuleset.Beatmap, drawableRuleset.Mods)); } private void addHitObject(HitObject hitObject) { drawableRuleset.AddHitObject((TObject)hitObject); drawableRuleset.Playfield.PostProcess(); } private void removeHitObject(HitObject hitObject) { drawableRuleset.RemoveHitObject((TObject)hitObject); drawableRuleset.Playfield.PostProcess(); } public override bool PropagatePositionalInputSubTree => false; public override bool PropagateNonPositionalInputSubTree => false; public PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => drawableRuleset.CreatePlayfieldAdjustmentContainer(); protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); if (beatmap.IsNotNull()) { beatmap.HitObjectAdded -= addHitObject; beatmap.HitObjectRemoved -= removeHitObject; } } } }