-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathlevel.s
1427 lines (1361 loc) · 52.7 KB
/
level.s
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
ZONEH_LEFT = 0
ZONEH_RIGHT = 1
ZONEH_UP = 2
ZONEH_DOWN = 3
ZONEH_CHARSET = 4
ZONEH_BG1 = 5
ZONEH_BG2 = 6
ZONEH_BG3 = 7
ZONEH_MUSIC = 8
ZONEH_SPAWNPARAM = 9
ZONEH_SPAWNSPEED = 10
ZONEH_SPAWNCOUNT = 11
ZONEH_DATA = 12
OBJ_ANIMATE = $80 ;In levelobject Y-coordinate
OBJ_MODEBITS = $03
OBJ_TYPEBITS = $1c
OBJ_AUTODEACT = $20
OBJ_SIZE = $40
OBJ_ACTIVE = $80
OBJMODE_NONE = $00
OBJMODE_TRIG = $01
OBJMODE_MANUAL = $02
OBJMODE_MANUALAD = $03
OBJTYPE_SIDEDOOR = $00
OBJTYPE_DOOR = $04
OBJTYPE_BACKDROP = $08
OBJTYPE_SWITCH = $0c
OBJTYPE_REVEAL = $10
OBJTYPE_SCRIPT = $14
OBJTYPE_CHAIN = $18
OPERATEDELAY = 1
DOORENTRYDELAY = 6
AUTODEACTDELAY = 12
RCP_RESETTIME = 0
RCP_CONTINUETIME = 4
UpdateLevel = lvlCodeStart
LoadLevelError: jsr LFR_ErrorPrompt
jmp LoadLevelRetry
; Change current level and remove leveldata-actors back into statebits
;
; Parameters: A Level number
; Returns: -
; Modifies: A,X,Y,temp vars
ChangeLevel: cmp levelNum ;Check if level already loaded
beq SameLevel
pha
jsr SaveLevelState
pla
sta levelNum
; Load level without processing actor removal. Note: does not call InitMap
; as that is usually done later after finding the correct zone in the new level
;
; Parameters: levelNum
; Returns: -
; Modifies: A,X,Y,temp vars
LoadLevel: lda levelNum
ldx #F_LEVEL
jsr MakeFileName
jsr BlankScreen ;Level loading will trash the second screen partially, so blank
LoadLevelRetry: lda #<lvlObjX ;Load level objects
ldx #>lvlObjX
jsr LoadFile
bcs LoadLevelError
lda #<lvlDataActX ;Load level actors under screen2
ldx #>lvlDataActX
jsr LoadFile
bcs LoadLevelError
ldy #C_MAP
jsr LoadAllocFile ;Load MAP chunk
bcs LoadLevelError
lda #$ff
sta autoDeactObjNum ;Reset object auto-deactivation
ldx #MAX_LVLACT-1
LL_PurgeOldLevelDataActors:
lda lvlActOrg,x ;Remove the current leveldata actors
bpl LL_PurgeNext ;to make room for new
lda #$00
sta lvlActT,x
sta lvlActOrg,x
LL_PurgeNext: dex
bpl LL_PurgeOldLevelDataActors
jsr GetLevelDataActorBits
ldx #MAX_LVLDATAACT-1 ;Copy level actors
LL_CopyLevelDataActors:
lda lvlDataActT,x ;Slot occupied in leveldata?
beq LL_NextLevelDataActor
txa
jsr DecodeBit
and (actLo),y ;Check state, whether actor still exists
beq LL_NextLevelDataActor
jsr GetLevelActorIndex
lda lvlDataActX,x
sta lvlActX,y
lda lvlDataActY,x
sta lvlActY,y
lda lvlDataActF,x
sta lvlActF,y
lda lvlDataActT,x
sta lvlActT,y
lda lvlDataActWpn,x
sta lvlActWpn,y
txa ;Store the index in leveldata
ora #ORG_LEVELDATA
sta lvlActOrg,y
LL_NextLevelDataActor:
dex
bpl LL_CopyLevelDataActors
LL_SkipLevelDataActors:
jsr GetLevelObjectBits ;Set persistent levelobjects' active state now
ldx #$00 ;Levelobject index
stx temp1 ;Persistent levelobject index
LL_SetLevelObjectsActive:
jsr IsLevelObjectPersistent
beq LL_NextLevelObject
and (actLo),y ;Active?
beq LL_NextLevelObject
txa
tay
lda lvlObjB,y
ora #OBJ_ACTIVE
sta lvlObjB,y
and #OBJ_TYPEBITS
cmp #OBJTYPE_REVEAL ;If this is a weapon closet, make sure items at it are revealed
bne LL_NoReveal
jsr AO_RevealSub
LL_NoReveal: jsr AnimateObjectActivation ;Animate if necessary
tya
tax
LL_NextLevelObject:
inx
cpx #MAX_LVLOBJ
bcc LL_SetLevelObjectsActive
rts
; Find the zone at player's position. Also load the proper charset if not loaded
; Falls through to InitMap regardless of whether charset was changed.
;
; Parameters: -
; Returns: -
; Modifies: A,X,Y,loader temp vars
FindPlayerZone: ldx actXH+ACTI_PLAYER
ldy actYH+ACTI_PLAYER
jsr FindZoneXY
jsr EnsureCharSet
; Calculate start addresses for each map-row (of current zone) and for each
; block
;
; Parameters: -
; Returns: -
; Modifies: A,X,Y,loader temp vars
PostLoad:
InitMap: lda zoneNum ;Map address might have changed
jsr FindZoneNum ;(dynamic memory), so re-find
lda limitU ;Startrow of zone
ldy mapSizeX ;Multiply with map row width
ldx #zpSrcLo
jsr MulU
lda limitL ;Add startcolumn of zone
jsr Add8
jsr Negate16 ;Negate
ldy #zoneLo ;Add zone startaddress
jsr Add16
lda #ZONEH_DATA ;Add zone mapdata offset
jsr Add8
ldy #$00 ;Row counter
IM_MapLoop: cpy limitU ;Check if outside zone vertically,
bcc IM_MapRowOutside ;store zero address in that case
cpy limitD
bcs IM_MapRowOutside
lda zpSrcLo
sta mapTblLo,y
lda zpSrcHi
bne IM_MapRowDone
IM_MapRowOutside:
lda #$00
IM_MapRowDone: sta mapTblHi,y
lda mapSizeX
jsr Add8
iny
bpl IM_MapLoop
lda fileLo+C_BLOCKS ;Address of first block
sta zpSrcLo
lda fileHi+C_BLOCKS
sta zpSrcHi
ldy #$00
IM_BlockLoop: lda zpSrcLo ;Store and increase block-
sta blkTblLo,y ;pointer
lda zpSrcHi
sta blkTblHi,y
lda #$10
jsr Add8
iny
cpy #MAX_BLK
bcc IM_BlockLoop
ldy #ZONEH_BG2 ;Check if zone air is toxic
lda (zoneLo),y ;(result needed in several places)
bpl IM_NoToxicAir
ldy lvlAirToxinDelay ;Unaffected by filter?
bmi IM_HasToxicAir
eor upgrade
IM_HasToxicAir:
IM_NoToxicAir: sta ULO_AirToxinFlag+1
rts
; Get address of levelactor-bits according to current level
;
; Parameters: levelNum
; Returns: bits address in actLo/actHi, Y bitarea size-1, A=0
; Modifies: A,X,Y,actLo-actHi
GetLevelDataActorBits:
lda #$00
skip2
; Get address of levelobject-bits according to current level
;
; Parameters: levelNum
; Returns: bits address in actLo/actHi, Y bitarea size-1, A=0
; Modifies: A,X,Y,actLo-actHi
GetLevelObjectBits:
lda #NUMLEVELS
clc
adc levelNum
tax
lda #<lvlStateBits
adc lvlDataActBitsStart,x
sta actLo
lda #>lvlStateBits
adc #$00
sta actHi
lda lvlDataActBitsStart+1,x
sbc lvlDataActBitsStart,x ;C=0, subtract one more as intended (size-1)
tay
lda #$00
rts
; Check if a levelobject should be persisted. If yes, calculate its bitvalue
;
; Parameters: X levelobject index, temp1 persistent levelobject index
; Returns: A>0 is persistent, bit in A and temp1 incremented, A=0 not persistent
; Modifies: A
IsLevelObjectPersistent:
lda lvlObjB,x ;Autodeactivating: not persistent
tay
and #OBJ_AUTODEACT
bne ILOP_No
lda lvlObjY,x ;Animating: is persistent
bmi ILOP_Yes
tya
and #OBJ_TYPEBITS
cmp #OBJTYPE_SWITCH
bcc ILOP_No
ILOP_Yes: lda temp1
inc temp1
jmp DecodeBit
ILOP_No: lda #$00
rts
; Save state of leveldata actors & leveldata objects as bits
; Needs to be done on level change and on game save
;
; Parameters: levelNum
; Returns: -
; Modifies: A,X,Y,actLo-actHi
SaveLevelState: jsr RemoveLevelActors ;Make sure are removed from screen first
jsr GetLevelDataActorBits ;First clear all actor bits, then set those that exist
SLAS_ClearLoop: sta (actLo),y
dey
bpl SLAS_ClearLoop
ldx #MAX_LVLACT-1
SLAS_ActorLoop: lda lvlActT,x
beq SLAS_NextActor
lda lvlActOrg,x ;Check persistence mode, must be leveldata
bpl SLAS_NextActor
and #$7f ;Actor is not gone, set bit
jsr DecodeBit
ora (actLo),y
sta (actLo),y
SLAS_NextActor: dex
bpl SLAS_ActorLoop
jsr GetLevelObjectBits ;First clear all object bits, then set those that are active
SLOS_ClearLoop: sta (actLo),y
dey
bpl SLOS_ClearLoop
tax
sta temp1 ;Persistent object index
SLOS_Loop: jsr IsLevelObjectPersistent
beq SLOS_NextObject
lda lvlObjB,x ;Check if active
bpl SLOS_NextObject
lda DB_Value+1
ora (actLo),y
sta (actLo),y
SLOS_NextObject:inx
cpx #MAX_LVLOBJ
bcc SLOS_Loop
ECS_HasCharSet: rts
; Find new level to load after entering a sidedoor
;
; Parameters: temp1 target X coord, temp2 target Y coord
; Returns: new level loaded, X & Y new target coords
; Modifies: A,X,Y,temp regs,loader temp regs
FindNewLevel: lda temp1 ;Convert X coord to screens
cmp #$ff ;Handle -1 (moving left out of level) as a special case
bne FNL_NotNegative
lda #$ff
sta temp3
lda #9
bne FNL_NegativeDone
FNL_NotNegative:ldy #10
ldx #<temp3
jsr DivU
FNL_NegativeDone:
pha ;Store remainder
lda temp3
ldx levelNum
clc
adc lvlLimitL,x ;Add level X origin in screens
sta temp5
ldx #$00
FNL_Loop: cpx levelNum ;Current level is always excluded
beq FNL_Next
lda temp5 ;Note: if there's ambiguity in bounds
cmp lvlLimitL,x ;the first matching level number is used
bcc FNL_Next
cmp lvlLimitR,x
bcs FNL_Next
lda temp2 ;Y coordinates are always just blocks
cmp lvlLimitU,x
bcc FNL_Next
cmp lvlLimitD,x
bcc FNL_Found
FNL_Next: inx
bpl FNL_Loop ;Will produce rubbish result if not found
FNL_Found: stx FNL_NewLevelNum+1
lda temp5
sec
sbc lvlLimitL,x ;Subtract screen origin of new level
ldy #10
ldx #<temp3 ;Convert back to blocks
jsr MulU
pla ;Add back block remainder
clc
adc temp3
pha ;New X coord
lda temp2
pha ;Old Y coord (temp2 will likely be trashed by ChangeLevel)
FNL_NewLevelNum:lda #$00
jsr ChangeLevel
pla
tay
pla
tax
rts
; Ensure that zone's charset is loaded. Does not call InitMap on all code paths
;
; Parameters: -
; Returns: -
; Modifies: A,X,Y,loader temp regs
EnsureCharSet: ldy #ZONEH_CHARSET
lda (zoneLo),y
ECS_LoadedCharSet:
cmp #$ff ;Switch charset if required
beq ECS_HasCharSet
sta ECS_LoadedCharSet+1
ldx #F_CHARSET
jsr MakeFileName
jsr BlankScreen ;Blank screen to make sure no animation
beq ECS_RetryCharSet ;X=0 on return
ECS_LoadCharSetError:
jsr LFR_ErrorPrompt
ECS_RetryCharSet:
lda #<lvlCodeStart ;Load char animation code, charset and colors/infos
ldx #>lvlCodeStart
jsr LoadFile
bcs ECS_LoadCharSetError
ldy #C_BLOCKS
jsr LoadAllocFile ;Load BLOCKS chunk
bcs ECS_LoadCharSetError
ldx #lvlPropertiesEnd-lvlPropertiesStart-1
ECS_CopyLevelProperties: ;Copy level properties into place
lda charsetLoadProperties,x
sta lvlPropertiesStart,x
dex
bpl ECS_CopyLevelProperties
rts
; Set zone's multicolors
;
; Parameters: zoneLo,zoneHi
; Returns: -
; Modifies: A,Y
SetZoneColors: ldy #ZONEH_BG1 ;Set zone multicolors
lda (zoneLo),y
sta Irq1_Bg1+1
iny
lda (zoneLo),y
sta Irq1_Bg2+1
iny
lda (zoneLo),y
sta Irq1_Bg3+1
rts
; Calculate horizontal centerpoint of zone
;
; Parameters: -
; Returns: A zone center, also stored in temp8
; Modifies: A, temp8
GetZoneCenterX: lda limitL
clc
adc limitR
ror
sta temp8
rts
; Find the zone indicated by coordinates or number.
;
; Parameters: A zone number (FindZoneNum) or X,Y pos (FindZoneXY)
; Returns: zoneNum, zoneLo-zoneHi
; Modifies: A,X,Y,loader temp vars
FindZoneXY: sty zpBitBuf
lda #$00
FZXY_Loop: jsr FindZoneNum
cpx limitL
bcc FZXY_Next
cpx limitR
bcs FZXY_Next
ldy zpBitBuf
cpy limitU
bcc FZXY_Next
cpy limitD
bcc FZXY_Done
FZXY_Next: inc zoneNum
lda zoneNum
cmp fileNumObjects+C_MAP
bcc FZXY_Loop
FZXY_Done: rts
FindZoneNum: sta zoneNum
asl
tay
lda fileLo+C_MAP
sta zpBitsLo
lda fileHi+C_MAP
sta zpBitsHi
lda (zpBitsLo),y
sta zoneLo
iny
lda (zpBitsLo),y
sta zoneHi
ldy #ZONEH_LEFT
lda (zoneLo),y
sta limitL
iny
lda (zoneLo),y
sta limitR
sec
sbc limitL
sta mapSizeX
iny
lda (zoneLo),y
sta limitU
iny
lda (zoneLo),y
sta limitD
OO_Done: rts
; Operate a level object. Sets the operate stance and delay counter,
; but actions are performed later in UpdateLevelObjects
;
; Parameters: Y object number (should also be in lvlObjNum)
; Returns: C=1 if object is operable, C=0 if not
; Modifies: A,Y
OperateObject: lda actF1+ACTI_PLAYER ;Already in enter/operate stance?
cmp #FR_ENTER
beq OO_Continue
lda actPrevCtrl+ACTI_PLAYER ;If joystick already held up, do not operate again
and #JOY_UP ;(eg. after entering a door)
bne OO_Fail
lda lvlObjB,y ;Must either be manually activated object,
and #$ff-OBJ_SIZE ;or a door opened from elsewhere
cmp #OBJTYPE_DOOR+OBJ_ACTIVE
beq OO_BeginOperate
and #OBJ_MODEBITS
cmp #OBJMODE_MANUAL
bcc OO_Fail
lda lvlObjB,y
bpl OO_BeginOperate
OO_Active: and #OBJ_MODEBITS ;Object was active, inactivate if possible
cmp #OBJMODE_MANUALAD
bne OO_Fail
OO_BeginOperate:
lda #FR_ENTER
sta actF1+ACTI_PLAYER
sta actF2+ACTI_PLAYER
lda #$00
sta actFd+ACTI_PLAYER ;Reset operate/door entry delay
sta actSX+ACTI_PLAYER ;Reset speed to prevent sliding especially after script load finishes
beq OO_Success
OO_Continue: lda actFd+ACTI_PLAYER
bmi OO_Success
inc actFd+ACTI_PLAYER ;Increment operate/door entry delay, up to 128
OO_Success: sec
rts
OO_Fail: clc
IO_Done: rts
; Toggle a level object
;
; Parameters: Y object number
; Returns: -
; Modifies: A,X,Y,temp vars
ToggleObject: lda lvlObjB,y
bpl ActivateObject
; Inactivate a level object
;
; Parameters: Y object number
; Returns: -
; Modifies: A,X,temp vars
InactivateObject:
lda lvlObjB,y ;Make sure that is active
bpl IO_Done
and #$ff-OBJ_ACTIVE
sta lvlObjB,y
pha
lda #$ff
jsr AnimateObjectDelta
pla
and #OBJ_TYPEBITS
cmp #OBJTYPE_CHAIN
bne IO_Done
lda lvlObjDL,y
tay
jmp InactivateObject
; Animate a level object by block deltavalue
;
; Parameters: A deltavalue, Y object number
; Returns: -
; Modifies: A,X,temp vars
AnimateObjectActivation:
lda #$01
AnimateObjectDelta:
sty temp3
sta temp4
lda lvlObjY,y
bpl AOD_Done ;No animation
jsr AOD_Sub
lda lvlObjB,y
and #OBJ_SIZE
beq AOD_Done
lda lvlObjY,y
sec
sbc #$01
AOD_Sub: and #$7f
ldx lvlObjX,y
tay
lda temp4
jsr UpdateBlockDelta
ldy temp3
AO_Done:
AOD_Done: rts
; Activate a level object
;
; Parameters: Y object number
; Returns: temp2: object number
; Modifies: A,X,Y,temp vars
AO_Chain: lda lvlObjDL,y
tay
ActivateObject: sty temp2
lda lvlObjB,y ;Make sure that is inactive
bmi AO_Done
ora #OBJ_ACTIVE
sta lvlObjB,y
pha
and #OBJ_AUTODEACT ;Enable auto-deactivation if necessary
beq AO_NoAutoDeact
lda autoDeactObjNum ;If another object already deactivating,
bmi AO_NoPreviousAutoDeact ;deactivate it immediately
cpy autoDeactObjNum
beq AO_NoPreviousAutoDeact ;If same object deactivating, no need to do that
tay
jsr InactivateObject
ldy temp2
AO_NoPreviousAutoDeact:
sty autoDeactObjNum
lda #AUTODEACTDELAY
sta autoDeactObjCounter
AO_NoAutoDeact: jsr AnimateObjectActivation ;Animate object if necessary
pla
and #OBJ_TYPEBITS ;Check for type-specific action
cmp #OBJTYPE_CHAIN
beq AO_Chain
cmp #OBJTYPE_SCRIPT
beq AO_Script
cmp #OBJTYPE_SWITCH
beq AO_Toggle
cmp #OBJTYPE_REVEAL
beq AO_Reveal
AO_NoOperation: rts
; Script execution
AO_Script: ldx lvlObjDH,y
lda lvlObjDL,y
jmp ExecScript
; Toggle object
AO_Toggle: lda lvlObjDL,y
tay
jmp ToggleObject
; Reveal actors (weapon closet)
AO_Reveal: jsr AO_RevealSub
jmp AddAllActors ;Add all actors now to reveal the object(s) immediately
AO_RevealSub: lda lvlObjY,y
ora #$80 ;Check for hidden bit
sta AO_RevealYCmp+1
ldx #MAX_LVLACT-1
AO_RevealLoop: lda lvlActT,x
beq AO_RevealNext
lda lvlActOrg,x ;Check whether is a leveldata actor,
bmi AO_RevealLevelOK ;or is global/temp which belongs to the current level
and #ORG_LEVELNUM
cmp levelNum
bne AO_RevealNext
AO_RevealLevelOK:
lda lvlActX,x
cmp lvlObjX,y
bne AO_RevealNext
lda lvlActY,x
AO_RevealYCmp: cmp #$00
bne AO_RevealNext
AO_DoReveal: and #$7f
sta lvlActY,x
AO_RevealNext: dex
bpl AO_RevealLoop
rts
; Position actor to levelobject, coarsely only
;
; Parameters: X:actor number, Y levelobject number
; Returns: -
; Modifies: A
SetActorAtObject:
lda #$80
sta actXL,x
asl ;A=0
sta actYL,x
lda lvlObjX,y
sta actXH,x
lda lvlObjY,y
and #$7f
sta actYH,x
AAOG_Done: rts
; Save an in-memory checkpoint. Removes other actors than player as a byproduct
;
; Parameters: -
; Returns: -
; Modifies: A,X,Y,temp regs
SaveCheckpoint: jsr SaveLevelState
ldx #6
ldy #6*MAX_ACT
sec
StorePlayerActorVars:
lda actXL+ACTI_PLAYER,y
sta saveXL,x
tya
sbc #MAX_ACT
tay
dex
bpl StorePlayerActorVars
lda difficulty ;Save difficulty, but if the savestate has
cmp saveDifficulty ;already recorded a lower difficulty, do not bump it up
bcs SCP_NoDifficulty
sta saveDifficulty
SCP_NoDifficulty:
ldx #playerStateZPEnd-playerStateZPStart
SCP_ZPState: lda playerStateZPStart-1,x ;Copy player ZP variables
sta saveStateZP-1,x
dex
bne SCP_ZPState
lda #<playerStateStart
sta zpSrcLo
lda #>playerStateStart
sta zpSrcHi
lda #<saveState
ldx #>saveState
jsr SaveState_CopyMemory ;Copy rest of variables
lda #LOW_OXYGEN ;Ensure minimum oxygen,health,battery
cmp saveOxygen ;at save time
bcc SCP_OxygenOK
sta saveOxygen
SCP_OxygenOK: lda #LOW_HEALTH
cmp saveHP
bcc SCP_HealthOK
sta saveHP
SCP_HealthOK: lda #$00
ldx saveBattery+1
cpx #LOW_BATTERY
bcs SCP_BatteryOK
ldx #LOW_BATTERY
stx saveBattery+1
sta saveBattery
SCP_BatteryOK: ldy #MAX_SAVEACT ;Clear actor save table
SCP_ClearSaveLoop:
sta saveLvlActT-1,y
dey
bne SCP_ClearSaveLoop
ldx #MAX_LVLACT-1
SCP_SaveGlobalLoop:
lda lvlActT,x ;Save the important global actors
beq SCP_SaveGlobalNext
lda lvlActOrg,x
bmi SCP_SaveGlobalNext ;(skip leveldata actors and temp now)
asl
bpl SCP_SaveGlobalNext
jsr SaveActorSub
SCP_SaveGlobalNext:
dex
bpl SCP_SaveGlobalLoop
ldx levelActorIndex
SCP_SaveItemsLoop:
cpy #MAX_SAVEACT ;Then save as many temp items (weapons etc.)
bcs SCP_SaveItemsDone ;as possible, from the latest stored
lda lvlActT,x
bpl SCP_SaveItemsNext
lda lvlActOrg,x
cmp #ORG_GLOBAL
bcs SCP_SaveItemsNext
jsr SaveActorSub
SCP_SaveItemsNext:
inx
cpx #MAX_LVLACT
bcc SCP_SaveItemsNotOver
ldx #$00
SCP_SaveItemsNotOver:
cpx levelActorIndex ;Exit when wrapped
bne SCP_SaveItemsLoop
SCP_SaveItemsDone:
rts
SaveActorSub: lda lvlActX,x
sta saveLvlActX,y
lda lvlActY,x
sta saveLvlActY,y
lda lvlActF,x
sta saveLvlActF,y
lda lvlActT,x
sta saveLvlActT,y
lda lvlActWpn,x
sta saveLvlActWpn,y
lda lvlActOrg,x
sta saveLvlActOrg,y
iny
ULO_Paused:
ULO_ToxinDelay:
rts
; Update level objects. Handle operation, auto-deactivation and actually entering doors.
; Also check for picking up items & player health regeneration and toxin damage
;
; Parameters: -
; Returns: -
; Modifies: A,X,Y,temp vars
ULO_DoToxinDamage:
and #$7f
cmp toxinDelay
bcc ULO_ResetToxinDelay ;If transitioning to stronger toxin, reset delay
dec toxinDelay ;immediately
bpl ULO_ToxinDelay
ULO_ResetToxinDelay:
tay
dey
sty toxinDelay
ULO_DoDrowningDamage:
ldy #NODAMAGESRC
lda #DMG_DROWNING
jmp DamageActor ;X must be 0
UpdateLevelObjects:
lda menuMode
cmp #MENU_PAUSE
bcs ULO_Paused
lda attackTime ;Decrement global attack timer if necessary
bpl ULO_NoGlobalAttack
inc attackTime
ULO_NoGlobalAttack:
ldx scriptF ;Check for continuous script execution
beq ULO_NoScript ;Can't be from scriptfile 0 (title)
lda scriptEP
jsr ExecScript
ULO_NoScript: ldy autoDeactObjNum ;Check object auto-deactivation
bmi ULO_NoAutoDeact
dec autoDeactObjCounter
bne ULO_NoAutoDeact
lda lvlObjB,y ;Sidedoor?
and #OBJ_TYPEBITS
bne ULO_AutoDeactOK
ldx #ACTI_LASTNPC
ULO_AutoDeactCheckActors: ;For sidedoors, check player and enemies to have
lda actT,x ;exited before closing
beq ULO_AutoDeactCheckActorsNext
lda actXH,x
cmp lvlObjX,y
beq ULO_AutoDeactDoorWait
ULO_AutoDeactCheckActorsNext:
dex
bpl ULO_AutoDeactCheckActors
bmi ULO_AutoDeactOK
ULO_AutoDeactDoorWait:
inc autoDeactObjCounter ;Retry next frame
bne ULO_NoAutoDeact
ULO_AutoDeactOK:lda #$ff
sta autoDeactObjNum
jsr InactivateObject
ULO_NoAutoDeact:ldx #ACTI_PLAYER
lda actHp+ACTI_PLAYER ;Heal if not dead and not yet at full health
beq ULO_OxygenDone
ULO_PlayerAlive:cmp #HP_PLAYER
bcs ULO_NoHealing
lda battery+1 ;No healing if low battery
cmp #LOW_BATTERY+1
bcc ULO_NoHealing
lda healTimer
ULO_HealingRate:adc #INITIAL_HEALTIMER-1 ;C=1 here
bcc ULO_NoHealing2
lda #DRAIN_HEAL ;This is never multiplied
jsr DrainBattery
inc actHp+ACTI_PLAYER
lda #HEALTIMER_RESET ;Heal faster after first unit
ULO_NoHealing2: sta healTimer
ULO_NoHealing: lda upgrade ;Check battery auto-recharge
asl
bpl ULO_NoRecharge
ldy #$02 ;Restore 2 units per frame
ULO_RechargeLoop:
lda battery+1
cmp #MAX_BATTERY
bcs ULO_NoRecharge
inc battery
bne ULO_RechargeNoMSB
inc battery+1
ULO_RechargeNoMSB:
dey
bne ULO_RechargeLoop
ULO_NoRecharge: lda #$07
ULO_NoAirFlag: ldy #$00
bne ULO_OxygenDelay
ULO_CheckHeadUnderWater:
lda actF1+ACTI_PLAYER ;Check for player losing oxygen
cmp #FR_SWIM ;(must be swimming & head under water)
bcc ULO_RestoreOxygen
lda #-1
jsr GetCharInfoOffset
and #CI_WATER|CI_OBSTACLE
beq ULO_RestoreOxygen
lda #$03 ;Swimming oxygen drain is faster than "no air zone" drain
ULO_OxygenDelay:and UA_ItemFlashCounter+1
bne ULO_OxygenDone
lda oxygen
beq ULO_OutOfOxygen
dec oxygen
jmp ULO_OxygenDone
ULO_OutOfOxygen:lda UA_ItemFlashCounter+1 ;Separate drowning damage delay counting (always same)
and #$07
bne ULO_OxygenDone
jsr ULO_DoDrowningDamage
jmp ULO_OxygenDone
ULO_RestoreOxygen:
ldy oxygen
iny
iny
cpy #MAX_OXYGEN
bcc ULO_RestoreNotOver
ldy #MAX_OXYGEN
ULO_RestoreNotOver:
sty oxygen
ULO_OxygenDone:
ULO_AirToxinFlag:
lda #$00 ;Flashing screen effect for toxic air (as in Fist II)
bpl ULO_NoAirDamage
ldy #ZONEH_BG1
lda UA_ItemFlashCounter+1
and #$01
beq ULO_ToxinEffectColor
lda (zoneLo),y
ULO_ToxinEffectColor:
sta Irq1_Bg1+1
lda lvlAirToxinDelay
jsr ULO_DoToxinDamage
ULO_NoAirDamage:lda lvlWaterToxinDelay ;Toxic water?
beq ULO_NoWaterDamage
bmi ULO_WaterDamageNotFiltered ;Filter upgrade cancels damage?
ldy upgrade
bmi ULO_NoWaterDamage ;Note: filter upgrade must stay at bit 7
ULO_WaterDamageNotFiltered:
ldy actMB+ACTI_PLAYER
bpl ULO_NoWaterDamage
jsr ULO_DoToxinDamage
ULO_NoWaterDamage:
lda actHp+ACTI_PLAYER
beq ULO_PlayerDead
lda actYH+ACTI_PLAYER ;Kill player actor if fallen outside level
cmp limitD ;or run out of battery
bcc ULO_NotOutside
bne ULO_Outside
ULO_NotOutside: lda battery
ora battery+1
bne ULO_CheckPickupIndex
ULO_Outside: jsr DestroyActorNoSource
txa
sta actSX,x ;Zero speed to prevent unnecessary scrolling
sta actSY,x
ULO_PlayerDead: rts
ULO_CheckPickupIndex: ;Check if player is colliding with an item
ldy #ACTI_FIRSTITEM ;If was at an item last frame, continue search from that
ULO_CheckPickupLoop:
lda actT,y ;There may be other actors such as explosions in
cmp #ACT_ITEM ;item indices during bossfights, so make sure
bne ULO_CPNoItem ;it's actually an item
jsr CheckActorCollision
bcs ULO_HasItem
ULO_CPNoItem: iny
cpy #ACTI_LASTITEM+1
bcc ULO_CPNoItemNoWrap
ldy #ACTI_FIRSTITEM
ULO_CPNoItemNoWrap:
cpy ULO_CheckPickupIndex+1
bne ULO_CheckPickupLoop
lda displayedItemName ;If no items, clear existing item name
beq ULO_CheckObject ;text
jsr ClearPanelText
bcs ULO_CheckObject ;C=1 when returning
ULO_HasItem: sty ULO_CheckPickupIndex+1
lda textTime ;Make sure to not overwrite other game
bne ULO_SkipItemName ;messages
lda actF1,y
cmp displayedItemName ;Do not reprint same item name
beq ULO_SkipItemName
pha
jsr GetItemName
ldy #$00
jsr PrintPanelText
pla
sta displayedItemName
ULO_SkipItemName:
lda actF1+ACTI_PLAYER
cmp #FR_DUCK
bne ULO_CheckObject
lda actCtrl+ACTI_PLAYER ;Check pickup when starting to duck
and #JOY_DOWN
beq ULO_CheckObject
lda actPrevCtrl+ACTI_PLAYER
and #JOY_DOWN
bne ULO_CheckObject
ldy ULO_CheckPickupIndex+1
jsr TryPickup
ULO_CheckObject:ldy actYH+ACTI_PLAYER ;Rescan objects whenever player block position changes
lda actYL+ACTI_PLAYER ;If player stands on the upper half of a block
cmp #$81 ;check 1 block above
bcs ULO_CONotAtTop