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