forked from dabadab/wizardofwor
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathwizard_of_wor.asm
6830 lines (5732 loc) · 156 KB
/
wizard_of_wor.asm
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
;============================================================
;
; CHARACTER ENCODINGS
;
; the game uses three different character sets (the built-in, a 2x1 character set for most text and a huge one for text like "ready" and "go")
; to write the 'a' character you have to encode that differently for these charset: $69 for the first one, $0a for the second and $00 for the last
; these encoding definitions tell the assembler that which character is represented by what value in a specific encoding
;
;============================================================
; -------------
.enc "petscii" ;define an ascii->petscii encoding
.cdef " @", 32 ;characters
.cdef "AZ", $c1
.cdef "az", $41
; -------------
.enc "2x1" ; the 2x1 character set
; $00-$09: digits, $0a - $23: letters, $24: (C), $25: space, $26: dot)
.cdef "09", $00
.cdef "az", $0a
.cdef "©©", $24
.edef "(c)", $24
.cdef " ", $25
.cdef "..", $26
; --------------
.enc "charrom" ; the characters copied from the character rom
.cdef "az", $69
.cdef "09", $83
.cdef " ", $00
.cdef "__", $06
; --------------
.enc "bigwords" ; the oversized characters - it does not include all letters, just the necessary ones
.cdef "aa", $00
.cdef "bb", $01
.cdef "cc", $02
.cdef "dd", $03
.cdef "ee", $04
.cdef "gg", $05
.cdef "ll", $06
.cdef "mm", $07
.cdef "nn", $08
.cdef "oo", $09
.cdef "rr", $0a
.cdef "ss", $0b
.cdef "tt", $0c
.cdef "uu", $0d
.cdef "vv", $0e
.cdef "yy", $0f
;============================================================
;
; MACROS
;
; macros that generate data
;
;============================================================
; -------------
;
; the first part of the macros create tables to store data
;
; -------------
; a table (actually two tables) for storing 16 byte values: first all the lower bytes (marked with the local "lo" label) then the high bytes ("hi" label)
lohi_tbl .macro
_ = ( \@ )
lo .byte <_
hi .byte >_
.endm
; -------------
; this is functionally equivalent to lohi_tbl, but the coder sometimes used this form so it is preserved here too
hilo_tbl .macro
_ = ( \@ )
hi .byte >_
lo .byte <_
.endm
; -------------
; this is a variation of the lohi_tbl - the difference is that the "lo" and "hi" labels are placed a byte earlier than they should be so the first value
; can be accessed with an index of 1, not 0
lohi_tbl_1 .macro
_ = ( \@ )
lo = *-1
.byte <_
hi = *-1
.byte >_
.endm
; -------------
; a table for storing 16 byte values: but in case only the lower bytes are stored as the high byte is the same for all
lo_tbl .macro
_ = ( \@ )
lo .byte <_
.endm
; -------------
; like lo_tbl, but like above, the first value can be accessed with an index of 1
lo_tbl_1 .macro
_ = ( \@ )
lo = *-1
.byte <_
.endm
; -------------
;
; the second part of the macros are there to help with the storage of text
;
; -------------
; for text with the oversized characters the text is written backwards
big_text .macro text
.enc "bigwords"
.text \text[::-1]
.endm
; -------------
; the routine that writes text with 2x1 charset to the screen expects the text to be prefixed with the color and the position of the text
text_line .macro color, row, col, text
.byte \color
.byte \row
.byte \col
.enc "2x1"
.ptext \text
.endm
; -------------
;
; the last macro lets the sprites be defined in a visual way - see the inc.sprites* files
;
; -------------
; sprite definition
sprite .macro def
.cerror (len(\def) != 24), "sprite definition is not 24 char long"
.bfor byte := 0, byte < 3, byte += 1
val := 0;
.for bit := 0, bit < 8, bit += 1
val <<= 1
.switch \def[byte*8+bit]
.case ' '
val += 0
.case '_'
val += 0
.case '.'
val += 0
.case '#'
val += 1
.case '*'
val += 1
.default
.error "illegal char in sprite definition"
.endswitch
.next
.byte val
.next
.endm
;============================================================
;
; MACRO FUNCTIONS
;
; all these functions are about setting and manipulating 16 bit pointers
;
;============================================================
; -------------
;
; the first two ones simply set a pointer to a given value
;
; -------------
; simply sets a pointer to a given value
PTR_SET .function ptr, val
LDA #<val
STA ptr
LDA #>val
STA ptr+1
.endf
; -------------
; this is functionally equivalent to PTR_SET, but the coder sometimes used this form so it is preserved here too
PTR_SET2 .function ptr, val
LDA #>val
STA ptr+1
LDA #<val
STA ptr
.endf
; -------------
;
; the next four ones are about setting pointers with values from the "lohi_tbl" (or "hilo_tbl") tables mentioned above - where only the lower byte is stored in a table as the high byte is the same for all values
; all do the same thing: load the lower and high byte from a table indexed by the X or Y register and put it into the pointer
; the first two uses the X, the next to the Y registers and there are the usual variations on if the high or the lower byte is stored first
;
; -------------
PTR_SET_TBL_X .function ptr, tbl
LDA tbl.lo,x
STA ptr
LDA tbl.hi,x
STA ptr+1
.endf
; -------------
PTR_SET_TBL_X2 .function ptr, tbl
LDA tbl.hi,x
STA ptr+1
LDA tbl.lo,x
STA ptr
.endf
; -------------
PTR_SET_TBL_Y .function ptr, tbl
LDA tbl.lo,y
STA ptr
LDA tbl.hi,y
STA ptr+1
.endf
; -------------
PTR_SET_TBL_Y2 .function ptr, tbl
LDA tbl.hi,y
STA ptr+1
LDA tbl.lo,y
STA ptr
.endf
; -------------
;
; the next four ones are about setting pointers with values from the "lo_tbl" tables mentioned above - where only the lower byte is stored in a table as the high byte is the same for all values
;
; -------------
; this is a helper macro that is used only in the next three macros - it sets the fixed high byte and does an error checking
PTR_SET_LOTBL_HI .function ptr, tbl
; this scheme depends on the fact that all stored values have the same high byte
; check it by comparing the high byte of the first (assumed to be the lowest) and the last (assumed to be the highest) value of the table
; this check is not necessary for this disassembly but if you modify the code it saves you from rude suprises
.cerror (>tbl._[0]) != (>tbl._[-1]), "addresses not on the same page"
LDA #>tbl._[0]
STA ptr+1
.endf
; -------------
; set up the pointer from the table using the X register as index
PTR_SET_LOTBL_X .function ptr, tbl
PTR_SET_LOTBL_HI ptr, tbl
LDA tbl.lo,x
STA ptr
.endf
; -------------
; functionally same as PTR_SET_LOTBL_X
PTR_SET_LOTBL_X2 .function ptr, tbl
LDA tbl.lo,x
STA ptr
PTR_SET_LOTBL_HI ptr, tbl
.endf
; -------------
; set up the pointer from the table using the Y register as index
PTR_SET_LOTBL_Y .function ptr, tbl
PTR_SET_LOTBL_HI ptr, tbl
LDA tbl.lo,y
STA ptr
.endf
; -------------
;
; the last three ones add or substract an 8 bit value to/from a pointer
;
; -------------
; add an 8 bit value to the pointer
PTR_ADD .function ptr, val
LDA ptr
CLC
ADC val
STA ptr
BCC +
INC ptr+1
+
.endf
; -------------
; functionally the same as PTR_ADD
PTR_ADD2 .function ptr, val
LDA val
CLC
ADC ptr
STA ptr
BCC +
INC ptr+1
+
.endf
; -------------
; add an 8 bit value from the pointer
PTR_SUB .function ptr, val
LDA ptr
SEC
SBC val
STA ptr
BCS +
DEC ptr+1
+
.endf
;============================================================
;
; CONSTANTS
;
; these define labels to memory locations and fixed values
;
; the game uses zero page addresses for variables - some
; have a single functions these are named accordingly, some
; have multiple purposes, these are named tmp_hitbox_char_noXXX where
; XXXX is the actual address
;
;============================================================
; gameplay constants
MAX_PLAYERS = 2
MAX_MONSTERS = 6
MAX_ACTORS = MAX_PLAYERS + MAX_MONSTERS
STARTING_EXTRA_LIVES = 2
; locations of zero page variables
* = $0002
tmp_0002 .byte ?
* = $0004
tmp_0004_ptr .addr ?
tmp_0006_ptr .addr ?
tmp_0008 .byte ?
tmp_0009 .byte ?
tmp_000a .byte ?
tmp_000b .byte ?
tmp_000c .byte ?
tmp_000d .byte ?
tmp_000e .byte ?
tmp_000f .byte ?
tmp_0010_ptr .addr ?
dungeon_layout_ptr .addr ?
tmp_0014_ptr .addr ?
actor_heading_x .byte ?
actor_heading_y .byte ?
actor_x_9th_bit .byte ?
tmp_sprite_x .byte ?
tmp_sprite_y .byte ?
tmp_001b .byte ?
actor_heading_x_tbl .fill MAX_ACTORS ; the horizontal direction in which the actor looks: $ff: left, 0: none, $01: right
actor_heading_y_tbl .fill MAX_ACTORS ; the vertical direction in which the actor looks: $ff: up, 0: none, $01: down
animation_phase .byte ?
tmp_text_lo_ptr .addr ?
actual_actor .byte ?
actor_bit_0 .byte ?
actor_sprite_pos_offset .byte ?
actor_bit_1 .byte ?
animation_timer_tbl .fill MAX_ACTORS
new_heading_y_tbl .fill MAX_ACTORS ; TODO: better name?
new_heading_x_tbl .fill MAX_ACTORS ; TODO: better name?
monster_dest_x .byte ?
monster_dest_y .byte ?
monster_dest_9th .byte ?
horiz_anim_ptr .addr ?
vert_anim_ptr .addr ?
irq_timer_sec .byte ? ; a timer run by the IRQ - decreased in about every second (every 64th frame, to be precise)
irq_timer_frame .byte ? ; a timer run by the IRQ - decreased in every frame
launch_status .fill MAX_PLAYERS ; status of the launch from the cage: $ff: no launch in progress, $00: countdown in progress, $07: launch in progress
sprite_tmp .fill MAX_ACTORS ; the players' sprites are stored here when they shoot
snd_ingame_dur .byte ?
snd_ingame_dur_cnt .byte ?
snd_ingame_status .byte ?
snd_voice_base_ptr .addr ?
snd_sfx_to_start .byte ?
snd_sfx_ptr .addr ?
snd_sfx_shifts_no .byte ?
snd_sfx_dur_cnt .byte ?
snd_sfx_freq_lo .byte ?
snd_sfx_freq_hi .byte ?
snd_sfx_freq_inc_lo .byte ?
snd_sfx_freq_inc_hi .byte ?
snd_sfx_no_of_channels_idx .byte ?
snd_sfx_effect_switch .byte ?
snd_pattern_no .byte ?
snd_pattern_pos .byte ?
snd_voice_offset .byte ?
snd_duration .byte ?
snd_pattern_ptr .addr ?
snd_vibrato .byte ?
MV_sentence_idx .byte ?
MV_sentence .byte ?
MV_sentence_ptr .addr ?
one_second_wait_tbl .fill MAX_ACTORS
launch_counters .byte ?,?
is_monster .byte ? ; current actor is: 0: player, non-0 (2-7): monster
actor_type_before_dying_tbl .fill MAX_ACTORS ; actor's original type is saved here when it goes to dying (0) type
burwors_alive .byte ?
tmp_text_hi_ptr .addr ?
monster_bullet_move_counter .byte ?
tmp_0090 .byte ? ; used for many purposes
normal_monsters_on_screen .byte ? ; actually normal monsters on screen - 1
gameplay_ptr .addr ?
is_easter_egg_activated .byte ?
random_number .byte ?
word_len_cnt .byte ?
score_ptr .addr ?
.fill 38 ; unused
firing_status .fill MAX_ACTORS
FIRING_BULLET_ACTIVE = $80
FIRING_FIRE_BUTTON = $01
.byte ? ; unused
bullet_char .fill MAX_ACTORS
bullet_pos .lohi_tbl ?,?,?,?,?,?,?,? ; ? * MAX_ACTORS
bullet_direction .fill MAX_ACTORS
.byte ? ; unused
tmp_00e9 .byte ? ; used as various local variables
; $0200 page variables
* = $200
is_MV_missing .byte ? ; 0: Magic Voice cartridge is present 1: no MV cartridge
current_dungeon .byte ?
prev_dungeon_layout .byte ?
lives_player2 .byte ?
lives_player1 .byte ?
actor_speed_tbl .fill MAX_ACTORS ; actually it is relevant only for the monsters
monster_speed_tbl = actor_speed_tbl + MAX_PLAYERS
once_in_a_while_timer .byte ?
* = $215
time_to_invis_counter .fill MAX_ACTORS
actor_type_tbl .fill MAX_ACTORS
monster_type_tbl = actor_type_tbl + MAX_PLAYERS
DEAD_PLAYER = $f0
DEAD_MONSTER = $ff
DYING_ACTOR = $00
PLAYER1 = $02
PLAYER2 = $01
BURWOR = $03
GARWOR = $04
THORWOR = $05
WORLUK = $07
WIZARD = $08
unused_01 .byte ? ; unused
is_title_screen .byte ? ; 0: game is in progress, 1: title screen
animation_counter_tbl .fill 8 ; used for timing animation phases
monster_anim_counter_tbl = animation_counter_tbl + MAX_PLAYERS
player1_score_string .fill 6
player2_score_string .fill 6
; TODO: rest of the status codes
game_status .byte ?
STATUS_GAME_OVER = $AA
monster_head_ptr_tbl .hilo_tbl ?,?,?,?,?,?,?,? ; ? * MAX_ACTORS
tmp_irq_ptr_save .addr ?
high_scores
high_score_1 .fill 6
high_score_2 .fill 6
high_score_3 .fill 6
high_score_4 .fill 6
high_score_5 .fill 6
bullet_move_counter .byte ?
* = $26D
tmp_score .byte ?
* = $26F
music_speed .byte ?
curr_sfx_idx .byte ?
next_sfx_idx .byte ?
SFX_MONSTER_SHOOTS = $00
SFX_PLAYER_SHOOTS = $01
SFX_MONSTER_HIT = $02
SFX_WORLUK_MOVING = $03
SFX_WARP_DOOR_CLOSED = $04
SFX_INVISIBILITY_ENDS = $05
SFX_PLAYER_HIT = $06
SFX_PLAYER_LAUNCH = $07
SFX_WORLUK_KILLED = $08
SFX_WORLUK_ESCAPED = $09
SFX_WIZARD_ESCAPED = $0a
SFX_WIZARD_KILLED = $0b
SFX_NO_SFX = $80
; screen ram locations
SCR_04C8 = $04C8
SCR_04CA = $04CA
SCR_04EC = $04EC
SCR_04ED = $04ED
SCR_04F0 = $04F0
SCR_04F2 = $04F2
SCR_0500 = $0500
SCR_0514 = $0514
SCR_0515 = $0515
SCR_0518 = $0518
SCR_051A = $051A
SCR_051B = $051B
SCR_051C = $051C
SCR_053A = $053A
SCR_053B = $053B
SCR_053C = $053C
SCR_053E = $053E
SCR_0540 = $0540
SCR_0542 = $0542
SCR_0564 = $0564
SCR_0565 = $0565
SCR_0568 = $0568
SCR_056A = $056A
SCR_058C = $058C
SCR_058D = $058D
SCR_0600 = $0600
SCR_06AB = $06AB
SCR_06AC = $06AC
SCR_06AD = $06AD
SCR_06C9 = $06C9
SCR_06CA = $06CA
SCR_06CB = $06CB
SCR_06D3 = $06D3
SCR_06D6 = $06D6
SCR_06DD = $06DD
SCR_06E7 = $06E7
SCR_06E8 = $06E8
SCR_06F0 = $06F0
SCR_06F1 = $06F1
SCR_0706 = $0706
SCR_0728 = $0728
SCR_072E = $072E
SCR_073C = $073C
SCR_0756 = $0756
SCR_077E = $077E
SCR_07A6 = $07A6
SCR_07CE = $07CE
SPRITE_PTR = $07F8
nmi_handler = $1F47
; calls of the Magic Voice interface
MV_RESET = $C003
MV_GET_STATUS = $C006
MV_SAY_IT = $C009
MV_SET_SPEED = $C00F
MV_SET_SPEECH_TABLE = $C012
MV_ENABLE_COMPLETION_CODE = $C015
MV_COMPLETION_HOOK = $C018
; chip registers
VIC_S0X = $D000
VIC_S0Y = $D001
VIC_S1X = $D002
VIC_S1Y = $D003
VIC_D004 = $D004
VIC_D005 = $D005
VIC_D010 = $D010
VIC_D011 = $D011
VIC_D012 = $D012
VIC_D015 = $D015
VIC_D016 = $D016
VIC_D019 = $D019
VIC_D01C = $D01C
VIC_D020 = $D020
VIC_D021 = $D021
VIC_D022 = $D022
VIC_D023 = $D023
VIC_D027 = $D027
VIC_D028 = $D028
VIC_D02B = $D02B
SID_FrqLo1 = $D400
SID_FrqHi1 = $D401
SID_PulLo1 = $D402
SID_PulHi1 = $D403
SID_WV1 = $D404
SID_AD1 = $D405
SID_SR1 = $D406
SID_PulLo2 = $D409
SID_PulHi2 = $D40A
SID_WV2 = $D40B
SID_AD2 = $D40C
SID_SR2 = $D40D
SID_PulLo3 = $D410
SID_PulHi3 = $D411
SID_WV3 = $D412
SID_AD3 = $D413
SID_SR3 = $D414
SID_FltLo = $D415
SID_FltHi = $D416
SID_Rsn_IO = $D417
SID_FltMod_Vol = $D418
SID_Osc = $D41B
SID_Env = $D41C
; color ram locations
COL_D800 = $D800
COL_D807 = $D807
COL_D900 = $D900
COL_D918 = $D918
COL_D93E = $D93E
COL_DA00 = $DA00
COL_DAD3 = $DAD3
COL_DADD = $DADD
COL_DAE7 = $DAE7
COL_DAE8 = $DAE8
COL_DAF1 = $DAF1
COL_DB00 = $DB00
COL_DB06 = $DB06
COL_DB28 = $DB28
COL_DB2E = $DB2E
COL_DB3C = $DB3C
COL_DB56 = $DB56
COL_DB7E = $DB7E
COL_DBA6 = $DBA6
COL_DBCE = $DBCE
CIA1_JOY_KEY1 = $DC00
CIA1_JOY_KEY2 = $DC01
CIA1_IRQ = $DC0D
CIA2_PORTA = $DD00
KERNAL_IOINIT = $FF84
; dungeon chars
FLOOR = $0f
MONSTER_CENTER = $63
MONSTER_HEAD = $38
MONSTER_BULLET_H = $42
MONSTER_BULLET_V = $43
PLAYER_BULLET_H = $40
PLAYER_BULLET_V = $41
; sprites
BOOM1 = $94
BOOM2 = $95
NO_SPRITE = $D6
;============================================================
;
; ACTUAL CODE
;
;============================================================
; code start at $8000 because that's where the cartridge is mapped into the memory
* = $8000
; when the C64 powers on, the KERNAL routines look at $8000 to see if there is a header for a cartridge that takes over the boot process
; this is that header
; boot address
.word start
; nmi vector
.word nmi_handler
; magic signature "CBM80" - this is what the KERNAL looks for
.enc "petscii"
.text "CBM80"
; end of header
start
; various initialization routines
; set up memory layout
LDA #$57 ; RAM / CART / RAM / IO / KERNAL
STA $01
LDA #$2F
STA $00
; set up IRQ and NMI handlers
SEI
JSR copy_nmi_hander_to_ram
PTR_SET $0318, nmi_handler
PTR_SET $0314, irq_handler
JSR init_sid_cia_mem
JSR reset_scores
JSR reset_per_dungeon_stuff
init_stuff ; nmi handler jumps here
JSR create_sprites
JSR init_cset
; disable CIA IRQs
LDA #$7F
STA CIA1_IRQ
LDA CIA1_IRQ ; clear any pending CIA IRQ
; TODO: effectively clears bit 3 (userport data PA 2), but why?
LDA #$93
STA CIA2_PORTA
CLI
; The title screens
display_high_scores_and_enemies
JSR vic_init
; high score + copyright
JSR clear_screen
JSR display_copyright_and_high_scores
LDX tmp_0090
BNE start_game
; the tables with the names, pictures and point values of the enemies
JSR clear_screen
JSR print_enemies_table
LDX tmp_0090
BNE start_game
JSR clear_screen ; this seems to be superfluous
JMP display_high_scores_and_enemies
start_game
JSR reset_scores
JSR clear_screen
start_dungeon
;
; all the stuff before the actual gameplay begins
;
; print the previous dungeon (except for the first one, of course) to use as background
LDA current_dungeon
BMI + ; skip if we have just started the game (dungeon = $ff)
JSR draw_dungeon
+
JSR select_dungeon_layout
JSR reset_per_dungeon_stuff
; GET READY ... GO
JSR vic_init
JSR get_ready_go_screen
; DOUBLE SCORE DUNGEON / bonus player
JSR clear_screen
JSR check_double_score_and_bonus_life
; prepare the screen
JSR clear_screen
JSR draw_playfield
JSR draw_dungeon
JSR draw_score_boxes
JSR score.print
JSR score.print.only_player2
JSR draw_player1_lives
JSR draw_player2_lives
LDA #$01
STA irq_timer_sec
LDA #$80
STA snd_ingame_status ; start in-game music
; find out the title for this dungeon
LDX #$05 ; title: radar
LDA current_dungeon
BEQ _print_fixed_title ; this is the first dungeon
LDX #$02 ; title: the Arena
LDA prev_dungeon_layout
CMP #$18
BEQ _print_fixed_title
DEX ; title: the Pit
CMP #$17
BEQ _print_fixed_title
DEX ; title: dungeon
JSR print_radar_title
; print dungeon number with one or two digits
LDX current_dungeon
INX
TXA
; count the tens
LDX #$00
SEC
- INX
SBC #$0A
BCS -
DEX
; print the ones, with color
ADC #$8D
STA SCR_06E8
LDA #$02
STA COL_DAE8
; print the tens, with color
TXA
BEQ _end_of_title_print ; no tens
CLC
ADC #$83
STA SCR_06E7
LDA #$02
STA COL_DAE7
BNE _end_of_title_print ; always branch
_print_fixed_title
JSR print_radar_title
_end_of_title_print
; say something appropriate
LDA VIC_D012
AND #$07
TAX
LDY MV_dungeon_start_tbl,X
JSR magic_voice.say
normal_gameplay_loop
;
; the actual gameplay loop
;
; check keyboard (pause / easter egg)
JSR check_keyboard
; next actor
INC actual_actor
LDA actual_actor
; update radar only when all but one actors are processed
CMP #MAX_ACTORS-1
BNE _no_radar_update
JSR update_radar
_no_radar_update
; move bullets
JSR move_bullets
; check if game is over
LDA game_status
CMP #STATUS_GAME_OVER
BNE _no_game_over
JSR game_over_screen
JMP high_score.set
_no_game_over
; keep the actual actor in the 0-7 range
LDA actual_actor
AND #$07
STA actual_actor
; update offset of X and Y coordinates of the actor's sprite
; this is twice actor's number, as the 0. sprite's X is at D000, the 1. is at D002, the 2. at D004, etc
TAX
ASL A
STA actor_sprite_pos_offset
; update current monster status
TXA
AND #$FE
STA is_monster
; check if actor is dead
LDA actor_type_tbl,X
BPL _actor_not_dead ; it's not dead
; actor is dead, no further processing is needed for it
; this short busy loop is probably here to keep the game from being to fast with just a few (or a single) monsters
LDY #$20
- DEY
BNE -
BEQ normal_gameplay_loop ; always branch
_actor_not_dead
BNE _actor_is_alive ; it's alive
JMP actor_is_dying ; it's dying
_actor_is_alive
; check if actor is hit by bullet
JSR check_hitbox_of_current_actor
; decrease animation timer for monsters - for players it's done in the IRQ handler
LDX actual_actor
LDA is_monster
BEQ _is_player
DEC animation_timer_tbl,X
BEQ _do_monster_action
BNE normal_gameplay_loop ; always branch
_is_player
; check if player's animation counter has underflown - if so, reset the counter and move the player
LDA animation_timer_tbl,X
BPL normal_gameplay_loop ; no underflow, skip movement
LDA #$00
STA animation_timer_tbl,X
JMP _handle_movement
_do_monster_action
INC animation_counter_tbl,X
; keep monster's speed from underflowing
; also, if that happens be sure to disable invisibility
LDA actor_speed_tbl,X
BPL +
LDA VIC_D015
ORA power_of_2_tbl,X
STA VIC_D015
LDA #$00
STA actor_speed_tbl,X
+
TAY
INY
STY animation_timer_tbl,X
; decide if monster should shoot
; 50% chance of trying to shoot
LDA random_number
BPL _dont_shoot
; skip if it already has a bullet out there
LDA bullet_direction,X
BNE _dont_shoot
; skip if it already has a bullet out there by firin status TODO: isn't it redundant?
LDA firing_status,X
BNE _dont_shoot
; shoot only if on the same row or column as one of the player - even numbered monsters shoot only P2, odd numbered ones p1
LDA actor_sprite_pos_offset
TAX
AND #$02
TAY
LDA VIC_S0X,X
CMP VIC_S0X,Y
BEQ _shoot
LDA VIC_S0Y,X
CMP VIC_S0Y,Y
BNE _dont_shoot
_shoot
; it's been decided, shoot!
LDX actual_actor
JSR fire_bullet
LDX actual_actor