2023-02-14 05:25:52 +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 osu.Framework.Localisation ;
2023-02-28 23:14:03 +08:00
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.Taiko.Objects ;
using osu.Game.Rulesets.Scoring ;
using osu.Game.Rulesets.UI ;
using osu.Game.Screens.Play ;
using osu.Game.Utils ;
using osu.Game.Rulesets.Taiko.UI ;
2023-02-14 05:25:52 +08:00
namespace osu.Game.Rulesets.Taiko.Mods
{
2023-03-01 02:38:09 +08:00
public partial class TaikoModSingleTap : Mod , IApplicableToDrawableRuleset < TaikoHitObject > , IUpdatableByPlayfield
2023-02-14 05:25:52 +08:00
{
public override string Name = > @"Single Tap" ;
public override string Acronym = > @"SG" ;
2023-02-27 23:22:06 +08:00
public override LocalisableString Description = > @"One key for dons, one key for kats." ;
2023-02-14 05:25:52 +08:00
2023-03-04 01:13:39 +08:00
protected bool CheckValidNewAction ( TaikoAction action )
{
if ( action = = TaikoAction . LeftCentre | | action = = TaikoAction . RightCentre )
2023-03-06 18:57:09 +08:00
return lastAcceptedCentreAction = = null | | lastAcceptedCentreAction = = action ;
2023-03-04 01:13:39 +08:00
if ( action = = TaikoAction . LeftRim | | action = = TaikoAction . RightRim )
2023-03-06 18:57:09 +08:00
return lastAcceptedRimAction = = null | | lastAcceptedRimAction = = action ;
2023-03-04 02:11:29 +08:00
2023-03-04 01:13:39 +08:00
return true ;
}
2023-02-28 23:14:03 +08:00
public override double ScoreMultiplier = > 1.0 ;
public override Type [ ] IncompatibleMods = > new [ ] { typeof ( ModAutoplay ) , typeof ( ModRelax ) , typeof ( TaikoModCinema ) } ;
public override ModType Type = > ModType . Conversion ;
private DrawableTaikoRuleset ruleset = null ! ;
private TaikoPlayfield playfield { get ; set ; } = null ! ;
2023-03-06 18:57:09 +08:00
private TaikoAction ? lastAcceptedCentreAction { get ; set ; }
private TaikoAction ? lastAcceptedRimAction { get ; set ; }
2023-02-28 23:14:03 +08:00
/// <summary>
2023-03-06 18:59:47 +08:00
/// A tracker for periods where single tap should not be enforced (i.e. non-gameplay periods).
2023-02-28 23:14:03 +08:00
/// </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 = null ! ;
private IFrameStableClock gameplayClock = null ! ;
public void ApplyToDrawableRuleset ( DrawableRuleset < TaikoHitObject > drawableRuleset )
{
ruleset = ( DrawableTaikoRuleset ) drawableRuleset ;
ruleset . InputManager . Add ( new InputInterceptor ( this ) ) ;
playfield = ( TaikoPlayfield ) ruleset . Playfield ;
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 ) ;
}
nonGameplayPeriods = new PeriodTracker ( periods ) ;
gameplayClock = drawableRuleset . FrameStableClock ;
}
public void Update ( Playfield playfield )
{
if ( ! nonGameplayPeriods . IsInAny ( gameplayClock . CurrentTime ) ) return ;
2023-03-06 18:57:09 +08:00
if ( lastAcceptedCentreAction ! = null )
lastAcceptedCentreAction = null ;
2023-02-28 23:14:03 +08:00
2023-03-06 18:57:09 +08:00
if ( lastAcceptedRimAction ! = null )
lastAcceptedRimAction = null ;
2023-02-28 23:14:03 +08:00
}
private bool checkCorrectAction ( TaikoAction action )
{
if ( nonGameplayPeriods . IsInAny ( gameplayClock . CurrentTime ) )
return true ;
// If next hit object is strong, allow usage of all actions. Strong drumrolls are ignored in this check.
if ( playfield . HitObjectContainer . AliveObjects . FirstOrDefault ( h = > h . Result ? . HasResult ! = true ) ? . HitObject is TaikoStrongableHitObject hitObject
& & hitObject . IsStrong
2023-02-28 23:41:06 +08:00
& & hitObject is not DrumRoll )
2023-02-28 23:14:03 +08:00
return true ;
if ( CheckValidNewAction ( action ) )
{
if ( action = = TaikoAction . LeftCentre | | action = = TaikoAction . RightCentre )
2023-03-06 18:57:09 +08:00
lastAcceptedCentreAction = action ;
2023-02-28 23:14:03 +08:00
if ( action = = TaikoAction . LeftRim | | action = = TaikoAction . RightRim )
2023-03-06 18:57:09 +08:00
lastAcceptedRimAction = action ;
2023-03-04 02:11:29 +08:00
2023-02-28 23:14:03 +08:00
return true ;
}
return false ;
}
private partial class InputInterceptor : Component , IKeyBindingHandler < TaikoAction >
{
private readonly TaikoModSingleTap mod ;
public InputInterceptor ( TaikoModSingleTap mod )
{
this . mod = mod ;
}
public bool OnPressed ( KeyBindingPressEvent < TaikoAction > e )
// if the pressed action is incorrect, block it from reaching gameplay.
= > ! mod . checkCorrectAction ( e . Action ) ;
public void OnReleased ( KeyBindingReleaseEvent < TaikoAction > e )
{
}
}
2023-02-14 05:25:52 +08:00
}
}