2022-03-21 04:19:05 +00:00
package funkin . play . character ;
2022-03-23 05:18:23 +00:00
import flixel . math . FlxPoint ;
2022-04-29 15:56:08 +00:00
import funkin . modding . events . ScriptEvent ;
import funkin . play . character . CharacterData . CharacterDataParser ;
2023-02-22 01:58:15 +00:00
import funkin . play . character . CharacterData . CharacterRenderType ;
2022-03-21 04:19:05 +00:00
import funkin . play . stage . Bopper ;
2023-06-22 05:41:01 +00:00
import funkin . play . notes . NoteDirection ;
2022-03-21 04:19:05 +00:00
/ * *
* A Character is a stage prop which bops to the music as well as controlled by the strumlines .
2023-06-08 20:30:45 +00:00
*
2022-03-21 04:19:05 +00:00
* Remember : The character ' s o r i g i n i s a t i t s F E E T . ( h o r i z o n t a l c e n t e r , v e r t i c a l b o t t o m )
* /
class BaseCharacter extends Bopper
{
2023-01-23 03:25:45 +00:00
// Metadata about a character.
public var characterId( default , null ) : String ;
public var characterName( default , null ) : String ;
/ * *
* Whether the player is an active character ( Boyfriend ) or not .
* /
2023-05-26 21:10:08 +00:00
public var characterType( default , set ) : CharacterType = OTHER ;
function set_characterType ( value : CharacterType ) : CharacterType
{
return this . characterType = value ;
}
2023-01-23 03:25:45 +00:00
/ * *
* Tracks how long , i n seconds , the character has been playing the current ` sing ` animation .
* This is used to ensure that characters play the ` sing ` animations for a t l e a s t o n e b e a t ,
* preventing them from reverting to the ` idle ` animation between notes .
* /
public var holdTimer: Float = 0 ;
2023-05-26 21:10:08 +00:00
/ * *
* Set to true when the character dead . Part of the handling for d e a t h a n i m a t i o n s .
* /
2023-01-23 03:25:45 +00:00
public var isDead: Bool = false ;
2023-05-26 21:10:08 +00:00
/ * *
* Set to true when the character being used in a special way .
* This includes the Chart Editor and the Animation Editor .
2023-06-08 20:30:45 +00:00
*
2023-05-26 21:10:08 +00:00
* Used by scripts to ensure that they don ' t t r y t o r u n c o d e t o i n t e r a c t w i t h t h e s t a g e w h e n t h e s t a g e d o e s n ' t actually exist .
* /
public var debug: Bool = false ;
2023-01-23 03:25:45 +00:00
/ * *
* This character plays a given animation when hitting these specific combo numbers .
* /
public var comboNoteCounts( default , null ) : Array < Int > ;
/ * *
* This character plays a given animation when dropping combos larger than these numbers .
* /
public var dropNoteCounts( default , null ) : Array < Int > ;
2023-11-07 09:04:22 +00:00
@ : allow ( funkin . ui . debug . anim . DebugBoundingState )
2023-01-23 03:25:45 +00:00
final _data : CharacterData ;
2024-03-01 13:13:06 +00:00
final singTimeSteps : Float ;
2023-01-23 03:25:45 +00:00
/ * *
* The offset between the corner of the sprite and the origin of the sprite ( at t h e c h a r a c t e r ' s f e e t ) .
* cornerPosition = stageData - characterOrigin
* /
2023-08-31 22:47:23 +00:00
public var characterOrigin( get , never ) : FlxPoint ;
2023-01-23 03:25:45 +00:00
function get_characterOrigin ( ) : FlxPoint
{
var xPos = ( width / 2 ) ; // Horizontal center
var yPos = ( height ) ; // Vertical bottom
return new FlxPoint ( xPos , yPos ) ;
}
/ * *
* The absolute position of the top - left of the character .
2023-06-08 20:30:45 +00:00
* @ return
2023-01-23 03:25:45 +00:00
* /
2023-02-22 01:58:15 +00:00
public var cornerPosition( get , set ) : FlxPoint ;
2023-01-23 03:25:45 +00:00
function get_cornerPosition ( ) : FlxPoint
{
return new FlxPoint ( x , y ) ;
}
2023-02-22 01:58:15 +00:00
function set_cornerPosition ( value : FlxPoint ) : FlxPoint
{
var xDiff: Float = value . x - this . x ;
var yDiff: Float = value . y - this . y ;
this . cameraFocusPoint . x += xDiff ;
this . cameraFocusPoint . y += yDiff ;
super . set_x ( value . x ) ;
super . set_y ( value . y ) ;
return value ;
}
2023-01-23 03:25:45 +00:00
/ * *
* The absolute position of the character ' s f e e t , a t t h e b o t t o m - c e n t e r o f t h e s p r i t e .
* /
2023-08-31 22:47:23 +00:00
public var feetPosition( get , never ) : FlxPoint ;
2023-01-23 03:25:45 +00:00
function get_feetPosition ( ) : FlxPoint
{
return new FlxPoint ( x + characterOrigin . x , y + characterOrigin . y ) ;
}
/ * *
* Returns the point the camera should focus on .
* Should be approximately centered on the character , and should not move based on the current animation .
2023-06-08 20:30:45 +00:00
*
2023-01-23 03:25:45 +00:00
* Set the position of this rather than reassigning it , so that anything referencing it will not be affected .
* /
public var cameraFocusPoint( default , null ) : FlxPoint = new FlxPoint ( 0 , 0 ) ;
/ * *
* If the x position changes , other than via changing the animation offset ,
* then we need to update the camera focus point .
* /
override function set_x ( value : Float ) : Float
{
if ( value == this . x ) return value ;
var xDiff = value - this . x ;
this . cameraFocusPoint . x += xDiff ;
return super . set_x ( value ) ;
}
/ * *
* If the y position changes , other than via changing the animation offset ,
* then we need to update the camera focus point .
* /
override function set_y ( value : Float ) : Float
{
if ( value == this . y ) return value ;
var yDiff = value - this . y ;
this . cameraFocusPoint . y += yDiff ;
return super . set_y ( value ) ;
}
2023-02-22 01:58:15 +00:00
public function n e w ( id : String , renderType : CharacterRenderType )
2023-01-23 03:25:45 +00:00
{
2024-06-29 03:25:59 +00:00
super ( CharacterDataParser . DEFAULT_DANCEEVERY ) ;
2023-01-23 03:25:45 +00:00
this . characterId = id ;
_data = CharacterDataParser . fetchCharacterData ( this . characterId ) ;
if ( _data == null )
{
throw ' C o u l d n o t f i n d c h a r a c t e r d a t a f o r c h a r a c t e r I d : $ characterId ' ;
}
2023-02-22 01:58:15 +00:00
e lse if ( _data . renderType != renderType )
{
throw ' R e n d e r t y p e m i s m a t c h f o r c h a r a c t e r ( $ characterId ) : e x p e c t e d ${ renderType } , g o t ${ _data . renderType } ' ;
}
2023-01-23 03:25:45 +00:00
e lse
{
this . characterName = _data . name ;
2023-02-03 23:10:06 +00:00
this . name = _data . name ;
2024-06-27 19:05:01 +00:00
this . danceEvery = _data . danceEvery ;
2024-03-01 13:13:06 +00:00
this . singTimeSteps = _data . singTime ;
2023-01-23 03:25:45 +00:00
this . globalOffsets = _data . offsets ;
this . flipX = _data . flipX ;
}
shouldBop = false ;
}
2023-10-11 05:04:56 +00:00
public function getDeathCameraOffsets ( ) : Array < Float >
{
return _data . death ? . cameraOffsets ? ? [ 0.0 , 0.0 ] ;
}
2024-03-15 20:45:18 +00:00
public function getBaseScale ( ) : Float
{
return _data . scale ;
}
2024-03-01 13:13:06 +00:00
public function getDeathCameraZoom ( ) : Float
{
return _data . death ? . cameraZoom ? ? 1.0 ;
}
2024-03-04 21:37:42 +00:00
public function getDeathPreTransitionDelay ( ) : Float
{
return _data . death ? . preTransitionDelay ? ? 0.0 ;
}
2023-01-23 03:25:45 +00:00
/ * *
* Gets the value of flipX from the character data .
* ` ! getFlipX ( ) ` is the direction Boyfriend should face .
* /
public function getDataFlipX ( ) : Bool
{
return _data . flipX ;
}
function findCountAnimations ( prefix : String ) : Array < Int >
{
var animNames: Array < String > = this . animation . getNameList ( ) ;
var result: Array < Int > = [ ] ;
for ( anim in animNames )
{
if ( anim . startsWith ( prefix ) )
{
var comboNum: Null < Int > = Std . parseInt ( anim . substring ( prefix . length ) ) ;
if ( comboNum != null )
{
result . push ( comboNum ) ;
}
}
}
// Sort numerically.
result . sort ( ( a , b ) - > a - b ) ;
return result ;
}
/ * *
* Reset the character so it can be used at the start of the level .
* Call this when restarting the level .
* /
public function resetCharacter ( resetCamera : Bool = true ) : Void
{
// Reset the animation offsets. This will modify x and y to be the absolute position of the character.
2023-08-02 22:08:49 +00:00
// this.animOffsets = [0, 0];
2023-01-23 03:25:45 +00:00
// Now we can set the x and y to be their original values without having to account for animOffsets.
this . resetPosition ( ) ;
2023-08-02 22:08:49 +00:00
// Then reapply animOffsets...
// applyAnimationOffsets(getCurrentAnimation());
2023-01-23 03:25:45 +00:00
this . dance ( true ) ; // Force to avoid the old animation playing with the wrong offset at the start of the song.
2023-08-04 21:25:13 +00:00
// Make sure we are playing the idle animation
2023-01-23 03:25:45 +00:00
// ...then update the hitbox so that this.width and this.height are correct.
this . updateHitbox ( ) ;
// Reset the camera focus point while we're at it.
if ( resetCamera ) this . resetCameraFocusPoint ( ) ;
}
/ * *
2024-03-15 20:45:18 +00:00
* Set the character ' s s p r i t e s c a l e t o t h e a p p r o p r i a t e v a l u e .
* @ param scale The desired scale .
2023-01-23 03:25:45 +00:00
* /
2023-05-26 21:10:08 +00:00
public function setScale ( scale : Null < Float > ) : Void
2023-01-23 03:25:45 +00:00
{
if ( scale == null ) scale = 1.0 ;
var feetPos: FlxPoint = feetPosition ;
this . scale . x = scale ;
this . scale . y = scale ;
this . updateHitbox ( ) ;
// Reposition with newly scaled sprite.
this . x = feetPos . x - characterOrigin . x + globalOffsets [ 0 ] ;
this . y = feetPos . y - characterOrigin . y + globalOffsets [ 1 ] ;
}
/ * *
* The per - character camera offset .
* /
2023-08-31 22:47:23 +00:00
var characterCameraOffsets( get , never ) : Array < Float > ;
2023-01-23 03:25:45 +00:00
function get_characterCameraOffsets ( ) : Array < Float >
{
return _data . cameraOffsets ;
}
override function onCreate ( event : ScriptEvent ) : Void
{
2023-02-22 01:58:15 +00:00
super . onCreate ( event ) ;
2023-01-23 03:25:45 +00:00
// Make sure we are playing the idle animation...
2023-05-26 21:10:08 +00:00
this . dance ( true ) ;
2023-01-23 03:25:45 +00:00
// ...then update the hitbox so that this.width and this.height are correct.
this . updateHitbox ( ) ;
// Without the above code, width and height (and therefore character position)
// will be based on the first animation in the sheet rather than the default animation.
this . resetCameraFocusPoint ( ) ;
// Child class should have created animations by now,
// so we can query which ones are available.
this . comboNoteCounts = findCountAnimations ( ' c o m b o ' ) ; // example: combo50
this . dropNoteCounts = findCountAnimations ( ' d r o p ' ) ; // example: drop50
2024-06-29 02:29:59 +00:00
if ( comboNoteCounts . length > 0 ) trace ( ' C o m b o n o t e c o u n t s : ' + this . comboNoteCounts ) ;
if ( dropNoteCounts . length > 0 ) trace ( ' D r o p n o t e c o u n t s : ' + this . dropNoteCounts ) ;
2023-01-23 03:25:45 +00:00
super . onCreate ( event ) ;
}
2024-06-29 02:29:59 +00:00
override function onAnimationFinished ( animationName : String ) : Void
{
super . onAnimationFinished ( animationName ) ;
trace ( ' ${ characterId } h a s f i n i s h e d a n i m a t i o n : ${ animationName } ' ) ;
if ( ( animationName . endsWith ( Constants . ANIMATION_END_SUFFIX ) && ! animationName . startsWith ( ' i d l e ' ) && ! animationName . startsWith ( ' d a n c e ' ) )
| | animationName . startsWith ( ' c o m b o ' )
| | animationName . startsWith ( ' d r o p ' ) )
{
// Force the character to play the idle after the animation ends.
this . dance ( true ) ;
}
}
2023-01-23 03:25:45 +00:00
function resetCameraFocusPoint ( ) : Void
{
// Calculate the camera focus point
var charCenterX = this . x + this . width / 2 ;
var charCenterY = this . y + this . height / 2 ;
this . cameraFocusPoint = new FlxPoint ( charCenterX + _data . cameraOffsets [ 0 ] , charCenterY + _data . cameraOffsets [ 1 ] ) ;
}
public function initHealthIcon ( isOpponent : Bool ) : Void
{
if ( ! isOpponent )
{
if ( PlayState . instance . iconP1 == null )
{
trace ( ' [ W A R N ] P l a y e r 1 h e a l t h i c o n n o t f o u n d ! ' ) ;
2023-05-26 21:10:08 +00:00
return ;
2023-01-23 03:25:45 +00:00
}
2023-10-17 06:42:52 +00:00
PlayState . instance . iconP1 . configure ( _data . healthIcon ) ;
PlayState . instance . iconP1 . flipX = ! PlayState . instance . iconP1 . flipX ; // BF is looking the other way.
2023-01-23 03:25:45 +00:00
}
e lse
{
if ( PlayState . instance . iconP2 == null )
{
trace ( ' [ W A R N ] P l a y e r 2 h e a l t h i c o n n o t f o u n d ! ' ) ;
2023-05-26 21:10:08 +00:00
return ;
2023-01-23 03:25:45 +00:00
}
2023-10-17 06:42:52 +00:00
PlayState . instance . iconP2 . configure ( _data . healthIcon ) ;
2023-01-23 03:25:45 +00:00
}
}
public override function onUpdate ( event : UpdateScriptEvent ) : Void
{
super . onUpdate ( event ) ;
// Reset hold timer for each note pressed.
if ( justPressedNote ( ) && this . characterType == BF )
{
holdTimer = 0 ;
}
if ( isDead )
{
2023-08-02 22:08:49 +00:00
// playDeathAnimation();
2023-01-23 03:25:45 +00:00
return ;
}
2023-05-26 21:10:08 +00:00
// If there is an animation, and another animation with the same name + "-hold" exists,
// the second animation will play (and be looped if configured to do so) after the first animation finishes.
// This is good for characters that need to hold a pose while maintaining an animation, like the parents (this keeps their eyes flickering)
// and Darnell (this keeps the flame on his lighter flickering).
// Works for idle, singLEFT/RIGHT/UP/DOWN, alt singing animations, and anything else really.
2023-02-22 01:58:15 +00:00
2024-07-15 10:30:10 +00:00
if ( isAnimationFinished ( )
& & ! getCurrentAnimation ( ) . endsWith ( Constants . ANIMATION_HOLD_SUFFIX )
& & hasAnimation ( getCurrentAnimation ( ) + Constants . ANIMATION_HOLD_SUFFIX ) )
2023-05-26 21:10:08 +00:00
{
2024-06-29 02:29:59 +00:00
playAnimation ( getCurrentAnimation ( ) + Constants . ANIMATION_HOLD_SUFFIX ) ;
2023-05-26 21:10:08 +00:00
}
2024-07-15 10:30:10 +00:00
e lse
{
if ( isAnimationFinished ( ) )
{
2024-09-10 02:35:02 +00:00
// trace('Not playing hold (${getCurrentAnimation()}) (${isAnimationFinished()}, ${getCurrentAnimation().endsWith(Constants.ANIMATION_HOLD_SUFFIX)}, ${hasAnimation(getCurrentAnimation() + Constants.ANIMATION_HOLD_SUFFIX)})');
2024-07-15 10:30:10 +00:00
}
}
2023-01-23 03:25:45 +00:00
// Handle character note hold time.
2023-07-08 05:03:46 +00:00
if ( isSinging ( ) )
2023-01-23 03:25:45 +00:00
{
// TODO: Rework this code (and all character animations ugh)
// such that the hold time is handled by padding frames,
// and reverting to the idle animation is done when `isAnimationFinished()`.
// This lets you add frames to the end of the sing animation to ease back into the idle!
holdTimer += event . elapsed ;
2024-03-01 13:13:06 +00:00
var singTimeSec: Float = singTimeSteps * ( Conductor . instance . stepLengthMs / Constants . MS_PER_SEC ) ; // x beats, to ms.
2023-01-23 03:25:45 +00:00
2024-03-01 13:13:06 +00:00
if ( getCurrentAnimation ( ) . endsWith ( ' m i s s ' ) ) singTimeSec *= 2 ; // makes it feel more awkward when you miss???
2023-01-23 03:25:45 +00:00
// Without this check here, the player character would only play the `sing` animation
// for one beat, as opposed to holding it as long as the player is holding the button.
var shouldStopSinging: Bool = ( this . characterType == BF ) ? ! isHoldingNote ( ) : true ;
2023-05-26 21:10:08 +00:00
FlxG . watch . addQuick ( ' s i n g T i m e S e c - ${ characterId } ' , singTimeSec ) ;
if ( holdTimer > singTimeSec && shouldStopSinging )
2023-01-23 03:25:45 +00:00
{
2024-03-01 13:13:06 +00:00
trace ( ' h o l d T i m e r r e a c h e d ${ holdTimer } s e c ( > ${ singTimeSec } ) , s t o p p i n g s i n g a n i m a t i o n ' ) ;
2023-01-23 03:25:45 +00:00
holdTimer = 0 ;
2024-06-29 02:29:59 +00:00
var currentAnimation: String = getCurrentAnimation ( ) ;
// Strip "-hold" from the end.
if ( currentAnimation . endsWith ( Constants . ANIMATION_HOLD_SUFFIX ) ) currentAnimation = currentAnimation . substring ( 0 ,
currentAnimation . length - Constants . ANIMATION_HOLD_SUFFIX . length ) ;
var endAnimation: String = currentAnimation + Constants . ANIMATION_END_SUFFIX ;
if ( hasAnimation ( endAnimation ) )
{
// Play the '-end' animation, if one exists.
trace ( ' ${ characterId } : p l a y i n g ${ endAnimation } ' ) ;
playAnimation ( endAnimation ) ;
}
e lse
{
// Play the idle animation.
dance ( true ) ;
}
2023-01-23 03:25:45 +00:00
}
}
e lse
{
holdTimer = 0 ;
// super.onBeatHit handles the regular `dance()` calls.
}
FlxG . watch . addQuick ( ' h o l d T i m e r - ${ characterId } ' , holdTimer ) ;
}
2023-07-08 05:03:46 +00:00
public function isSinging ( ) : Bool
{
2024-06-29 02:29:59 +00:00
var currentAnimation: String = getCurrentAnimation ( ) ;
return currentAnimation . startsWith ( ' s i n g ' ) && ! currentAnimation . endsWith ( Constants . ANIMATION_END_SUFFIX ) ;
2023-07-08 05:03:46 +00:00
}
2023-02-22 01:58:15 +00:00
override function dance ( force : Bool = false ) : Void
2023-01-23 03:25:45 +00:00
{
// Prevent default dancing behavior.
2023-05-26 21:10:08 +00:00
if ( isDead ) return ;
2023-01-23 03:25:45 +00:00
if ( ! force )
{
2024-06-29 02:29:59 +00:00
// Prevent dancing while a singing animation is playing.
2023-07-08 05:03:46 +00:00
if ( isSinging ( ) ) return ;
2023-02-22 01:58:15 +00:00
2024-06-29 02:29:59 +00:00
// Prevent dancing while a non-idle special animation is playing.
2024-05-13 17:35:26 +00:00
var currentAnimation: String = getCurrentAnimation ( ) ;
2024-06-29 02:29:59 +00:00
if ( ! currentAnimation . startsWith ( ' d a n c e ' ) && ! currentAnimation . startsWith ( ' i d l e ' ) && ! isAnimationFinished ( ) ) return ;
2023-01-23 03:25:45 +00:00
}
// Otherwise, fallback to the super dance() method, which handles playing the idle animation.
super . dance ( ) ;
}
/ * *
* Returns true if t h e p l a y e r j u s t p r e s s e d a n o t e .
* Used when determing whether a the player character should revert to the ` idle ` animation .
* On non - player characters , this should be ignored .
* /
function justPressedNote ( player : Int = 1 ) : Bool
{
// Returns true if at least one of LEFT, DOWN, UP, or RIGHT is being held.
switch ( player )
{
c ase 1 :
2024-05-13 17:35:26 +00:00
return PlayerSettings . player1 . controls . NOTE_LEFT_P
| | PlayerSettings . player1 . controls . NOTE_DOWN_P
| | PlayerSettings . player1 . controls . NOTE_UP_P
| | PlayerSettings . player1 . controls . NOTE_RIGHT_P ;
2023-01-23 03:25:45 +00:00
c ase 2 :
2024-05-13 17:35:26 +00:00
return PlayerSettings . player2 . controls . NOTE_LEFT_P
| | PlayerSettings . player2 . controls . NOTE_DOWN_P
| | PlayerSettings . player2 . controls . NOTE_UP_P
| | PlayerSettings . player2 . controls . NOTE_RIGHT_P ;
2023-01-23 03:25:45 +00:00
}
return false ;
}
/ * *
* Returns true if t h e p l a y e r i s h o l d i n g a n o t e .
* Used when determing whether a the player character should revert to the ` idle ` animation .
* On non - player characters , this should be ignored .
* /
function isHoldingNote ( player : Int = 1 ) : Bool
{
// Returns true if at least one of LEFT, DOWN, UP, or RIGHT is being held.
switch ( player )
{
c ase 1 :
2024-05-13 17:35:26 +00:00
return PlayerSettings . player1 . controls . NOTE_LEFT
| | PlayerSettings . player1 . controls . NOTE_DOWN
| | PlayerSettings . player1 . controls . NOTE_UP
| | PlayerSettings . player1 . controls . NOTE_RIGHT ;
2023-01-23 03:25:45 +00:00
c ase 2 :
2024-05-13 17:35:26 +00:00
return PlayerSettings . player2 . controls . NOTE_LEFT
| | PlayerSettings . player2 . controls . NOTE_DOWN
| | PlayerSettings . player2 . controls . NOTE_UP
| | PlayerSettings . player2 . controls . NOTE_RIGHT ;
2023-01-23 03:25:45 +00:00
}
return false ;
}
/ * *
* Every time a note is hit , check if t h e n o t e i s f r o m t h e s a m e s t r u m l i n e .
* If it is , then play the sing animation .
* /
2024-03-06 02:48:04 +00:00
public override function onNoteHit ( event : HitNoteScriptEvent )
2023-01-23 03:25:45 +00:00
{
super . onNoteHit ( event ) ;
2024-07-13 01:40:46 +00:00
// If another script cancelled the event, don't do anything.
if ( event . eventCanceled ) return ;
2023-06-22 05:41:01 +00:00
if ( event . note . noteData . getMustHitNote ( ) && characterType == BF )
2023-01-23 03:25:45 +00:00
{
// If the note is from the same strumline, play the sing animation.
2023-06-22 05:41:01 +00:00
this . playSingAnimation ( event . note . noteData . getDirection ( ) , false ) ;
2023-01-23 03:25:45 +00:00
holdTimer = 0 ;
}
2023-06-22 05:41:01 +00:00
e lse if ( ! event . note . noteData . getMustHitNote ( ) && characterType == DAD )
2023-01-23 03:25:45 +00:00
{
// If the note is from the same strumline, play the sing animation.
2023-06-22 05:41:01 +00:00
this . playSingAnimation ( event . note . noteData . getDirection ( ) , false ) ;
2023-01-23 03:25:45 +00:00
holdTimer = 0 ;
}
2024-06-29 02:29:59 +00:00
e lse if ( characterType == GF && event . note . noteData . getMustHitNote ( ) )
{
switch ( event . judgement )
{
c ase ' s i c k ' | ' g o o d ' :
playComboAnimation ( event . comboCount ) ;
d efault :
playComboDropAnimation ( event . comboCount ) ;
}
}
2023-01-23 03:25:45 +00:00
}
/ * *
* Every time a note is missed , check if t h e n o t e i s f r o m t h e s a m e s t r u m l i n e .
* If it is , then play the sing animation .
* /
public override function onNoteMiss ( event : NoteScriptEvent )
{
super . onNoteMiss ( event ) ;
2024-07-13 01:40:46 +00:00
// If another script cancelled the event, don't do anything.
if ( event . eventCanceled ) return ;
2023-06-22 05:41:01 +00:00
if ( event . note . noteData . getMustHitNote ( ) && characterType == BF )
2023-01-23 03:25:45 +00:00
{
// If the note is from the same strumline, play the sing animation.
2023-06-22 05:41:01 +00:00
this . playSingAnimation ( event . note . noteData . getDirection ( ) , true ) ;
2023-01-23 03:25:45 +00:00
}
2023-06-22 05:41:01 +00:00
e lse if ( ! event . note . noteData . getMustHitNote ( ) && characterType == DAD )
2023-01-23 03:25:45 +00:00
{
// If the note is from the same strumline, play the sing animation.
2023-06-22 05:41:01 +00:00
this . playSingAnimation ( event . note . noteData . getDirection ( ) , true ) ;
2023-01-23 03:25:45 +00:00
}
2023-06-22 05:41:01 +00:00
e lse if ( event . note . noteData . getMustHitNote ( ) && characterType == GF )
2023-01-23 03:25:45 +00:00
{
2024-06-29 02:29:59 +00:00
playComboDropAnimation ( Highscore . tallies . combo ) ;
}
}
2023-01-23 03:25:45 +00:00
2024-06-29 02:29:59 +00:00
function playComboAnimation ( comboCount : Int ) : Void
{
var comboAnim = ' c o m b o ${ comboCount } ' ;
if ( hasAnimation ( comboAnim ) )
{
trace ( ' P l a y i n g G F c o m b o a n i m a t i o n : ${ comboAnim } ' ) ;
this . playAnimation ( comboAnim , true , true ) ;
}
}
function playComboDropAnimation ( comboCount : Int ) : Void
{
var dropAnim: Null < String > = null ;
2023-01-23 03:25:45 +00:00
2024-06-29 02:29:59 +00:00
// Choose the combo drop anim to play.
// If there are several (for example, drop10 and drop50) the highest one will be used.
// If the combo count is too low, no animation will be played.
for ( count in dropNoteCounts )
{
if ( comboCount >= count )
2023-01-23 03:25:45 +00:00
{
2024-06-29 02:29:59 +00:00
dropAnim = ' d r o p ${ count } ' ;
2023-01-23 03:25:45 +00:00
}
}
2024-06-29 02:29:59 +00:00
if ( dropAnim != null )
{
trace ( ' P l a y i n g G F c o m b o d r o p a n i m a t i o n : ${ dropAnim } ' ) ;
this . playAnimation ( dropAnim , true , true ) ;
}
2023-01-23 03:25:45 +00:00
}
/ * *
* Every time a wrong key is pressed , play the miss animation if w e a r e B o y f r i e n d .
* /
2024-08-29 17:40:56 +00:00
public override function onNoteGhostMiss ( event : GhostMissNoteScriptEvent ) : Void
2023-01-23 03:25:45 +00:00
{
super . onNoteGhostMiss ( event ) ;
if ( event . eventCanceled || ! event . playAnim )
{
// Skipping...
return ;
}
if ( characterType == BF )
{
// If the note is from the same strumline, play the sing animation.
// trace('Playing ghost miss animation...');
this . playSingAnimation ( event . dir , true ) ;
}
}
public override function onDestroy ( event : ScriptEvent ) : Void
{
this . characterType = OTHER ;
}
/ * *
* Play the appropriate singing animation , for t h e g i v e n n o t e d i r e c t i o n .
* @ param dir The direction of the note .
* @ param miss If true , play the miss animation instead of the sing animation .
* @ param suffix A suffix to append to the animation name , like ` alt ` .
* /
2023-08-28 19:03:29 +00:00
public function playSingAnimation ( dir : NoteDirection , miss : Bool = false , ? suffix : String = ' ' ) : Void
2023-01-23 03:25:45 +00:00
{
2023-02-22 01:58:15 +00:00
var anim: String = ' s i n g ${ dir . nameUpper } ${ miss ? ' m i s s ' : ' ' } ${ suffix != ' ' ? ' - ${ suffix } ' : ' ' } ' ;
2023-01-23 03:25:45 +00:00
// restart even if already playing, because the character might sing the same note twice.
2024-07-15 10:30:10 +00:00
trace ( ' P l a y i n g ${ anim } . . . ' ) ;
2023-01-23 03:25:45 +00:00
playAnimation ( anim , true ) ;
}
2023-05-30 20:20:57 +00:00
2023-08-28 19:03:29 +00:00
public override function playAnimation ( name : String , restart : Bool = false , ignoreOther : Bool = false , reversed : Bool = false ) : Void
2023-05-30 20:20:57 +00:00
{
super . playAnimation ( name , restart , ignoreOther , reversed ) ;
}
2022-03-21 04:19:05 +00:00
}
2023-02-22 01:58:15 +00:00
/ * *
* The type of a given character sprite . Defines its d efault behaviors .
* /
2022-03-21 04:19:05 +00:00
enum CharacterType
{
2023-01-23 03:25:45 +00:00
/ * *
* The BF character has the following behaviors .
* - At idle , dances with ` danceLeft ` and ` danceRight ` if a v a i l a b l e , o r ` i d l e ` i f n o t .
* - When the player hits a note , plays the appropriate ` singDIR ` animation until BF is done singing .
* - If there is a ` singDIR - end ` animation , the ` singDIR ` animation will play once before looping the ` singDIR - end ` animation until BF is done singing .
* - If the player misses or hits a ghost note , plays the appropriate ` singDIR - miss ` animation until BF is done singing .
* /
BF ;
/ * *
* The DAD character has the following behaviors .
* - At idle , dances with ` danceLeft ` and ` danceRight ` if a v a i l a b l e , o r ` i d l e ` i f n o t .
* - When the CPU hits a note , plays the appropriate ` singDIR ` animation until DAD is done singing .
* - If there is a ` singDIR - end ` animation , the ` singDIR ` animation will play once before looping the ` singDIR - end ` animation until DAD is done singing .
2023-02-22 01:58:15 +00:00
* - When the CPU misses a note ( NOTE : T h i s o n l y h a p p e n s v i a s c r i p t , not b y d e f a u l t ) ,
* plays the appropriate ` singDIR - miss ` animation until DAD is done singing .
2023-01-23 03:25:45 +00:00
* /
DAD ;
/ * *
* The GF character has the following behaviors .
* - At idle , dances with ` danceLeft ` and ` danceRight ` if a v a i l a b l e , o r ` i d l e ` i f n o t .
* - If available , ` combo # # # ` animations will play when certain combo counts are reached .
* - For example , ` combo50 ` will play when the player hits 50 notes in a row .
* - Multiple combo animations can be provided for d i f f e r e n t t h r e s h o l d s .
* - If available , ` drop # # # ` animations will play when combos are dropped above certain thresholds .
* - For example , ` drop10 ` will play when the player drops a combo larger than 10.
* - Multiple drop animations can be provided for d i f f e r e n t t h r e s h o l d s ( i . e . d r o p p i n g l a r g e r c o m b o s ) .
* - No drop animation will play if o n e i s n ' t a p p l i c a b l e ( i . e . i f t h e c o m b o c o u n t i s t o o l o w ) .
* /
GF ;
/ * *
* The OTHER character will only perform the ` danceLeft ` / ` danceRight ` or ` idle ` animation by d efault , depending on what ' s a v a i l a b l e .
* Additional behaviors can be performed via scripts .
* /
OTHER ;
2022-03-21 04:19:05 +00:00
}