-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathcastlemaster2-annotated.asm
15951 lines (15213 loc) · 517 KB
/
castlemaster2-annotated.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
; --------------------------------
; "Castle Master 2: The Crypt" by Incentive Software Ltd., 1990
; Disassembled by Santiago Ontañón in 2023
;
; Disclaimer: All the comments, label and constant names in this disassembly are my best interpretation of what the
; code actually does. Fully annotating a disassembly like this one requires a large amount of work (this one took
; me over a month, dedicating 2-3 hours every day). Therefore, it might contain errors or misunderstandings.
; Please report if you find something that is incorrect. When I am very unsure of what some code does, I added
; a note, but I might have missed many.
;
; Notes and curiosities from the codebase:
; - There are two identical functions:
; - La9de_hl_eq_h_times_64
; - Lcb6d_hl_eq_h_times_64
; - There are two implemented versions of the same multiplication operation "(de,hl) = de * hl":
; - La15e_de_times_hl_signed
; - L8ab4_de_times_hl_signed
; - Interestingly: the first is redundant, since the second is smaller and faster. However, it is the
; first that is the most commonly used in the code!!!
; - There is self-modifying code
; - The 3d rendering engine is quite advanced for the year it was written:
; - It contains all basic elements of later 3d engines
; - It only considers 2 rotation angles (pitch and yaw), but it would be trivial to add a third, if it
; wasn't because of the skybox (which would have to rotate if we added "roll").
; - It contains skybox rendering code for outdoor areas (and even a "lightning" animation over the skybox!)
; - It supports textured shapes
; - Objects can be lines, triangles, quads or pentagons (in principle arbitrary number of vertices, but the
; codebase only contains definitions for up to pentagons).
; - It implements many levels of culling (quick rendering cube, rendering frustum/pyramid)
; - It implements polygon clipping for those that are only partly within the screen
; - Different stages of rendering are "cached" in memory, so that we do not need to repeat them. For example,
; when entering a menu, and going back to the game, all the 3d -> 2d projection does not need to be redone,
; as positions have not changed. So, this is skipped. Similarly, when player does not move, rotation matrices
; are not recalculated.
; - All in all, even if the individual functions are not very optimized (things can be done significantly faster),
; the overall structure is very nice (and some of the low-level functions are indeed quite optimized, such as
; the one that renders textured horizontal lines).
; - All the computations are done with fixed point arithmetic. Even line and polygon drawing uses this fixed-point
; calculations, rather than the more optimized Bresenham routines. This makes the code simpler, even if
; slower than it could be.
; - Sorting of objects for rendering is quite curious, as it happens in coordinates *before* they are projected to
; camera coordinates (just distance from player in each separate axis). I am sure this causes many issues in corner
; cases.
; - I think the code has a at least one bug, I marked it with a "BUG?" tag. Of course, I am not 100% sure.
;
; Potential optimization of the code:
; - The code seems more functional than optimized. The lowest level drawing routines seem to be optimized well, but most
; of the math routines are not. So, there is a lot of opportunity to make the engine faster.
; - I have added "OPTIMIZATION" tags in places where small things could be optimized. I only added those that
; an automatic optimizer (in this case MDL: https://github.com/santiontanon/mdlz80optimizer) would not already
; detect automatically. Basically, these are notes for an potential optimized version. Only small things are noted
; large architectural changes (like moving from fixed-point arithmetic line-drawing to Bresenham-style, are not
; annotated in the code).
;
; Related work:
; - See Phantasma, a reimplementation of the Freescape engine: https://github.com/TomHarte/Phantasma
; - See the information on the Freescape reimplementation in SCUMMVM: https://wiki.scummvm.org/index.php?title=Freescape
;
; --------------------------------
; BIOS Functions and constants:
; - Information obtained from "The Spectrum Machine Code Reference Guide" book.
; Saves a collection of bytes to tape
; Input:
; - ix: address to save
; - de: byte count
L04c6_BIOS_CASSETTE_SAVE_NO_BREAK_TEST: equ #04c6
; Loads a collection of bytes from tape
; Input:
; - ix: address where to load
; - de: byte count
L0562_BIOS_READ_FROM_TAPE_SKIP_TESTS: equ #0562
ULA_PORT: equ #fe ; Writing to this port ignores the high 8bits.
; The 8 bit value written is used as follows:
; - bits 0, 1, 2: border color
; - bit 3: MIC (tape output)
; - bit 4: speaker output
; --------------------------------
; Video memory constants:
; - Information obtained from: http://www.breakintoprogram.co.uk/hardware/computers/zx-spectrum/screen-memory-layout
L4000_VIDEOMEM_PATTERNS: equ #4000
L5800_VIDEOMEM_ATTRIBUTES: equ #5800
SCREEN_WIDTH: equ 24
SCREEN_HEIGHT: equ 14
SCREEN_WIDTH_IN_PIXELS: equ SCREEN_WIDTH * 8 ; 192
SCREEN_HEIGHT_IN_PIXELS: equ SCREEN_HEIGHT * 8 ; 112
; --------------------------------
; Game constants:
CONTROL_MODE_KEYBOARD: equ 0
CONTROL_MODE_SINCLAIR_JOYSTICK: equ 1
CONTROL_MODE_KEMPSTON_JOYSTICK: equ 2
CONTROL_MODE_CURSOR_JOYSTICK: equ 3
MAX_COORDINATE: equ 127*64
MAX_PRESSED_KEYS: equ 5
FILENAME_BUFFER_SIZE: equ 12
SPIRIT_METER_MAX: equ 64
MAX_STRENGTH: equ 24
GAME_OVER_REASON_OVERPOWERED: equ 1
GAME_OVER_REASON_YOU_COLLAPSE: equ 2
GAME_OVER_REASON_CRUSHED: equ 3
GAME_OVER_REASON_FATAL_FALL: equ 4
GAME_OVER_REASON_ESCAPED: equ 5
; Sound FX:
SFX_MENU_SELECT: equ 3 ; Also used for when player collides with an object
SFX_THROW_ROCK_OR_LAND: equ 5
SFX_FALLING: equ 6
SFX_GAME_START: equ 7
SFX_LIGHTNING: equ 8
SFX_GATE_CLOSE: equ 9
SFX_PICK_UP_ITEM: equ 10
SFX_OPEN_CHEST: equ 11
SFX_CLIMB_DROP: equ 12
SFX_OPEN_ESCAPED: equ 13
; There are other SFX defined, but only used in the game scripts:
; 1 ; sounds like if you die / get hit / error
; 2 ; sounds like game over
; 4 ; short high -> higher pitch beep
; 14 ; low-pitch repeated sound, not sure what
; 15 ; tiny short SFX
INPUT_FORWARD: equ 3
INPUT_BACKWARD: equ 4
INPUT_TURN_LEFT: equ 5
INPUT_TURN_RIGHT: equ 6
INPUT_LOOK_UP: equ 7
INPUT_LOOK_DOWN: equ 8
INPUT_CRAWL: equ 9
INPUT_WALK: equ 10
INPUT_RUN: equ 11
INPUT_FACE_FORWARD: equ 12
INPUT_U_TURN: equ 13
INPUT_MOVEMENT_POINTER_ON_OFF: equ 21
INPUT_THROW_ROCK: equ 22
INPUT_MOVE_POINTER_RIGHT: equ 23
INPUT_MOVE_POINTER_LEFT: equ 24
INPUT_MOVE_POINTER_DOWN: equ 25
INPUT_MOVE_POINTER_UP: equ 26
INPUT_ACTION: equ 27
INPUT_SWITCH_BETWEEN_MOVEMENT_AND_POINTER: equ 30
INPUT_INFO_MENU: equ 41
; How many degrees is a full circle:
FULL_ROTATION_DEGREES: equ 72
; Datablock structures:
AREA_HEADER_SIZE: equ 8
; Area struct:
AREA_FLAGS: equ 0
AREA_N_OBJECTS: equ 1
AREA_ID: equ 2
AREA_RULES_OFFSET: equ 3 ; 2 bytes
AREA_SCALE: equ 5
AREA_ATTRIBUTE: equ 6
AREA_NAME: equ 7
; Object struct:
OBJECT_TYPE_AND_FLAGS: equ 0
OBJECT_X: equ 1
OBJECT_Y: equ 2
OBJECT_Z: equ 3
OBJECT_SIZE_X: equ 4
OBJECT_SIZE_Y: equ 5
OBJECT_SIZE_Z: equ 6
OBJECT_ID: equ 7
OBJECT_SIZE: equ 8
OBJECT_ADDITIONAL_DATA: equ 9
; Object types:
OBJECT_TYPE_ENTRANCE: equ 0
OBJECT_TYPE_CUBE: equ 1
OBJECT_TYPE_SPIRIT: equ 2
OBJECT_TYPE_RECTANGLE: equ 3
; - Object types in between 4 and 9 are different solids, like pyramids,
; hourglasses, wedges, etc. that are synthesized on the fly.
; - I believe the object ID here just indicates their orientation (one of
; the 6 possible cardinal directions in 3d), and their additional data
; is used to determine their exact shape (via some checks in at the bedinning of
; function "L97bb_project_other_solids").
OBJECT_TYPE_LINE: equ 10
OBJECT_TYPE_TRIANGLE: equ 11
OBJECT_TYPE_QUAD: equ 12
OBJECT_TYPE_PENTAGON: equ 13
OBJECT_TYPE_HEXAGON: equ 14
; Rule types:
RULE_TYPE_ADD_TO_SCORE: equ 1
RULE_TYPE_TOGGLE_OBJECT_VISIBILITY: equ 3
RULE_TYPE_MAKE_OBJECT_VISIBILE: equ 4
RULE_TYPE_MAKE_OBJECT_INVISIBILE: equ 5
RULE_TYPE_TOGGLE_OBJECT_FROM_AREA_VISIBILITY: equ 6
RULE_TYPE_MAKE_OBJECT_FROM_AREA_VISIBILE: equ 7
RULE_TYPE_MAKE_OBJECT_FROM_AREA_INVISIBILE: equ 8
RULE_TYPE_INCREMENT_VARIABLE: equ 9
RULE_TYPE_DECREMENT_VARIABLE: equ 10
RULE_TYPE_END_RULE_IF_VARIABLE_DIFFERENT: equ 11
RULE_TYPE_SET_BOOLEAN_TRUE: equ 12
RULE_TYPE_SET_BOOLEAN_FALSE: equ 13
RULE_TYPE_END_RULE_IF_BOOLEAN_DIFFERENT: equ 14
RULE_TYPE_PLAY_SFX: equ 15
RULE_TYPE_DESTROY_OBJECT: equ 16
RULE_TYPE_DESTROY_OBJECT_FROM_AREA: equ 17
RULE_TYPE_TELEPORT: equ 18
RULE_TYPE_STRENGTH_UPDATE: equ 19
RULE_TYPE_SET_VARIABLE: equ 20
RULE_TYPE_REDRAW: equ 26
RULE_TYPE_PAUSE: equ 27
RULE_TYPE_REQUEST_SFX_NEXT_FRAME: equ 28
RULE_TYPE_TOGGLE_BOOLEAN: equ 29
RULE_TYPE_END_RULE_IF_OBJECT_INVISIBLE: equ 30
RULE_TYPE_END_RULE_IF_OBJECT_VISIBLE: equ 31
RULE_TYPE_END_RULE_IF_OBJECT_FROM_AREA_INVISIBLE: equ 32
RULE_TYPE_END_RULE_IF_OBJECT_FROM_AREA_VISIBLE: equ 33
RULE_TYPE_SHOW_MESSAGE: equ 34
RULE_TYPE_RENDER_EFFECT: equ 35
RULE_TYPE_FLIP_SKIP_RULE: equ 44
RULE_TYPE_UNSET_SKIP_RULE: equ 45
RULE_TYPE_END_RULE_IF_VARIABLE_LOWER: equ 46
RULE_TYPE_END_RULE_IF_VARIABLE_LARGER: equ 47
RULE_TYPE_SELECT_OBJECT: equ 48
; --------------------------------
; RAM Variables before the game data:
L5cbc_render_buffer: equ #5cbc ; 2712 bytes ((SCREEN_HEIGHT * 8 + 1) * SCREEN_WIDTH)
; Variables that overlap with the render buffer, these are used when projecting
; the 3d vertices into 2d, so, they are discarded and not needed when using the
; render buffer.
L5e4c_pitch_rotation_matrix: equ #5e4c
L5e55_rotation_matrix: equ #5e55
L5e5e_at_least_one_vertex_outside_rendering_frustum: equ #5e5e
L5e5f_add_to_projected_objects_flag: equ #5e5f ; If this is 1, the current object being projected from 3d to 2d, will be added to the list of objects to draw.
L5e60_projection_pre_work_type: equ #5e60 ; Indicates whether we need to do additional computations before projecting each face.
L5e61_object_currently_being_processed_type: equ #5e61
L5e62_player_collision_with_object_flags: equ #5e62
L5e63_3d_vertex_coordinates_relative_to_player: equ #5e63
L5e75_48_bit_accumulator: equ #5e75
L5e7b_48bitmul_tmp1: equ #5e7b
L5e7d_48bitmul_tmp2: equ #5e7d
L5e9f_3d_vertex_coordinates_after_rotation_matrix: equ #5e9f ; 16 bit representation.
L5edc_vertex_rendering_frustum_checks: equ #5edc ; 5 bits per vertex, indicating if they passed or not each of the 5 culling tests for the rendering frustum.
L5ee8_already_projected_vertex_coordinates: equ #5ee8
L5f24_shape_edges_ptr: equ #5f24 ; Pointer to the array with the order of edges to use for projection.
L5f26_alternative_shape_edges_ptr: equ #5f26 ; Alternative edges pointer (for when object is seen from below, this is only needed for flat shapes).
L5f28_cull_face_when_no_projected_vertices: equ #5f28
L5f29_extra_solid_dimensions: equ #5f29 ; stores 4 additional dimensions used temporarily to synthesize solids like pyramids, hourglasses, etc. on the fly. (4 16bit numbers).
L5f31_sorting_comparison_result: equ #5f31 ; result of comparing the coordinates of two objects to see if they should be flipped for rendering.
L5f32_sorting_any_change: equ #5f32
L5f33_sorting_boundingbox_ptr1: equ #5f33
L5f35_sorting_boundingbox_ptr2: equ #5f35
L5f37_sorting_bbox1_c1: equ #5f37 ; These four variables hold the values of the min/max coordinates for the current axis of the two bounding boxes being compared for sorting.
L5f39_sorting_bbox2_c1: equ #5f39
L5f3b_sorting_bbox1_c2: equ #5f3b
L5f3d_sorting_bbox2_c2: equ #5f3d
L5f3f_n_objects_covering_the_whole_screen_left: equ #5f3f
L5f40_16_bit_tmp_matrix: equ #5f40 ; Used internally to save the results of matrix multiplication.
L5f52_16_bit_tmp_matrix_ptr: equ #5f52 ; Used to keep track of the elements in the matrix above.
L5fa2_3d_object_bounding_boxes_relative_to_player: equ #5fa2 ; in 16 bit precision: x1, x2, y1, y2, z1, z2
L6664_row_pointers: equ #6664 ; Pointers to each row of pixels in the buffer.
L6754_end_of_render_buffer: equ #6754
; This contains the current room objects, already projected to 2d coordinates:
; - Each entry has 2 pointers:
; - One pointer to the "L67f4_projected_vertex_data" (with the projected vertices)
; - One pointer to the "L5fa2_3d_object_bounding_boxes_relative_to_player"
L6754_current_room_object_projected_data: equ #6754
; For each projected object, the data is organized as follows:
; - 1 byte: object ID
; - 1 byte: number of primitives/faces:
; - If the most significant bit is set, it means this object covers the whole screen.
; - face data:
; - 1 byte (texture / # vertices),
; - and then 2 bytes per vertex screen x, screen y (screen y is reversed, 0 = bottom).
L67f4_projected_vertex_data: equ #67f4
org #6a00
; --------------------------------
; Program start
L6a00_start:
jp L6a2f_game_init
; --------------------------------
; Set up the interrupt routine to "Lbe66_interrupt_routine".
L6a03_setup_interrupts:
di
push hl
push de
push bc
push af
xor a
ld (L747c_within_interrupt_flag), a
ld hl, Lfe00_interrupt_vector_table
ld a, h
ld i, a
ld d, h
ld e, l
inc e
ld (hl), #fd
ld bc, 256
ldir
ld a, #c3 ; jp opcode
ld hl, Lbe66_interrupt_routine
ld (Lfdfd_interrupt_jp), a
ld (Lfdfe_interrupt_pointer), hl
im 2
pop af
pop bc
pop de
pop hl
ei
ret
; --------------------------------
; Initializes the game the very first time.
L6a2f_game_init:
ld sp, #fff8 ; initialize the stack
di
xor a ; Set control mode to keyboard
ld (L7683_control_mode), a
ld (L747d), iy ; Note: This instruction is very strange, as at this point "iy" is undefined.
call La4c9_init_game_state
call L6a03_setup_interrupts
jp L6a7e_main_application_loop
; --------------------------------
; Unused?
db #00, #00, #00, #00, #00, #00, #00, #00, #00, #00, #00, #00, #00, #00, #00, #00
db #00, #00, #00, #00, #00, #00, #00, #00, #00, #00, #00, #00, #00, #00, #00, #00
db #00, #00, #00, #00, #00, #00, #00, #00, #00, #00, #00, #00, #00, #00, #00, #00
db #00, #00, #00, #00, #00, #00, #00, #00, #00, #00
; --------------------------------
; Main application loop: calls title screen, starts game, restarts title screen, etc.
L6a7e_main_application_loop:
ld hl, #fffd
ld (L746c_game_flags), hl
ld a, 2
ld (L7477_render_buffer_effect), a ; Request gate opening effect
call Lc72e_title_screen_loop
call L83aa_redraw_whole_screen
xor a
ld (L7479_current_game_state), a
jp L6a99
L6a96_game_loop:
call L83aa_redraw_whole_screen
L6a99:
call L9dec_game_tick
call La005_check_rules
ld hl, (L746c_game_flags)
bit 1, l ; check the "game over" flag
jp z, L6a96_game_loop
call La4c9_init_game_state
jp L6a7e_main_application_loop
; --------------------------------
; Game state variables:
; Saving a game saves data starting from here:
L6aad_savegame_data_start:
L6aad_player_current_x:
dw #00a0
L6aaf_player_current_y:
dw #09e0
L6ab1_player_current_z:
dw #1b60
L6ab3_current_speed_in_this_room:
dw #11d0
L6ab5_current_speed: ; This is a value form the Ld0c8_speed_when_crawling array, depending on L6b0b_selected_movement_mode.
db #f0
L6ab6_player_pitch_angle: ; from 18 to -18 (54)
db #00
L6ab7_player_yaw_angle: ; from 0 - 71
db #1a
L6ab8_player_crawling: ; 2 when standing up, 1 when crawling.
db 2
L6ab9_player_height: ; player height * room scale
db #26
L6aba_max_falling_height_without_damage: ; 2 * room scale
db #26
L6abb_max_climbable_height:
db #13
L6abc_current_room_scale:
db #13
L6abd_cull_by_rendering_volume_flag:
db #00
L6abe_use_eye_player_coordinate: ; When this is 0, we will use "feet" coordinates for collision checks, when 1, we will use "eye" coordinates.
db #00
L6abf_current_area_name_string:
db 0, " THE CRYPT "
L6acf_current_area_id:
db #02
L6ad0_current_area_n_objects:
db #18
L6ad1_current_area_objects:
dw #d6ca
db #00, #00 ; unused?
L6ad5_current_area_rules:
dw #d8ce
L6ad7_current_border_color:
db #14, #00
L6ad9_current_attribute_color:
db #16, #0b
L6adb_desired_border_color:
db #14, #00
L6add_desired_attribute_color:
db #47, #0b
L6adf_game_boolean_variables:
; One bit corresponding to each boolean variable.
; The first few correspond to collected keys.
db #00, #00, #00, #00
L6ae3_visited_areas: ; one bit per area (keeps track of which areas the player has already visited).
db #00, #00, #00, #00, #00, #00, #00, #00
L6aeb_score: ; 3 bytes
db #00, #00, #00
L6aee_game_variables: ; These can be accessed by the game scripts.
db #00, #00, #00, #00, #00, #00, #00, #00
db #00, #00, #00, #00, #00, #00, #00, #00
db #00, #00, #00, #00, #00, #00, #00, #00
db #00, #00, #00
L6b09_number_of_spirits_destroyed:
db 0
L6b0a_current_strength:
db 16
L6b0b_selected_movement_mode: ; 0: crawl, 1: walk, 2: run
db 2
L6b0c_num_collected_keys:
db 0
L6b0d_new_key_taken:
db 0 ; Contains the ID of a key just picked up, before being added to the inventory.
L6b0e_lightning_time_seconds_countdown:
db #14
L6b0f_collected_keys:
; Different from "L6adf_game_boolean_variables", this array has the keys in
; the order the player picked them, directly as a list of IDs.
db #00, #00, #00, #00, #00, #00, #00, #00, #00, #00
L6b19_current_area_flags:
db #00
L6b1a_pointer_x:
db 0
L6b1b_pointer_y:
db 0
L6b1c_movement_or_pointer:
db 0 ; 0: movement, otherwise: pointer
L6b1d_time_interrupts:
db #01
L6b1e_time_unit5: ; Changes once per second, counting from 10 to 1
db #00
L6b1f_current_spirit_meter: ; Increments in 1 each time Lbe65_time_unit3 wraps around (each 120 seconds).
db #20
L6b20_display_movement_pointer_flag: ; Whether to draw a small cross in the center of the screen when in movement mode.
db #ff
L6b21_time_unit6_previous: ; to keep track of when L6b22_time_unit6 changes.
db #00
L6b22_time_unit6: ; Increments by one each time L6b1e_time_unit5 cycles.
db #00
L6b23_set_bit7_byte_3_flag_at_start: ; If this is != 0, when starting a game, bit 7 of the 3rd byte of the boolean variables is set to 1 (not sure of the effect of this).
db #00
L6b24_savegame_data_end:
db #00, #00, #07, #00 ; Unused?
L6b28_player_radius:
dw #000a
L6b2a_spirit_in_room: ; 0: no spirit, 1: spirit
db 0
L6b2b_desired_eye_compass_frame:
db #00
; If an object definition has more bytes than this, it means there are rule effects associated with it:
L6b2c_expected_object_size_by_type:
db #09, #0c, #0e, #0a, #10, #10, #10, #10, #10, #10, #10, #13, #16, #19, #1c, #00
L6b3c_rule_size_by_type: ; Assuming there are only 49 rule types (maximum type is 48).
db #01, #04, #02, #02, #02, #02, #03, #03, #03, #02, #02, #03, #02, #02, #03, #02
db #02, #03, #03, #02, #03, #00, #00, #00, #00, #02, #01, #02, #02, #02, #02, #02
db #03, #03, #02, #02, #00, #00, #00, #00, #00, #02, #01, #00, #01, #01, #03, #03
db #02
; Edges for cubes:
L6b6d_cube_edges:
db #0c
db #00, #01, #01, #02, #02, #03, #03, #00
db #04, #05, #05, #06, #06, #07, #07, #04
db #00, #04, #01, #05, #02, #06, #03, #07
; byte 0: number of faces
; Each face then:
; - byte: texture
; - byte: number of vertices/edges
; - bytes 2+: edge indexes from where to get the vertices
; - the msb in the index indicates if we need to flip the vertexes in the edge in question.
L6b86_face_definition_for_cubes:
db #06
db #00, #04, #83, #0b, #07, #88
db #00, #04, #05, #8a, #81, #09
db #00, #04, #08, #04, #89, #80
db #00, #04, #0a, #06, #8b, #82
db #00, #04, #00, #01, #02, #03
db #00, #04, #84, #87, #86, #85
; Edges for pyramids:
L6bab_pyramid_edges:
db #08
db #00, #01, #01, #02, #02, #03, #03, #00
db #00, #04, #01, #04, #02, #04, #03, #04
L6bbc_face_definition_for_pyramids:
db #05
db #00, #03, #83, #07, #84
db #00, #03, #82, #06, #87
db #00, #03, #81, #05, #86
db #00, #03, #80, #04, #85
db #00, #04, #00, #01, #02, #03
L6bd7_wedge_edges:
db #09
db #00, #01, #01, #02, #02, #03
db #03, #00, #00, #04, #01, #04
db #02, #05, #03, #05, #04, #05
L6bea_face_definition_for_wedges:
db #05
db #00, #04, #83, #07, #88, #84
db #00, #03, #82, #06, #87
db #00, #04, #81, #05, #08, #86
db #00, #03, #80, #04, #85
db #00, #04, #00, #01, #02, #03
L6c07_triangle_houglass_edges:
db #09
db #00, #01, #01, #02, #02, #03
db #03, #00, #00, #04, #01, #05
db #02, #05, #03, #04, #04, #05
L6c1a_face_definition_for_triangle_hourglasses:
db #05
db #00, #03, #83, #07, #84
db #00, #04, #82, #06, #88, #87
db #00, #03, #81, #05, #86
db #00, #04, #80, #04, #08, #85
db #00, #04, #00, #01, #02, #03
L6c37_hourglass_edges:
db #0c
db #00, #01, #01, #02, #02, #03, #03, #00
db #04, #07, #07, #05, #05, #06, #06, #04
db #00, #04, #01, #06, #02, #05, #03, #07
L6c50_face_definition_for_hourglasses:
db #06
db #00, #04, #83, #0b, #84, #88
db #00, #04, #82, #0a, #85, #8b
db #00, #04, #81, #09, #86, #8a
db #00, #04, #80, #08, #87, #89
db #00, #04, #00, #01, #02, #03
db #00, #04, #04, #05, #06, #07
; Edge definition for different shapes (lines, triangles, rectangles and pentagons),
; - the first byte is the # of edges
; - after that, each pair of bytes defines an edge.
L6c75_line_edges:
db #02, #00, #01, #01, #00
; Edges for triangles:
L6c7a_triangle_edges_top:
db #03, #00, #01, #01, #02, #02, #00
L6c81_triangle_edges_bottom:
db #03, #00, #02, #02, #01, #01, #00
; Edges for rectangles:
L6c88_rectangle_edges_top:
db #04, #00, #01, #01, #02, #02, #03, #03, #00
L6c91_rectangle_edges_bottom:
db #04, #00, #03, #03, #02, #02, #01, #01, #00
; Edges for pentagons:
L6c9a_pentagon_edges_top:
db #05, #00, #01, #01, #02, #02, #03, #03, #04, #04, #00
L6ca5_pentagon_edges_bottom:
db #05, #00, #04, #04, #03, #03, #02, #02, #01, #01, #00
L6cb0_face_definition_for_flat_objects:
db #01
db #00, #06, #00, #01, #02, #03, #04, #05
; --------------------------------
L6cb9_game_text:
db 0, " PRESS ANY KEY "
L6cc9_text_overpowered:
db 0, " OVERPOWERED "
db 1, " YOU COLLAPSE "
db 0, " CRUSHED "
db 1, " FATAL FALL "
db 0, " ESCAPE !! "
db 0, " THE CRYPT "
L6d29_text_out_of_reach:
db 1, " OUT OF REACH "
L6d39_text_no_effect:
db 0, " NO EFFECT "
db 1, " NO ENTRY "
db 0, " WAY BLOCKED "
L6d69_text_not_enough_room:
db 0, "NOT ENOUGH ROOM"
L6d79_text_too_weak:
db 1, " TOO WEAK "
L6d89_text_crawl:
db 1, "CRAWL SELECTED "
L6d99_text_walk:
db 0, " WALK SELECTED "
L6da9_text_run:
db 1, " RUN SELECTED "
db 1, " AAAAAARRRGH! "
db 0, " KEY COLLECTED "
db 0, " NO KEYS FOUND "
db 1, "NEED RIGHT KEY "
db 0, " IT IS EMPTY "
db 1, " DOOR TO... "
db 0, "IN CASE OF FIRE"
db 0, "CHOMP CHOMP AHH"
db 1, " OOOOFFF! "
db 1, "TREASURE FOUND "
db 1, "THE DOOR OPENS "
db 0, "THE DOOR CLOSES"
db 0, " PADLOCKED "
db 0, "IT'S VERY HEAVY"
db 0, " SMASH ! "
db 0, "SHOWS LEVEL NO."
db 0, " PADLOCKED "
db 0, "HMM, NEED A BIT"
db 1, "MORE SPRING IN "
db 1, "YOUR STEP HERE "
db 0, " THE LID OPENS "
db 1, "THE LID CLOSES "
db 1, "GLUG GLUG GLUG "
db 0, "RETRY THE CHEST"
db 1, "REVITALISATION "
L6f49_area_names:
db 1, " WILDERNESS "
db 0, " THE CRYPT "
db 1, "CRYPT CORRIDOR "
db 0, " THE MOUSETRAP "
db 0, " LAST TREASURE "
db 1, " TANTALUS "
db 0, " BELENUS "
db 0, " POTHOLE "
db 0, " THE STEPS "
db 1, " LOOKOUT POST "
db 1, " KERBEROS "
db 0, " CRYPT KEY "
db 0, " GATEHOUSE "
db 0, " BELENUS KEY "
db 1, "SPIRITS' ABODE "
db 1, " RAVINE "
db 1, " LIFT SHAFT "
db 0, " LEVEL 2 KEY "
db 0, "LIFT ENTRANCE 6"
db 1, " TUNNEL "
db 0, " LEVEL 3 KEY "
db 1, " THE TUBE "
db 0, "LIFT ENTRANCE 5"
db 0, " EPONA "
db 0, " LEVEL 4 KEY "
db 0, "LIFT ENTRANCE 4"
db 0, " NANTOSUELTA "
db 0, " STALACTITES "
db 0, " NO ROOM "
db 0, " THE TRAPEZE "
db 1, "TREASURE CHEST "
db 0, "LIFT ENTRANCE 3"
db 1, " THE SWITCH "
db 1, " THE PILLAR "
db 1, " GROUND FLOOR "
db 1, " THE RAT TRAP "
db 0, " YIN KEY "
db 1, " LIFT "
db 0, " TRAPEZE KEY "
db 1, " YANG KEY "
; --------------------------------
L71c9_text_status_array:
db 0, "FEEBLE "
db 0, "WEAK "
db 0, "HEALTHY "
db 0, "STRONG "
db 0, "MIGHTY "
db 0, "HERCULEAN "
; --------------------------------
; Used to store the filename the user inputs in the load/save menu.
L720b_text_input_buffer:
db 0, " "
db 19 ; Unused?
L721a_text_asterisks:
db 0, "*********************"
db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
db #00, #00, #00, #00, #00, #00, #00, #00, #00, #00, #00, #30, #72, #30, #72, #30
db #72, #30, #72, #30, #72, #30, #72, #30, #72, #30, #72, #30, #72, #30, #72
; --------------------------------
L725c_videomem_row_pointers:
dw #4084, #4184, #4284, #4384, #4484, #4584, #4684, #4784
dw #40a4, #41a4, #42a4, #43a4, #44a4, #45a4, #46a4, #47a4
dw #40c4, #41c4, #42c4, #43c4, #44c4, #45c4, #46c4, #47c4
dw #40e4, #41e4, #42e4, #43e4, #44e4, #45e4, #46e4, #47e4
dw #4804, #4904, #4a04, #4b04, #4c04, #4d04, #4e04, #4f04
dw #4824, #4924, #4a24, #4b24, #4c24, #4d24, #4e24, #4f24
dw #4844, #4944, #4a44, #4b44, #4c44, #4d44, #4e44, #4f44
dw #4864, #4964, #4a64, #4b64, #4c64, #4d64, #4e64, #4f64
dw #4884, #4984, #4a84, #4b84, #4c84, #4d84, #4e84, #4f84
dw #48a4, #49a4, #4aa4, #4ba4, #4ca4, #4da4, #4ea4, #4fa4
dw #48c4, #49c4, #4ac4, #4bc4, #4cc4, #4dc4, #4ec4, #4fc4
dw #48e4, #49e4, #4ae4, #4be4, #4ce4, #4de4, #4ee4, #4fe4
dw #5004, #5104, #5204, #5304, #5404, #5504, #5604, #5704
dw #5024, #5124, #5224, #5324, #5424, #5524, #5624, #5724
; --------------------------------
; Unused?
L733c:
db #30, #72, #30, #72, #30, #72, #30, #72, #30, #72
db #30, #72, #30, #72, #30, #72, #30, #72, #30, #72
L7350_compass_eye_ui_row_pointers:
; from (25, 158) to (25, 162)
dw #5679, #5779, #5099, #5199, #5299
L735a_ui_message_row_pointers
; from (11, 176) to (11, 183)
dw #50cb, #51cb, #52cb, #53cb
dw #54cb, #55cb, #56cb, #57cb
L736a_spirit_count_ui_row_pointers:
; from (14, 152) to (14, 159)
dw #506e, #516e, #526e, #536e
dw #546e, #556e, #566e, #576e
L737a_strength_ui_row_pointers
; from (4, 151) to (4, 165)
dw #5744, #5064, #5164, #5264
dw #5364, #5464, #5564, #5664
dw #5764, #5084, #5184, #5284
dw #5384, #5484, #5584
L7398_key_count_ui_row_pointers
; from (4, 173) to (4, 186)
dw #55a4, #56a4, #57a4, #50c4
dw #51c4, #52c4, #53c4, #54c4
dw #55c4, #56c4, #57c4, #50e4
dw #51e4, #52e4
L73b4_waving_flag_row_pointers:
dw #451d, #461d, #471d, #403d
dw #413d, #423d, #433d, #443d
dw #453d
L73c6_cosine_sine_table:
; Each "dw" contains (cos, sin) (one byte each):
; [-64, 64]
; 72 steps is a whole turn.
dw #4000, #4006, #3f0b, #3e11
dw #3c16, #3a1b, #3720, #3425
dw #3129, #2d2d, #2931, #2534
dw #2037, #1b3a, #163c, #113e
dw #0b3f, #0640, #0040, #fa40
dw #f53f, #ef3e, #ea3c, #e53a
dw #e037, #db34, #d731, #d32d
dw #cf29, #cc25, #c920, #c61b
dw #c416, #c211, #c10b, #c006
dw #c000, #c0fa, #c1f5, #c2ef
dw #c4ea, #c6e5, #c9e0, #ccdb
dw #cfd7, #d3d3, #d7cf, #dbcc
dw #e0c9, #e5c6, #eac4, #efc2
dw #f5c1, #fac0, #00c0, #06c0
dw #0bc1, #11c2, #16c4, #1bc6
dw #20c9, #25cc, #29cf, #2dd3
dw #31d7, #34db, #37e0, #3ae5
dw #3cea, #3eef, #3ff5, #40fa
L7456_player_desired_x:
dw 0
L7458_player_desired_y:
dw 0
L745a_player_desired_z:
dw 0
db #00 ; Unused?
L745d_rendering_cube_volume: ; max/min x, max/min y, max/min z (objects outside this will not be rendered).
db #00, #00, #00, #00, #00, #00
L7463_global_area_objects:
dw #d2c6
L7465_global_area_n_objects:
db #4b
L7466_need_attribute_refresh_flag:
db 1
L7467_player_starting_position_object_id:
db #01
L7468_focus_object_id:
db #01
L7469_n_spirits_found_in_current_area:
db #00
L746a_current_drawing_texture_id:
db #00
L746b_n_objects_to_draw:
db #00
L746c_game_flags:
db #fd, #ff ; 1st byte :
; - bit 0: ????
; - bit 1: game over indicator.
; - bit 2: indicates that we need to "reproject" 3d objects to the 2d viewport.
; - bit 3: ????
; - bit 4/5: trigger redraw of compass eye.
; - bit 6: ????
; - bit 7: ????
; 2nd byte:
; - bit 0: ????
; - bit 1: ????
; - bit 2: ????
; - bit 3: trigger a re-render.
; - bit 4: flag to refresh spirit meter.
; - bit 5: in the update function, it triggers waiting until interrupt timer is 0, and then reprints the current room name.
; - bit 6: flag to refresh # of keys in UI.
; - bit 7: flag to redraw keys in the UI.
L746e_global_rules_ptr:
dw #d11f
L7470_previous_area_id: ; Set when a RULE_TYPE_TELEPORT is triggered, but it is unused.
db #00
L7471_event_rule_found: ; 1 indicates that a rule for the corresponding event was found (0 otherwise).
db #00
L7472_symbol_shift_pressed:
db 0 ; 0 = not pressed, 1 = pressed.
L7473_timer_event: ; every time L6b22_time_unit6 changes, this is set to 8.
db #00
L7474_check_if_object_crushed_player_flag:
db #00
L7475_call_Lcba4_check_for_player_falling_flag: ; Indicates whether we should call Lcba4_check_for_player_falling this game cycle.
db #01
L7476_trigger_collision_event_flag: ; If this is "1" after the player has tried to move, it means we collided with an object.
db #00
L7477_render_buffer_effect:
db #02
L7478_interrupt_executed_flag: ; some methods use this to wait for the interrupt to be executed.
db #01
L7479_current_game_state:
; This is:
; - 0: for when player is controlling
; - 1: some times game state is 1 even not at game over (e.g. before game starts).
; - 1-5: when game is over (and number identifies the reason, including successful escape!)
; - 6: for when in the load/save/quit menu
db #01
L747a_requested_SFX:
db #00
L747b: ; Unused, set to 63 at game start, unused afterwards.
db #3f
L747c_within_interrupt_flag:
db 0 ; This is changed to #80 when we are inside the interrupt.
L747d: ; Note: This saves the value of "iy" at game start, and restores it each time tape is accessed.
; But it makes no sense, as the tape load/save functions do not make use of iy. Very strange!
dw #5c3a
L747f_player_event: ; player events (they can be "or-ed"): 1: moving, 2: interact, 4: trow rock
db #00
L7480_under_pointer_object_ID: ; stores the ID of the object under the player pointer.
db #00
L7481_n_objects_covering_the_whole_screen:
db 0
; Copy of the projected vertex coordinates used by the Lb607_find_object_under_pointer
; function:
L7482_object_under_pointer__current_face_vertices:
db #00, #00, #00, #00, #00, #00, #00, #00, #00, #00
db #00, #00, #00, #00, #00, #00, #00, #00, #00, #00
L7496_current_drawing_primitive_n_vertices:
db #00
L7497_next_projected_vertex_ptr: ; initialized at 'L67f4_projected_vertex_data', and keeps increasing as we project objects from 3d to 2d.
dw #0000
L7499_3d_object_bounding_box_relative_to_player_ptr:
dw #0000
L749b_next_object_projected_data_ptr:
dw #0000
L749d_object_currently_being_processed_ptr:
dw #0000
L749f_number_of_pressed_keys:
db #00
L74a0_pressed_keys_buffer:
db #ef, #4e, #cd, #77, #66
L74a5_interrupt_timer:
db #00 ; Decreases by 1 at each interrupt until reaching 0. It is used by the game to create pauses.
L74a6_player_movement_delta:
dw #0000, #0000, #0000
; These two sets of coordinates are used to define the volume the player will traverse when moving.
; It is used because, due to the low frame rate, the player moves in very large steps, and we need
; to ensure small objects are not skipped.
L74ac_movement_volume_max_coordinate:
dw #0000, #0000, #0000
L74b2_movement_volume_min_coordinate:
dw #0000, #0000, #0000
; These 3 sets of coordinates are used in the "Lab6d_correct_player_movement_if_collision_internal"
; function to store the target movement position after correcting them in case there is a
; collision with an object.
L74b8_collision_corrected_coordinates_2:
dw #0000, #0000, #0000
L74be_collision_corrected_climb_coordinates:
dw #0000, #0000, #0000
L74c4_collision_corrected_coordinates_1:
dw #0000, #0000, #0000
L74ca_movement_target_coordinates_2: ; used when there is falling involved in movement
dw #0000, #0000, #0000
L74d0_target_object_climb_coordinates:
dw #0000 ; x
dw #0000 ; y
dw #0000 ; z
L74d6_movement_target_coordinates_1: ; used when there is no falling involved in movement
dw #0000, #0000, #0000
L74dc_falling_reference_coordinates:
dw #0000, #0000, #0000
L74e2_movement_direction_bits:
; bit 0 means negative movement on x, bit 1 means positive movement on y
; bits 2, 3 the same for y, and 4, 5 the same for z.
db #00
L74e3_player_height_16bits: ; (L6ab9_player_height) * 64
dw #0000
L74e5_collision_correction_object_shape_type: ; the game prefers 3d shapes to 2d shapes when correcting movement upon collisions. This variable is used to implemetn such preference.
db #00
L74e6_movement_involves_falling_flag: ; 0: no falling, 1: we fell
db #00
L74e7_closest_object_below_distance:
dw #0000
L74e9_closest_object_below_ptr:
dw #0000
L74eb_closest_object_below_ID:
db #00
L74ec_previous_pressed_keys_buffer:
db #16, #5e, #3a, #d6, #1f
L74f1: ; Note: I believe this is unused (written, but never read)
db #0e
L74f2_keyboard_input: ; 8 bytes, one per keyboard half row
db #00, #00, #00, #00, #00, #00, #00, #00
L74fa_object_under_pointer__current_face: ; saves the pointer to the current face we are checking when trying to find which object is under the pointer.
dw #0c67
L74fc_object_under_pointer__projected_xs_at_pointer_y:
db #18 ; number of points in the list below
db #f5, #c6, #30, #23, #77 ; screen x coordinates of face edges at the y coordinate of the pointer.
; Used by the "Lb607_find_object_under_pointer" function, to determine
; which object is under the player pointer.
; --------------------------------
; Rendering variables:
L7502_sp_tmp: ; Used to temporarily save the 'sp' register.
dw 0
L7504_line_drawing_slope: ; Amount we need to move in the X axis each time we move one pixel in the Y axis.
dw #0000 ; Uses fixed point arithmetic (with 8 bits of decimal part).
L7506_polygon_drawing_second_slope: ; When drawing polygons, we calculate two lines at once, and draw
dw #0000 ; horizontal lines between them. This is the slopw of the second line.
L7508_current_drawing_row:
db #00
L7509_line_drawing_thinning_direction:
db #00
L750a_first_loop_flags: ; Used to indicate if we have drawn at least one pixel when drawing polygons.
db #00
L750b_current_drawing_n_vertices_left: ; How many vertices are there to draw in the current primitive.
db 0
L750c_current_drawing_row_ptr:
dw 0
L750e_current_drawing_texture_ptr:
dw 0