2022-05-03 20:36:55 +00:00
using System.Collections ;
using System.Collections.Generic ;
using UnityEngine ;
using DG.Tweening ;
using HeavenStudio.Util ;
using Starpelly ;
2023-03-11 04:51:22 +00:00
using HeavenStudio.Common ;
2022-05-03 20:36:55 +00:00
namespace HeavenStudio.Games
{
public class PlayerActionEvent : PlayerActionObject
{
2023-03-12 02:56:26 +00:00
public static bool EnableAutoplayCheat = true ;
2022-05-06 20:05:19 +00:00
public delegate void ActionEventCallback ( PlayerActionEvent caller ) ;
public delegate void ActionEventCallbackState ( PlayerActionEvent caller , float state ) ;
2022-05-04 18:05:51 +00:00
2022-05-03 20:36:55 +00:00
public ActionEventCallbackState OnHit ; //Function to trigger when an input has been done perfectly
public ActionEventCallback OnMiss ; //Function to trigger when an input has been missed
public ActionEventCallback OnBlank ; //Function to trigger when an input has been recorded while this is pending
2022-05-06 20:05:19 +00:00
public ActionEventCallback OnDestroy ; //Function to trigger whenever this event gets destroyed. /!\ Shouldn't be used for a minigame! Use OnMiss instead /!\
2022-05-04 18:05:51 +00:00
2022-05-03 20:36:55 +00:00
public float startBeat ;
public float timer ;
2022-05-04 17:21:11 +00:00
public bool canHit = true ; //Indicates if you can still hit the cue or not. If set to false, it'll guarantee a miss
2022-05-06 20:05:19 +00:00
public bool enabled = true ; //Indicates if the PlayerActionEvent is enabled. If set to false, it'll not trigger any events and destroy itself AFTER it's not relevant anymore
2022-05-04 17:21:11 +00:00
public bool autoplayOnly = false ; //Indicates if the input event only triggers when it's autoplay. If set to true, NO Miss or Blank events will be triggered when you're not autoplaying.
public bool noAutoplay = false ; //Indicates if this PlayerActionEvent is recognized by the autoplay. /!\ Overrides autoPlayOnly /!\
2022-05-06 20:05:19 +00:00
public InputType inputType ; //The type of input. Check the InputType class to see a list of all of them
2022-05-03 20:36:55 +00:00
2022-05-06 20:05:19 +00:00
public bool perfectOnly = false ; //Indicates that the input only recognize perfect inputs.
2023-01-25 03:54:19 +00:00
public bool countsForAccuracy = true ; //Indicates if the input counts for the accuracy or not. If set to false, it'll not be counted in the accuracy calculation
2022-05-03 20:36:55 +00:00
public void setHitCallback ( ActionEventCallbackState OnHit )
{
this . OnHit = OnHit ;
}
public void setMissCallback ( ActionEventCallback OnMiss )
{
this . OnMiss = OnMiss ;
}
public void Enable ( ) { enabled = true ; }
public void Disable ( ) { enabled = false ; }
public void CanHit ( bool canHit )
{
this . canHit = canHit ;
}
2022-09-18 20:48:14 +00:00
2022-05-03 20:36:55 +00:00
public void Update ( )
{
if ( ! Conductor . instance . NotStopped ( ) ) { CleanUp ( ) ; } // If the song is stopped entirely in the editor, destroy itself as we don't want duplicates
2022-05-04 17:21:11 +00:00
if ( noAutoplay & & autoplayOnly ) autoplayOnly = false ;
if ( noAutoplay & & triggersAutoplay ) { triggersAutoplay = false ; }
2023-03-11 04:51:22 +00:00
if ( ! enabled ) return ;
2022-05-04 17:21:11 +00:00
2023-01-25 03:54:19 +00:00
double normalizedTime = GetNormalizedTime ( ) ;
double stateProg = ( ( normalizedTime - Minigame . PerfectTime ( ) ) / ( Minigame . LateTime ( ) - Minigame . PerfectTime ( ) ) - 0.5f ) * 2 ;
StateCheck ( normalizedTime ) ;
2022-05-03 20:36:55 +00:00
2022-05-28 02:40:16 +00:00
//BUGFIX: ActionEvents destroyed too early
2023-01-25 03:54:19 +00:00
if ( normalizedTime > Minigame . EndTime ( ) ) Miss ( ) ;
2022-05-03 20:36:55 +00:00
2022-05-04 17:21:11 +00:00
if ( IsCorrectInput ( ) & & ! autoplayOnly )
2022-05-03 20:36:55 +00:00
{
if ( state . perfect )
{
2023-01-25 03:54:19 +00:00
Hit ( stateProg , normalizedTime ) ;
2022-05-03 20:36:55 +00:00
}
2022-05-06 20:05:19 +00:00
else if ( state . early & & ! perfectOnly )
2022-05-03 20:36:55 +00:00
{
2023-01-25 03:54:19 +00:00
Hit ( - 1f , normalizedTime ) ;
2022-05-03 20:36:55 +00:00
}
2022-05-06 20:05:19 +00:00
else if ( state . late & & ! perfectOnly )
2022-05-03 20:36:55 +00:00
{
2023-01-25 03:54:19 +00:00
Hit ( 1f , normalizedTime ) ;
2022-05-03 20:36:55 +00:00
}
else
{
Blank ( ) ;
}
}
2022-05-04 18:37:52 +00:00
}
public bool IsExpectingInputNow ( )
{
2023-01-25 03:54:19 +00:00
double normalizedBeat = GetNormalizedTime ( ) ;
2022-05-04 18:37:52 +00:00
return normalizedBeat > Minigame . EarlyTime ( ) & & normalizedBeat < Minigame . EndTime ( ) ;
}
2022-05-03 20:36:55 +00:00
2023-01-25 03:54:19 +00:00
double GetNormalizedTime ( )
2023-01-14 04:53:25 +00:00
{
var cond = Conductor . instance ;
double currTime = cond . GetSongPosFromBeat ( cond . songPositionInBeatsAsDouble ) ;
double targetTime = cond . GetSongPosFromBeat ( startBeat + timer ) ;
double min = targetTime - 1f ;
double max = targetTime + 1f ;
2023-01-25 03:54:19 +00:00
return 1f + ( ( ( currTime - min ) / ( max - min ) ) - 0.5f ) * 2 ;
2023-01-14 04:53:25 +00:00
}
2022-05-03 20:36:55 +00:00
public bool IsCorrectInput ( )
{
2022-05-04 16:42:06 +00:00
// This one is a mouthful but it's an evil good to detect the correct input
// Forgive me for those input type names
2022-05-03 20:36:55 +00:00
return (
2022-05-04 16:42:06 +00:00
//General inputs, both down and up
2022-07-28 23:12:21 +00:00
( PlayerInput . Pressed ( ) & & inputType . HasFlag ( InputType . STANDARD_DOWN ) ) | |
( PlayerInput . AltPressed ( ) & & inputType . HasFlag ( InputType . STANDARD_ALT_DOWN ) ) | |
( PlayerInput . GetAnyDirectionDown ( ) & & inputType . HasFlag ( InputType . DIRECTION_DOWN ) ) | |
( PlayerInput . PressedUp ( ) & & inputType . HasFlag ( InputType . STANDARD_UP ) ) | |
( PlayerInput . AltPressedUp ( ) & & inputType . HasFlag ( InputType . STANDARD_ALT_UP ) ) | |
( PlayerInput . GetAnyDirectionUp ( ) & & inputType . HasFlag ( InputType . DIRECTION_UP ) ) | |
2022-05-04 16:42:06 +00:00
//Specific directional inputs
2022-07-28 23:12:21 +00:00
( PlayerInput . GetSpecificDirectionDown ( PlayerInput . DOWN ) & & inputType . HasFlag ( InputType . DIRECTION_DOWN_DOWN ) ) | |
( PlayerInput . GetSpecificDirectionDown ( PlayerInput . UP ) & & inputType . HasFlag ( InputType . DIRECTION_UP_DOWN ) ) | |
( PlayerInput . GetSpecificDirectionDown ( PlayerInput . LEFT ) & & inputType . HasFlag ( InputType . DIRECTION_LEFT_DOWN ) ) | |
( PlayerInput . GetSpecificDirectionDown ( PlayerInput . RIGHT ) & & inputType . HasFlag ( InputType . DIRECTION_RIGHT_DOWN ) ) | |
( PlayerInput . GetSpecificDirectionUp ( PlayerInput . DOWN ) & & inputType . HasFlag ( InputType . DIRECTION_DOWN_UP ) ) | |
( PlayerInput . GetSpecificDirectionUp ( PlayerInput . UP ) & & inputType . HasFlag ( InputType . DIRECTION_UP_UP ) ) | |
( PlayerInput . GetSpecificDirectionUp ( PlayerInput . LEFT ) & & inputType . HasFlag ( InputType . DIRECTION_LEFT_UP ) ) | |
( PlayerInput . GetSpecificDirectionUp ( PlayerInput . RIGHT ) & & inputType . HasFlag ( InputType . DIRECTION_RIGHT_UP ) )
2022-05-03 20:36:55 +00:00
) ;
}
//For the Autoplay
public override void OnAce ( )
{
2023-01-25 03:54:19 +00:00
if ( EnableAutoplayCheat )
{
Hit ( 0f , 1f ) ;
}
else
{
double normalizedBeat = GetNormalizedTime ( ) ;
double stateProg = ( ( normalizedBeat - Minigame . PerfectTime ( ) ) / ( Minigame . LateTime ( ) - Minigame . PerfectTime ( ) ) - 0.5f ) * 2 ;
Hit ( stateProg , normalizedBeat ) ;
}
2022-05-03 20:36:55 +00:00
}
2022-05-04 17:21:11 +00:00
//The state parameter is either -1 -> Early, 0 -> Perfect, 1 -> Late
2023-01-25 03:54:19 +00:00
public void Hit ( double state , double time )
2022-05-03 20:36:55 +00:00
{
if ( OnHit ! = null & & enabled )
{
if ( canHit )
{
2023-01-25 03:54:19 +00:00
double normalized = time - 1f ;
2023-01-14 04:53:25 +00:00
int offset = Mathf . CeilToInt ( ( float ) normalized * 1000 ) ;
GameManager . instance . AvgInputOffset = offset ;
2023-01-25 03:54:19 +00:00
OnHit ( this , ( float ) state ) ;
2023-02-05 03:05:43 +00:00
CleanUp ( ) ;
2023-03-11 04:51:22 +00:00
if ( countsForAccuracy & & ! ( noAutoplay | | autoplayOnly ) & & isEligible )
{
GameManager . instance . ScoreInputAccuracy ( TimeToAccuracy ( time ) , time > 1.0 , time ) ;
if ( state > = 1f | | state < = - 1f )
{
GoForAPerfect . instance . Miss ( ) ;
SectionMedalsManager . instance . MakeIneligible ( ) ;
}
else
{
GoForAPerfect . instance . Hit ( ) ;
}
}
2022-05-03 20:36:55 +00:00
} else
{
2022-05-06 20:05:19 +00:00
Blank ( ) ;
2022-05-03 20:36:55 +00:00
}
}
2023-01-25 03:54:19 +00:00
}
double TimeToAccuracy ( double time )
{
if ( time > = Minigame . AceStartTime ( ) & & time < = Minigame . AceEndTime ( ) )
{
// Ace
return 1.0 ;
}
double state = 0 ;
if ( time > = Minigame . PerfectTime ( ) & & time < = Minigame . LateTime ( ) )
{
// Good Hit
if ( time > 1.0 )
{
// late half of timing window
state = 1.0 - ( ( time - Minigame . AceEndTime ( ) ) / ( Minigame . LateTime ( ) - Minigame . AceEndTime ( ) ) ) ;
state * = 1.0 - Minigame . rankHiThreshold ;
state + = Minigame . rankHiThreshold ;
}
else
{
//early half of timing window
state = ( ( time - Minigame . PerfectTime ( ) ) / ( Minigame . AceStartTime ( ) - Minigame . PerfectTime ( ) ) ) ;
state * = 1.0 - Minigame . rankHiThreshold ;
state + = Minigame . rankHiThreshold ;
}
}
else
{
if ( time > 1.0 )
{
// late half of timing window
state = 1.0 - ( ( time - Minigame . LateTime ( ) ) / ( Minigame . EndTime ( ) - Minigame . LateTime ( ) ) ) ;
state * = Minigame . rankOkThreshold ;
}
else
{
//early half of timing window
state = ( ( time - Minigame . PerfectTime ( ) ) / ( Minigame . AceStartTime ( ) - Minigame . PerfectTime ( ) ) ) ;
state * = Minigame . rankOkThreshold ;
}
}
return state ;
2022-05-03 20:36:55 +00:00
}
public void Miss ( )
{
2022-05-04 17:21:11 +00:00
if ( OnMiss ! = null & & enabled & & ! autoplayOnly )
2022-05-03 20:36:55 +00:00
{
2022-05-06 20:05:19 +00:00
OnMiss ( this ) ;
2022-05-03 20:36:55 +00:00
}
2022-05-04 17:21:11 +00:00
CleanUp ( ) ;
2023-01-25 03:54:19 +00:00
if ( countsForAccuracy & & ! ( noAutoplay | | autoplayOnly ) )
2023-03-11 04:51:22 +00:00
{
GameManager . instance . ScoreInputAccuracy ( 0 , true , 2.0 , 1.0 , false ) ;
GoForAPerfect . instance . Miss ( ) ;
SectionMedalsManager . instance . MakeIneligible ( ) ;
}
2022-05-03 20:36:55 +00:00
}
public void Blank ( )
{
2022-05-04 17:21:11 +00:00
if ( OnBlank ! = null & & enabled & & ! autoplayOnly )
2022-05-03 20:36:55 +00:00
{
2022-05-06 20:05:19 +00:00
OnBlank ( this ) ;
2022-05-03 20:36:55 +00:00
}
}
public void CleanUp ( )
{
2022-05-04 18:05:51 +00:00
OnDestroy ( this ) ;
2022-05-03 20:36:55 +00:00
Destroy ( this . gameObject ) ;
}
}
}