2023-06-22 05:41:01 +00:00
package funkin . play . notes ;
2024-03-06 02:48:04 +00:00
import flixel . util . FlxSignal . FlxTypedSignal ;
2023-07-04 20:38:10 +00:00
import flixel . FlxG ;
2023-07-14 00:27:45 +00:00
import funkin . play . notes . notestyle . NoteStyle ;
2023-07-04 20:38:10 +00:00
import flixel . group . FlxSpriteGroup ;
import flixel . group . FlxSpriteGroup . FlxTypedSpriteGroup ;
2023-06-22 05:41:01 +00:00
import flixel . tweens . FlxEase ;
import flixel . tweens . FlxTween ;
import flixel . util . FlxSort ;
2023-07-04 20:38:10 +00:00
import funkin . play . notes . NoteHoldCover ;
import funkin . play . notes . NoteSplash ;
import funkin . play . notes . NoteSprite ;
2023-06-22 05:41:01 +00:00
import funkin . play . notes . SustainTrail ;
2023-09-08 21:46:44 +00:00
import funkin . data . song . SongData . SongNoteData ;
2023-11-07 09:04:22 +00:00
import funkin . ui . options . PreferencesMenu ;
2023-07-04 20:38:10 +00:00
import funkin . util . SortUtil ;
2024-01-09 18:16:00 +00:00
import funkin . modding . events . ScriptEvent ;
2023-06-22 05:41:01 +00:00
/ * *
* A group of sprites which handles the receptor , the note splashes , and the notes ( with s u s t a i n s ) for a g i v e n p l a y e r .
* /
class Strumline extends FlxSpriteGroup
{
public static final DIRECTIONS : Array < NoteDirection > = [ NoteDirection . LEFT , NoteDirection . DOWN , NoteDirection . UP , NoteDirection . RIGHT ] ;
2023-07-08 05:03:46 +00:00
public static final STRUMLINE_SIZE : Int = 104 ;
2023-06-22 05:41:01 +00:00
public static final NOTE_SPACING : Int = STRUMLINE_SIZE + 8 ;
// Positional fixes for new strumline graphics.
static final INITIAL_OFFSET = - 0.275 * STRUMLINE_SIZE ;
static final NUDGE : Float = 2.0 ;
static final KEY_COUNT : Int = 4 ;
static final NOTE_SPLASH_CAP : Int = 6 ;
2023-08-31 22:47:23 +00:00
static var RENDER_DISTANCE_MS( get , never ) : Float ;
2023-06-22 05:41:01 +00:00
static function get_RENDER_DISTANCE_MS ( ) : Float
{
return FlxG . height / 0.45 ;
}
2024-03-06 03:27:07 +00:00
/ * *
* Whether this strumline is controlled by the player ' s i n p u t s .
* False means it ' s c o n t r o l l e d b y t h e o p p o n e n t o r B o t P l a y .
* /
2023-06-22 05:41:01 +00:00
public var isPlayer: Bool ;
2024-02-13 02:41:16 +00:00
/ * *
* Usually you want to keep this as is , but if y o u a r e u s i n g a S t r u m l i n e a n d
* playing a sound that has it ' s o w n c o n d u c t o r , s e t t h i s ( L a t e n c y S t a t e f o r e x a m p l e )
* /
2024-03-23 19:34:37 +00:00
public var conductorInUse( get , set ) : Conductor ;
2024-05-09 16:51:03 +00:00
// Used in-game to control the scroll speed within a song, for example if a song has an additive scroll speed of 1 and the base scroll speed is 1, it will be 2 - burgerballs
public var scrollSpeedAdditive: Float = 0 ;
public var scrollSpeed( get , never ) : Float ;
function get_scrollSpeed ( ) : Float
{
return ( PlayState . instance ? . currentChart ? . scrollSpeed ? ? 1.0 ) + s c r o l l S p e e d A d d i t i v e ;
}
2024-03-23 19:34:37 +00:00
var _conductorInUse: Null < Conductor > ;
function get_conductorInUse ( ) : Conductor
{
if ( _conductorInUse == null ) return Conductor . instance ;
return _conductorInUse ;
}
function set_conductorInUse ( value : Conductor ) : Conductor
{
return _conductorInUse = value ;
}
2024-02-13 02:41:16 +00:00
2023-06-22 05:41:01 +00:00
/ * *
* The notes currently being rendered on the strumline .
* This group iterates over this every frame to update note positions .
* The PlayState also iterates over this to calculate user inputs .
* /
public var notes: FlxTypedSpriteGroup < NoteSprite > ;
public var holdNotes: FlxTypedSpriteGroup < SustainTrail > ;
2024-03-06 02:48:04 +00:00
public var onNoteIncoming: FlxTypedSignal < NoteSprite -> Void > ;
2023-06-22 05:41:01 +00:00
var strumlineNotes: FlxTypedSpriteGroup < StrumlineNote > ;
var noteSplashes: FlxTypedSpriteGroup < NoteSplash > ;
2023-07-04 20:38:10 +00:00
var noteHoldCovers: FlxTypedSpriteGroup < NoteHoldCover > ;
2023-06-22 05:41:01 +00:00
2023-07-31 17:42:13 +00:00
var notesVwoosh: FlxTypedSpriteGroup < NoteSprite > ;
var holdNotesVwoosh: FlxTypedSpriteGroup < SustainTrail > ;
2023-07-14 00:27:45 +00:00
final noteStyle : NoteStyle ;
2023-07-27 01:34:38 +00:00
/ * *
* The note data for t h e s o n g . S h o u l d N O T b e a l t e r e d a f t e r t h e s o n g s t a r t s ,
* so we can easily rewind .
* /
2023-06-22 05:41:01 +00:00
var noteData: Array < SongNoteData > = [ ] ;
2023-07-27 01:34:38 +00:00
2023-06-22 05:41:01 +00:00
var nextNoteIndex: Int = - 1 ;
2023-06-27 00:40:26 +00:00
var heldKeys: Array < Bool > = [ ] ;
2023-07-14 00:27:45 +00:00
public function n e w ( noteStyle : NoteStyle , isPlayer : Bool )
2023-06-22 05:41:01 +00:00
{
super ( ) ;
this . isPlayer = isPlayer ;
2023-07-14 00:27:45 +00:00
this . noteStyle = noteStyle ;
2023-06-22 05:41:01 +00:00
this . strumlineNotes = new FlxTypedSpriteGroup < StrumlineNote > ( ) ;
2023-06-27 00:40:26 +00:00
this . strumlineNotes . zIndex = 10 ;
2023-06-22 05:41:01 +00:00
this . add ( this . strumlineNotes ) ;
// Hold notes are added first so they render behind regular notes.
this . holdNotes = new FlxTypedSpriteGroup < SustainTrail > ( ) ;
2023-06-27 00:40:26 +00:00
this . holdNotes . zIndex = 20 ;
2023-06-22 05:41:01 +00:00
this . add ( this . holdNotes ) ;
2023-07-31 17:42:13 +00:00
this . holdNotesVwoosh = new FlxTypedSpriteGroup < SustainTrail > ( ) ;
this . holdNotesVwoosh . zIndex = 21 ;
this . add ( this . holdNotesVwoosh ) ;
2023-06-22 05:41:01 +00:00
this . notes = new FlxTypedSpriteGroup < NoteSprite > ( ) ;
2023-06-27 00:40:26 +00:00
this . notes . zIndex = 30 ;
2023-06-22 05:41:01 +00:00
this . add ( this . notes ) ;
2023-07-31 17:42:13 +00:00
this . notesVwoosh = new FlxTypedSpriteGroup < NoteSprite > ( ) ;
this . notesVwoosh . zIndex = 31 ;
this . add ( this . notesVwoosh ) ;
2023-07-04 20:38:10 +00:00
this . noteHoldCovers = new FlxTypedSpriteGroup < NoteHoldCover > ( 0 , 0 , 4 ) ;
this . noteHoldCovers . zIndex = 40 ;
this . add ( this . noteHoldCovers ) ;
2023-06-22 05:41:01 +00:00
this . noteSplashes = new FlxTypedSpriteGroup < NoteSplash > ( 0 , 0 , NOTE_SPLASH_CAP ) ;
2023-07-04 20:38:10 +00:00
this . noteSplashes . zIndex = 50 ;
2023-06-22 05:41:01 +00:00
this . add ( this . noteSplashes ) ;
2023-07-08 05:03:46 +00:00
this . refresh ( ) ;
2024-03-06 02:48:04 +00:00
this . onNoteIncoming = new FlxTypedSignal < NoteSprite -> Void > ( ) ;
2023-06-27 00:40:26 +00:00
for ( i in 0 ... KEY_COUNT )
2023-06-22 05:41:01 +00:00
{
2023-07-14 00:27:45 +00:00
var child: StrumlineNote = new StrumlineNote ( noteStyle , isPlayer , DIRECTIONS [ i ] ) ;
2023-06-22 05:41:01 +00:00
child . x = getXPos ( DIRECTIONS [ i ] ) ;
child . x += INITIAL_OFFSET ;
child . y = 0 ;
2023-07-14 00:27:45 +00:00
noteStyle . applyStrumlineOffsets ( child ) ;
2023-06-22 05:41:01 +00:00
this . strumlineNotes . add ( child ) ;
}
2023-06-27 00:40:26 +00:00
for ( i in 0 ... KEY_COUNT )
{
heldKeys . push ( false ) ;
}
2023-06-22 05:41:01 +00:00
// This MUST be true for children to update!
this . active = true ;
}
2023-07-08 05:03:46 +00:00
public function refresh ( ) : Void
{
sort ( SortUtil . byZIndex , FlxSort . ASCENDING ) ;
}
2023-06-22 05:41:01 +00:00
override function get_width ( ) : Float
{
2023-06-27 00:40:26 +00:00
return KEY_COUNT * Strumline . NOTE_SPACING ;
2023-06-22 05:41:01 +00:00
}
public override function update ( elapsed : Float ) : Void
{
super . update ( elapsed ) ;
updateNotes ( ) ;
}
2024-01-09 18:16:00 +00:00
/ * *
2024-03-23 19:34:37 +00:00
* Return notes that are within ` Constants . HIT_WINDOW ` ms of the strumline .
* @ return An array of ` NoteSprite ` objects .
2024-01-09 18:16:00 +00:00
* /
2023-06-27 17:43:42 +00:00
public function getNotesMayHit ( ) : Array < NoteSprite >
{
return notes . members . filter ( function ( note : NoteSprite ) {
return note != null && note . alive && ! note . hasBeenHit && note . mayHit ;
} ) ;
}
2024-03-23 19:34:37 +00:00
/ * *
* Return hold notes that are within ` Constants . HIT_WINDOW ` ms of the strumline .
* @ return An array of ` SustainTrail ` objects .
* /
2023-06-27 22:06:33 +00:00
public function getHoldNotesHitOrMissed ( ) : Array < SustainTrail >
{
return holdNotes . members . filter ( function ( holdNote : SustainTrail ) {
return holdNote != null && holdNote . alive && ( holdNote . hitNote || holdNote . missedNote ) ;
} ) ;
}
2023-06-22 05:41:01 +00:00
public function getNoteSprite ( noteData : SongNoteData ) : NoteSprite
{
if ( noteData == null ) return null ;
for ( note in notes . members )
{
if ( note == null ) continue ;
if ( note . alive ) continue ;
if ( note . noteData == noteData ) return note ;
}
return null ;
}
2024-05-09 16:51:03 +00:00
public function resetScrollSpeed ( ) : Void
{
scrollSpeedAdditive = 0 ;
}
2023-06-22 05:41:01 +00:00
public function getHoldNoteSprite ( noteData : SongNoteData ) : SustainTrail
{
if ( noteData == null || ( ( noteData . length ? ? 0.0 ) < = 0 . 0 ) ) r e t u r n n u l l ;
for ( holdNote in holdNotes . members )
{
if ( holdNote == null ) continue ;
if ( holdNote . alive ) continue ;
if ( holdNote . noteData == noteData ) return holdNote ;
}
return null ;
}
2023-07-31 17:42:13 +00:00
/ * *
* Call this when resetting the playstate .
* /
public function vwooshNotes ( ) : Void
{
for ( note in notes . members )
{
if ( note == null ) continue ;
if ( ! note . alive ) continue ;
notes . remove ( note ) ;
notesVwoosh . add ( note ) ;
var targetY: Float = FlxG . height + note . y ;
2023-10-17 04:38:28 +00:00
if ( Preferences . downscroll ) targetY = 0 - note . height ;
2023-07-31 17:42:13 +00:00
FlxTween . tween ( note , { y : targetY } , 0.5 ,
{
ease : FlxEase . expoIn ,
onComplete : function ( twn ) {
note . kill ( ) ;
notesVwoosh . remove ( note , true ) ;
note . destroy ( ) ;
}
} ) ;
}
for ( holdNote in holdNotes . members )
{
if ( holdNote == null ) continue ;
if ( ! holdNote . alive ) continue ;
holdNotes . remove ( holdNote ) ;
holdNotesVwoosh . add ( holdNote ) ;
var targetY: Float = FlxG . height + holdNote . y ;
2023-10-17 04:38:28 +00:00
if ( Preferences . downscroll ) targetY = 0 - holdNote . height ;
2023-07-31 17:42:13 +00:00
FlxTween . tween ( holdNote , { y : targetY } , 0.5 ,
{
ease : FlxEase . expoIn ,
onComplete : function ( twn ) {
holdNote . kill ( ) ;
holdNotesVwoosh . remove ( holdNote , true ) ;
holdNote . destroy ( ) ;
}
} ) ;
}
}
2023-06-22 05:41:01 +00:00
/ * *
* For a note ' s s t r u m T i m e , c a l c u l a t e i t s Y p o s i t i o n r e l a t i v e t o t h e s t r u m l i n e .
* NOTE : Assumes Conductor and PlayState are both initialized .
* @ param strumTime
* @ return Float
* /
2024-02-13 02:41:16 +00:00
public function calculateNoteYPos ( strumTime : Float , vwoosh : Bool = true ) : Float
2023-06-22 05:41:01 +00:00
{
// Make the note move faster visually as it moves offscreen.
2024-01-03 07:51:52 +00:00
// var vwoosh:Float = (strumTime < Conductor.songPosition) && vwoosh ? 2.0 : 1.0;
// ^^^ commented this out... do NOT make it move faster as it moves offscreen!
var vwoosh: Float = 1.0 ;
2023-06-22 05:41:01 +00:00
2024-04-11 01:31:22 +00:00
return
Constants . PIXELS_PER_MS * ( conductorInUse . songPosition - strumTime - Conductor . instance . inputOffset ) * scrollSpeed * vwoosh * ( Preferences . downscroll ? 1 : - 1 ) ;
2023-06-22 05:41:01 +00:00
}
function updateNotes ( ) : Void
{
if ( noteData . length == 0 ) return ;
2024-03-23 19:34:37 +00:00
// Ensure note data gets reset if the song happens to loop.
2024-03-29 04:49:02 +00:00
// NOTE: I had to remove this line because it was causing notes visible during the countdown to be placed multiple times.
// I don't remember what bug I was trying to fix by adding this.
// if (conductorInUse.currentStep == 0) nextNoteIndex = 0;
2024-03-23 19:34:37 +00:00
2023-11-16 05:02:42 +00:00
var songStart: Float = PlayState . instance ? . startTimestamp ? ? 0.0 ;
2024-02-13 02:41:16 +00:00
var hitWindowStart: Float = conductorInUse . songPosition - Constants . HIT_WINDOW_MS ;
var renderWindowStart: Float = conductorInUse . songPosition + RENDER_DISTANCE_MS ;
2023-06-22 05:41:01 +00:00
for ( noteIndex in nextNoteIndex ... noteData . length )
{
var note: Null < SongNoteData > = noteData [ noteIndex ] ;
2023-07-27 01:34:38 +00:00
if ( note == null ) continue ; // Note is blank
if ( note . time < songStart || note . time < hitWindowStart )
{
// Note is in the past, skip it.
nextNoteIndex = noteIndex + 1 ;
continue ;
}
if ( note . time > renderWindowStart ) break ; // Note is too far ahead to render
2023-06-22 05:41:01 +00:00
2023-06-27 00:40:26 +00:00
var noteSprite = buildNoteSprite ( note ) ;
2023-06-22 05:41:01 +00:00
if ( note . length > 0 )
{
2023-06-27 00:40:26 +00:00
noteSprite . holdNoteSprite = buildHoldNoteSprite ( note ) ;
2023-06-22 05:41:01 +00:00
}
2023-07-27 01:34:38 +00:00
nextNoteIndex = noteIndex + 1 ; // Increment the nextNoteIndex rather than splicing the array, because splicing is slow.
2024-03-06 02:48:04 +00:00
onNoteIncoming . dispatch ( noteSprite ) ;
2023-06-22 05:41:01 +00:00
}
// Update rendering of notes.
for ( note in notes . members )
{
2024-01-16 03:10:42 +00:00
if ( note == null || ! note . alive ) continue ;
2023-06-22 05:41:01 +00:00
2023-06-27 00:40:26 +00:00
var vwoosh: Bool = note . holdNoteSprite == null ;
2023-06-27 17:43:42 +00:00
// Set the note's position.
2023-06-27 00:40:26 +00:00
note . y = this . y - INITIAL_OFFSET + calculateNoteYPos ( note . strumTime , vwoosh ) ;
2023-06-22 05:41:01 +00:00
2023-06-27 17:43:42 +00:00
// If the note is miss
2023-10-17 04:38:28 +00:00
var isOffscreen = Preferences . downscroll ? note . y > FlxG . height : note . y < - note . height ;
2023-06-27 17:43:42 +00:00
if ( note . handledMiss && isOffscreen )
2023-06-22 05:41:01 +00:00
{
2023-06-27 17:43:42 +00:00
killNote ( note ) ;
2023-06-22 05:41:01 +00:00
}
}
// Update rendering of hold notes.
for ( holdNote in holdNotes . members )
{
if ( holdNote == null || ! holdNote . alive ) continue ;
2024-02-13 02:41:16 +00:00
if ( conductorInUse . songPosition > holdNote . strumTime && holdNote . hitNote && ! holdNote . missedNote )
2023-06-27 00:40:26 +00:00
{
if ( isPlayer && ! isKeyHeld ( holdNote . noteDirection ) )
{
// Stopped pressing the hold note.
playStatic ( holdNote . noteDirection ) ;
holdNote . missedNote = true ;
2023-07-08 05:03:46 +00:00
holdNote . visible = true ;
2024-01-15 19:20:44 +00:00
holdNote . alpha = 0.0 ; // Completely hide the dropped hold note.
2023-06-27 00:40:26 +00:00
}
}
2023-07-23 00:16:43 +00:00
var renderWindowEnd = holdNote . strumTime + holdNote . fullSustainLength + Constants . HIT_WINDOW_MS + RENDER_DISTANCE_MS / 8 ;
2023-06-22 05:41:01 +00:00
2024-02-13 02:41:16 +00:00
if ( holdNote . missedNote && conductorInUse . songPosition >= renderWindowEnd )
2023-06-22 05:41:01 +00:00
{
// Hold note is offscreen, kill it.
holdNote . visible = false ;
holdNote . kill ( ) ; // Do not destroy! Recycling is faster.
}
2023-06-22 23:28:39 +00:00
e lse if ( holdNote . hitNote && holdNote . sustainLength <= 0 )
2023-06-22 05:41:01 +00:00
{
// Hold note is completed, kill it.
2023-06-27 22:06:33 +00:00
if ( isKeyHeld ( holdNote . noteDirection ) )
{
playPress ( holdNote . noteDirection ) ;
}
e lse
{
playStatic ( holdNote . noteDirection ) ;
}
2023-07-06 02:11:58 +00:00
2024-04-03 08:52:28 +00:00
if ( holdNote . cover != null && isPlayer )
2023-07-06 02:11:58 +00:00
{
holdNote . cover . playEnd ( ) ;
}
2024-04-03 08:52:28 +00:00
e lse if ( holdNote . cover != null )
{
// *lightning* *zap* *crackle*
holdNote . cover . visible = false ;
holdNote . cover . kill ( ) ;
}
2023-07-06 02:11:58 +00:00
2023-06-22 05:41:01 +00:00
holdNote . visible = false ;
holdNote . kill ( ) ;
}
2023-06-27 00:40:26 +00:00
e lse if ( holdNote . missedNote && ( holdNote . fullSustainLength > holdNote . sustainLength ) )
2023-06-22 05:41:01 +00:00
{
2023-06-27 00:40:26 +00:00
// Hold note was dropped before completing, keep it in its clipped state.
2023-06-22 05:41:01 +00:00
holdNote . visible = true ;
2023-07-23 00:16:43 +00:00
var yOffset: Float = ( holdNote . fullSustainLength - holdNote . sustainLength ) * Constants . PIXELS_PER_MS ;
2023-06-27 00:40:26 +00:00
var vwoosh: Bool = false ;
2023-06-22 05:41:01 +00:00
2023-10-17 04:38:28 +00:00
if ( Preferences . downscroll )
2023-06-22 05:41:01 +00:00
{
2023-06-27 00:40:26 +00:00
holdNote . y = this . y + calculateNoteYPos ( holdNote . strumTime , vwoosh ) - holdNote . height + STRUMLINE_SIZE / 2 ;
2023-06-22 05:41:01 +00:00
}
e lse
{
2023-06-27 00:40:26 +00:00
holdNote . y = this . y - INITIAL_OFFSET + calculateNoteYPos ( holdNote . strumTime , vwoosh ) + yOffset + STRUMLINE_SIZE / 2 ;
2023-06-22 05:41:01 +00:00
}
2024-04-03 08:52:28 +00:00
// Clean up the cover.
if ( holdNote . cover != null )
{
holdNote . cover . visible = false ;
holdNote . cover . kill ( ) ;
}
2023-06-22 05:41:01 +00:00
}
2024-02-13 02:41:16 +00:00
e lse if ( conductorInUse . songPosition > holdNote . strumTime && holdNote . hitNote )
2023-06-22 05:41:01 +00:00
{
2023-06-27 00:40:26 +00:00
// Hold note is currently being hit, clip it off.
holdConfirm ( holdNote . noteDirection ) ;
2023-06-22 05:41:01 +00:00
holdNote . visible = true ;
2024-02-13 02:41:16 +00:00
holdNote . sustainLength = ( holdNote . strumTime + holdNote . fullSustainLength ) - conductorInUse . songPosition ;
2023-06-22 05:41:01 +00:00
2023-06-27 22:06:33 +00:00
if ( holdNote . sustainLength <= 10 )
{
holdNote . visible = false ;
}
2023-10-17 04:38:28 +00:00
if ( Preferences . downscroll )
2023-06-22 05:41:01 +00:00
{
2023-06-27 00:40:26 +00:00
holdNote . y = this . y - holdNote . height + STRUMLINE_SIZE / 2 ;
2023-06-22 05:41:01 +00:00
}
e lse
{
2023-06-27 00:40:26 +00:00
holdNote . y = this . y - INITIAL_OFFSET + STRUMLINE_SIZE / 2 ;
2023-06-22 05:41:01 +00:00
}
}
e lse
{
// Hold note is new, render it normally.
holdNote . visible = true ;
2023-06-27 00:40:26 +00:00
var vwoosh: Bool = false ;
2023-06-22 05:41:01 +00:00
2023-10-17 04:38:28 +00:00
if ( Preferences . downscroll )
2023-06-22 05:41:01 +00:00
{
2023-06-27 00:40:26 +00:00
holdNote . y = this . y + calculateNoteYPos ( holdNote . strumTime , vwoosh ) - holdNote . height + STRUMLINE_SIZE / 2 ;
2023-06-22 05:41:01 +00:00
}
e lse
{
2023-06-27 00:40:26 +00:00
holdNote . y = this . y - INITIAL_OFFSET + calculateNoteYPos ( holdNote . strumTime , vwoosh ) + STRUMLINE_SIZE / 2 ;
2023-06-22 05:41:01 +00:00
}
}
}
2023-07-08 05:03:46 +00:00
// Update rendering of pressed keys.
for ( dir in DIRECTIONS )
{
if ( isKeyHeld ( dir ) && getByDirection ( dir ) . getCurrentAnimation ( ) == " s t a t i c " )
{
playPress ( dir ) ;
}
}
2023-06-22 05:41:01 +00:00
}
2023-07-27 01:34:38 +00:00
/ * *
* Called when the PlayState skips a large amount of time forward or backward .
* /
public function handleSkippedNotes ( ) : Void
{
// By calling clean(), we remove all existing notes so they can be re-added.
clean ( ) ;
// By setting noteIndex to 0, the next update will skip past all the notes that are in the past.
nextNoteIndex = 0 ;
}
2023-06-22 05:41:01 +00:00
public function onBeatHit ( ) : Void
{
if ( notes . members . length > 1 ) notes . members . insertionSort ( compareNoteSprites . bind ( FlxSort . ASCENDING ) ) ;
if ( holdNotes . members . length > 1 ) holdNotes . members . insertionSort ( compareHoldNoteSprites . bind ( FlxSort . ASCENDING ) ) ;
}
2023-06-27 00:40:26 +00:00
public function pressKey ( dir : NoteDirection ) : Void
{
heldKeys [ dir ] = true ;
}
public function releaseKey ( dir : NoteDirection ) : Void
{
heldKeys [ dir ] = false ;
}
public function isKeyHeld ( dir : NoteDirection ) : Bool
{
return heldKeys [ dir ] ;
}
2023-07-26 01:39:19 +00:00
/ * *
* Called when the song is reset .
* Removes any special animations and the like .
* Doesn ' t r e s e t t h e n o t e s f r o m t h e c h a r t , t h a t ' s handled by the PlayState .
* /
public function clean ( ) : Void
{
for ( note in notes . members )
{
if ( note == null ) continue ;
killNote ( note ) ;
}
for ( holdNote in holdNotes . members )
{
if ( holdNote == null ) continue ;
holdNote . kill ( ) ;
}
for ( splash in noteSplashes )
{
if ( splash == null ) continue ;
splash . kill ( ) ;
}
for ( cover in noteHoldCovers )
{
if ( cover == null ) continue ;
cover . kill ( ) ;
}
2023-08-03 16:19:36 +00:00
heldKeys = [ false , false , false , false ] ;
for ( dir in DIRECTIONS )
{
playStatic ( dir ) ;
}
2024-05-09 16:51:03 +00:00
scrollSpeedAdditive = 0 ;
2023-07-26 01:39:19 +00:00
}
2023-06-22 05:41:01 +00:00
public function applyNoteData ( data : Array < SongNoteData > ) : Void
{
this . notes . clear ( ) ;
this . noteData = data . copy ( ) ;
this . nextNoteIndex = 0 ;
// Sort the notes by strumtime.
this . noteData . insertionSort ( compareNoteData . bind ( FlxSort . ASCENDING ) ) ;
}
2024-01-16 03:10:42 +00:00
/ * *
* @ param note The note to hit .
* @ param removeNote True to remove the note immediately , false to make it transparent and let it move offscreen .
* /
public function hitNote ( note : NoteSprite , removeNote : Bool = true ) : Void
2023-06-22 05:41:01 +00:00
{
playConfirm ( note . direction ) ;
2023-06-22 23:28:39 +00:00
note . hasBeenHit = true ;
2024-01-16 03:10:42 +00:00
if ( removeNote )
{
killNote ( note ) ;
}
e lse
{
note . alpha = 0.5 ;
note . desaturate ( ) ;
}
2023-06-22 23:28:39 +00:00
if ( note . holdNoteSprite != null )
{
note . holdNoteSprite . hitNote = true ;
note . holdNoteSprite . missedNote = false ;
note . holdNoteSprite . alpha = 1.0 ;
2024-05-08 05:20:39 +00:00
note . holdNoteSprite . sustainLength = ( note . holdNoteSprite . strumTime + note . holdNoteSprite . fullSustainLength ) - conductorInUse . songPosition ;
2023-06-22 23:28:39 +00:00
}
2023-06-22 05:41:01 +00:00
}
public function killNote ( note : NoteSprite ) : Void
{
2023-07-26 01:39:19 +00:00
if ( note == null ) return ;
2023-06-22 05:41:01 +00:00
note . visible = false ;
notes . remove ( note , false ) ;
note . kill ( ) ;
if ( note . holdNoteSprite != null )
{
2023-06-22 23:28:39 +00:00
note . holdNoteSprite . missedNote = true ;
2023-07-08 05:03:46 +00:00
note . holdNoteSprite . visible = false ;
2023-06-22 05:41:01 +00:00
}
}
public function getByIndex ( index : Int ) : StrumlineNote
{
return this . strumlineNotes . members [ index ] ;
}
public function getByDirection ( direction : NoteDirection ) : StrumlineNote
{
return getByIndex ( DIRECTIONS . indexOf ( direction ) ) ;
}
public function playStatic ( direction : NoteDirection ) : Void
{
getByDirection ( direction ) . playStatic ( ) ;
}
public function playPress ( direction : NoteDirection ) : Void
{
getByDirection ( direction ) . playPress ( ) ;
}
public function playConfirm ( direction : NoteDirection ) : Void
{
getByDirection ( direction ) . playConfirm ( ) ;
}
public function holdConfirm ( direction : NoteDirection ) : Void
{
getByDirection ( direction ) . holdConfirm ( ) ;
}
public function isConfirm ( direction : NoteDirection ) : Bool
{
return getByDirection ( direction ) . isConfirm ( ) ;
}
public function playNoteSplash ( direction : NoteDirection ) : Void
{
// TODO: Add a setting to disable note splashes.
// if (Settings.noSplash) return;
2023-07-14 00:27:45 +00:00
if ( ! noteStyle . isNoteSplashEnabled ( ) ) return ;
2023-06-22 05:41:01 +00:00
var splash: NoteSplash = this . constructNoteSplash ( ) ;
if ( splash != null )
{
splash . play ( direction ) ;
splash . x = this . x ;
splash . x += getXPos ( direction ) ;
splash . x += INITIAL_OFFSET ;
splash . y = this . y ;
splash . y -= INITIAL_OFFSET ;
splash . y += 0 ;
}
}
2023-07-06 02:11:58 +00:00
public function playNoteHoldCover ( holdNote : SustainTrail ) : Void
2023-07-04 20:38:10 +00:00
{
// TODO: Add a setting to disable note splashes.
// if (Settings.noSplash) return;
2023-07-14 00:27:45 +00:00
if ( ! noteStyle . isHoldNoteCoverEnabled ( ) ) return ;
2023-07-04 20:38:10 +00:00
var cover: NoteHoldCover = this . constructNoteHoldCover ( ) ;
if ( cover != null )
{
2023-07-06 02:11:58 +00:00
cover . holdNote = holdNote ;
holdNote . cover = cover ;
cover . visible = true ;
cover . playStart ( ) ;
2023-07-04 20:38:10 +00:00
cover . x = this . x ;
2023-07-06 02:11:58 +00:00
cover . x += getXPos ( holdNote . noteDirection ) ;
cover . x += STRUMLINE_SIZE / 2 ;
cover . x -= cover . width / 2 ;
2023-07-08 05:03:46 +00:00
cover . x += - 12 ; // Manual tweaking because fuck.
2023-07-04 20:38:10 +00:00
cover . y = this . y ;
2023-07-06 02:11:58 +00:00
cover . y += INITIAL_OFFSET ;
cover . y += STRUMLINE_SIZE / 2 ;
2023-07-08 05:03:46 +00:00
cover . y += - 96 ; // Manual tweaking because fuck.
2023-07-04 20:38:10 +00:00
}
}
2023-06-27 00:40:26 +00:00
public function buildNoteSprite ( note : SongNoteData ) : NoteSprite
2023-06-22 05:41:01 +00:00
{
var noteSprite: NoteSprite = constructNoteSprite ( ) ;
if ( noteSprite != null )
{
noteSprite . direction = note . getDirection ( ) ;
noteSprite . noteData = note ;
noteSprite . x = this . x ;
noteSprite . x += getXPos ( DIRECTIONS [ note . getDirection ( ) % KEY_COUNT ] ) ;
noteSprite . x -= NUDGE ;
// noteSprite.x += INITIAL_OFFSET;
noteSprite . y = - 9999 ;
}
2023-06-27 00:40:26 +00:00
return noteSprite ;
2023-06-22 05:41:01 +00:00
}
2023-06-27 00:40:26 +00:00
public function buildHoldNoteSprite ( note : SongNoteData ) : SustainTrail
2023-06-22 05:41:01 +00:00
{
var holdNoteSprite: SustainTrail = constructHoldNoteSprite ( ) ;
if ( holdNoteSprite != null )
{
2024-05-09 16:51:03 +00:00
holdNoteSprite . parentStrumline = this ;
2023-06-22 05:41:01 +00:00
holdNoteSprite . noteData = note ;
holdNoteSprite . strumTime = note . time ;
holdNoteSprite . noteDirection = note . getDirection ( ) ;
holdNoteSprite . fullSustainLength = note . length ;
holdNoteSprite . sustainLength = note . length ;
2023-06-22 23:28:39 +00:00
holdNoteSprite . missedNote = false ;
holdNoteSprite . hitNote = false ;
2023-07-08 05:03:46 +00:00
holdNoteSprite . visible = true ;
2023-06-27 00:40:26 +00:00
holdNoteSprite . alpha = 1.0 ;
2023-06-22 05:41:01 +00:00
holdNoteSprite . x = this . x ;
holdNoteSprite . x += getXPos ( DIRECTIONS [ note . getDirection ( ) % KEY_COUNT ] ) ;
holdNoteSprite . x += STRUMLINE_SIZE / 2 ;
holdNoteSprite . x -= holdNoteSprite . width / 2 ;
holdNoteSprite . y = - 9999 ;
}
2023-06-27 00:40:26 +00:00
return holdNoteSprite ;
2023-06-22 05:41:01 +00:00
}
/ * *
* Custom recycling behavior .
* /
function constructNoteSplash ( ) : NoteSplash
{
var result: NoteSplash = null ;
// If we haven't filled the pool yet...
if ( noteSplashes . length < noteSplashes . maxSize )
{
// Create a new note splash.
result = new NoteSplash ( ) ;
this . noteSplashes . add ( result ) ;
}
e lse
{
// Else, find a note splash which is inactive so we can revive it.
result = this . noteSplashes . getFirstAvailable ( ) ;
if ( result != null )
{
result . revive ( ) ;
}
e lse
{
// The note splash pool is full and all note splashes are active,
// so we just pick one at random to destroy and restart.
result = FlxG . random . getObject ( this . noteSplashes . members ) ;
}
}
return result ;
}
2023-07-04 20:38:10 +00:00
/ * *
* Custom recycling behavior .
* /
function constructNoteHoldCover ( ) : NoteHoldCover
{
var result: NoteHoldCover = null ;
// If we haven't filled the pool yet...
if ( noteHoldCovers . length < noteHoldCovers . maxSize )
{
// Create a new note hold cover.
result = new NoteHoldCover ( ) ;
this . noteHoldCovers . add ( result ) ;
}
e lse
{
// Else, find a note splash which is inactive so we can revive it.
result = this . noteHoldCovers . getFirstAvailable ( ) ;
if ( result != null )
{
result . revive ( ) ;
}
e lse
{
// The note hold cover pool is full and all note hold covers are active,
// so we just pick one at random to destroy and restart.
result = FlxG . random . getObject ( this . noteHoldCovers . members ) ;
}
}
return result ;
}
2023-06-22 05:41:01 +00:00
/ * *
* Custom recycling behavior .
* /
function constructNoteSprite ( ) : NoteSprite
{
var result: NoteSprite = null ;
// Else, find a note which is inactive so we can revive it.
result = this . notes . getFirstAvailable ( ) ;
if ( result != null )
{
// Revive and reuse the note.
result . revive ( ) ;
}
e lse
{
// The note sprite pool is full and all note splashes are active.
// We have to create a new note.
2023-07-14 00:27:45 +00:00
result = new NoteSprite ( noteStyle ) ;
2023-06-22 05:41:01 +00:00
this . notes . add ( result ) ;
}
return result ;
}
/ * *
* Custom recycling behavior .
* /
function constructHoldNoteSprite ( ) : SustainTrail
{
var result: SustainTrail = null ;
// Else, find a note which is inactive so we can revive it.
result = this . holdNotes . getFirstAvailable ( ) ;
if ( result != null )
{
// Revive and reuse the note.
result . revive ( ) ;
}
e lse
{
// The note sprite pool is full and all note splashes are active.
// We have to create a new note.
2024-03-29 04:52:20 +00:00
result = new SustainTrail ( 0 , 0 , noteStyle ) ;
2023-06-22 05:41:01 +00:00
this . holdNotes . add ( result ) ;
}
return result ;
}
function getXPos ( direction : NoteDirection ) : Float
{
return switch ( direction )
{
c ase NoteDirection . LEFT : 0 ;
c ase NoteDirection . DOWN : 0 + ( 1 * Strumline . NOTE_SPACING ) ;
c ase NoteDirection . UP : 0 + ( 2 * Strumline . NOTE_SPACING ) ;
c ase NoteDirection . RIGHT : 0 + ( 3 * Strumline . NOTE_SPACING ) ;
d efault : 0 ;
}
}
/ * *
* Apply a small animation which moves the arrow down and fades it in .
* Only plays at the start of Free Play songs .
*
* Note that modifying the offset of the whole strumline won ' t h a v e t h e
* @ param arrow The arrow to animate .
* @ param index The index of the arrow in the strumline .
* /
2023-06-27 21:22:51 +00:00
function fadeInArrow ( index : Int , arrow : StrumlineNote ) : Void
2023-06-22 05:41:01 +00:00
{
arrow . y -= 10 ;
2023-06-27 21:22:51 +00:00
arrow . alpha = 0.0 ;
FlxTween . tween ( arrow , { y : arrow . y + 10 , alpha : 1 } , 1 , { ease : FlxEase . circOut , startDelay : 0.5 + ( 0.2 * index ) } ) ;
2023-06-22 05:41:01 +00:00
}
public function fadeInArrows ( ) : Void
{
2023-06-27 21:22:51 +00:00
for ( index => arrow in this . strumlineNotes . members . keyValueIterator ( ) )
2023-06-22 05:41:01 +00:00
{
2023-06-27 21:22:51 +00:00
fadeInArrow ( index , arrow ) ;
2023-06-22 05:41:01 +00:00
}
}
function compareNoteData ( order : Int , a : SongNoteData , b : SongNoteData ) : Int
{
return FlxSort . byValues ( order , a . time , b . time ) ;
}
function compareNoteSprites ( order : Int , a : NoteSprite , b : NoteSprite ) : Int
{
return FlxSort . byValues ( order , a ? . strumTime , b ? . s t r u m T i m e ) ;
}
function compareHoldNoteSprites ( order : Int , a : SustainTrail , b : SustainTrail ) : Int
{
return FlxSort . byValues ( order , a ? . strumTime , b ? . s t r u m T i m e ) ;
}
}