-
-
Notifications
You must be signed in to change notification settings - Fork 311
Expand file tree
/
Copy pathPlayState.hx
More file actions
2215 lines (1915 loc) · 67.7 KB
/
PlayState.hx
File metadata and controls
2215 lines (1915 loc) · 67.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package funkin.game;
import flixel.FlxState;
import flixel.FlxSubState;
import flixel.graphics.FlxGraphic;
import flixel.math.FlxPoint;
import flixel.sound.FlxSound;
import flixel.text.FlxText;
import flixel.tweens.FlxTween;
import flixel.tweens.misc.VarTween;
import flixel.ui.FlxBar;
import flixel.util.FlxColor;
import flixel.util.FlxSort;
import flixel.util.FlxTimer;
import funkin.backend.FunkinText;
import funkin.backend.chart.Chart;
import funkin.backend.chart.ChartData;
import funkin.backend.chart.EventsData;
import funkin.backend.scripting.DummyScript;
import funkin.backend.scripting.Script;
import funkin.backend.scripting.ScriptPack;
import funkin.backend.scripting.events.*;
import funkin.backend.scripting.events.gameplay.*;
import funkin.backend.scripting.events.note.*;
import funkin.backend.system.Conductor;
import funkin.backend.system.RotatingSpriteGroup;
import funkin.editors.SaveWarning;
import funkin.editors.charter.Charter;
import funkin.editors.charter.CharterSelection;
import funkin.game.SplashHandler;
import funkin.game.cutscenes.*;
import funkin.menus.*;
import funkin.backend.week.WeekData;
import funkin.savedata.FunkinSave;
import haxe.io.Path;
using StringTools;
@:access(flixel.text.FlxText.FlxTextFormatRange)
@:access(funkin.game.StrumLine)
class PlayState extends MusicBeatState
{
/**
* Current PlayState instance.
*/
public static var instance:PlayState = null;
/**
* SONG DATA (Chart, Metadata).
*/
public static var SONG:ChartData;
/**
* Whenever the song is being played in Story Mode.
*/
public static var isStoryMode:Bool = false;
/**
* The week data of the current week
*/
public static var storyWeek:WeekData = null;
/**
* The remaining songs in the Story Mode playlist.
*/
public static var storyPlaylist:Array<String> = [];
/**
* The remaining variations to play with the Story Mode
*/
public static var storyVariations:Array<String> = [];
/**
* The selected difficulty name.
*/
public static var difficulty:String = Flags.DEFAULT_DIFFICULTY;
/**
* The selected variation name. (It can be null)
*/
public static var variation:Null<String>;
/**
* Whenever the week is coming from the mods folder or not.
*/
public static var fromMods:Bool = false;
/**
* Whenever Charting Mode is enabled for this song.
*/
public static var chartingMode:Bool = false;
/**
* Whenever the song is started with opponent mode on.
*/
public static var opponentMode:Bool = Flags.DEFAULT_OPPONENT_MODE;
/**
* Whenever the song is started with co-op mode on.
*/
public static var coopMode:Bool = Flags.DEFAULT_COOP_MODE;
/**
* Script Pack of all the scripts being ran.
*/
public var scripts:ScriptPack;
/**
* Array of all the players in the stage.
*/
public var strumLines:FlxTypedGroup<StrumLine> = new FlxTypedGroup<StrumLine>();
/**
* Death counter on current week (or song if from freeplay).
*/
public static var deathCounter:Int = 0;
/**
* Game Over Song. (assets/music/gameOver.ogg).
*/
public var gameOverSong:String = Flags.DEFAULT_GAMEOVER_MUSIC;
/**
* Game Over Song. (assets/sounds/gameOverSFX.ogg).
*/
public var lossSFX:String = Flags.DEFAULT_GAMEOVERSFX_SOUND;
/**
* Game Over End SFX, used when retrying. (assets/sounds/gameOverEnd.ogg).
*/
public var retrySFX:String = Flags.DEFAULT_GAMEOVEREND_SOUND;
/**
* Current Stage.
*/
public var stage:Stage;
/**
* Whenever the score will save when you beat the song.
*/
public var validScore:Bool = true;
/**
* Whenever the player can die.
*/
public var canDie:Bool = !opponentMode && !coopMode;
/**
* Whenever Ghost Tapping is enabled.
*/
public var ghostTapping:Bool = Options.ghostTapping;
/**
* Whenever the opponent can die.
*/
public var canDadDie:Bool = opponentMode && !coopMode;
/**
* Current scroll speed for all strums.
* To set a scroll speed for a specific strum, use `strum.scrollSpeed`.
*/
public var scrollSpeed:Float = 0;
/**
* Whenever the game is in downscroll or not. (Can be set)
*/
public var downscroll(get, set):Bool;
private inline function set_downscroll(v:Bool) return camHUD.downscroll = v;
private inline function get_downscroll():Bool return camHUD.downscroll;
/**
* Instrumental sound (Inst.ogg).
*/
public var inst:FlxSound;
/**
* Vocals sound (Vocals.ogg).
*/
public var vocals:FlxSound;
/**
* Dad character.
*/
public var dad(get, set):Character;
/**
* Girlfriend character.
*/
public var gf(get, set):Character;
/**
* Boyfriend character.
*/
public var boyfriend(get, set):Character;
/**
* Boyfriend character.
* Same as boyfriend, just shorter.
**/
public var bf(get, set):Character;
/**
* Strum line position.
*/
public var strumLine:FlxObject;
/**
* Number of ratings.
*/
public var ratingNum:Int = 0;
/**
* Object defining the camera follow target.
*/
public var camFollow:FlxObject;
/**
* Previous cam follow.
*/
private static var smoothTransitionData:PlayStateTransitionData;
/**
* Player strums.
*/
public var playerStrums(get, set):StrumLine;
/**
* CPU strums.
*/
public var cpuStrums(get, set):StrumLine;
/**
* Shortcut to `playerStrums`.
*/
public var player(get, set):StrumLine;
/**
* Shortcut to `cpuStrums`.
*/
public var cpu(get, set):StrumLine;
/**
* Note splashes container.
*/
public var splashHandler:SplashHandler;
/**
* Whenever the vocals should be muted when a note is missed.
*/
public var muteVocalsOnMiss:Bool = Flags.DEFAULT_MUTE_VOCALS_ON_MISS;
/**
* Whenever the player can press 7, 8 or 9 to access the debug menus.
*/
public var canAccessDebugMenus:Bool = !Flags.DISABLE_EDITORS;
/**
* Whether or not to show the secret gitaroo pause.
*/
public var allowGitaroo:Bool = Flags.DEFAULT_GITAROO;
/**
* Whether or not to bop the icons on beat.
*/
public var doIconBop:Bool = Flags.DEFAULT_ICONBOP;
/**
* Current song name (lowercase).
*/
public var curSong:String = "";
/**
* Current song name (lowercase and spaces to dashes).
*/
public var curSongID:String = "";
/**
* Current stage name.
*/
public var curStage(get, set):String;
/**
* Interval at which Girlfriend dances.
*/
public var gfSpeed(get, set):Int;
/**
* Current health. Goes from 0 to maxHealth (defaults to 2).
*/
public var health(default, set):Float = 1;
/**
* Maximum health the player can have. Defaults to 2.
*/
public var maxHealth(default, set):Float = Flags.DEFAULT_MAX_HEALTH;
/**
* Current combo.
*/
public var combo:Int = 0;
/**
* Whenever the misses should show "Combo Breaks" instead of "Misses".
*/
public var comboBreaks:Bool = !Options.ghostTapping;
/**
* Health bar background.
*/
public var healthBarBG:FlxSprite;
/**
* Health bar.
*/
public var healthBar:FlxBar;
/**
* Whenever the music has been generated.
*/
public var generatedMusic:Bool = false;
/**
* Whenever the song is currently being started.
*/
public var startingSong:Bool = false;
/**
* Player's icon.
*/
public var iconP1:HealthIcon;
/**
* Opponent's icon.
*/
public var iconP2:HealthIcon;
/**
* Every active icon that will be updated during gameplay (defaults to `iconP1` and `iconP1` between `create` and `postCreate` in scripts).
*/
public var iconArray:Array<HealthIcon> = [];
/**
* Camera for the HUD (notes, misses).
*/
public var camHUD:HudCamera;
/**
* Camera for the game (stages, characters).
*/
public var camGame:FlxCamera;
/**
* The player's current score.
*/
public var songScore:Int = 0;
/**
* The player's amount of misses.
*/
public var misses:Int = 0;
/**
* The player's accuracy (shortcut to `accuracyPressedNotes / totalAccuracyAmount`).
*/
public var accuracy(get, set):Float;
/**
* The number of pressed notes.
*/
public var accuracyPressedNotes:Float = 0;
/**
* The total accuracy amount.
*/
public var totalAccuracyAmount:Float = 0;
/**
* FunkinText that shows your score.
*/
public var scoreTxt:FunkinText;
/**
* FunkinText that shows your amount of misses.
*/
public var missesTxt:FunkinText;
/**
* FunkinText that shows your accuracy.
*/
public var accuracyTxt:FunkinText;
/**
* Score for the current week.
*/
public static var campaignScore:Int = 0;
/**
* Misses for the current week.
*/
public static var campaignMisses:Int = 0;
/**
* Accuracy for the current week.
*/
public static var campaignAccuracy(get, never):Float;
public static var campaignAccuracyTotal:Float = 0;
public static var campaignAccuracyCount:Float = 0;
/**
* Camera zoom at which the game lerps to.
*/
public var defaultCamZoom:Float = Flags.DEFAULT_CAM_ZOOM;
/**
* Speed at which the game camera zoom lerps to.
*/
public var camGameZoomLerp:Float = Flags.DEFAULT_CAM_ZOOM_LERP;
/**
* The current multiplier for game camera zooming.
*/
public var camGameZoomMult:Float = Flags.DEFAULT_CAM_ZOOM_MULT;
/**
* Camera zoom at which the hud lerps to.
*/
public var defaultHudZoom:Float = Flags.DEFAULT_HUD_ZOOM;
/**
* Speed at which the hud camera zoom lerps to.
*/
public var camHUDZoomLerp:Float = Flags.DEFAULT_HUD_ZOOM_LERP;
/**
* The current multiplier for game camera zooming.
*/
public var camHUDZoomMult:Float = Flags.DEFAULT_HUD_ZOOM_MULT;
/**
* Camera zoom at which the cameras that zooms lerps to.
* (Only used if useCamZoomMult is on).
*/
public var defaultZoom:Float = Flags.DEFAULT_ZOOM;
/**
* Speed at which the cameras that zooms zoom lerps to.
* (Only used if useCamZoomMult is on).
*/
public var camZoomLerp:Float = Flags.DEFAULT_ZOOM_LERP;
/**
* Whenever cam zooming is enabled, enables on a note hit if not cancelled.
*/
public var camZooming:Bool = false;
/**
* Interval of cam zooming (in conductor values).
* Example: If Interval is 1 and Beat Type is on MEASURE, it'll zoom every a measure.
* NOTE: Will set to 4 if not found any other time signatures unlike 4/4.
*/
public var camZoomingInterval:Float = Flags.DEFAULT_CAM_ZOOM_INTERVAL;
/**
* Number of Conductor values to offset camZooming by.
*/
public var camZoomingOffset:Float = Flags.DEFAULT_CAM_ZOOM_OFFSET;
/**
* Beat type for interval of cam zooming.
* Example: If Beat Type is on STEP and Interval is 2, it'll zoom every 2 steps.
* NOTE: Will set to BEAT if not found any other time signatures unlike 4/4.
*/
public var camZoomingEvery:BeatType = MEASURE;
/**
* Stores what was the last beat for the cam zooming intervals.
*/
public var camZoomingLastBeat:Float;
/**
* How strong the cam zooms should be (defaults to 1).
*/
public var camZoomingStrength:Float = Flags.DEFAULT_CAM_ZOOM_STRENGTH;
/**
* Default multiplier for `maxCamZoom`.
*/
public var maxCamZoomMult:Float = Flags.MAX_CAMERA_ZOOM_MULT;
/**
* Whether it should use a implementation where it multiplies the current camera zoom instead.
*/
public var useCamZoomMult:Bool = Flags.USE_CAM_ZOOM_MULT;
/**
* The current multiplier for camera zooming.
* (Only used if useCamZoomMult is on).
*/
public var camZoomingMult:Float = Flags.DEFAULT_ZOOM;
/**
* Maximum amount of zoom for the camera (based on `maxCamZoomMult` and the camera's zoom IF not set).
*/
public var maxCamZoom(get, default):Float = Math.NaN;
private inline function get_maxCamZoom() return Math.isNaN(maxCamZoom) ? defaultCamZoom + (camZoomingMult * camGameZoomMult) : maxCamZoom;
/**
* Zoom for the pixel assets.
*/
public static var daPixelZoom:Float = Flags.PIXEL_ART_SCALE;
/**
* Whenever the game is currently in a cutscene or not.
*/
public var inCutscene:Bool = false;
/**
* Whenever the game should play the cutscenes. Defaults to whenever the game is currently in Story Mode or not.
*/
public var playCutscenes:Bool = isStoryMode;
/**
* Whenever the game has already played a specific cutscene for the current song. Check `startCutscene` for more details.
*/
public static var seenCutscene:Bool = false;
/**
* Cutscene script path.
*/
public var cutscene:String = null;
/**
* End cutscene script path.
*/
public var endCutscene:String = null;
/**
* Last rating (may be null).
*/
public var curRating:ComboRating;
/**
* Timer for the start countdown.
*/
public var startTimer:FlxTimer;
/**
* Remaining events.
*/
public var events:Array<ChartEvent> = [];
/**
* Current camera target. -1 means no automatic camera targeting.
*/
public var curCameraTarget:Int = 0;
/**
* Length of the intro countdown.
*/
public var introLength:Int = Flags.DEFAULT_INTRO_LENGTH;
/**
* Array of sprites for the intro.
*/
public var introSprites:Array<String> = Flags.DEFAULT_INTRO_SPRITES.copy();
/**
* Array of sounds for the intro.
*/
public var introSounds:Array<String> = Flags.DEFAULT_INTRO_SOUNDS.copy();
/**
* Whenever the game is paused or not.
*/
public var paused:Bool = false;
/**
* Whenever the countdown has started or not.
*/
public var startedCountdown:Bool = false;
/**
* Whenever the game can be paused or not.
*/
public var canPause:Bool = true;
/**
* Format for the accuracy rating.
*/
public var accFormat:FlxTextFormat = new FlxTextFormat(0xFF888888, false, false, 0);
/**
* Whenever the song is ending or not.
*/
public var endingSong:Bool = false;
/**
* Group containing all of the combo sprites.
*/
public var comboGroup:RotatingSpriteGroup;
/**
* Whenever the Rating sprites should be shown or not.
*
* NOTE: This is just a default value for the final value, the final value can be changed through notes hit events.
*/
public var defaultDisplayRating:Bool = true;
/**
* Whenever the Combo sprite should be shown or not (like old Week 7 patches).
*
* NOTE: This is just a default value for the final value, the final value can be changed through notes hit events.
*/
public var defaultDisplayCombo:Bool = false;
/**
* Minimum Combo Count to display the combo digits. Anything less than 0 means it won't be shown.
*/
public var minDigitDisplay:Int = 10;
/**
* Array containing all of the note types names.
*/
public var noteTypesArray:Array<String> = [null];
/**
* Hit window, in milliseconds. Defaults to 250ms unless changed in options.
* Base game hit window is 175ms.
*/
public var hitWindow:Float = Options.hitWindow; // is calculated in create(), is safeFrames in milliseconds.
@:noCompletion @:dox(hide) private var _startCountdownCalled:Bool = false;
@:noCompletion @:dox(hide) private var _endSongCalled:Bool = false;
@:dox(hide)
var __vocalSyncTimer:Float = 1;
private function get_accuracy():Float {
if (accuracyPressedNotes <= 0) return -1;
return totalAccuracyAmount / accuracyPressedNotes;
}
private function set_accuracy(v:Float):Float {
if (accuracyPressedNotes <= 0)
accuracyPressedNotes = 1;
return totalAccuracyAmount = v * accuracyPressedNotes;
}
/**
* All combo ratings.
*/
public var comboRatings:Array<ComboRating> = [
new ComboRating(0, "F", 0xFFFF4444),
new ComboRating(0.5, "E", 0xFFFF8844),
new ComboRating(0.7, "D", 0xFFFFAA44),
new ComboRating(0.8, "C", 0xFFFFFF44),
new ComboRating(0.85, "B", 0xFFAAFF44),
new ComboRating(0.9, "A", 0xFF88FF44),
new ComboRating(0.95, "S", 0xFF44FFFF),
new ComboRating(1, "S++", 0xFF44FFFF),
];
public var detailsText:String = "";
public var detailsPausedText:String = "";
@:unreflective
private var __cachedGraphics:Array<FlxGraphic> = [];
/**
* Updates the rating.
*/
public function updateRating() {
var rating = null;
var acc = accuracy; // caching since it has a getter with an operation - Nex
if (comboRatings != null && comboRatings.length > 0) for (e in comboRatings)
if ((e.percent <= acc && e.maxMisses >= misses) && (rating == null || (rating.percent < e.percent && e.maxMisses >= misses)))
rating = e;
var event = gameAndCharsEvent("onRatingUpdate", EventManager.get(RatingUpdateEvent).recycle(rating, curRating));
if (!event.cancelled)
curRating = event.rating;
}
private inline function set_health(v:Float)
return health = FlxMath.bound(v, 0, maxHealth);
private inline function set_maxHealth(v:Float) {
if (healthBar != null && healthBar.max == maxHealth) healthBar.setRange(healthBar.min, v);
maxHealth = v;
health = health; // running the setter - Nex
return maxHealth;
}
private inline function get_curStage()
return stage == null ? "" : stage.stageName;
private inline function set_curStage(name:String) {
if (stage != null) stage.stageName = name;
return name;
}
public inline function callOnCharacters(func:String, ?parameters:Array<Dynamic>) {
if(strumLines != null) strumLines.forEachAlive(function (strLine:StrumLine) {
if (strLine.characters != null) for (character in strLine.characters)
if (character != null) character.script.call(func, parameters);
});
}
public inline function gameAndCharsCall(func:String, ?parameters:Array<Dynamic>, ?charsFunc:String) {
scripts.call(func, parameters);
callOnCharacters(charsFunc != null ? charsFunc : func, parameters);
}
public inline function gameAndCharsEvent<T:CancellableEvent>(func:String, ?event:T, ?charsFunc:String):T {
scripts.event(func, event);
callOnCharacters(charsFunc != null ? charsFunc : func, [event]);
return event;
}
@:dox(hide) override public function create()
{
Note.__customNoteTypeExists = [];
// SCRIPTING & DATA INITIALIZATION
#if REGION
instance = this;
if (FlxG.sound.music != null) FlxG.sound.music.stop();
PauseSubState.script = Flags.DEFAULT_PAUSE_SCRIPT;
GameOverSubstate.script = Flags.DEFAULT_GAMEOVER_SCRIPT;
(scripts = new ScriptPack("PlayState")).setParent(this);
camGame = camera;
FlxG.cameras.add(camHUD = new HudCamera(), false);
camHUD.bgColor.alpha = 0;
downscroll = Options.downscroll;
persistentUpdate = true;
persistentDraw = true;
if (SONG == null)
SONG = Chart.parse('tutorial', difficulty = 'normal', variation = null);
scrollSpeed = SONG.scrollSpeed;
Conductor.setupSong(SONG);
detailsText = isStoryMode ? ("Story Mode: " + storyWeek.name) : "Freeplay";
// Checks if cutscene files exists
var cutscenePath = Paths.script('songs/${SONG.meta.name}/cutscene');
var endCutscenePath = Paths.script('songs/${SONG.meta.name}/cutscene-end');
if (Assets.exists(cutscenePath)) cutscene = cutscenePath;
if (Assets.exists(endCutscenePath)) endCutscene = endCutscenePath;
// String for when the game is paused
detailsPausedText = "Paused - " + detailsText;
#end
// CHARACTER INITIALIZATION
#if REGION
comboGroup = new RotatingSpriteGroup(FlxG.width * 0.55, (FlxG.height * 0.5) - 60);
comboGroup.maxSize = Flags.DEFAULT_COMBO_GROUP_MAX_SIZE;
#end
// CAMERA FOLLOW, SCRIPTS & STAGE INITIALIZATION
#if REGION
camFollow = new FlxObject(0, 0, 2, 2);
add(camFollow);
if (SONG.stage == null || SONG.stage.trim() == "") SONG.stage = Flags.DEFAULT_STAGE;
add(stage = new Stage(SONG.stage));
if (!chartingMode || Options.charterEnablePlaytestScripts) {
switch(SONG.meta.name) {
// case "":
// ADD YOUR HARDCODED SCRIPTS HERE!
default:
var normal = 'songs/${SONG.meta.name}/scripts';
var scriptsFolders:Array<String> = [normal, normal + '/$difficulty/', 'data/charts/', 'songs/'];
for (folder in scriptsFolders) {
for (file in Paths.getFolderContent(folder, true, fromMods ? MODS : BOTH)) {
if (folder == 'data/charts/')
Logs.warn('data/charts/ is deprecated and will be removed in the future. Please move script $file to songs/', DARKYELLOW, "PlayState");
addScript(file);
}
}
var songEvents:Array<String> = [];
for (event in SONG.events) songEvents.pushOnce(event.name);
for (file in Paths.getFolderContent('data/events/', true, fromMods ? MODS : BOTH)) {
var fileName:String = CoolUtil.getFilename(file);
if (EventsData.eventsList.contains(fileName) && songEvents.contains(fileName)) {
addScript(file);
}
}
}
}
add(comboGroup);
#end
// PRECACHING
#if REGION
for(content in Paths.getFolderContent('images/game/score/', true, BOTH))
graphicCache.cache(Paths.getPath(content));
for(i in 1...4) {
FlxG.sound.load(Paths.sound('missnote' + Std.string(i)));
}
#end
// STRUMS & NOTES INITIALIZATION
#if REGION
strumLine = new FlxObject(0, 50, FlxG.width, 10);
strumLine.scrollFactor.set();
generateSong(SONG);
for(noteType in SONG.noteTypes) {
var scriptPath = Paths.script('data/notes/${noteType}');
if (Assets.exists(scriptPath) && !scripts.contains(scriptPath)) {
var script = Script.create(scriptPath);
if (!(script is DummyScript)) {
scripts.add(script);
script.load();
}
}
}
for(i=>strumLine in SONG.strumLines) {
if (strumLine == null) continue;
var chars = [];
var charPosName:String = strumLine.position == null ? (switch(strumLine.type) {
case 0: "dad";
case 1: "boyfriend";
case 2: "girlfriend";
}) : strumLine.position;
if (strumLine.characters != null) for(k=>charName in strumLine.characters) {
var char = new Character(0, 0, charName, stage.isCharFlipped(stage.characterPoses[charName] != null ? charName : charPosName, strumLine.type == 1));
stage.applyCharStuff(char, charPosName, k);
chars.push(char);
}
var strOffset:Float = strumLine.strumLinePos != null ? strumLine.strumLinePos : (strumLine.type == 1 ? 0.75 : 0.25);
var strScale:Float = strumLine.strumScale != null ? strumLine.strumScale : 1;
var strSpacing:Float = strumLine.strumSpacing == null ? 1 : strumLine.strumSpacing;
var keyCount:Int = strumLine.keyCount == null ? 4 : strumLine.keyCount;
var strXPos:Float = StrumLine.calculateStartingXPos(strOffset, strScale, strSpacing, keyCount);
var startingPos:FlxPoint = strumLine.strumPos != null ?
FlxPoint.get(strumLine.strumPos[0] == 0 ? strXPos : strumLine.strumPos[0], strumLine.strumPos[1]) :
FlxPoint.get(strXPos, this.strumLine.y);
var strLine = new StrumLine(chars,
startingPos,
strumLine.strumScale == null ? 1 : strumLine.strumScale,
strumLine.type == 2 || (!coopMode && !((strumLine.type == 1 && !opponentMode) || (strumLine.type == 0 && opponentMode))),
strumLine.type != 1, coopMode ? ((strumLine.type == 1) != opponentMode ? controlsP1 : controlsP2) : controls,
strumLine.vocalsSuffix
);
strLine.cameras = [camHUD];
strLine.data = strumLine;
strLine.visible = (strumLine.visible != false);
strLine.vocals.group = FlxG.sound.defaultMusicGroup;
strLine.ID = i;
strumLines.add(strLine);
}
add(strumLines);
splashHandler = new SplashHandler();
add(splashHandler);
scripts.set("SONG", SONG);
scripts.load();
scripts.call("create");
#end
// HUD INITIALIZATION & CAMERA INITIALIZATION
#if REGION
var event = EventManager.get(AmountEvent).recycle(null);
if (!gameAndCharsEvent("onPreGenerateStrums", event).cancelled) {
generateStrums(event.amount);
gameAndCharsEvent("onPostGenerateStrums", event);
}
for(str in strumLines)
str.generate(str.data, (chartingMode && Charter.startHere) ? Charter.startTime : null);
FlxG.camera.follow(camFollow, LOCKON, Flags.DEFAULT_CAMERA_FOLLOW_SPEED);
FlxG.camera.zoom = defaultCamZoom;
// camHUD.zoom = defaultHudZoom;
if (smoothTransitionData != null && smoothTransitionData.stage == curStage) {
FlxG.camera.scroll.set(smoothTransitionData.camX, smoothTransitionData.camY);
FlxG.camera.zoom = smoothTransitionData.camZoom;
MusicBeatState.skipTransIn = true;
camFollow.setPosition(smoothTransitionData.camFollowX, smoothTransitionData.camFollowY);
} else {
FlxG.camera.focusOn(camFollow.getPosition(FlxPoint.weak()));
}
smoothTransitionData = null;
FlxG.worldBounds.set(0, 0, FlxG.width, FlxG.height);
healthBarBG = new FlxSprite(0, FlxG.height * 0.9).loadAnimatedGraphic(Paths.image('game/healthBar'));
healthBarBG.screenCenter(X);
healthBarBG.scrollFactor.set();
add(healthBarBG);
healthBar = new FlxBar(healthBarBG.x + 4, healthBarBG.y + 4, RIGHT_TO_LEFT, Std.int(healthBarBG.width - 8), Std.int(healthBarBG.height - 8), this,
'health', 0, maxHealth);
healthBar.scrollFactor.set();
var leftColor:Int = dad != null && dad.iconColor != null && Options.colorHealthBar ? dad.iconColor : (opponentMode ? 0xFF66FF33 : 0xFFFF0000);
var rightColor:Int = boyfriend != null && boyfriend.iconColor != null && Options.colorHealthBar ? boyfriend.iconColor : (opponentMode ? 0xFFFF0000 : 0xFF66FF33); // switch the colors
healthBar.createFilledBar(leftColor, rightColor);
add(healthBar);
if (Flags.DEFAULT_HEALTH != null) health = Flags.DEFAULT_HEALTH;
else health = maxHealth / 2;
iconArray.push(iconP1 = new HealthIcon(boyfriend != null ? boyfriend.getIcon() : Flags.DEFAULT_HEALTH_ICON, true));
iconArray.push(iconP2 = new HealthIcon(dad != null ? dad.getIcon() : Flags.DEFAULT_HEALTH_ICON, false));
for (icon in iconArray) {
icon.y = healthBar.y - (icon.height / 2);
add(icon);
}
scoreTxt = new FunkinText(healthBarBG.x + 50, healthBarBG.y + 30, Std.int(healthBarBG.width - 100), TEXT_GAME_SCORE.format([songScore]), 16);
missesTxt = new FunkinText(healthBarBG.x + 50, healthBarBG.y + 30, Std.int(healthBarBG.width - 100), TEXT_GAME_MISSES.format([misses]), 16);
accuracyTxt = new FunkinText(healthBarBG.x + 50, healthBarBG.y + 30, Std.int(healthBarBG.width - 100), TEXT_GAME_ACCURACY.format(["-%", "(N/A)"]), 16);
accuracyTxt.addFormat(accFormat, 0, 1);
for(text in [scoreTxt, missesTxt, accuracyTxt]) {
text.scrollFactor.set();
add(text);
}
scoreTxt.alignment = RIGHT;
missesTxt.alignment = CENTER;
accuracyTxt.alignment = LEFT;
if (updateRatingStuff != null)
updateRatingStuff();
for(e in [healthBar, healthBarBG, iconP1, iconP2, scoreTxt, missesTxt, accuracyTxt])
e.cameras = [camHUD];
#end
startingSong = true;
super.create();
for(s in introSprites)
if (s != null)
graphicCache.cache(Paths.image(s));
for(s in introSounds)
if (s != null)
FlxG.sound.load(Paths.sound(s));
if (chartingMode) {
WindowUtils.prefix = Charter.undos.unsaved ? Flags.UNDO_PREFIX : "";
WindowUtils.suffix = TU.translate("playtesting.chartPlaytesting");
SaveWarning.showWarning = Charter.undos.unsaved;
SaveWarning.selectionClass = CharterSelection;
SaveWarning.warningFunc = saveWarn;
SaveWarning.saveFunc = () -> Charter.saveEverything(false);
}
}
@:dox(hide) public override function createPost() {
startCutscene("", cutscene, null, true);
super.createPost();
updateDiscordPresence();
// Make icons appear in the correct spot during cutscenes
healthBar.update(0);
if (updateIconPositions != null)
updateIconPositions();
__updateNote_event = EventManager.get(NoteUpdateEvent);
gameAndCharsCall("postCreate", null, "gamePostCreate");
}
/**
* Function used to update Discord Presence.
*
* This function is dynamic, which means you can do `updateDiscordPresence = function() {}` in scripts.
*/
public dynamic function updateDiscordPresence()
DiscordUtil.call("onPlayStateUpdate", []);
/**
* Starts a cutscene.
* @param prefix Custom prefix. Using `midsong-` will require you to for example rename your video cutscene to `songs/song/midsong-cutscene.mp4` instead of `songs/song/cutscene.mp4`
* @param cutsceneScriptPath Optional: Custom script path.
* @param callback Callback called after the cutscene ended. If equals to `null`, `startCountdown` will be called.
* @param checkSeen Bool that by default is false, if true and `seenCutscene` is also true, it won't play the cutscene but directly call `callback` (PS: `seenCutscene` becomes true if the cutscene gets played and `checkSeen` was true)
* @param canSkipTransIn Bool that by default is true makes the in transition skip on certain types of cutscenes like dialogues.
*/
public function startCutscene(prefix:String = "", ?cutsceneScriptPath:String, ?callback:Void->Void, checkSeen:Bool = false, canSkipTransIn:Bool = true) {
if (callback == null) callback = startCountdown;
if ((checkSeen && seenCutscene) || !playCutscenes) {
callback();
return;
}
var songName = SONG.meta.name;
if (cutsceneScriptPath == null)
cutsceneScriptPath = Paths.script('songs/$songName/${prefix}cutscene');
inCutscene = true;
var videoCutscene = Paths.video('$songName-${prefix}cutscene');
var videoCutsceneAlt = Paths.file('songs/$songName/${prefix}cutscene.${Flags.VIDEO_EXT}');
var dialogue = Paths.file('songs/$songName/${prefix}dialogue.xml');
persistentUpdate = true;
var toCall:Void->Void = function() {
if(checkSeen) seenCutscene = true;
callback();
}
if (cutsceneScriptPath != null && Assets.exists(cutsceneScriptPath)) {
openSubState(new ScriptedCutscene(cutsceneScriptPath, toCall));
} else if (Assets.exists(dialogue)) {
if (canSkipTransIn) MusicBeatState.skipTransIn = true;
openSubState(new DialogueCutscene(dialogue, toCall));
} else if (Assets.exists(videoCutsceneAlt)) {
if (canSkipTransIn) MusicBeatState.skipTransIn = true;
persistentUpdate = false;
openSubState(new VideoCutscene(videoCutsceneAlt, toCall));
persistentDraw = false;
} else if (Assets.exists(videoCutscene)) {
if (canSkipTransIn) MusicBeatState.skipTransIn = true;
persistentUpdate = false;
openSubState(new VideoCutscene(videoCutscene, toCall));
persistentDraw = false;
} else
callback();
}
@:dox(hide) public function startCountdown():Void
{
if (!_startCountdownCalled) {
_startCountdownCalled = true;
inCutscene = false;
if (gameAndCharsEvent("onStartCountdown", new CancellableEvent()).cancelled) return;
}
startedCountdown = true;
Conductor.songPosition = 0;
Conductor.songPosition -= Conductor.crochet * introLength - Conductor.songOffset;
if(introLength > 0) {
var swagCounter:Int = 0;
startTimer = new FlxTimer().start(Conductor.crochet / 1000, (tmr:FlxTimer) -> {
countdown(swagCounter++);
}, introLength);
}
gameAndCharsCall("onPostStartCountdown");
}
/**
* Creates a fake countdown.
*/
public function countdown(swagCounter:Int) {
var event:CountdownEvent = gameAndCharsEvent("onCountdown", EventManager.get(CountdownEvent).recycle(
swagCounter,