-
Notifications
You must be signed in to change notification settings - Fork 1
/
sm64-livesplit-autosplitter.asl
1549 lines (1248 loc) · 61.9 KB
/
sm64-livesplit-autosplitter.asl
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
// Version: 4.0.1
// Code: https://github.com/n64decomp/sm64/
// Address map: https://github.com/SM64-TAS-ABC/STROOP/tree/Development/STROOP/Mappings
// Sometimes needs to be offset for endianness: long:0, short:-2 or +2, byte:0 or +3????
state("Project64") {
// This looks like it always has value DEBUG_FUNCTION_VALUE in the correct ROM.
// Used to determine which ROM is currently loaded in PJ64 automatically.
uint debugFunctionJP : "Project64.exe", 0xD6A1C, 0x2CA6E0;
uint debugFunctionUS : "Project64.exe", 0xD6A1C, 0x2CB1C0;
uint gameRunTimeJP : "Project64.exe", 0xD6A1C, 0x32C640;
uint gameRunTimeUS : "Project64.exe", 0xD6A1C, 0x32D580;
uint globalTimerJP : "Project64.exe", 0xD6A1C, 0x32C694;
uint globalTimerUS : "Project64.exe", 0xD6A1C, 0x32D5D4;
byte stageIndexJP : "Project64.exe", 0xD6A1C, 0x32CE9A; // N64 addr: 0x8032CE98
byte stageIndexUS : "Project64.exe", 0xD6A1C, 0x32DDFA; // N64 addr: 0x8032DDF8
uint animationJP : "Project64.exe", 0xD6A1C, 0x339E0C; // N64 addr: 0x80339E00 + 0xC (struct field)
uint animationUS : "Project64.exe", 0xD6A1C, 0x33B17C; // N64 addr: 0x8033B170 + 0xC (struct field)
ushort starCountJP : "Project64.exe", 0xD6A1C, 0x339EA8; // N64 addr: 0x80339E00 + 0xAA (struct field)
ushort starCountUS : "Project64.exe", 0xD6A1C, 0x33B218; // N64 addr: 0x8033B170 + 0xAA (struct field)
ushort hudCameraModeJP : "Project64.exe", 0xD6A1C, 0x3314FA; // N64 addr: 0x803314F8
ushort hudCameraModeUS : "Project64.exe", 0xD6A1C, 0x33260A; // N64 addr: 0x80332608
float positionXJP : "Project64.exe", 0xD6A1C, 0x339E3C; // N64 addr: 0x80339E00 + 0x3C (struct field) + 0x0 (array offset)
float positionXUS : "Project64.exe", 0xD6A1C, 0x33B1AC; // N64 addr: 0x8033B170 + 0x3C (struct field) + 0x0 (array offset)
float positionYJP : "Project64.exe", 0xD6A1C, 0x339E40; // N64 addr: 0x80339E00 + 0x3C (struct field) + 0x4 (array offset)
float positionYUS : "Project64.exe", 0xD6A1C, 0x33B1B0; // N64 addr: 0x8033B170 + 0x3C (struct field) + 0x4 (array offset)
float positionZJP : "Project64.exe", 0xD6A1C, 0x339E44; // N64 addr: 0x80339E00 + 0x3C (struct field) + 0x8 (array offset)
float positionZUS : "Project64.exe", 0xD6A1C, 0x33B1B4; // N64 addr: 0x8033B170 + 0x3C (struct field) + 0x8 (array offset)
byte warpDestinationJP : "Project64.exe", 0xD6A1C, 0x339EDA; // N64 addr: 0x80339ED8 + 0x4 (struct field)
byte warpDestinationUS : "Project64.exe", 0xD6A1C, 0x33B24A; // N64 addr: 0x8033B248 + 0x4 (struct field)
// Files from gSaveBuffer, each file is 0x70 apart.
uint fileAFlagsJP : "Project64.exe", 0xD6A1C, 0x207B08;
uint fileAFlagsUS : "Project64.exe", 0xD6A1C, 0x207708;
uint fileBFlagsJP : "Project64.exe", 0xD6A1C, 0x207B78;
uint fileBFlagsUS : "Project64.exe", 0xD6A1C, 0x207778;
uint fileCFlagsJP : "Project64.exe", 0xD6A1C, 0x207BE8;
uint fileCFlagsUS : "Project64.exe", 0xD6A1C, 0x2077E8;
uint fileDFlagsJP : "Project64.exe", 0xD6A1C, 0x207C58;
uint fileDFlagsUS : "Project64.exe", 0xD6A1C, 0x207858;
// Non-stop code writes 0/2400 to this address when enabled, changing interactions.
ushort nonStopInteractionOverwriteJP : "Project64.exe", 0xD6A1C, 0x24DC1E; // N64 addr: 0x8024DC1C (found in Gameshark Code).
ushort nonStopInteractionOverwriteUS : "Project64.exe", 0xD6A1C, 0x24DDBE; // N64 addr: 0x8024DDBC (found in Gameshark Code).
byte menuSelectedButtonIDJP : "Project64.exe", 0xD6A1C, 0x1A7BD3; // N64 addr: 0x801A7BD0
byte menuSelectedButtonIDUS : "Project64.exe", 0xD6A1C, 0x1A7D13; // N64 addr: 0x801A7D10
short menuClickPosJP : "Project64.exe", 0xD6A1C, 0x1A7BE8;
short menuClickPosUS : "Project64.exe", 0xD6A1C, 0x1A7D28;
uint behaviorSegmentInfoJP : "Project64.exe", 0xD6A1C, 0x33A0DC; // N64 addr: sSegmentTable[0x13] = 0x8033A090 + 4 * 0x13 = 0x8033A0DC
uint behaviorSegmentInfoUS : "Project64.exe", 0xD6A1C, 0x33B44C; // N64 addr: sSegmentTable[0x13] = 0x8033B400 + 4 * 0x13 = 0x8033B44C
uint object0TimerJP : "Project64.exe", 0xD6A1C, 0x33C26C; // N64 addr: 0x8033C118 + 0x154
uint object0TimerUS : "Project64.exe", 0xD6A1C, 0x33D5DC; // N64 addr: 0x8033D488 + 0x154
uint object0BehaviorJP : "Project64.exe", 0xD6A1C, 0x33C324; // N64 addr: 0x8033C118 + 0x20C
uint object0BehaviorUS : "Project64.exe", 0xD6A1C, 0x33D694; // N64 addr: 0x8033D488 + 0x20C
ushort controller0ButtonsJP : "Project64.exe", 0xD6A1C, 0x339C30; // N64 addr: 0x80339C20 + 0x10 (gControllers[0].buttonDown)
ushort controller0ButtonsUS : "Project64.exe", 0xD6A1C, 0x33AFA0; // N64 addr: 0x8033AF90 + 0x10 (gControllers[0].buttonDown)
}
startup {
Func<string[], List<string[]>> buildKeywords = delegate(string[] phrases) {
List<string[]> result = new List<string[]>();
foreach (string phrase in phrases) {
string[] words = phrase.ToLower().Split(' ');
result.Add(words);
}
return result;
};
// ********** USER_CUSTOMIZATION_BEGIN **********
//
// This auto-splitter uses certain keywords in split names in order to detect special types of splits. Each
// is explained here. Some rules:
//
// - You may only modify this part of the code to add your own keywords and maintain upstream support.
// - Keywords must be lowercase below, but they will match all cases, they must always match a full word (eg. "dw" will match "dw (9)" but not "wdw (39)").
// - Keywords may only be alpha-numeric values (no special characters).
// - You may include a phrase instead of a word, it will then match all cases/spacing of that phrase. (eg. "mips clip" matches "MipS Clip")
//
// Please note that these changes will apply to all of your splits. Remember to port any changes you make to
// new version whenever updating this script.
// NO_RESET_SECONDS_LEEWAY: number of seconds given to double-reset and actually reset timer on [noreset] splits.
// Set to 0 to disable double-resetting behavior.
int NO_RESET_SECONDS_LEEWAY = 3;
// BOWSER_FIGHT_KEYWORDS: Any split name containing these keywords will split only once bowser fight is won.
List<string[]> BOWSER_FIGHT_KEYWORDS = buildKeywords(new string[] {
"bowser",
"key",
});
// BOWSER_STAGE_KEYWORDS: Any split name containing one of these keywords will split on pipe entry within bowser
// stages (dark world, fire sea, sky).
List<string[]> BOWSER_STAGE_KEYWORDS = buildKeywords(new string[] {
"pipe",
});
// KEY_UNLOCK_KEYWORDS: Any split name containing these keywords will split only once a key door is unlocked.
List<string[]> KEY_UNLOCK_KEYWORDS = buildKeywords(new string[] {
"upstairs",
"basement",
});
// THIRTY_STAR_DOOR_CLIP_KEYWORDS: Any split name containing these keywords will split on DDD/FS entry (when entering XCAM).
List<string[]> THIRTY_STAR_DOOR_CLIP_KEYWORDS = buildKeywords(new string[] {
"mips clip",
"sblj",
});
// ********** USER_CUSTOMIZATION_END **********
byte BOB_STAGE_INDEX = 9;
byte CCM_STAGE_INDEX = 5;
byte WF_STAGE_INDEX = 24;
byte JRB_STAGE_INDEX = 12;
byte BBH_STAGE_INDEX = 4;
byte SSL_STAGE_INDEX = 8;
byte LLL_STAGE_INDEX = 22;
byte HMC_STAGE_INDEX = 7;
byte DDD_STAGE_INDEX = 23;
byte WDW_STAGE_INDEX = 11;
byte THI_STAGE_INDEX = 13;
byte TTM_STAGE_INDEX = 36;
byte SL_STAGE_INDEX = 10;
byte TTC_STAGE_INDEX = 14;
byte RR_STAGE_INDEX = 15;
byte BITDW_STAGE_INDEX = 17;
byte BITFS_STAGE_INDEX = 19;
byte BITS_STAGE_INDEX = 21;
byte BOWSER1_STAGE_INDEX = 30;
byte BOWSER2_STAGE_INDEX = 33;
byte BOWSER3_STAGE_INDEX = 34;
byte TOTWC_STAGE_INDEX = 29;
byte VCUTM_STAGE_INDEX = 18;
byte WMOTR_STAGE_INDEX = 31;
byte COTMC_STAGE_INDEX = 28;
byte AQUA_STAGE_INDEX = 20;
byte PSS_STAGE_INDEX = 27;
byte CASTLE_INSIDE_STAGE_INDEX = 6; // Basement + Lobby + Upstairs + Tippy
byte CASTLE_OUTSIDE_FRONT_STAGE_INDEX = 16; // Garden & Moat (Outside Front)
byte CASTLE_OUTSIDE_BACK_STAGE_INDEX = 26; // Castle Courtyard (Outside Back)
// Arrays used to quickly check which stage we are in.
bool[] BOWSER_FIGHT_STAGE_INDEXES = new bool[0x100];
BOWSER_FIGHT_STAGE_INDEXES[BOWSER1_STAGE_INDEX] = true;
BOWSER_FIGHT_STAGE_INDEXES[BOWSER2_STAGE_INDEX] = true;
BOWSER_FIGHT_STAGE_INDEXES[BOWSER3_STAGE_INDEX] = true;
bool[] BITX_STAGE_INDEXES = new bool[0x100];
BITX_STAGE_INDEXES[BITDW_STAGE_INDEX] = true;
BITX_STAGE_INDEXES[BITFS_STAGE_INDEX] = true;
BITX_STAGE_INDEXES[BITS_STAGE_INDEX] = true;
bool[] STAGE_INDEXES = new bool[0x100];
STAGE_INDEXES[AQUA_STAGE_INDEX] = true;
STAGE_INDEXES[BBH_STAGE_INDEX] = true;
STAGE_INDEXES[BITDW_STAGE_INDEX] = true;
STAGE_INDEXES[BITFS_STAGE_INDEX] = true;
STAGE_INDEXES[BITS_STAGE_INDEX] = true;
STAGE_INDEXES[BOB_STAGE_INDEX] = true;
STAGE_INDEXES[CCM_STAGE_INDEX] = true;
STAGE_INDEXES[DDD_STAGE_INDEX] = true;
STAGE_INDEXES[HMC_STAGE_INDEX] = true;
STAGE_INDEXES[JRB_STAGE_INDEX] = true;
STAGE_INDEXES[LLL_STAGE_INDEX] = true;
STAGE_INDEXES[PSS_STAGE_INDEX] = true;
STAGE_INDEXES[RR_STAGE_INDEX] = true;
STAGE_INDEXES[SL_STAGE_INDEX] = true;
STAGE_INDEXES[SSL_STAGE_INDEX] = true;
STAGE_INDEXES[THI_STAGE_INDEX] = true;
STAGE_INDEXES[TOTWC_STAGE_INDEX] = true;
STAGE_INDEXES[TTC_STAGE_INDEX] = true;
STAGE_INDEXES[TTM_STAGE_INDEX] = true;
STAGE_INDEXES[VCUTM_STAGE_INDEX] = true;
STAGE_INDEXES[WDW_STAGE_INDEX] = true;
STAGE_INDEXES[WF_STAGE_INDEX] = true;
STAGE_INDEXES[WMOTR_STAGE_INDEX] = true;
STAGE_INDEXES[COTMC_STAGE_INDEX] = true;
bool[] CASTLE_STAGE_INDEXES = new bool[0x100];
CASTLE_STAGE_INDEXES[CASTLE_INSIDE_STAGE_INDEX] = true;
CASTLE_STAGE_INDEXES[CASTLE_OUTSIDE_FRONT_STAGE_INDEX] = true;
CASTLE_STAGE_INDEXES[CASTLE_OUTSIDE_BACK_STAGE_INDEX] = true;
// Short-hand used to
Dictionary<string, byte> STAGE_NAMES_TO_INDEX = new Dictionary<string, byte>();
STAGE_NAMES_TO_INDEX["aqua"] = AQUA_STAGE_INDEX;
STAGE_NAMES_TO_INDEX["bbh"] = BBH_STAGE_INDEX;
STAGE_NAMES_TO_INDEX["bob"] = BOB_STAGE_INDEX;
STAGE_NAMES_TO_INDEX["bow1"] = BOWSER1_STAGE_INDEX;
STAGE_NAMES_TO_INDEX["bow2"] = BOWSER2_STAGE_INDEX;
STAGE_NAMES_TO_INDEX["bow3"] = BOWSER3_STAGE_INDEX;
STAGE_NAMES_TO_INDEX["ccm"] = CCM_STAGE_INDEX;
STAGE_NAMES_TO_INDEX["cotmc"] = COTMC_STAGE_INDEX;
STAGE_NAMES_TO_INDEX["ddd"] = DDD_STAGE_INDEX;
STAGE_NAMES_TO_INDEX["dw"] = BITDW_STAGE_INDEX;
STAGE_NAMES_TO_INDEX["fs"] = BITFS_STAGE_INDEX;
STAGE_NAMES_TO_INDEX["hmc"] = HMC_STAGE_INDEX;
STAGE_NAMES_TO_INDEX["jrb"] = JRB_STAGE_INDEX;
STAGE_NAMES_TO_INDEX["lll"] = LLL_STAGE_INDEX;
STAGE_NAMES_TO_INDEX["pss"] = PSS_STAGE_INDEX;
STAGE_NAMES_TO_INDEX["rr"] = RR_STAGE_INDEX;
STAGE_NAMES_TO_INDEX["sky"] = BITS_STAGE_INDEX;
STAGE_NAMES_TO_INDEX["sl"] = SL_STAGE_INDEX;
STAGE_NAMES_TO_INDEX["ssl"] = SSL_STAGE_INDEX;
STAGE_NAMES_TO_INDEX["thi"] = THI_STAGE_INDEX;
STAGE_NAMES_TO_INDEX["totwc"] = TOTWC_STAGE_INDEX;
STAGE_NAMES_TO_INDEX["ttc"] = TTC_STAGE_INDEX;
STAGE_NAMES_TO_INDEX["ttm"] = TTM_STAGE_INDEX;
STAGE_NAMES_TO_INDEX["vcutm"] = VCUTM_STAGE_INDEX;
STAGE_NAMES_TO_INDEX["wdw"] = WDW_STAGE_INDEX;
STAGE_NAMES_TO_INDEX["wf"] = WF_STAGE_INDEX;
STAGE_NAMES_TO_INDEX["wmotr"] = WMOTR_STAGE_INDEX;
// Settings for this auto splitter.
string GAME_VERSION_JP = "gameVersionJP";
string GAME_VERSION_US = "gameVersionUS";
string LAUNCH_ON_START = "launchOnStart";
string DISABLE_RESET_AFTER_END = "disableResetAfterEnd";
string DISABLE_RTA_MODE = "disableRTAMode";
string DISABLE_BOWSER_REDS_DELAYED_SPLIT = "disableBowserRedsDelayedSplit";
string DISABLE_AUTO_START_ON_FILE_D = "disableAutoStartOnFileD";
// Splitting time is controlled depending on the type of split this is.
int SPLIT_TYPE_MANUAL = -1;
int SPLIT_TYPE_CASTLE_MOVEMENT = 0;
int SPLIT_TYPE_KEY_GRAB = 1;
int SPLIT_TYPE_KEY_DOOR_UNLOCK = 2;
int SPLIT_TYPE_BOWSER_PIPE_ENTRY = 3;
int SPLIT_TYPE_THIRTY_STAR_DOOR_CLIP = 4;
int SPLIT_TYPE_STAR_GRAB = 5;
int SPLIT_TYPE_STAR_DOOR_ENTRY = 6;
int SPLIT_TYPE_STAGE_ENTRY = 7;
int SPLIT_TYPE_STAGE_EXIT = 8;
int SPLIT_TYPE_FINAL_STAR_GRAB = 9;
// Constant values used within the code.
uint ACT_STAR_DANCE_EXIT = 0x1302;
uint ACT_STAR_DANCE_WATER = 0x1303;
uint ACT_STAR_DANCE_NO_EXIT = 0x1307;
uint ACT_UNLOCKING_KEY_DOOR = 0x132e;
uint ACT_ENTERING_STAR_DOOR = 0x1331;
uint ACT_JUMBO_STAR_CUTSCENE = 0x1909;
uint ACT_FALL_AFTER_STAR_GRAB = 0x1904;
uint[] DOOR_XCAM_COUNT_ACTIONS = new uint[]{
0x1320, // ACT_PULLING_DOOR
0x1321, // ACT_PUSHING_DOOR
ACT_UNLOCKING_KEY_DOOR,
ACT_ENTERING_STAR_DOOR,
};
uint DEBUG_FUNCTION_VALUE = 0x27bdffd8;
ushort FIXED_CAMERA_HUD = 0x4;
ushort FIXED_CAMERA_CDOWN_HUD = 0xC;
ushort NON_STOP_OVERWRITE_VALUE_GAMESHARK = 0x2400;
ushort NON_STOP_OVERWRITE_VALUE_USAMUNE = 0x0;
uint KEY_FLAGS = 0x10 | 0x20;
uint BHV_ACT_SELECTOR_JP = 0x13003028;
uint BHV_ACT_SELECTOR_US = 0x13003048;
ushort BUTTON_L_TRIG = 0x0020;
ushort BUTTON_DPAD_DOWN = 0x0400;
// Allows defining a 3D rectangular box to checkpoint mario's position for various splitting conditions.
Func<byte, float, float, float, float, float, float, dynamic> create3DBox = delegate(byte stageIndex, float x1, float y1, float z1, float x2, float y2, float z2) {
dynamic box = new ExpandoObject();
box.stageIndex = stageIndex;
box.x1 = x1;
box.y1 = y1;
box.z1 = z1;
box.x2 = x2;
box.y2 = y2;
box.z2 = z2;
return box;
};
// Measured outside the 8 star door on castle lobby side.
// Actual measured values and their corresponding visual direction:
// X (depth to door): -2800 (after door, also left side), -2300 (before door)
// Y (height above ground): 512 (ground), 704 (double jump)
// Z (width of hallway): -1587 (right side), -1100 (left side),
dynamic STAR_DOOR_8_POSITION = create3DBox(
CASTLE_INSIDE_STAGE_INDEX,
-2800, 500, -1600,
-2200, 800, -1050
);
// Measured outside the 30 star door on SBLJ stairs side.
// Actual measured values and their corresponding visual direction:
// X (depth to door): 0 (before door), 600 (after door)
// Y (height above ground): -1074 (floor), -465 (triple jump max height seen)
// Z (width around door): 1750 (left side), 2300 (right side)
dynamic STAR_DOOR_30_POSITION = create3DBox(
CASTLE_INSIDE_STAGE_INDEX,
0, -1090, 1700,
600, -200, 2300
);
// Measured outside the 50 star door on upstairs side.
// Actual measured values and their corresponding visual direction:
// X (width around door): -615 (right side), 206 (left side),
// Y (height above ground): 2253 (ground), 2418 (double jump)
// Z (depth into the door): 4400 (before door), 4900 (after door)
dynamic STAR_DOOR_50_POSITION = create3DBox(
CASTLE_INSIDE_STAGE_INDEX,
-600, 2200, 4400,
230, 2500, 4900
);
// Measured outside the 70 star door on tippy side.
// Actual measured values and their corresponding visual direction:
// X (width around door): -450 (left side), 65 (right side)
// Y (height above ground): 3174 (ground), 3500 (double jump)
// Z (depth into the door): 3670 (after door), 4100 (before door)
dynamic STAR_DOOR_70_POSITION = create3DBox(
CASTLE_INSIDE_STAGE_INDEX,
-450, 3160, 3670,
65, 3700, 4100
);
// Regexes used to parse clean up split name into proper groups.
System.Text.RegularExpressions.Regex BRACKET_TYPE1 = new System.Text.RegularExpressions.Regex(@"(AS:)?\[(?<values>[\w\d,=-]+)\]");
System.Text.RegularExpressions.Regex BRACKET_TYPE2 = new System.Text.RegularExpressions.Regex(@"(AS:)?\((?<values>[\w\d,=-]+)\)");
// Flags within the splitter information (in brackets).
System.Text.RegularExpressions.Regex STAR_COUNT = new System.Text.RegularExpressions.Regex(@"^(?<starCount>\d+)$");
System.Text.RegularExpressions.Regex ENTRY = new System.Text.RegularExpressions.Regex(@"^entry=(?<stageID>(\w+|\d+))$");
System.Text.RegularExpressions.Regex EXIT = new System.Text.RegularExpressions.Regex(@"^exit=(?<stageID>(\w+|\d+))$");
System.Text.RegularExpressions.Regex STAR_DOOR = new System.Text.RegularExpressions.Regex(@"^star-door=(?<starCount>(8|30|50|70))$");
System.Text.RegularExpressions.Regex MODE = new System.Text.RegularExpressions.Regex(@"^run-mode=(?<modeName>(romhack|rta))$");
// TODO(#6): AutoSplitter64 compatibility
// System.Text.RegularExpressions.Regex XCAM_COUNT = new System.Text.RegularExpressions.Regex(@"^xcam=(?<count>\d+)$");
// System.Text.RegularExpressions.Regex DOOR_XCAM_COUNT = new System.Text.RegularExpressions.Regex(@"^door-xcam=(?<count>\d+)$");
// System.Text.RegularExpressions.Regex FADE_IN_COUNT = new System.Text.RegularExpressions.Regex(@"^fade-in=(?<count>\d+)$");
// System.Text.RegularExpressions.Regex FADE_OUT_COUNT = new System.Text.RegularExpressions.Regex(@"^fade-out=(?<count>\d+)$");
// Helper attributes related to LiveSplit.
vars.timerModel = new TimerModel { CurrentState = timer };
// Working data which can be changed in various places.
Func<ExpandoObject> initSplitConfigData = delegate() {
dynamic data = new ExpandoObject();
data.starCountRequirement = -1;
data.entryStageID = -1;
data.exitStageID = -1;
data.starDoorID = -1;
data.type = SPLIT_TYPE_CASTLE_MOVEMENT;
data.isNoReset = false;
data.isForcedFade = false;
data.isForcedImmediate = false;
return data;
};
Func<ExpandoObject> initSplitConditionsData = delegate() {
dynamic data = new ExpandoObject();
data.isSplittingOnFade = false;
data.isSplittingImmediately = false;
return data;
};
Func<ExpandoObject> initRunLiveData = delegate() {
dynamic data = new ExpandoObject();
data.previousStage = 0;
data.selectedFileID = -1;
data.wantToReset = false;
data.wantToResetTiming = 0;
data.starSelectTimerUpdateCount = 0;
data.starSelectLastTimerUpdateGT = 0;
return data;
};
Func<ExpandoObject> initRunConfigData = delegate() {
dynamic data = new ExpandoObject();
data.isJapaneseVersion = false;
data.isRTAMode = false;
data.relaxedFadeMatch = false;
data.lastSplitName = "";
data.lastFirstSplitName = "";
return data;
};
Func<ExpandoObject> initVarsData = delegate() {
dynamic data = new ExpandoObject();
data.splitConfig = initSplitConfigData();
data.splitConditions = initSplitConditionsData();
data.runLiveData = initRunLiveData();
data.runConfig = initRunConfigData();
data.updateCounter = (uint) 0;
return data;
};
// resetVarsDataForSplitChange re-initializes configuration related to split.
Action<dynamic, string> resetVarsDataForSplitChange = delegate(dynamic varsD, string splitName) {
varsD.data.splitConfig = initSplitConfigData();
varsD.data.splitConditions = initSplitConditionsData();
varsD.data.runConfig.lastSplitName = splitName;
};
// resetVarsDataForFirstSplitChange re-initializes configuration related to run configuration.
Action<dynamic, string> resetVarsDataForFirstSplitChange = delegate(dynamic varsD, string splitName) {
varsD.data.runConfig = initRunConfigData();
varsD.data.runConfig.lastFirstSplitName = splitName;
};
vars.data = initVarsData();
// Settings are copied to this for testing purposes.
Func<ExpandoObject> initSettingsData = delegate() {
dynamic settingsD = new ExpandoObject();
settingsD.isResetEnabled = false;
settingsD.currentTimerPhase = TimerPhase.NotRunning;
settingsD.currentSplitIndex = -1;
settingsD.splitCount = 0;
settingsD.forceLaunchOnStart = false;
settingsD.forceJPGameVersion = false;
settingsD.forceUSGameVersion = false;
settingsD.disableResetAfterEnd = false;
settingsD.disableRTAMode = false;
settingsD.disableBowserRedsDelayedSplit = false;
settingsD.disableAutoStartOnFileD = false;
return settingsD;
};
vars.settings = initSettingsData();
// Debugging function.
Func<dynamic, string> varsToString = delegate(dynamic varsD) {
string result = "";
result += "\n\n";
result += string.Format(" varsD.data.splitConfig.type = {0}\n", varsD.data.splitConfig.type);
result += string.Format(" varsD.data.splitConfig.starCountRequirement = {0}\n", varsD.data.splitConfig.starCountRequirement);
result += string.Format(" varsD.data.splitConfig.entryStageID = {0}\n", varsD.data.splitConfig.entryStageID);
result += string.Format(" varsD.data.splitConfig.exitStageID = {0}\n", varsD.data.splitConfig.exitStageID);
result += string.Format(" varsD.data.splitConfig.starDoorID = {0}\n", varsD.data.splitConfig.starDoorID);
result += string.Format(" varsD.data.splitConfig.isNoReset = {0}\n", varsD.data.splitConfig.isNoReset);
result += string.Format(" varsD.data.splitConfig.isForcedFade = {0}\n", varsD.data.splitConfig.isForcedFade);
result += string.Format(" varsD.data.splitConfig.isForcedImmediate = {0}\n", varsD.data.splitConfig.isForcedImmediate);
result += "\n";
result += string.Format(" varsD.data.splitConditions.isSplittingOnFade = {0}\n", varsD.data.splitConditions.isSplittingOnFade);
result += string.Format(" varsD.data.splitConditions.isSplittingImmediately = {0}\n", varsD.data.splitConditions.isSplittingImmediately);
result += "\n";
result += string.Format(" varsD.data.runConfig.isJapaneseVersion = {0}\n", varsD.data.runConfig.isJapaneseVersion);
result += string.Format(" varsD.data.runConfig.isRTAMode = {0}\n", varsD.data.runConfig.isRTAMode);
result += string.Format(" varsD.data.runConfig.relaxedFadeMatch = {0}\n", varsD.data.runConfig.relaxedFadeMatch);
result += string.Format(" varsD.data.runConfig.lastSplitName = {0}\n", varsD.data.runConfig.lastSplitName);
result += string.Format(" varsD.data.runConfig.lastFirstSplitName = {0}\n", varsD.data.runConfig.lastFirstSplitName);
result += "\n";
result += string.Format(" varsD.data.runLiveData.selectedFileID = {0}\n", varsD.data.runLiveData.selectedFileID);
result += string.Format(" varsD.data.runLiveData.previousStage = {0}\n", varsD.data.runLiveData.previousStage);
result += string.Format(" varsD.data.runLiveData.wantToReset = {0}\n", varsD.data.runLiveData.wantToReset);
result += string.Format(" varsD.data.runLiveData.wantToResetTiming = {0}\n", varsD.data.runLiveData.wantToResetTiming);
result += "\n";
result += string.Format(" varsD.settings.isResetEnabled = {0}\n", varsD.settings.isResetEnabled);
result += string.Format(" varsD.settings.currentTimerPhase = {0}\n", varsD.settings.currentTimerPhase);
result += string.Format(" varsD.settings.currentSplitIndex = {0}\n", varsD.settings.currentSplitIndex);
result += string.Format(" varsD.settings.splitCount = {0}\n", varsD.settings.splitCount);
result += string.Format(" varsD.settings.forceLaunchOnStart = {0}\n", varsD.settings.forceLaunchOnStart);
result += string.Format(" varsD.settings.disableAutoStartOnFileD = {0}\n", varsD.settings.disableAutoStartOnFileD);
result += string.Format(" varsD.settings.forceJPGameVersion = {0}\n", varsD.settings.forceJPGameVersion);
result += string.Format(" varsD.settings.forceUSGameVersion = {0}\n", varsD.settings.forceUSGameVersion);
result += string.Format(" varsD.settings.disableResetAfterEnd = {0}\n", varsD.settings.disableResetAfterEnd);
result += string.Format(" varsD.settings.disableRTAMode = {0}\n", varsD.settings.disableRTAMode);
result += string.Format(" varsD.settings.disableBowserRedsDelayedSplit = {0}\n", varsD.settings.disableBowserRedsDelayedSplit);
return result;
};
// Helper function used in code to avoid duplication related to ROM version handling.
Func<dynamic, dynamic, uint> getGameRuntime = delegate(dynamic varsD, dynamic state) {
return varsD.data.runConfig.isJapaneseVersion ? state.gameRunTimeJP : state.gameRunTimeUS;
};
Func<dynamic, dynamic, uint> getGlobalTimer = delegate(dynamic varsD, dynamic state) {
return varsD.data.runConfig.isJapaneseVersion ? state.globalTimerJP : state.globalTimerUS;
};
Func<dynamic, dynamic, byte> getStageIndex = delegate(dynamic varsD, dynamic state) {
return varsD.data.runConfig.isJapaneseVersion ? state.stageIndexJP : state.stageIndexUS;
};
Func<dynamic, dynamic, uint> getAnimation = delegate(dynamic varsD, dynamic state) {
return varsD.data.runConfig.isJapaneseVersion ? state.animationJP : state.animationUS;
};
Func<dynamic, dynamic, ushort> getStarCount = delegate(dynamic varsD, dynamic state) {
return varsD.data.runConfig.isJapaneseVersion ? state.starCountJP : state.starCountUS;
};
Func<dynamic, dynamic, ushort> getHUDCameraMode = delegate(dynamic varsD, dynamic state) {
return varsD.data.runConfig.isJapaneseVersion ? state.hudCameraModeJP : state.hudCameraModeUS;
};
Func<dynamic, dynamic, float> getPositionX = delegate(dynamic varsD, dynamic state) {
return varsD.data.runConfig.isJapaneseVersion ? state.positionXJP : state.positionXUS;
};
Func<dynamic, dynamic, float> getPositionY = delegate(dynamic varsD, dynamic state) {
return varsD.data.runConfig.isJapaneseVersion ? state.positionYJP : state.positionYUS;
};
Func<dynamic, dynamic, float> getPositionZ = delegate(dynamic varsD, dynamic state) {
return varsD.data.runConfig.isJapaneseVersion ? state.positionZJP : state.positionZUS;
};
Func<dynamic, dynamic, byte> getWarpDestination = delegate(dynamic varsD, dynamic state) {
return varsD.data.runConfig.isJapaneseVersion ? state.warpDestinationJP : state.warpDestinationUS;
};
Func<dynamic, dynamic, ushort> getNonStopInteractionOverwrite = delegate(dynamic varsD, dynamic state) {
return varsD.data.runConfig.isJapaneseVersion ? state.nonStopInteractionOverwriteJP : state.nonStopInteractionOverwriteUS;
};
Func<dynamic, dynamic, uint> getBehaviorSegmentInfo = delegate(dynamic varsD, dynamic state) {
return varsD.data.runConfig.isJapaneseVersion ? state.behaviorSegmentInfoJP : state.behaviorSegmentInfoUS;
};
Func<dynamic, dynamic, uint> getObject0Behavior = delegate(dynamic varsD, dynamic state) {
uint segmentOffset = getBehaviorSegmentInfo(varsD, state);
uint virt = varsD.data.runConfig.isJapaneseVersion ? state.object0BehaviorJP : state.object0BehaviorUS;
return 0x13000000 + ((virt & 0x1FFFFFFF) - segmentOffset);
};
Func<dynamic, uint> getBhvActSelector = delegate(dynamic varsD) {
return varsD.data.runConfig.isJapaneseVersion ? BHV_ACT_SELECTOR_JP : BHV_ACT_SELECTOR_US;
};
Func<dynamic, dynamic, uint> getObject0Timer = delegate(dynamic varsD, dynamic state) {
return varsD.data.runConfig.isJapaneseVersion ? state.object0TimerJP : state.object0TimerUS;
};
Func<dynamic, dynamic, ushort> getController0Buttons = delegate(dynamic varsD, dynamic state) {
return varsD.data.runConfig.isJapaneseVersion ? state.controller0ButtonsJP : state.controller0ButtonsUS;
};
Func<dynamic, dynamic, uint> getFileFlags = delegate(dynamic varsD, dynamic state) {
if (varsD.data.runLiveData.selectedFileID == 0) {
return varsD.data.runConfig.isJapaneseVersion ? state.fileAFlagsJP : state.fileAFlagsUS;
} else if (varsD.data.runLiveData.selectedFileID == 1) {
return varsD.data.runConfig.isJapaneseVersion ? state.fileBFlagsJP : state.fileBFlagsUS;
} else if (varsD.data.runLiveData.selectedFileID == 2) {
return varsD.data.runConfig.isJapaneseVersion ? state.fileCFlagsJP : state.fileCFlagsUS;
} else if (varsD.data.runLiveData.selectedFileID == 3) {
return varsD.data.runConfig.isJapaneseVersion ? state.fileDFlagsJP : state.fileDFlagsUS;
}
return 0;
};
Func<dynamic, dynamic, uint> getFileAFlags = delegate(dynamic varsD, dynamic state) {
return varsD.data.runConfig.isJapaneseVersion ? state.fileAFlagsJP : state.fileAFlagsUS;
};
Func<dynamic, dynamic, byte> getMenuSelectedButtonID = delegate(dynamic varsD, dynamic state) {
return varsD.data.runConfig.isJapaneseVersion ? state.menuSelectedButtonIDJP : state.menuSelectedButtonIDUS;
};
Func<dynamic, dynamic, short> getMenuClickPos = delegate(dynamic varsD, dynamic state) {
return varsD.data.runConfig.isJapaneseVersion ? state.menuClickPosJP : state.menuClickPosUS;
};
// isIn3DBox returns true if mario is currently in the defined 3D box.
Func<dynamic, dynamic, dynamic, bool> isIn3DBox = delegate(dynamic varsD, dynamic currentD, dynamic box) {
byte stageIndex = getStageIndex(varsD, currentD);
float positionX = getPositionX(varsD, currentD);
float positionY = getPositionY(varsD, currentD);
float positionZ = getPositionZ(varsD, currentD);
return (
stageIndex == box.stageIndex &&
box.x1 <= positionX && positionX <= box.x2 &&
box.y1 <= positionY && positionY <= box.y2 &&
box.z1 <= positionZ && positionZ <= box.z2
);
};
// isStageFadeIn returns true when mario first enters a stage painting from anywhere in the castle.
Func<byte, byte, bool> isStageFadeIn = delegate(byte stageIndex_old, byte stageIndex_current) {
return (
stageIndex_old != stageIndex_current &&
CASTLE_STAGE_INDEXES[stageIndex_old] &&
STAGE_INDEXES[stageIndex_current]
);
};
// isStageFadeOut returns true when mario exists any stage to castle, or to bowser fight.
Func<byte, byte, bool> isStageFadeOut = delegate(byte stageIndex_old, byte stageIndex_current) {
return (
stageIndex_old != stageIndex_current &&
(
(STAGE_INDEXES[stageIndex_old] && CASTLE_STAGE_INDEXES[stageIndex_current]) ||
(STAGE_INDEXES[stageIndex_old] && BOWSER_FIGHT_STAGE_INDEXES[stageIndex_current]) ||
(BOWSER_FIGHT_STAGE_INDEXES[stageIndex_old] && CASTLE_STAGE_INDEXES[stageIndex_current])
)
);
};
// splitWordsOnWhitespace returns all unique words from text.
Func<string, string[]> splitWordsOnWhitespace = delegate(string text) {
return System.Text.RegularExpressions.Regex.Split(text, @"\s+");
};
// parseStageID takes the id within entry=* and exit=* format, and returns the id as a byte.
Func<string, byte> parseStageID = delegate(string stageID) {
if (STAGE_NAMES_TO_INDEX.ContainsKey(stageID)) {
return STAGE_NAMES_TO_INDEX[stageID];
} else {
try {
return ((byte) Convert.ToInt32(stageID));
} catch (Exception e) {
print(string.Format("The value {0} is not in a recognizable stageID format (number or recognizable stage name): {1}", stageID, e));
return ((byte) 0xff);
}
}
};
// parseStarDoorID taks the id within star-door=* format, and returns the id as a byte.
Func<string, ushort> parseStarDoorID = delegate(string starCount) {
return ((ushort) Convert.ToInt32(starCount));
};
// parseSplitNameForAction is an helper function to make it easier to implement instructions
// parser inside of split names.
Action<dynamic, string, Action<dynamic, dynamic, string[]>, Action<dynamic, dynamic, string>> parseSplitNameForAction = delegate(dynamic varsD, string splitName, Action<dynamic, dynamic, string[]> parseWords, Action<dynamic, dynamic, string> parseInstructions) {
splitName = splitName.TrimStart('-');
string[] splitNameWords = splitWordsOnWhitespace(splitName);
dynamic splitConfig = varsD.data.splitConfig;
dynamic runConfig = varsD.data.runConfig;
if (parseWords != null) {
parseWords(runConfig, splitConfig, splitNameWords);
}
System.Text.RegularExpressions.MatchCollection splitterInstructions1 = BRACKET_TYPE1.Matches(splitName);
foreach (System.Text.RegularExpressions.Match match in splitterInstructions1) {
foreach (string val in match.Groups["values"].Value.Split(',')) {
parseInstructions(runConfig, splitConfig, val);
}
}
System.Text.RegularExpressions.MatchCollection splitterInstructions2 = BRACKET_TYPE2.Matches(splitName);
foreach (System.Text.RegularExpressions.Match match in splitterInstructions2) {
foreach (string val in match.Groups["values"].Value.Split(',')) {
parseInstructions(runConfig, splitConfig, val);
}
}
};
// hasOccurenceOfAnyPhrase returns true if the sequence of words provided contains any of the sequences of words
// in wantedPhrases (eg. does {"this", "is", "a", "test"} contains any of {{ "is", "good" }, {"a", "test"}}.)
Func<string[], List<string[]>, bool> hasOccurenceOfAnyPhrase = delegate(string[] words, List<string[]> wantedPhrases) {
List<string> wordsLst = new List<string>(words);
foreach (string[] wantedPhrase in wantedPhrases) {
if (wantedPhrase.Length == 0) {
continue;
}
string firstWord = wantedPhrase[0];
int wordCount = wantedPhrase.Length;
int startIdx = 0;
while (startIdx != -1) {
startIdx = wordsLst.IndexOf(firstWord, startIdx);
if (startIdx != -1) {
if (string.Join(" ", wordsLst.GetRange(startIdx, wordCount)) == string.Join(" ", wantedPhrase)) {
return true;
}
startIdx++;
}
}
}
return false;
};
// parseRunInstructions parses run config information from the first split.
Action<dynamic, dynamic, string> parseRunInstructions = delegate(dynamic runConfig, dynamic splitConfig, string val) {
System.Text.RegularExpressions.MatchCollection modeMatch = MODE.Matches(val);
if (modeMatch.Count != 0) {
switch (modeMatch[0].Groups["modeName"].Value) {
case "romhack":
runConfig.relaxedFadeMatch = true;
break;
case "rta":
runConfig.isRTAMode = true;
break;
};
}
};
// parseRunConfigFromFirstSplit retrieves run information from the first split name.
Action<dynamic, string> parseRunConfigFromFirstSplit = delegate(dynamic varsD, string splitName) {
parseSplitNameForAction(varsD, splitName, null, parseRunInstructions);
};
// parseSplitConfigWords finds special split types from the split name words.
Action<dynamic, dynamic, string[]> parseSplitConfigWords = delegate(dynamic runConfig, dynamic splitConfig, string[] words) {
if (hasOccurenceOfAnyPhrase(words, BOWSER_FIGHT_KEYWORDS)) {
splitConfig.type = SPLIT_TYPE_KEY_GRAB;
} else if (hasOccurenceOfAnyPhrase(words, BOWSER_STAGE_KEYWORDS)) {
splitConfig.type = SPLIT_TYPE_BOWSER_PIPE_ENTRY;
} else if (hasOccurenceOfAnyPhrase(words, KEY_UNLOCK_KEYWORDS)) {
splitConfig.type = SPLIT_TYPE_KEY_DOOR_UNLOCK;
} else if (hasOccurenceOfAnyPhrase(words, THIRTY_STAR_DOOR_CLIP_KEYWORDS)) {
splitConfig.type = SPLIT_TYPE_THIRTY_STAR_DOOR_CLIP;
}
};
// parseSplitterInstructions takes the contents of splitter instructions (within brackets) and adjusts the current split config accordingly.
Action<dynamic, dynamic, string> parseSplitterInstructions = delegate(dynamic runConfig, dynamic splitConfig, string val) {
System.Text.RegularExpressions.MatchCollection starCountMatch = STAR_COUNT.Matches(val);
if (starCountMatch.Count != 0) {
if (splitConfig.type == SPLIT_TYPE_CASTLE_MOVEMENT) {
splitConfig.type = SPLIT_TYPE_STAR_GRAB;
}
splitConfig.starCountRequirement = Convert.ToInt32(starCountMatch[0].Groups["starCount"].Value);
}
System.Text.RegularExpressions.MatchCollection entryMatch = ENTRY.Matches(val);
if (entryMatch.Count != 0) {
splitConfig.type = SPLIT_TYPE_STAGE_ENTRY;
splitConfig.entryStageID = parseStageID(entryMatch[0].Groups["stageID"].Value);
}
System.Text.RegularExpressions.MatchCollection exitMatch = EXIT.Matches(val);
if (exitMatch.Count != 0) {
splitConfig.type = SPLIT_TYPE_STAGE_EXIT;
splitConfig.exitStageID = parseStageID(exitMatch[0].Groups["stageID"].Value);
}
System.Text.RegularExpressions.MatchCollection starDoorMatch = STAR_DOOR.Matches(val);
if (starDoorMatch.Count != 0) {
splitConfig.type = SPLIT_TYPE_STAR_DOOR_ENTRY;
splitConfig.starDoorID = parseStarDoorID(starDoorMatch[0].Groups["starCount"].Value);
}
switch (val) {
case "fade":
splitConfig.isForcedFade = true;
break;
case "immediate":
splitConfig.isForcedImmediate = true;
break;
case "noreset":
splitConfig.isNoReset = true;
break;
case "manual":
splitConfig.type = SPLIT_TYPE_MANUAL;
break;
case "bowser":
case "key":
splitConfig.type = SPLIT_TYPE_KEY_GRAB;
break;
case "key-door":
splitConfig.type = SPLIT_TYPE_KEY_DOOR_UNLOCK;
break;
case "pipe":
splitConfig.type = SPLIT_TYPE_BOWSER_PIPE_ENTRY;
break;
case "mips":
case "30s-door-clip":
splitConfig.type = SPLIT_TYPE_THIRTY_STAR_DOOR_CLIP;
break;
case "final-star-grab":
splitConfig.type = SPLIT_TYPE_FINAL_STAR_GRAB;
break;
};
};
// parseSplitName configures splitConfig based on information within split name.
Action<dynamic, string> parseSplitName = delegate(dynamic varsD, string splitName) {
parseSplitNameForAction(varsD, splitName, parseSplitConfigWords, parseSplitterInstructions);
};
// updateRunConditionInner is the inner logic of update based exclusively on varsD (no settings/timer use). Helpful
// for testing of general behavior.
Func<dynamic, dynamic, dynamic, bool> updateRunConditionInner = delegate(dynamic varsD, dynamic oldD, dynamic currentD) {
// Game version detection needs to be in update for game switching to work properly (before any current/old use).
varsD.data.runConfig.isJapaneseVersion = (
vars.settings.forceJPGameVersion ||
(
!vars.settings.forceUSGameVersion &&
currentD.debugFunctionJP == DEBUG_FUNCTION_VALUE
)
);
// LiveSplit.AutoSplitter does not run reset once the last segment has been split (game finished),
// which prevents from easily starting a new run after finishing the game. This works around it.
if (
varsD.settings.isResetEnabled &&
varsD.settings.currentTimerPhase == TimerPhase.Ended &&
!varsD.settings.disableResetAfterEnd &&
varsD.functions.resetRunCondition(varsD, oldD, currentD)
) {
varsD.timerModel.Reset();
}
// If we are in a star select menu, we keep track of when the timer was last updated to find when it stops
// (a star was selected) so stage rta mode can start the timer.
uint bhvActSelector = getBhvActSelector(varsD);
uint object0Behavior_current = getObject0Behavior(varsD, currentD);
uint object0Timer_old = getObject0Timer(varsD, oldD);
uint object0Timer_current = getObject0Timer(varsD, currentD);
uint globalTimer_current = getGlobalTimer(varsD, currentD);
if (object0Behavior_current == bhvActSelector && object0Timer_old != object0Timer_current) {
varsD.data.runLiveData.starSelectTimerUpdateCount += 1;
varsD.data.runLiveData.starSelectLastTimerUpdateGT = globalTimer_current;
}
return true;
};
int DEBUG_VARS_DUMP_SECONDS = 0;
// updateRunCondition is run before every loop, we update all the internal data.
Func<LiveSplitState, dynamic, dynamic, dynamic, dynamic, bool> updateRunCondition = delegate(LiveSplitState timerD, dynamic settingsD, dynamic varsD, dynamic oldD, dynamic currentD) {
// Copy settings to var to help with testing.
varsD.settings.isResetEnabled = settingsD.ResetEnabled;
varsD.settings.currentTimerPhase = timerD.CurrentPhase;
varsD.settings.currentSplitIndex = timerD.CurrentSplitIndex;
varsD.settings.splitCount = timerD.Run.Count;
varsD.settings.forceLaunchOnStart = settingsD[LAUNCH_ON_START];
varsD.settings.forceJPGameVersion = settingsD[GAME_VERSION_JP];
varsD.settings.forceUSGameVersion = settingsD[GAME_VERSION_US];
varsD.settings.disableResetAfterEnd = settingsD[DISABLE_RESET_AFTER_END];
varsD.settings.disableRTAMode = settingsD[DISABLE_RTA_MODE];
varsD.settings.disableBowserRedsDelayedSplit = settingsD[DISABLE_BOWSER_REDS_DELAYED_SPLIT];
varsD.settings.disableAutoStartOnFileD = settingsD[DISABLE_AUTO_START_ON_FILE_D];
// Whenever current selected split changes, we parse information which decides when to split.
string currentSplitName = timerD.CurrentSplit != null ? timerD.CurrentSplit.Name.ToLower() : "";
if (varsD.data.runConfig.lastSplitName != currentSplitName) {
resetVarsDataForSplitChange(varsD, currentSplitName);
parseSplitName(varsD, currentSplitName);
}
string currentFirstSplitName = timerD.Run.Count != 0 ? timerD.Run[0].Name.ToLower() : "";
if (varsD.data.runConfig.lastFirstSplitName != currentFirstSplitName) {
resetVarsDataForFirstSplitChange(varsD, currentFirstSplitName);
parseRunConfigFromFirstSplit(varsD, currentFirstSplitName);
}
// DEBUGGING: Add prints here for debugging. Every DEBUG_VARS_DUMP_SECONDS seconds.
if (DEBUG_VARS_DUMP_SECONDS != 0 && (varsD.data.updateCounter % (DEBUG_VARS_DUMP_SECONDS * 60)) == 0) {
print(string.Format("MARIO POSITION: X:{0}, Y:{1}, Z:{2}", getPositionX(varsD, currentD), getPositionY(varsD, currentD), getPositionZ(varsD, currentD)));
print(varsToString(varsD));
}
varsD.data.updateCounter += 1;
// Call inner update logic.
return updateRunConditionInner(varsD, oldD, currentD);
};
// startRunCondition determines if the timer should be started when it is currently stopped.
Func<dynamic, dynamic, dynamic, bool> startRunCondition = delegate(dynamic varsD, dynamic oldD, dynamic currentD) {
uint gameRuntime_old = getGameRuntime(varsD, oldD);
uint gameRuntime_current = getGameRuntime(varsD, currentD);
uint globalTimer_old = getGlobalTimer(varsD, oldD);
uint globalTimer_current = getGlobalTimer(varsD, currentD);
byte stageIndex_old = getStageIndex(varsD, oldD);
byte stageIndex_current = getStageIndex(varsD, currentD);
uint animation_old = getAnimation(varsD, oldD);
uint animation_current = getAnimation(varsD, currentD);
byte menuSelectedButtonID_old = getMenuSelectedButtonID(varsD, oldD);
byte menuSelectedButtonID_current = getMenuSelectedButtonID(varsD, currentD);
short menuClickPos_current = getMenuClickPos(varsD, currentD);
// First frame of the logo appears on frame 4 (1.33s after launch).
if (!varsD.settings.forceLaunchOnStart && globalTimer_current == 4) {
return true;
}
// As soon as game is relaunched if option is selected, quite inconsistent time-wise.
if (varsD.settings.forceLaunchOnStart && stageIndex_current == 1 && gameRuntime_current < gameRuntime_old) {
return true;
}
// When a file is selected on the main menu, timer also starts.
if (
menuSelectedButtonID_old != menuSelectedButtonID_current &&
menuSelectedButtonID_old == 255 &&
0 <= menuSelectedButtonID_current && menuSelectedButtonID_current < 4 &&
(menuSelectedButtonID_current != 3 || !varsD.settings.disableAutoStartOnFileD) &&
menuClickPos_current == -10000
) {
varsD.data.runConfig.selectedFileID = (int) menuSelectedButtonID_current;
return true;
}
// RTA mode, timer starts when we see star select screen, we leave a stage (fade-out) or we touch a door.
uint bhvActSelector = getBhvActSelector(varsD);
uint object0Behavior_current = getObject0Behavior(varsD, currentD);
if (
!varsD.settings.disableRTAMode &&
varsD.data.runConfig.isRTAMode &&
(
isStageFadeOut(stageIndex_old, stageIndex_current) ||
(
animation_old != animation_current &&
animation_current == ACT_UNLOCKING_KEY_DOOR
) ||
(
object0Behavior_current == bhvActSelector &&
2 <= varsD.data.runLiveData.starSelectTimerUpdateCount &&
varsD.data.runLiveData.starSelectLastTimerUpdateGT <= globalTimer_current - 5
)
)
) {
return true;
}
return false;
};
// onResetRunCondition ensures important variable re-initialization always happens after reset.
Action<dynamic> onResetRunCondition = delegate(dynamic varsD) {
varsD.data.runLiveData = initRunLiveData();
};
// resetRunCondition determines if run should be reset, stopping the timer and resetting it to its initial value.
Func<dynamic, dynamic, dynamic, bool> resetRunCondition = delegate(dynamic varsD, dynamic oldD, dynamic currentD) {
uint gameRuntime_old = getGameRuntime(varsD, oldD);