2022-07-13 06:07:10 +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.
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 < OsuHitObject >
{
public override double ScoreMultiplier = > 1.0 ;
2022-07-13 14:49:08 +08:00
public override Type [ ] IncompatibleMods = > new [ ] { typeof ( ModAutoplay ) , typeof ( ModRelax ) , typeof ( OsuModCinema ) } ;
2022-07-13 06:07:10 +08:00
public override ModType Type = > ModType . Conversion ;
protected const double FLASH_DURATION = 1000 ;
2022-07-13 21:04:57 +08:00
protected DrawableRuleset < OsuHitObject > Ruleset = null ! ;
protected OsuAction ? LastActionPressed ;
2022-07-13 06:07:10 +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>
2022-07-13 21:04:57 +08:00
private PeriodTracker nonGameplayPeriods = null ! ;
2022-07-13 06:07:10 +08:00
2022-07-13 21:04:57 +08:00
private IFrameStableClock gameplayClock = null ! ;
2022-07-13 06:07:10 +08:00
public void ApplyToDrawableRuleset ( DrawableRuleset < OsuHitObject > drawableRuleset )
{
Ruleset = drawableRuleset ;
drawableRuleset . KeyBindingInputManager . Add ( new InputInterceptor ( this ) ) ;
var periods = new List < Period > ( ) ;
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 ) ;
}
2022-07-13 21:04:57 +08:00
nonGameplayPeriods = new PeriodTracker ( periods ) ;
2022-07-13 06:07:10 +08:00
2022-07-13 21:04:57 +08:00
gameplayClock = drawableRuleset . FrameStableClock ;
2022-07-13 06:07:10 +08:00
}
2022-07-13 21:26:44 +08:00
protected abstract bool CheckValidNewAction ( OsuAction action ) ;
private bool checkCorrectAction ( OsuAction action )
2022-07-13 06:07:10 +08:00
{
2022-07-13 21:04:57 +08:00
if ( nonGameplayPeriods . IsInAny ( gameplayClock . CurrentTime ) )
2022-07-13 06:07:10 +08:00
{
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 ;
}
2022-07-13 21:26:44 +08:00
if ( CheckValidNewAction ( action ) )
{
LastActionPressed = action ;
return true ;
}
ruleset . Cursor . FlashColour ( Colour4 . Red , flash_duration , Easing . OutQuint ) ;
2022-07-13 06:07:10 +08: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 21:26:44 +08:00
= > ! mod . checkCorrectAction ( e . Action ) ;
2022-07-13 06:07:10 +08:00
public void OnReleased ( KeyBindingReleaseEvent < OsuAction > e )
{
}
}
}
}