// 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.Collections.Generic; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mania.UI { /// /// Ensures that only the most recent is hittable, affectionately known as "note lock". /// public class OrderedHitPolicy { private readonly HitObjectContainer hitObjectContainer; public OrderedHitPolicy(HitObjectContainer hitObjectContainer) { this.hitObjectContainer = hitObjectContainer; } /// /// Determines whether a can be hit at a point in time. /// /// /// Only the most recent can be hit, a previous hitobject's window cannot extend past the next one. /// /// The to check. /// The time to check. /// Whether can be hit at the given . public bool IsHittable(DrawableHitObject hitObject, double time) { var nextObject = hitObjectContainer.AliveObjects.GetNext(hitObject); return nextObject == null || time < nextObject.HitObject.StartTime; } /// /// Handles a being hit to potentially miss all earlier s. /// /// The that was hit. public void HandleHit(DrawableHitObject hitObject) { if (!IsHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset)) throw new InvalidOperationException($"A {hitObject} was hit before it became hittable!"); foreach (var obj in enumerateHitObjectsUpTo(hitObject.HitObject.StartTime)) { if (obj.Judged) continue; ((DrawableManiaHitObject)obj).MissForcefully(); } } private IEnumerable enumerateHitObjectsUpTo(double targetTime) { foreach (var obj in hitObjectContainer.AliveObjects) { if (obj.HitObject.GetEndTime() >= targetTime) yield break; yield return obj; foreach (var nestedObj in obj.NestedHitObjects) { if (nestedObj.HitObject.GetEndTime() >= targetTime) break; yield return nestedObj; } } } } }