| Chase H.Q. | Routines |
| Prev: A623 | Up: Map | Next: A7E7 |
|
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.
|
||||||||
| 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 |