// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Input.Bindings; using osu.Game.Audio; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { public abstract class DrawableTaikoHitObject : DrawableHitObject, IKeyBindingHandler { protected readonly Container Content; private readonly Container proxiedContent; private readonly Container nonProxiedContent; protected DrawableTaikoHitObject(TaikoHitObject hitObject) : base(hitObject) { AddRangeInternal(new[] { nonProxiedContent = new Container { RelativeSizeAxes = Axes.Both, Child = Content = new Container { RelativeSizeAxes = Axes.Both } }, proxiedContent = new ProxiedContentContainer { RelativeSizeAxes = Axes.Both } }); } /// /// is proxied into an upper layer. We don't want to get masked away otherwise would too. /// protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false; private bool isProxied; /// /// Moves to a layer proxied above the playfield. /// Does nothing if content is already proxied. /// protected void ProxyContent() { if (isProxied) return; isProxied = true; nonProxiedContent.Remove(Content); proxiedContent.Add(Content); } /// /// Moves to the normal hitobject layer. /// Does nothing is content is not currently proxied. /// protected void UnproxyContent() { if (!isProxied) return; isProxied = false; proxiedContent.Remove(Content); nonProxiedContent.Add(Content); } /// /// Creates a proxy for the content of this . /// public Drawable CreateProxiedContent() => proxiedContent.CreateProxy(); public abstract bool OnPressed(TaikoAction action); public virtual void OnReleased(TaikoAction action) { } public override double LifetimeStart { get => base.LifetimeStart; set { base.LifetimeStart = value; proxiedContent.LifetimeStart = value; } } public override double LifetimeEnd { get => base.LifetimeEnd; set { base.LifetimeEnd = value; proxiedContent.LifetimeEnd = value; } } private class ProxiedContentContainer : Container { public override bool RemoveWhenNotAlive => false; } } public abstract class DrawableTaikoHitObject : DrawableTaikoHitObject where TObject : TaikoHitObject { public override Vector2 OriginPosition => new Vector2(DrawHeight / 2); public new TObject HitObject; protected Vector2 BaseSize; protected SkinnableDrawable MainPiece; private readonly Bindable isStrong; private readonly Container strongHitContainer; protected DrawableTaikoHitObject(TObject hitObject) : base(hitObject) { HitObject = hitObject; isStrong = HitObject.IsStrongBindable.GetBoundCopy(); Anchor = Anchor.CentreLeft; Origin = Anchor.Custom; RelativeSizeAxes = Axes.Both; AddInternal(strongHitContainer = new Container()); } [BackgroundDependencyLoader] private void load() { isStrong.BindValueChanged(_ => { // will overwrite samples, should only be called on change. updateSamplesFromStrong(); RecreatePieces(); }); RecreatePieces(); } private HitSampleInfo[] getStrongSamples() => HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_FINISH).ToArray(); protected override void LoadSamples() { base.LoadSamples(); if (HitObject.CanBeStrong) isStrong.Value = getStrongSamples().Any(); } private void updateSamplesFromStrong() { var strongSamples = getStrongSamples(); if (isStrong.Value != strongSamples.Any()) { if (isStrong.Value) HitObject.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_FINISH }); else { foreach (var sample in strongSamples) HitObject.Samples.Remove(sample); } } } protected virtual void RecreatePieces() { Size = BaseSize = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE); MainPiece?.Expire(); Content.Add(MainPiece = CreateMainPiece()); } protected override void AddNestedHitObject(DrawableHitObject hitObject) { base.AddNestedHitObject(hitObject); switch (hitObject) { case DrawableStrongNestedHit strong: strongHitContainer.Add(strong); break; } } protected override void ClearNestedHitObjects() { base.ClearNestedHitObjects(); strongHitContainer.Clear(); } protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject) { switch (hitObject) { case StrongHitObject strong: return CreateStrongHit(strong); } return base.CreateNestedHitObject(hitObject); } // Most osu!taiko hitsounds are managed by the drum (see DrumSampleMapping). public override IEnumerable GetSamples() => Enumerable.Empty(); protected abstract SkinnableDrawable CreateMainPiece(); /// /// Creates the handler for this 's . /// This is only invoked if is true for . /// /// The strong hitobject. /// The strong hitobject handler. protected virtual DrawableStrongNestedHit CreateStrongHit(StrongHitObject hitObject) => null; } }