// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using osu.Game.Utils; namespace osu.Game.Rulesets.Osu.Mods { public abstract class InputBlockingMod : Mod, IApplicableToDrawableRuleset { public override double ScoreMultiplier => 1.0; public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax) }; public override ModType Type => ModType.Conversion; protected const double FLASH_DURATION = 1000; /// /// A tracker for periods where alternate should not be forced (i.e. non-gameplay periods). /// /// /// This is different from in that the periods here end strictly at the first object after the break, rather than the break's end time. /// protected PeriodTracker NonGameplayPeriods; protected OsuAction? LastActionPressed; protected DrawableRuleset Ruleset; protected IFrameStableClock GameplayClock; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { Ruleset = drawableRuleset; drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this)); var periods = new List(); if (drawableRuleset.Objects.Any()) { periods.Add(new Period(int.MinValue, getValidJudgementTime(Ruleset.Objects.First()) - 1)); foreach (BreakPeriod b in drawableRuleset.Beatmap.Breaks) periods.Add(new Period(b.StartTime, getValidJudgementTime(Ruleset.Objects.First(h => h.StartTime >= b.EndTime)) - 1)); static double getValidJudgementTime(HitObject hitObject) => hitObject.StartTime - hitObject.HitWindows.WindowFor(HitResult.Meh); } NonGameplayPeriods = new PeriodTracker(periods); GameplayClock = drawableRuleset.FrameStableClock; } protected virtual bool CheckCorrectAction(OsuAction action) { if (NonGameplayPeriods.IsInAny(GameplayClock.CurrentTime)) { LastActionPressed = null; return true; } switch (action) { case OsuAction.LeftButton: case OsuAction.RightButton: break; // Any action which is not left or right button should be ignored. default: return true; } return false; } private class InputInterceptor : Component, IKeyBindingHandler { private readonly InputBlockingMod mod; public InputInterceptor(InputBlockingMod mod) { this.mod = mod; } public bool OnPressed(KeyBindingPressEvent e) // if the pressed action is incorrect, block it from reaching gameplay. => !mod.CheckCorrectAction(e.Action); public void OnReleased(KeyBindingReleaseEvent e) { } } } }