2022-01-29 20:38:12 +08:00
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
2022-06-17 15:37:17 +08:00
#nullable disable
2022-01-29 21:49:40 +08:00
using System ;
2022-04-29 10:26:56 +08:00
using System.Collections.Generic ;
2022-01-29 21:49:40 +08:00
using System.Linq ;
2022-01-29 20:38:12 +08:00
using osu.Framework.Graphics ;
2022-01-29 21:49:40 +08:00
using osu.Framework.Graphics.Sprites ;
using osu.Framework.Input.Bindings ;
using osu.Framework.Input.Events ;
2022-04-29 10:26:56 +08:00
using osu.Game.Beatmaps.Timing ;
2022-01-29 20:38:12 +08:00
using osu.Game.Rulesets.Mods ;
2022-04-29 10:26:56 +08:00
using osu.Game.Rulesets.Objects ;
2022-01-29 20:38:12 +08:00
using osu.Game.Rulesets.Osu.Objects ;
2022-01-29 21:49:40 +08:00
using osu.Game.Rulesets.Scoring ;
2022-01-29 20:38:12 +08:00
using osu.Game.Rulesets.UI ;
2022-01-29 21:49:40 +08:00
using osu.Game.Screens.Play ;
2022-04-29 10:26:56 +08:00
using osu.Game.Utils ;
2022-01-29 20:38:12 +08:00
namespace osu.Game.Rulesets.Osu.Mods
{
2022-04-29 10:26:56 +08:00
public class OsuModAlternate : Mod , IApplicableToDrawableRuleset < OsuHitObject >
2022-01-29 20:38:12 +08:00
{
2022-01-29 21:49:40 +08:00
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 ;
2022-05-05 05:25:34 +08:00
public override Type [ ] IncompatibleMods = > new [ ] { typeof ( ModAutoplay ) , typeof ( ModRelax ) } ;
2022-02-02 12:58:13 +08:00
public override ModType Type = > ModType . Conversion ;
2022-01-29 21:49:40 +08:00
public override IconUsage ? Icon = > FontAwesome . Solid . Keyboard ;
2022-01-29 20:38:12 +08:00
private const double flash_duration = 1000 ;
2022-04-29 10:26:56 +08:00
/// <summary>
/// A tracker for periods where alternate should not be forced (i.e. non-gameplay periods).
/// </summary>
/// <remarks>
/// This is different from <see cref="Player.IsBreakTime"/> in that the periods here end strictly at the first object after the break, rather than the break's end time.
/// </remarks>
private PeriodTracker nonGameplayPeriods ;
2022-01-29 20:38:12 +08:00
private OsuAction ? lastActionPressed ;
private DrawableRuleset < OsuHitObject > ruleset ;
2022-02-02 13:33:17 +08:00
private IFrameStableClock gameplayClock ;
2022-01-29 21:49:40 +08:00
public void ApplyToDrawableRuleset ( DrawableRuleset < OsuHitObject > drawableRuleset )
2022-01-29 20:38:12 +08:00
{
ruleset = drawableRuleset ;
2022-01-29 21:49:40 +08:00
drawableRuleset . KeyBindingInputManager . Add ( new InputInterceptor ( this ) ) ;
2022-04-29 10:26:56 +08:00
var periods = new List < Period > ( ) ;
2022-02-02 13:33:17 +08:00
2022-04-29 10:26:56 +08:00
if ( drawableRuleset . Objects . Any ( ) )
2022-01-29 21:49:40 +08:00
{
2022-04-29 10:26:56 +08:00
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 ;
2022-01-29 20:38:12 +08:00
}
2022-02-02 13:02:48 +08:00
private bool checkCorrectAction ( OsuAction action )
2022-01-29 20:38:12 +08:00
{
2022-04-29 10:26:56 +08:00
if ( nonGameplayPeriods . IsInAny ( gameplayClock . CurrentTime ) )
{
lastActionPressed = null ;
2022-02-02 13:02:48 +08:00
return true ;
2022-04-29 10:26:56 +08:00
}
2022-02-02 13:02:48 +08:00
2022-02-02 13:04:10 +08:00
switch ( action )
{
case OsuAction . LeftButton :
case OsuAction . RightButton :
break ;
// Any action which is not left or right button should be ignored.
default :
return true ;
}
2022-02-02 13:02:48 +08:00
if ( lastActionPressed ! = action )
2022-01-29 20:38:12 +08:00
{
2022-02-02 13:04:10 +08:00
// User alternated correctly.
2022-02-02 13:02:48 +08:00
lastActionPressed = action ;
2022-01-29 20:38:12 +08:00
return true ;
}
2022-02-02 13:02:48 +08:00
ruleset . Cursor . FlashColour ( Colour4 . Red , flash_duration , Easing . OutQuint ) ;
2022-01-29 20:38:12 +08:00
return false ;
}
2022-01-29 21:49:40 +08:00
private class InputInterceptor : Component , IKeyBindingHandler < OsuAction >
{
private readonly OsuModAlternate mod ;
public InputInterceptor ( OsuModAlternate mod )
{
this . mod = mod ;
}
public bool OnPressed ( KeyBindingPressEvent < OsuAction > e )
2022-02-02 13:02:48 +08:00
// if the pressed action is incorrect, block it from reaching gameplay.
= > ! mod . checkCorrectAction ( e . Action ) ;
2022-01-29 21:49:40 +08:00
public void OnReleased ( KeyBindingReleaseEvent < OsuAction > e )
{
}
}
2022-01-29 20:38:12 +08:00
}
}