// Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Game.Rulesets.Judgements; using Container = osu.Framework.Graphics.Containers.Container; using osu.Game.Rulesets.Objects.Types; using OpenTK.Graphics; using osu.Game.Audio; using System.Linq; using osu.Game.Graphics; namespace osu.Game.Rulesets.Objects.Drawables { public abstract class DrawableHitObject : Container, IHasAccentColour { public readonly HitObject HitObject; /// /// The colour used for various elements of this DrawableHitObject. /// public virtual Color4 AccentColour { get; set; } = Color4.Gray; protected DrawableHitObject(HitObject hitObject) { HitObject = hitObject; } } public abstract class DrawableHitObject : DrawableHitObject where TObject : HitObject { public event Action OnJudgement; public new readonly TObject HitObject; public override bool HandleInput => Interactive; public bool Interactive = true; /// /// Whether this can be judged. /// protected virtual bool ProvidesJudgement => true; private readonly List judgements = new List(); public IReadOnlyList Judgements => judgements; protected List Samples = new List(); protected DrawableHitObject(TObject hitObject) : base(hitObject) { HitObject = hitObject; Depth = (float)hitObject.StartTime; } private ArmedState state; public ArmedState State { get { return state; } set { if (state == value) return; state = value; if (!IsLoaded) return; UpdateState(state); if (State == ArmedState.Hit) PlaySamples(); } } protected void PlaySamples() { Samples.ForEach(s => s?.Play()); } protected override void LoadComplete() { base.LoadComplete(); //force application of the state that was set before we loaded. UpdateState(State); } private bool hasJudgementResult; private bool judgementOccurred; /// /// Whether this and all of its nested s have been judged. /// public virtual bool AllJudged => (!ProvidesJudgement || hasJudgementResult) && (NestedHitObjects?.All(h => h.AllJudged) ?? true); /// /// Notifies that a new judgement has occurred for this . /// /// The . protected void AddJudgement(Judgement judgement) { hasJudgementResult = judgement.Result >= HitResult.Miss; judgementOccurred = true; // Ensure that the judgement is given a valid time offset, because this may not get set by the caller var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; judgement.TimeOffset = Time.Current - endTime; judgements.Add(judgement); switch (judgement.Result) { case HitResult.None: break; case HitResult.Miss: State = ArmedState.Miss; break; default: State = ArmedState.Hit; break; } OnJudgement?.Invoke(this, judgement); } /// /// Processes this , checking if any judgements have occurred. /// /// Whether the user triggered this process. /// Whether a judgement has occurred from this or any nested s. protected bool UpdateJudgement(bool userTriggered) { judgementOccurred = false; if (AllJudged || State != ArmedState.Idle) return false; if (NestedHitObjects != null) { foreach (var d in NestedHitObjects) { if (d.AllJudged) continue; d.UpdateJudgement(userTriggered); if (d.AllJudged) return true; } } var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; CheckForJudgements(userTriggered, Time.Current - endTime); return judgementOccurred; } /// /// Checks if any judgements have occurred for this . This method must construct /// all s and notify of them through . /// /// Whether the user triggered this check. /// The offset from the end time at which this check occurred. A > 0 /// implies that this check occurred after the end time of . protected virtual void CheckForJudgements(bool userTriggered, double timeOffset) { } protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); UpdateJudgement(false); } [BackgroundDependencyLoader] private void load(AudioManager audio) { foreach (SampleInfo sample in HitObject.Samples) { SampleChannel channel = audio.Sample.Get($@"Gameplay/{sample.Bank}-{sample.Name}"); if (channel == null) continue; channel.Volume.Value = sample.Volume; Samples.Add(channel); } } private List> nestedHitObjects; protected IEnumerable> NestedHitObjects => nestedHitObjects; protected virtual void AddNested(DrawableHitObject h) { if (nestedHitObjects == null) nestedHitObjects = new List>(); h.OnJudgement += (d, j) => OnJudgement?.Invoke(d, j); nestedHitObjects.Add(h); } protected abstract void UpdateState(ArmedState state); } }