// 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 osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.UI { /// /// Ensures that s are hit in-order. /// If a is hit out of order: /// /// The hit is blocked if it occurred earlier than the previous 's start time. /// The hit causes all previous s to missed otherwise. /// /// 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. /// /// The to check. /// The time to check. /// Whether can be hit at the given . public bool IsHittable(DrawableHitObject hitObject, double time) { DrawableHitObject blockingObject = null; // Find the last hitobject which blocks future hits. foreach (var obj in enumerateHitObjectsUpTo(hitObject)) { if (hitObjectCanBlockFutureHits(obj)) blockingObject = obj; } // If there is no previous hitobject, allow the hit. if (blockingObject == null) return true; // A hit is allowed if: // 1. The last blocking hitobject has been judged. // 2. The current time is after the last hitobject's start time. // Hits at exactly the same time as the blocking hitobject are allowed for maps that contain simultaneous hitobjects (e.g. /b/372245). if (blockingObject.Judged || time >= blockingObject.HitObject.StartTime) return true; return false; } /// /// Handles a being hit to potentially miss all earlier s. /// /// The that was hit. public void HandleHit(DrawableHitObject hitObject) { // Hitobjects which themselves don't block future hitobjects don't cause misses (e.g. slider ticks, spinners). if (!hitObjectCanBlockFutureHits(hitObject)) return; foreach (var obj in enumerateHitObjectsUpTo(hitObject)) { if (obj.Judged) continue; if (hitObjectCanBlockFutureHits(obj)) ((DrawableOsuHitObject)obj).MissForcefully(); } } /// /// Whether a blocks hits on future s until its start time is reached. /// /// The to test. private static bool hitObjectCanBlockFutureHits(DrawableHitObject hitObject) => hitObject is DrawableHitCircle; // Todo: Inefficient private IEnumerable enumerateHitObjectsUpTo(DrawableHitObject hitObject) { return enumerate(hitObjectContainer.AliveObjects); IEnumerable enumerate(IEnumerable list) { foreach (var obj in list) { if (obj.HitObject.StartTime >= hitObject.HitObject.StartTime) yield break; yield return obj; foreach (var nested in enumerate(obj.NestedHitObjects)) yield return nested; } } } } }