Prev: A623 Up: Map Next: A7E7
A637: Perp car behaviour
This gets called whenever the perp is within sight of the hero car. It moves the perp to avoid other vehicles etc.
IX[7], the hit timer, picks one of three paths on entry. Positive means the perp has just been hit: apply the crash penalty, add the bonus and set the timer to $FC. Negative means the post-hit cooldown is still running: count it up towards zero and return, ignoring input. Zero is the normal frame.
A normal frame first walks the five non-perp slots for an active vehicle in the perp's lane and within range, which forces a random lane change. It then picks a lane by a random +/-1 walk biased by the road width, clamps that to the spawn lane bounds, slides the horizontal position toward it and finally scales the perp's approach speed by the distance remaining.
Exit if we've caught the perp.
Input
IX Address of hazard[0] (the perp)
perp_behaviour A637 LD A,($A230) Return if perp_caught_phase > 0
A63A AND A
A63B RET NZ
Start the chase if required (enables flashing lights, smash bar, sirens, etc.)
A63C LD A,($A22E) Call start_chase if sighted_flag is zero
A63F AND A
A640 CALL Z,start_chase
IX[7] is the hit timer. pb_set_delay sets it to $FC after a smash and the INC below counts it back up, so the four frames it takes to reach zero are the perp's post-hit cooldown, during which the rest of this routine is skipped.
A643 LD A,(IX+$07) Read IX[7] e.g. $A18F -- the hit timer
A646 AND A Set flags
A647 JR Z,pb_is_zero Jump if zero -- cooldown finished
A649 JP P,pb_set_delay Jump to set delay if positive
Otherwise A is negative.
This line gets hit 4 times when we smash into the perp's car - matching the $FC value it's reset to.
A64C INC (IX+$07) IX[7]++
A64F RET NZ Return if non-zero
IX[7] must be zero to arrive here. We now iterate over all non-perp hazards.
pb_is_zero A650 PUSH IY Preserve IY
A652 LD C,(IX+$01) Read perp's distance (buffer offset) into C
A655 LD B,$05 5 iterations
A657 LD IY,$A19C Point IY at hazards[1]
A65B LD DE,$0014 Stride of hazards is 20 bytes
pb_find_unused_hazard_loop A65E RLC (IY+$00) If the hazard is active then jump to pb_ensure_vehicle
A662 JR C,pb_ensure_vehicle
pb_find_unused_hazard_continue A664 ADD IY,DE Move to next hazard
A666 DJNZ pb_find_unused_hazard_loop Loop while B
A668 POP IY Restore IY
A66A JR pb_check_changing_lane_flag Jump to pb_check_changing_lane_flag
pb_ensure_vehicle A66C LD A,(IY+$0F) Read IY[15]. It's $80 for vehicles, 0+ for hazards or $FF if unused
A66F RLA If the top bit is clear then it's not a vehicle, continue to next hazard
A670 JP NC,pb_find_unused_hazard_continue
Calculate distance between current hazard and the perp.
A673 LD A,(IY+$01) A = IY[1] - <perp distance>
A676 SUB C
A677 JP NC,pb_a67e Jump to pb_a67e if car/hazard further than perp
Otherwise car is behind perp...
A67A ADD A,$02 A += 2 -- move it two lanes away?
A67C JR pb_a680 Jump to pb_a680
pb_a67e A67E SUB $03 A -= 3
pb_a680 A680 JR NC,pb_find_unused_hazard_continue Jump if (out of range?) - continue to next hazard
A682 LD A,(IY+$11) A = IY[17] -- lane
A685 CP (IX+$12) Compare to IX[18] -- compare to perp's lane
A688 JP NZ,pb_find_unused_hazard_continue Jump if not equal - continue to next hazard
So the lanes match.
A68B POP IY Restore IY pushed at pb_is_zero
A68D JR pb_random_move_left_or_right Jump to pb_random_move_left_or_right
pb_check_changing_lane_flag A68F LD A,$00 A = <self modified> -- load "changing lane" flag that appears to be set to 1 when the perp changes lane
A691 AND A Jump to pb_check_lane if non-zero
A692 JR NZ,pb_check_lane
Not mid lane-change, so a new one may be started -- but only once the perp is within 7 slots of the player.
A694 LD A,(IX+$01) A = perp's distance (IX+1)
A697 CP $07 Jump to pb_check_lane if A >= 7
A699 JR NC,pb_check_lane
Delay between perp lane changes: lane_change_timer counts down; on zero it resets to perp_lane_change_base + (rng() & 31).
In-place decrementing counter.
A69B LD A,$14 A = <self modified> - 1 -- Self modified below
A69D DEC A
A69E JR NZ,pb_update_counter Jump to pb_update_counter if non-zero
When it hits zero we pick a random number...
A6A0 INC C C incremented but immediately overwritten by rng() below -- no effect
A6A1 CALL rng C = rng() & 31
A6A4 AND $1F
A6A6 LD C,A
Timer hit zero: reload it from perp_lane_change_base + C
A6A7 LD A,($5D1B) A = perp_lane_change_base + C
A6AA ADD A,C
pb_update_counter A6AB LD ($A69C),A Self modify 'LD A,x' at A69B (above) to load A
This smells like it's detecting position and turning that into lanes. The values are like those used by get_spawn_lanes.
A6AE LD HL,($A26C) HL = road_pos - 164
A6B1 LD DE,$00A4
A6B4 SBC HL,DE
A6B6 LD BC,$0404 BC = $0404
A6B9 JR C,pb_a6cf Jump to pb_a6cf if HL < 164
A6BB LD E,$46 DE = $0046
A6BD DEC B BC = $0304
A6BE SBC HL,DE HL -= DE -- includes previous
A6C0 JR C,pb_a6cf Jump to pb_a6cf if HL < 70
A6C2 DEC B BC = $0203
A6C3 DEC C
A6C4 SBC HL,DE HL -= DE -- includes previous
A6C6 JR C,pb_a6cf Jump to pb_a6cf if HL < 70
A6C8 DEC B BC = $0102
A6C9 DEC C
A6CA SBC HL,DE HL -= DE -- includes previous
A6CC JR C,pb_a6cf Jump to pb_a6cf if HL < 70
A6CE DEC C BC = $0101
pb_a6cf A6CF LD A,(IX+$12) Read current_lane
A6D2 CP C Jump to pb_random_move_left_or_right if A == C
A6D3 JR Z,pb_random_move_left_or_right
A6D5 CP B Jump to pb_check_lane if A != B
A6D6 JR NZ,pb_check_lane
pb_random_move_left_or_right A6D8 LD C,(IX+$12) Read current_lane
A6DB CALL rng Generate a random byte
A6DE INC C Increment current_lane
A6DF AND A Jump if random value is positive
A6E0 JP P,pb_clamping
A6E3 DEC C Otherwise decrement current_lane (twice to counter earlier increment)
A6E4 DEC C
pb_clamping A6E5 LD A,C Copy current_lane
A6E6 AND A Set flags
A6E7 LD C,$02 Delta = 2
A6E9 JP Z,pb_change_lane_by_delta Jump if current_lane is zero -- new current_lane will be 2
A6EC CP $05 Jump if A < 5 -- lane is reasonable?
A6EE JR C,pb_set_current_lane
Arrive here if the updated current_lane is >= 5.
A6F0 LD C,$FE Delta = -2 -- new current_lane will be 3+
pb_change_lane_by_delta A6F2 ADD A,C Change A by delta
pb_set_current_lane A6F3 LD (IX+$12),A Update current_lane
pb_check_lane A6F6 LD C,(IX+$01) Read buffer offset/distance
A6F9 CALL get_spawn_lanes Call get_spawn_lanes
A6FC LD A,(IX+$12) Read current_lane from IX[18]
A6FF CP B Jump to pb_min_lane_set/pb_min_lane_set if current_lane >= min_lane
A700 JR NC,pb_min_lane_set
Otherwise the (perp?) needs to move right to stay on the road.
A702 ADD A,$02 Move right by two lanes: stepping two lands the perp inside the legal band rather than on its boundary, where the next random walk could step straight back out
A704 LD (IX+$12),A Update current_lane
pb_min_lane_set A707 CP C Jump to pb_reread_current_lane/pb_reread_current_lane if current_lane <= max_lane
A708 JR Z,pb_reread_current_lane
A70A JR C,pb_reread_current_lane
Otherwise the (perp?) needs to move left to stay on the road.
A70C SUB $02 Move left by two lanes
A70E LD (IX+$12),A Update current_lane
current_lane is 1/2/3/4
pb_reread_current_lane A711 LD A,(IX+$12) Re-read current_lane [not convinced this is required]
A714 LD HL,$A7E6 Load address of hazard_pos_speed[current_lane]
A717 ADD A,L
A718 LD L,A
A719 LD A,(IX+$05) Compare horizontal position IX[5] with table value
A71C CP (HL)
C is the changing-lane flag: 1 while the perp is still moving toward its target lane, 0 once it has arrived. The position is bumped by +/-10 a frame until it does.
A71D LD C,$01 Set flag indicating we're changing lane
pb_check_low A71F JR Z,pb_set_lane_from_table_2 Jump with C==1 if horizontal position <= table value Carry means horizontal position < table value, i.e. we're left of target; A72E checks moving right
A721 JR C,pb_check_high
A723 SUB $0A Jump with C==1 if horizontal position - 10 carried -- at lower limit?
A725 JR C,pb_set_lane_from_table_1
A727 CP (HL) Compare with table value (again)
A728 JR NC,pb_set_horz_pos Jump with C==1 if (horizontal position - 10) was >= table value
Redundant code path; jump to pb_set_lane_from_table_2 instead.
pb_set_lane_from_table_1 A72A DEC C Decrement 1 to 0 so we're not changing lane
A72B LD A,(HL) Read table value
A72C JR pb_set_horz_pos Jump to pb_set_horz_pos
pb_check_high A72E ADD A,$0A Jump with C==1 if horizontal position + 10 carried -- at upper limit?
A730 JR C,pb_set_lane_from_table_2
A732 CP (HL) Compare with table value
A733 JR C,pb_set_horz_pos Jump with C==1 if (horizontal position + 10) was < table value
pb_set_lane_from_table_2 A735 DEC C Decrement 1 to 0 so we're not changing lane
A736 LD A,(HL) Read table value
pb_set_horz_pos A737 LD (IX+$05),A Set horizontal position
A73A LD A,C Self modify 'LD A' @ pb_check_changing_lane_flag to load C -- changing lane flag will be 0 or 1
A73B LD ($A690),A
A73E LD A,$00 A = <self modified> -- delay counter
A740 AND A Set flags
A741 LD DE,$001E DE = 30 -- the step added per unit of distance still to close
A744 LD HL,$00E6 HL = 230 -- the perp's base approach rate
A747 JR NZ,pb_bypass Jump pb_bypass if non-zero -- bypasses all the delay stuff
Countdown+rng stuff again... as at A69B
In-place decrementing counter.
A749 LD A,$14 A = <self modified> - 1 -- Self modified below
A74B DEC A
A74C LD ($A74A),A Self modify 'LD A,x' @ A749 (above) to load A
A74F JR NZ,pb_a776 Jump to pb_a776 if non-zero
When it hits zero we pick a random number...
A751 PUSH HL Preserve the accumulator, still $00E6 here
A752 CALL rng Generate a random byte
A755 POP HL Restore HL
A756 AND $0F A = perp_approach_base + (random value & 15), the inner approach timer reloaded from the per-stage base delay at perp_approach_base plus a random 0 to 15
A758 LD C,A
A759 LD A,($5D1C)
A75C ADD A,C
A75D LD ($A74A),A Self modify 'LD A,x' @ A749 (above) to load A
A760 LD A,$0A A = 10 -- reset the delay loop
Count down outer delay loop.
pb_bypass A762 DEC A A--
A763 LD ($A73F),A Self modify 'LD A,x' @ A73E (above) to load A
A766 JR Z,pb_a776 Jump to pb_a776 if zero
sampled IX = $A188 (hazards)
A768 LD A,(IX+$01) A = IX[1] -- load hazard_1 distance byte / buffer offset
A76B CP $0D Jump if A >= 13 -- too far
A76D JR NC,pb_a776
Distance to perp is 12 or less.
HL += (13 - A) * DE HL is 230, DE is 30
The nearer the perp is, the more of these steps run, so its approach rate climbs as the gap closes.
A76F CPL B = (13 - A) -- iterations
A770 ADD A,$0E
A772 LD B,A
pb_mult_loop A773 ADD HL,DE HL += 30
A774 DJNZ pb_mult_loop Loop while B > 0
pb_a776 A776 LD A,(IX+$01) A = IX[1] - 6 -- load hazard_1 distance byte / buffer offset again
A779 SUB $06
A77B JR NC,pb_store_exit Jump to pb_store_exit if A >= 6
Distance to perp is 5 or less.
A77D ADD A,$05 Put back most of what we just subtracted
A77F ADD A,A Multiply by 8
A780 ADD A,A
A781 ADD A,A
A is then discarded - the three shifts are dead. Only the extra step below has any effect, giving the perp one more increment when it is very close.
A782 ADD HL,DE HL += DE
pb_store_exit A783 LD (IX+$0D),L wordat(IX + 13) = HL -- store the perp's approach rate, whole units in the high byte and fractional in the low
A786 LD (IX+$0E),H
A789 RET Return
pb_set_delay A78A LD (IX+$07),$FC IX[7] = $FC -- four frames of post-hit cooldown, read back at the top of perp_behaviour. Raise it and the perp gets away
A78E CP $03 Jump if A < 3 -- preserve A
A790 PUSH AF
A791 JR C,pb_check_boost
A793 SUB $03 A -= 3 -- 0..
pb_check_boost A795 EX AF,AF' Bank
A796 LD A,($A24E) Load turbo boost time remaining (60..0)
A799 AND A Set flags
A79A LD A,$C8 200 when not boosting
A79C JR Z,pb_not_boosting Jump if not boosting
A79E LD A,$E6 230 when boosting
pb_not_boosting A7A0 EX AF,AF' Bank the chosen value (200 or 230) for later; A now holds whatever was banked before this exchange, not the chosen value
A7A1 CALL scenery_hit Call scenery_hit
A7A4 LD HL,($B32F) Read HL from 'LD BC,x' @ B32E -- a value set when crashed
A7A7 LD DE,$0028 Add 40 to it
A7AA ADD HL,DE
A7AB LD ($B32F),HL Self modify 'LD BC,x' @ B32E
A7AE LD D,$00 Zero bonus high digit
A7B0 POP AF Restore A which holds IX[7] and flags from earlier
A7B1 LD HL,$B4F0 Put a call to 'smash' on the stack
A7B4 PUSH HL
A7B5 JR NC,pb_a7be Jump if no carry
A7B7 CP $02 Jump if A == 2
A7B9 JR Z,pb_a7be
A7BB PUSH HL Put another call to smash on the stack
Break?
A7BC LD D,$04 Set bonus high digit to 4
pb_a7be A7BE LD A,($8007) Add wanted_stage_number to D (could be 0 or 4)
A7C1 ADD A,D
A7C2 LD D,A
A7C3 LD E,$00 Zero bonus middle digit(s)
A7C5 LD A,($8006) Jump if retry_count is zero
A7C8 AND A
A7C9 JR Z,pb_retry_was_zero
A7CB LD A,D High digit(s) of bonus
A7CC LD D,E Set top two digits of bonus
A7CD RLCA Move digit into position
A7CE RLCA
A7CF RLCA
A7D0 RLCA
A7D1 LD E,A Set middle digits of bonus
pb_retry_was_zero A7D2 XOR A Clear low digits of bonus
A7D3 CALL add_bonus Call add_bonus
A7D6 LD A,$05 Set delay to 5 turns?
A7D8 LD ($A73F),A Self modify 'LD A' @ A73E to load A -- delay counter
A7DB LD HL,$98D6 Point HL at smash_chatter ("BEAR DOWN" / "OH MAN" / etc.)
A7DE CALL start_chatter Call start_chatter (priority 5)
A7E1 LD BC,$0301 Effect 3 (car crash), Priority 1
A7E4 JP start_sfx Exit via start_sfx
Prev: A623 Up: Map Next: A7E7