// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; 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 lastObject = hitObject; // Get the last hitobject that can block future hits while ((lastObject = hitObjectContainer.AliveObjects.GetPrevious(lastObject)) != null) { if (canBlockFutureHits(lastObject.HitObject)) break; } // If there is no previous object alive, allow the hit. if (lastObject == null) return true; // Ensure that either the last object has received a judgement or the hit time occurs at or after the last object's start time. // Simultaneous hitobjects are allowed to be hit at the same time value to account for edge-cases such as Centipede. if (lastObject.Judged || time >= lastObject.HitObject.StartTime) return true; return false; } /// /// Handles a being hit to potentially miss all earlier s. /// /// The that was hit. public void HandleHit(HitObject hitObject) { if (!canBlockFutureHits(hitObject)) return; double minimumTime = hitObject.StartTime; foreach (var obj in hitObjectContainer.AliveObjects) { if (obj.HitObject.StartTime >= minimumTime) break; switch (obj) { case DrawableHitCircle circle: miss(circle); break; case DrawableSlider slider: miss(slider.HeadCircle); break; } } static void miss(DrawableOsuHitObject obj) { // Hitobjects that have already been judged cannot be missed. if (obj.Judged) return; obj.MissForcefully(); } } /// /// Whether a blocks hits on future s until its start time is reached. /// /// The to test. private bool canBlockFutureHits(HitObject hitObject) => hitObject is HitCircle || hitObject is Slider; } }