-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.c
1871 lines (1611 loc) · 55.6 KB
/
main.c
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
#pragma bank 0
#include "machine.c"
#include "projectile.c"
#include "stage.c"
#include "maps/goodroadmap.c"
#include "maps/holestartmap.c"
#include "maps/holemap.c"
#include "maps/holeendmap.c"
extern const hUGESong_t deserttheme;
extern const hUGESong_t citytheme;
extern const hUGESong_t mountaintheme;
extern const hUGESong_t tunneltheme;
extern const hUGESong_t fortstratostheme;
extern const hUGESong_t undergroundtheme;
extern const hUGESong_t cleartheme;
extern unsigned char roadtiles[];
extern unsigned char deserttiles[];
extern unsigned char cloudtiles[];
extern unsigned char playerspritetiles[];
extern unsigned char projectiletiles[];
extern unsigned char enemyspritetiles[];
extern unsigned char hudtiles[];
extern unsigned char scorpbosstiles[];
extern unsigned char bossspritetiles[];
extern unsigned char fonttiles[];
extern unsigned char misctiles[];
extern unsigned char titlelogotiles[];
extern unsigned char citytiles[];
extern unsigned char mountaintiles[];
extern unsigned char desertmap[];
extern unsigned char cloudmap[];
extern unsigned char scorpbossmap[];
extern unsigned char titlelogomap[];
extern unsigned char hudmap[];
extern unsigned char citymap[];
extern unsigned char mountainmap[];
extern unsigned char tunneltiles[];
extern unsigned char tunnelmap[];
extern unsigned char fortstiles[];
extern unsigned char fortsmap[];
extern unsigned char fortsinttiles[];
extern unsigned char fortsintmap[];
// Commmon stage processing vars
UINT8 stageidx; // For the stage arrays
UBYTE holeflg; // Flag indicating if currently rendered area is a hole or a road
UINT8 stageclearflg, bossclearflg; // stage/boss completed flags
const Placement * lvlplacptr; // stage placement objects array pointer
const UINT8 lanesy[] = {98, 114, 130};
const UINT8 placmntxpos = 167; // Initial x position of stage enemies
// Stages data
extern const UINT8 stage1road[], stage2road[], stage3road[], stage4road[], stage5road[], stage6road[];
extern const Placement stage1objs[], stage2objs[], stage3objs[], stage4objs[], stage5objs[], stage6objs[];
extern const UINT8 scorpbossexpl[5][2], jggrbossexpl[7][2], mechbossexpl[4][2], genrlbossexpl[5][2], defsysbossexpl[6][2], ultgenexplcrdsground[4][2], ultgenbossexpl[5][2], emittersexpl[5][2];
extern UINT8 jgrbkgposx, jgrposx;
const UINT8 pausesign[5] = {0x23, 0x14, 0x28, 0x26, 0x18};
const Stage stages[] = {{stage1road, 17, stage1objs, deserttiles, 39, desertmap, 0, 1, 2, &deserttheme},
{stage2road, 25, stage2objs, citytiles, 46, citymap, 1, 1, 3, &citytheme},
{stage3road, 27, stage3objs, mountaintiles, 61, mountainmap, 1, 1, 4, &mountaintheme},
{stage4road, 31, stage4objs, tunneltiles, 64, tunnelmap, 1, 0, 5, &tunneltheme}, // Temporarily using some assets from previous levels
{stage5road, 33, stage5objs, fortstiles, 104, fortsmap, 1, 0, 6, &fortstratostheme},
{stage6road, 39, stage6objs, fortsinttiles, 67, fortsintmap, 0, 0, 6, &undergroundtheme}
};
const Stage * crntstage = stages; // Current stage pointer
UINT8 stagenum; // Current stage counter
// Enemies order - 0 - rider, 1 - drone, 2 - rocket, 3 - turret, 4 - bomber, 5 - mine, 6 - laser turret, 7 - tri-turret, 8 - seeker, 9 - explosion/boss
const INT8 enprops[10][10] = {{1, 2, 0, 1, 13, 15, -3, 12, 0, 23},
{0, 1, 2, 5, 14, 7, 7, 13, 1, 27},
{1, 50, 2, 5, 13, 10, 1, 4, 2, 31},
{0, 100, 3, 2, 14, 12, 7, -4, 3, 35},
{0, 6, 0, 5, 13, 7, 4, 11, 4, 39},
{1, 50, 3, 2, 11, 11, 0, 0, 5, 43},
{1, 100, 0, 0, 15, 13, 6, 13, 6, 47},
{1, 100, 0, 0, 15, 5, 7, 7, 7, 51},
{1, 2, 0, 4, 15, 9, 0, 0, 8, 55},
{0, 0, 0, 0, 16, 16, 0, 0, 120, 120}
};
// Projectiles order - 0 - bullet, 1 - bigbullet, 2 - horizontal laser, 3 - plasma, 4 - bomb, 5 - vertical laser
const UINT8 projctlprops[6][4] = {{3, 3, 1, 17},
{4, 4, 2, 18},
{8, 3, 3, 19},
{8, 8, 5, 20},
{8, 8, 5, 21},
{3, 8, 3, 22}};
// Represents the screen, divided into 16x16px squares, each containing the slope
// between the player and enemy projectile
const UINT8 slopesidx[8][9] = {{8, 1, 1, 1, 1, 1, 1, 1, 1},
{8, 1, 4, 3, 2, 2, 2, 2, 2},
{8, 2, 1, 6, 4, 4, 3, 3, 3},
{8, 3, 1, 1, 8, 6, 6, 4, 4},
{8, 4, 2, 1, 1, 8, 7, 6, 6},
{8, 4, 2, 2, 1, 1, 8, 7, 7},
{8, 4, 3, 2, 1, 1, 1, 8, 8},
{8, 4, 3, 2, 2, 1, 1, 1, 9}};
UINT8 roadbuildidx; // index for the stage road array
UINT8 camtileidx, nextcamtileidx; // current tile index of the right camera border, index of area where
// the next part of the stage should be drawn
UINT8 i, citr; // iterators for whenever
UINT8 roadposx, sceneryposx, cloudposx;
const UINT8 roadscrspeed = 5; // Road scrolling speed
UINT8 plspeed;
const UINT8 jumpspeed = 3;
const UINT8 plgroundspeed = 1; // Player movement speed
const UINT8 roadboundy = 101; // Vertical bound between road and sky
UINT8 oamidx;
const UINT8 lockedoamtiles = 4; // Number of locked tiles in the OAM (currently reserved for player tiles)
const unsigned char blanktile[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
Projectile projectiles[10]; // Array of all the projectiles
Projectile * crntpjct, * pjctptr; // Pointer to current position in projectile array / Pointer for loops
const UINT8 pjctllimit = 9; // Projectile array index limit(max projectiles on screen allowed - 1)
UINT8 prjcnt; // Projectile counter, index
const UINT8 screenminx = 8, screenminy = 16; // 0 + horizontal offset / 0 + vertical offset
const UINT8 screenmaxx = 168, screenmaxy = 144; // x0 + screen width / y0 + screen height
UINT8 abtncnt; // Counts cycles after A button press
const UINT8 abtncooldown = 24; // Cycles to wait until A button becomes active
Machine machines[6];
Machine * pl; // Pointer to the player element in the machines array
Machine * crntenemy, * machptr; // Pointer for current enemy in the machines array / machine pointer for loops
UINT8 pllives;
const UINT8 enlimit = 5; // Enemy limit, with player makes for max num of machines
const UINT8 pliframeprd = 120; // Iframe duration
const UINT8 expldur = 32; // Explosion duration
UINT8 lockmvmnt; // 0 - no locking, 1 - horizontal, 2 - vertical, 3 - both
Machine * hitmchptr; // Pointer to the enemy who was hit last. Used during boss fights for animations
Machine * fsten; // Pointer to the first enemy in the pool. Used extensively during bosses
UINT8 iframecnt;
UBYTE iframeflg;
UBYTE fallinholeflg;
UBYTE ascendflg; // Ascending or descending state during jump animation
const UINT8 jumplimity = 50; // Jumping vert distance limit
const UINT8 jumphalflimy = 25;
UINT8 jumpstarty; // Player y position at start of jump
UINT8 holestartx, holeendx; // Used to indicate start and end x position of road holes
UBYTE isapressed; // Indicates that A is currently pressed
UINT8 chmutedcyccnt[] = {255, 255, 255, 255}; // Used to mute a sound channel for a number of cycles
INT8 fractdiv; // Precision factor for caluculating coordinates
INT8 slope; // Used to calculate projectile trajectory when aimed at player
INT16 gradient; // Used to calculate projectile trajectory when aimed at player
UINT8 hitanimtmr; // Damage animation timer when boss is hit
UINT8 plgun, defaultplgun, numkills;
UINT8 menuidx, extrasflg, bossrushflg;
UINT8 cycrulecheck; // Keeping track of cyles, used for optimization purposes
UINT8 animitr = 0; // Used during cutscenes for animations
UINT8 oami; // Used in loop to get free sprites
const UINT8 numkillsthresh[4] = {10, 20, 20, 255}; // Number of kills required to get a gun upgrade and an extra life
UINT8 get_OAM_free_tile_idx() NONBANKED;
void custom_delay(UINT8 cycles) NONBANKED;
inline void incr_cycle_counter() NONBANKED;
void incr_oam_sprite_tile_idx(INT8 steps) NONBANKED;
void itr_enemies_ptr() NONBANKED;
inline void incr_projectile_counter() NONBANKED;
inline void itr_projectile_ptr() NONBANKED;
inline UBYTE found_free_projectile_space() NONBANKED;
inline UINT8 get_tile_idx(UINT8 newidxnum) NONBANKED;
void reset_sprites(UINT8 fstsprite, UINT8 lastsprite) NONBANKED;
void init_stage_bkg(UINT8 stnum) NONBANKED;
void init_stage_road() NONBANKED;
void set_machine_tile(Machine * mch, UINT8 tlnum) NONBANKED;
void set_machine_sprite_tiles(Machine * mch, UINT8 fsttile) NONBANKED;
void place_machine(Machine * mch, UINT8 x, UINT8 y) NONBANKED;
void init_player() NONBANKED;
void respawn_player() NONBANKED;
void init_machine_props(UINT8 x, UINT8 y, const INT8 * mchprops) NONBANKED;
void init_scorpboss() BANKED;
inline UBYTE collides_with_sidewalk(INT8 vspeed) NONBANKED;
inline UBYTE is_inside_x_bounds(UINT8 posx) NONBANKED;
void move_machine(Machine * mch, INT8 speedx, INT8 speedy) NONBANKED;
void move_player(INT8 speedx, INT8 speedy) NONBANKED;
void move_enemy(Machine * en, INT8 speedx, INT8 speedy) NONBANKED;
void incr_bkg_x_coords(UINT8 roadsp) NONBANKED;
void incr_boss_bkg_x_coords(UINT8 roadsp, UINT8 jgrspeed) NONBANKED;
void scroll_stage_bkg_outd() NONBANKED;
void scroll_stage_bkg_ind() NONBANKED;
void scroll_boss_bkg() NONBANKED;
void disable_bkg_scroll(UINT8 stageidx) NONBANKED;
void build_stage() NONBANKED;
void build_road() NONBANKED;
void build_hole() NONBANKED;
void build_boss_road() NONBANKED;
void manage_hole_props();
void manage_projectiles() NONBANKED;
void manage_machines(UINT8 limit) NONBANKED;
void manage_sound_chnls() NONBANKED;
void manage_player() NONBANKED;
inline UINT8 get_horiz_dist(UINT8 fstobjx, UINT8 sndobjx) NONBANKED;
void set_projctl_comm_prop(Machine * mch, UINT8 type, INT8 speedx, INT8 speedy) NONBANKED;
void fire_projctl(Machine * mch, UINT8 type, INT8 speedx, INT8 speedy) NONBANKED;
void fire_projctl_aimed(Machine * mch, UINT8 type, INT8 speedxy) NONBANKED;
inline UBYTE is_obj_inside_screen(UINT8 x, UINT8 y, UINT8 width, UINT8 height) NONBANKED;
inline UBYTE is_alive(Machine * mch) NONBANKED;
void destroy_projectile(Projectile * pr) NONBANKED;
void move_projectile(Projectile * pr) NONBANKED;
void init_explosion(Machine * mch) NONBANKED;
void explode_machine(Machine * mch) NONBANKED;
Machine * create_explosion(UINT8 x, UINT8 y) NONBANKED;
void anim_explode_boss(const UINT8 explarr[][2], UINT8 numexpl, UINT8 hasscroll, UINT8 offsx, UINT8 offsy) NONBANKED;
void take_damage(Machine * mch, UINT8 dmgamt) NONBANKED;
void check_iframes() NONBANKED;
void destroy_machine(Machine * mch) NONBANKED;
void check_projectile_collsn(Machine * mch, Projectile * prj) NONBANKED;
void check_player_machine_collsn(Machine * mch) NONBANKED;
void anim_jump() NONBANKED;
void exec_enemy_pattern(Machine * mch) NONBANKED;
void exec_rider_pattern(Machine * mch) NONBANKED;
void exec_drone_pattern(Machine * mch) NONBANKED;
void exec_missile_pattern(Machine * mch) NONBANKED;
void exec_turret_pattern(Machine * mch) NONBANKED;
void exec_bomber_pattern(Machine * mch) NONBANKED;
void exec_mine_pattern(Machine * mch) NONBANKED;
void exec_laserturret_pattern(Machine * mch) NONBANKED;
void exec_triturret_pattern(Machine * mch) BANKED;
void exec_seeker_pattern(Machine * mch) BANKED;
UBYTE cooldown_enemy(Machine * mch, UINT8 period) NONBANKED;
void hud_init() NONBANKED;
void hud_upd_shield() NONBANKED;
inline void hud_upd_lives() NONBANKED;
void hud_clear_pause() NONBANKED;
inline void hud_draw_gun() NONBANKED;
void init_game() NONBANKED;
void init_stage(UINT8 stnum, UBYTE hasscroll) NONBANKED;
void stage_loop() NONBANKED;
void scorpboss_loop() BANKED;
void pause_game() NONBANKED;
void clear_all_projectiles() NONBANKED;
void anim_stage_start() NONBANKED;
void anim_stage_end() NONBANKED;
void anim_blackout_loop(UINT8 indictr) NONBANKED;
void anim_blackout() NONBANKED;
void anim_reverse_blackout() NONBANKED;
void mute_music_pl_chnl(UINT8 chnum) NONBANKED;
void upd_mute_chnl_cycles(UINT8 chnum) NONBANKED;
void unmute_all_channels() NONBANKED;
void se_fall_in_hole() NONBANKED;
void se_fire_bullet() NONBANKED;
void se_fire_laser() NONBANKED;
void se_drop_bomb() NONBANKED;
void se_fire_plasma() NONBANKED;
void se_explode() NONBANKED;
void se_get_hit() NONBANKED;
void se_jump() NONBANKED;
void se_pause() NONBANKED;
void se_wpn_upgrd() NONBANKED;
void main_menu() BANKED;
void game_over_menu() BANKED;
void stage_intro_screen(UINT8 stnum) BANKED;
void play_stage() NONBANKED;
void play_boss() NONBANKED;
void init_boss(UINT8 stnum) NONBANKED;
void boss_loop(UINT8 stnum) NONBANKED;
void update_hit_anim_counter() NONBANKED;
void boss_clear_sequence(UINT8 stnum) NONBANKED;
void toggle_mute_music(UINT8 toggleon) NONBANKED;
void mech_clear_sequence() BANKED;
void init_mechboss() BANKED;
void mechboss_loop() BANKED;
void init_jggrrboss() BANKED;
void jggrrboss_loop() BANKED;
void show_textbox(UINT8 dialidx) NONBANKED;
void disable_boss_bkg_scroll() BANKED;
void init_mechbrosboss() BANKED;
void mechbrosboss_loop() BANKED;
void destroy_mech(Machine * mech) BANKED;
void init_genrlboss() BANKED;
void genrlboss_loop() BANKED;
void genrl_clear_sequence() BANKED;
void init_defsysboss() BANKED;
void defsysboss_loop() BANKED;
void defsys_clear_sequence() BANKED;
void init_emitters_boss() BANKED;
void emitters_boss_loop() BANKED;
void init_ultgen_boss() BANKED;
void ultgen_boss_loop() BANKED;
void play_pre_encore_cutscene() BANKED;
void ending_sequence_prep() BANKED;
void ultgen_flash_sparks(INT8 coordsarr[][2], UINT8 arrlen) BANKED;
void anim_airbase_destr(UBYTE moveplflg) BANKED;
void scroll_textbox(UINT8 dialidx) BANKED;
void play_epilogue() BANKED;
void play_credits() BANKED;
void play_prologue() BANKED;
void boss_rush_results_screen() BANKED;
void init_boss_overworld_theme() BANKED;
void init_boss_airbase_theme() BANKED;
void init_final_boss_theme() BANKED;
UINT8 get_OAM_free_tile_idx() NONBANKED {
for(oami = 4; oami < 40; oami++) {
if(shadow_OAM[oami].y == 0) {
return oami; // First free tile for usage
}
}
return 180; // No free OAM tile space found
}
void custom_delay(UINT8 cycles) NONBANKED {
for(citr = 0; citr < cycles; citr++) {
wait_vbl_done();
}
}
inline void incr_cycle_counter() NONBANKED {
cycrulecheck = cycrulecheck == 3 ? 0 : cycrulecheck + 1;
}
void incr_oam_sprite_tile_idx(INT8 steps) NONBANKED {
UINT8 nextoamind = oamidx + steps;
oamidx = nextoamind < 40 ? nextoamind : nextoamind - 40 + lockedoamtiles; // Out of bounds check and reset
if(shadow_OAM[oamidx].y != 0) { // If next OAM struct used, loop to find a free one
oamidx = get_OAM_free_tile_idx();
}
}
void itr_enemies_ptr() NONBANKED {
crntenemy = crntenemy == machines + enlimit ? machines + 1 : crntenemy + 1;
if(crntenemy->y != 0) { // If next element is used, loop to find a free one
for(i = 1; i != enlimit + 1; i++) {
if((machines + i)->y == 0) {
crntenemy = machines + i;
break;
}
}
}
}
inline void incr_projectile_counter() NONBANKED {
prjcnt += prjcnt == pjctllimit + 1 ? 0 : 1;
}
inline void itr_projectile_ptr() NONBANKED {
crntpjct = crntpjct == projectiles + pjctllimit ? projectiles : crntpjct + 1;
}
inline UBYTE found_free_projectile_space() NONBANKED { // Checks if there is free space in memory to spawn a projectile
return prjcnt != pjctllimit + 1 && oamidx != 180;
}
inline UINT8 get_tile_idx(UINT8 newidxnum) NONBANKED { // Recalculate tile index according to the OAM tile limit
return newidxnum > 31 ? newidxnum - 32 : newidxnum;
}
void reset_sprites(UINT8 fstsprite, UINT8 lastsprite) NONBANKED {
for(i = fstsprite; i != lastsprite + 1; i++) {
set_sprite_tile(i, 0);
set_sprite_prop(i, 0);
move_sprite(i, 0, 0);
}
wait_vbl_done();
}
void init_stage_bkg(UINT8 stnum) NONBANKED {
const Stage * stg = stages + stnum;
SWITCH_ROM_MBC5(stg->stagebank);
set_bkg_data(94, stg->bkgtilesnum, stg->bkgtiles);
set_bkg_tiles(0, stnum == 2 && stageclearflg != 0 ? 4 : stg->hasclouds, 32, 10 - stg->hasclouds, stg->bkgmap);
if(stg->hasclouds) {
SWITCH_ROM_MBC5(11);
set_bkg_data(53, 13, cloudtiles);
set_bkg_tiles(0, 0, 32, 1, cloudmap);
SWITCH_ROM_MBC5(stg->stagebank);
}
}
void init_stage_road() NONBANKED { // Layout initial road tiles to start the stage
SWITCH_ROM_MBC5(11);
set_bkg_data(66, 28, roadtiles);
for(roadbuildidx = 0; roadbuildidx != 7; roadbuildidx++) {
set_bkg_tiles(roadbuildidx * 3, 10, 3, 7, goodroadmap);
}
camtileidx = 18;
nextcamtileidx = camtileidx + 3;
}
void set_machine_tile(Machine * mch, UINT8 tlnum) NONBANKED { // Sets all machine tiles to a specific tile from memory
set_sprite_tile(mch->oamtilenums[0], tlnum);
set_sprite_tile(mch->oamtilenums[1], tlnum);
set_sprite_tile(mch->oamtilenums[2], tlnum);
set_sprite_tile(mch->oamtilenums[3], tlnum);
}
void set_machine_sprite_tiles(Machine * mch, UINT8 fsttile) NONBANKED {
set_sprite_tile(mch->oamtilenums[0], fsttile);
set_sprite_tile(mch->oamtilenums[1], fsttile + 2);
set_sprite_tile(mch->oamtilenums[2], fsttile + 1);
set_sprite_tile(mch->oamtilenums[3], fsttile + 3);
}
void place_machine(Machine * mch, UINT8 x, UINT8 y) NONBANKED {
mch->x = x;
mch->y = y;
move_sprite(mch->oamtilenums[0], x, y);
move_sprite(mch->oamtilenums[1], x + 8, y);
move_sprite(mch->oamtilenums[2], x, y + 8);
move_sprite(mch->oamtilenums[3], x + 8, y + 8);
}
void init_player() NONBANKED {
pl->groundflg = 1;
pl->hboffx = 3;
pl->hboffy = 1;
pl->width = 13;
pl->height = 15;
pl->gunoffx = 17;
pl->gunoffy = 12;
pl->explcount = 0;
pl->cyccount = 0;
pl->oamtilenums[0] = 0;
pl->oamtilenums[1] = 1;
pl->oamtilenums[2] = 2;
pl->oamtilenums[3] = 3;
set_machine_sprite_tiles(pl, 1);
incr_oam_sprite_tile_idx(4);
place_machine(pl, 248, lanesy[1]);
}
void respawn_player() {
pl->shield = 4;
pl->hboffx = 3;
pl->hboffy = 1;
ascendflg = 1;
hud_upd_shield();
if(fallinholeflg) {
fallinholeflg = 0;
pl->groundflg = 0;
set_machine_sprite_tiles(pl, 13);
place_machine(pl, 16, 64);
lockmvmnt = 2;
jumpstarty = lanesy[1];
} else {
pl->groundflg = 1;
set_machine_sprite_tiles(pl, 1);
place_machine(pl, 16, lanesy[1]);
lockmvmnt = 0;
}
iframeflg = 1;
}
// ENEMIES INITIALIZATION
void init_machine_props(UINT8 x, UINT8 y, const INT8 * mchprops) NONBANKED {
crntenemy->explcount = 0;
crntenemy->cyccount = 0;
crntenemy->groundflg = mchprops[0];
crntenemy->shield = mchprops[1];
crntenemy->hboffx = mchprops[2];
crntenemy->hboffy = mchprops[3];
crntenemy->width = mchprops[4];
crntenemy->height = mchprops[5];
crntenemy->gunoffx = mchprops[6];
crntenemy->gunoffy = mchprops[7];
crntenemy->type = mchprops[8];
for(i = 0; i < 4; i++) {
crntenemy->oamtilenums[i] = oamidx;
incr_oam_sprite_tile_idx(1);
}
set_machine_sprite_tiles(crntenemy, mchprops[9]);
place_machine(crntenemy, x, y);
itr_enemies_ptr();
}
inline UBYTE collides_with_sidewalk(INT8 vspeed) NONBANKED {
return pl->y + vspeed < 96 || pl->y + vspeed > 127;
}
inline UBYTE is_inside_x_bounds(UINT8 posx) NONBANKED {
return posx > 15 && posx < 141;
}
void move_machine(Machine * mch, INT8 speedx, INT8 speedy) NONBANKED {
mch->x += speedx;
mch->y += speedy;
move_sprite(mch->oamtilenums[0], mch->x, mch->y);
move_sprite(mch->oamtilenums[1], mch->x + 8, mch->y);
move_sprite(mch->oamtilenums[2], mch->x, mch->y + 8);
move_sprite(mch->oamtilenums[3], mch->x + 8, mch->y + 8);
}
void move_player(INT8 speedx, INT8 speedy) NONBANKED {
if((speedx != 0 && is_inside_x_bounds(pl->x + speedx))
|| (speedy != 0 && !collides_with_sidewalk(speedy)) ) {
move_machine(pl, speedx, speedy);
}
}
void move_enemy(Machine * en, INT8 speedx, INT8 speedy) NONBANKED {
move_machine(en, speedx, speedy);
if(!is_obj_inside_screen(en->x, en->y, 16, 16)) {
destroy_machine(en);
}
}
void incr_bkg_x_coords(UINT8 roadsp) NONBANKED {
__critical {
cloudposx += roadsp - 3;
sceneryposx += roadsp - 1;
roadposx += roadsp;
}
}
void incr_boss_bkg_x_coords(UINT8 roadsp, UINT8 jgrspeed) NONBANKED {
__critical {
cloudposx += roadsp - 3;
jgrbkgposx += roadsp - 4 + jgrspeed;
sceneryposx += roadsp - 1;
roadposx += roadsp;
}
}
void scroll_stage_bkg_outd() NONBANKED {
switch(LYC_REG) {
case 0x00:
move_bkg(cloudposx, 0);
LYC_REG = 0x07;
break;
case 0x07:
move_bkg(sceneryposx, 0);
LYC_REG = 0x50;
break;
case 0x50:
move_bkg(roadposx, 0);
LYC_REG = 0x00;
break;
}
}
void scroll_stage_bkg_ind() NONBANKED {
switch(LYC_REG) {
case 0x00:
move_bkg(sceneryposx, 0);
LYC_REG = 0x50;
break;
case 0x50:
move_bkg(roadposx, 0);
LYC_REG = 0x00;
break;
}
}
void scroll_boss_bkg() NONBANKED {
switch(LYC_REG) {
case 0x00:
move_bkg(cloudposx, 0);
LYC_REG = 0x07;
break;
case 0x07:
move_bkg(jgrbkgposx, 0);
LYC_REG = 0x2F;
break;
case 0x2F:
move_bkg(sceneryposx, 0);
LYC_REG = 0x50;
break;
case 0x50:
move_bkg(roadposx, 0);
LYC_REG = 0x00;
break;
}
}
void disable_bkg_scroll(UINT8 stageidx) NONBANKED {
__critical {
cloudposx = sceneryposx = roadposx = 0;
if(stages[stageidx].hasclouds) {
scroll_stage_bkg_outd();
remove_LCD(scroll_stage_bkg_outd);
} else {
scroll_stage_bkg_ind();
remove_LCD(scroll_stage_bkg_ind);
}
disable_interrupts();
}
}
void build_stage() NONBANKED { // Automatically builds the road ahead while scrolling the stage
camtileidx = get_tile_idx((SCX_REG + 168) / 8);
if(camtileidx == nextcamtileidx) {
if(holeflg) {
build_hole();
} else {
build_road();
}
if(roadbuildidx == crntstage->roadlayout[stageidx]) {
stageidx++; // Moving to next elem in stage array
roadbuildidx = 0;
holeflg = !holeflg; // Roads and holes alternate
} else {
roadbuildidx++;
}
// Placing objects inside stage
if(lvlplacptr->arridx == stageidx && lvlplacptr->elemidx == roadbuildidx) {
init_machine_props(placmntxpos, lvlplacptr->y, *(enprops + lvlplacptr->type));
lvlplacptr++;
}
}
if(stageidx == crntstage->roadlength) { // End of stage reached
stageclearflg = 1;
}
}
void build_road() NONBANKED {
set_bkg_tiles(camtileidx, 10, 3, 7, goodroadmap);
nextcamtileidx = get_tile_idx(camtileidx + 3);
}
void build_hole() NONBANKED {
if(roadbuildidx == 0) {
//holestartx = 208;
holestartx = 238;
set_bkg_tiles(camtileidx, 10, 4, 7, holestartmap);
nextcamtileidx = get_tile_idx(camtileidx + 4);
} else if(roadbuildidx == crntstage->roadlayout[stageidx]) {
//holeendx = 144;
holeendx = 174;
holestartx = 255; // Resetting hole start value
set_bkg_tiles(camtileidx, 10, 3, 7, holeendmap);
nextcamtileidx = get_tile_idx(camtileidx + 3);
} else {
set_bkg_tiles(camtileidx, 10, 3, 7, holemap);
nextcamtileidx = get_tile_idx(camtileidx + 3);
}
}
void build_boss_road() NONBANKED {
camtileidx = get_tile_idx((SCX_REG + 168) / 8);
if(camtileidx == nextcamtileidx) {
build_road();
}
}
void manage_hole_props() NONBANKED {
if(holestartx != 255 && holestartx > screenminx) { // Road hole has appeared
holestartx -= roadscrspeed;
}
if (holeendx != 0) { // Road hole end reached
holeendx -= roadscrspeed;
} else if(holeendx < screenminx) {
holeendx = 0; // Resetting hole end value
}
}
void manage_projectiles() NONBANKED {
for(pjctptr = projectiles; pjctptr <= projectiles + pjctllimit; pjctptr++) { // Projectiles handling
if(pjctptr->oam != NULL) {
move_projectile(pjctptr);
if ((cycrulecheck == 3)) { // Check for collision every othe cycle
for(machptr = machines; machptr <= machines + enlimit; machptr++) {
if(is_alive(machptr)) {
if(pl == machptr && iframeflg) {
continue; // Avoid hit during iframes
}
check_projectile_collsn(machptr, pjctptr); // Check collision for all machines on screen
}
}
}
}
}
}
void manage_machines(UINT8 limit) NONBANKED {
for(machptr = machines; machptr <= machines + limit; machptr++) { // Player and enemies handling
if(machptr != pl && machptr->shield != 0) {
if(!iframeflg && cycrulecheck == 1 && pl->explcount == 0 && pl->groundflg == machptr->groundflg) { // Player hasn't exploded
check_player_machine_collsn(machptr);
}
if(machptr->explcount == 0) {
exec_enemy_pattern(machptr);
}
}
if(machptr->explcount != 0) {
explode_machine(machptr);
}
}
}
void manage_sound_chnls() NONBANKED {
for(i = 0; i < 4; i++) {
if(chmutedcyccnt[i] != 255) {
upd_mute_chnl_cycles(i);
}
}
}
void manage_player() NONBANKED {
if(iframeflg) { // Invincibility period
check_iframes();
}
if(lockmvmnt != 2) {
if(pl->x + pl->width > holestartx && !fallinholeflg) { // Hole in the road and player is inside
fallinholeflg = 1;
lockmvmnt = 3;
se_fall_in_hole();
}
} else {
anim_jump();
}
if(fallinholeflg) {
move_machine(pl, -1, 2); // Animate fall
if(pl->y > 144 && pl->explcount == 0) {
take_damage(pl, pl->shield);
}
}
if(!is_alive(pl) && pllives != 0 && pl->explcount == 0) {
respawn_player();
}
if(abtncnt != 0) {
abtncnt = abtncnt == abtncooldown ? 0 : abtncnt + 1;
}
if(lockmvmnt != 1 && lockmvmnt != 3) { // Check horizontal lock
if(joypad() & J_LEFT) {
move_player(-plspeed, 0);
}
if(joypad() & J_RIGHT) {
move_player(plspeed, 0);
}
}
if(lockmvmnt != 2 && lockmvmnt != 3) { // Check vertical lock
if(joypad() & J_UP) {
move_player(0, -plspeed);
}
if(joypad() & J_DOWN) {
move_player(0, plspeed);
}
}
if(joypad() & J_B) {
if(abtncnt == 0 && lockmvmnt != 3) { // Check cooldown period before firing
fire_projctl(pl, plgun, 2, 0);
abtncnt = 1;
}
}
if(joypad() & J_A) {
if(lockmvmnt == 0 && !isapressed) {
lockmvmnt = 2; // Lock vert movement during jump
pl->groundflg = 0;
jumpstarty = pl->y; // Keeping player y position for landing
set_machine_sprite_tiles(pl, 13);
se_jump();
}
isapressed = 1;
} else {
isapressed = 0;
}
if(joypad() & J_START) {
pause_game();
}
}
inline UINT8 get_horiz_dist(UINT8 fstobjx, UINT8 sndobjx) NONBANKED { // Returns the distance between 2 objects on x axis
return fstobjx > sndobjx ? fstobjx - sndobjx : sndobjx - fstobjx;
}
void set_projctl_comm_prop(Machine * mch, UINT8 type, INT8 speedx, INT8 speedy) NONBANKED {
crntpjct->speedx = speedx;
crntpjct->speedy = speedy;
crntpjct->oam = (shadow_OAM + oamidx);
crntpjct->oam->x = mch->x + mch->gunoffx;
crntpjct->oam->y = mch->y + mch->gunoffy;
crntpjct->width = projctlprops[type][0];
crntpjct->height = projctlprops[type][1];
crntpjct->damage = projctlprops[type][2];
set_sprite_tile(oamidx, projctlprops[type][3]);
if(crntpjct->aimedflg == 1) { // Case for projectiles that should be aimed at the player
fractdiv = 1; // Default starting value
if(crntpjct->oam->x > pl->x && crntpjct->oam->x < pl->x + pl->width) { // Firing straight up/down
crntpjct->aimedflg = 0; // The projectile's coordinates do not have to be calculated using the slope equation
} else if(crntpjct->oam->y > pl->y && crntpjct->oam->y < pl->y + pl->height) { // Firing left/right
crntpjct->aimedflg = 0;
fractdiv = 10; // Using value to indicate horizontal trajectory
} else {
INT8 hdistidx = (crntpjct->oam->x - pl->x) >> 4; // Distance between projectile and player in 16x16px suares
INT8 vdistidx = (crntpjct->oam->y - pl->y) >> 4;
hdistidx = hdistidx < 0 ? -hdistidx : hdistidx;
vdistidx = vdistidx < 0 ? -vdistidx : vdistidx;
slope = slopesidx[vdistidx][hdistidx]; // Assign a slope using the square distances as indexes
if(vdistidx < hdistidx) { // Slopes in the array that match the condition are fractions between 0 - 1
fractdiv = 10; // Multiplying/Dividing by set amount to simulate fractions calculations without actually implementing them
}
if((pl->x < crntpjct->oam->x && pl->y > crntpjct->oam->y) || (pl->x > crntpjct->oam->x && pl->y < crntpjct->oam->y)) {
slope = -slope; // Setting projectile direction based on it's location, compared to the player's
}
gradient = crntpjct->oam->y - ((slope * crntpjct->oam->x) / fractdiv );
}
if(fractdiv == 10) { // Deciding wether x or y should be calculated or change at a constant rate
crntpjct->speedx = pl->x > crntpjct->oam->x ? speedx : -speedx; // X will change at the rate of speedx on each cycle
crntpjct->speedy = 0; // Y coordinates will be calculated using the slope equation(See definition of move_projectile for more details)
} else {
crntpjct->speedy = pl->y > crntpjct->oam->y ? speedy : -speedy;
crntpjct->speedx = 0;
}
}
incr_oam_sprite_tile_idx(1);
incr_projectile_counter();
itr_projectile_ptr();
switch(type) {
case 0:
se_fire_bullet();
break;
case 1:
se_fire_bullet();
break;
case 2:
se_fire_laser();
break;
case 3:
se_fire_plasma();
break;
case 4:
se_drop_bomb();
break;
case 5:
se_fire_laser();
break;
}
}
void fire_projctl(Machine * mch, UINT8 type, INT8 speedx, INT8 speedy) NONBANKED {
crntpjct->aimedflg = 0;
set_projctl_comm_prop(mch, type, speedx, speedy);
}
void fire_projctl_aimed(Machine * mch, UINT8 type, INT8 speedxy) NONBANKED {
crntpjct->aimedflg = 1;
set_projctl_comm_prop(mch, type, speedxy, speedxy);
}
inline UBYTE is_obj_inside_screen(UINT8 x, UINT8 y, UINT8 width, UINT8 height) NONBANKED {
return (x < screenmaxx && screenminx < x + width) &&
(y < screenmaxy && screenminy < y + height);
}
inline UBYTE is_alive(Machine * mch) NONBANKED {
return mch->shield > 0;
}
void destroy_projectile(Projectile * pr) NONBANKED {
pr->oam->x = 0;
pr->oam->y = 0;
pr->oam->tile = 0;
pr->oam = NULL;
prjcnt--;
}
void init_explosion(Machine * mch) NONBANKED {
mch->explcount = 1;
mch->hboffx = -(mch->x + mch->width); // x + offx + width == 0
mch->hboffy = -(mch->y + mch->height); // Avoiding damage dealing to player while exploding
if(mch->width < 9) {
move_enemy(mch, -4, 0);
}
if(mch->height < 9) {
move_enemy(mch, 0, -4);
}
se_explode();
}
void move_projectile(Projectile * pr) NONBANKED {
if(pr->oam != NULL && is_obj_inside_screen(pr->oam->x, pr->oam->y, pr->width, pr->height)) {
if(pr->aimedflg == 0) {
pr->oam->x += pr->speedx;
pr->oam->y += pr->speedy;
} else {
pr->oam->x = pr->speedx == 0 ? (((pr->oam->y - gradient) * fractdiv) / slope) : pr->oam->x + pr->speedx;
pr->oam->y = pr->speedy == 0 ? (((slope * pr->oam->x) / fractdiv) + gradient) : pr->oam->y + pr->speedy;
}
} else if(pr->oam != NULL) {
destroy_projectile(pr);
}
}
void explode_machine(Machine * mch) NONBANKED {
if(mch->explcount % 4 == 0) {
if(shadow_OAM[mch->oamtilenums[0]].tile != 5) {
set_machine_sprite_tiles(mch, 5);
} else {
set_machine_sprite_tiles(mch, 9);
}
}
mch->explcount++;
if(mch->explcount == expldur) {
destroy_machine(mch);
}
}
Machine * create_explosion(UINT8 x, UINT8 y) NONBANKED { // Used for animations
Machine * explptr = crntenemy;
init_machine_props(x, y, enprops[9]);
init_explosion(explptr);
return explptr;
}
void anim_explode_boss(const UINT8 explarr[][2], UINT8 numexpl, UINT8 hasscroll, UINT8 offsx, UINT8 offsy) NONBANKED {
Machine * crntexpl;
for(citr = 0; citr < numexpl; citr++) {
crntexpl = create_explosion(offsx + explarr[citr][0], offsy + explarr[citr][1]);
while(1) {
if(hasscroll) {
build_boss_road();
incr_boss_bkg_x_coords(4, 0);
}
manage_machines(enlimit);
wait_vbl_done();
if(crntexpl->explcount == 0) {
break;
}
}
}
}
void destroy_machine(Machine * mch) NONBANKED {
set_machine_tile(mch, 0);
move_sprite(mch->oamtilenums[0], 0, 0);
move_sprite(mch->oamtilenums[1], 0, 0);
move_sprite(mch->oamtilenums[2], 0, 0);
move_sprite(mch->oamtilenums[3], 0, 0);
mch->explcount = mch->shield = mch->x = mch->y = 0;
}
void take_damage(Machine * mch, UINT8 dmgamt) NONBANKED {