1;; ======================================================================== ;;
2;;  Bad guy thinker routines for saucer                                     ;;
3;;  I broke these out into a separate file since they're so involved.       ;;
4;; ======================================================================== ;;
5
6;; ======================================================================== ;;
7;;  SCTBL -- Saucer control table.                                          ;;
8;;                                                                          ;;
9;;  Saucer Control Table Format                                             ;;
10;;                                                                          ;;
11;;      Top level is a series of 4-word records.  Each record contains      ;;
12;;                                                                          ;;
13;;       -- Magnitude of random component for bullet fire delay.            ;;
14;;       -- Minimum delay between bullet fire                               ;;
15;;       -- Pointer to Delta-X table                                        ;;
16;;       -- Pointer to Delta-Y table                                        ;;
17;;                                                                          ;;
18;;      Delta-X table consists of a 3-word header followed by a series of   ;;
19;;      2-word entries.                                                     ;;
20;;                                                                          ;;
21;;      DXT header:                                                         ;;
22;;       -- Minimum X coordinate, in 8Q8                                    ;;
23;;       -- Maximum X coordinate, in 8Q8                                    ;;
24;;       -- Number of entries in table                                      ;;
25;;                                                                          ;;
26;;      DXT entry:                                                          ;;
27;;       -- Rightward velocity in lower byte of first word (biased 2Q6)     ;;
28;;       -- Number of ticks in upper byte of first word.                    ;;
29;;       -- Total delta-X for this motion, in 8Q8                           ;;
30;;                                                                          ;;
31;;      Delta-Y table format is identical to Delta-X table.                 ;;
32;;                                                                          ;;
33;;  The saucer control tables are compiled by SCTC.                         ;;
34;; ======================================================================== ;;
35
36;; ======================================================================== ;;
37;;  Saucer Control State Machine                                            ;;
38;;                                                                          ;;
39;;  Saucers have at their heart a three-state control machine.  The three   ;;
40;;  states are "Move left/right", "Fire", and "Move up/down".  The          ;;
41;;  following diagram indicates the allowed state transitions:              ;;
42;;                                                                          ;;
43;;                                                                          ;;
44;;                            +--------------+                              ;;
45;;                            |              |                              ;;
46;;                            |   +------+   |                              ;;
47;;        +----------+        +-->|      |---+           +----------+       ;;
48;;        |          |            | FIRE |               |          |       ;;
49;;    +-->|          |----------->|      |-------------->|          |---+   ;;
50;;    |   | MOVE L/R |            +------+               | MOVE U/D |   |   ;;
51;;    |   |          |                                   |          |   |   ;;
52;;    |   |          |---------------------------------->|          |   |   ;;
53;;    |   +----------+                                   +----------+   |   ;;
54;;    |                                                                 |   ;;
55;;    |                                                                 |   ;;
56;;    +-----------------------------------------------------------------+   ;;
57;;                                                                          ;;
58;;  Saucers start out in the MOVE L/R state.  They exit from this state     ;;
59;;  into either FIRE or MOVE U/D based on whether the random delay the      ;;
60;;  MOB picked is longer than the delay specified for the selected L/R      ;;
61;;  move.                                                                   ;;
62;;                                                                          ;;
63;;  If the saucer enters the FIRE state, it records the total delay         ;;
64;;  req'd for the L/R sweep in the second state byte.  The firing code      ;;
65;;  then fires after random delays, until the total delay is consumed.      ;;
66;;                                                                          ;;
67;;  The MOVE L/R and MOVE U/D code picks motions from the Delta-X and       ;;
68;;  Delta-Y tables.  These tables contain various motion parameters, in     ;;
69;;  the form of a velocity, tick count, and total delta.  The total         ;;
70;;  delta is used to calculate the saucer's endpoint for a given motion,    ;;
71;;  and thus ultimately ensure that the saucer stays within its bounding    ;;
72;;  box.  The velocity and tick count are used directly to control the      ;;
73;;  saucer motion.                                                          ;;
74;; ======================================================================== ;;
75
76;; ======================================================================== ;;
77;;  SAUC_PICK_D  -- Given a current position and DXT/DYT pointer, pick a    ;;
78;;                  a slot in the DXT/DYT at random.                        ;;
79;;                                                                          ;;
80;;  INPUTS                                                                  ;;
81;;      R1  Slot #                                                          ;;
82;;      R2  DXT/DYT pointer.                                                ;;
83;;      R3  Pointer to BGMPTBL slot                                         ;;
84;;      R4  Current X/Y coordinate, in 8Q8 format                           ;;
85;;      R5  Return address.                                                 ;;
86;;                                                                          ;;
87;;  OUTPUTS                                                                 ;;
88;;      R0  Velocity                                                        ;;
89;;      R1  unmodified                                                      ;;
90;;      R2  Tick count                                                      ;;
91;;      R3  unmodified                                                      ;;
92;;      R4  unmodified                                                      ;;
93;;      R5  trashed.                                                        ;;
94;;                                                                          ;;
95;; ======================================================================== ;;
96SAUC_PICK_D PROC
97            PSHR    R5              ; Save return addr.
98            PSHR    R3
99            MOVR    R2,     R3      ; Save DXT/DYT pointer
100
101            ADDI    #2,     R2
102            MVI@    R2,     R2      ; Get # of entries
103
104            CALL    BGBRAND         ; pick a random starting point
105            SLL     R0,     1       ;
106
107            MOVR    R3,     R2
108@@try_next:
109            MOVR    R2,     R3      ; \
110            ADDI    #4,     R3      ;  |-- Index to selected entry's 'delta'
111            ADDR    R0,     R3      ; /    field.
112
113            MOVR    R4,     R5      ; Copy X/Y coord to temp
114            ADD@    R3,     R5      ; Try going right/down
115
116            INCR    R2              ;  |-- Is it too big?
117            BC      @@not_ok_pos    ; (overflowed!)
118            CMP@    R2,     R5      ; /
119            BNC     @@ok_pos        ; No:  This entry is ok in the +ve dir.
120@@not_ok_pos
121
122            MOVR    R4,     R5
123            SUB@    R3,     R5
124
125            DECR    R2              ; \___ Is it too small?
126            BNC     @@not_ok_neg    ; (underflowed!)
127            CMP@    R2,     R5      ; /
128            BC      @@ok_neg        ; No:  This entry is ok in the -ve dir.
129@@not_ok_neg:
130
131            SUBI    #2,     R0      ; Neither direction ok, so go to prev entry
132            BPL     @@try_next
133
134            ADDI    #2,     R2      ; \
135            MVI@    R2,     R0      ;  |   Wrap around if go prior to 1st
136            SUBI    #2,     R2      ;  |-- entry in DXT/DYT
137            DECR    R0              ;  |
138            SLL     R0              ; /
139
140            B       @@try_next
141
142@@ok_pos:
143            DECR    R3              ; point to vel/tick field
144            MVI@    R3,     R0      ; Read vel/tick
145            B       @@unpack_vel_tick
146
147@@ok_neg:
148            DECR    R3              ; point to vel/tick field
149            MVI@    R3,     R0      ; Read vel/tick
150            XORI    #$FF,   R0      ; \__ negate velocity field
151;           INCR    R0              ; /   (no incr for 1s compl)
152
153@@unpack_vel_tick:
154            MOVR    R0,     R2      ; copy packed field over
155            ANDI    #$FF,   R0      ; keep only velocity in R0
156            XORR    R0,     R2      ; clear velocity from R2
157            SWAP    R2              ; put tick in lower half of R2
158
159            XORI    #$80,   R0      ; \
160            SUBI    #$80,   R0      ;  |- Sign extend and scale velocity
161            SLL     R0,     2       ; /   from 2Q6 to 8Q8
162            SWAP    R0              ; swapped 1s compl.
163
164            PULR    R3              ; restore R3
165            PULR    PC              ; return
166
167            ENDP
168
169;; ======================================================================== ;;
170;;  BGT_SAUC_LR -- Left/Right motion state for Saucer.                      ;;
171;; ======================================================================== ;;
172BGT_SAUC_LR PROC
173
174            SLL     R1,     1       ; double slot number for x/y indexing
175
176            MVI@    R3,     R2      ; Get SCP # from status byte 1.
177            SLL     R2,     2       ; \
178            ADDI    #SCTBL, R2      ;  |-- Index into SCTBL and remember it.
179            MVO     R2,     BGTTMP  ; /
180
181            ADDI    #2,     R2
182            MVI@    R2,     R2      ; Get DXT pointer into R2.
183
184            ADDI    #SPXY1, R1      ;\
185            MVI@    R1,     R0      ; |-- Get current X coordinate into R4
186            SWAP    R0
187            MOVR    R0,     R4
188            SUBI    #SPXY1, R1      ;/
189
190            CALL    SAUC_PICK_D     ; pick a velocity and tick-count
191
192            INCR    R3
193            MVO@    R2,     R3      ; record tick count in status byte 2
194
195            MOVR    R1,     R4      ; \   Set the velocity of this saucer
196            ADDI    #SPXYV, R4      ;  |- to the selected horiz velocity.
197            MVO@    R0,     R4      ; /
198            CLRR    R0              ; \__ no vertical velocity
199            MVO@    R0,     R4      ; /
200
201            SLR     R1,     1       ; restore slot number
202
203            MVI     BGTTMP, R4      ; \
204            MVI@    R4,     R2      ;  |-- calculate random portion of delay
205            CALL    BGBRAND         ; /
206            ADD@    R4,     R0      ; Add fixed portion of delay
207
208            MOVR    R0,     R2      ; \
209            SUB@    R3,     R2      ;  |-- Is this delay >= than motion
210            BGE     @@next_is_ud    ; /    duration?
211
212            ; If not (eg. result was negative), then next state is "FIRE"
213            COMR    R2              ; \__ Store updated "remaining ticks",
214            MVO@    R2,     R3      ; /   compensating for off-by-1 tick counts
215
216            ; Decide whether we're running firing program 0 or 1 based on
217            ; whether this is a spinner or a regular saucer.
218            MVII    #SPAT1, R2          ; \
219            ADDR    R1,     R2          ;  |- look up the saucer's attribute
220            MVI@    R2,     R2          ; /
221
222            ; Set firing program to #1 for spinners, #0 otherwise
223            ; NOTE: Change this if we get more than 2 spinners to check a
224            ; range.  This will force spinners to be consecutive in SPATBL.
225
226            CMPI    #SPATBL.wht0, R2    ; Is it the spinner?
227            BLT     @@set_next_state    ;
228            CMPI    #SPATBL.wht3, R2    ; Is it the spinner?
229            MVII    #BGI.sauc_f0, R2    ; Assume not a spinner
230            BGT     @@set_next_state
231@@spinner:  MVII    #BGI.sauc_f1, R2    ; Oh, it was a spinner
232
233@@set_next_state:
234            SUBI    #3,     R3      ; Rewind to program pointer
235            MVO@    R2,     R3      ; Set thinker to "FIRE" or "MOVE U/D"
236
237            ADDI    #2,     R3      ; Restore R3
238
239            B       BGT_RET
240
241@@next_is_ud:
242            ;; We picked a random delay that's longer than the delay for
243            ;; this left/right motion, so clamp to the L/R motion length
244            ;; and transition directly to the MOVE U/D state.
245            MVI@    R3,     R0      ; Get movement's duration as our delay
246            MVII    #BGI.sauc_ud,R2
247            B       @@set_next_state
248
249            ENDP
250
251;; ======================================================================== ;;
252;;  BGT_SAUC_UD -- Up/Down motion state for Saucer.                         ;;
253;; ======================================================================== ;;
254BGT_SAUC_UD PROC
255            SLL     R1,     1
256
257            MVI@    R3,     R2      ; Get SCP # from status byte 1.
258            SLL     R2,     2       ; \
259            ADDI    #SCTBL, R2      ;  |-- Index into SCTBL and remember it.
260            MVO     R2,     BGTTMP  ; /
261
262            ADDI    #3,     R2
263            MVI@    R2,     R2      ; Get DYT pointer into R2.
264
265            ADDI    #SPXY1+1,R1     ; \
266            MVI@    R1,      R0     ;  |-- Get current Y coordinate into R4
267            SWAP    R0
268            MOVR    R0,      R4
269            SUBI    #SPXY1+1,R1     ; /
270
271            CALL    SAUC_PICK_D     ; pick a velocity and tick-count
272
273            INCR    R3
274            MVO@    R2,     R3      ; record tick count in status byte 2
275
276            MOVR    R1,     R4      ; \
277            CLRR    R2              ;  |
278            ADDI    #SPXYV, R4      ;  |__ Set the velocity of this saucer
279            MVO@    R2,     R4      ;  |   to the selected vert velocity.
280            MVO@    R0,     R4      ; /
281
282            MVI@    R3,     R0      ; get tick count back into R0.
283
284            MVII    #BGI.sauc_lr,R2
285
286            SUBI    #3,     R3      ; Rewind to program pointer
287            MVO@    R2,     R3      ; Set thinker to "MOVE L/R"
288
289            ADDI    #2,     R3      ; Restore R3
290
291            SLR     R1,     1
292            B       BGT_RET
293
294            ENDP
295
296;; ======================================================================== ;;
297;;  BGT_SAUC_F1 -- FIRE state for White Spinner Saucer                      ;;
298;; ======================================================================== ;;
299BGT_SAUC_F1 PROC
300
301            MVII    #BGI.sauc_f1, R2
302            MVO     R2,     BGTTMP
303
304            ;; ------------------------------------------------------------ ;;
305            ;;  We only let spinners fire in the left half of the screen.   ;;
306            ;; ------------------------------------------------------------ ;;
307            MVI     SPINB,  R0
308            TSTR    R0
309            BNEQ    @@cd2
310
311            MVII    #SPXY1, R4      ; \
312            ADDR    R1,     R4      ;  |_ Get the X&Y coordinate of the bad guy
313            ADDR    R1,     R4      ;  |
314            SDBD                    ;  |
315            MVI@    R4,     R2      ; /
316            MOVR    R2,     R0
317            SWAP    R2              ; Put X in MSB
318            SUBI    #$1800, R2
319            BNC     @@cd2           ; Underflow?  Too far left
320            CMPR    R0,     R2      ; \__ ok to fire if X-24 > Y.
321@@cd2       MVII    #1,     R0      ; (default delay if not firing)
322            BNC     @@com_dly       ; /   (note unsigned comparisons.)
323
324            ; XXX: Add rule here to prevent firing if ship too far right.
325
326            MVI     CRATCNT,R2      ; \
327            CMPI    #3,     R2      ;  |- Don't fire if crater still onscreen
328            BGT     @@com_dly       ; /   (Allow it in the last several cards)
329
330            MVO     R0,     SPINB
331            CALL    BGFIRE.b
332@@common:
333            MVI@    R3,     R4      ; Get SCP # from status byte 1.
334            ADDR    R4,     R4      ; \
335            ADDR    R4,     R4      ;  |-- Index into SCTBL  (SCTBL + 4*SCP#)
336            ADDI    #SCTBL, R4      ; /
337
338            MVI@    R4,     R2      ; \___ calculate random portion of delay
339            CALL    BGBRAND         ; /
340            ADD@    R4,     R0      ; Add fixed portion of delay
341            INCR    R0              ; Compensate for "off-by-1" on delay counts
342@@com_dly:
343            MOVR    R0,     R2      ; \
344            INCR    R3              ;  |__ Is this delay >= than motion
345            SUB@    R3,     R2      ;  |   duration?
346            BGE     @@next_is_ud    ; /
347
348            ; If not (eg. result was negative), then next state is "FIRE"
349            COMR    R2              ; \__ Store updated "remaining ticks",
350            MVO@    R2,     R3      ; /   compensating for off-by-1 tick counts
351            MVI     BGTTMP, R2      ; (we saved proper ptr earlier.)
352
353@@set_next_state:
354            SUBI    #3,     R3      ; Rewind to program pointer
355            MVO@    R2,     R3      ; Set thinker to "FIRE" or "MOVE U/D"
356            ADDI    #2,     R3      ; Restore R3
357
358            B       BGT_RET
359
360@@next_is_ud:
361            ;; We picked a random delay that's longer than the delay for
362            ;; this left/right motion, so clamp to the L/R motion length
363            ;; and transition directly to the MOVE U/D state.
364            MVI@    R3,     R0      ; Get movement's duration as our delay
365            MVII    #BGI.sauc_ud,R2
366            B       @@set_next_state
367
368            ENDP
369
370;; ======================================================================== ;;
371;;  BGT_SAUC_F0 -- FIRE state for Pink/Blue Sacuers                         ;;
372;; ======================================================================== ;;
373BGT_SAUC_F0 PROC
374
375            MVII    #BGI.sauc_f0, R2
376            MVO     R2,     BGTTMP
377
378            MVII    #2,     R0      ; default delay if not firing
379@@no_fire   SET     BGT_SAUC_F1.com_dly
380
381            ;; ------------------------------------------------------------ ;;
382            ;;  We only let saucers fire in certain ranges of the screen,   ;;
383            ;;  in order to keep it 'fair'.  Those ranges are set up like   ;;
384            ;;  so, with X representing "no fire" areas:                    ;;
385            ;;                                                              ;;
386            ;;          ---------------X-XX--XXXXXXXXXXXXXXXXXXX            ;;
387            ;;                                                              ;;
388            ;;  The island of X-XX's represents the area to the right of    ;;
389            ;;  the moon buggy's vertical gun turret up through the tip of  ;;
390            ;;  of its horizontal turret, when the buggy is at its maximum  ;;
391            ;;  velocity.                                                   ;;
392            ;;                                                              ;;
393            ;;  We allow some bullets just in front of the buggy as a sort  ;;
394            ;;  of "bluffing" bullet fire.  We disallow unproductive fire   ;;
395            ;;  through the rest of the right hand of the screen since it   ;;
396            ;;  just wastes bullet sprites from group 2.                    ;;
397            ;; ------------------------------------------------------------ ;;
398            MVII    #SPXY1, R2      ; \
399            ADDR    R1,     R2      ;  |_ Get the X coordinate of the bad guy
400            ADDR    R1,     R2      ;  |
401            MVI@    R2,     R2      ; /
402            SWAP    R2
403            CMPI    #55*256,R2      ; \__ ok to fire in left part of screen
404            BNC     @@ok_fire       ; /   (note unsigned comparisons.)
405
406            CMPI    #60*256,R2      ; \
407            BNC     @@no_fire       ;  |_ tiny island to allow firing directly
408            CMPI    #62*256,R2      ;  |  above buggy's gun turret  :-)
409            BNC     @@ok_fire       ; /
410
411            ; If there's a crater directly beneath the saucer,
412            ; only fire 25% of the time
413            MVI     TICK,   R0
414            ANDI    #7,     R0
415            BEQ     @@skipnice
416
417            PSHR    R2
418            SWAP    R2
419            SLR     R2,     2
420            SLR     R2,     1
421            MOVR    R2,     R4
422            PULR    R2
423            ANDI    #$1F,   R4
424            ADDI    #GROW+20, R4
425            MVII    #GCARDx,R0
426            CMP@    R4,     R0
427            BNEQ    @@no_fire1
428            INCR    R4
429            CMP@    R4,     R0
430            BNEQ    @@no_fire1
431
432            ; Skip the next test with odds proportional to
433            ; (POINT + COURSE*4 + k) / 128
434@@skipnice: CALL    BGRAND
435            ANDI    #$7F,   R0
436            SLL     R0,     2
437            SLL     R0,     1
438            MOVR    R0,     R4
439            MVI     COURSE, R0
440            SLL     R0,     2
441            SLL     R0,     2
442            SLL     R0,     1
443            ADDI    #30,    R0
444            ADD     POINT,  R0
445            CMPR    R0,     R4
446            MVII    #2,     R0
447            BLT     @@ok_fire
448
449@@nofireovertank
450            CMPI    #81*256,R2      ; \
451            BC      @@no_fire       ;  |_ make sure we don't fire inside the
452            CMPI    #77*256,R2      ;  |  two no-fire islands.
453            BNC     @@no_fire       ; /
454
455@@ok_fire:  MVII    #BGT_SAUC_F1.common, R5
456            B       BGFIRE.a        ;
457
458@@no_fire1: MVII    #5,     R0      ; If over crater, wait 1/6th second
459            B       @@no_fire
460            ENDP
461
462;; ======================================================================== ;;
463;;  BGT_SAUC_EX -- Exit motion state for Saucer.                            ;;
464;;                                                                          ;;
465;;  When exiting, go in the direction that'll take us over the tank.  This  ;;
466;;  allows the tank to make one last pot shot at the saucer.  Note that     ;;
467;;  the test need only be approximate--if the saucer is near the tank       ;;
468;;  anyway, the player doesn't need the extra help so much.                 ;;
469;; ======================================================================== ;;
470BGT_SAUC_EX PROC
471
472            MOVR    R1,     R2      ; \
473            SLL     R2,     1       ;  |- index into SPXYP table
474            ADDI    #SPXYP, R2      ; /
475
476            MVI@    R2,     R0      ; \
477            SUBI    #4,     R0      ;  |_ Are we to the left or right of
478            ANDI    #$FF,   R0      ;  |  the tank?  (approximately)
479            CMP     TXLO,   R0      ; /
480
481@@got_xvel: MVII    #$00FE, R0      ; default to moving left.
482            ADCR    PC              ; Exit right if compared velocity >= 0
483            COMR    R0              ; $0200 is high speed to right.
484
485            ADDI    #SPXYV-SPXYP, R2
486
487            MVO@    R0,     R2      ; Store as new X velocity
488            INCR    R2
489            CLRR    R0
490            MVO@    R0,     R2      ; Store zero out Y velocity
491
492            MVII    #$FF,   R0
493            B       BGT_RET         ; Sleep a long time.
494
495            ENDP
496
497
498
499