diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs new file mode 100644 index 0000000000..9a9074ee52 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -0,0 +1,45 @@ +// 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.Graphics; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModAlternate : ModAlternate + { + private const double flash_duration = 1000; + private OsuAction? lastActionPressed; + private DrawableRuleset ruleset; + + public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + ruleset = drawableRuleset; + base.ApplyToDrawableRuleset(drawableRuleset); + } + + protected override void Reset() + { + lastActionPressed = null; + } + + protected override bool OnPressed(OsuAction key) + { + if (lastActionPressed == key) + { + ruleset.Cursor.FlashColour(Colour4.Red, flash_duration, Easing.OutQuint); + return true; + } + + lastActionPressed = key; + + return false; + } + + protected override void OnReleased(OsuAction key) + { + } + } +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 18e4bb259c..7e8974b5ed 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -159,6 +159,7 @@ namespace osu.Game.Rulesets.Osu new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()), new OsuModHidden(), new MultiMod(new OsuModFlashlight(), new OsuModBlinds()), + new OsuModAlternate(), }; case ModType.Conversion: diff --git a/osu.Game/Rulesets/Mods/ModAlternate.cs b/osu.Game/Rulesets/Mods/ModAlternate.cs new file mode 100644 index 0000000000..683654f605 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModAlternate.cs @@ -0,0 +1,77 @@ +// 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 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.Objects; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModAlternate : Mod + { + public override string Name => @"Alternate"; + public override string Acronym => @"AL"; + public override string Description => @"Never hit the same key twice!"; + public override double ScoreMultiplier => 1.0; + public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) }; + public override ModType Type => ModType.DifficultyIncrease; + public override IconUsage? Icon => FontAwesome.Solid.Keyboard; + } + + public abstract class ModAlternate : ModAlternate, IApplicableToDrawableRuleset, IApplicableToPlayer + where THitObject : HitObject + where TAction : struct + { + public bool CanIntercept => !isBreakTime.Value; + + private IBindable isBreakTime; + + public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this)); + } + + public void ApplyToPlayer(Player player) + { + isBreakTime = player.IsBreakTime.GetBoundCopy(); + isBreakTime.ValueChanged += e => + { + if (e.NewValue) + Reset(); + }; + } + + protected abstract void Reset(); + + protected abstract bool OnPressed(TAction key); + + protected abstract void OnReleased(TAction key); + + private class InputInterceptor : Component, IKeyBindingHandler + { + private readonly ModAlternate mod; + + public InputInterceptor(ModAlternate mod) + { + this.mod = mod; + } + + public bool OnPressed(KeyBindingPressEvent e) + { + return mod.CanIntercept && mod.OnPressed(e.Action); + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + if (mod.CanIntercept) + mod.OnReleased(e.Action); + } + } + } +}