2022-07-12 22:07:10 +00: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.
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
{
2022-09-16 16:48:24 +00:00
public abstract class InputBlockingMod : Mod , IApplicableToDrawableRuleset < OsuHitObject > , IUpdatableByPlayfield
2022-07-12 22:07:10 +00:00
{
public override double ScoreMultiplier = > 1.0 ;
2022-07-13 06:49:08 +00:00
public override Type [ ] IncompatibleMods = > new [ ] { typeof ( ModAutoplay ) , typeof ( ModRelax ) , typeof ( OsuModCinema ) } ;
2022-07-12 22:07:10 +00:00
public override ModType Type = > ModType . Conversion ;
2022-07-13 13:31:09 +00:00
private const double flash_duration = 1000 ;
2022-07-12 22:07:10 +00:00
2022-07-13 13:31:09 +00:00
private DrawableRuleset < OsuHitObject > ruleset = null ! ;
2022-07-13 13:04:57 +00:00
2022-07-13 16:40:44 +00:00
protected OsuAction ? LastAcceptedAction { get ; private set ; }
2022-07-13 13:04:57 +00:00
2022-07-12 22:07:10 +00: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>
2022-07-13 13:04:57 +00:00
private PeriodTracker nonGameplayPeriods = null ! ;
2022-07-12 22:07:10 +00:00
2022-07-13 13:04:57 +00:00
private IFrameStableClock gameplayClock = null ! ;
2022-07-12 22:07:10 +00:00
public void ApplyToDrawableRuleset ( DrawableRuleset < OsuHitObject > drawableRuleset )
{
2022-07-13 13:31:09 +00:00
ruleset = drawableRuleset ;
2022-07-12 22:07:10 +00:00
drawableRuleset . KeyBindingInputManager . Add ( new InputInterceptor ( this ) ) ;
var periods = new List < Period > ( ) ;
if ( drawableRuleset . Objects . Any ( ) )
{
2022-07-13 13:31:09 +00:00
periods . Add ( new Period ( int . MinValue , getValidJudgementTime ( ruleset . Objects . First ( ) ) - 1 ) ) ;
2022-07-12 22:07:10 +00:00
foreach ( BreakPeriod b in drawableRuleset . Beatmap . Breaks )
2022-07-13 13:31:09 +00:00
periods . Add ( new Period ( b . StartTime , getValidJudgementTime ( ruleset . Objects . First ( h = > h . StartTime > = b . EndTime ) ) - 1 ) ) ;
2022-07-12 22:07:10 +00:00
static double getValidJudgementTime ( HitObject hitObject ) = > hitObject . StartTime - hitObject . HitWindows . WindowFor ( HitResult . Meh ) ;
}
2022-07-13 13:04:57 +00:00
nonGameplayPeriods = new PeriodTracker ( periods ) ;
2022-07-12 22:07:10 +00:00
2022-07-13 13:04:57 +00:00
gameplayClock = drawableRuleset . FrameStableClock ;
2022-07-12 22:07:10 +00:00
}
2022-09-16 16:48:24 +00:00
public void Update ( Playfield playfield )
{
if ( LastAcceptedAction ! = null & & nonGameplayPeriods . IsInAny ( gameplayClock . CurrentTime ) )
LastAcceptedAction = null ;
}
2022-07-13 13:26:44 +00:00
protected abstract bool CheckValidNewAction ( OsuAction action ) ;
private bool checkCorrectAction ( OsuAction action )
2022-07-12 22:07:10 +00:00
{
2022-07-13 13:04:57 +00:00
if ( nonGameplayPeriods . IsInAny ( gameplayClock . CurrentTime ) )
2022-07-12 22:07:10 +00:00
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 ;
}
2022-07-13 13:26:44 +00:00
if ( CheckValidNewAction ( action ) )
{
2022-07-13 16:40:44 +00:00
LastAcceptedAction = action ;
2022-07-13 13:26:44 +00:00
return true ;
}
ruleset . Cursor . FlashColour ( Colour4 . Red , flash_duration , Easing . OutQuint ) ;
2022-07-12 22:07:10 +00:00
return false ;
}
private class InputInterceptor : Component , IKeyBindingHandler < OsuAction >
{
private readonly InputBlockingMod mod ;
public InputInterceptor ( InputBlockingMod mod )
{
this . mod = mod ;
}
public bool OnPressed ( KeyBindingPressEvent < OsuAction > e )
// if the pressed action is incorrect, block it from reaching gameplay.
2022-07-13 13:26:44 +00:00
= > ! mod . checkCorrectAction ( e . Action ) ;
2022-07-12 22:07:10 +00:00
public void OnReleased ( KeyBindingReleaseEvent < OsuAction > e )
{
}
}
}
}