// 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.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; namespace osu.Game.Rulesets.Osu.Mods { public class OsuModAlternate : Mod, IApplicableToDrawableRuleset, IApplicableToPlayer { public override string Name => @"Alternate"; public override string Acronym => @"AL"; public override string Description => @"Don't use the same key twice in a row!"; public override double ScoreMultiplier => 1.0; public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) }; public override ModType Type => ModType.Conversion; public override IconUsage? Icon => FontAwesome.Solid.Keyboard; private double firstObjectValidJudgementTime; private IBindable isBreakTime; private const double flash_duration = 1000; private OsuAction? lastActionPressed; private DrawableRuleset ruleset; private IFrameStableClock gameplayClock; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { ruleset = drawableRuleset; drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this)); var firstHitObject = ruleset.Objects.FirstOrDefault(); firstObjectValidJudgementTime = (firstHitObject?.StartTime ?? 0) - (firstHitObject?.HitWindows.WindowFor(HitResult.Meh) ?? 0); gameplayClock = drawableRuleset.FrameStableClock; } public void ApplyToPlayer(Player player) { isBreakTime = player.IsBreakTime.GetBoundCopy(); isBreakTime.ValueChanged += e => { if (e.NewValue) lastActionPressed = null; }; } private bool checkCorrectAction(OsuAction action) { if (isBreakTime.Value) return true; if (gameplayClock.CurrentTime < firstObjectValidJudgementTime) 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; } if (lastActionPressed != action) { // User alternated correctly. lastActionPressed = action; return true; } ruleset.Cursor.FlashColour(Colour4.Red, flash_duration, Easing.OutQuint); return false; } private class InputInterceptor : Component, IKeyBindingHandler { private readonly OsuModAlternate mod; public InputInterceptor(OsuModAlternate 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) { } } } }