nes-contra-us/src/bank0.asm

10422 lines
553 KiB
NASM

; Contra US Disassembly - v1.2
; https://github.com/vermiceli/nes-contra-us
; Bank 0 is used exclusively for enemy routines. Enemy routines are the logic
; controlling enemy behaviors, AI, movements, and attack patterns. Almost every
; enemy is coded in bank 0, but some enemies, usually those who appear in more
; than one level, are coded in bank 7.
.segment "BANK_0"
.include "constants.asm"
; import labels from bank 7
.import play_sound, init_APU_channels, load_bank_3_update_nametable_supertile
.import load_bank_3_update_nametable_tiles, load_palettes_color_to_cpu
.import get_bg_collision_far, get_cart_bg_collision, wall_core_routine_05
.import boss_defeated_routine, enemy_routine_init_explosion
.import mortar_shot_routine_03, set_enemy_delay_adv_routine
.import advance_enemy_routine, roller_routine_04, shared_enemy_routine_03
.import enemy_routine_explosion, enemy_routine_remove_enemy
.import shared_enemy_routine_clear_sprite, set_enemy_routine_to_a
.import update_enemy_pos, update_enemy_x_pos_rem_off_screen
.import set_enemy_y_vel_rem_off_screen, set_outdoor_weapon_item_vel
.import add_scroll_to_enemy_pos, set_enemy_velocity_to_0
.import set_enemy_y_velocity_to_0, set_enemy_x_velocity_to_0
.import reverse_enemy_x_direction, set_destroyed_enemy_routine
.import destroy_all_enemies, clear_supertile_bg_collision
.import set_supertile_bg_collision, set_supertile_bg_collisions
.import create_explosion_89, create_two_explosion_89, create_enemy_for_explosion
.import level_boss_defeated, set_delay_remove_enemy
.import disable_bullet_enemy_collision, disable_enemy_collision
.import enable_enemy_player_collision_check, enable_bullet_enemy_collision
.import enable_enemy_collision, add_a_to_enemy_y_pos, add_a_to_enemy_x_pos
.import set_08_09_to_enemy_pos, add_with_enemy_pos, add_10_to_enemy_y_fract_vel
.import add_a_to_enemy_y_fract_vel, generate_enemy_a, generate_enemy_at_pos
.import add_4_to_enemy_y_pos, add_a_with_vert_scroll_to_enemy_y_pos
.import update_nametable_tiles_set_delay, draw_enemy_supertile_a_set_delay
.import draw_enemy_supertile_a, update_2_enemy_supertiles
.import update_enemy_nametable_tiles_no_palette, update_enemy_nametable_tiles
.import check_enemy_collision_solid_bg, init_vars_get_enemy_bg_collision
.import add_y_to_y_pos_get_bg_collision, add_a_y_to_enemy_pos_get_bg_collision
.import set_flying_capsule_y_vel, set_flying_capsule_x_vel
.import red_turret_find_target_player, player_enemy_x_dist
.import find_far_segment_for_x_pos, find_far_segment_for_a
.import set_enemy_falling_arc_pos, set_weapon_item_indoor_velocity
.import find_next_enemy_slot, clear_sprite_clear_enemy_pt_3
.import clear_enemy_custom_vars, initialize_enemy, aim_and_create_enemy_bullet
.import bullet_generation, create_enemy_bullet_angle_a, set_bullet_velocities
.import aim_var_1_for_quadrant_aim_dir_01, aim_var_1_for_quadrant_aim_dir_00
.import get_rotate_00, get_rotate_01
.import get_rotate_dir, dragon_arm_orb_seek_should_move
.import get_quadrant_aim_dir_for_player, remove_enemy
; export labels for bank 7
; level 1 enemies
.export bomb_turret_routine_ptr_tbl
.export boss_wall_plated_door_routine_ptr_tbl
.export exploding_bridge_routine_ptr_tbl
; level 2 and 4 enemies
.export boss_eye_routine_ptr_tbl
.export roller_routine_ptr_tbl
.export grenade_routine_ptr_tbl
.export wall_turret_routine_ptr_tbl
.export wall_core_routine_ptr_tbl
.export indoor_soldier_routine_ptr_tbl
.export jumping_soldier_routine_ptr_tbl
.export grenade_launcher_routine_ptr_tbl
.export four_soldiers_routine_ptr_tbl
.export indoor_soldier_gen_routine_ptr_tbl
.export indoor_roller_gen_routine_ptr_tbl
.export eye_projectile_routine_ptr_tbl
.export boss_gemini_routine_ptr_tbl
.export spinning_bubbles_routine_ptr_tbl
.export blue_soldier_routine_ptr_tbl
.export red_soldier_routine_ptr_tbl
.export red_blue_soldier_gen_routine_ptr_tbl
; level 3 enemies
.export floating_rock_routine_ptr_tbl
.export moving_flame_routine_ptr_tbl
.export rock_cave_routine_ptr_tbl
.export falling_rock_routine_ptr_tbl
.export boss_mouth_routine_ptr_tbl
.export dragon_arm_orb_routine_ptr_tbl
; level 5 enemies
.export ice_grenade_generator_routine_ptr_tbl
.export ice_grenade_routine_ptr_tbl
.export tank_routine_ptr_tbl
.export ice_separator_routine_ptr_tbl
.export boss_ufo_routine_ptr_tbl
.export mini_ufo_routine_ptr_tbl
.export boss_ufo_bomb_routine_ptr_tbl
; level 6 enemies
.export fire_beam_down_routine_ptr_tbl
.export fire_beam_left_routine_ptr_tbl
.export fire_beam_right_routine_ptr_tbl
.export boss_giant_soldier_routine_ptr_tbl
.export boss_giant_projectile_routine_ptr_tbl
; level 7 enemies
.export claw_routine_ptr_tbl
.export rising_spiked_wall_routine_ptr_tbl
.export spiked_wall_routine_ptr_tbl
.export mine_cart_generator_routine_ptr_tbl
.export moving_cart_routine_ptr_tbl
.export immobile_cart_generator_routine_ptr_tbl
.export boss_door_routine_ptr_tbl
.export boss_mortar_routine_ptr_tbl
.export boss_soldier_generator_routine_ptr_tbl
; level 8 enemies
.export alien_guardian_routine_ptr_tbl
.export alien_fetus_routine_ptr_tbl
.export alien_mouth_routine_ptr_tbl
.export white_blob_routine_ptr_tbl
.export alien_spider_routine_ptr_tbl
.export alien_spider_spawn_routine_ptr_tbl
.export boss_heart_routine_ptr_tbl
; enemies that exist on multiple levels
.export enemy_bullet_routine_ptr_tbl
.export rotating_gun_routine_ptr_tbl
.export red_turret_routine_ptr_tbl
.export sniper_routine_ptr_tbl
.export soldier_routine_ptr_tbl
.export weapon_box_routine_ptr_tbl
.export weapon_item_routine_ptr_tbl
.export flying_capsule_routine_ptr_tbl
; Every PRG ROM bank starts with a single byte specifying which number it is
.byte $00 ; The PRG ROM bank number (0)
; table of pointers for weapon item routines ($03 * $02 = $06 bytes)
weapon_item_routine_ptr_tbl:
.addr weapon_item_routine_00 ; CPU address $8007
.addr weapon_item_routine_01 ; CPU address $8068
.addr weapon_item_routine_02 ; CPU address $8100
; weapon item - pointer 1
; sets collision code, velocity
; weapon items are created after flying capsule, pill box sensors, or red soldiers (in indoor/base levels) are destroyed
weapon_item_routine_00:
lda #$80 ; a = #$80
sta ENEMY_STATE_WIDTH,x ; mark weapon item so bullets travel through it
lda #$22 ; a = #$22
sta ENEMY_SCORE_COLLISION,x ; score code #$02, collision type #$02
.ifdef Probotector
lda #$00 ; use sprite code palette
.else
lda #$05 ; set sprite palette #$01, bit 2 specifies sprite code ROM data override
.endif
sta ENEMY_SPRITE_ATTR,x ; set weapon item sprite palette to palette
lda LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor
beq @set_velocity_outdoor ; branch for outdoor level
lda ENEMY_Y_POS,x ; indoor level, load y position on screen
sta ENEMY_VAR_1,x ; set ENEMY_VAR_1 to initial Y position
jsr set_weapon_item_indoor_velocity ; sets X and Y velocities for indoor level based on X position
lda #$80
sta ENEMY_VAR_4,x
lda #$fd
sta ENEMY_VAR_B,x
jmp advance_enemy_routine ; advance enemy x to next routine
@set_velocity_outdoor:
ldy #$00 ; set weapon_item_init_vel_tbl to first set of entries
lda LEVEL_SCROLLING_TYPE ; 0 = horizontal, indoor/base; 1 = vertical
beq @continue ; branch for horizontal scrolling
ldy #$04 ; vertical scrolling, set weapon_item_init_vel_tbl to second set of entries
lda ENEMY_X_POS,x ; load weapon item position on screen
cmp #$80
bcc @continue ; branch if ENEMY_X_POS < #$80
ldy #$08 ; weapon item close to right edge, set weapon_item_init_vel_tbl to 3rd set of entries
@continue:
lda weapon_item_init_vel_tbl,y ; load weapon item initial fractional y velocity
sta ENEMY_Y_VELOCITY_FRACT,x ; set weapon item initial fractional y velocity
lda weapon_item_init_vel_tbl+1,y ; load weapon item initial fast y velocity
sta ENEMY_Y_VELOCITY_FAST,x ; set weapon item initial fast y velocity
lda weapon_item_init_vel_tbl+2,y ; load weapon item initial fractional x velocity
sta ENEMY_X_VELOCITY_FRACT,x ; set weapon item initial fractional x velocity
lda weapon_item_init_vel_tbl+3,y ; load weapon item initial fast x velocity
sta ENEMY_X_VELOCITY_FAST,x ; set weapon item initial fast x velocity
weapon_item_advance_enemy_routine:
jmp advance_enemy_routine ; advance to next routine
; table for outdoor weapon item velocities (#$4 * #$4 = #$c bytes)
; #$04 bytes each entry
; * byte 0 - initial y velocity - fractional velocity byte
; * byte 1 - initial y velocity - fast velocity byte
; * byte 2 - initial x velocity - fractional velocity byte
; * byte 3 - initial x velocity - fast velocity byte
weapon_item_init_vel_tbl:
.byte $00,$fd,$80,$00 ; outdoor horizontal level initial velocities (go up 3, go right .5)
.byte $00,$fd,$40,$00 ; vertical level left side of screen (go up 3, go right .25)
.byte $00,$fd,$c0,$ff ; vertical level close to right edge (to up -3, go left .75)
; weapon item - pointer 2
; responsible for falling and landing on ground pattern in outdoor levels and
; falling arc pattern on indoor levels until land on ground, then advance enemy routine
weapon_item_routine_01:
jsr set_weapon_item_sprite ; set the correct sprite code based on weapon item type
lda LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor
beq @outdoor_pos_update ; branch for outdoor level
lda ENEMY_VAR_4,x ; indoor level, load ENEMY_VAR_4,x
clc ; clear carry in preparation for addition
adc #$12 ; ENEMY_VAR_4 + #$12
sta ENEMY_VAR_4,x ; used to help calculate arc trajectory
lda ENEMY_VAR_B,x ; used to help calculate arc trajectory
adc #$00 ; add any overflow from ENEMY_VAR_4 + #$12
sta ENEMY_VAR_B,x ; used to help calculate arc trajectory
jsr set_enemy_falling_arc_pos ; set X and Y position to follow a falling arc
lda ENEMY_VAR_3,x ; used to help calculate arc trajectory
bmi @exit ; see if weapon item should "land" at the bottom of the indoor/base level
lda #$a4 ; weapon item should stop on ground, hard-coded y position #$a4 for indoor levels
sta ENEMY_Y_POS,x ; set weapon item Y position on screen
bne weapon_item_advance_enemy_routine ; set routine to weapon_item_routine_02
@exit:
rts
; updates weapon item's X and Y position for outdoor levels
@outdoor_pos_update:
jsr set_outdoor_weapon_item_vel ; apply weapon item velocity and remove if off screen (at bottom, left, and right)
jsr @top_of_screen_check ; see if off screen to the top, if so, don't do collision checks
bcc @check_collision_reverse_dir ; branch if no collision
lda #$0a ; collision detected, set a #$0a (the amount to move in the Y direction to land)
jsr add_a_with_vert_scroll_to_enemy_y_pos ; weapon item landed on ground, update weapon item Y position
jsr set_enemy_velocity_to_0 ; set x/y velocities to zero
jmp advance_enemy_routine ; move to weapon_item_routine_02
@check_collision_reverse_dir:
lda ENEMY_X_POS,x ; load enemy x position on screen
ldy ENEMY_X_VELOCITY_FAST,x ; load fast x velocity
bmi @check_collision ; check if a collision for weapon item floating left
cmp #$e8 ; weapon item stationary or floating right, compare x position to #$e8
bcs @reverse_direction ; if close to right side of screen > #$e8, then reverse direction
lda #$0a ; add #$0a to weapon item X position
jsr weapon_item_check_bg_collision ; add a to X position and get bg collision
; exit if LEVEL_SOLID_BG_COLLISION_CHECK is #$00
bmi @reverse_direction ; branch if weapon item collided with solid object to reverse direction
bpl @add_10_to_y_fract_vel ; branch if not solid bg collision to add #$10 to fractional y velocity and exit
; no collision detected or negative X velocity
@check_collision:
cmp #$18 ; see if close close to left edge of screen
bcc @reverse_direction ; branch if close to left edge of screen
lda #$f6 ; subtract #$10 (#$f6) from weapon item X position
jsr weapon_item_check_bg_collision ; going left, see if about to collide with solid bg
bpl @add_10_to_y_fract_vel ; branch if not solid bg collision to add #$10 to fractional y velocity and exit
@reverse_direction:
jsr reverse_enemy_x_direction ; reverse enemy's x direction
@add_10_to_y_fract_vel:
jmp add_10_to_enemy_y_fract_vel ; add #$10 to y fractional velocity (.06 faster)
@top_of_screen_check:
lda ENEMY_Y_POS,x ; load enemy y position on screen
cmp #$20 ; compare ENEMY_Y_POS,x < #$20
bcc clear_carry_exit ; branch if weapon item is off the top of the screen
; outdoor weapon item collision check
; if weapon item is falling (not ascending), then check for bg collision
; output
; * carry set when falling and collided with background
; * carry clear when either ascending or no bg collision
check_weapon_item_collision:
lda ENEMY_FRAME,x ; load enemy animation frame number
ora ENEMY_Y_VELOCITY_FAST,x
bmi clear_carry_exit ; branch if flying upward, no bg collision detection
ldy #$08 ; y = #$08
jsr add_y_to_y_pos_get_bg_collision ; add #$10 to enemy y position and gets bg collision code
; (see if about to land on something)
beq clear_carry_exit ; exit if no background collision
sec ; landing on something, set carry
rts
clear_carry_exit:
clc ; clear the carry flag
rts
; check for background collision for weapon item if LEVEL_SOLID_BG_COLLISION_CHECK is non-zero
; add a to ENEMY_X_POS,x
; input
; * a - weapon item test x position
; output
; * a collision code #$00 (empty), #$01 (floor), #$02 (water), or #$80 (solid)
; * carry set when collision is with floor (#$01)
; carry clear when LEVEL_SOLID_BG_COLLISION_CHECK is #$00
weapon_item_check_bg_collision:
clc ; clear carry in preparation for addition
adc ENEMY_X_POS,x ; add to enemy x position on screen
sta $08
lda LEVEL_SOLID_BG_COLLISION_CHECK ; see if level specifies that weapon items should collide with solid bg objects
beq clear_carry_exit ; exit as if no background solid collision enabled for level
lda ENEMY_FRAME,x ; checking for solid bg collision, enemy animation frame number
bne @continue
ldy ENEMY_Y_POS,x ; enemy y position on screen
cpy #$10
bcs @continue_2 ; branch if ENEMY_Y_POS,x >= #$10
@continue:
ldy #$10 ; use position #$10 for weapon item Y position for bg collision check
@continue_2:
lda $08 ; load enemy X position
jmp get_bg_collision_far ; get background collision code
; weapon item - pointer 3
; continually watches to see if still on ground
; if ground disappears or weapon item slides off, move back to weapon_item_routine_01
weapon_item_routine_02:
jsr set_weapon_item_sprite
lda LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor
beq @outdoor_weapon_item ; branch for outdoor level
lda LEVEL_SCREEN_SCROLL_OFFSET ; indoor level, load scrolling offset within frame in pixels
beq @exit
jmp remove_enemy ; from bank 7
@outdoor_weapon_item:
jsr set_outdoor_weapon_item_vel ; apply weapon item velocity and remove if off screen (at bottom, left, and right)
jsr check_weapon_item_collision ; check weapon item bg collision if falling
bcs @exit ; exit if collision detected
lda #$02 ; no bg collision detected, a = #$02
jmp set_enemy_routine_to_a ; set routine index to weapon_item_routine_01
@exit:
rts
; sets sprite, attributes (palette) and updates for flashing if falcon weapon item
set_weapon_item_sprite:
lda #$00 ; a = #$00
ldy ENEMY_FRAME,x ; enemy animation frame number
bne @set_a_to_sprite_code_exit ; not first frame of animation, set invisible and exit
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
and #$07 ; keep enemy attributes portion
tay
cmp #$06 ; 06 = falcon item
bne @set_sprite_code
lda FRAME_COUNTER ; falcon item, flash falcon colors based on frame counter
lsr
lsr
lsr ; flash every #$08 frames
and #$03 ; keep bits .... ..xx
ora #$04 ; set bit 3
sta ENEMY_SPRITE_ATTR,x ; update enemy sprite attribute so it flashes
@set_sprite_code:
lda weapon_item_sprite_code_tbl,y ; load weapon item sprite code
@set_a_to_sprite_code_exit:
sta ENEMY_SPRITES,x ; set weapon item sprite code
rts
; table for item sprite codes (#$7 bytes)
; #$33 - sprite code for r weapon (sprite_33)
; #$34 - sprite code for m weapon (sprite_34)
; #$31 - sprite code for f weapon (sprite_35)
; #$2f - sprite code for s weapon (sprite_2f)
; #$32 - sprite code for l weapon (sprite_32)
; #$30 - sprite code for b weapon (barrier/invincibility) (sprite_30)
; #$4e - sprite code for falcon (sprite_4e)
weapon_item_sprite_code_tbl:
.byte $33,$34,$31,$2f,$32,$30,$4e
; pointer table for enemy bullet (#$4 * #$2 = #$8 bytes)
enemy_bullet_routine_ptr_tbl:
.addr enemy_bullet_routine_00 ; CPU address $814f (initialize collision code)
.addr enemy_bullet_routine_01 ; CPU address $8161 (init palette, sprite, and velocity)
.addr enemy_bullet_routine_02 ; CPU address $81e4 (level 1 boss cannonball explosion animation routine)
.addr remove_enemy ; CPU address $e809 from bank 7
; enemy bullet - pointer 1
enemy_bullet_routine_00:
ldy ENEMY_VAR_1,x ; load bullet type
lda bullet_collision_code_tbl,y ; load bullet collision code
sta ENEMY_SCORE_COLLISION,x ; store bullet collision code
jmp advance_enemy_routine ; advance to next routine
; for enemy bullet collision box (#$6 bytes)
; * #$01 - regular bullets (bullet types #$00, #$03)
; * #$05 - larger cannonball bullets (bullet types #$01, #$02)
; * #$02 - level 3 dragon boss fire ball (dragon arm orb projectile) (bullet type #$04)
bullet_collision_code_tbl:
.byte $01,$05,$05,$01,$02,$00
; enemy bullet - pointer 2
enemy_bullet_routine_01:
ldy ENEMY_VAR_1,x ; load bullet type
bne @init_bullet_vel_pos_sprite ; bullet type is not #$00, no need changing to red for snow field
lda CURRENT_LEVEL ; current level
cmp #$04 ; check if level 5 (snow field)
bne @init_bullet_vel_pos_sprite ; not level 5, don't change bullet type #$00 to #05
ldy #$05 ; snow field, change bullet type #$00 to #05 (for red bullets)
@init_bullet_vel_pos_sprite:
lda bullet_sprite_tbl,y ; load bullet sprite
sta ENEMY_SPRITES,x ; store bullet sprite
lda bullet_palette_tbl,y ; load bullet's palette
sta ENEMY_SPRITE_ATTR,x ; set sprite attribute (palette)
jsr update_enemy_pos ; apply velocities and scrolling adjust
ldy ENEMY_VAR_1,x ; re-load bullet type (clears snow level #$05 override)
bne @continue ; skip if not a regular bullet
lda LEVEL_SOLID_BG_COLLISION_CHECK ; see if should test bullet - solid bg collisions
bpl @continue ; skip if level doesn't specify bullets should collide with solid bg objects
jsr check_enemy_collision_solid_bg ; see if bullet is colliding with solid object, or floor on top of solid object
bpl enemy_bullet_routine_01_exit ; exit if solid collision code
jmp remove_enemy ; from bank 7
@continue:
dey
beq cannonball_add_gravity_explode ; branch if bullet type #$01 (large cannonball)
dey
dey
beq indoor_bullet_offscreen_check ; branch if bullet type #$03 (indoor regular bullet)
dey
bne enemy_bullet_routine_01_exit ; exit if bullet not bullet type #$04 (dragon arm orb projectile)
lda FRAME_COUNTER ; bullet type #$04, load frame counter for animating fire ball projectile
lsr ; shift right twice
lsr
and #$03 ; bits 0 and 1 cause animation to change every #$04 frames
tay ; transfer bullet_04_palette_mirror_tbl offset to y
lda bullet_04_palette_mirror_tbl,y ; load sprite flipping and palette code for animating dragon arm orb projectile
sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes
enemy_bullet_routine_01_exit:
rts
; dragon arm orb projectile (orange fireballs) palette code and mirroring (#$4 bytes)
; used for animating palette and sprite flipping every #$04 frames
bullet_04_palette_mirror_tbl:
.byte $01,$41,$c1,$81
; check if bullet should be removed, otherwise exit
indoor_bullet_offscreen_check:
lda ENEMY_Y_POS,x ; load enemy y position on screen
cmp #$b4 ; see if bullet far at bottom of screen
bcs @remove_bullet ; remove if too far down on screen
lda ENEMY_X_POS,x ; load enemy x position on screen
cmp #$20 ; see if bullet far to the left
bcc @remove_bullet ; remove if bullet too far to the left
cmp #$e0 ; see if bullet too far to the right
bcc enemy_bullet_exit ; exit if not too far to the left
@remove_bullet:
jmp remove_enemy ; from bank 7
; large cannonball bullet (bullet type #$01)
; adds gravity to create arc, and explodes at height #$d0 (the ground)
; if explodes, moves to enemy_bullet_routine_02, otherwise exists
cannonball_add_gravity_explode:
lda #$14 ; a = #$14 (gravity coefficient for bombs)
jsr add_a_to_enemy_y_fract_vel ; add a to enemy y fractional velocity
lda ENEMY_Y_POS,x ; load enemy y position on screen
cmp #$d0 ; bombs explode at this height
bcc enemy_bullet_exit ; exit if not yet exploded
lda #$00 ; a = #$00
sta ENEMY_FRAME,x ; set enemy animation frame number
lda #$01 ; a = #$01
sta ENEMY_ANIMATION_DELAY,x ; enemy explosion animation frame delay counter
adv_bullet_routine:
jmp advance_enemy_routine ; advance to next routine
; table for bullet sprite codes (#$6 bytes)
; sprite_1e - regular bullet
; sprite_21 - large cannonball bullet
; sprite_79 - level 3 dragon boss fire ball
; sprite_07 - level 5 - snow field red bullets
bullet_sprite_tbl:
.byte $1e,$21,$21,$1e,$79,$07
; table for bullet palette codes (#$6 bytes)
bullet_palette_tbl:
.byte $01,$02,$02,$01,$01,$02
; enemy bullet - pointer 3
; only used for bullet type #$03 (level 1 boss cannonball) explosion animation
enemy_bullet_routine_02:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne enemy_bullet_exit ; exit if animation delay hasn't elapsed
lda #$08 ; a = #$08
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
inc ENEMY_FRAME,x ; increment enemy animation frame number
ldy ENEMY_FRAME,x ; load enemy animation frame number
cpy #$03 ; see if on third frame of animation
bcs adv_bullet_routine ; go to enemy_bullet_routine_03 if explosion animation complete
lda cannonball_explosion_sprite_tbl,y ; load cannonball explosion sprite
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
enemy_bullet_exit:
rts
; table for bullet type #$02 (cannonball) explosion sprite codes (#$3 bytes)
cannonball_explosion_sprite_tbl:
.byte $37,$36,$37
; pointer table for weapon box (#$b * #$2 = #$16 bytes)
weapon_box_routine_ptr_tbl:
.addr weapon_box_routine_00 ; CPU address $821b
.addr weapon_box_routine_01 ; CPU address $8225
.addr weapon_box_routine_02 ; CPU address $8248
.addr weapon_box_routine_03 ; CPU address $82b0
.addr weapon_box_routine_04 ; CPU address $82bd
.addr enemy_routine_init_explosion ; CPU address $e74b
.addr enemy_routine_explosion ; CPU address $e7b0
.addr enemy_routine_remove_enemy ; CPU address $e806
.addr enemy_routine_init_explosion ; CPU address $e74b
.addr enemy_routine_explosion ; CPU address $e7b0
.addr enemy_routine_remove_enemy ; CPU address $e806
; weapon box - pointer 1
; initialize ENEMY_FRAME, set delay and move to weapon_box_routine_01
weapon_box_routine_00:
lda #$01 ; a = #$01
sta ENEMY_FRAME,x ; set enemy animation frame number
lda #$20 ; a = #$20 (weapon box initial delay)
set_enemy_delay_adv_routine_far:
jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a; advance enemy routine
; Weapon Box - Pointer 2
; wait for scroll to bring weapon box to specified position, then advance to weapon_box_routine_02
; if pill box sensor is close to edge of screen (left for horizontal levels, bottom for vertical levels), then move to weapon_box_routine_03
weapon_box_routine_01:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda #$f0 ; set x position for weapon box activation
ldy #$30 ; set y position for vertical level weapon box activation
jsr set_carry_if_past_trigger_point ; set carry if enemy has crossed #$f0 X position for horizontal levels, and #$30 for vertical levels
bcc weapon_box_exit ; exit if player is too far from pill box sensor to activate it
lda #$18 ; a = #$18
ldy #$c8 ; y = #$c8
jsr set_carry_if_past_trigger_point ; set carry if enemy has crossed #$18 X position for horizontal levels, and #$c8 for vertical levels
bcs adv_to_weapon_box_routine_03 ; see if pill box sensor is close to left (or bottom) of screen, if so, move to weapon_box_routine_03 (closes weapon box)
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne weapon_box_exit
lda #$08 ; a = #$08
bne set_enemy_delay_adv_routine_far ; set ENEMY_ANIMATION_DELAY to #$08 and move to weapon_box_routine_02
adv_to_weapon_box_routine_03:
lda #$04 ; a = #$04
jmp set_enemy_routine_to_a ; set to weapon_box_routine_03
; Weapon Box - Pointer 3
; animates opening and closing of weapon box
weapon_box_routine_02:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda #$18 ; a = #$18
ldy #$c8 ; y = #$c8
jsr set_carry_if_past_trigger_point ; set carry if enemy has crossed #$18 X position for horizontal levels, and #$c8 for vertical levels
bcs adv_to_weapon_box_routine_03 ; see if pill box sensor is close to left (or bottom) of screen, if so, move to weapon_box_routine_03 (closes weapon box)
dec ENEMY_ANIMATION_DELAY,x ; decrement animation frame
bne weapon_box_exit ; exit if animation hasn't completed
lda #$08 ; a = #$08
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
lda ENEMY_VAR_2,x ; load whether the weapon box is open or not
bne @open_weapon_box ; branch if weapon box is open
jsr set_weapon_box_supertile ; weapon box is close,d set the weapon box super tile based on ENEMY_FRAME
bcs weapon_box_exit
lda ENEMY_FRAME,x ; load enemy animation frame number
cmp #$02 ; see if weapon box is open
bcc inc_weapon_box_frame ; branch if weapon box is closed, or closing/opening, increment ENEMY_FRAME to move to next frame's super-tile
dec ENEMY_FRAME,x ; weapon box is closed; animate closing by decrementing ENEMY_FRAME to use the partially closed super-tile
lda #$01 ; a = #$01
sta ENEMY_HP,x ; set weapon box hp to #$01 (when not closed)
lda #$01 ; a = #$01
bne @set_animation_delay ; always branch
@open_weapon_box:
jsr set_weapon_box_supertile ; set the weapon box super tile based on ENEMY_FRAME
bcs weapon_box_exit
lda ENEMY_FRAME,x ; load enemy animation frame number
bne dec_weapon_box_frame ;
inc ENEMY_FRAME,x ; increment enemy animation frame number
lda #$f0 ; a = #$f0 (f0 = no hit)
sta ENEMY_HP,x ; set enemy hp
lda #$00 ; a = #$00
@set_animation_delay:
sta ENEMY_VAR_2,x ; store whether the weapon box is open
lda #$40 ; a = #$40 (delay when weapon box fully open)
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
lda #$02 ; a = #$02
jmp set_enemy_routine_to_a ; set routine to weapon_box_routine_01
; next animation frame
inc_weapon_box_frame:
inc ENEMY_FRAME,x ; increment enemy animation frame number
weapon_box_exit:
rts
; previous animation frame
dec_weapon_box_frame:
dec ENEMY_FRAME,x ; decrement enemy animation frame number
rts
; sets the weapon box super tile based on ENEMY_FRAME
set_weapon_box_supertile:
ldy ENEMY_FRAME,x ; enemy animation frame number
lda weapon_box_supertile_tbl,y ; load the correct super-tile index (indexes into level_X_nametable_update_supertile_data)
jmp draw_enemy_supertile_a_set_delay ; draw pattern table tile specified in a at enemy position
; table for weapon box frame codes (#$3 bytes)
; #$00 closed
; #$01 semi-open
; #$02 fully open
weapon_box_supertile_tbl:
.byte $00,$01,$02
; weapon box pointer 4
; weapon box deactivated, mark closed, only executed once
weapon_box_routine_03:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda #$00 ; #$00 = weapon box closed (indexes into level_X_nametable_update_supertile_data)
jsr draw_enemy_supertile_a ; draw the closed weapon box super-tile
bcs weapon_box_exit_1
jmp remove_enemy ; remove the enemy since not drawn
; Weapon Box - Pointer 5
; initiated when weapon box is destroyed via enemy_destroyed_routine_00
; set appropriate background super-tile based on level and ENEMY_ATTRIBUTES (bit 4)
weapon_box_routine_04:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda CURRENT_LEVEL ; current level
asl
tay
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
and #$08 ; keep bits .... x...
beq @continue ; if big 4 is not set then use the first byte
iny ; bit 4 was set, use second byte
@continue:
lda weapon_box_destroyed_supertile,y
jsr draw_enemy_supertile_a ; draw super-tile a at position (ENEMY_X_POS, ENEMY_Y_POS)
bcs weapon_box_exit
play_explosion_sound:
lda #$19 ; a = #$19 (sound_19)
jsr play_sound ; play enemy destroyed sound
jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position
jsr create_two_explosion_89 ; create explosion #$89 at location ($09, $08)
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
and #$07 ; keep bits .... .xxx
sta ENEMY_ATTRIBUTES,x
jsr clear_sprite_clear_enemy_pt_3
lda #$01 ; a = #$01
sta ENEMY_ROUTINE,x ; enemy routine index
lda #$00 ; a = #$00
sta ENEMY_TYPE,x ; set enemy slot to be weapon item
weapon_box_exit_1:
rts
; table for weapon box tile code after destruction (#$8 * #$2 = #$10 bytes)
; each entry is for a level
; the least significant bit of the vertical position is also used to
; specify which tile is shown after being destroyed.
weapon_box_destroyed_supertile:
.byte $16,$16 ; level 1
.byte $16,$16 ; level 2
.byte $16,$16 ; level 3
.byte $16,$16 ; level 4
.byte $19,$1a ; level 5
.byte $03,$04 ; level 6
.byte $09,$09 ; level 7
.byte $16,$16 ; ending
; pointer table for weapon zeppelin (#$3 * #$2 = #$6 bytes)
flying_capsule_routine_ptr_tbl:
.addr flying_capsule_routine_00 ; CPU address $830b
.addr flying_capsule_routine_01 ; CPU address $835d
.addr flying_capsule_routine_02 ; CPU address $8376
; weapon zeppelin - pointer 1
flying_capsule_routine_00:
lda #$03 ; a = #$03 (weapon zeppelin palette code)
sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes
lda ENEMY_Y_POS,x
sta ENEMY_VAR_1,x
lda ENEMY_X_POS,x ; load enemy x position on screen
sta ENEMY_VAR_2,x
lda LEVEL_SCROLLING_TYPE ; 0 = horizontal, indoor/base; 1 = vertical
bne @set_vertical_level_vel ; branch for vertical level (waterfall)
lda #$20 ; a = #$20 (zeppelin vertical amplitude)
jsr add_a_to_enemy_y_pos ; add #$20 to y position
lda #$10 ; a = #$10 (zeppelin initial x position)
sta ENEMY_X_POS,x ; set initial enemy x position on screen
ldy #$00 ; y = #$00
beq @set_vel_adv_routine ; always branch
@set_vertical_level_vel:
lda #$20 ; a = #$20
jsr add_a_to_enemy_x_pos ; add #$20 to enemy x position on screen
lda #$e0 ; a = #$e0
sta ENEMY_Y_POS,x ; enemy y position on screen
ldy #$04 ; y = #$04
@set_vel_adv_routine:
lda flying_capsule_vel_tbl,y ; load y fractional velocity byte
sta ENEMY_Y_VELOCITY_FRACT,x ; set y fractional velocity byte
lda flying_capsule_vel_tbl+1,y ; load y velocity fast byte
sta ENEMY_Y_VELOCITY_FAST,x ; set y velocity fast byte
lda flying_capsule_vel_tbl+2,y ; load x fractional velocity byte
sta ENEMY_X_VELOCITY_FRACT,x ; set x fractional velocity byte
lda flying_capsule_vel_tbl+3,y ; load x velocity fast byte
sta ENEMY_X_VELOCITY_FAST,x ; set x velocity fast byte
jmp advance_enemy_routine ; go to flying_capsule_routine_01
; table for weapon zeppelin velocities (#$4 * #$2 = #$8 bytes)
flying_capsule_vel_tbl:
.byte $00,$00,$80,$01 ; horizontal and indoor/base levels
.byte $80,$fe,$00,$00 ; vertical level (level 3 - waterfall)
; weapon zeppelin - pointer 2
flying_capsule_routine_01:
lda #$4d ; a = #$4d (sprite_4d)
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
lda LEVEL_SCROLLING_TYPE ; 0 = horizontal, indoor/base; 1 = vertical
bne @continue ; branch if indoor level
ldy #$01 ; outdoor level; y = #$01
jsr set_flying_capsule_y_vel
jmp @update_enemy_pos
@continue:
ldy #$01 ; y = #$01
jsr set_flying_capsule_x_vel
@update_enemy_pos:
jmp update_enemy_pos ; apply velocities and scrolling adjust
; weapon zeppelin pointer 3
flying_capsule_routine_02:
jmp play_explosion_sound
; pointer table for rotating gun (#$a * #$2 = #$14 bytes)
; level 1 or level 3 enemy
rotating_gun_routine_ptr_tbl:
.addr rotating_gun_routine_00 ; CPU address $838d - set aim direction to left
.addr rotating_gun_routine_01 ; CPU address $8397 - wait until player is close to activate
.addr rotating_gun_routine_02 ; CPU address $83ac - show opening animation, enable collision
.addr rotating_gun_routine_03 ; CPU address $83d5 - shut down if off screen, otherwise, wait for animation delay, then aim
.addr rotating_gun_routine_04 ; CPU address $842e - fire desired number of bullets at player, once complete go back to rotating_gun_routine_03
.addr rotating_gun_routine_05 ; CPU address $8482 - shuts down rotating gun, gun retracts and no longer fires, removes enemy
.addr rotating_gun_routine_06 ; CPU address $848f - enemy destroyed routine (see enemy_destroyed_routine_01)
.addr enemy_routine_init_explosion ; CPU address $e74b from bank 7
.addr enemy_routine_explosion ; CPU address $e7b0 from bank 7
.addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7
; rotating gun - pointer 1
; set aim direction to left
rotating_gun_routine_00:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda #$06 ; a = #$06
sta ENEMY_VAR_1,x ; set aim direction to face left
bne rotating_gun_adv_routine_exit ; advance routine to rotating_gun_routine_01 and exit
; rotating gun - pointer 2
; wait until player is close to activate
rotating_gun_routine_01:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda #$f0 ; a = #$f0 (horizontal level trigger point)
ldy #$30 ; y = #$30 (vertical level trigger point)
jsr set_carry_if_past_trigger_point ; set carry if enemy has crossed #$f0 X position for horizontal levels, and #$30 for vertical levels
bcc rotating_gun_exit_00 ; exit if not yet at trigger point on screen, i.e. don't activate
lda #$08 ; should activate rotating gun, set delay to #$08
; advance routine to rotating_gun_routine_02
rotating_gun_set_delay_adv_routine_exit:
sta ENEMY_ANIMATION_DELAY,x
rotating_gun_adv_routine_exit:
jmp advance_enemy_routine
rotating_gun_exit_00:
rts
; rotating gun - pointer 3
; show opening animation (#$02 frames), enable collision
rotating_gun_routine_02:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
dec ENEMY_ANIMATION_DELAY,x ; decrement animation delay
bne rotating_gun_exit_00 ; exit if animation delay hasn't elapsed
lda #$08 ; a = #$08
sta ENEMY_ANIMATION_DELAY,x ; set next animation delay to #$08
lda ENEMY_FRAME,x ; load current super-tile index to (level_xx_nametable_update_supertile_data)
clc ; clear carry in preparation for addition
adc #$03 ; rotating gun super-tiles start at offset #$03, add #$03
jsr draw_enemy_supertile_a_set_delay ; draw pattern table tile specified in a at enemy position
bcs rotating_gun_exit_00 ; exit if unable to update super-tile
inc ENEMY_FRAME,x ; increment rotating gun super-tile to draw
lda ENEMY_FRAME,x ; load rotating gun super-tile to draw
cmp #$03 ; see if rotating gun is active and open, i.e. the gun is showing
bcc rotating_gun_exit_00 ; branch if rotating gun not yet open
jsr enable_bullet_enemy_collision ; rotating gun active, allow bullets to collide (and stop) upon colliding with rotating gun
lda #$08 ; a = #$08
bne rotating_gun_set_delay_adv_routine_exit ; set delay and move to rotating_gun_routine_03
; rotating gun - pointer 4
; shut down if off screen, otherwise, wait for animation delay, then aim
rotating_gun_routine_03:
jsr rotating_gun_should_disable ; determine if almost scrolled off screen
bcc rotating_gun_routine_03_continue ; branch if not past trigger point
; shuts down rotating gun by moving to rotating_gun_routine_05,
; which sets super-tile to closed and removes enemy
rotating_gun_disable:
lda #$06 ; a = #$06
jmp set_enemy_routine_to_a ; set enemy routine index to rotating_gun_routine_05
; rotating gun isn't disabled, continue animation delay and aiming
rotating_gun_routine_03_continue:
dec ENEMY_ANIMATION_DELAY,x ; decrement animation delay
bne @exit ; exit if animation delay hasn't elapsed
ldy PLAYER_WEAPON_STRENGTH ; load player's weapon strength
lda rotating_gun_rotation_delay_tbl,y ; load rotation animation delay based on weapon strength
sta ENEMY_ANIMATION_DELAY,x ; set new animation delay
jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01)
sty $0a ; store closest player index in $0a
jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position
jsr aim_var_1_for_quadrant_aim_dir_00 ; determine next aim direction [#$00-#$0b] ($0c), adjusts ENEMY_VAR_1 to get closer to that value using quadrant_aim_dir_00
php ; save the processor flags to the stack
lda ENEMY_VAR_1,x ; load new enemy aim direction [#$00-#$0b] #$00 when facing right incrementing clockwise
sec ; set carry flag in preparation for subtraction
sbc #$06 ; subtract #$06 to get to correct super-tile to draw based on aim dir
bcs @draw_supertile ; draw super-tile immediately if no underflow occurred
adc #$0c ; underflow, add #$0c to wrap around back to correct super-tile
; e.g. aim dir #$03 should result in #$09
@draw_supertile:
clc ; clear carry in preparation for addition
adc #$05 ; add #$05, offset to open rotating gun super-tiles (level_xx_nametable_update_supertile_data)
jsr draw_enemy_supertile_a_set_delay ; draw pattern table tile specified in a at enemy position
bcc @set_vars_adv_routine ; branch if successfully drawn supertile
; to set ENEMY_VAR_2, animation delay, and advance routine if rotating gun aiming at player
plp ; restore processor flags
@exit:
rts
; if rotating gun aiming at player (carry flag set when enemy aiming at player from aim_var_1_for_quadrant_aim_dir_00)
; set vars and advance routine to rotating_gun_routine_04 to fire at player
@set_vars_adv_routine:
plp ; restore processor flags
bcc @exit ; exit if not aiming at player
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
and #$03 ; keep bits .... ..xx
tay
lda rotating_gun_bullets_per_attack_tbl,y ; load bullets per attack
sta ENEMY_VAR_2,x ; store bullets per attack in ENEMY_VAR_2
lda #$08 ; a = #$08
jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to #$08 go to rotating_gun_routine_04
; table for rotating gun bullets per attack (from attributes) (#$4 bytes)
rotating_gun_bullets_per_attack_tbl:
.byte $01,$02,$03,$03
; whether or not to disable/shut down the rotating gun
; rotating gun shuts down when scrolled to the left 10% of the screen (horizontal level)
; rotating gun shuts down when scrolled down to the bottom 20% of the screen (vertical level)
rotating_gun_should_disable:
jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll
lda #$18 ; a = #$18
ldy #$c8 ; y = #$c8
jmp set_carry_if_past_trigger_point ; set carry if enemy has crossed #$18 X position for horizontal levels, and #$c8 for vertical levels
; rotating gun - pointer 5
; fire desired number of bullets at player, once complete go back to rotating_gun_routine_03
rotating_gun_routine_04:
jsr rotating_gun_should_disable ; determine if almost scrolled off screen
bcs rotating_gun_disable ; if out of range, shut down by moving to rotating_gun_routine_05
dec ENEMY_ANIMATION_DELAY,x ; decrement animation delay
bne @exit ; exit if animation delay hasn't elapsed
ldy ENEMY_VAR_1,x ; load current aim direction. [#$00-#$0b] #$00 when facing right incrementing clockwise
lda rotating_gun_bullet_y_offset_tbl,y ; load the bullet y offset from rotating gun based on aim direction
clc ; clear carry in preparation for addition
adc ENEMY_Y_POS,x ; add bullet offset to enemy position
sta $08 ; store bullet generation y position
lda rotating_gun_bullet_x_offset_tbl,y ; load the bullet x offset from rotating gun based on aim direction
clc ; clear carry in preparation for addition
adc ENEMY_X_POS,x ; add to enemy x position on screen
sta $09 ; store bullet generation x position
tya ; set bullet type
ldy #$04 ; y = #$04 rotating gun bullet speed
jsr bullet_generation ; create enemy bullet (ENEMY_TYPE #$02) of type a with speed y at ($09, $08)
lda #$10 ; a = #$10 delay between bullets
sta ENEMY_ANIMATION_DELAY,x ; set delay between bullets
dec ENEMY_VAR_2,x ; created bullet, decrement number of bullets to fire
bne @exit ; exit if still more bullets to fire
ldy PLAYER_WEAPON_STRENGTH ; fired all bullets, load player weapon strength
lda rotating_gun_animation_delay_tbl,y ; load animation delay based on weapon strength
sta ENEMY_ANIMATION_DELAY,x ; store enemy animation delay
lda #$04 ; a = #$04
jmp set_enemy_routine_to_a ; set enemy routine index to rotating_gun_routine_03
@exit:
rts
; table for rotating gun delays while rotating based on player weapon strength (#$4 bytes)
; the stronger the weapon the shorter the delay
rotating_gun_rotation_delay_tbl:
.byte $30,$28,$20,$18
; table for rotating gun delays after attack (#$4 bytes)
rotating_gun_animation_delay_tbl:
.byte $80,$60,$40,$30
; table for rotating gun bullet initial y offset positions (#$f bytes)
rotating_gun_bullet_y_offset_tbl:
.byte $00,$07,$0c
rotating_gun_bullet_x_offset_tbl:
.byte $0d,$0c,$07
.byte $00,$f9,$f4
.byte $f3,$f4,$f9
.byte $00,$07,$0c
; rotating gun - pointer 6
; shuts down rotating gun, gun retracts and no longer fires, removes enemy
rotating_gun_routine_05:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda #$03 ; a = #$03 (rotating gun closed super-tile)
jsr draw_enemy_supertile_a ; draw super-tile a (level_xx_nametable_update_supertile_data offset) at position (ENEMY_X_POS, ENEMY_Y_POS)
bcs rotating_gun_exit_01 ; exit if unable to draw super-tile
jmp remove_enemy ; remove rotating gun
; rotating gun - pointer 7
; enemy destroyed routine (see enemy_destroyed_routine_01)
rotating_gun_routine_06:
jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll
lda #$16 ; a = #$16 (red turret and rotating gun rock background, see level_xx_nametable_update_supertile_data)
jsr draw_enemy_supertile_a ; draw super-tile a (offset into level nametable update table) at position (ENEMY_X_POS, ENEMY_Y_POS)
bcs rotating_gun_exit_01 ; exit if unable to draw super-tile
jmp advance_enemy_routine ; advance to enemy_routine_init_explosion
; sets carry once enemy is far enough on the screen
; for horizontal levels, triggers once enemy is to the left of specified x location
; for vertical levels, triggers once enemy is below the specified y location
; also used to see if player past enemy for red turrets
; input
; * a - x position on screen to trigger enemy (for horizontal levels)
; * y - y position on screen where enemy is activated (for vertical levels)
set_carry_if_past_trigger_point:
sta $08
sty $09
lda LEVEL_SCROLLING_TYPE ; 0 = horizontal, indoor/base; 1 = vertical
bne @vertical_level ; branch for vertical level
lda ENEMY_X_POS,x ; horizontal or indoor/base level, load enemy x position on screen
cmp $08 ; compare enemy position to X trigger position
bcs @exit_carry_clear ; scroll has not put enemy at trigger position, exit with carry clear
bcc @exit_carry_set ; enemy crossed trigger position, exit with carry set
@vertical_level:
lda ENEMY_Y_POS,x ; load enemy Y position on screen
cmp $09 ; compare enemy position to Y trigger position
bcc @exit_carry_clear ; scroll has not put enemy at trigger position, exit with carry clear
@exit_carry_set:
sec ; set the carry flag, enemy close to player
rts
@exit_carry_clear:
clc ; clear the carry flag, enemy too far away from player
rotating_gun_exit_01:
rts
; pointer table for red turret (#$9 * #$2 bytes = #$12 bytes)
red_turret_routine_ptr_tbl:
.addr red_turret_routine_00 ; CPU address $84ca - initialize ENEMY_VAR_1 (aim direction), advance to red_turret_routine_01
.addr red_turret_routine_01 ; CPU address $84d4 - wait for player to get close, then advance routine
.addr red_turret_routine_02 ; CPU address $84e8
.addr red_turret_routine_03 ; CPU address $8537
.addr red_turret_routine_04 ; CPU address $85d1
.addr red_turret_routine_05 ; CPU address $85e1
.addr enemy_routine_init_explosion ; CPU address $e74b
.addr enemy_routine_explosion ; CPU address $e7b0
.addr enemy_routine_remove_enemy ; CPU address $e806
; red turret - pointer 1
; initialize ENEMY_VAR_1 (aim direction), advance to red_turret_routine_01
; identical code to rotating_gun_routine_00
red_turret_routine_00:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda #$06 ; a = #$06
sta ENEMY_VAR_1,x ; set aim direction to face left
bne red_turret_adv_routine ; always branch, advance enemy routine
; red turret - pointer 2
; wait for player to get close, then advance routine
red_turret_routine_01:
lda #$f0 ; a = #$f0 (red turret emerge at this x offset)
ldy #$40 ; y = #$40
jsr set_carry_if_past_trigger_point ; set carry if enemy has crossed #$f0 X position for horizontal levels, and #$40 for vertical levels
bcc red_turret_add_scroll_to_pos ; player not close, do nothing
lda #$01 ; player close, set a = #$01 (delay before emerging)
red_turret_set_delay_adv_routine:
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
red_turret_adv_routine:
jsr advance_enemy_routine ; advance to next routine
red_turret_add_scroll_to_pos:
jmp add_scroll_to_enemy_pos ; add scrolling to enemy position
; red turret - pointer 3
red_turret_routine_02:
jsr red_turret_load_supertile ; load the appropriate super-tile if the animation delay has elapsed
bcs red_turret_add_scroll_to_pos ; exit if enemy animation delay hasn't elapsed, no super-tile to update
inc ENEMY_FRAME,x ; increment the enemy animation frame number
lda ENEMY_FRAME,x ; load the enemy animation frame number
cmp #$04 ; compare to the last frame
bcc red_turret_add_scroll_to_pos ; if not the last frame, then add scroll and exit
lda #$02 ; a = #$02
sta ENEMY_VAR_2,x
lda #$28 ; a = #$28 (delay before first attack)
ldy GAME_COMPLETION_COUNT ; load the number of times the game has been completed
beq @set_attack_delay_enable_collision ; keep #$28 attack delay if game hasn't been beaten
lda #$08 ; lower attack delay to #$08 when game has been beaten at least once
@set_attack_delay_enable_collision:
sta ENEMY_ATTACK_DELAY,x
jsr enable_bullet_enemy_collision ; allow bullets to collide (and stop) upon colliding with red turret
lda #$10 ; set animation delay to #$10
bne red_turret_set_delay_adv_routine ; always branch, set animation delay to #$10 and advance routine
red_turret_load_supertile:
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne @set_carry_exit ; set carry and exit if animation delay hasn't elapsed
lda #$04 ; a = #$04
sta ENEMY_ANIMATION_DELAY,x ; delay between frames when emerging
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
lsr ; move bit 0 to carry flag, this bit specifies which background to load
lda ENEMY_FRAME,x ; load enemy animation frame number
bcc @load_supertile ; if bit 0 was 0, then rocky background, don't adjust super-tile offset
adc #$03
@load_supertile:
tay
lda red_turret_supertile_1_tbl,y
jmp draw_enemy_supertile_a_set_delay ; draw pattern table tile specified in a at enemy position
@set_carry_exit:
sec ; set carry flag
rts
; table for red turret super-tile codes (#$b bytes)
; see level_1_nametable_update_supertile_data
; see level_3_nametable_update_supertile_data
; #$11 - red turret facing left
; #$12 - red turret facing up left
; #$13 - red turret facing up up left (almost straight up)
; #$14 - red turret 1/2 rising from ground rocky
; #$15 - red turret 1/2 rising from ground metal/waterfall background
; #$16 - just rocky background no red turret
; #$17 - just secondary background no red turret (mostly metal background for level 1, waterfall for level 3)
; #$18 - red turret 3/4 rising from ground black background
red_turret_supertile_1_tbl:
.byte $16,$14
; see level_1_nametable_update_supertile_data
; see level_3_nametable_update_supertile_data
red_turret_supertile_2_tbl:
.byte $18,$11,$17,$15,$18,$11,$11,$12,$13
; red turret - pointer 4
red_turret_routine_03:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda #$30 ; a = #$30 (x offset to return to ground)
ldy #$c0 ; y = #$c0
jsr set_carry_if_past_trigger_point ; set carry if enemy has crossed #$30 X position for horizontal levels (left side of screen)
; and #$c0 for the vertical level (bottom of screen)
bcc @gen_bullet_if_appropriate ; branch if red turret should still be active (not scrolled to left/bottom of screen yet)
lda #$02 ; red turret on far left (horizontal) or far bottom (vertical), disable
sta ENEMY_FRAME,x ; initial frame code when returning to ground (#$02)
jsr disable_enemy_collision ; prevent player enemy collision check and allow bullets to pass through enemy
lda #$01 ; a = #$01
jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to #$01 advance to red_turret_routine_04
@gen_bullet_if_appropriate:
jsr red_turret_find_target_player ; find player to target, set to y
jsr check_red_turret_firing_range ; see if the player is above (or equal) and to the left of the red turret
tya ; transfer closest player to a
bcs @continue
eor #$01 ; flip bits .... ...x
@continue:
sta $0a ; store player index in $0a
jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position
jsr get_rotate_00 ; get enemy aim direction and rotation direction using quadrant_aim_dir_00
sta $08 ; set rotate direction in $08 (#$00 clockwise, #$01 counterclockwise, #$80 no rotation)
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne @dec_attack_delay_fire_bullet
lda #$10 ; a = #$10
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
ldy ENEMY_VAR_1,x
lda $08
bmi @dec_attack_delay_fire_bullet
bne @continue2
cpy #$08
beq @dec_attack_delay_fire_bullet
inc ENEMY_VAR_1,x
bne @set_supertile_fire
@continue2:
cpy #$06
beq @dec_attack_delay_fire_bullet
dec ENEMY_VAR_1,x
@set_supertile_fire:
ldy ENEMY_VAR_1,x
lda red_turret_supertile_2_tbl,y
jsr draw_enemy_supertile_a_set_delay ; draw pattern table tile specified in a at enemy position
@dec_attack_delay_fire_bullet:
dec ENEMY_ATTACK_DELAY,x ; decrement attack delay
bne red_turret_exit ; exit if attack delay hasn't elapsed
ldy #$10 ; y = #$10 (delay between attacks)
dec ENEMY_VAR_2,x ; delay between consecutive bullets
bpl @generate_bullet ; fire bullet
lda #$02 ; a = #$02
sta ENEMY_VAR_2,x ; consecutive bullets (#$02 = 3 bullets)
ldy #$50 ; y = #$50 (delay between attacks)
@generate_bullet:
tya
sta ENEMY_ATTACK_DELAY,x ; set delay between attacks
ldy $0f ; load the closest player in y
jsr check_red_turret_firing_range ; see if the player is above (or equal) and to the left of the red turret
bcc red_turret_exit ; exit if player not in firing range of red turret
ldy ENEMY_VAR_1,x
lda @bullet_offset_tbl_base,y ; load y bullet generation point y offset
clc ; clear carry in preparation for addition
adc ENEMY_Y_POS,x ; add offset to red turret's y position
sta $08 ; store result in $08 for bullet_generation call
lda red_turret_bullet_offset_tbl-3,y ; silly to have offsets like this !(WHY?)
; load bullet generation point x offset
clc ; clear carry in preparation for addition
adc ENEMY_X_POS,x ; add offset to red turret's x position
sta $09 ; store in $09 for bullet_generation call
tya
; !(WHY?) weird to have this as a label point when only used for reading offset data in red_turret_bullet_offset_tbl
; #$06 bytes before table data
@bullet_offset_tbl_base:
ldy #$05 ; red turret bullet speed
jmp bullet_generation ; create enemy bullet (ENEMY_TYPE #$02) of type a with speed y at ($09, $08)
red_turret_exit:
rts
; table for red turret bullet initial offsets (#$6 bytes)
red_turret_bullet_offset_tbl:
.byte $00,$f8,$f0 ; x offset #$0 , -#$8 , -#$10
.byte $f2,$f2,$f8 ; y offset -#$e , -#$e , -#$8
; red turret - pointer 5
red_turret_routine_04:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
jsr red_turret_load_supertile
bcs red_turret_exit
dec ENEMY_FRAME,x ; decrement enemy animation frame number
bpl red_turret_exit
jmp remove_enemy ; from bank 7
; red turret - pointer 6
red_turret_routine_05:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
lsr ; move bit 0 to carry
lda #$16 ; a = #$16 (rocky background)
bcc @draw_supertile_adv_routine ; branch if ENEMY_ATTRIBUTES specifies red turret has rocky background (bit 0 - 0)
lda #$17 ; a = #$17 (metal or waterfall background)
@draw_supertile_adv_routine:
jsr draw_enemy_supertile_a ; update red turret super-tile
bcs red_turret_exit ; exit if unable to update super-tile, will try again later
jmp advance_enemy_routine ; updated super-tile, advance to next routine
; red turrets only fire left and up left, checks to see if player is in firing range
; i.e. the player is above (or equal) and to the left of the red turret
; output
; * carry - set when red turret below player and to the right
check_red_turret_firing_range:
lda ENEMY_Y_POS,x ; load enemy y position on screen
clc ; clear carry in preparation for addition
adc #$20 ; add #$20 to allow firing if player and turret are at same height
cmp SPRITE_Y_POS,y ; player y position on screen
bcc @exit ; red turret above player, exit with carry clear
lda ENEMY_X_POS,x ; load enemy x position on screen
cmp SPRITE_X_POS,y ; set carry if red turret to right of player
@exit:
rts
; pointer table for running man (#$6 * #$2 = #$16 bytes)
soldier_routine_ptr_tbl:
.addr soldier_routine_00 ; CPU address $861e - slightly offset y position, set initial animation delay based on ENEMY_ATTRIBUTES
.addr soldier_routine_01 ; CPU address $8665 - set velocities, enable collision
.addr soldier_routine_02 ; CPU address $86af - soldier animation routine: walk, if jumping try to find find landing
.addr soldier_routine_03 ; CPU address $8803 - try and fire bullet
.addr soldier_routine_04 ; CPU address $88c3 - soldier hit, begin destroying soldier
.addr soldier_routine_05 ; CPU address $8900 - soldier hit, apply negative gravity
.addr enemy_routine_init_explosion ; CPU address $e74b
.addr enemy_routine_explosion ; CPU address $e7b0
.addr enemy_routine_remove_enemy ; CPU address $e806
.addr soldier_routine_09 ; CPU address $888c - soldier landing in water routine
.addr soldier_routine_0a ; CPU address $88a1 - continue splash animation and begin removing soldier
; running man - pointer 1
; slightly offset y position, set initial animation delay based on ENEMY_ATTRIBUTES
soldier_routine_00:
jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll
jsr add_4_to_enemy_y_pos ; adjust soldier down slightly so walks on ground
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
lsr ; shift right 4 times to load high byte
lsr
lsr
lsr
and #$03 ; keep bits 0,1,2
tay ; transfer offset to y
lda soldier_initial_anim_delay_tbl,y ; load enemy animation delay
jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a
; advance enemy routine
; table for soldier initial animation delay (#$4 bytes)
soldier_initial_anim_delay_tbl:
.byte $01,$10,$20,$30
; set soldier x velocity based on ENEMY_VAR_2 (#$00 left, #$01 right) (see soldier_x_vel_tbl)
; stop y velocity
soldier_stop_y_set_x_velocity:
jsr soldier_set_x_velocity ; set soldier x velocity based on ENEMY_VAR_2 (#$00 left, #$01 right)
jmp set_enemy_y_velocity_to_0 ; stop any y velocity
; set soldier x velocity based on ENEMY_VAR_2 index and level scrolling type
soldier_set_x_velocity:
ldy #$00 ; y = #$00
lda LEVEL_SCROLLING_TYPE ; 0 = horizontal, indoor/base; 1 = vertical
beq @continue ; horizontal scrolling, continue
ldy #$04 ; vertical level, add #$04 to offset, y = #$04
@continue:
sty $08 ; set offset into $08
lda ENEMY_VAR_2,x ; load soldier x direction (#$00 left, #$01 right)
asl ; multiply by #$02, each entry is #$02 bytes
clc ; clear carry in preparation for addition
adc $08 ; add result to base offset (#$00 or #$04)
tay ; transfer offset to y
lda soldier_x_vel_tbl,y ; load x velocity fractional byte
sta ENEMY_X_VELOCITY_FRACT,x ; set x velocity fractional byte
lda soldier_x_vel_tbl+1,y ; load x velocity fast byte
sta ENEMY_X_VELOCITY_FAST,x ; set x velocity fast byte
soldier_routine_exit:
rts
; table for running man (#$8 bytes)
; first pair of bytes per level scroll is moving left, second pair is moving right
; byte 0 - x fractional velocity
; byte 1 - x fast velocity
soldier_x_vel_tbl:
.byte $00,$ff ; (-1.00) (horizontal scrolling)
.byte $40,$01 ; ( 1.25) (horizontal scrolling)
.byte $00,$ff ; (-1.00) (vertical level)
.byte $00,$01 ; ( 1.00) (vertical level)
; running man - pointer 2
; set velocities, enable collision
soldier_routine_01:
lda LEVEL_SCROLLING_TYPE ; 0 = horizontal, indoor/base; 1 = vertical
beq @horizontal_level ; branch for horizontal/indoor levels
jsr add_scroll_to_enemy_pos ; vertical level, adjust enemy location based on scroll
jmp @dec_delay_enable_set_vel ; decrement animation delay and exit
@horizontal_level:
lda FRAME_SCROLL ; how much to scroll the screen (#00 - no scroll)
beq @dec_delay_enable_set_vel
lda ENEMY_ATTRIBUTES,x ; scrolling, load soldier enemy attributes
and #$01 ; keep bit 0 specifying run direction
beq @continue ; branch if running left
and FRAME_COUNTER ; running right, load frame counter
lsr ; push bit 0 to carry
bcs @dec_delay_enable_set_vel ; if odd frame, decrement animation delay
bcc soldier_routine_exit ; even frame, exit without decrementing animation delay
@continue:
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
beq @enable_set_vel
@dec_delay_enable_set_vel:
dec ENEMY_ANIMATION_DELAY,x
bne soldier_routine_exit
@enable_set_vel:
ldy #$10 ; y = #$10
jsr add_y_to_y_pos_get_bg_collision ; add #$10 to enemy y position and gets bg collision code
bne @enable_collision_set_vel ; branch if collision with ground, water, or solid
jmp remove_enemy ; remove enemy if no collision
; soldier wasn't placed in an appropriate position, e.g. bridge destroyed
@enable_collision_set_vel:
jsr enable_enemy_collision ; enable bullet-enemy collision and player-enemy collision checks
lda ENEMY_ATTRIBUTES,x ; load soldier enemy attributes
and #$01 ; keep soldier running direction
sta ENEMY_VAR_2,x ; set running direction in ENEMY_VAR_2
beq @stop_y_set_x_adv_routine ; if running left, branch
lda #$0a ; running right, set x position to #$0a, a = #$0a
sta ENEMY_X_POS,x ; set enemy x position on screen
@stop_y_set_x_adv_routine:
jsr soldier_stop_y_set_x_velocity ; set soldier x velocity based on direction (ENEMY_VAR_2); set y velocity to #$00
lda #$10 ; a = #$10
jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a; advance enemy routine
; Running Man - Pointer 3
; soldier animation routine: walk, if jumping try to find find landing
soldier_routine_02:
lda ENEMY_VAR_3,x ; see if soldier is jumping or not
beq @continue ; branch if not jumping
lda #$0a ; soldier jumping, set a = #$0a
sta ENEMY_FRAME,x ; set enemy animation frame number to jumping frame
lda ENEMY_Y_VELOCITY_FAST,x ; load y velocity
bmi @no_landing
ldy #$10 ; y = #$10
jsr add_y_to_y_pos_get_bg_collision ; add #$10 to enemy y position and gets bg collision code
bmi @floor_solid_landing ; branch if landed on solid object
bcc @land_in_water_or_no_landing ; branch if not a collision with the floor, i.e. empty, or water collision
; floor or solid collision
@floor_solid_landing:
lda #$00 ; a = #$00
sta ENEMY_VAR_3,x ; clear flag specifying soldier is jumping
sta ENEMY_FRAME,x ; set enemy animation frame number
jsr add_4_to_enemy_y_pos
jsr soldier_stop_y_set_x_velocity ; set soldier x velocity based on direction (ENEMY_VAR_2); set y velocity to #$00
jmp soldier_apply_vel_check_solid_collision
@land_in_water_or_no_landing:
cmp #$02 ; see if water collision code (#$02)
bne @no_landing ; branch if not a water collision, i.e. floor collision or empty collision
lda #$0a ; soldier landed in water, change routine, a = #$0a
jsr set_enemy_routine_to_a ; set enemy routine index to soldier_routine_09
@no_landing:
jsr add_10_to_enemy_y_fract_vel ; add #$10 to y fractional velocity (.06 faster)
jmp soldier_apply_vel_check_solid_collision
; soldier_routine_02 - soldier not jumping
@continue:
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
and #$0c ; keep bits .... xx..
beq @continue_walk_routine ; soldier doesn't fire, continue walking routine
lda ENEMY_ATTACK_FLAG ; see if enemies should attack
beq @continue_walk_routine ; enemies shouldn't attack, continue walking routine
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne @continue_walk_routine ; delay timer hasn't elapsed, continue walking routine
lda #$80 ; a = #$80
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
lda #$08 ; a = #$08
sta ENEMY_ATTACK_DELAY,x ; set enemy attack delay to #$08
jsr get_soldier_num_bullets ; get random number of bullets to fire (influenced by PLAYER_WEAPON_STRENGTH)
sta ENEMY_VAR_3,x ; set number of bullets to fire
jsr advance_enemy_routine ; advance to soldier_routine_03
jmp set_soldier_sprite_update_pos ; set soldier sprite and apply velocities to position
; soldier isn't firing
@continue_walk_routine:
inc ENEMY_VAR_A,x ; increment ENEMY_FRAME update timer
lda ENEMY_VAR_A,x ; load ENEMY_FRAME update timer
and #$07 ; keep bits 0, 1, and 2
bne @soldier_move ; continue if #$08 frames haven't elapsed
inc ENEMY_FRAME,x ; increment enemy animation frame number
lda ENEMY_FRAME,x ; load enemy animation frame number
cmp #$06 ; compare to last frame of soldier animation sequence
bcc @soldier_move ; continue if not past last frame
lda #$00 ; animated past last frame, go back to #$0th frame
sta ENEMY_FRAME,x ; set enemy animation frame number to first frame
@soldier_move:
ldy #$10 ; increment y position by #$10
lda ENEMY_X_VELOCITY_FAST,x ; load x fast velocity
jsr add_a_y_to_enemy_pos_get_bg_collision ; add a to X position and y to Y position; get bg collision code
bmi soldier_apply_vel_check_solid_collision ; branch if enemy collided with solid bg object
bcs soldier_apply_vel_check_solid_collision ; branch if collision with floor (#$01)
lda ENEMY_VAR_4,x ; no collision, or collision with water
cmp #$02 ; compare the number of times the soldier has already turned around
; if #$02, have soldier jump off ledge
bcs @soldier_fall_off_ledge ; branch if soldier has decided to jump off ledge
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
and #$02 ; see if soldier should turn around at edge
beq @soldier_fall_off_ledge ; branch if soldier should walk off edge
jsr soldier_change_direction ; soldier should turn around, change direction
jmp soldier_apply_vel_check_solid_collision ; apply velocity
@soldier_fall_off_ledge:
inc ENEMY_VAR_3,x ; set that the soldier is jumping
jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01)
lda SPRITE_Y_POS,y ; load the y position of the closest player
sec ; set carry flag in preparation for subtraction
sbc ENEMY_Y_POS,x ; subtract closest player y position from the enemy y position on screen
ldy #$04 ; set default soldier_vel_index_tbl offset (higher probability of larger y jump)
bcs @cmp_player_dist ; branch if no underflow occurred
eor #$ff ; underflow occurred, flip all bits and add #$01 to get absolute value
adc #$01
ldy #$00 ; set soldier_vel_index_tbl (higher probability of larger x jump)
@cmp_player_dist:
cmp #$10 ; compare closest player and enemy y distance
bcs @soldier_set_jump_vel ; branch if enemy is far away from player vertically to keep the configured y
ldy #$00 ; if enemy is close, give higher probability of shorter y jump
@soldier_set_jump_vel:
sty $08 ; store current soldier_vel_index_tbl offset into $08
lda RANDOM_NUM ; load random number
and #$03 ; random number between #$00 and #$03
clc ; clear carry in preparation for addition
adc $08 ; add random number between #$00 and #$03 to current soldier_vel_index_tbl offset (#$00 or #$04)
tay ; transfer offset to y
lda soldier_vel_index_tbl,y ; load appropriate jump velocity configuration
tay ; transfer jump velocity configuration to y
lda soldier_velocity_tbl,y ; load jump y fractional velocity
sta ENEMY_Y_VELOCITY_FRACT,x ; set jump y fractional velocity
lda soldier_velocity_tbl+1,y ; load jump y fast velocity
sta ENEMY_Y_VELOCITY_FAST,x ; se jump y fast velocity
lda soldier_velocity_tbl+2,y ; load jump x fractional velocity
sta ENEMY_X_VELOCITY_FRACT,x ; set jump x fractional velocity
lda soldier_velocity_tbl+3,y ; load jump x fast velocity
sta ENEMY_X_VELOCITY_FAST,x ; set jump x fast velocity
lda ENEMY_VAR_2,x ; load enemy running direction
beq @set_sprite_update_pos ; branch if running left
jsr reverse_enemy_x_direction ; reverse enemy's x velocities if running right
; soldier_velocity_tbl had values assuming running left
@set_sprite_update_pos:
jmp set_soldier_sprite_update_pos ; set soldier sprite and apply velocities to position
soldier_apply_vel_check_solid_collision:
jsr check_enemy_collision_solid_bg ; see if soldier is colliding with solid object
bpl @continue ; continue if solid collision code
lda #$07 ; no solid collision, go to soldier_routine_09, a = #$07
jmp set_enemy_routine_to_a ; set enemy routine index soldier_routine_09
@continue:
lda ENEMY_VAR_4,x
cmp #$02
bcs set_soldier_sprite_update_pos ; set soldier sprite and apply velocities to position
lda #$f8 ; set amount to adjust x position to -8
ldy ENEMY_VAR_2,x ; load current enemy direction (#$00 = left, #$01 = right)
beq @check_collision_update_x_pos ; continue to add -8 to x position if current direction is left
lda #$08 ; direction is right, set x amount to add to #$08
; update soldier location, if on screen see if running into a solid object, if so, turn soldier around
; input
; * a - amount to add to x position
@check_collision_update_x_pos:
clc ; clear carry in preparation for addition
adc ENEMY_X_POS,x ; add to enemy x position on screen
cmp #$f0 ; see if off screen to the right
bcs set_soldier_sprite_update_pos ; branch if far right to set soldier sprite and apply velocities to position
cmp #$10 ; see if off screen to the left
bcc set_soldier_sprite_update_pos ; branch if far left to set soldier sprite and apply velocities to position
ldy ENEMY_Y_POS,x ; on screen, load enemy y position on screen
jsr get_bg_collision_far ; determine player background collision code at position (a,y)
bpl set_soldier_sprite_update_pos ; branch if solid collision to set soldier sprite and apply velocities to position
jsr soldier_change_direction ; solid collision, turn soldier around
set_soldier_sprite_update_pos:
jsr set_soldier_sprite ; set soldier sprite code based on ENEMY_FRAME
jmp update_enemy_pos ; apply velocities and scrolling adjust
soldier_change_direction:
inc ENEMY_VAR_4,x
lda ENEMY_VAR_2,x ; load current enemy x direction
eor #$01 ; swap offset to turn the soldier around (change soldier x direction)
; #$00 -> #$01, or #$01 -> #$0
sta ENEMY_VAR_2,x ; update soldier direction
jmp soldier_set_x_velocity ; set soldier x velocity based on ENEMY_VAR_2 index (#$00 left, #$01 right)
; randomly (based on PLAYER_WEAPON_STRENGTH) determine the number of bullets to fire and store in a
; output
; * a - number of bullets to fire, #$00 or #$02
get_soldier_num_bullets:
lda PLAYER_WEAPON_STRENGTH ; load player weapon strength
and #$02 ; keep bit 1 (FSL)
asl ; shift left
sta $08 ; store value in $08
lda RANDOM_NUM ; load random number
and #$03 ; between #$00 and #$03
adc $08 ; add value between #$00 and #$04
tay ; transfer to y for offset
lda soldier_num_bullets_tbl,y ; load ENEMY_VAR_3
rts
; table for soldier running direction (#$8 bytes)
; PLAYER_WEAPON_STRENGTH increases chances of soldiers firing twice
soldier_num_bullets_tbl:
.byte $01,$01,$02,$01,$02,$01,$02,$02
; running man possible jumping codes (#$8 bytes)
; offset into table at soldier_velocity_tbl below
; higher probability of
soldier_vel_index_tbl:
.byte $00,$00,$04,$00,$04,$00,$04,$04
; table for running man jumping velocities (#$8 byte)
; grouped into 2 sections
; first #$04 bytes is a larger y jump, but a shorter x jump
; second #$04 bytes is a shorter y jump, but a farther x jump
soldier_velocity_tbl:
.byte $00,$fe ; jumping y velocity
.byte $48,$ff ; jumping x velocity
.byte $00,$ff ; jumping y velocity
.byte $60,$ff ; jumping x velocity
; running man - pointer 4 - try and fire bullet
soldier_routine_03:
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
and #$0c ; keep bits .... xx..
cmp #$05 ; see if should fire bullet
ldy #$00 ; set y = #$00 for standing bullet y pos offset index (soldier_bullet_y_offset offset)
lda #$06 ; ENEMY_FRAME #$06 (see soldier_sprite_codes) (sprite_40 - soldier shooting)
bcc @continue ; branch if soldier should shoot while standing
lda #$1b ; ENEMY_ATTRIBUTES bit 3 is set, enemy crouches to shoot, set a = #$1b
sta ENEMY_SCORE_COLLISION,x ; update collision code for crouching soldier (score byte (high byte) is already #$01)
ldy #$02 ; set y = #$02 for crouching bullet y pos offset index (soldier_bullet_y_offset offset)
lda #$07 ; ENEMY_FRAME #$07 (see soldier_sprite_codes) (sprite_26 - soldier crouching shooting)
@continue:
sta ENEMY_FRAME,x ; set the enemy animation frame (either shooting while standing, or shooting while crouching)
dec ENEMY_ATTACK_DELAY,x ; decrement enemy attack delay
bne set_soldier_sprite_add_scroll_01 ; attack delay hasn't elapsed, update sprite and exit
dec ENEMY_VAR_3,x ; decrement number of bullets to fire
bmi soldier_fired_all_bullets ; branch if fired all bullets to reset and go to soldier_routine_02
lda #$10 ; another bullet to fire, a = #$10
sta ENEMY_ATTACK_DELAY,x ; set attack delay to #$10
lda ENEMY_VAR_2,x ; load soldier running direction (#$00 left, #$01 right)
beq @set_bullet_pos_and_fire ; branch if running left
iny ; running right, increment soldier_bullet_y_offset offset
; determine where bullet should generate based on enemy pos and fire if on screen
@set_bullet_pos_and_fire:
lda ENEMY_Y_POS,x ; load enemy y position on screen
clc ; clear carry in preparation for addition
adc soldier_bullet_y_offset,y ; add bullet y position offset to enemy position
sta $08 ; set bullet y position
lda soldier_bullet_x_offset,y ; load bullet x position offset
clc
bmi @negative_x_offset ; branch if x position offset of bullet is negative
adc ENEMY_X_POS,x ; bullet x position offset positive, add to enemy x position on screen
bcs set_soldier_sprite_add_scroll_01 ; branch to update sprite and exit if soldier's bullet would be offscreen to the right
bcc @soldier_fire_bullet
@negative_x_offset:
adc ENEMY_X_POS,x ; add enemy x position on screen
bcc set_soldier_sprite_add_scroll_01 ; branch to update sprite and exit if soldier's bullet would be offscreen
cmp #$08
bcc set_soldier_sprite_add_scroll_01 ; branch to update sprite and exit if soldier's bullet would be offscreen to the left
@soldier_fire_bullet:
sta $09
ldy ENEMY_VAR_2,x ; set y to walking direction (#$00 - left, #$01 - right)
lda soldier_bullet_type_tbl,y ; load bullet type
ldy #$06 ; y = #$06 (regular bullet firing left (angle #$0c)
jsr bullet_generation ; create enemy bullet (ENEMY_TYPE #$02) of type a (and angle) with speed y at ($09, $08)
bne set_soldier_sprite_add_scroll_01 ; branch if unable to create bullet
lda #$06 ; a = #$06
sta ENEMY_VAR_1,x ; set gun recoil timer
set_soldier_sprite_add_scroll_01:
jsr set_soldier_sprite ; set soldier sprite code based on ENEMY_FRAME
jmp add_scroll_to_enemy_pos ; adjust enemy location based on scroll
; soldier has fired all bullets, stand back up if crouching,
; set enemy routine back soldier animation routine (soldier_routine_02)
soldier_fired_all_bullets:
lda #$10 ; a = #$10
sta ENEMY_SCORE_COLLISION,x ; reset collision box in case soldier was crouched
lda #$00 ; a = #$00
sta ENEMY_VAR_3,x ; reset number of bullets to fire to #$00
sta ENEMY_FRAME,x ; reset enemy animation frame (see soldier_sprite_codes) (sprite_3b - soldier running frame #$00)
jsr set_soldier_sprite ; set soldier sprite code based on ENEMY_FRAME
jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll
lda #$03 ; a = #$03
jmp set_enemy_routine_to_a ; set enemy routine index to soldier_routine_02
; table for specifying y offset from soldier position of generated bullets for soldier (#$4 bytes)
; first two bytes are while soldier is standing, second 2 bytes are for crouching and firing
; byte 0 - running left
; byte 1 - running right
soldier_bullet_y_offset:
.byte $f7,$f7 ; -9, -9 - standing and firing
.byte $0a,$0a ; 10, 10 - crouching and firing
; table for specifying x offset from soldier position of generated bullets for soldier (#$4 bytes)
; first two bytes are while soldier is standing, second 2 bytes are for crouching and firing
; byte 0 - running left
; byte 1 - running right
soldier_bullet_x_offset:
.byte $f0,$10 ; -16, 10 standing and firing
.byte $f0,$10 ; -16, 10 crouching and firing
; table for the bullet type and angle to generate for a soldier (#$2 bytes)
; byte 0 - soldier walking left (#$0c firing direction)
; byte 1 - soldier walking right
soldier_bullet_type_tbl:
.byte $06,$00
; running man - pointer a
; soldier landing in water routine
soldier_routine_09:
lda #$08 ; ENEMY_FRAME #$08 (see soldier_sprite_codes) (sprite_73 - water splash)
sta ENEMY_FRAME,x ; set enemy animation frame number to be a water splash, soldier is landing in water
lda #$10 ; a = #$10
jsr soldier_set_y_pos_sprite_add_scroll ; add #$10 to solider y position to slightly lower his position in the water
jsr set_soldier_sprite ; set soldier sprite code based on ENEMY_FRAME (water splash)
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda #$08 ; a = #$08
jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY and advance enemy routine to soldier_routine_0a
; running man - pointer b
; continue splash animation and begin removing soldier
soldier_routine_0a:
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne set_soldier_sprite_add_scroll ; continue animation if should still show splash
lda #$08 ; splash animation complete, begin removing soldier
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter to #$08
inc ENEMY_FRAME,x ; increment enemy animation frame number to #$09 (see soldier_sprite_codes) (sprite_18 - water splash/puddle)
lda ENEMY_FRAME,x ; load enemy animation frame number
cmp #$0a ; compare ENEMY_FRAME to #$0a (water splash sprite)
bcc @continue ; branch if haven't completed showing sprite_18
jmp remove_enemy ; animation elapsed and sprite_18 has been shown, remove soldier (from bank 7)
@continue:
lda #$08 ; add #$08 to enemy y position, a = #$08
soldier_set_y_pos_sprite_add_scroll:
jsr add_a_to_enemy_y_pos ; add a to y position on screen
set_soldier_sprite_add_scroll:
jsr set_soldier_sprite ; set soldier sprite code based on ENEMY_FRAME
jmp add_scroll_to_enemy_pos ; add scrolling to enemy position
; running man - pointer 5
; soldier hit, begin destroying soldier
soldier_routine_04:
lda #$0b ; a = #$0b
sta ENEMY_FRAME,x ; set enemy animation frame number
jsr set_soldier_sprite ; set soldier sprite code based on ENEMY_FRAME
; set velocities for soldier being hit to
; -4.5 y velocity
; 6.0 x velocity
init_soldier_hit_vel:
jsr disable_enemy_collision ; prevent player enemy collision check and allow bullets to pass through enemy
lda #$80 ; a = #$80
sta ENEMY_Y_VELOCITY_FRACT,x ; initial y velocity when enemy fly up (low)
lda #$fc ; a = #$fc
sta ENEMY_Y_VELOCITY_FAST,x ; initial y velocity when enemy fly up (high)
lda #$60 ; a = #$60
sta ENEMY_X_VELOCITY_FRACT,x ; initial x velocity when enemy fly up (low)
lda #$00 ; a = #$00
sta ENEMY_X_VELOCITY_FAST,x ; initial x velocity when enemy fly up (high)
lda ENEMY_X_POS,x ; load enemy x position on screen
cmp #$10 ; compare enemy x position to left edge
bcc @stop_x_vel_set_delay_adv_routine ; stop x velocity if on left edge
cmp #$f0 ; compare to right edge
bcc @set_dir_delay_adv_routine ; branch if not near right edge, otherwise, stop x velocity
@stop_x_vel_set_delay_adv_routine:
jsr set_enemy_x_velocity_to_0 ; set x velocity to zero
@set_dir_delay_adv_routine:
lda ENEMY_VAR_2,x ; load soldier running direction
beq @set_delay_adv_routine ; branch if running left
jsr reverse_enemy_x_direction ; reverse enemy's x direction to face left
@set_delay_adv_routine:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda #$10 ; a = #$10
jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a; advance enemy routine
; running man - pointer 6
; soldier hit, apply negative gravity
soldier_routine_05:
jsr set_soldier_sprite ; set soldier sprite code based on ENEMY_FRAME
; applies gravity to destroyed soldier that is floating up
; removes enemy if off screen, or if animation timer has elapsed
apply_gravity_to_destroyed_soldier:
lda #$30 ; a = #$30 (gravity to slow enemy down as they are flying up)
jsr add_a_to_enemy_y_fract_vel ; add #$30 to enemy y fractional velocity
lda ENEMY_Y_POS,x ; load enemy y position on screen
cmp #$08 ; compare to top of screen
bcc @adv_enemy_routine ; branch if soldier moved off top of screen, move to next routine
jsr update_enemy_pos ; apply velocities and scrolling adjust
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne soldier_routine_05_exit ; exit if animation timer hasn't elapsed
@adv_enemy_routine:
jmp advance_enemy_routine ; advance to next routine
; set soldier sprite code based on ENEMY_FRAME
set_soldier_sprite:
ldy ENEMY_FRAME,x ; enemy animation frame number
lda soldier_sprite_codes,y ; load appropriate sprite code
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
lda #$40 ; a = #$40 (running man facing right, flip sprite horizontally)
ldy ENEMY_VAR_2,x ; load running direction
beq @set_sprite_attr ; branch if running left
lda #$00 ; a = #$00 (running man facing right, don't flip sprite)
@set_sprite_attr:
ldy ENEMY_VAR_1,x ; load gun recoil timer
beq @set_sprite_attr_exit ; exit if not firing weapon
dec ENEMY_VAR_1,x ; decrement gun recoil timer
ora #$08 ; set bits .... x... (gun recoil flag)
@set_sprite_attr_exit:
sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes
soldier_routine_05_exit:
rts
; table for running man sprite codes (#$c bytes)
; sprite_18, sprite_26, sprite_27, sprite_28
; sprite_3b, sprite_3c, sprite_3d, sprite_3e
; sprite_3f, sprite_40, sprite_73
soldier_sprite_codes:
.byte $3b,$3c,$3d,$3f,$3c,$3e,$40,$26,$73,$18,$28,$27
; pointer table for rifle man (#$9 * #$2 = #$12 bytes)
sniper_routine_ptr_tbl:
.addr sniper_routine_00 ; CPU address $8958 - load variables (ENEMY_ANIMATION_DELAY, ENEMY_FRAME), adjust y pos for crouching sniper
.addr sniper_routine_01 ; CPU address $8982 - cycle crouch animation (if crouching sniper), enable collision (when standing only for crouching snipers)
.addr sniper_routine_02 ; CPU address $89d2 - attack
.addr sniper_routine_03 ; CPU address $8ab3
.addr sniper_routine_04 ; CPU address $8af1
.addr sniper_routine_05 ; CPU address $8afc
.addr enemy_routine_init_explosion ; CPU address $e74b from bank 7
.addr enemy_routine_explosion ; CPU address $e7b0 from bank 7
.addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7
; rifle man - pointer 1
; load variables (ENEMY_ANIMATION_DELAY, ENEMY_FRAME), adjust y pos for crouching sniper
sniper_routine_00:
ldy ENEMY_ATTRIBUTES,x ; load sniper type (#$00 = standing, #$01 = hiding, #$02 = boss hiding)
lda sniper_animation_delay_tbl,y ; load animation delay based on sniper type
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
lda sniper_frame_tbl,y ; load ENEMY_FRAME (sniper_sprite_xx offset)
sta ENEMY_FRAME,x ; set enemy animation frame number
jsr add_4_to_enemy_y_pos ; adjust sniper slightly down
lda ENEMY_ATTRIBUTES,x ; load sniper type (#$00 = standing, #$01 = hiding, #$02 = boss hiding)
cmp #$01 ; see if sniper type #$01 (crouching sniper)
bne @adv_enemy_routine ; advance routine if standing sniper, or boss screen sniper
lda #$05 ; crouching sniper adjust y position by #$05
jsr add_a_to_enemy_y_pos ; lower enemy y position on screen
@adv_enemy_routine:
jmp advance_enemy_routine
; table for rifle man initial animation delay (#$3 bytes)
; each byte is for each sniper type #$00, #$01, or #$04
sniper_animation_delay_tbl:
.byte $01,$30,$80
; table for rifle man sniper_routine_03 animation delay (#$3 bytes)
; each byte is for each sniper type #$00, #$01, or #$04
sniper_animation_delay_2_tbl:
.byte $01,$60,$80
; table for rifle man initial ENEMY_FRAME (#$3 bytes)
; each byte is for each sniper type #$00, #$01, or #$04
; offsets into sniper_sprite_xx table
; byte 0 - sprite_43 or sprite_2c (sniper aiming horizontally)
; byte 1 and byte 2 - sprite_44 (sniper crouched behind bush)
sniper_frame_tbl:
.byte $03,$00,$00
; rifle man - pointer 2
; cycle crouch animation (if crouching sniper), enable collision (when standing only for crouching snipers)
sniper_routine_01:
jsr sniper_set_sprite ; set sprite and attributes based on sniper type and firing angle
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne sniper_routine_exit ; exit if animation delay hasn't elapsed
ldy ENEMY_ATTRIBUTES,x ; load sniper type (#$00 = standing, #$01 = hiding, #$02 = boss hiding)
beq @enable_collision_adv_routine ; exit if sniper type #$00 (standing sniper), the following logic is only for crouching snipers
lda #$08 ; crouching or boss screen sniper, set a = #$08
sta ENEMY_ANIMATION_DELAY,x ; set delay between frames when un-hiding to #$08
inc ENEMY_FRAME,x ; increment enemy animation frame number (sniper_sprite_xx offset)
lda ENEMY_FRAME,x ; load current enemy animation frame number (sniper_sprite_xx offset)
cmp #$03 ; see if last sprite of animation
bcc sniper_routine_exit ; exit if animation cycle not yet complete
cpy #$01 ; see if ENEMY_FRAME is #$01 (sprite_45 - rifle man behind bush (frame 2))
bne @continue ; branch if not finished crouch animation
dec ENEMY_FRAME,x ; decrement enemy animation frame number
bne @enable_collision_adv_routine
@continue:
lda #$f2 ; a = #$f2 (-14)
jsr add_a_to_enemy_y_pos ; add #$f2 (-14) to enemy y position on screen
lda #$01 ; a = #$01
jsr add_a_to_enemy_x_pos ; add #$01 to enemy x position on screen
@enable_collision_adv_routine:
jsr enable_enemy_collision ; enable bullet-enemy collision and player-enemy collision checks
lda #$30 ; a = #$30 (related to score and collision test)
sta ENEMY_SCORE_COLLISION,x ; set score code to #$03, collision code to #$00
lda sniper_attack_delay_tbl,y ; load attack delay
sta ENEMY_ATTACK_DELAY,x ; set delay between attacks based on sniper type
lda sniper_bullet_attack_count_tbl,y ; load number of bullets for attack round
sta ENEMY_VAR_4,x ; enemy bullet counter
jmp advance_enemy_routine ; advance to sniper_routine_02
sniper_routine_exit:
rts
; table for rifle man (#$3 bytes)
; #$40 - delay before resuming attack - standing rifle man
; #$04 - delay before shooting after un-hiding (hiding rifle man)
; #$10 - delay before shooting after un-hiding (boss screen rifle man)
sniper_attack_delay_tbl:
.byte $40,$04,$10
; table for rifle man, number of bullets per attack (#$3 bytes)
; #$03 - number of bullets to shoot per attack - standing rifle man
; #$01 - number of bullets to shoot per attack - hiding rifle man
; #$03 - number of bullets to shoot per attack - boss screen rifle man
sniper_bullet_attack_count_tbl:
.byte $03, $01,$03
; rifle man - pointer 3
sniper_routine_02:
jsr sniper_set_sprite ; set sprite and attributes based on sniper type and firing angle
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
dec ENEMY_ATTACK_DELAY,x ; decrement delay between attacks
bne sniper_routine_exit ; exit if attack delay hasn't elapsed
dec ENEMY_VAR_4,x ; decrement enemy bullet counter
bpl @continue_fire_bullet ; if bullets left to fire, branch
jmp @standing_set_attack_count_exit ; fired all bullets, jump
@continue_fire_bullet:
lda #$18 ; a = #$18
sta ENEMY_ATTACK_DELAY,x ; set attack delay between bullets
jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01)
sty $0a ; store closest player index in $0a
lda SPRITE_X_POS,y ; load the x position of the closest player
cmp ENEMY_X_POS,x ; enemy x position on screen
lda #$00 ; a = #$00
bcc @check_bullet_angle ; branch if closest player is left of the enemy
lda #$01 ; closest player to right of enemy, set a = #$01
@check_bullet_angle:
sta ENEMY_VAR_2,x ; #$00 if player to left, #$01 if player to right of enemy
lda ENEMY_ATTRIBUTES,x ; load sniper type (#$00 = standing, #$01 = hiding, #$02 = boss hiding)
lsr
bcc @adjust_bullet_angle_y_offset
lda #$00 ; a = #$00
ldy ENEMY_VAR_2,x ; bullet angle
bne @adjust_bullet_angle_with_a
lda #$0c ; a = #$0c
@adjust_bullet_angle_with_a:
sta $0c ; set bullet type (xxx. ....) and angle index (...x xxxx)
jmp @adjust_bullet_angle
@adjust_bullet_angle_y_offset:
ldy #$00 ; y = #$00
lda ENEMY_ATTRIBUTES,x ; load sniper type (#$00 = standing, #$01 = hiding, #$02 = boss hiding)
cmp #$02 ; compare to boss screen sniper
bne @prep_create_bullet_vars ; branch if not boss screen sniper
ldy #$f0 ; set vertical offset from enemy position for crouching sniper (param for add_with_enemy_pos)
@prep_create_bullet_vars:
lda #$00 ; set horizontal offset from enemy position (param for add_with_enemy_pos)
jsr add_with_enemy_pos ; stores absolute screen x position in $09, and y position in $08
jsr get_rotate_01 ; get enemy aim direction and rotation direction using quadrant_aim_dir_01
@adjust_bullet_angle:
lda $0c ; load bullet aim direction
clc ; clear carry in preparation for addition
adc #$06 ; add one quadrant to the calculated direction
cmp #$18 ; compare to maximum direction code (3 o'clock)
bcc @continue
sbc #$18 ; wrapped around, subtract max value
; i.e. a = ($0c + #$06) % #$18
@continue:
cmp #$0c ; compare the midway aim direction (9 o'clock)
bcc @continue2 ; branch if player is in quadrant I
sta $08
lda #$18 ; a = #$18
sec ; set carry flag in preparation for subtraction
sbc $08
@continue2:
ldy #$00 ; y = #$00
cmp #$05
bcc @continue_create_bullet
iny
cmp #$08
bcc @continue_create_bullet
iny
@continue_create_bullet:
lda ENEMY_ATTRIBUTES,x ; load sniper type (#$00 = standing, #$01 = crouching, #$02 = boss screen crouching)
cmp #$01 ; compare to crouching sniper
beq @get_pos_create_bullet ; branch if crouching sniper
lda sniper_standing_sprite_tbl,y ; standing sniper, or boss screen sniper, load sprite
sta ENEMY_FRAME,x ; set enemy animation frame number
@get_pos_create_bullet:
lda ENEMY_Y_POS,x ; load sniper y position
clc ; clear carry in preparation for addition
adc sniper_bullet_y_offset,y ; add bullet y offset
sta $08 ; store bullet creation y location
lda ENEMY_VAR_2,x ; load sniper firing angle
lsr
lda sniper_bullet_x_offset,y
bcc @create_bullet
eor #$ff ; negative offset, flip all bits and add #$01
adc #$00
@create_bullet:
clc ; clear carry in preparation for addition
adc ENEMY_X_POS,x ; add to enemy x position on screen
sta $09 ; store bullet creation x location
ldy ENEMY_ATTRIBUTES,x ; load sniper type (#$00 = standing, #$01 = hiding, #$02 = boss hiding)
lda sniper_bullet_speed,y ; load bullet speed based on sniper type
tay ; transfer bullet speed to y
lda $0c ; load bullet type (xxx. ....) and angle index (...x xxxx)
; bullet type is going to be #$00
jsr create_enemy_bullet_angle_a ; create a bullet with speed y, bullet type a, angle a at position ($09, $08)
bne @exit
lda #$06 ; a = #$06
sta ENEMY_VAR_3,x ; sniper firing, set to #$03
@exit:
rts
@standing_set_attack_count_exit:
ldy ENEMY_ATTRIBUTES,x ; load sniper type (#$00 = standing, #$01 = hiding, #$02 = boss hiding)
bne @set_frame_adv_routine ; branch if sniper that crouches
lda sniper_bullet_attack_count_tbl,y ; standing sniper, load bullet attack count
sta ENEMY_VAR_4,x ; set enemy bullet counter
lda #$80 ; a = #$80
sta ENEMY_ATTACK_DELAY,x ; set delay between attacks (standing)
rts
@set_frame_adv_routine:
lda ENEMY_ATTRIBUTES,x ; load sniper type (#$00 = standing, #$01 = hiding, #$02 = boss hiding)
lsr ; shift bit 0 to carry
lda #$02 ; a = #$02
bcs @set_enemy_frame_adv_routine ; branch if sniper type #$01 (crouching) to set frame #$02 (sprite_46)
lda #$03 ; boss screen sniper, sprite_2c by setting ENEMY_FRAME to #$03
@set_enemy_frame_adv_routine:
sta ENEMY_FRAME,x ; set enemy animation frame number
lda #$80 ; a = #$80 (delay before re-hiding)
jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a; advance enemy routine
; tile codes while shooting - standing rifle man
; $04 = shooting up
; $03 = shooting straight
; $05 = shooting down
sniper_standing_sprite_tbl:
.byte $04,$03,$05
; initial y offset of bullets - standing rifle man
sniper_bullet_y_offset:
.byte $ee,$f5,$06
; initial x offset of bullets - standing rifle man
sniper_bullet_x_offset:
.byte $f3,$f1,$f1
; bullet speed code
; $03 = bullet speed code for standing rifle man
; $05 = bullet speed code for hiding rifle man
; $03 = bullet speed code for boss screen sniper
sniper_bullet_speed:
.byte $03,$05,$03
; rifle man - pointer 4
sniper_routine_03:
dec ENEMY_ANIMATION_DELAY,x
bne @set_sprite_add_scroll_exit
jsr disable_enemy_collision ; prevent player enemy collision check and allow bullets to pass through enemy
lda #$08 ; a = #$08
sta ENEMY_ANIMATION_DELAY,x ; delay between frames when hiding
dec ENEMY_FRAME,x ; decrement enemy animation frame number
bne @continue
ldy ENEMY_ATTRIBUTES,x ; load sniper type (#$00 = standing, #$01 = hiding, #$02 = boss hiding)
lda sniper_animation_delay_2_tbl,y
sta ENEMY_ANIMATION_DELAY,x
lda #$02 ; a = #$02
jsr set_enemy_routine_to_a ; set enemy routine index to a
@continue:
lda ENEMY_ATTRIBUTES,x ; load sniper type (#$00 = standing, #$01 = hiding, #$02 = boss hiding)
cmp #$02
bne @set_sprite_add_scroll_exit
lda ENEMY_FRAME,x ; load enemy animation frame number
cmp #$02
bne @set_sprite_add_scroll_exit
lda #$0e ; a = #$0e
jsr add_a_to_enemy_y_pos ; add a to enemy y position on screen
lda #$ff ; a = #$ff
jsr add_a_to_enemy_x_pos ; add #$ff to enemy x position on screen
@set_sprite_add_scroll_exit:
jsr sniper_set_sprite ; set sprite and attributes based on sniper type and firing angle
jmp add_scroll_to_enemy_pos ; adjust enemy location based on scroll
; rifle man - pointer 5
sniper_routine_04:
lda #$06 ; a = #$06
sta ENEMY_FRAME,x ; set enemy animation frame number
jsr sniper_set_sprite ; set sprite and attributes based on sniper type and firing angle
jmp init_soldier_hit_vel ; set the velocities for sniper to start floating after hit
; rifle man - pointer 6
sniper_routine_05:
jsr sniper_set_sprite ; set sprite and attributes based on sniper type and firing angle
jmp apply_gravity_to_destroyed_soldier ; apply gravity to destroyed sniper that is floating up
; removes enemy if off screen, or if animation timer has elapsed
; set sniper sprite and sprite attributes based on sniper type and firing angle
sniper_set_sprite:
ldy #$00 ; default to use sniper_sprite_00
lda ENEMY_ATTRIBUTES,x ; load sniper type (#$00 = standing, #$01 = hiding, #$02 = boss hiding)
cmp #$02 ; see if sniper type #$02 (boss screen sniper)
bcc @continue ; branch if not boss screen sniper
ldy #$02 ; boss screen sniper, use sniper_sprite_01
@continue:
lda sniper_sprite_ptr_tbl,y ; load low byte
sta $08 ; set low byte
lda sniper_sprite_ptr_tbl+1,y ; load high byte
sta $09 ; set high byte
ldy ENEMY_FRAME,x ; load current ENEMY_FRAME index
lda ($08),y ; load specific sprite code from sniper_sprite_xx
sta ENEMY_SPRITES,x ; set enemy sprite code to CPU buffer
lda ENEMY_VAR_2,x ; load sniper firing angle
lsr ; shift right
lda #$40 ; a = #$40
bcc @continue2 ; branch if bullet bit 0 was clear
lda #$00 ; a = #$00
@continue2:
ldy ENEMY_VAR_3,x
beq @set_sprite_attr_exit
dec ENEMY_VAR_3,x
ora #$08 ; set bits .... x... (gun recoil flag)
@set_sprite_attr_exit:
sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes
rts
; pointer table for rifle man sprite codes (#$2 * #$2 = #$4 bytes)
sniper_sprite_ptr_tbl:
.addr sniper_sprite_00 ; CPU address $8b3b - regular/hiding rifle man (sniper type #$00 and type #$01)
.addr sniper_sprite_01 ; CPU address $8b42 - boss screen sniper (sniper type #$04)
; regular/hiding rifle man sprite codes (#$7 bytes)
; sprite_29, sprite_41 sprite_42, sprite_43, sprite_44, sprite_45, sprite_46
sniper_sprite_00:
.byte $44,$45,$46,$43,$42,$41,$29
; boss screen sniper sprite codes (#$7 bytes)
; sprite_29, sprite_2c, sprite_2d, sprite_42, sprite_44, sprite_45, sprite_46
sniper_sprite_01:
.byte $44,$45,$46,$2c,$42,$2d,$29
; pointer table for level 1 bomb turret (#$6 * #$2 = c bytes)
; the two turrets on the jungle level boss wall
bomb_turret_routine_ptr_tbl:
.addr boss_bomb_turret_routine_00 ; CPU address $8b55
.addr boss_bomb_turret_routine_01 ; CPU address $8b5c
.addr boss_bomb_turret_routine_02 ; CPU address $8bbf
.addr enemy_routine_init_explosion ; CPU address $e74b from bank 7
.addr enemy_routine_explosion ; CPU address $e7b0 from bank 7
.addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7
; bomb turret - pointer 1
; set attack delay and move to routine_01
boss_bomb_turret_routine_00:
lda #$20 ; a = #$20
sta ENEMY_ATTACK_DELAY,x ; set attack delay to 20 frames
bne boss_bomb_turret_advance_routine ; set to go to next routine boss_bomb_turret_routine_01
; bomb turret - pointer 2
; firing animation and bullet generation
; ENEMY_VAR_1 is the enemy super-tile index to draw (level_1_nametable_update_supertile_data)
boss_bomb_turret_routine_01:
jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll
dec ENEMY_ATTACK_DELAY,x ; decrement delay between attacks
bne level_1_boss_exit ; exit if delay timer hasn't elapsed
; either #$08 frames between firing animation, or #$28 frames between bullets
jsr draw_boss_bomb_turret ; draws the bomb turret based on the current recoil (ENEMY_VAR_1)
bcs level_1_boss_exit
lda #$28 ; set bomb firing delay to #28 frames
ldy ENEMY_VAR_1,x ; load current super-tile index (alternates between #$00 and #$02)
beq @continue ; branch if #$00 (not firing)
lda #$08 ; firing a bomb, set delay to #$08 (number of frames between recoil animation)
@continue:
sta ENEMY_ATTACK_DELAY,x ; store delay between bombs (either #$28 or #$08)
tya ; move ENEMY_VAR_1 to a
eor #$02 ; toggle between #$00 and #$02 (which super-tile to draw)
sta ENEMY_VAR_1,x ; store updated super-tile index back in ENEMY_VAR_1
beq level_1_boss_exit ; if not firing a bomb, exit
lda #$f8 ; set bomb horizontal offset from enemy position (param for add_with_enemy_pos)
ldy #$00 ; set bomb vertical offset from enemy position (param for add_with_enemy_pos)
jsr add_with_enemy_pos ; stores absolute screen x position in $09, and y position in $08
lda RANDOM_NUM ; load random number
and #$03 ; random number between 0 and 3
tay
lda boss_bomb_turret_bomb_velocity_tbl,y ; select the random initial velocity
tay
lda #$17 ; a = #$17 - bullet type (#$00) and angle (#$17)
jmp bullet_generation ; create enemy bullet (ENEMY_TYPE #$02) of type a with speed y at ($09, $08)
; table for bombs initial x velocities (#$4 bytes)
boss_bomb_turret_bomb_velocity_tbl:
.byte $01,$03,$05,$07
draw_boss_bomb_turret:
ldy ENEMY_VAR_1,x ; load current super-tile index for enemy
; uses value y to load correct super-tile
draw_boss_bomb_turret_y:
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
lsr
bcc @continue ; branch if ENEMY_ATTRIBUTES bit 0 is 0 (wall background)
iny ; increment boss_bomb_turret_supertile_tbl offset by 1 to get jungle bg super-tile
lda #$f8 ; move jungle bg bomb turret enemy to the left 8 pixels
; the jungle turret super-tile has some background, which isn't part of the enemy position
; so subtract #$08, draw super-tile at position, then add #$08 back
jsr add_a_to_enemy_x_pos ; subtract 8 from the enemy position (drawn super-tile is 8 to the left of enemy position)
@continue:
lda boss_bomb_turret_supertile_tbl,y ; load the correct super-tile to draw for the bomb turret (3-frame)
jsr draw_enemy_supertile_a_set_delay ; draw pattern table tile specified in a at enemy position
php ; save status flags on stack
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
beq @exit ; exit if wall background
iny
lda #$08 ; move jungle bg bomb turret enemy to the right 8 pixels
jsr add_a_to_enemy_x_pos ; add #$08 back to enemy x position on screen
@exit:
plp ; restore pushed status flags from above
level_1_boss_exit:
rts
; table for bomb turrets super-tile codes (#$6 bytes)
; offsets into level_1_nametable_update_supertile_data
; $29, $2a, $2b - wall bg bomb turret super-tiles
; $26, $27, $28 - jungle bg bomb turret super-tiles
boss_bomb_turret_supertile_tbl:
.byte $29,$26,$2a,$27,$2b,$28
; bomb turret - pointer 3
; updates super-tile to show boss bomb turret destroy and move to next routine
; this routine is started when enemy is destroyed (enemy_destroyed_routine_ptr_tbl)
boss_bomb_turret_routine_02:
ldy #$04 ; load the super-tile for the turret being destroyed
jsr draw_boss_bomb_turret_y ; draw super-tile from boss_bomb_turret_supertile_tbl ($2b or $28 depending on bg)
bcs level_1_boss_exit ; exit
boss_bomb_turret_advance_routine:
jmp advance_enemy_routine ; advance to next routine
; pointer table for door plate with siren (#$7 * #$2 = #$e bytes)
; level 1 boss defense wall boss target
boss_wall_plated_door_routine_ptr_tbl:
.addr boss_wall_plated_door_routine_00 ; CPU address $8bd7
.addr add_scroll_to_enemy_pos ; CPU address $e8a7 from bank 7 - add scrolling to enemy position
.addr boss_defeated_routine ; CPU address $e740 from bank 7
.addr enemy_routine_explosion ; CPU address $e7b0 from bank 7
.addr shared_enemy_routine_clear_sprite ; CPU address $e814 from bank 7 - set tile sprite code to #$00 and advance routine
.addr boss_wall_plated_door_routine_05 ; CPU address $8bdf
.addr boss_wall_plated_door_routine_06 ; CPU address $8be9
; plays a siren sound and advance enemy routine
boss_wall_plated_door_routine_00:
lda #$1b ; a = #$1b (sound_1b)
jsr play_sound ; play level 1 jungle boss siren sound
jmp advance_enemy_routine ; advance to next routine
; door plate - pointer 6
boss_wall_plated_door_routine_05:
lda #$00 ; a = #$00
sta ENEMY_VAR_1,x
lda #$08 ; a = #$08
jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a; advance enemy routine
; door plate - pointer 7
boss_wall_plated_door_routine_06:
dec ENEMY_ANIMATION_DELAY,x
bne @create_tunnel_explosion ; create tunnel explosion to go to next level if delay has elapsed
lda #$08 ; a = #$08
sta ENEMY_ANIMATION_DELAY,x
ldy ENEMY_VAR_1,x ; load current tunnel explosion index
lda wall_plated_door_supertile_tbl,y ; load the correct supertile for the tunnel
jsr draw_enemy_supertile_a_set_delay ; draw tunnel supertile specified in a at enemy position
bcs level_1_boss_exit ; exit if unable to draw supertile
ldy ENEMY_VAR_1,x ; load current tunnel explosion index
lda wall_plated_door_collision_code_tbl,y ; load correct collision code for tunnel super-tile component
jsr set_supertile_bg_collision ; update bg collision codes to collision code a for a single super-tile at PPU address $12 (low) $13 (high)
jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position
inc ENEMY_VAR_1,x ; increment current tunnel index
jmp create_enemy_for_explosion ; create explosion
; creates tunnel to go to next level if delay has elapsed
@create_tunnel_explosion:
lda ENEMY_ANIMATION_DELAY,x
cmp #$01 ; see if explosion delay on wall plated door is completed
bne level_1_boss_exit ; exit if delay hasn't elapsed
lda ENEMY_VAR_1,x ; current tunnel creation index
asl ; double entry since each entry is two bytes
tay ; transfer offset to y
lda wall_plated_door_explosion_offset_tbl,y ; load current tunnel index
cmp #$ff ; see if end of tunnel explosions
beq @set_delay_remove_enemy ; set delay to #$30 and remove enemy if all explosions have been shown
jsr add_a_to_enemy_y_pos ; add offset to plated door location y position
lda wall_plated_door_explosion_offset_tbl+1,y
jmp add_a_to_enemy_x_pos ; add a to enemy x position on screen
@set_delay_remove_enemy:
lda #$30 ; a = #$30
jmp set_delay_remove_enemy
; table for destroyed plated door tunnel explosions offsets (#$11 bytes)
; byte 0 - y offset
; byte 1 - x offset
wall_plated_door_explosion_offset_tbl:
.byte $f0,$f0 ; -10, -10
.byte $20,$00 ; 20, 00
.byte $e0,$20 ; -20, 20
.byte $20,$00 ; 20, 00
.byte $e0,$20 ; -20, 20
.byte $20,$00 ; 20, 00
.byte $e0,$20 ; -20, 20
.byte $20,$00 ; 20, 00
.byte $ff
; table for boss wall plated door tunnel super-tiles (#$8 bytes)
wall_plated_door_supertile_tbl:
.byte $1e,$22,$1f,$23,$20,$24,$21,$25
; table for wall plated door tunnel collision codes (#$8 bytes)
wall_plated_door_collision_code_tbl:
.byte $00,$00,$00,$04,$00,$04,$00,$04
; pointer table for exploding bridge (#$5 * #$2 = #$a bytes)
exploding_bridge_routine_ptr_tbl:
.addr exploding_bridge_routine_00 ; CPU address $8c5c
.addr exploding_bridge_routine_01 ; CPU address $8c73
.addr enemy_routine_init_explosion ; CPU address $e74b from bank 7
.addr enemy_routine_explosion ; CPU address $e7b0 from bank 7
.addr exploding_bridge_routine_04 ; CPU address $8cf0
; waits until player is close to bridge, then advance to next routine
exploding_bridge_routine_00:
jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll
jsr player_enemy_x_dist ; a = closest x distance to bridge from players, y = closest player (#$00 or #$01)
cmp #$18 ; see if the player is within #$18 x distance from the bridge
bcs exploding_bridge_exit ; player(s) aren't close enough, simply exit
lda #$01 ; a = #$01
; sets delay to a, clears ENEMY_VAR_2 and advances to next routine
exploding_bridge_advance_routine:
sta ENEMY_ANIMATION_DELAY,x ; set delay to #$01 frame
lda #$00 ; a = #$00
sta ENEMY_VAR_2,x ; clear cloud explosion animation number
advance_enemy_routine_far:
jmp advance_enemy_routine
exploding_bridge_routine_01:
jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll
dec ENEMY_ANIMATION_DELAY,x ; decrement animation delay between explosions
bne exploding_bridge_exit ; current animation not complete, exit
lda ENEMY_VAR_1,x ; load currently exploding bridge section
asl ; each bridge section has 2 super-tiles to animate through
sta $08 ; store into $08
lda ENEMY_VAR_2,x ; load the sprite cloud explosion number
cmp #$02 ; see if last small explosion before generic explosion animation
bcs bridge_explosion_clouds ; only 2 super-tile animations per bridge section, skip updating nametable if already done
clc ; clear carry in preparation for addition
adc $08 ; (2 * ENEMY_VAR_1,x) + ENEMY_VAR_2
; ENEMY_VAR_2 is only ever #$00 or #$01 here due to cmp above
tay ; set as offset, y can be #$00 to #$07, which overflows into exploding_bridge_cloud_y_offset by 1 entry
lda exploding_bridge_destroyed_supertile_tbl,y ; load super-tile code
beq bridge_explosion_clouds ; if loaded #$00, then no nametable update, draw explosion cloud sprites
sta $10 ; store super-tile code in $10 to update nametable
lda ENEMY_Y_POS,x ; load the bridge X position
clc ; clear carry in preparation for addition
adc #$f4 ; bridge Y position minus #$c
tay ; store value in Y, this is the super-tile draw y position
lda ENEMY_VAR_2,x ; load the sprite cloud explosion number
lsr ; move bit 0 into carry
lda ENEMY_X_POS,x ; load the bridge X position
bcs @draw_exploding_bridge_supertile ; if ENEMY_VAR_2 is #$00, updating current bridge section
adc #$e0 ; updating previous bridge section, subtract #$20 (one super-tile width)
; executed twice per bridge section (except last bridge section, only executes once)
; updates nametable super-tile for current bridge section on first frame of explosion animation
; on next call, updates previous bridge section to second animation super-tile
@draw_exploding_bridge_supertile:
clc ; clear carry in preparation for addition
adc #$f4 ; subtract #$c from x position
jsr load_bank_3_update_nametable_supertile ; draw super-tile $10 at position (a,y)
ldx ENEMY_CURRENT_SLOT
bcc clear_supertile_bg_collision_draw_clouds
lda #$01 ; a = #$01
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
exploding_bridge_exit:
rts
; exploding bridge - clear the background collision code so that a player falls through
; continue through to bridge_explosion_clouds logic
clear_supertile_bg_collision_draw_clouds:
jsr clear_supertile_bg_collision ; set background collision code to #$00 (empty) for a single super-tile at PPU address $12 (low) $13 (high)
; show cloud explosion and play sound
bridge_explosion_clouds:
inc ENEMY_VAR_2,x ; increment explosion cloud number
lda ENEMY_VAR_2,x ; a = explosion cloud number
cmp #$04 ; number of explosion clouds per segment
bcs advance_enemy_routine_far ; >= 4 explosions have happened, move to next routine
lda #$24 ; a = #$24 (sound_24)
jsr play_sound ; play explosion sound
lda #$04 ; a = #$04 (delay between explosions and clouds)
sta ENEMY_ANIMATION_DELAY,x ; set a #$4 frame delay between explosions
ldy ENEMY_VAR_2,x ; a = explosion cloud number
lda exploding_bridge_cloud_x_offset,y ; load explosion x offset
sta $08 ; store x offset in $08
lda exploding_bridge_cloud_y_offset,y ; load explosion y offset (#$01 to #$03)
tay ; set vertical offset from enemy position (param for add_with_enemy_pos)
lda $08 ; set horizontal offset from enemy position (param for add_with_enemy_pos)
jsr add_with_enemy_pos ; stores absolute screen x position in $09, and y position in $08
jmp create_enemy_for_explosion ; create new enemy for explosion animation (enemy_routine_init_explosion)
; table for tile codes after destruction (#$7 bytes), see level_1_nametable_update_supertile_data
; for first animation of left-most section of bridge, #$00 indicates no super-tile is changed
; #$19 - blank super-tile, used for destroyed bridges
; #$1a - exploding bridge partially destroyed both ends still exist
; #$1b - exploding bridge partially destroyed left only
; #$1c - exploding bridge partially destroyed right only
; #$1d - exploding bridge partially destroyed right only (more destroyed)
exploding_bridge_destroyed_supertile_tbl:
.byte $00
.byte $1a,$1b ; first bridge section
.byte $1c,$19 ; second bridge section
.byte $1c,$19 ; third bridge section
; table for clouds y offsets (#$4 bytes)
; #$1d is actually used when referencing exploding_bridge_destroyed_supertile_tbl
; #$00 = 0
; #$f0 = -16
exploding_bridge_cloud_y_offset:
.byte $1d,$00,$f0,$00
; table for clouds x offsets (#$5 bytes)
; #$f0 = -16
; #$00 = 0
; #$10 = 20
exploding_bridge_cloud_x_offset:
.byte $10,$f0,$00,$10,$00
; initializes next section of bridge for exploding
; then sets ENEMY_ROUTINE #$02 (exploding_bridge_routine_01) to initiate next explosion
; if all bridge sections have exploded, then remove enemy
exploding_bridge_routine_04:
jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll
inc ENEMY_VAR_1,x ; increment currently exploding bridge section (#$00 - #$03)
lda ENEMY_VAR_1,x ; load currently exploding bridge section
cmp #$04 ; number of explosions
bcs @remove_enemy ; remove bridge if all sections have exploded
lda ENEMY_X_POS,x ; load enemy x position on screen
adc #$20 ; adjust position to next bridge section (move over to the next super-tile)
bcs @remove_enemy ; unnecessary, already checked if last section earlier, this branch shouldn't ever happen
sta ENEMY_X_POS,x ; set enemy x position on screen
lda #$01 ; a = #$01
sta ENEMY_SPRITES,x ; remove last cloud explosion sprite from previous bridge section (sprite_01 is invisible sprite)
lda #$01 ; a = #$01
jsr exploding_bridge_advance_routine ; set delay to #$01, clear ENEMY_VAR_2 (advance to next routine is overwritten below)
lda #$02 ; initialize a to desired enemy routine to initiate next explosion
jmp set_enemy_routine_to_a ; set enemy routine to exploding_bridge_routine_01 to begin next bridge element explosion
@remove_enemy:
jmp remove_enemy ; remove enemy
; pointer table for green guys generator (#$3 * #$2 = #$6 bytes)
; generates soldier enemies: running, jumping, grenade launcher, and group of 4
indoor_soldier_gen_routine_ptr_tbl:
.addr indoor_soldier_gen_routine_00 ; CPU address $8d1f
.addr indoor_soldier_gen_routine_01 ; CPU address $8d28
.addr remove_enemy ; CPU address $e809 from bank 7
indoor_soldier_gen_routine_00:
lda #$40 ; a = #$40
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter to #$40
jmp advance_enemy_routine
indoor_soldier_gen_exit:
rts
; generates indoor soldier enemies: running, jumping, grenade launcher, and group of 4
; delay between enemy generation is specified in byte 3 of lvl_x_enemy_gen_screen_xx
indoor_soldier_gen_routine_01:
lda FRAME_COUNTER ; load frame counter
lsr
bcc indoor_soldier_gen_exit ; if FRAME_COUNTER is even, return
lda GRENADE_LAUNCHER_FLAG ; see if a grenade launcher enemy (ENEMY_TYPE #$15) is on the screen
bne indoor_soldier_gen_exit ; if GRENADE_LAUNCHER_FLAG exists on screen, exit
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne indoor_soldier_gen_exit ; exit if the animation delay hasn't elapsed
lda ENEMY_ATTRIBUTES,x ; load the soldier attributes (byte 2 of level_x_enemy_screen_xx for enemy)
asl ; disregard bit 7 and double bit 0, which is used as offset indoor_enemy_gen_tbl
tay ; transfer
lda indoor_enemy_gen_tbl,y ; pointer to table entry, low byte
sta $0a ; load lvl_x_enemy_gen_tbl low byte
lda indoor_enemy_gen_tbl+1,y ; pointer to table entry, high byte
sta $0b ; load lvl_x_enemy_gen_tbl high byte
lda LEVEL_SCREEN_NUMBER ; load current screen number within the level
asl ; double screen number since each entry is a 2 byte address
tay ; transfer offset lvl_x_enemy_gen_tbl offset to y
lda ($0a),y ; load the low byte of the lvl_x_enemy_gen_tbl address
sta $08 ; store in $08
iny ; increment indoor_enemy_gen_tbl read offset
lda ($0a),y ; load the high byte of the lvl_x_enemy_gen_tbl address
sta $09 ; store in $09
ldy ENEMY_VAR_1,x ; load current screen's enemy offset
lda ($08),y ; read the first byte
and #$3f ; keep bits 0 to 5 (ENEMY_ATTRIBUTES)
sta $0a ; store ENEMY_ATTRIBUTES
lda ($08),y ; re-read the first byte
rol ; look at bits 6 and 7 to see enemy type
rol ; (0 = indoor soldier, 1 = jumping soldier, 2 = group of four, 3 = grenade launcher)
rol ; rotate until top 2 bits are bits 0 and bit 1
and #$03 ; keep bits .... ..xx (enemy type)
sta $0b ; set current enemy type (#$00-#$03)
; #$00 - running guy, #$01 - jumping guy, #$02 - group of 4, #$03 - grenade launcher
iny ; increment lvl_x_enemy_gen_screen_xx read offset
lda ($08),y ; read next byte (delay byte)
iny ; increment lvl_x_enemy_gen_screen_xx read offset
asl ; shift bit 7 to the carry flag
bcc @create_enemy ; if bit 7 = 0, don't increment INDOOR_ENEMY_ATTACK_COUNT
ldy #$00 ; y = #$00
pha ; push a on to the stack
inc INDOOR_ENEMY_ATTACK_COUNT ; increment the total number of enemy attack rounds
lda INDOOR_ENEMY_ATTACK_COUNT ; load the total number of enemy attack rounds for the screen
cmp #$07 ; guys stop after 7 cycles
pla ; pop a from stack
bcc @create_enemy ; continue to create an enemy if not all #$07 rounds of attack have occurred
jmp remove_enemy ; remove enemy generator if all 7 rounds of attack have happened
@create_enemy:
lsr ; shift byte 2 back (with the former bit 7 now 0)
; this is the animation delay for the enemy to generate
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
tya ; transfer lvl_x_enemy_gen_screen_xx read offset to a
sta ENEMY_VAR_1,x ; store updated lvl_x_enemy_gen_screen_xx read offset in ENEMY_VAR_1
ldy $0b ; load enemy type (#$00 - #$03)
beq @create_running_guy ; if enemy type is #$00 create indoor soldier
dey ; decrement enemy type
beq @create_jumping_guy ; if enemy type was #$01 create jumping soldier
dey ; decrement enemy type
beq @create_group_of_4 ; if enemy type was #$02 create group of 4 soldiers
lda #$17 ; a = #$17 grenade launcher
bne @create_enemy_a ; enemy type was #$03, create grenade launcher
@create_running_guy:
lda #$15 ; a = #$15 running guy
bne @create_enemy_a
@create_jumping_guy:
lda #$16 ; a = #$16 jumping guy
; creates indoor soldier, jumping soldier, and seeking guy (grenade launcher) based on ENEMY_TYPE value in a
@create_enemy_a:
sta $08 ; store ENEMY_TYPE in $08
jsr find_next_enemy_slot ; find next available enemy slot, put result in x register
bne indoor_soldier_gen_routine_exit ; exit if not empty enemy slots
lda $08 ; load ENEMY_TYPE to create
sta ENEMY_TYPE,x ; save in ENEMY_TYPE
jsr initialize_enemy ; initialize enemy variables
lda $0a ; load enemy attributes from lvl_x_enemy_gen_screen_xx
sta ENEMY_ATTRIBUTES,x ; store in ENEMY_ATTRIBUTES
jmp indoor_soldier_gen_routine_exit ; done creating enemy, exit
; group of 4 running guys
@create_group_of_4:
lda #$03 ; a = #$03
sta $0c ; loop counter, create #$04 green soldiers
; create 4 green guys
@green_guy_creation_loop:
jsr find_next_enemy_slot ; find next available enemy slot, put result in x register
bne indoor_soldier_gen_routine_exit ; exit if unable find an empty slog
lda #$18 ; a = #$18 (group of 4 running guys)
sta ENEMY_TYPE,x ; set enemy to type 18
jsr initialize_enemy ; initialize enemy variables
lda $0a ; load enemy attributes from lvl_x_enemy_gen_screen_xx
sta ENEMY_ATTRIBUTES,x ; store in ENEMY_ATTRIBUTES
lda $0c ; load remaining number of green soldiers (enemy type #$18) to create
sta ENEMY_VAR_1,x ; store in ENEMY_VAR_1, this is used to label each individual soldier [#$00-#$03]
dec $0c ; decrement remaining number of green soldiers (enemy type #$18) to create
bpl @green_guy_creation_loop ; loop if haven't yet created #$04 soldiers
indoor_soldier_gen_routine_exit:
ldx ENEMY_CURRENT_SLOT
rts
; pointer table for level 2/4 enemy cycles pointers (#$2 * #$2 = #$4 bytes)
indoor_enemy_gen_tbl:
.addr lvl_2_enemy_gen_tbl ; CPU address $8dd3
.addr lvl_4_enemy_gen_tbl ; CPU address $8e09
; pointer table for level 2 enemy cycles (#$5 * #$2 = #$e)
lvl_2_enemy_gen_tbl:
.addr lvl_2_enemy_gen_screen_00 ; CPU address $8ddd
.addr lvl_2_enemy_gen_screen_01 ; CPU address $8de3
.addr lvl_2_enemy_gen_screen_02 ; CPU address $8def
.addr lvl_2_enemy_gen_screen_03 ; CPU address $8df3
.addr lvl_2_enemy_gen_screen_04 ; CPU address $8df9
; byte 0
; - xx.. .... type (0 = indoor soldier, 1 = jumping soldier, 2 = group of four, 3 = grenade launcher)
; - ..xx xxxx enemy attributes, different per enemy type
; byte 1
; * bit 7 = 0, don't increment INDOOR_ENEMY_ATTACK_COUNT
; * bits 0-6 = delay
lvl_2_enemy_gen_screen_00:
.byte $42,$30 ; jumping soldier, regular bullet, from right
.byte $01,$01 ; indoor soldier, regular bullet, from left
.byte $00,$c0 ; indoor soldier, regular bullet, from right
lvl_2_enemy_gen_screen_01:
.byte $46,$30 ; jumping soldier
.byte $81,$50 ; group of 4
.byte $01,$10 ; indoor soldier
.byte $00,$30 ; indoor soldier
.byte $00,$10 ; indoor soldier
.byte $01,$e0 ; indoor soldier
lvl_2_enemy_gen_screen_02:
.byte $00,$30 ; indoor soldier
.byte $c5,$a0 ; grenade launcher
lvl_2_enemy_gen_screen_03:
.byte $46,$20 ; jumping soldier
.byte $81,$60 ; group of 4
.byte $c3,$e1 ; grenade launcher
lvl_2_enemy_gen_screen_04:
.byte $40,$30 ; jumping soldier
.byte $81,$60 ; group of 4
.byte $00,$10 ; indoor soldier
.byte $03,$30 ; indoor soldier
.byte $02,$10 ; indoor soldier
.byte $01,$40 ; indoor soldier
.byte $47,$10 ; jumping soldier
.byte $4a,$e0 ; jumping soldier
; pointer table for level 4 enemy cycles (#$8 * #$2 = #$10 bytes)
lvl_4_enemy_gen_tbl:
.addr lvl_4_enemy_gen_screen_00 ; CPU address $8e19
.addr lvl_4_enemy_gen_screen_01 ; CPU address $8e25
.addr lvl_4_enemy_gen_screen_02 ; CPU address $8e33
.addr lvl_4_enemy_gen_screen_03 ; CPU address $8e3b
.addr lvl_4_enemy_gen_screen_04 ; CPU address $8e43
.addr lvl_4_enemy_gen_screen_05 ; CPU address $8e49
.addr lvl_4_enemy_gen_screen_06 ; CPU address $8e51
.addr lvl_4_enemy_gen_screen_07 ; CPU address $8e5d
; (#$c bytes)
lvl_4_enemy_gen_screen_00:
.byte $04,$30 ; indoor soldier
.byte $05,$60 ; indoor soldier
.byte $41,$60 ; jumping soldier
.byte $02,$30 ; indoor soldier
.byte $03,$60 ; indoor soldier
.byte $80,$e0 ; group of 4
; (#$e bytes)
lvl_4_enemy_gen_screen_01:
.byte $4a,$50 ; jumping soldier
.byte $c3,$20 ; grenade launcher
.byte $c2,$20 ; grenade launcher
.byte $04,$20 ; indoor soldier
.byte $05,$50 ; indoor soldier
.byte $47,$50 ; jumping soldier
.byte $c2,$b0 ; grenade launcher
; (#$8 bytes)
lvl_4_enemy_gen_screen_02:
.byte $05,$40 ; indoor soldier
.byte $80,$60 ; group of 4
.byte $53,$60 ; jumping soldier
.byte $80,$e0 ; group of 4
; (#$8 bytes)
lvl_4_enemy_gen_screen_03:
.byte $57,$60 ; jumping soldier
.byte $40,$60 ; jumping soldier
.byte $41,$60 ; jumping soldier
.byte $40,$e0 ; jumping soldier
; (#$6 bytes)
lvl_4_enemy_gen_screen_04:
.byte $05,$30 ; indoor soldier
.byte $04,$60 ; indoor soldier
.byte $42,$e0 ; jumping soldier
; (#$8 bytes)
lvl_4_enemy_gen_screen_05:
.byte $4e,$40 ; jumping soldier
.byte $81,$60 ; group of 4
.byte $41,$60 ; jumping soldier
.byte $40,$e0 ; jumping soldier
; (#$c bytes)
lvl_4_enemy_gen_screen_06:
.byte $04,$20 ; indoor soldier
.byte $03,$40 ; indoor soldier
.byte $4b,$60 ; jumping soldier
.byte $07,$20 ; indoor soldier
.byte $02,$40 ; indoor soldier
.byte $4b,$e0 ; jumping soldier
; (#$a bytes)
lvl_4_enemy_gen_screen_07:
.byte $02,$30 ; indoor soldier
.byte $47,$40 ; jumping soldier
.byte $80,$60 ; group of 4
.byte $03,$20 ; indoor soldier
.byte $04,$d0 ; indoor soldier
; pointer table for base i boss eye (#$7 * #$2 = #$e bytes)
boss_eye_routine_ptr_tbl:
.addr boss_eye_routine_00 ; CPU address $8e75
.addr boss_eye_routine_01 ; CPU address $8e7d
.addr boss_eye_routine_02 ; CPU address $8ea4
.addr boss_eye_routine_03 ; CPU address $8f08
.addr boss_defeated_routine ; CPU address $e740
.addr enemy_routine_explosion ; CPU address $e7b0
.addr boss_eye_routine_06 ; CPU address $8f2d
; base I boss eye - pointer 1
boss_eye_routine_00:
lda #$40 ; a = #$40 (delay before indoor boss appears)
; set the animation delay to a and advanced the ENEMY_ROUTINE
; input
; * a - the ENEMY_ANIMATION_DELAY
; this label is identical to two other labels
; * bank 7 - set_anim_delay_adv_enemy_routine
; * bank 0 - (this bank) set_anim_delay_adv_enemy_routine_01
set_anim_delay_adv_enemy_routine_00:
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
jmp advance_enemy_routine ; advance to next routine
boss_eye_routine_01:
lda WALL_PLATING_DESTROYED_COUNT ; number of boss platings destroyed
cmp #$04 ; number of boss platings to destroy (level 1)
bcc boss_eye_exit ; exit if not all boss platings have been destroyed
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne boss_eye_exit ; exist if ENEMY_ANIMATION_DELAY hasn't elapsed
lda #$40 ; ready to create boss, a = #$40
sta ENEMY_X_VELOCITY_FRACT,x ; boss x velocity (low byte)
lda #$01 ; a = #$01
sta ENEMY_X_VELOCITY_FAST,x ; boss x velocity (high byte)
lda #$10 ; a = #$10
sta ENEMY_VAR_1,x ; boss hp
jsr enable_enemy_collision ; enable bullet-enemy collision and player-enemy collision checks
lda #$20 ; a = #$20 (delay before first attack)
sta ENEMY_ATTACK_DELAY,x ; set delay between attacks
lda #$c0 ; a = #$c0
bne set_anim_delay_adv_enemy_routine_00 ; set ENEMY_ANIMATION_DELAY to #$c0 and advance enemy routine
boss_eye_exit:
rts
boss_eye_routine_02:
ldy #$00 ; y = #$00
lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter
beq @continue ; animation delay has elapsed, continue
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
lda FRAME_COUNTER ; load frame counter
lsr
lsr
lsr
bcc @continue
ldy #$04 ; y = #$04
@continue:
sty $08
inc ENEMY_FRAME,x ; increment enemy animation frame number
lda ENEMY_FRAME,x ; load enemy animation frame number
lsr
lsr
lsr
and #$03 ; keep bits .... ..xx
clc ; clear carry in preparation for addition
adc $08
tay
lda boss_eye_sprite_code_tbl,y
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
jsr update_enemy_pos ; apply velocities and scrolling adjust
lda ENEMY_X_POS,x ; load enemy x position on screen
ldy ENEMY_X_VELOCITY_FAST,x
bmi @check_pos_create_projectile ; see if boss
cmp #$b0 ; compare to left-most 70% of horizontal screen
bcc @create_projectile_if_should ; in firing position, create eye projectile if attack delay has elapsed
bcs @reverse_dir_fire_projectile ; reverse player direction and create eye projectile if attack delay has elapsed
@check_pos_create_projectile:
cmp #$50 ; minimum x position
bcs @create_projectile_if_should ; branch if boss eye is on the right 2/3rds of the screen
@reverse_dir_fire_projectile:
jsr reverse_enemy_x_direction ; reverse enemy's x direction
@create_projectile_if_should:
lda ENEMY_ATTACK_FLAG ; see if enemies should attack
beq boss_eye_exit ; exit if enemies shouldn't attack
dec ENEMY_ATTACK_DELAY,x ; decrement attack delay
bne boss_eye_exit ; exit if ENEMY_ATTACK_DELAY hasn't elapsed
ldy PLAYER_WEAPON_STRENGTH ; weapon strength code
lda boss_eye_attack_delay_tbl,y ; load attack delay based on player's weapon strength
sta ENEMY_ATTACK_DELAY,x ; set delay between attacks
lda #$1b ; a = #$1b (boss eye fire ring projectile)
jmp generate_enemy_a ; generate eye projectile enemy
; tables for indoor/base level 1 boss eye
; boss eye attack delay (#$04 bytes)
; based off player's weapon strength
boss_eye_attack_delay_tbl:
.byte $70,$50,$40,$28
; table for boss eye sprite codes (#$8 bytes)
; sprite_5d, sprite_5e, sprite_5f
; sprite_60, sprite_61, sprite_62
boss_eye_sprite_code_tbl:
.byte $5d,$5e,$5f,$5e,$60,$61,$62,$61
; 'enemy destroyed routine', called every time the boss eye is hit since his
; ENEMY_HP is 1. Real HP stored in ENEMY_VAR_1. This is used to play a metal
; ting sound (sound_16) every time the player hits the enemy and reset ENEMY_HP
; to 1 unless boss eye destroyed. If destroyed, advance to boss_defeated_routine
boss_eye_routine_03:
dec ENEMY_VAR_1,x ; decrement boss eye's actual HP
beq boss_eye_adv_routine ; advance to boss_defeated_routine if boss destroyed
lda ENEMY_VAR_1,x ; boss not destroyed, load enemy's actual HP
cmp #$01 ; see if only 1 HP
bne @continue ; branch if greater than 1 HP remaining
lda #$52 ; a = #$52
sta ENEMY_SCORE_COLLISION,x ; update score (collision code doesn't change)
@continue:
lda #$16 ; a = #$16 (sound_16)
jsr play_sound ; play bullet - metal ting sound
lda #$01 ; a = #$01
sta ENEMY_HP,x ; set enemy hp
lda #$20 ; a = #$20
sta ENEMY_ANIMATION_DELAY,x ; time for enemy flashing red when hit
lda #$03 ; a = #$03
jmp set_enemy_routine_to_a ; set enemy routine index to a
boss_eye_routine_06:
jsr shared_enemy_routine_clear_sprite ; set tile sprite code to #$00 and advance routine
lda #$60 ; a = #$60
jmp set_delay_remove_enemy
; pointer table for base i boss eye sphere projectile (#$5 * #$2 = #$a bytes)
eye_projectile_routine_ptr_tbl:
.addr eye_projectile_routine_00 ; CPU address $8f3f
.addr eye_projectile_routine_01 ; CPU address $8f58
.addr enemy_routine_init_explosion ; CPU address $e74b from bank 7
.addr enemy_routine_explosion ; CPU address $e7b0 from bank 7
.addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7
eye_projectile_routine_00:
jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01)
sty $0a ; store closest player in $0a
jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position
lda #$06 ; a = #$06 (projectile speed)
sta $06
lda #$01 ; a = #$01 (quadrant_aim_dir_01)
sta $0f ; quadrant_aim_dir_lookup_tbl offset
jsr get_quadrant_aim_dir_for_player ; set a to the aim direction within a quadrant
; based on source position ($09, $08) targeting player index $0a
jsr set_bullet_velocities ; set the projectile X and Y velocities (both high and low) based on register a (#$01)
boss_eye_adv_routine:
jmp advance_enemy_routine
eye_projectile_routine_01:
lda #$63 ; a = #$63
ldy ENEMY_Y_POS,x ; enemy y position on screen
cpy #$48 ; height for projectile to become big & hittable
bcc @continue
jsr enable_enemy_collision ; enable bullet-enemy collision and player-enemy collision checks
lda #$64 ; a = #$64
@continue:
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
lda FRAME_COUNTER ; load frame counter
lsr
lsr
and #$03 ; keep bits .... ..xx
tay
lda ENEMY_SPRITE_ATTR,x ; load enemy sprite attributes
and #$3f ; keep bits ..xx xxxx
ora eye_projectile_sprite_attr_tbl,y
sta ENEMY_SPRITE_ATTR,x
jmp update_enemy_pos ; apply velocities and scrolling adjust
; table for sphere projectile mirroring codes (#$4 bytes)
eye_projectile_sprite_attr_tbl:
.byte $00,$40,$c0,$80
; pointer table for rollers (#$5 * #$2 = #$a bytes)
roller_routine_ptr_tbl:
.addr roller_routine_00 ; CPU address $8f8c - initialize y position to #$72 advance routine
.addr roller_routine_01 ; CPU address $8f94 - set appropriate sprite, apply velocity, enable player collision when close, remove when rolled past
.addr enemy_routine_init_explosion ; CPU address $e74b from bank 7
.addr roller_routine_04 ; CPU address $e7a4 from bank 7
.addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7
; initialize y position to #$72 advance routine
roller_routine_00:
lda #$72 ; a = #$72 (initial y position)
sta ENEMY_Y_POS,x ; enemy y position on screen
jmp advance_enemy_routine
; set appropriate sprite, apply velocity, enable player collision when close, remove when rolled past
roller_routine_01:
ldy #$03 ; y = #$03
lda ENEMY_Y_POS,x ; load enemy y position on screen
@sprite_y_check:
cmp roller_sprite_y_cutoff_tbl-1,y ; see if y position is below cutoff
bcs @found_size ; branch if found size of sprite to use
dey ; roller isn't below y cutoff for current y, got to next higher cutoff
bne @sprite_y_check ; loop to next y offset if y isn't 0
; otherwise use smallest sprite (sprite_99)
@found_size:
tya ; transfer sprite size index to a
clc ; clear carry in preparation for addition
adc #$99 ; add #$99 to get actual sprite code (sprite_99 up to sprite_9c)
sta ENEMY_SPRITES,x ; write roller sprite code to CPU buffer
cpy #$02 ; see how close the roller is to the player
bcc @continue ; branch if roller is relatively far from the player
lda #$2e ; a = #$2e
sta ENEMY_SCORE_COLLISION,x ; set score and collision code to #$2e
@continue:
jsr update_enemy_pos ; apply velocities and scrolling adjust
lda ENEMY_Y_POS,x ; load enemy y position on screen
cmp #$ac ; see if roller should have collision enabled (close to the player)
bcc @exit ; exit if roller shouldn't yet be enabled for player-roller collision
cmp #$bc ; see if roller should be removed
bcs @remove_enemy ; roller rolled past player, remove roller
jmp enable_enemy_collision ; roller position is between #$ac and #$bc, enable roller-player collision
@exit:
rts
@remove_enemy:
jmp remove_enemy ; remove enemy
; the y positions where the roller sprite changes to a larger size
; #$7c - sprite_9a
; #$8c - sprite_9b
; #$9c - sprite_9c
roller_sprite_y_cutoff_tbl:
.byte $7c,$8c,$9c
; pointer table for grenades (indoor) (#$6 * #$2 = #$c bytes)
; thrown by indoor soldiers (15) and grenade launchers (17) on indoor levels
grenade_routine_ptr_tbl:
.addr grenade_routine_00 ; CPU address $8fd5 - init ENEMY_VAR_1, ENEMY_VAR_4, and ENEMY_ATTACK_DELAY, advance routine
.addr grenade_routine_01 ; CPU address $8fe8 - sets sprite code, sprite attribute, apply vector to get falling arc, advance routine if appropriate
.addr grenade_routine_02 ; CPU address $907c
.addr enemy_routine_init_explosion ; CPU address $e74b from bank 7
.addr enemy_routine_explosion ; CPU address $e7b0 from bank 7
.addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7
; init ENEMY_VAR_1, ENEMY_VAR_4, and ENEMY_ATTACK_DELAY, advance routine
grenade_routine_00:
lda ENEMY_Y_POS,x ; load grenade y position
sta ENEMY_VAR_1,x ; store grenade y position in ENEMY_VAR_1
lda #$00 ; a = #$00
sta ENEMY_VAR_4,x
lda #$fd ; a = #$fd
sta ENEMY_ATTACK_DELAY,x ; set attack delay to #$fd (253)
grenade_adv_routine:
jmp advance_enemy_routine
; sets sprite code, sprite attribute, apply vector to get falling arc, advance routine if appropriate
grenade_routine_01:
ldy #$02 ; y = #$02 (start at farthest point from player)
lda ENEMY_VAR_1,x ; load grenade y position
; find appropriate y register index based on y position
@determine_sprite_code_loop:
cmp grenade_sprite_tbl_y_cutoff_tbl-1,y ; compare y position to cutoff point
bcs @sprite_code_tbl_found ; branch if found appropriate y value
dey ; y position was less than value, decrement y
bne @determine_sprite_code_loop ; try next higher cutoff point if y isn't #$00
@sprite_code_tbl_found:
lda FRAME_COUNTER ; load current frame number
and #$07 ; keep bits 0-2
bne @check_frame_number ; don't move to next frame if the frame number isn't divisible by #$08
inc ENEMY_FRAME,x ; increment enemy animation frame number every #$08 frames
@check_frame_number:
lda ENEMY_FRAME,x ; load enemy animation frame number
cmp grenade_sprite_codes_len_tbl,y ; see if ENEMY_FRAME hasn't gone past last frame
bcc @set_sprite_create_arc ; branch if showing a frame in animation and don't need to loop back
lda #$00 ; loop back to first frame, a = #$00
; sets sprite code, sprite attribute, apply vector to get falling arc, advance routine if appropriate
@set_sprite_create_arc:
sta ENEMY_FRAME,x ; set enemy animation frame number (offset into grenade_sprite_codes_xx)
lda grenade_sprite_codes_len_tbl,y ; load the number of sprites in the grenade_sprite_codes_xx table
sta $0a ; store value in $09
tya ; transfer the sprite code table index to a
asl ; double sprite code table index since each entry has #$92 bytes
tay ; transfer sprite code table index back to y
lda grenade_sprite_codes_ptr_tbl,y ; load low byte of grenade_sprite_codes_xx
sta $08 ; store in $08
lda grenade_sprite_codes_ptr_tbl+1,y ; load high byte of grenade_sprite_codes_xx
sta $09 ; store in $09
ldy ENEMY_FRAME,x ; load enemy animation frame number
lda ($08),y ; load sprite code from grenade_sprite_codes_xx
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
tya ; transfer sprite code table index back to y
clc ; clear carry in preparation for addition
adc $0a ; add offset to get to corresponding sprite attribute
; each grenade_sprite_codes_xx contains both sprite codes and sprite attribute values
; $0a is the number of (sprite code, sprite attribute) pairs
tay ; transfer offset back to y
lda ($08),y ; load grenade sprite attribute
sta ENEMY_SPRITE_ATTR,x ; store grenade sprite attribute
lda ENEMY_VAR_4,x
clc ; clear carry in preparation for addition
adc #$0c ; add #$0c into ENEMY_VAR_4
sta ENEMY_VAR_4,x
lda ENEMY_ATTACK_DELAY,x
adc #$00
sta ENEMY_ATTACK_DELAY,x
jsr set_enemy_falling_arc_pos ; set X and Y position to follow a falling arc
lda ENEMY_VAR_3,x
bpl grenade_adv_routine
rts
; table for delays before changing sprite (#$2 bytes)
grenade_sprite_tbl_y_cutoff_tbl:
.byte $80,$90
; table for sprite attribute offset (related to z position and palette) (#$3 bytes)
grenade_sprite_codes_len_tbl:
.byte $04,$08,$08
; pointer table for grenade sprites and palettes (#$3 * #$2 = #$6 bytes)
grenade_sprite_codes_ptr_tbl:
.addr grenade_sprite_codes_00 ; CPU address $9054
.addr grenade_sprite_codes_01 ; CPU address $905c
.addr grenade_sprite_codes_02 ; CPU address $906c
; table for grenade sprites and sprite attributes (closest to player) (#$8 bytes)
; sprite_a8, sprite_a9, sprite_a6
; sprite attributes
; * $c0 - flip horizontally and vertically
grenade_sprite_codes_00:
.byte $a8,$a9,$a6,$a9 ; sprite codes
.byte $00,$00,$00,$c0 ; sprite attributes
; table for grenade sprites and sprite attributes (close to player) (#$10 bytes)
; sprite_a4, sprite_a5, sprite_a6, sprite_a6, sprite_a7
; sprite attributes
; * $c0 - flip horizontally and vertically
grenade_sprite_codes_01:
.byte $a4,$a5,$a6,$a5,$a4,$a7,$a6,$a7 ; sprite codes
.byte $00,$00,$00,$c0,$c0,$00,$00,$c0 ; sprite attributes
; table for grenade sprites and sprite attributes (farthest from player) (#$10 bytes)
; sprite_a0, sprite_a1, sprite_a2, sprite_a3
; sprite attributes
; * $c0 - flip horizontally and vertically
grenade_sprite_codes_02:
.byte $a0,$a1,$a2,$a1,$a0,$a3,$a2,$a3 ; sprite codes
.byte $00,$00,$00,$c0,$c0,$00,$00,$c0 ; sprite attributes
; play sound, set
grenade_routine_02:
lda #$24 ; a = #$24 (sound_24)
jsr play_sound ; play explosion sound
lda #$ac ; a = #$ac
sta ENEMY_Y_POS,x ; set y position to #$ac (bottom of screen where explosion occurs)
jsr mortar_shot_routine_03 ; update collision to allow player-enemy collision
jmp advance_enemy_routine ; go to enemy_routine_init_explosion
; pointer table for wall turret (#$8 * #$2 = #$10 bytes)
wall_turret_routine_ptr_tbl:
.addr wall_turret_routine_00 ; CPU address $909c - set initial delay and advance routine
.addr wall_turret_routine_01 ; CPU address $90a8 - draw 'wall turret / core - closed' super-tile, wait for delay, advance routine
.addr wall_turret_routine_02 ; CPU address $90c1 - opening animation and enabling collision
.addr wall_turret_routine_03 ; CPU address $90ee - aim and fire turret
.addr wall_turret_routine_04 ; CPU address $9108 - enemy destroyed routine - draw 'core - destroyed' nametable tiles, advance routine
.addr wall_core_routine_05 ; CPU address $e737 from bank 7
.addr enemy_routine_explosion ; CPU address $e7b0 from bank 7
.addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7
; set initial delay and advance routine
wall_turret_routine_00:
ldy ENEMY_ATTRIBUTES,x ; load enemy attributes
lda wall_turret_initial_delay_tbl,y
bne set_turret_delay_adv_routine
; table for wall turret deployment delays (#$4 bytes)
wall_turret_initial_delay_tbl:
.byte $50,$80,$b0,$f0
; draw 'wall turret / core - closed' super-tile, wait for delay, advance routine
wall_turret_routine_01:
lda ENEMY_VAR_1,x ; load byte specifying
bne @wait_for_delay_adv_routine ; branch if already set 'wall turret / core - closed' super-tile
lda #$84 ; level_2_4_tile_animation offset (#$04) (wall turret / core - closed)
jsr update_enemy_nametable_tiles ; draw the nametable tiles from level_2_4_tile_animation (a) at the enemy position
bcs @wait_for_delay_adv_routine ; branch if unable to draw super-tile. Don't set super-tile drawn byte (ENEMY_VAR_1)
inc ENEMY_VAR_1,x ; set byte specifying that the 'wall turret / core - closed' super-tile was drawn
@wait_for_delay_adv_routine:
dec ENEMY_ANIMATION_DELAY,x ; decrement animation delay as set by ENEMY_ATTRIBUTES (wall_turret_initial_delay_tbl)
bne wall_turret_exit ; exit if delay hasn't elapsed
lda #$01 ; animation delay elapsed, set new animation delay, and advance routine to wall_turret_routine_02
set_turret_delay_adv_routine:
jmp set_anim_delay_adv_enemy_routine_00 ; set ENEMY_ANIMATION_DELAY to a and advance enemy routine
; opening animation and enabling collision
wall_turret_routine_02:
dec ENEMY_ANIMATION_DELAY,x ; decrement animation delay
bne wall_turret_exit ; exit if delay hasn't elapsed
lda #$08 ; a = #$08 (delay between deployment frames)
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
ldy ENEMY_FRAME,x ; load enemy animation frame number
lda wall_turret_tile_animation_tbl,y ; load level_2_4_tile_animation offset
jsr update_nametable_tiles_set_delay ; draw the nametable tiles from level_2_4_tile_animation (a) at the enemy position
; set animation delay to #$01 if drawing was successful
bcs wall_turret_exit ; exit if unable to draw nametable tiles
inc ENEMY_FRAME,x ; increment enemy animation frame number
lda ENEMY_FRAME,x ; load enemy animation frame number
cmp #$03 ; see if last frame of opening animation frames
bcc wall_turret_exit ; exit if not finished opening
jsr enable_bullet_enemy_collision ; allow bullets to collide (and stop) upon colliding with wall turret
lda #$80 ; a = #$80 (initial delay before attacking)
sta ENEMY_ATTACK_DELAY,x ; set attack delay
wall_turret_adv_routine:
jmp advance_enemy_routine ; advance to next routine
; table for wall turret opening tile super-tile codes (#$3 bytes)
; offsets into level_2_4_tile_animation
; #$85 - wall turret / core - opening frame 1
; #$88 - wall turret - opening frame 2
; #$89 - wall turret - open
wall_turret_tile_animation_tbl:
.byte $85,$88,$89
; aim and fire turret
wall_turret_routine_03:
dec ENEMY_ATTACK_DELAY,x ; decrement attack delay
bne wall_turret_exit ; exit if attack delay hasn't elapsed
lda #$50 ; ready to attack, set next round attack delay to #$50
sta ENEMY_ATTACK_DELAY,x ; set next round of attack delay to #$50
jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01)
sty $0a ; store closest player in $0a
jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position
lda #$60 ; a = #$60
ldy #$04 ; bullet speed code
jmp aim_and_create_enemy_bullet ; get firing dir based on enemy ($08, $09) and player pos ($0b, $0a)
; and creates bullet (type a) with speed y if appropriate
wall_turret_exit:
rts
; draw 'core - destroyed' nametable tiles, advance routine
wall_turret_routine_04:
lda #$83 ; a = #$83 (core - destroyed)
jsr update_enemy_nametable_tiles ; draw the nametable tiles from level_2_4_tile_animation (a) at the enemy position
bcc wall_turret_adv_routine ; advance routine to wall_core_routine_05
rts ; exit if unable to draw 'core - destroyed' super-tile
; pointer table for base core (#$a * #$2 = #$14 bytes)
wall_core_routine_ptr_tbl:
.addr wall_core_routine_00 ; CPU address $9124 (init variables)
.addr wall_core_routine_01 ; CPU address $9167 (set core plating)
.addr wall_core_routine_02 ; CPU address $91a0 (wall core opening)
.addr wall_core_routine_03 ; CPU address $91cf (fire at player if conditions met)
.addr wall_core_routine_04 ; CPU address $91fb (update nametable for destroyed plating/destroyed core, reset HP)
.addr wall_core_routine_05 ; CPU address $e737 (initialize explosion)
.addr enemy_routine_explosion ; CPU address $e7b0
.addr wall_core_routine_07 ; CPU address $923b (core destroyed, see if last core, if so destroy all enemies)
.addr wall_core_routine_08 ; CPU address $9251 (boss appears after or during this routine)
.addr wall_core_routine_09 ; CPU address $92ae (wait for explosion delay, mark screen cleared, remove enemy)
; initializes variables like HP, collision box code, destruction animation sequence
wall_core_routine_00:
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
lsr ; shift right
lsr ; shift right, removing opening delay
and #$03 ; keep bits .... ..xx (core size, and whether or not it is plated)
tay ; transfer value to y
lsr ; shift bit 2 of ENEMY_ATTRIBUTES into carry
lda #$25 ; a = #$25
bcc @continue ; branch if not plated
lda #$04 ; plated, set bullet collision sound code to #$04
sta ENEMY_VAR_A,x ; core plating bullet collision sound code (see bullet_hit_sound_tbl)
lda #$22 ; score code #$02, collision box code #$02
@continue:
sta ENEMY_SCORE_COLLISION,x ; set score code and collision box code
lda wall_core_hp_tbl,y ; load ENEMY_HP based on core type (size and plating)
sta ENEMY_HP,x ; set enemy hp
lda wall_core_init_dmg_tile_anim_tbl,y ; load correct initial tile update animation offset (wall_core_tile_anim_tbl)
; based on core type (size and plating) for destruction animation sequence
sta ENEMY_VAR_2,x ; set current wall_core_tile_anim_tbl offset
ldy #$00 ; default #$20 core opening delay
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
and #$04 ; keep bit 2 (whether core is plated)
bne @set_opening_delay_adv_routine ; branch if core is plated, use default #$20 opening delay
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
and #$03 ; keep bits 0 and 1 (core opening delay)
tay ; transfer opening delay offset to y
@set_opening_delay_adv_routine:
lda core_opening_delay,y
bne wall_core_adv_routine ; always branch
; set animation delay and advance to wall_core_routine_01
; table for core opening delays (#$4 bytes)
; the larger the number, the longer the delay (#$f0 is largest delay)
core_opening_delay:
.byte $20,$80,$b0,$f0
; table for indoor/base wall core hp (#$4 bytes)
; #$08 - ENEMY_HP for normal-sized core not plated
; #$05 - ENEMY_HP for normal-sized plated core
; #$10 - ENEMY_HP for big core, not plated
; #$05 - ENEMY_HP for big core, plated (not used)
wall_core_hp_tbl:
.byte $08,$05,$10,$05
; table for initial cracked core tiles when being attacked (#$4 bytes)
; as core is destroyed nametable tiles are updated in descending sequence
; from wall_core_tile_anim_tbl
; #$00 - normal-sized core not plated - #$83 core - destroyed
; #$03 - normal-sized plated core - #$81 core plating - cracked
; #$00 - big core, not plated - #$83 core - destroyed
; #$03 - big core, plated (not used) - #$81 core plating - cracked
wall_core_init_dmg_tile_anim_tbl:
.byte $00,$03,$00,$03
; set core plating
wall_core_routine_01:
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
and #$08 ; keep bit 3 (whether or not is a large core)
bne @wait_for_delay_adv_routine ; branch if big core
lda ENEMY_VAR_1,x ; see if nametable has been updated
bne @wait_for_delay_adv_routine
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
lsr
lsr
lsr
lda #$84 ; a = #$84 (level_2_4_tile_animation offset) wall turret / core - closed
bcc @update_nametable ; branch if not plated core
lda #$80 ; plated core, set a = #$80 (level_2_4_tile_animation offset) core plating
@update_nametable:
jsr update_enemy_nametable_tiles ; draw the nametable tiles from level_2_4_tile_animation (a) at the enemy position
bcs @wait_for_delay_adv_routine ; branch if unable to update nametable tiles
inc ENEMY_VAR_1,x ; mark nametable as updated
@wait_for_delay_adv_routine:
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne wall_core_exit_00
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
and #$04 ; keep bit 2 (whether or not is a plated core)
beq @set_delay_adv_routine ; branch if not a plated core, no need to enable collision
jsr wall_core_enable_collision_adv_routine ; enable collision and move to wall_core_routine_02
@set_delay_adv_routine:
lda #$01 ; a = #$01, attack and animation delay
sta ENEMY_ATTACK_DELAY,x
lda #$01 ; a = #$01 (unneeded)
wall_core_adv_routine:
jmp set_anim_delay_adv_enemy_routine_00 ; set ENEMY_ANIMATION_DELAY to a and advance enemy routine
wall_core_routine_02:
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne wall_core_exit_00
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
and #$08 ; keep bit 3 (whether or not is a large core)
bne wall_core_enable_collision_adv_routine ; enable collision and move to wall_core_routine_03, large core doesn't have opening delay
lda #$08 ; a = #$08 (delay between frames when core opens)
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
ldy ENEMY_FRAME,x ; enemy animation frame number
lda wall_core_nametable_update_tbl,y
jsr update_nametable_tiles_set_delay ; draw the nametable tiles from level_2_4_tile_animation (a) at the enemy position
; set animation delay to #$01 if drawing was successful
bcs wall_core_exit_00 ; exit if unable to update nametable tiles
inc ENEMY_FRAME,x ; increment enemy animation frame number
lda ENEMY_FRAME,x ; load enemy animation frame number
cmp #$03 ; see if last frame (fully open)
bcc wall_core_exit_00 ; exit if not fully open yet
wall_core_enable_collision_adv_routine:
jsr enable_bullet_enemy_collision ; allow bullets to collide (and stop) upon colliding with wall core
jmp advance_enemy_routine
; table for core opening update nametable tiles (offsets into level_2_4_tile_animation) (#$03 bytes)
; #$85 wall turret / core - opening frame 1
; #$86 core - opening frame 2
; #$87 core - open
wall_core_nametable_update_tbl:
.byte $85,$86,$87
; fire at player if conditions met
wall_core_routine_03:
lda INDOOR_ENEMY_ATTACK_COUNT ; load the total number of enemy attack rounds for the screen
cmp #$07 ; number of cycles for core to start shooting
bcc wall_core_exit_00 ; don't attack, not enough rounds of soldier attacks have happened
lda ENEMY_VAR_2,x ; load current nametable index
bne wall_core_exit_00 ; exit if wall core is still plated
lda ENEMY_Y_POS,x ; load enemy y position on screen
cmp #$70 ; check if core is not too low
bcs wall_core_exit_00 ; exit if core is too low (player needs to crouch to attack)
dec ENEMY_ATTACK_DELAY,x ; decrement delay between attacks
bne wall_core_exit_00 ; exit if attack delay hasn't elapsed
lda #$28 ; a = #$28 (delay between bullets)
sta ENEMY_ATTACK_DELAY,x ; reset attack delay
jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01)
sty $0a ; store closest player in $0a
jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position
lda #$60 ; a = #$60
ldy #$05 ; y = #$05 (wall turret bullet speed code)
jmp aim_and_create_enemy_bullet ; get firing dir based on enemy ($08, $09) and player pos ($0b, $0a)
; and creates bullet (type a) with speed y if appropriate
wall_core_exit_00:
rts
; update nametable for destroyed plating/destroyed core, reset HP
wall_core_routine_04:
ldy ENEMY_VAR_2,x ; load current state of nametable tiles
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
and #$08 ; keep bit 3 (whether or not is a large core)
beq @continue ; branch if a normal-sized core
tya ; large core, transfer ENEMY_VAR_2 to a
clc ; clear carry in preparation for addition
adc #$04 ; add #$04 to set correct large core nametable tiles
tay ; transfer a back to y to use as animation offset
@continue:
lda wall_core_tile_anim_tbl,y ; load appropriate level_2_4_tile_animation offset to update tiles
jsr update_enemy_nametable_tiles ; draw the nametable tiles from level_2_4_tile_animation (a) at the enemy position
bcs wall_core_exit_00 ; exit if unable to update nametable tiles
ldy #$05 ; y = #$05 (hp for 2nd and 3rd levels of plating)
dec ENEMY_VAR_2,x ; decrement to next level of destroyed plating
bmi @adv_routine ; go to wall_core_routine_05 if plating has been destroyed
bne @set_hp_go_routine_03 ; plating not yet destroyed, set HP to #$05 and set enemy routine to wall_core_routine_03
lda #$00 ; plating destroyed, a = #$00
sta ENEMY_VAR_A,x ; normal collision sound since bullet no longer colliding with plating (see bullet_hit_sound_tbl)
lda #$25 ; a = #$25 (updating collision box code)
sta ENEMY_SCORE_COLLISION,x ; score code #$02, collision box code #$05
ldy #$08 ; y = #$08 (hp for core after plating destroyed)
@set_hp_go_routine_03:
tya ; transfer HP to a
sta ENEMY_HP,x ; set enemy hp
lda #$04 ; a = #$04
jmp set_enemy_routine_to_a ; set enemy routine index to wall_core_routine_03
@adv_routine:
jmp advance_enemy_routine
; nametable update offsets into level_2_4_tile_animation for destroyed core (#$8 bytes)
; #$81 core plating - cracked
; #$82 core plating - more cracks
; #$83 core - destroyed
; #$87 core - open
; #$8a big core
wall_core_tile_anim_tbl:
.byte $83,$87,$82,$81,$83,$8a,$82,$81
; core destroyed, see if last core, if so destroy all enemies
wall_core_routine_07:
dec WALL_CORE_REMAINING ; decrement remaining cores/bosses to destroy required to allow advance to next screen
bne wall_core_remove_enemy ; branch to remove enemy if other wall cores still need to be destroyed
lda #$00 ; all wall cores have been destroyed, set a = #$00 (sprite_00)
sta ENEMY_SPRITES,x ; hide enemy (invisible sprite)
jsr destroy_all_enemies ; destroy any other enemies on screen
lda #$03 ; a = #$03
sta ENEMY_VAR_3,x ;
lda #$04 ; a = #$04 (animation delay)
wall_core_set_delay_adv_routine:
jmp set_anim_delay_adv_enemy_routine_00 ; set ENEMY_ANIMATION_DELAY to a and advance enemy routine
; removes back wall
wall_core_routine_08:
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne wall_core_wait_play_sound
ldy ENEMY_VAR_3,x ; load current destroyed back wall super-tile index to load
lda wall_core_y_pos_tbl,y ; load destroyed back wall quadrant y position
sta ENEMY_Y_POS,x ; enemy y position on screen
lda wall_core_x_pos_tbl,y ; load destroyed back wall quadrant x position
sta ENEMY_X_POS,x ; set enemy x position on screen
lda wall_core_update_supertile_tbl,y ; load level update super-tile to draw (level_xx_nametable_update_supertile_data)
jsr draw_enemy_supertile_a ; draw super-tile a at position (ENEMY_X_POS, ENEMY_Y_POS)
bcs @set_delay_exit ; exit if unable to update super-tile
ldy ENEMY_VAR_3,x ; load current destroyed back wall super-tile index
tya ; transfer to y
lsr
lsr
lda #$fc ; a = #$fc
bcc @continue ; branch if not updating bottom super-tile
lda #$f4 ; a = #$f4
@continue:
clc ; clear carry in preparation for addition
adc wall_core_y_pos_tbl,y ; subtract from wall position to set explosion animation y position
sta $08 ; store y position of explosion in $08
lda wall_core_x_pos_tbl,y ; subtract from wall position to set explosion animation x position
sta $09 ; store x position of explosion in $09
jsr create_explosion_89 ; create explosion type #$89 at ($09, $08)
dec ENEMY_VAR_3,x ; move to next destroyed wall quadrant
bmi wall_core_set_delay_10_adv_routine ; advance routine if finished updated destroyed back wall
@set_delay_exit:
lda #$01 ; a = #$01
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
wall_core_exit_01:
rts
wall_core_set_delay_10_adv_routine:
lda #$10 ; a = #$10
bne wall_core_set_delay_adv_routine
wall_core_wait_play_sound:
lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter
cmp #$01
bne wall_core_exit_01
lda #$25 ; a = #$25 (small boom sound)
jmp play_sound
; tables related to back wall cleared explosions and replacing super-tiles
; indexes into level_2_nametable_update_supertile_data/level_4_nametable_update_supertile_data
; #$00 - top left back wall destroyed
; #$01 - top right back wall destroyed
; #$02 - bottom left back wall destroyed
; #$03 - bottom right back wall destroyed
wall_core_update_supertile_tbl:
.byte $02,$03,$01,$00
; table for destroyed back wall super-tile y position (#$4 bytes)
wall_core_y_pos_tbl:
.byte $78,$78,$58,$58
; table for destroyed back wall super-tile x position (#$4 bytes)
wall_core_x_pos_tbl:
.byte $70,$90,$90,$70
; wait for explosion delay, mark screen cleared, remove enemy
wall_core_routine_09:
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne wall_core_exit_01 ; exit if explosion animations aren't completed
lda #$01 ; a = #$01 (mark screen cleared)
sta INDOOR_SCREEN_CLEARED ; indoor screen cleared flag (0 = not cleared; 1 = cleared)
wall_core_remove_enemy:
jmp remove_enemy ; remove enemy
indoor_soldier_routine_ptr_tbl:
.addr indoor_soldier_routine_00 ; CPU address $92c8
.addr indoor_soldier_routine_01 ; CPU address $92d5
.addr shared_enemy_routine_00 ; CPU address $9346 - soldier has been hit by player bullet
.addr shared_enemy_routine_01 ; CPU address $9360 - perform enemy hit by bullet animation, then advance routine
.addr enemy_routine_init_explosion ; CPU address $e74b from bank 7
.addr shared_enemy_routine_03 ; CPU address $e7aa from bank 7 - show explosion_type_02
.addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7
; initializes indoor soldier
; * sets position, velocity and attack delay
indoor_soldier_routine_00:
ldy #$00 ; y = #$00
jsr init_indoor_enemy_pos_and_vel ; initialize indoor soldier soldier velocity and position
lda #$08 ; a = #$08 (delay before attacking, running guy)
sta ENEMY_ATTACK_DELAY,x ; set delay between attacks
jmp advance_enemy_routine
; wait for attack delay to elapse, then fire weapon based on ENEMY_ATTRIBUTES
indoor_soldier_routine_01:
jsr init_sprite_from_frame ; determine enemy sprite based on ENEMY_FRAME, flip sprite horizontally if running left
jsr apply_enemy_velocity_set_bg_priority ; apply enemy velocity to position (remove if off screen), set bg priority
dec ENEMY_ATTACK_DELAY,x ; decrement delay between attacks
bne @exit ; delay between attacks not yet elapsed, just exit
lda #$10 ; ready to attack, first reset attack delay to #$10
sta ENEMY_ATTACK_DELAY,x ; set delay between attacks
lda ENEMY_X_POS,x ; load enemy x position on screen
cmp #$68 ; location of disappearance, from left
bcc @exit ; enemy too far to the left to attack, exit
cmp #$98 ; location of disappearance, from right
bcs @exit ; enemy too far to the right to attack, exit
lda ENEMY_ATTRIBUTES,x ; load the enemy weapon type and enemy direction
lsr ; shift enemy direction bit to carry (not used to determine enemy weapon type)
and #$03 ; keep bits .... ..xx that specify weapon type
tay ; transfer weapon type to y
bne @continue ; not a regular bullet weapon type (#$00), see if grenade or a roller
jmp create_indoor_bullet ; fire a regular indoor bullet
@continue:
dey ; decrement enemy weapon type
bne @create_roller ; not a grenade, so create a roller (ENEMY_TYPE #$11)
inc ENEMY_VAR_1,x ; increment total grenades fired
lda ENEMY_VAR_1,x ; load total grenades fired
lsr ; shift bit 0 to the carry flag
bcc @exit ; exit every other time, effectively doubling ENEMY_ATTACK_DELAY
jmp enemy_launch_grenade ; create a grenade (ENEMY_TYPE #$12)
; creates a roller x pixels down from indoor soldier to roll towards player
@create_roller:
ldy #$08 ; set vertical offset from enemy position (param for add_with_enemy_pos)
lda #$00 ; set horizontal offset from enemy position (param for add_with_enemy_pos)
jsr add_with_enemy_pos ; stores absolute screen x position in $09, and y position in $08
jmp create_roller ; create the roller
@exit:
rts
; sets the enemy sprite and flips it if necessary depending on enemy direction
init_sprite_from_frame:
lda FRAME_COUNTER ; load frame counter
and #$03 ; keep bits .... ..xx
bne @set_sprite_and_attr ; set the enemy sprite code, and sprite attribute
inc ENEMY_FRAME,x ; increment enemy animation frame number
lda ENEMY_FRAME,x ; load enemy animation frame number
cmp #$03 ; indoor enemies have 3 frames to cycle through
bcc @set_frame_sprite_and_attr ; set the enemy sprite based on frame, and flip sprite if necessary
lda #$00 ; a = #$00
@set_frame_sprite_and_attr:
sta ENEMY_FRAME,x ; update enemy animation frame number
@set_sprite_and_attr:
lda ENEMY_FRAME,x ; load enemy animation frame number
clc ; clear carry in preparation for addition
adc #$93 ; determine sprite based on enemy frame
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
lda ENEMY_SPRITE_ATTR,x ; load enemy sprite attributes
ldy ENEMY_X_VELOCITY_FAST,x ; see if enemy is running left
bmi @horizontal_flip_sprite ; branch if enemy is traveling left, flip sprite horizontally
and #$bf ; keep bits x.xx xxxx (no horizontal flipping)
bpl @set_sprite_attr
@horizontal_flip_sprite:
ora #$40 ; set bits .x.. .... (flip sprite horizontally)
@set_sprite_attr:
sta ENEMY_SPRITE_ATTR,x ; set updated sprite attribute
; also exit for
shared_enemy_routine_01_exit:
rts
; used by the indoor soldiers: #$15 - Indoor Soldier, #$16 - Jumping Soldier, #$17 - Grenade Launcher, #$18 - Group of Four Soldiers
; soldier has been hit by player bullet
shared_enemy_routine_00:
jsr disable_enemy_collision ; prevent player enemy collision check and allow bullets to pass through enemy
lda #$96 ; a = #$96 (sprite_96) indoor soldier hit by bullet
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
lda #$80 ; a = #$80
sta ENEMY_Y_VELOCITY_FRACT,x ; set negative velocity for initial hit reaction (slow .5)
lda #$fd ; a = #$fd
sta ENEMY_Y_VELOCITY_FAST,x ; set negative velocity for initial hit reaction (fast -3)
jsr set_enemy_x_velocity_to_0 ; set x velocity to zero
lda #$10 ; a = #$10
jmp set_anim_delay_adv_enemy_routine_00 ; set ENEMY_ANIMATION_DELAY to #$10 and advance enemy routine
; perform enemy hit by bullet animation
shared_enemy_routine_01:
jsr update_enemy_pos ; apply velocities and scrolling adjust
lda #$38 ; a = #$38 (gravity when flying up, indoor)
jsr add_a_to_enemy_y_fract_vel ; add a to enemy y fractional velocity
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne shared_enemy_routine_01_exit
jmp advance_enemy_routine
; pointer table for jumping guy (#$8 * #$2 = #$10 bytes)
jumping_soldier_routine_ptr_tbl:
.addr jumping_soldier_routine_00 ; CPU address $9380 - see if red soldier, if so mark flag, advance routine
.addr jumping_soldier_routine_01 ; CPU address $93a5 - set sprite, and perform jump animation
.addr shared_enemy_routine_00 ; CPU address $9346 - soldier has been hit by player bullet
.addr shared_enemy_routine_01 ; CPU address $9360 - perform enemy hit by bullet animation, then advance routine
.addr jumping_soldier_routine_04 ; CPU address $9437 - soldier destroyed, if red soldier play explosion
.addr enemy_routine_init_explosion ; CPU address $e74b from bank 7
.addr shared_enemy_routine_03 ; CPU address $e7aa from bank 7 - show explosion_type_02
.addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7
; see if red soldier, if so mark flag, advance routine
jumping_soldier_routine_00:
lda ENEMY_ATTRIBUTES,x ; load the ENEMY_ATTRIBUTES to get bit 1
lsr ; shift bit 0 into carry to disregard
lsr ; shift bit 1 into carry. This specifies if jumping soldier is red (drops a weapon item)
bcc @init_enemy_adv_routine ; branch if jumping soldier is not red
lda INDOOR_RED_SOLDIER_CREATED ; jumping soldier is red, see if one has already been created
bne @clear_red_soldier_continue ; jumping red soldier has been created, don't create another
lda INDOOR_ENEMY_ATTACK_COUNT ; load the total number of enemy attack rounds for the screen
beq @clear_red_soldier_continue ; don't have red jumping soldier on the first round of attacks
lda #$01 ; a = #$01
sta INDOOR_RED_SOLDIER_CREATED ; create a red jumping soldier, mark as created to prevent further creation
bne @init_enemy_adv_routine ; always jump, create red jumping soldier
@clear_red_soldier_continue:
lda ENEMY_ATTRIBUTES,x ; load the original ENEMY_ATTRIBUTES
and #$fd ; keep bits xxxx xx.x (drop red soldier status)
sta ENEMY_ATTRIBUTES,x ; update ENEMY_ATTRIBUTES to no longer create a red jumping soldier
@init_enemy_adv_routine:
ldy #$02 ; y = #$02 (jumping soldier)
jsr init_indoor_enemy_pos_and_vel ; initialize indoor jumping soldier enemy velocity and position
jmp advance_enemy_routine ; advance routine to jumping_soldier_routine_01
; set sprite, and perform jump animation
jumping_soldier_routine_01:
lda #$97 ; a = #$97 (sprite_97 jumping man in air)
ldy ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter
beq @set_sprite ; branch to continue if animation delay is #$00
lda #$93 ; a = #$93 (sprite_93 jumping man running)
cpy #$04 ; compare animation delay to #$04
bcc @set_sprite ; continue if animation delay is less than #$04
lda #$98 ; animation delay is > #$04, set a = #$98 (sprite_98 jumping man running)
@set_sprite:
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
lsr
lsr
lda #$00 ; a = #$00 (default palette)
bcc @set_sprite_attr ; branch if jumping soldier doesn't drop a R weapon item
.ifdef Probotector
lda #$07 ; red jumping soldier, set sprite palette #$03 and sprite code override bit
; jumping soldier drops R weapon, so override palette so it's red
.else
lda #$05 ; red jumping soldier, set sprite palette #$01 and sprite code override bit
; jumping soldier drops R weapon, so override palette so it's red
.endif
@set_sprite_attr:
sta $08
lda ENEMY_SPRITE_ATTR,x ; load enemy sprite attributes
ldy ENEMY_X_VELOCITY_FAST,x ; load enemy x velocity (direction)
bmi @flip_spite ; branch if jumping left
and #$bf ; jumping towards the right, strip bit 6 (don't horizontally flip sprite)
bpl @continue ; always branch
@flip_spite:
ora #$40 ; set bit 6 (flip sprite horizontally)
@continue:
and #$f8 ; strip sprite palette bits
ora $08 ; set sprite palette bits as calculated above (#$05 if jumping soldier is red, #$00 otherwise)
sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes
lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter
beq @apply_y_vel
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
and #$02 ; keep bits .... ..x.
bne @exit
lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter
cmp #$08
bne @exit
jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01)
sty $0a ; store closest player in $0a
jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position
lda #$60 ; a = #$60
ldy #$04 ; y = #$04 (jumping guy bullet speed code)
jmp aim_and_create_enemy_bullet ; get firing dir based on enemy ($08, $09) and player pos ($0b, $0a)
; and creates bullet (type a) with speed y if appropriate
@apply_y_vel:
jsr apply_enemy_velocity_set_bg_priority ; apply enemy velocity to position (remove if off screen), set bg priority
ldy ENEMY_VAR_1,x ; load current jumping_soldier_y_vel_tbl velocity
lda jumping_soldier_y_vel_tbl,y ; load actual amount to change y velocity
clc ; clear carry in preparation for addition
adc ENEMY_Y_POS,x ; add distance to current jumping soldier y position
sta ENEMY_Y_POS,x ; set new enemy y position on screen
inc ENEMY_VAR_1,x ; increment jumping_soldier_y_vel_tbl for next frame
lda ENEMY_VAR_1,x ; load new jumping_soldier_y_vel_tbl
cmp #$14 ; check to see if finished jump
bcc @exit ; exit if haven't yet finished jump
lda #$00 ; finished jumping, set a = #$00
sta ENEMY_VAR_1,x ; reset jumping_soldier_y_vel_tbl index to start next jump
lda #$10 ; a = #$10
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
@exit:
rts
; table for jumping guy y velocities when jumping (#$14 bytes)
jumping_soldier_y_vel_tbl:
.byte $fd,$fd,$fe,$fe,$fe,$ff,$ff,$ff,$00,$00,$00,$00,$01,$01,$01,$02
.byte $02,$02,$03,$03
jumping_soldier_routine_04:
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
and #$02 ; keep bits .... ..x.
beq @adv_routine ; not a red jumping soldier, just advance routine
lda ENEMY_X_POS,x ; red jumping soldier, enemy x position on screen
cmp #$64 ; check if on left side
bcc @adv_routine ; if too far to the left (behind wall), just advance routine
cmp #$9c ; check if on right side
bcs @adv_routine ; if too far to the right (behind wall), just advance routine
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
bmi @adv_routine ; if bit 7 is set, just advance routine (not sure when this happens) !(WHY?)
lsr ENEMY_ATTRIBUTES,x
lsr ENEMY_ATTRIBUTES,x ; zero out the ENEMY_ATTRIBUTES (not sure why this is needed) !(WHY?)
jmp play_explosion_sound ; play explosion
@adv_routine:
jmp advance_enemy_routine
; pointer table for seeking guy (#$7 * #$2 = #$e bytes)
grenade_launcher_routine_ptr_tbl:
.addr grenade_launcher_routine_00 ; CPU address $9468
.addr grenade_launcher_routine_01 ; CPU address $9479
.addr shared_enemy_routine_00 ; CPU address $9346 - soldier has been hit by player bullet
.addr shared_enemy_routine_01 ; CPU address $9360 - perform enemy hit by bullet animation, then advance routine
.addr enemy_routine_init_explosion ; CPU address $e74b from bank 7
.addr shared_enemy_routine_03 ; CPU address $e7aa from bank 7
.addr grenade_launcher_routine_06 ; CPU address $9529
grenade_launcher_routine_00:
lda #$01 ; a = #$01
sta GRENADE_LAUNCHER_FLAG ; flag that there is a grenade launcher on screen to prevent other enemies from being generated
jsr set_enemy_var_2_to_closest_x_player ; set closest player (#$00 or #$01) in ENEMY_VAR_2, a, and y
ldy #$06 ; y = #$06 (offset specifying grenade launcher configuration)
jsr init_indoor_enemy_pos_and_vel ; initialize indoor grenade launcher enemy velocity and position
lda #$20 ; a = #$20
jmp set_anim_delay_adv_enemy_routine_00 ; set ENEMY_ANIMATION_DELAY to #$20 and advance enemy routine
grenade_launcher_routine_01:
lda ENEMY_VAR_3,x
beq grenade_launcher_apply_vel_aim ; apply velocities, if animation timer elapsed, aim and set number of grenades to fire
lda #$96 ; a = #$96 (sprite_96) - grenade launcher
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
dec ENEMY_ANIMATION_DELAY,x ; decrement animation delay
bne @launch_grenade_if_appropriate ; fire if animation timer has elapsed
lda #$08 ; a = #$08
sta ENEMY_ANIMATION_DELAY,x ; set delay between bullets
lda #$00 ; a = #$00
sta ENEMY_VAR_3,x ;
lda ENEMY_X_POS,x ; load enemy x position on screen
jsr find_far_segment_for_a ; find the appropriate velocity code, given the X position
sta $0a ; store enemy segment code in $0a
jsr set_enemy_var_2_to_closest_x_player ; set closest player (#$00 or #$01) in ENEMY_VAR_2, a, and y
jsr find_close_segment ; get segment number for where player is on screen (#$06 = farthest left, #$00 = farthest right)
cmp $0a ; compare player and enemy segment
lda #$00 ; a = #$00
bcc @set_direction ; branch if player is to the right of the enemy
lda #$80 ; player to left of enemy
@set_direction:
eor ENEMY_X_VELOCITY_FAST,x ; exclusive or #$80 and the current fast velocity
bpl @exit ; branch if enemy needs to change direction to seek player
jsr reverse_enemy_x_direction ; reverse x direction
@exit:
rts
@launch_grenade_if_appropriate:
lda ENEMY_VAR_1,x ; see if grenade launcher is in same horizontal segment as player and ready to fire
beq @launch_grenade_exit ; exit if not ready to fire
dec ENEMY_ATTACK_DELAY,x ; decrement delay between attacks
bne @launch_grenade_exit ; exit if attack delay hasn't elapsed
lda #$14 ; a = #$14 (delay between grenades, indoor)
sta ENEMY_ATTACK_DELAY,x ; need to attack, but first reinitialize attack delay
dec ENEMY_VAR_1,x ; mark that the grenade has launched
jmp enemy_launch_grenade ; create a grenade enemy (ENEMY_TYPE #$12)
@launch_grenade_exit:
rts
; apply velocities, if animation timer elapsed, aim and set number of grenades to fire
grenade_launcher_apply_vel_aim:
jsr init_sprite_from_frame ; determine enemy sprite based on ENEMY_FRAME, flip sprite horizontally if running left
lda ENEMY_X_POS,x ; load enemy x position on screen
ldy ENEMY_X_VELOCITY_FAST,x ; load enemy fast velocity
bmi @grenade_launcher_running_left ; branch if enemy is running left
cmp #$a0 ; see if enemy position is off the screen to the right
bcs @cmp_player_enemy_segment ; if enemy too far to the right
bcc @apply_vel_and_aim ; always branch, enemy is running right and not off screen
@grenade_launcher_running_left:
cmp #$60
bcc @cmp_player_enemy_segment ; if enemy too far to the left
@apply_vel_and_aim:
jsr apply_enemy_velocity_set_bg_priority ; apply enemy velocity to position (remove if off screen), set bg priority
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne grenade_launcher_exit ; exit if animation delay hasn't elapsed
; animation delay elapsed
@cmp_player_enemy_segment:
lda ENEMY_X_POS,x ; load enemy x position on screen
jsr find_far_segment_for_a ; find the appropriate velocity code, given the X position
sta $0a ; set enemy segment in $0a
ldy ENEMY_VAR_2,x ; player offset to find the segment of
jsr find_close_segment ; get segment number for where player is on screen (#$06 = farthest left, #$00 = farthest right)
ldy #$18 ; y = #$18 (delay for pause between seeks)
cmp $0a ; see if player and enemy are in same segment
php ; push status flags on to the stack (namely the zero flag)
bne @set_num_grenades_exit ; branch if player and enemy are in different horizontal segments
ldy #$38 ; y = #$38 (delay for resume seek after attack)
@set_num_grenades_exit:
tya
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
inc ENEMY_VAR_3,x
lda #$04 ; a = #$04 (delay before attacking)
sta ENEMY_ATTACK_DELAY,x ; set delay between attacks
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
lsr ; strip off direction bit
and #$03 ; keep bits .... ..xx (number of grenades)
plp ; restore status flags
beq @set_enemy_var_1 ; player and grenade launcher in same segment, mark as available to fire
lda #$00 ; a = #$00 (don't fire grenades)
; set whether or not the grenade launcher is in the same horizontal segment as the player
; meaning the grenade launcher is ready to fire
@set_enemy_var_1:
sta ENEMY_VAR_1,x ; set number of grenades to fire
grenade_launcher_exit:
rts
; determine the closest player to the enemy horizontally, then store result in ENEMY_VAR_2, a, and y
; output
; * ENEMY_VAR_2, y, and a - closest player index (#$00 or #$01)
set_enemy_var_2_to_closest_x_player:
jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01)
lda PLAYER_STATE,y ; load the player state of the closest player
cmp #$01 ; see if in normal player state
beq @set_closest_player
tya ; store closest player in a
eor #$01 ; swap to other player (flip bit 0)
tay ; move new closest player back to y
@set_closest_player:
tya ; move closest player to a
sta ENEMY_VAR_2,x ; store closest player in ENEMY_VAR_2
rts
grenade_launcher_routine_06:
jsr enemy_routine_remove_enemy
lda #$00 ; a = #$00
sta GRENADE_LAUNCHER_FLAG ; clear flag that there is a grenade launcher on screen
; allows others enemies to be generated again
rts
; pointer table for group of 4 (#$8 * #$2 = #$10 bytes)
four_soldiers_routine_ptr_tbl:
.addr four_soldiers_routine_00 ; CPU address $9541 - initialize soldier
.addr four_soldiers_routine_01 ; CPU address $954c - walk until timer elapsed begin firing move to four_soldiers_routine_02
.addr four_soldiers_routine_02 ; CPU address $9582 - waits for delay, get into firing position, set new delay, go back to four_soldiers_routine_01
.addr shared_enemy_routine_00 ; CPU address $9346 - soldier has been hit by player bullet
.addr shared_enemy_routine_01 ; CPU address $9360 - perform enemy hit by bullet animation, then advance routine
.addr enemy_routine_init_explosion ; CPU address $e74b from bank 7
.addr shared_enemy_routine_03 ; CPU address $e7aa from bank 7 - show explosion_type_02
.addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7
; initialize soldier
four_soldiers_routine_00:
ldy #$04 ; y = #$04
jsr init_indoor_enemy_pos_and_vel ; initialize indoor group of 4 soldiers enemy velocity and position
jsr four_soldiers_set_firing_delay ; set appropriate animation delay
jmp advance_enemy_routine ; advance routine to four_soldiers_routine_01
; wait for firing delay, then begin running
four_soldiers_routine_01:
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne @fire_if_appropriate ; branch if delay hasn't elapsed to possibly fire
lda ENEMY_VAR_2,x ; animation delay is #$00, load number of fires in current round
cmp #$01 ; see if fired already
bne @set_delay_adv_routine ; branch haven't fired to continue
lda ENEMY_VAR_1,x ; already fired once, load soldier number within the group of four [#$00-#$03]
cmp #$02 ; split soldiers so some go left, some go right
bcc @set_delay_adv_routine ; continue in same direction for first 2 soldiers
jsr reverse_enemy_x_direction ; reverse enemy's x direction for other 2 soldiers
@set_delay_adv_routine:
jsr four_soldiers_get_delay_offset
lda four_soldiers_delay_running_tbl,y ; load initial animation delay
jmp set_anim_delay_adv_enemy_routine_00 ; set ENEMY_ANIMATION_DELAY and advance to four_soldiers_routine_02
; fire if animation delay is #$04 fire, otherwise, just exit
@fire_if_appropriate:
lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter
cmp #$04 ; compare animation delay to #$04
bne four_soldiers_exit ; exit if animation delay isn't #$04
jmp create_indoor_bullet ; animation delay is #$04, fire a regular indoor bullet
four_soldiers_exit:
rts
; table for group of 4 running distances (#$c bytes)
; initial delays before stop
; each column is for the specific soldier within the group of 4
four_soldiers_delay_running_tbl:
.byte $3f,$39,$33,$2d
.byte $18,$10,$10,$18 ; delays for second attack, each side
.byte $ff,$ff,$ff,$ff
; waits for animation delay to elapse, get into firing position, set new delay, go back to four_soldiers_routine_01
four_soldiers_routine_02:
jsr init_sprite_from_frame ; determine enemy sprite based on ENEMY_FRAME, flip sprite horizontally if running left
jsr apply_enemy_velocity_set_bg_priority ; apply enemy velocity to position (remove if off screen), set bg priority
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne four_soldiers_exit ; exit if animation delay hasn't elapsed
lda #$96 ; animation delay elapsed, set a = #$96 (sprite_96) indoor soldier firing position
sta ENEMY_SPRITES,x ; set enemy sprite to indoor soldier firing position
inc ENEMY_VAR_2,x ; increment the number of times the soldier has fired
jsr four_soldiers_set_firing_delay ; set animation delay
lda #$02 ; a = #$02
jmp set_enemy_routine_to_a ; set enemy routine index to four_soldiers_routine_01
four_soldiers_set_firing_delay:
jsr four_soldiers_get_delay_offset ; determine next delay for soldier and set it to y
lda four_soldiers_firing_delay_tbl,y ; load appropriate delay
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
rts
; gets the appropriate delay based on how many times the soldier has fired and
; the soldier index within the group of four
; output
; * y - the four_soldiers_delay_running_tbl and four_soldiers_firing_delay_tbl offset for the enemy
four_soldiers_get_delay_offset:
lda ENEMY_VAR_2,x ; load number of times the soldier has fired
asl
asl ; double twice since each entry is #$04 bytes
sta $08 ; store result in $08
lda ENEMY_VAR_1,x ; load soldier number within the group of four [#$00-#$03]
clc ; clear carry in preparation for addition
adc $08 ; add the soldier index to the row offset to get exact delay offset
tay ; transfer offset to y
rts
; table for group of 4 soldiers standing still to fire delay (#$c bytes)
; each row is are delays for a round of attacks
; each column is for the specific soldier within the group of 4
; used in four_soldiers_routine_00 and four_soldiers_routine_02
four_soldiers_firing_delay_tbl:
.byte $01,$07,$0d,$13 ; delay before running into screen
.byte $18,$18,$18,$18 ; delay for firing first shot
.byte $10,$18,$18,$10 ; delay for firing second shot
; pointer table for rollers generator (#$3 * #$2 = #$6 bytes)
indoor_roller_gen_routine_ptr_tbl:
.addr indoor_roller_gen_routine_00 ; CPU address $95c8
.addr indoor_roller_gen_routine_01 ; CPU address $95cd
.addr remove_enemy ; CPU address $e809 from bank 7
indoor_roller_gen_routine_00:
lda #$60 ; a = #$60 (delay before first set of rollers)
jmp set_anim_delay_adv_enemy_routine_00 ; set ENEMY_ANIMATION_DELAY to #$60 and advance enemy routine
indoor_roller_gen_routine_01:
lda INDOOR_ENEMY_ATTACK_COUNT ; load the total number of enemy attack rounds for the screen
cmp #$07 ; total number of cycles for rollers to stop
bcc @continue ; haven't reached total 'rounds' of attack, create roller if timer elapsed
jmp remove_enemy ; remove enemy
@continue:
lda FRAME_COUNTER ; load frame counter
lsr
bcc @exit ; ENEMY_ANIMATION_DELAY is decremented every odd frame
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne @exit ; exit if ENEMY_ANIMATION_DELAY hasn't elapsed
lda ENEMY_ATTRIBUTES,x ; generate roller, load roller generator attributes
and #$07 ; keep bits .... .xxx
asl ; double since each entry is #$02 byte memory address
tay ; transfer offset to y
lda roller_gen_init_tbl,y ; rollers pattern - low byte
sta $10 ; store address low byte in $10
lda roller_gen_init_tbl+1,y ; rollers pattern - high byte
sta $11 ; store address high byte in $11
@create_roller:
ldy ENEMY_VAR_1,x ; load roller number to generate
@loop:
lda ($10),y ; load the first byte
cmp #$ff
bne @create_roller_for_a
ldy #$00 ; y = #$00
beq @loop
@create_roller_for_a:
sta $0b ; store first byte in $0b
and #$0f ; keep bits .... xxxx
sta $0a ; store ENEMY_ATTRIBUTES for roller
iny
lda ($10),y
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
iny
tya
sta ENEMY_VAR_1,x
lda $0b
lsr
lsr
lsr
lsr
tay
lda roller_initial_x_pos_tbl,y ; load the roller's initial x position
sta $09 ; store roller x position
lda #$70 ; a = #$70 (rollers starting y position)
sta $08 ; store roller y position
tya ; move the horizontal segment number into a
jsr create_roller_with_segment_a ; set roller x velocity based on X position
lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter
beq @create_roller
@exit:
rts
; rollers starting x positions (#$7 bytes)
roller_initial_x_pos_tbl:
.byte $98,$90,$88,$80,$78,$70,$68
; pointer table for rollers pattern (#$2 * #$2 = #$4 bytes)
roller_gen_init_tbl:
.addr roller_gen_init_00 ; CPU address $9634
.addr roller_gen_init_01 ; CPU address $966d
; table for rollers type a (#$39 bytes)
; byte 0 : position and attributes for roller 0
; byte 1 : delay before roller 0
; byte 2 : position and attributes for roller 1
; byte 3 : delay before roller 1
;
; ... 7 times (d bytes) ...
;
; byte d : delay before next set of rollers
;
; ff : end of sets, go from the start
roller_gen_init_00:
.byte $00,$00
.byte $10,$00
.byte $20,$00
.byte $30,$00
.byte $40,$00
.byte $50,$00
.byte $60,$f0
.byte $01,$00
.byte $11,$00
.byte $21,$00
.byte $31,$00
.byte $41,$00
.byte $51,$00
.byte $61,$f0
.byte $30,$10
.byte $20,$00
.byte $40,$10
.byte $10,$00
.byte $50,$10
.byte $00,$00
.byte $60,$f0
.byte $00,$00
.byte $60,$10
.byte $10,$00
.byte $50,$10
.byte $20,$00
.byte $40,$10
.byte $30,$f0
.byte $ff
; table for rollers type b (#$f bytes)
roller_gen_init_01:
.byte $00,$00
.byte $20,$00
.byte $40,$00
.byte $60,$f0
.byte $10,$00
.byte $30,$00
.byte $50,$f0
.byte $ff
; gets a number from #$06 to #$00 indicating how far the closest player to the enemy is from the left of the screen
; starting from #$06 for farthest left, down to #$00 for farthest right
; input
; * y - player offset to compare
; output
; * a - #$00 when player to the right of cutoff, offset when to the left
; very similar to find_far_segment_for_a in bank 7
; usually used together to compare player and enemy x positions on indoor levels
find_close_segment:
lda SPRITE_X_POS,y ; load the player's x position
ldy #$06 ; y = #$06
@loop:
cmp indoor_close_segment_tbl,y
bcc @exit
dey
bmi @use_segment_0
bcs @loop
@use_segment_0:
lda #$00 ; a = #$00
rts
@exit:
tya
rts
; table for close segment x offsets (right to left) (#$7 bytes)
indoor_close_segment_tbl:
.byte $ff,$bc,$a4,$8c,$74,$5c,$44
; initializes indoor enemy velocity and position
; input
; * a - is indoor enemy type
; * #$00 - indoor soldier (ENEMY_TYPE #$15)
; * #$02 - jumping soldier (ENEMY_TYPE #$16)
; * #$04 - group of 4 soldiers (ENEMY_TYPE #$18)
; * #$06 - grenade launcher (ENEMY_TYPE #$17)
init_indoor_enemy_pos_and_vel:
lda indoor_soldier_x_velocity_tbl,y ; load the enemy's fractional velocity byte
sta ENEMY_X_VELOCITY_FRACT,x ; store the enemy's fractional velocity byte
lda indoor_soldier_x_velocity_tbl+1,y ; load the enemy's velocity high byte
sta ENEMY_X_VELOCITY_FAST,x ; store the enemy's velocity high byte
lda ENEMY_ATTRIBUTES,x ; load the ENEMY_ATTRIBUTES
lsr ; shift bit 0 to the carry
lda #$a8 ; a = #$a8 (initial x position, from right)
bcc @set_enemy_pos ; if bit 0 is 0 then enemy comes from right screen, branch
jsr reverse_enemy_x_direction ; enemy comes from left side, reverse enemy's x direction
lda #$58 ; a = #$58 (initial x position, from left)
@set_enemy_pos:
sta ENEMY_X_POS,x ; set enemy x position on screen
lda #$6d ; load y position, hard-coded for indoor levels
sta ENEMY_Y_POS,x ; set enemy y position on screen
rts
; table for guys x velocities (#$8 bytes)
; byte 0 - ENEMY_X_VELOCITY_FRACT
; byte 1 - ENEMY_X_VELOCITY_FAST
indoor_soldier_x_velocity_tbl:
.byte $20,$ff ; indoor soldier (-.875)
.byte $40,$ff ; jumping soldier (-.75)
.byte $40,$ff ; group of 4 (-.75)
.byte $40,$ff ; grenade launcher (-.75)
; apply enemy velocity to position
; use updated position to determine if enemy off screen and removes enemy if so
; use updated position to determine sprite attribute background priority
; (enemy drawn in front or behind background)
apply_enemy_velocity_set_bg_priority:
lda ENEMY_X_VEL_ACCUM,x
clc ; clear carry in preparation for addition
adc ENEMY_X_VELOCITY_FRACT,x
sta ENEMY_X_VEL_ACCUM,x
lda ENEMY_X_POS,x ; load enemy x position on screen
adc ENEMY_X_VELOCITY_FAST,x
sta ENEMY_X_POS,x ; set enemy x position on screen
ldy ENEMY_X_VELOCITY_FAST,x
bmi @continue
cmp #$b0 ; indoor enemy right limit (disappearance)
bcs @remove_enemy ; remove enemy if too far to the right
bcc @set_enemy_bg_priority
@continue:
cmp #$50 ; indoor enemy left limit (disappearance)
bcs @set_enemy_bg_priority
@remove_enemy:
jmp remove_enemy ; remove enemy
@set_enemy_bg_priority:
ldy ENEMY_SPRITE_ATTR,x ; enemy sprite attributes
cmp #$a0
bcs @draw_enemy_behind_bg
cmp #$60
bcs @draw_enemy_in_front_of_bg
@draw_enemy_behind_bg:
tya
ora #$20 ; set bits ..x. .... (drawn behind bg)
bne @set_enemy_sprite_attr
@draw_enemy_in_front_of_bg:
tya
and #$df ; keep bits xx.x xxxx (drawn in front of bg)
@set_enemy_sprite_attr:
sta ENEMY_SPRITE_ATTR,x
rts
; dead code, never called !(UNUSED)
bank_0_unused_label_00:
jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's Y and X position respectively
; creates an indoor roller enemy (ENEMY_TYPE #$11)
create_roller:
jsr find_far_segment_for_x_pos ; get horizontal segment based on X position ($09)
; creates an indoor roller enemy (ENEMY_TYPE #$11)
; input
; * a - roller horizontal segment number (offset into roller_vel_code_tbl)
; * $09 - x position
; * $08 - y position
create_roller_with_segment_a:
sta $0f ; store horizontal segment code in $0f
lda ENEMY_ATTACK_FLAG ; see if enemies should attack
beq @exit ; exit if enemies shouldn't attack
jsr find_next_enemy_slot ; find next available enemy slot, put result in x register
bne @exit ; exit if not enemy slot available to create the roller
lda #$11 ; a = #$11 (enemy type code for rollers)
jsr init_enemy_set_type_and_pos ; initialize enemy, set enemy type to a, and set X ($09) and Y ($08) position
lda $0a ; load ENEMY_ATTRIBUTES
sta ENEMY_ATTRIBUTES,x
lda $0f ; load velocity code
asl
tay
lda roller_vel_code_tbl,y
sta ENEMY_X_VELOCITY_FRACT,x
lda roller_vel_code_tbl+1,y
sta ENEMY_X_VELOCITY_FAST,x
lda #$80 ; a = #$80 (y velocity for rollers, low byte) (.5)
sta ENEMY_Y_VELOCITY_FRACT,x
lda #$00 ; a = #$00 (y velocity for rollers, high byte)
sta ENEMY_Y_VELOCITY_FAST,x
@exit:
ldx ENEMY_CURRENT_SLOT
rts
; table for rollers x velocities (#$e bytes)
roller_vel_code_tbl:
.byte $55,$00
.byte $38,$00
.byte $1c,$00
.byte $00,$00
.byte $e4,$ff
.byte $c8,$ff
.byte $ab,$ff
; grenade launcher and indoor soldier
enemy_launch_grenade:
jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position
jsr find_far_segment_for_x_pos ; get horizontal segment based on X position ($09)
sta $0f ; store velocity code in $0f
lda ENEMY_ATTACK_FLAG ; see if enemies should attack
beq @exit ; exit if enemies shouldn't attack
jsr find_next_enemy_slot ; find next available enemy slot, put result in x register
bne @exit
lda #$12 ; a = #$12 (code for grenade)
jsr init_enemy_set_type_and_pos ; initialize enemy, set enemy type to a, and set X ($09) and Y ($08) position
lda $0f ; load velocity code
asl
tay
lda grenade_vel_code_tbl,y
sta ENEMY_X_VELOCITY_FRACT,x
lda grenade_vel_code_tbl+1,y
sta ENEMY_X_VELOCITY_FAST,x
lda #$80 ; a = #$80
sta ENEMY_Y_VELOCITY_FRACT,x
lda #$00 ; a = #$00
sta ENEMY_Y_VELOCITY_FAST,x
@exit:
ldx ENEMY_CURRENT_SLOT
rts
; table for grenade X velocities (#$e bytes)
grenade_vel_code_tbl:
.byte $55,$00,$38,$00,$1c,$00,$00,$00,$e4,$ff,$c8,$ff,$ab,$ff
; fire a regular indoor bullet
create_indoor_bullet:
jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position
lda $09 ; load enemy x position
cmp #$a0 ; compare to right side of screen
bcs @exit ; if x position is >= #$a0 (right edge of indoor screen), just exit
cmp #$60 ; compare to left side of screen
bcc @exit ; if x position is < #$60 (left edge of indoor screen), just exit
lda ENEMY_ATTACK_FLAG ; see if enemies should attack
beq @exit ; exit if enemies shouldn't attack
jsr find_far_segment_for_x_pos ; get horizontal segment based on X position ($09)
sta $0f ; store segment number in $0f
jsr find_next_enemy_slot ; find next available enemy slot, put result in x register
bne @exit ; exit if no enemy slot available
lda #$01 ; a = #$01 (bullet enemy type)
jsr init_enemy_set_type_and_pos ; initialize bullet enemy, set enemy type to a, and set X ($09) and Y ($08) position
lda #$03 ; a = #$03 (indoor regular bullet type)
sta ENEMY_VAR_1,x ; set ENEMY_VAR_1 to #$03
lda $0f ; load horizontal segment
asl ; each entry in table is #$02 bytes (fractional and fast), so double offset
tay ; transfer offset to y
lda indoor_bullet_velocity_tbl,y ; load the x fractional velocity
sta ENEMY_X_VELOCITY_FRACT,x ; store in ENEMY_X_VELOCITY_FRACT
lda indoor_bullet_velocity_tbl+1,y ; load the x fast velocity
sta ENEMY_X_VELOCITY_FAST,x ; store in ENEMY_X_VELOCITY_FAST
lda #$40 ; a = #$40 (y velocity of bullet)
sta ENEMY_Y_VELOCITY_FRACT,x
lda #$01 ; a = #$01 (y velocity of bullet)
sta ENEMY_Y_VELOCITY_FAST,x
@exit:
ldx ENEMY_CURRENT_SLOT
rts
; table for indoor bullet velocity (#$e bytes)
indoor_bullet_velocity_tbl:
.byte $d4,$00 ; (.83)
.byte $8d,$00 ; (.55)
.byte $46,$00 ; (.27)
.byte $00,$00 ; (0)
.byte $ba,$ff ; (-.27)
.byte $73,$ff ; (-.55)
.byte $2c,$ff ; (-.83)
; initialize enemy, set enemy type to a, and set X ($09) and Y ($08) position
init_enemy_set_type_and_pos:
sta ENEMY_TYPE,x ; set enemy type
jsr initialize_enemy
lda $08
sta ENEMY_Y_POS,x ; enemy y position on screen
lda $09
sta ENEMY_X_POS,x ; set enemy x position on screen
rts
; Pointer table for Rock Platform Code (#$2 * #$2 = #$4 bytes)
floating_rock_routine_ptr_tbl:
.addr floating_rock_routine_00 ; CPU address $97e9
.addr floating_rock_routine_01 ; CPU address $981c
; also used for moving flame enemy
; loads initial velocity, direction, and boundaries for floating rock and moving flames on vertical level
floating_rock_routine_00:
lda ENEMY_ATTRIBUTES,x ; load attributes to determine direction and boundaries
asl
tay
lda rock_moving_flame_init_vel_tbl,y ; load x fractional velocity value
sta ENEMY_X_VELOCITY_FRACT,x ; store x fractional velocity value
lda rock_moving_flame_init_vel_tbl+1,y ; load x number of units to move per frame
sta ENEMY_X_VELOCITY_FAST,x ; store x number of units to move per frame
lda rock_moving_flame_boundaries_tbl,y ; load left X boundary
sta ENEMY_VAR_2,x ; store left boundary in ENEMY_VAR_2
lda rock_moving_flame_boundaries_tbl+1,y
sta ENEMY_VAR_1,x ; store right boundary in ENEMY_VAR_1
jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll
jmp advance_enemy_routine
; table for rock platform and moving flame velocities (#$8 bytes)
; byte 0 - x fractional velocity value
; byte 1 - x velocity fast value
rock_moving_flame_init_vel_tbl:
.byte $80,$ff ; slow (00) rock platform x velocity (move left every other frame)
.byte $c0,$00 ; fast (01) rock platform x velocity (move right 3 out of every 4 frames)
.byte $80,$ff ; moving flame going left x velocity (move left every other frame)
.byte $80,$00 ; moving flame going right x velocity (move right every other frame)
; table for rock platform and moving flame boundaries (#$8 bytes)
; byte 0 - left X boundary
; byte 1 - right X boundary
rock_moving_flame_boundaries_tbl:
.byte $50,$b0 ; slow (00) rock platform boundaries
.byte $70,$c0 ; fast (01) rock platform boundaries
.byte $48,$b8 ; flame going left boundaries
.byte $48,$b8 ; flame going right boundaries
; update enemy position based on velocity and direction
; see if enemy encountered left or right barrier if so, tell enemy to turn around
floating_rock_routine_01:
lda #$48 ; a = #$48 (sprite_48) floating rock
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
; also used by moving flame
update_pos_turn_around_if_needed:
jsr update_enemy_pos ; apply velocities and scrolling adjust
lda ENEMY_X_POS,x ; load enemy x position on screen
ldy ENEMY_X_VELOCITY_FAST,x ; see if moving left (#$ff) or right (#$00)
bmi @compare_left_barrier ; branch if enemy is moving left
cmp ENEMY_VAR_1,x ; enemy moving right; compare X position to right barrier
bcc @exit ; enemy didn't hit barrier, exit
bcs @turn_around ; enemy hig barrier, tell them to turn around
@compare_left_barrier:
cmp ENEMY_VAR_2,x ; compare enemy X position to left barrier position
bcs @exit ; enemy didn't hit barrier, exit
@turn_around:
jmp reverse_enemy_x_direction ; reverse x direction
@exit:
rts
; pointer table for moving flame (#$2 * #$2 = #$4 bytes)
moving_flame_routine_ptr_tbl:
.addr floating_rock_routine_00 ; CPU address $97e9
.addr moving_flame_routine_01 ; CPU address $9840
; update enemy position based on velocity and direction
; see if enemy encountered left or right barrier if so, tell enemy to turn around
; also, set flashing palette
moving_flame_routine_01:
lda #$49 ; a = #$49 (sprite_49) bridge fire
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
lda FRAME_COUNTER ; load frame counter to enable flashing every #$08 frames
lsr
lsr
lsr
lsr
lda #$00 ; default sprite attribute (palette)
bcc @continue
lda #$40 ; secondary sprite attribute (palette)
@continue:
sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes
jmp update_pos_turn_around_if_needed ; update enemy position and turn around if encountered barrier
; pointer table for falling rock generator (#$3 * #$2 = #$6 bytes)
rock_cave_routine_ptr_tbl:
.addr rock_cave_routine_00 ; CPU address $985d
.addr rock_cave_routine_01 ; CPU address $9863
.addr rock_cave_routine_02 ; CPU address $986b
rock_cave_routine_00:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
jmp advance_enemy_routine ; advance to next routine
rock_cave_routine_01:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda #$08 ; a = #$08 (delay before first falling rock)
jmp set_anim_delay_adv_routine ; set ENEMY_ANIMATION_DELAY then advance enemy routine to rock_cave_routine_02
rock_cave_routine_02:
jsr update_enemy_pos ; apply velocities and scrolling adjust
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne rock_exit
lda #$e0 ; a = #$e0 (delay before next falling rock)
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
lda #$13 ; a = #$13 (enemy type for falling rock)
jmp generate_enemy_a ; generate #$13 enemy (falling rock)
; pointer table for falling rock (#$6 * #$2 = #$c bytes)
falling_rock_routine_ptr_tbl:
.addr falling_rock_routine_00 ; CPU address $9889 - initialize sprite, set initial delay, advance routine
.addr falling_rock_routine_01 ; CPU address $988e - wobble left and right until animation delay elapsed, then advance routine
.addr falling_rock_routine_02 ; CPU address $98ce - actual falling of the rock, and bounce against the ground
.addr enemy_routine_init_explosion ; CPU address $e74b
.addr enemy_routine_explosion ; CPU address $e7b0
.addr enemy_routine_remove_enemy ; CPU address $e806
falling_rock_routine_00:
lda #$40 ; a = #$40 (delay before rock starts falling)
jmp set_anim_delay_adv_routine ; set ENEMY_ANIMATION_DELAY then advance enemy routine to falling_rock_routine_01
; wobble left and right until animation delay elapsed, then advance routine
falling_rock_routine_01:
jsr falling_rock_set_sprite ; set boulder sprite (sprite_4a)
jsr update_enemy_pos ; apply velocities and scrolling adjust
lda FRAME_COUNTER ; load frame counter
and #$03 ; keep bits .... ..xx
bne @continue ; branch if not 4th frame since no rocking/swaying direction change
lda FRAME_COUNTER ; frame counter divisible by #$04, reload frame counter
lsr
lsr
lsr ; push bit 2 into carry, every #$04 frames rock sways in a direction
bcc @dec_x_pos ; branch if rocking left
inc ENEMY_X_POS,x ; rocking right, increment enemy x position
bcs @continue ; skip decrement if rocking right
; rocking left
@dec_x_pos:
dec ENEMY_X_POS,x ; enemy x position
; enable collision, set animation delay, wait, advance routine
@continue:
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne rock_exit ; exit if animation delay hasn't elapsed
jsr enable_enemy_collision ; delay elapsed, enable bullet-enemy collision and player-enemy collision checks
lda #$01 ; a = #$01
jmp set_anim_delay_adv_routine ; set ENEMY_ANIMATION_DELAY then advance enemy routine to falling_rock_routine_02
; sets the sprite attribute for the falling rock so it tumbles
falling_rock_set_sprite_and_attr:
lda FRAME_COUNTER ; load frame counter
lsr
lsr ; moves to next flip every #$04 frames
and #$03 ; keep bits .... ..xx
tay ; transfer to y to be falling_rock_sprite_attr_tbl offset
lda ENEMY_SPRITE_ATTR,x ; load enemy sprite attributes
and #$3f ; strip horizontal and vertical flip bits
ora falling_rock_sprite_attr_tbl,y ; load whether the rock needs to be reflected
sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes
falling_rock_set_sprite:
lda #$4a ; a = #$4a (sprite_4a - boulder)
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
rock_exit:
rts
; actual falling of the rock, and bounce against ground
falling_rock_routine_02:
jsr falling_rock_set_sprite_and_attr ; set the sprite attribute so the rock tumbles
lda ENEMY_Y_POS,x ; load enemy y position on screen
cmp ENEMY_VAR_1,x ; compare y position to the ground collision y position
bcc @apply_gravity_update_pos ; branch if rock is still above where it collided with the ground
ldy #$08 ; rock below the ground it collided with, check for next collision
jsr add_y_to_y_pos_get_bg_collision ; add #$08 to enemy y position and gets bg collision code
bcc @apply_gravity_update_pos ; branch if no floor collision
lda #$05 ; collision with floor, a = #$05 (sound_05)
jsr play_sound ; play sound of rock hitting ground
lda #$40 ; a = #$40
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
lda ENEMY_Y_POS,x ; load enemy y position on screen
clc ; clear carry in preparation for addition
adc #$10 ; add #$10 to get y position of ground
bcc @continue ; branch if no overflow occurred (not offscreen)
lda #$ff ; off screen, set y position to #$ff
; set y velocity to -1.25
@continue:
sta ENEMY_VAR_1,x ; set ENEMY_VAR_1 to ground y position
lda #$c0 ; a = #$c0 (.75) (y velocity for rock bouncing, low)
sta ENEMY_Y_VELOCITY_FRACT,x
lda #$fe ; a = #$fe (-2) (y velocity for rock bouncing, high)
sta ENEMY_Y_VELOCITY_FAST,x ; combined the total velocity is -1.25
@apply_gravity_update_pos:
jsr add_10_to_enemy_y_fract_vel ; add #$10 to y fractional velocity (.06 faster) (applying gravity)
lda ENEMY_VAR_1,x ; load rock ground collision y position
clc ; clear carry in preparation for addition
adc FRAME_SCROLL ; how much to scroll the screen (#00 - no scroll)
bcc @update_pos ; branch if no carry to set ENEMY_VAR_1 and update position
lda #$ff ; a = #$ff
@update_pos:
sta ENEMY_VAR_1,x ; adjust by scroll position
jmp update_enemy_pos ; apply velocities and scrolling adjust
; table for falling rock mirroring codes (#$4 bytes)
; $00 - no flip
; $40 - flip horizontally
; $c0 - flip horizontally and vertically
; $80 - flip vertically
falling_rock_sprite_attr_tbl:
.byte $00,$40,$c0,$80
; pointer table for level 3 boss mouth (dragon) (#$9 * #$2 = #$12 bytes)
boss_mouth_routine_ptr_tbl:
.addr boss_mouth_routine_00 ; CPU address $992a
.addr boss_mouth_routine_01 ; CPU address $9941
.addr boss_mouth_routine_02 ; CPU address $9954
.addr boss_mouth_routine_03 ; CPU address $99a2
.addr boss_mouth_routine_04 ; CPU address $99ef
.addr boss_defeated_routine ; CPU address $e740 from bank 7
.addr enemy_routine_explosion ; CPU address $e7b0 from bank 7
.addr shared_enemy_routine_clear_sprite ; CPU address $e814 from bank 7 - set tile sprite code to #$00 and advance routine
.addr boss_mouth_routine_08 ; CPU address $9a14
; set HP, frame, animation/attack delay, advance routine
boss_mouth_routine_00:
lda #$20 ; a = #$20
sta ENEMY_VAR_1,x ; level 3 boss mouth hp
lda #$02 ; a = #$02
sta ENEMY_VAR_3,x ; set flag so when dragon is destroyed, the animation is
; delayed by one frame. See `boss_mouth_routine_08`
lda #$01 ; a = #$01
sta ENEMY_FRAME,x ; set enemy animation frame number
lda #$ff ; a = #$ff (positive delay before first attack)
; used by boss mouth, rock cave, and falling rock
set_anim_delay_adv_routine:
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
jmp advance_enemy_routine
; wait for boss auto scroll to complete, wait for animation delay to elapse, advance routine
boss_mouth_routine_01:
jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll
lda BOSS_AUTO_SCROLL_COMPLETE ; see if boss reveal auto-scroll has completed
beq boss_mouth_exit ; exit if boss reveal auto-scroll hasn't completed
lda BG_PALETTE_ADJ_TIMER
bne boss_mouth_exit
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne boss_mouth_exit ; exit if animation delay hasn't elapsed
jmp advance_enemy_routine ; advance to boss_mouth_routine_02
; animate opening of mouth
boss_mouth_routine_02:
jsr boss_mouth_draw_supertiles_set_delay
bcs boss_mouth_exit ; exit if didn't need to draw updates super-tiles
lda ENEMY_FRAME,x ; updated super-tiles, load enemy animation frame number
cmp #$02
bcs boss_mouth_enable_collision ; boss mouth is open, enable collision, set attack delay, advance routine
inc ENEMY_FRAME,x ; increment enemy animation frame number
boss_mouth_exit:
rts
boss_mouth_enable_collision:
jsr enable_bullet_enemy_collision ; allow bullets to collide (and stop) upon colliding with boss mouth
lda ENEMY_VAR_1,x
sta ENEMY_HP,x ; set enemy hp
lda #$06 ; a = #$06
sta ENEMY_ATTACK_DELAY,x ; delay between mouth open and attack
lda #$70 ; a = #$70 (time during which mouth is open)
bne set_anim_delay_adv_routine ; set ENEMY_ANIMATION_DELAY then advance enemy routine to boss_mouth_routine_03
boss_mouth_draw_supertiles_set_delay:
jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne @set_carry_exit ; exit if animation delay timer hasn't elapsed
lda ENEMY_FRAME,x ; load enemy animation frame number
asl ; double since each entry is #$02 bytes
tay ; transfer offset to y
lda boss_mouth_nametable_update_tbl,y ; load first nametable update super-tile index
sta $10 ; store in $10 for update_2_enemy_supertiles
lda boss_mouth_nametable_update_tbl+1,y ; load second nametable update super-tile index
ldy #$01 ; y = #$01, skip setting collision
jsr update_2_enemy_supertiles ; draw nametable update super-tile $10, then a at enemy position
lda #$06 ; a = #$06
bcc @set_anim_exit ; (delay between frames when animating mouth open/close)
lda #$01 ; a = #$01
@set_anim_exit:
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
rts
@set_carry_exit:
sec ; set carry flag
rts
; table for nametable update super-tile indexes (#$6 bytes)
; offsets into level_3_nametable_update_supertile_data
; related to closed/closing mouth nametable tiles
; #$20 (#$a0) - boss mouth closed (top half)
; #$21 (#$a1) - boss mouth closed (bottom half)
; #$22 (#$a2) - boss mouth partially open (top half)
; #$23 (#$a3) - boss mouth partially open (bottom half)
; #$24 (#$a4) - boss mouth fully open (top half)
; #$25 (#$a4) - boss mouth fully open (bottom half)
boss_mouth_nametable_update_tbl:
.byte $a0,$a1 ; closed
.byte $a2,$a3 ; partially open
.byte $a4,$a5 ; fully open
boss_mouth_routine_03:
jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll
lda ENEMY_ATTACK_DELAY,x ; load delay between attacks
beq @adv_routine
dec ENEMY_ATTACK_DELAY,x ; decrement delay between attacks
bne @adv_routine
lda #$02 ; a = #$02
sta $16 ; number of projectiles
@projectile_loop:
ldy #$08 ; set vertical offset from enemy position (param for add_with_enemy_pos)
lda #$00 ; set horizontal offset from enemy position (param for add_with_enemy_pos)
jsr add_with_enemy_pos ; stores absolute screen x position in $09, and y position in $08
ldy #$06 ; y = #$06 (bullet speed)
lda BOSS_SCREEN_ENEMIES_DESTROYED ; load number of destroyed dragon arm orbs
cmp #$02 ; see if both arms have been destroyed
bcc @continue ; branch if at least one dragon arm orb still exists
ldy #$07 ; both dragon arm orbs destroyed, increase bullet speed to #$07 (bullet speed)
@continue:
sty $0f
ldy $16
lda mouth_projectile_type_angle,y ; load mouth projectile type (xxx. ....) and angle index (...x xxxx)
ldy $0f ; load mouth projectile speed
jsr create_enemy_bullet_angle_a ; create a bullet with speed y, bullet type a, angle a at position ($09, $08)
dec $16
bpl @projectile_loop
@adv_routine:
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne boss_mouth_exit
jsr disable_bullet_enemy_collision ; allow bullets to travel through boss mouth
lda ENEMY_HP,x ; load enemy hp
sta ENEMY_VAR_1,x ; store enemy hp here while it's closed
lda #$f1 ; a = #$f1 (f1 = hittable, no damage)
sta ENEMY_HP,x ; set enemy hp
lda #$06 ; a = #$06
jmp set_anim_delay_adv_routine ; set ENEMY_ANIMATION_DELAY then advance enemy routine to boss_mouth_routine_04
; table for mouth projectiles types and angles (#$3 bytes)
; from 3h, clockwise
; $00-$17: white bullets
; $20-$37: bomb drop (like level 1 boss bomb turret)
; $40-$57: red bullets (like base triple cannon)
; $60-$77: white bullets with limited range
; $80-$97: orange fireballs (default)
mouth_projectile_type_angle:
.byte $88,$86,$84
boss_mouth_routine_04:
jsr boss_mouth_draw_supertiles_set_delay
bcs @exit
lda ENEMY_FRAME,x ; load enemy animation frame number
beq @set_mouth_delay
dec ENEMY_FRAME,x ; decrement enemy animation frame number
@exit:
rts
@set_mouth_delay:
lda BOSS_SCREEN_ENEMIES_DESTROYED ; load number of destroyed dragon arm orbs
cmp #$02 ; see if both arms have been destroyed
bcc @continue ; branch if at least one dragon arm orb still exists
lda #$02 ; both dragon arms destroyed, ensure a = #$02, should already be #$02
@continue:
tay ; transfer delay offset to y
lda boss_mouth_anim_delay_tbl,y
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
lda #$03 ; a = #$03
jmp set_enemy_routine_to_a ; set enemy routine index to boss_mouth_routine_02
; to begin wait to open mouth
; table for delays between mouth attacks, depends on number of dragon arms (#$3 bytes)
; #$c0 delay with 2 arms
; #$70 delay with 1 arm
; #$20 delay without arms
boss_mouth_anim_delay_tbl:
.byte $c0,$70,$20
; draw explosions
boss_mouth_routine_08:
dec ENEMY_VAR_3,x ; decrement variable to not start explosion until next iteration
bne @exit ; exit if ENEMY_VAR_3 is not #$00
lda #$01 ; a = #$01
sta ENEMY_VAR_3,x
jsr @update_nametable_create_explosions
bcs @exit ; exit if unable to create explosion
inc ENEMY_VAR_2,x ; increment current explosion counter
lda ENEMY_VAR_2,x
cmp #$0e ; see if have generated all explosions
bcs @set_delay_remove ; remove enemy all explosions have been generated
@exit:
rts
@set_delay_remove:
lda #$60 ; a = #$60
jmp set_delay_remove_enemy
@update_nametable_create_explosions:
ldy ENEMY_VAR_2,x
lda boss_mouth_y_pos_tbl,y
sta ENEMY_Y_POS,x ; enemy y position on screen
lda boss_mouth_x_pos_tbl,y
sta ENEMY_X_POS,x ; set enemy x position on screen
lda boss_mouth_destroyed_nametable_update_tbl,y
jsr draw_enemy_supertile_a ; draw super-tile a (offset into level nametable update table) at position (ENEMY_X_POS, ENEMY_Y_POS)
bcs @draw_failed_exit ; exit when unable to update super-tile
ldy ENEMY_VAR_2,x ; load current explosion counter
lda boss_mouth_y_pos_tbl,y ; load explosion y position
sta $08 ; store explosion y position in $80
lda boss_mouth_x_pos_tbl,y ; load explosion x position
sta $09 ; store explosion x position in $09
jsr create_two_explosion_89 ; create explosion #$89 at location ($09, $08)
clc ; indicate successfully created explosions
rts
@draw_failed_exit:
lda #$01
sta ENEMY_VAR_3,x ; unnecessary since already #$01
rts
; tables for explosions and replacing nametable super-tiles (after level 3 boss)
; y positions (#$e bytes)
boss_mouth_y_pos_tbl:
.byte $20,$20,$20,$20,$40,$40,$60,$60,$80,$80,$a0,$a0,$c0,$c0
; x positions (#$e bytes)
boss_mouth_x_pos_tbl:
.byte $50,$b0,$70,$90,$70,$90,$70,$90,$70,$90,$70,$90,$70,$90
; nametable update super-tile indexes (#$e bytes)
; offset into level_3_nametable_update_supertile_data or level_3_nametable_update_palette_data
boss_mouth_destroyed_nametable_update_tbl:
.byte $19,$19,$19,$19,$1a,$1b,$29,$2a,$1c,$1d,$1e,$1f,$26,$27
; pointer table for dragon arm orb (#$8 * #$2 = #$10 bytes)
dragon_arm_orb_routine_ptr_tbl:
.addr dragon_arm_orb_routine_00 ; CPU address $9a9c - variable initialization
.addr dragon_arm_orb_routine_01 ; CPU address $9ac5
.addr dragon_arm_orb_routine_02 ; CPU address $9b63 - dragon arms extending outward animation
.addr dragon_arm_orb_routine_03 ; CPU address $9c03 - dragon arms attack patterns, only executes code for shoulder orbs
.addr dragon_arm_orb_routine_04 ; CPU address $9edd - dragon arm orb destroyed routine
.addr enemy_routine_init_explosion ; CPU address $e74b from bank 7
.addr enemy_routine_explosion ; CPU address $e7b0 from bank 7
.addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7
; variable initialization
dragon_arm_orb_routine_00:
lda ENEMY_ATTRIBUTES,x ; load ENEMY_ATTRIBUTES
lsr ; shift bit 0 to the carry flag (dragon arm side)
lda #$38 ; a = #$38 (position index)
ldy #$08 ; used for enemy x adjustment
bcc @continue ; continue if right arm
lda #$28 ; left arm ball, a = #$28 (position index)
ldy #$f8 ; used for enemy x adjustment (-8)
@continue:
sta ENEMY_VAR_1,x ; set position index (see dragon_arm_orb_pos_tbl)
sta ENEMY_VAR_A,x ; set position index (see dragon_arm_orb_pos_tbl)
tya ; transfer x adjustment to a
jsr add_a_to_enemy_x_pos ; adjust enemy x position on screen
lda #$ff ; a = #$ff
sta ENEMY_VAR_4,x ; set previous dragon arm orb index to #$ff (shoulder)
lda #$04 ; a = #$04 (number of child dragon arm orbs to spawn)
sta ENEMY_FRAME,x ; set number of child dragon arm orbs to spawn
txa
sta ENEMY_VAR_2,x
jmp advance_enemy_routine ; advance routine to dragon_arm_orb_routine_01
dragon_arm_orb_routine_01:
jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll
lda BOSS_AUTO_SCROLL_COMPLETE ; see if boss reveal auto-scroll has completed
beq @exit ; exit if scrolling isn't complete
lda BG_PALETTE_ADJ_TIMER ; boss reveal auto-scroll complete, load BG_PALETTE_ADJ_TIMER
bne @exit
lda ENEMY_VAR_4,x ; load ENEMY_VAR_4 to see if parent dragon arm orb
bmi @create_child_dragon_arm_orb ; if parent dragon arm orb, spawn and initialize another dragon arm orb
; once spawned all child dragon arm orbs, advance all their routines
@exit:
rts
; only called from left and right parent dragon arm orbs to generate the entire arm
@create_child_dragon_arm_orb:
jsr find_next_enemy_slot ; find next available enemy slot, put result in x register
bne @set_slot_exit ; exit if no enemy slot available
lda #$15 ; enemy slot found, a = #$15 (dragon arm orb)
sta ENEMY_TYPE,x ; set enemy type to dragon arm orb
jsr initialize_enemy ; initialize dragon arm orb
jsr @init_child_dragon_arm_orb ; initialize dragon arm orb specific variables
stx $10 ; store spawn dragon arm orb enemy slot index in $10
ldx ENEMY_CURRENT_SLOT ; restore parent dragon arm orb slot index
dec ENEMY_FRAME,x ; decrement number of child dragon arm orbs to spawn
bne @set_slot_exit ; exit if enemy frame isn't #$00
ldy $10 ; spawned all dragon arm orbs for side
; continue to initialize red dragon arm orb and advance all arms enemy routines
; load last spawned dragon arm orb enemy slot index (red dragon arm orb)
lda #$ff ; a = #$ff
sta ENEMY_VAR_3,y ; set spawned dragon arm orb ENEMY_VAR_3 to #$ff to signify it's the 'hand' (red dragon arm orb)
lda #$10 ; a = #$10
sta ENEMY_HP,y ; set hand orb enemy hp (red dragon arm orb)
lda #$0c ; a = #$0c
sta ENEMY_STATE_WIDTH,y ; set hand orb collision box type, and explosion type (red dragon arm orb)
lda #$01 ; a = #$01
sta ENEMY_VAR_2,y ; set hand orb ENEMY_VAR_2 (red dragon arm orb)
lda #$20 ; a = #$20 (delay before arms appear)
sta ENEMY_ANIMATION_DELAY,y ; set spawned enemy dragon arm orb animation frame delay counter
tya ; transfer spawned enemy dragon arm orb slot index to a
sta ENEMY_X_VELOCITY_FRACT,x ; set fractional velocity based on enemy slot index
@loop:
jsr advance_enemy_routine ; advance dragon arm orb enemy routine to dragon_arm_orb_routine_02
lda ENEMY_VAR_3,x ; load next enemy dragon arm orb slot to advance the routine of
tax ; transfer enemy slot index of linked dragon arm orb to x
bpl @loop ; if not last orb (red orb) continue to advance the next orb's routine
ldx ENEMY_CURRENT_SLOT ; load parent orb slot index
lda #$00 ; a = #$00
sta ENEMY_VAR_2,x
sta ENEMY_FRAME,x ; set enemy animation frame number to #$00
@set_slot_exit:
ldx ENEMY_CURRENT_SLOT ; restore parent dragon arm orb slot index
rts
; input
; * x - enemy slot
@init_child_dragon_arm_orb:
lda #$02 ; a = #$02 (dragon_arm_orb_routine_01)
sta ENEMY_ROUTINE,x ; set enemy slot to dragon_arm_orb_routine_01
lda #$8c ; a = #$8c
sta ENEMY_STATE_WIDTH,x ; set bit 7, 3, 2. explosion type, enable sound on collision, allow bullets to fly through
lda #$52 ; a = #$52 (last nibble determines size)
sta ENEMY_SCORE_COLLISION,x ; score code 5 (2,000 points), collision code 2
lda #$f1 ; a = #$f1
sta ENEMY_HP,x ; set enemy hp to #$f1 (241)
lda #$00 ; a = #$00
sta ENEMY_VAR_1,x
ldy ENEMY_CURRENT_SLOT ; load parent orb
lda ENEMY_ATTRIBUTES,y ; load parent dragon arm orb attributes
sta ENEMY_ATTRIBUTES,x ; set dragon arm orb part attribute so it's the same side
lda ENEMY_Y_POS,y ; load enemy y position on screen
sta ENEMY_Y_POS,x ; copy parent dragon arm orb y position
lda ENEMY_X_POS,y ; load enemy x position on screen
sta ENEMY_X_POS,x ; copy parent dragon arm orb enemy x position on screen
lda ENEMY_VAR_2,y
sta ENEMY_VAR_4,x ; set parent dragon arm orb ENEMY_VAR_2 into ENEMY_VAR_4
sta $08
txa
sta ENEMY_VAR_2,y
ldy $08
sta ENEMY_VAR_3,y ; set child dragon arm orb
rts
; dragon arms extending outward animation
dragon_arm_orb_routine_02:
jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll
lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter
beq @set_pos_and_delay
lda FRAME_COUNTER ; load frame counter
lsr
bcc @exit
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
@exit:
rts
@set_pos_and_delay:
lda ENEMY_VAR_2,x
beq @exit2
jsr dragon_arm_orb_set_sprite ; set dragon arm orb sprite, either sprite_7a (gray) or sprite_7b (red)
jsr @set_pos_add_accum
lda ENEMY_VAR_2,x
bmi @exit2
inc ENEMY_VAR_2,x
lda ENEMY_VAR_2,x
cmp #$10
bcc @exit2
lda #$ff ; a = #$ff
sta ENEMY_VAR_2,x
ldy ENEMY_VAR_4,x ; load parent orb in y
lda #$01 ; a = #$01
sta ENEMY_VAR_2,y
lda #$00 ; a = #$00
sta ENEMY_ANIMATION_DELAY,y ; set animation delay for parent orb
lda ENEMY_VAR_4,y ; load parent orb of parent orb
bpl @set_enemy_slot_exit ; restore x to current enemy slot and exit
tya
tax
; arms fully extended, advance orb routines
@adv_routine_exit:
jsr advance_enemy_routine ; advance arm orb routine in slot x
lda #$00 ; a = #$00
sta ENEMY_VAR_2,x
lda ENEMY_VAR_3,x ; load child arm orb
tax
bpl @adv_routine_exit ; loop to advance arm orb routine of child
ldx ENEMY_CURRENT_SLOT
lda #$00 ; a = #$00
sta ENEMY_FRAME,x ; set enemy animation frame number
@set_enemy_slot_exit:
ldx ENEMY_CURRENT_SLOT
@exit2:
rts
@set_pos_add_accum:
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
and #$01 ; keep bit 0 (left or right arm)
asl
asl ; quadruple since each entry is #$04 bytes
tay ; transfer to animation table offset
lda dragon_arm_open_anim_tbl,y ; load y velocity accumulator adjustment
clc ; clear carry in preparation for addition
adc ENEMY_Y_VEL_ACCUM,x
sta ENEMY_Y_VEL_ACCUM,x
lda dragon_arm_open_anim_tbl+1,y
adc ENEMY_Y_POS,x
sta ENEMY_Y_POS,x ; enemy y position on screen
lda dragon_arm_open_anim_tbl+2,y
clc ; clear carry in preparation for addition
adc ENEMY_X_VEL_ACCUM,x
sta ENEMY_X_VEL_ACCUM,x
lda dragon_arm_open_anim_tbl+3,y
adc ENEMY_X_POS,x ; add to enemy x position on screen
sta ENEMY_X_POS,x ; set enemy x position on screen
rts
; table for dragon arm extending out animation values (#$8 bytes)
; byte 0 - y velocity accumulator adjustment
; byte 1 - y position adjustment
; byte 2 - x velocity accumulator adjustment
; byte 3 - x position adjustment
dragon_arm_open_anim_tbl:
.byte $4b,$ff,$b5,$00 ; right arm - go up one pixel
.byte $4b,$ff,$4b,$ff ; left arm - go up one pixel, go left one pixel
; set dragon arm orb sprite, either sprite_7a (gray) or sprite_7b (red)
dragon_arm_orb_set_sprite:
lda #$7a ; a = #$7a (sprite_7a) dragon arm interior orb (gray)
ldy ENEMY_VAR_3,x ; load the child dragon arm orb to see if its a hand (red orb)
bpl @set_sprite_exit ; branch if not #$ff (red orb) to keep gray sprite
lda #$7b ; a = #$7b (sprite_7b) dragon arm hand orb (red)
@set_sprite_exit:
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
rts
; dragon arms attack patterns, only executes code for shoulder orbs
dragon_arm_orb_routine_03:
jsr dragon_arm_orb_set_sprite ; set dragon arm orb sprite, either sprite_7a (gray) or sprite_7b (red)
lda ENEMY_VAR_4,x ; load to see which orb this is
bmi @continue ; continue if shoulder orb, otherwise do nothing
rts
@continue:
jsr dragon_arm_orb_attack_pat ; run appropriate logic based on attack pattern (ENEMY_FRAME)
lda ENEMY_FRAME,x ; load enemy animation frame number
cmp #$04 ; compare to #$04 (arm seeking player, reaching down)
beq @set_positions_exit ; branch if attack pattern is arm seeking player, reaching down
jsr dragon_arm_animate ; executed for all except ENEMY_FRAME #$04 (arm seeking player)
@set_positions_exit:
jmp dragon_arm_orb_set_positions
; dead code, never called !(UNUSED)
bank_0_unused_label_01:
bmi dragon_arm_open_anim_tbl ; .byte $30,$d0
dragon_arm_orb_attack_pat:
ldy ENEMY_FRAME,x ; load attack pattern, e.g. #$00 - wave up and down, #$01 - spin towards center, etc.
bne dragon_arm_orb_pat_1_2_3_or_4 ; branch if not the wave arms up and down pattern
jsr dragon_arm_orb_fire_projectile ; ENEMY_FRAME #$00 wave arm up and down
; fire projectile if shoulder dragon arm orb's ENEMY_VAR_A timer has elapsed
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
and #$01 ; keep bit 0 (which side of dragon the arm is)
tay ; transfer to offset register
lda ENEMY_Y_VELOCITY_FRACT,x ; 1 = dragon arm wave up, 0 = wave down
; not actually used to move shoulder dragon arm orb
bne @hand_below_shoulder ; branch if hands are below the shoulder
lda ENEMY_VAR_1,x ; hands above shoulder, load position index
cmp wave_direction_up_change_tbl,y ; compare to value that determines when to change direction
beq @set_delay_swap_dir ; see if need to change direction
lda dragon_arm_orb_pattern_timer_tbl,y ; load timer for given side of the dragon
sta ENEMY_VAR_2,x
rts
; dragon arm orb 'wave arms up and down' attack pattern (ENEMY_FRAME = #$00)
@hand_below_shoulder:
lda ENEMY_VAR_1,x ; load height of red arm ??
cmp wave_direction_down_change_tbl,y ; see if arm should change direction
beq @wave_in_other_direction ; branch if ENEMY_VAR_1 has value to cause change in directions
lda dragon_arm_orb_pattern_timer_tbl+1,y ; load timer for given side of the dragon
sta ENEMY_VAR_2,x
rts
@wave_in_other_direction:
inc ENEMY_ATTACK_DELAY,x ;
lda ENEMY_ATTACK_DELAY,x ; load delay between attacks
cmp #$03
bne @set_delay_swap_dir
inc ENEMY_FRAME,x ; move to next attack pattern (#$01 - spin toward center)
@set_delay_swap_dir:
lda #$03 ; a = #$03
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
lda ENEMY_Y_VELOCITY_FRACT,x
eor #$01 ; flip bits .... ...x
sta ENEMY_Y_VELOCITY_FRACT,x
rts
; table for ENEMY_VAR_1 trigger point to change waving direction (#$2 bytes)
; byte 0 - right screen of dragon arm value
; byte 1 - left screen of dragon arm value
wave_direction_up_change_tbl:
.byte $14,$0c
; table for ENEMY_VAR_1 trigger point to change waving direction (#$2 bytes)
; byte 0 - right screen of dragon arm value
; byte 1 - left screen of dragon arm value
wave_direction_down_change_tbl:
.byte $2c,$34
; used for ENEMY_FRAME #00 and #$02 to set ENEMY_VAR_2 (rotation timer)
dragon_arm_orb_pattern_timer_tbl:
.byte $40,$c0,$40
dragon_arm_orb_pat_1_2_3_or_4:
dey ; decrement from loaded ENEMY_FRAME
bne dragon_arm_orb_pat_2_3_or_4 ; branch if ENEMY_FRAME is not #$01 (spin toward center)
jsr dragon_arm_orb_fire_projectile ; ENEMY_FRAME is #$01 (spin toward center)
; fire projectile if shoulder dragon arm orb's ENEMY_VAR_A timer has elapsed
lda ENEMY_Y_VELOCITY_FAST,x ; load merged value of all orb ENEMY_VAR_2 timers
bne @exit ; exit without advancing ENEMY_FRAME if all rotation timers are not yet elapsed
inc ENEMY_FRAME,x ; increment enemy animation frame number (attack pattern)
@exit:
rts
dragon_arm_orb_pat_2_3_or_4:
dey ; decrement from loaded ENEMY_FRAME
bne dragon_arm_orb_pat_3_or_4 ; branch if ENEMY_FRAME is not #$02 (spin away from center)
jsr dragon_arm_orb_fire_projectile ; ENEMY_FRAME is #$02 (spin away from center)
; fire projectile if shoulder dragon arm orb's ENEMY_VAR_A timer has elapsed
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
and #$01 ; keep bit 0 (which side of dragon the arm is)
sta $08 ; store side into $08 (0 = right side of screen, 1 = left side of screen)
lda ENEMY_VAR_3,x ; load next arm orb (farther from body) enemy slot index
tax ; transfer to x
@frame_02_arm_orb_loop:
ldy $08 ; load the arm side (0 = right side of screen, 1 = left side of screen)
lda ENEMY_VAR_1,x
cmp dragon_arm_frame_02_tbl,y
beq @next_arm_orb
lda dragon_arm_orb_pattern_timer_tbl,y
sta ENEMY_VAR_2,x
ldx ENEMY_CURRENT_SLOT ; restore dragon arm shoulder orb enemy slot index
rts
@next_arm_orb:
lda ENEMY_VAR_3,x ; load next dragon arm orb (farther from body) enemy slot index
bmi @frame_02_exit ; branch if next dragon arm orb is the red hand
tax ; move next dragon arm orb enemy slot index to x
bpl @frame_02_arm_orb_loop
@frame_02_exit:
ldx ENEMY_CURRENT_SLOT
lda RANDOM_NUM ; load random number
adc FRAME_COUNTER
and #$03 ; keep bits .... ..xx
tay
lda dragon_arm_delay_tbl,y
bne dragon_arm_orb_03_set_delay_exit ; always branch
; table for ENEMY_VAR_1 trigger points for when ENEMY_FRAME is #$02 (spin away from center) (#$2 bytes)
; byte 0 - right side of screen
; byte 1 - left side of screen
dragon_arm_frame_02_tbl:
.byte $08,$38
; table for ? (#$4 bytes)
dragon_arm_delay_tbl:
.byte $40,$60,$30,$70
; see if ENEMY_FRAME is #$03 or #$04 and execute appropriate logic
; then decrement delay that controls moving to next attack pattern (ENEMY_FRAME)
dragon_arm_orb_pat_3_or_4:
dey ; decrement from loaded ENEMY_FRAME
bne dragon_arm_orb_seek_player ; branch if ENEMY_FRAME is not #$03 (hook shape)
; i.e. ENEMY_FRAME = #$04 (arm seeking player, reaching down)
jsr dragon_arm_orb_fire_projectile ; ENEMY_FRAME is #$03 (hook shape)
; fire projectile if shoulder dragon arm orb's ENEMY_VAR_A timer has elapsed
dec ENEMY_ATTACK_DELAY,x ; decrement delay to control moving to next ENEMY_FRAME attack pattern
bne dragon_arm_orb_03_exit ; exit if attack delay hasn't elapsed
lda #$c0 ; prepare to move to next attack pattern
; set a = #$c0 (delay between switching attack patterns)
dragon_arm_orb_03_set_delay_exit:
sta ENEMY_ATTACK_DELAY,x ; set delay between switching attack patterns to a
inc ENEMY_FRAME,x ; increment enemy animation frame number (attack pattern)
dragon_arm_orb_03_exit:
rts
; ENEMY_FRAME #$04 (arm seeking player, reaching down)
dragon_arm_orb_seek_player:
jsr dragon_arm_seek_player_logic
dec ENEMY_ATTACK_DELAY,x ; decrement enemy attack delay (timer before moving to next attack pattern)
bne @exit ; exit if timer hasn't elapsed
lda #$00 ; attack pattern delay elapsed, a = #$00
sta ENEMY_Y_VELOCITY_FRACT,x ; reset initial orb position point
sta ENEMY_ATTACK_DELAY,x ; clear delay between attacks
sta ENEMY_FRAME,x ; set enemy attack pattern to #$00 (wave arm up and down)
@exit:
rts
; fire projectile if shoulder dragon arm orb's ENEMY_VAR_A timer has elapsed
dragon_arm_orb_fire_projectile:
dec ENEMY_VAR_A,x
bne @exit
lda #$90 ; a = #$90
sta ENEMY_VAR_A,x
lda ENEMY_X_VELOCITY_FRACT,x
tax
jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01)
sty $0a ; store closest player in $0a
jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position
lda #$80 ; a = #$80
ldy #$05 ; bullet speed code
jsr aim_and_create_enemy_bullet ; get firing dir based on enemy ($08, $09) and player pos ($0b, $0a)
; and creates bullet (type a) with speed y if appropriate
ldx ENEMY_CURRENT_SLOT
@exit:
rts
; ENEMY_FRAME #$04 (arm seeking player, reaching down) set appropriate ENEMY_VAR_1
; update orb(s) ENEMY_VAR_1 (position offset index into dragon_arm_orb_pos_tbl)
dragon_arm_seek_player_logic:
lda ENEMY_X_VELOCITY_FRACT,x ; load enemy slot index of hand orb
; ENEMY_X_VELOCITY_FRACT is set to hand enemy slot index on shoulder orm
tax ; transfer hand orb enemy slot index to x
jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01)
; find closest player to the hand orb
sty $10 ; store closest player to hand orb in $10
; loop through enemy orbs (by following ENEMY_VAR_4, i.e. previous orbs) looking for when dragon_arm_orb_seek_should_move returns positive
; starting at hand, going up to shoulder
@enemy_orb_loop:
txa ; transfer current dragon arm orb enemy slot index to a
tay ; transfer current dragon arm orb enemy slot index to y
stx $11 ; store current dragon arm orb enemy slot index in $11
ldx ENEMY_VAR_4,y ; see if previous orb (closer to body) is shoulder dragon arm orb
bmi @exit ; exit if previous orb is shoulder dragon arm orb
lda $10 ; load player index of player closest to enemy
sta $0a ; store closest player index in $0a for use in get_quadrant_aim_dir_for_player
jsr dragon_arm_orb_seek_should_move ; determine whether dragon arm orb should move, and if so, in which direction
bmi @enemy_orb_loop ; continue to previous orb (closer to body) if orb doesn't need to move
ldx $11 ; orb should move, load successful dragon arm orb enemy slot index
tay ; transfer successful orb enemy slot index to y
bne @dec_position ; branch if dragon_arm_orb_seek_should_move returned #$01, i.e. decrement ENEMY_VAR_1
; increment ENEMY_VAR_1, very similar to @dec_position
@inc_position:
lda ENEMY_VAR_4,x ; see if specified orb is shoulder dragon arm orb
bmi @check_child_orb_inc ; continue if shoulder dragon arm orb
ldy ENEMY_VAR_1,x ; not shoulder dragon arm orb, load ENEMY_VAR_1
cpy #$08
bne @check_child_orb_inc
tax
jmp @inc_position
@check_child_orb_inc:
cpx $11 ; compare orb to move enemy slot index with enemy slot index of orb with ENEMY_VAR_1 set to #$08
; (or shoulder orb if not found)
bne @inc_var_1_exit ; branch if or is not the same
lda ENEMY_VAR_3,x ; load next dragon arm enemy slot index (farther away from body)
bmi @inc_11_var_1_exit ; increment enemy slot index $11's ENEMY_VAR_1 and exit if dragon arm orb is the hand
tax ; transfer next dragon arm enemy slot index (farther away from body) to x
dec ENEMY_VAR_1,x ; decrement position index (see dragon_arm_orb_pos_tbl)
lda ENEMY_VAR_1,x ; load position index (see dragon_arm_orb_pos_tbl)
and #$3f ; keep bits ..xx xxxx
sta ENEMY_VAR_1,x ; update position index
@inc_11_var_1_exit:
ldx $11 ; load enemy slot index of orb with ENEMY_VAR_1 set to #$08 (or shoulder) to $11
@inc_var_1_exit:
inc ENEMY_VAR_1,x ; increment position index (see dragon_arm_orb_pos_tbl)
jmp @sanitize_pos_index_exit
; finds the orb with the ENEMY_VAR_1 value of #$38 and decrements it if found
; decrement ENEMY_VAR_1, very similar to @inc_position
@dec_position:
lda ENEMY_VAR_4,x ; load previous dragon arm orb (closer to body)
bmi @check_child_orb_dec ; branch if previous orb is the shoulder
ldy ENEMY_VAR_1,x ; load successfully orb's position index
cpy #$38 ; see if it's #$38
bne @check_child_orb_dec ; branch if not #$38
tax ; found orb where ENEMY_VAR_1 is #$38
; transfer previous dragon arm orb (closer to body) to x
jmp @dec_position ; see if previous orb has an position index of #$38
; found orb with ENEMY_VAR_1 of #$38 or ended up on shoulder orb
@check_child_orb_dec:
cpx $11 ; compare found orb enemy slot index to successful dragon_arm_orb_seek_should_move orb enemy slot index
bne @dec_var_1_exit ; branch if they are not the same to decrement ENEMY_VAR_1 and exit
lda ENEMY_VAR_3,x ; load the next dragon arm orb (farther from body)
bmi @dec_11_var_1_exit ; branch if next orb is the hand
tax ; next orb is not hand, transfer that orb's enemy slot index to x
inc ENEMY_VAR_1,x ; increment that next orb's ENEMY_VAR_1 (position index)
lda ENEMY_VAR_1,x ; load ENEMY_VAR_1 to 'sanitize' it, i.e. make sure its bounds are safe
and #$3f ; keep bits ..xx xxxx
sta ENEMY_VAR_1,x ; store sanitized position index
@dec_11_var_1_exit:
ldx $11
@dec_var_1_exit:
dec ENEMY_VAR_1,x
@sanitize_pos_index_exit:
lda ENEMY_VAR_1,x
and #$3f ; keep bits ..xx xxxx
sta ENEMY_VAR_1,x
@exit:
ldx ENEMY_CURRENT_SLOT
rts
; executed for all but ENEMY_FRAME #$04 (arm seeking player)
; input
; * x - current dragon arm orb enemy slot index
; * $08 -
; * negative flag -
dragon_arm_animate:
lda #$00 ; a = #$00
sta $08
sta $0e
; merges every arm orb's ENEMY_VAR_2 to set new y velocity control value
@arm_orb_loop:
stx $10 ; backup shoulder orb's enemy slot index to $10
jsr @check_delay_run_timer
ldx $10 ; restore shoulder orb's enemy slot index to $10
lda ENEMY_VAR_2,x ; load rotation direction timer
ora $0e ; merge with previous arm orb rotation timers
sta $0e ; update shoulder orb's arm orb rotation timer
lda ENEMY_VAR_3,x ; load next orb farther out from current orb
tax ; transfer to x register
bpl @arm_orb_loop ; loop if next orb is not the hand
ldx ENEMY_CURRENT_SLOT ; next orb is the hand, load shoulder orb's enemy slot
lda $0e ; when all orbs' ENEMY_VAR_2 are #$00 and ENEMY_FRAME = #$01, then ENEMY_FRAME #$01 is complete
sta ENEMY_Y_VELOCITY_FAST,x ; when ENEMY_Y_VELOCITY_FAST is #$00 and ENEMY_FRAME = #$01, will specify to move to ENEMY_FRAME = #$02
; only used for ENEMY_FRAME = #$01
rts
; dec animation timer and run @timer_logic
@check_delay_run_timer:
lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter
beq @timer_elapsed ; continue once animation timer has elapsed
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
lda #$00 ; timer elapsed, a = #$00
beq @timer_logic ; always branch
@timer_elapsed:
lda ENEMY_VAR_2,x ; load dragon arm orb rotation timer (can be negative)
beq @timer_logic ; branch if dragon arm is frozen (not moving)
bmi @negative_rotation_adjustment ; branch if dragon arm is rotating counterclockwise
dec ENEMY_VAR_2,x ; rotating clockwise, decrement dragon arm rotation timer
lda #$01 ; a = #$01
bne @timer_logic ; always branch
@negative_rotation_adjustment:
inc ENEMY_VAR_2,x
lda #$ff ; a = #$ff
@timer_logic:
sta $0c ; store dragon arm rotation timer adjustment (increase, decrease, stay same)
ldy #$00 ; y = #$00
sty $0d
clc ; clear carry in preparation for addition
adc $08
sta $0b
beq @exit
bmi @inc_timer_loop
; very similar to @inc_timer_loop
@enemy_var_2_loop:
lda ENEMY_VAR_4,x ; load previous arm orb enemy slot (closer to body)
bmi @inc_var_1 ; branch if previous arm orb is the shoulder
ldy ENEMY_VAR_1,x ; load position index
cpy #$08 ; see if it is #$08
bne @inc_var_1 ; branch if position index is not #$08
ldy $0c ; load dragon arm rotation timer adjustment (increase, decrease, stay same)
beq @dec_var_2 ; decrement rotation timer if current rotation is frozen
bmi @dec_var_2 ; decrement rotation timer if current rotation is counterclockwise
lda #$00 ; rotation timer is positive, clear rotation timer
sta ENEMY_VAR_2,x ; reset duration timer
beq @continue ; always branch
@dec_var_2:
lda #$01
sta ENEMY_ANIMATION_DELAY,x
dec ENEMY_VAR_2,x
jmp @continue
@inc_var_1:
dec $0d
inc ENEMY_VAR_1,x
lda ENEMY_VAR_1,x
and #$3f ; keep bits ..xx xxxx
sta ENEMY_VAR_1,x
@continue:
dec $0b
bne @enemy_var_2_loop
beq @exit ; always branch
; very similar to enemy_var_2_loop
@inc_timer_loop:
lda ENEMY_VAR_4,x ; load previous arm orb enemy slot (closer to body)
bmi @dec_var_1 ; branch if previous arm orb is the shoulder
ldy ENEMY_VAR_1,x ; load position index
cpy #$38 ; see if it is #$38
bne @dec_var_1 ; branch if position index is not #$38
ldy $0c ; load dragon arm rotation timer adjustment (increase, decrease, stay same)
beq @inc_var_2 ; increment rotation timer if current rotation is frozen
bpl @inc_var_2 ; increment rotation timer if current rotation is clockwise
lda #$00 ; rotation timer is negative, clear rotation timer
sta ENEMY_VAR_2,x ; reset duration timer
beq @inc_0b_continue ; always branch
@inc_var_2:
lda #$01 ; a = #$01
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
inc ENEMY_VAR_2,x
jmp @inc_0b_continue
@dec_var_1:
inc $0d
dec ENEMY_VAR_1,x
lda ENEMY_VAR_1,x
and #$3f ; keep bits ..xx xxxx
sta ENEMY_VAR_1,x
@inc_0b_continue:
inc $0b
bne @inc_timer_loop
@exit:
lda $08
clc ; clear carry in preparation for addition
adc $0d
sta $08
rts
; adjust dragon arm orb positions based on ENEMY_VAR_1 for all orbs in an arm
dragon_arm_orb_set_positions:
ldy ENEMY_CURRENT_SLOT ; load current dragon arm orb enemy slot (shoulder)
lda ENEMY_VAR_1,y ; load position index
sta ENEMY_X_VELOCITY_FAST,y ; set to position index
; update the next arm orb's (farther away from body) x and y position based on
; the previous arm orb (closer to the body) for all orbs in the arm
@orb_pos_update_loop:
lda ENEMY_VAR_3,y ; load the next dragon arm orb enemy slot
bmi @exit ; exit if next orb is the hand (last orb in the arm) (#$ff)
tax ; transfer next orb enemy slot to x
; starting here x refers to 'current' orb and
; y refers to 'previous' orb (closer to body)
lda ENEMY_Y_POS,y ; load previous orb's y position on screen
sta $08 ; store previous orb y position in $08
lda ENEMY_X_POS,y ; load previous orb's x position on screen
sta $09 ; store previous orb x position in $09
lda ENEMY_X_VELOCITY_FAST,y ; load previous orb's position index (ENEMY_VAR_1)
clc ; clear carry in preparation for addition
adc ENEMY_VAR_1,x ; add previous orb's position index to current orb's position index
and #$3f ; strip bit 6 and 7 (sanitize)
sta ENEMY_X_VELOCITY_FAST,x ; set current orb's new ENEMY_VAR_1 (position index)
tay ; transfer position index to offset register
lda dragon_arm_orb_pos_tbl,y ; load offset from current y position for next orb
clc ; clear carry in preparation for addition
adc $08 ; add to current arm orb's y position
sta ENEMY_Y_POS,x ; update next orb's y position on screen
lda dragon_arm_orb_pos_tbl+16,y ; load offset from current x position for next orb
clc ; clear carry in preparation for addition
adc $09 ; add current arm orb's x position
sta ENEMY_X_POS,x ; update next orb's x position on screen
txa ; transfer next orb enemy slot to a
tay ; transfer next orb enemy slot to y
jmp @orb_pos_update_loop ; loop to next orb in the arm
@exit:
ldx ENEMY_CURRENT_SLOT
rts
; table for dragon arm orb position offsets from previous orb's pos based on ENEMY_VAR_1
; possible offsets from previous orb (in decimal)
; note that [row 2] = -1 * [row 0] and [row 3] = -1 * [row 2]
; (15,0) (15,1) (15,3) (15,4) (14,6) (14,7) (13,8) (12,10) (11,11) (10,12) (8,13) (7,14) (6,14) (4,15) (3,15) (1,15)
; (0,15) (-1,15) (-3,15) (-4,15) (-6,14) (-7,14) (-8,13) (-10,12) (-11,11) (-12,10) (-13,8) (-14,7) (-14,6) (-15,4) (-15,3) (-15,1)
; (-15,0) (-15,-1) (-15,-3) (-15,-4) (-14,-6) (-14,-7) (-13,-8) (-12,-10) (-11,-11) (-10,-12) (-8,-13) (-7,-14) (-6,-14) (-4,-15) (-3,-15) (-1,-15)
; (0,-15) (1,-15) (3,-15) (4,-15) (6,-14) (7,-14) (8,-13) (10,-12) (11,-11) (12,-10) (13,-8) (14,-7) (14,-6) (15,-4) (15,-3) (15,-1)
dragon_arm_orb_pos_tbl:
.byte $00,$01,$03,$04,$06,$07,$08,$0a,$0b,$0c,$0d,$0e,$0e,$0f,$0f,$0f
.byte $0f,$0f,$0f,$0f,$0e,$0e,$0d,$0c,$0b,$0a,$08,$07,$06,$04,$03,$01
.byte $00,$ff,$fd,$fc,$fa,$f9,$f8,$f6,$f5,$f4,$f3,$f2,$f2,$f1,$f1,$f1
.byte $f1,$f1,$f1,$f1,$f2,$f2,$f3,$f4,$f5,$f6,$f8,$f9,$fa,$fc,$fd,$ff
.byte $00,$01,$03,$04,$06,$07,$08,$0a,$0b,$0c,$0d,$0e,$0e,$0f,$0f,$0f
; dragon arm orb destroyed routine -
dragon_arm_orb_routine_04:
lda ENEMY_VAR_3,x ; load the child orb for current orb (farther from dragon)
bpl @adv_routine ; if not the hand, then advance routine to show explosions
inc BOSS_SCREEN_ENEMIES_DESTROYED ; current orb is the hand, increase number of dragon arms destroyed
@destroy_arm_part_loop:
lda ENEMY_VAR_4,x ; load the parent orb
bmi @set_slot_adv_routine ; branch if shoulder to exit, destroyed all orbs in arb
tax ; transfer parent orb index to x
jsr set_destroyed_enemy_routine ; update enemy's routine to the destroyed routine (enemy_routine_init_explosion)
jmp @destroy_arm_part_loop ; loop to update parent orb to the destroyed routine
@set_slot_adv_routine:
ldx ENEMY_CURRENT_SLOT ; restore x to current enemy slot
@adv_routine:
jmp advance_enemy_routine ; all arm orbs set to run explosions, advance routine to explode hand as well
; pointer table for boss gemini (#$7 * #$2 = #$e bytes)
boss_gemini_routine_ptr_tbl:
.addr boss_gemini_routine_00 ; CPU address $9f03 - initialize velocities and attack delay to #$80 once created
.addr boss_gemini_routine_01 ; CPU address $9f25 - wait for the #$03 wall platings to be destroyed, then wait for attack delay, enable collision
.addr boss_gemini_routine_02 ; CPU address $9f3d - main routine, animate and fire based on timer
.addr boss_gemini_routine_03 ; CPU address $9fff - boss gemini 'destroyed' routine. destroy if ENEMY_VAR_4 is 1, otherwise, reset ENEMY_HP, play ting! sound, and go back to boss_gemini_routine_02
.addr boss_gemini_routine_04 ; CPU address $a038 - create explosion, if both boss gemini destroyed, jump to boss_defeated_routine
.addr enemy_routine_explosion ; CPU address $e7b0 from bank 7
.addr boss_gemini_routine_06 ; CPU address $a042 - remove enemy, if last gemini destroyed set level end delay
; initialize velocities and attack delay to #$80 once created
boss_gemini_routine_00:
lda #$0a ; a = #$0a (boss gemini hp)
sta ENEMY_VAR_4,x ; set initial HP to #$0a, this routine doesn't ENEMY_HP in the standard way
lda ENEMY_X_POS,x ; load enemy x position on screen (set from level_4_enemy_screen_08)
sta ENEMY_VAR_1,x ; set static x position, used for offset calculations
lda #$80 ; a = #$80 (.5)
sta ENEMY_X_VELOCITY_FRACT,x ; set amount to move per frame to #$80 (.5)
lda #$00 ; a = #$00
sta ENEMY_X_VELOCITY_FAST,x ; set initial x fast velocity to 0
lda #$80 ; a = #$80
sta ENEMY_ATTACK_DELAY,x ; set delay between attacks
lda #$40 ; a = #$40 (delay before appearing)
; set the animation delay to a and advanced the ENEMY_ROUTINE
; input
; * a - the ENEMY_ANIMATION_DELAY
; this label is identical to two other labels
; * bank 7 - set_anim_delay_adv_enemy_routine
; * bank 0 - (this bank) set_anim_delay_adv_enemy_routine_00
set_anim_delay_adv_enemy_routine_01:
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
jmp advance_enemy_routine ; advance to next routine
; wait for the #$03 wall platings to be destroyed, then wait for attack delay, enable collision
boss_gemini_routine_01:
lda WALL_PLATING_DESTROYED_COUNT ; number of boss platings destroyed
cmp #$03 ; number of boss platings to destroy (level 4)
bcc @exit ; don't start up the boss gemini until all 3 platings have been destroyed
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne @exit ; exit if animation delay hasn't elapsed
lda #$a0 ; a = #$a0
sta ENEMY_ATTACK_DELAY,x ; set delay between attacks
jsr enable_bullet_enemy_collision ; allow bullets to collide (and stop) upon colliding with boss gemini
lda #$20 ; a = #$20
bne set_anim_delay_adv_enemy_routine_01 ; set ENEMY_ANIMATION_DELAY to #$20 and advance enemy routine
@exit:
rts
; main routine, animate and fire based on timer
boss_gemini_routine_02:
lda FRAME_COUNTER ; load frame counter
and #$07 ; keep bits .... .xxx
bne @set_sprite_mod_flag ; branch if not #$08th frame
inc ENEMY_FRAME,x ; #$08 frames have elapsed, increment enemy animation frame number (see boss_gemini_sprite_tbl)
lda ENEMY_FRAME,x ; load enemy animation frame number (see boss_gemini_sprite_tbl)
cmp #$03 ; see if past last frame
bcc @set_frame ; not past last frame, continue
lda #$00 ; past the last frame, go back to first frame (sprite_68)
@set_frame:
sta ENEMY_FRAME,x ; set enemy animation frame number
; determine sprite to used based on whether flashing after being hit and low hp flag
; if both low hp flag (ENEMY_VAR_3) is set and timer is odd, then flip so sprite is correct
@set_sprite_mod_flag:
lda ENEMY_VAR_3,x ; load boss gemini low hp flag
ldy ENEMY_VAR_2,x ; load timer that starts after being hit (#$10 -> #$00)
beq @set_sprite_offset_continue ; branch if the hit timer is #$00
dec ENEMY_VAR_2,x ; hit timer is not zero, decrement
lda ENEMY_VAR_2,x ; re-load updated timer that starts after being hit
lsr ; shift bit 0 to carry
lda ENEMY_VAR_3,x ; re-load boss gemini low hp flag
bcc @set_sprite_offset_continue ; branch if bit 0 of hit timer is 0
eor #$01 ; hit timer bit 0 was 1, flip bit 0 of low hp flag
@set_sprite_offset_continue:
lsr ; shift low hp flag/flashing due to being hit flag to the carry flag
lda #$00 ; a = #$00
bcc @continue ; branch if should show the green brain and not the red brain
; (low hp or flashing after being hit)
lda #$03 ; a = #$03
; create projectile if attack delay has elapsed
@continue:
clc ; clear carry in preparation for addition
adc ENEMY_FRAME,x ; add #$00 or #$03 to enemy animation frame number
tay ; transfer to offset register
lda boss_gemini_sprite_tbl,y ; load correct sprite based on ENEMY_FRAME
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
lda ENEMY_ATTACK_FLAG ; see if enemies should attack
beq @wait_delay_update_pos ; branch if attack flag disabled to see if update position animation delay has elapsed
dec ENEMY_ATTACK_DELAY,x ; decrement delay between attacks
bne @wait_delay_update_pos ; branch if attack flag disabled to see if update position animation delay has elapsed
lda PLAYER_WEAPON_STRENGTH ; load player's weapon strength
asl
asl
asl ; multiply weapon strength by 8
sta $08 ; store multiplied weapon strength into $08
lda RANDOM_NUM ; load random number
adc FRAME_COUNTER ; add frame counter to random number
sta RANDOM_NUM ; re-randomize random number
lsr ; shift random number right (not sure of need) !(WHY?)
and #$03 ; keep bits 0 and 1
tay ; transfer random number between 0 and 3 to offset register
lda boss_gemini_attack_delay_tbl,y ; load random attack delay
sec ; set carry flag in preparation for subtraction
sbc $08 ; subtract multiplied weapon strength from attack delay. the stronger the weapon, the shorter the attack delay
sta ENEMY_ATTACK_DELAY,x ; set delay between attacks
lda #$1d ; a = #$1d (#$1d = spinning bubbles)
jsr generate_enemy_a ; generate #$1d enemy (spinning bubbles)
@wait_delay_update_pos:
lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter
; this specifies how long the helmets should freeze when merged or when really far apart
; always #$00 when moving
; !(BUG?) if a bullet collision with the boss gemini occurs in a frame when ENEMY_ANIMATION_DELAY is #$02
; then the disable_enemy_collision method will not be called
; and the boss gemini will be vulnerable until the next time it starts moving again
beq @calc_offset_set_pos ; branch if moving, i.e. animation delay is #$00,
; or animation delay has elapsed and helmets should start moving
; to calculate new position
dec ENEMY_ANIMATION_DELAY,x ; helmets are staying still, either merged, or really far apart
; decrement enemy animation frame delay counter
bne @set_x_pos ; if animation delay still hasn't elapsed, set position based on FRAME_COUNTER and ENEMY_VAR_1
jsr disable_enemy_collision ; animation delay has elapsed and boss gemini are about to separate
; prevent player enemy collision check and allow bullets to pass through enemy
@calc_offset_set_pos:
lda ENEMY_Y_VELOCITY_FRACT,x ; alternates between #$00 or #$80
clc ; clear carry in preparation for addition
adc ENEMY_X_VELOCITY_FRACT,x ; load x fractional velocity. Always #$80 (.5)
sta ENEMY_Y_VELOCITY_FRACT,x ; store result back into x position offset
; this overflows every #$02 frames, causing ENEMY_Y_VELOCITY_FAST (x position) to increment
; every other frame
lda ENEMY_Y_VELOCITY_FAST,x ; load x position offset from merge point (#$00 to #$30)
adc ENEMY_X_VELOCITY_FAST,x ; add x direction (#$00 = away from center, #$ff = towards center)
; this includes any overflow from previous addition
sta ENEMY_Y_VELOCITY_FAST,x ; set new x position offset (#$00 to #$30)
ldy ENEMY_X_VELOCITY_FAST,x ; load x direction (#$00 = away from center, #$ff = towards center)
bmi @check_combined_set_x ; branch if boss gemini helmets are going to the center (combining), or have combined
cmp #$30 ; see if x position offset is at maximum (#$30)
bcc @set_x_pos ; branch if x position offset is less than max (#$30)
lda #$20 ; x position offset is max, set animation delay to #$20
bne @set_delay_reverse_dir ; always branch to reverse direction
; phantom helmets moving toward center (merging)
@check_combined_set_x:
tay ; transfer x position away from merge point (#$00 to #$30) offset to y
; x position will temporarily underflow to #$ff (-1)
; this is when helmet freeze for ENEMY_ANIMATION_DELAY amount of time
bpl @set_x_pos ; branch if boss gemini haven't yet combined, i.e. their offset isn't #$ff
jsr set_enemy_y_velocity_to_0 ; boss gemini have combined to become solid, pause motion
jsr enable_bullet_enemy_collision ; allow bullets to collide (and stop) upon colliding with boss gemini
lda #$30 ; a = #$30 (delay when gemini is not moving)
; either merged or really far apart
@set_delay_reverse_dir:
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
jsr reverse_enemy_x_direction ; reverse x direction
; sets x position based on ENEMY_VAR_1 and FRAME_COUNTER
; even frames use addition, odd frames use subtraction
@set_x_pos:
lda FRAME_COUNTER ; load frame counter
lsr ; shift bit 0 to carry flag
lda ENEMY_VAR_1,x ; load boss gemini initial x position
bcs @phase_left ; odd frame, branch to subtract offset from initial x position
adc ENEMY_Y_VELOCITY_FAST,x ; even frame, add offset from initial x position
jmp @set_x_pos_exit ; set new x position and exit
@phase_left:
sbc ENEMY_Y_VELOCITY_FAST,x
@set_x_pos_exit:
sta ENEMY_X_POS,x ; set enemy x position on screen
rts
; table for gemini boss sprite codes (#$6 bytes)
; ENEMY_FRAME is #$00, #$01, or #$02, but if ENEMY_VAR_3 is #$01, then #$03 is added so
; ENEMY_VAR_3 = #$00 -> sprite_68, sprite_69, sprite_6a
; ENEMY_VAR_3 = #$01 -> sprite_68, sprite_6b, sprite_6c (hit by bullet, or almost dead, red brain)
; sprite_68, sprite_69, sprite_6a, sprite_6b, sprite_6c
boss_gemini_sprite_tbl:
.byte $68,$69,$6a ; ENEMY_VAR_3 is #$00
.byte $68,$6b,$6c ; ENEMY_VAR_3 is #$01
; table for possible delays (#$4 bytes)
boss_gemini_attack_delay_tbl:
.byte $8a,$a9,$63,$d7
; boss gemini 'destroyed' routine, however, doesn't remove the enemy unless
; ENEMY_VAR_4 is 1, otherwise, reset ENEMY_HP, play ting! sound, and go back to boss_gemini_routine_02
boss_gemini_routine_03:
lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter
beq @continue
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
@continue:
dec ENEMY_VAR_4,x ; decrement boss gemini's HP
beq @adv_routine ; advance if HP is #$00
lda ENEMY_VAR_4,x ; load boss gemini HP
cmp #$07 ; compare to #$07
bcs @play_sound_set_routine_02 ; branch to skip low health flag setting and HP = 1 test
cmp #$01 ; see if HP is #$01
bne @set_low_hp_flag ; branch if HP is not #$01
lda #$52 ; HP is #$01, set score code to #$05 (2,000 points) and collision box to #$02
sta ENEMY_SCORE_COLLISION,x ; update score and collision code
@set_low_hp_flag:
lda #$01 ; a = #$01
sta ENEMY_VAR_3,x ; set low hp flag, used to use different sprites so brain is red and not green
@play_sound_set_routine_02:
lda #$01 ; a = #$01
sta ENEMY_HP,x ; reset enemy hp to #$01, always #$01 until ENEMY_VAR_4 is #$00 when killed
lda #$10 ; a = #$10
sta ENEMY_VAR_2,x ; initialize timer after being hit
lda #$16 ; a = #$16 (sound_16)
jsr play_sound ; play metal enemy hit ting sound
lda #$03 ; a = #$03
jmp set_enemy_routine_to_a ; set enemy routine index to boss_gemini_routine_02
@adv_routine:
jmp advance_enemy_routine ; advance to next routine
; create explosion, if both boss gemini destroyed, jump to boss_defeated_routine
boss_gemini_routine_04:
dec WALL_CORE_REMAINING ; load remaining boss gemini to destroy
bne @adv_routine ; at least one boss gemini exist, create normal explosion
jmp boss_defeated_routine ; normal explosion + final boom (with echo)
@adv_routine:
jmp enemy_routine_init_explosion ; normal explosion
; remove enemy, if last gemini destroyed set level end delay
boss_gemini_routine_06:
lda WALL_CORE_REMAINING ; load remaining boss gemini to destroy (at this point either #$00 or #$01)
beq @both_gemini_destroyed ; branch if both gemini are destroyed
jmp remove_enemy ; one more boss gemini exists, just remove this enemy
@both_gemini_destroyed:
jsr shared_enemy_routine_clear_sprite ; set sprite code to 0 and advance to next routine
; no routine after this, will be overwritten to #$00 in set_delay_remove_enemy
lda #$60 ; a = #$60 (delay before auto-move)
jmp set_delay_remove_enemy ; set delay to #$60 and remove the enemy
; pointer table for spinning bubbles projectile (#$5 * #$2 = #$a bytes)
spinning_bubbles_routine_ptr_tbl:
.addr spinning_bubbles_routine_00 ; CPU address $a05b
.addr spinning_bubbles_routine_01 ; CPU address $a094
.addr enemy_routine_init_explosion ; CPU address $e74b from bank 7
.addr enemy_routine_explosion ; CPU address $e7b0 from bank 7
.addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7
spinning_bubbles_routine_00:
jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01)
sty $0a ; store closest player in $0a
tya ; transfer closest player to a
sta ENEMY_VAR_2,x ; store closest player to enemy in ENEMY_VAR_2
jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position
lda FRAME_COUNTER ; load frame counter
and #$03 ; keep bits .... ..xx
sta ENEMY_ATTRIBUTES,x ; store random number between 0 and 3 in the enemy attributes
tay ; transfer random number to y
lda spinning_bubbles_speed_tbl,y ; load bullet velocity routine table value (bullet_velocity_adjust_xx)
sta $06 ; store bullet direction velocity routine value (bullet_velocity_adjust_xx) in $06
lda #$01 ; a = #$01 (quadrant_aim_dir_01)
sta $0f ; set quadrant_aim_dir_lookup_tbl offset to #$01
jsr get_quadrant_aim_dir_for_player ; set a to the aim direction within a quadrant
; based on source position ($09, $08) targeting player index $0a
pha ; push quadrant aim dir to the stack
jsr set_bullet_velocities ; set the projectile X and Y velocities (both high and low) based on register a (#$01)
pla ; pop quadrant aim dir from stack
jsr get_rotate_dir ; determine which direction to rotate
; based on a (quadrant aim dir) and quadrant ($07)
lda $0c ; load new enemy aim direction
sta ENEMY_VAR_1,x ; set new enemy aim direction
lda #$20 ; a = #$20
sta ENEMY_ATTACK_DELAY,x ; set delay between aim readjustments
jmp advance_enemy_routine ; advance to spinning_bubbles_routine_01
; table for possible initial speed codes (#$4 bytes)
; bullet_velocity_adjust_01 (.75x), bullet_velocity_adjust_03 (1.25x)
; bullet_velocity_adjust_04 (1.5x), bullet_velocity_adjust_05 (1.62x)
spinning_bubbles_speed_tbl:
.byte $01,$03,$04,$05
spinning_bubbles_routine_01:
ldy ENEMY_ATTRIBUTES,x ; load enemy attributes initialized in spinning_bubbles_routine_00
; it is random number between 0 and 3 inclusively
inc ENEMY_ANIMATION_DELAY,x ; increment animation delay
lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter
cmp spinning_bullet_spin_tbl,y ; compare animation delay to random value from table
; used to determine if bubble should animation, i.e. spin
bcc @continue ; don't animate bubble if delay was below threshold in spinning_bullet_spin_tbl
lda #$00 ; a = #$00
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
inc ENEMY_FRAME,x ; increment enemy animation frame number
lda ENEMY_FRAME,x ; load enemy animation frame number
cmp #$06 ; see if past last frame
bcc @set_frame_continue ; continue to set ENEMY_FRAME if not past the last frame
lda #$00 ; past the last animation frame, reset to #$00
@set_frame_continue:
sta ENEMY_FRAME,x ; set enemy animation frame number
@continue:
lda ENEMY_FRAME,x ; load enemy animation frame number
clc ; clear carry in preparation for addition
adc #$6d ; determine sprite code based on ENEMY_FRAME
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
jsr update_enemy_pos ; apply velocities and scrolling adjust
lda ENEMY_VAR_3,x ; load the number of times the bubbles have checked for aiming readjustment
cmp #$14 ; compare that to #$14
bcs @exit ; don't readjust more than 13 times
dec ENEMY_ATTACK_DELAY,x ; decrement delay between attacks
bne @exit ; exit if direction adjustment delay hasn't elapsed
lda #$08 ; attack delay has elapsed, set a = #$08 (delay before direction adjust)
sta ENEMY_ATTACK_DELAY,x ; set new direction adjustment delay
inc ENEMY_VAR_3,x ; increment number of direction adjustments
jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position
lda ENEMY_VAR_2,x ; load the player closest to the spinning bubble
sta $0a ; set player index for call aim_var_1_for_quadrant_aim_dir_01
jsr aim_var_1_for_quadrant_aim_dir_01 ; determine next aim direction [#$00-#$0b] ($0c), adjusts ENEMY_VAR_1 to get closer to that value using quadrant_aim_dir_01
bcs @exit ; exit if already aiming at player
lda ENEMY_ATTRIBUTES,x ; need to readjust aiming direction, load enemy attributes
ora #$03 ; set bits .... ..xx
sta ENEMY_ATTRIBUTES,x ; set enemy attributes to #$03
; this will cause the bubble to spin very frequently due to check against spinning_bullet_spin_tbl
lda ENEMY_VAR_1,x ; load enemy aim direction
asl ; double since each entry is #$02 bytes
tay ; transfer to offset register
lda spinning_bullet_vel_tbl,y ; load y fractional velocity
sta ENEMY_Y_VELOCITY_FRACT,x ; set y fractional velocity
lda spinning_bullet_vel_tbl+1,y ; load y fast velocity
sta ENEMY_Y_VELOCITY_FAST,x ; set y fast velocity
lda spinning_bullet_vel_tbl+12,y ; load x fractional velocity
sta ENEMY_X_VELOCITY_FRACT,x ; set y fractional velocity
lda spinning_bullet_vel_tbl+13,y ; load x fast velocity
sta ENEMY_X_VELOCITY_FAST,x ; set x fast velocity
@exit:
rts
; table for spinning bubble x/y velocities (#$3c bytes)
spinning_bullet_vel_tbl:
.byte $00,$00 ; 0
.byte $63,$00 ; .39
.byte $c0,$00 ; .75
.byte $0f,$01 ; 1.06
.byte $4b,$01 ; 1.29
.byte $72,$01 ; 1.44
.byte $7e,$01 ; 1.49
.byte $72,$01 ; 1.44
.byte $4b,$01 ; 1.29
.byte $0f,$01 ; 1.06
.byte $c0,$00 ; .75
.byte $63,$00 ; .39
.byte $00,$00 ; 0
.byte $9d,$ff ; -.39
.byte $40,$ff ; -.75
.byte $f1,$fe ; -1.06
.byte $b5,$fe ; -1.29
.byte $8e,$fe ; -1.44
.byte $82,$fe ; -1.49
.byte $8e,$fe ; -1.44
.byte $b5,$fe ; -1.29
.byte $f1,$fe ; -1.06
.byte $40,$ff ; -.75
.byte $9d,$ff ; -.39
.byte $00,$00 ; 0
.byte $63,$00 ; .39
.byte $c0,$00 ; .75
.byte $0f,$01 ; 1.06
.byte $4b,$01 ; 1.29
.byte $72,$01 ; 1.44
; table for possible spinning speeds (#$4 bytes)
spinning_bullet_spin_tbl:
.byte $08,$06,$04,$02
; pointer table for blue soldier (#$7 * #$2 = #$e bytes)
blue_soldier_routine_ptr_tbl:
.addr red_blue_soldier_routine_00 ; CPU address $a157 - initialize position and x velocity
.addr blue_soldier_routine_01 ; CPU address $a18a - run across screen, once past trigger point, see if close to player, if so advance routine to jump down
.addr blue_soldier_routine_02 ; CPU address $a1f7 - go through jump animation routine, then initialize jump velocities and advance routine
.addr blue_soldier_routine_03 ; CPU address $a245 - animate jumping down frames based on time since jump, apply velocity
.addr enemy_routine_init_explosion ; CPU address $e74b from bank 7
.addr enemy_routine_explosion ; CPU address $e7b0 from bank 7
.addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7
; initialization for both red and blue soldiers
red_blue_soldier_routine_00:
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
asl ; double since each entry is #$02 bytes
tay ; transfer offset to y
lda red_blue_soldier_init_pos_tbl,y ; load initial y position
sta ENEMY_Y_POS,x ; set enemy y position on screen
lda red_blue_soldier_init_pos_tbl+1,y ; load initial x position
sta ENEMY_X_POS,x ; set enemy x position on screen
lda ENEMY_ATTRIBUTES,x ; reload enemy attributes
and #$01 ; keep bit 0
asl ; double since each entry is #$02 bytes
tay ; transfer offset to y
lda red_blue_soldier_init_vel_tbl,y ; load initial fractional x velocity
sta ENEMY_X_VELOCITY_FRACT,x ; set fractional x velocity
lda red_blue_soldier_init_vel_tbl+1,y ; load initial fast x velocity
sta ENEMY_X_VELOCITY_FAST,x ; set fast x velocity
jmp advance_enemy_routine ; advance to next routine
; table for blue guys stop positions (#$8 bytes)
; byte 0 - y position
; byte 1 - x position
red_blue_soldier_init_pos_tbl:
.byte $9c,$f0 ; lower right - (#$f0, #$9c) - uses negative x velocity
.byte $9c,$10 ; lower left - (#$10, #$9c) - uses positive x velocity
.byte $61,$f0 ; upper right - (#$f0, #$61) - uses negative x velocity
.byte $61,$10 ; upper left - (#$10, #$61) - uses positive x velocity
; table for red/blue guys running velocities (#$4 bytes)
; byte 0 - fractional x velocity
; byte 1 - fast x velocity
red_blue_soldier_init_vel_tbl:
.byte $00,$ff ; x velocity from left
.byte $00,$01 ; x velocity from right
; run across screen, once past trigger point, see if close to player, if so advance routine to jump down
blue_soldier_routine_01:
jsr red_blue_soldier_set_run_frame ; set appropriate ENEMY_FRAME for running animation
lda ENEMY_FRAME,x ; load enemy animation frame number
clc ; clear carry in preparation for addition
adc #$85 ; ENEMY_FRAME is offset from sprite_85, add #$85 to get sprite
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
lsr ; shift bit 0 to carry (left or right soldier)
lda #$47 ; right soldier, horizontal flip and override with sprite palette #$02
bcc @continue ; branch if right soldier
lda #$07 ; left soldier, no flip and override with sprite palette #$02
@continue:
sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes (palette and whether to flip horizontally)
jsr red_blue_soldier_set_bg_priority ; set sprite bg priority for when blue soldiers are behind pillar
jsr update_enemy_pos ; apply velocities and scrolling adjust
lda ENEMY_X_POS,x ; load enemy x position on screen
cmp #$d8 ; compare to enable attack x position from right
bcs blue_soldier_routine_01_exit ; exit if (right) soldier should keep running toward enable attack trigger point
cmp #$28 ; compare to enable attack x position from left
bcc blue_soldier_routine_01_exit ; exit if (left) soldier should keep running toward enable attack trigger point
jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01)
cmp #$10 ; see if blue soldier within #$10 horizontal pixels of closest player
bcs blue_soldier_routine_01_exit ; exit if farther than #$10 from enemy
lda #$00 ; close to player, set delay and move to next routine to jump attack
sta ENEMY_FRAME,x ; set ENEMY_FRAME to #$00 (sprite_85)
; will be interpreted as sprite_88 in blue_soldier_routine_01
lda #$01 ; a = #$01
jmp set_anim_delay_adv_enemy_routine_01 ; set ENEMY_ANIMATION_DELAY to #$01 and advance enemy routine
red_blue_soldier_set_run_frame:
lda FRAME_COUNTER ; load frame counter
and #$03 ; keep bits .... ..xx
bne blue_soldier_routine_01_exit ; exit if not 4th frame
inc ENEMY_FRAME,x ; 4th frame, increment enemy animation frame number
lda ENEMY_FRAME,x ; load enemy animation frame number
cmp #$03 ; compare to the last blue soldier running sprite
bcc blue_soldier_routine_01_exit ; exit if not past last running sprite
lda #$00 ; reset back to first blue soldier running sprite
sta ENEMY_FRAME,x ; set enemy animation frame number
blue_soldier_routine_01_exit:
rts
; sets the sprite bg priority for when red and blue soldiers are behind pillar
red_blue_soldier_set_bg_priority:
ldy #$00 ; y = #$00
lda ENEMY_X_POS,x ; load enemy x position on screen
cmp #$dc ; compare x position to #$dc (86% of screen)
bcs @continue ; branch if to the right of #$dc (not behind pillar)
cmp #$24 ; compare x position to #$24 (14% of screen)
bcs @set_sprite_attr ; branch if to the right of #$24 (not behind pillar)
; left of #$24 or right of #$dc, e.g. behind pillar
@continue:
ldy #$20 ; y = #$20
@set_sprite_attr:
sty $08 ; set $08 to have the bg priority and sprite palette flags
lda ENEMY_SPRITE_ATTR,x ; load enemy sprite attributes
and #$df ; strip bit 5 (bg priority)
ora $08 ; specify bg priority and palette flags from $08
sta ENEMY_SPRITE_ATTR,x ; set sprite attributes
blue_soldier_routine_02_exit:
rts
; go through jump animation routine, then initialize jump velocities and advance routine
blue_soldier_routine_02:
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne blue_soldier_routine_02_exit ; exit if animation delay hasn't elapsed
lda #$08 ; a = #$08
sta ENEMY_ANIMATION_DELAY,x ; set next animation frame enemy delay counter
lda ENEMY_FRAME,x ; load enemy animation frame number (offset from sprite_88)
clc ; clear carry in preparation for addition
adc #$88 ; add blue soldier attack starting sprite location (sprite_88)
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
inc ENEMY_FRAME,x ; increment enemy animation frame number
lda ENEMY_FRAME,x ; load enemy animation frame number
cmp #$03 ; see if past last attacking sprite of attack animation
bcc blue_soldier_routine_02_exit ; exit if not on last attack frame
lda ENEMY_SPRITE_ATTR,x ; showing last attack sprite, start jump down to actually attack
; load enemy sprite attributes
and #$df ; strip bits 5 (bg priority)
sta ENEMY_SPRITE_ATTR,x ; update sprite attribute so blue soldier when attacking is always in foreground
jsr enable_enemy_collision ; enable bullet-enemy collision and player-enemy collision checks
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
and #$01 ; keep bit 0 (left or right soldier)
asl ; double since each entry is #$02 bytes
tay ; transfer to offset register
lda blue_soldier_jmp_x_vel_tbl,y ; load fractional x velocity
sta ENEMY_X_VELOCITY_FRACT,x ; set enemy fractional x velocity
lda blue_soldier_jmp_x_vel_tbl+1,y ; load fast x velocity
sta ENEMY_X_VELOCITY_FAST,x ; set enemy fast x velocity
lda #$00
sta ENEMY_Y_VELOCITY_FRACT,x ; set y fractional velocity to #$00
lda #$ff ; -1
sta ENEMY_Y_VELOCITY_FAST,x ; set y fast velocity to #$ff (-1)
lda #$10 ; a = #$10
jmp set_anim_delay_adv_enemy_routine_01 ; set ENEMY_ANIMATION_DELAY to #$10 and advance enemy routine
; table for blue guy x velocity while jumping (attacking) (#$4 bytes)
blue_soldier_jmp_x_vel_tbl:
.byte $c0,$ff ; coming from left
.byte $40,$00 ; coming from right
; animate jumping down frames based on time since jump, apply velocity
blue_soldier_routine_03:
lda #$8b ; a = #$8b (sprite_8b)
ldy ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter
beq @continue ; branch if delay is #$00 to use sprite_8b
dec ENEMY_ANIMATION_DELAY,x ; delay hasn't elapsed. decrement enemy animation frame delay counter
lda #$8a ; a = #$8a (sprite_8a)
@continue:
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
jsr add_10_to_enemy_y_fract_vel ; add #$10 to y fractional velocity (.06 faster)
jmp update_enemy_pos ; apply velocities and scrolling adjust
; pointer table for red shooting guys (#$6 * #$2 = #$c bytes)
red_soldier_routine_ptr_tbl:
.addr red_blue_soldier_routine_00 ; CPU address $a157 - initialize position and x velocity
.addr red_soldier_routine_01 ; CPU address $a266 - run across screen, once past trigger point, see if close to player, if so advance routine to fire at player
.addr red_soldier_routine_02 ; CPU address $a2bb - fire ENEMY_VAR_1 times and then go back to red_soldier_routine_01 to continue running off screen
.addr enemy_routine_init_explosion ; CPU address $e74b from bank 7
.addr enemy_routine_explosion ; CPU address $e7b0 from bank 7
.addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7
; run across screen, once past trigger point, see if close to player, if so advance routine to fire at player
; if already fired from red_soldier_routine_02, just continue running off screen
red_soldier_routine_01:
jsr red_blue_soldier_set_run_frame ; set appropriate ENEMY_FRAME for running animation
lda ENEMY_FRAME,x ; load enemy animation frame number
clc ; clear carry in preparation for addition
adc #$8c ; ENEMY_FRAME is offset from sprite_8c, add #$8c to get sprite
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
lsr ; shift bit 0 to carry (left or right soldier)
lda #$46 ; right soldier, horizontal flip and override with sprite palette #$01
bcc @continue ; branch if right soldier
lda #$06 ; left soldier, no flip and override with sprite palette #$01
@continue:
sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes (palette and whether to flip horizontally)
jsr red_blue_soldier_set_bg_priority ; set sprite bg priority for when red soldiers are behind pillar
jsr update_enemy_pos ; apply velocities and scrolling adjust
lda ENEMY_VAR_2,x ; load soldier fired flag
bne red_soldier_routine_exit ; exit when red soldier has already fired at the player
; this allows the red soldier to continue running off screen
lda ENEMY_X_POS,x ; load enemy x position on screen
cmp #$d8 ; compare to enable attack x position from right
bcs red_soldier_routine_exit ; exit if (right) soldier should keep running toward enable attack trigger point
cmp #$28 ; compare to enable attack x position from left
bcc red_soldier_routine_exit ; exit if (left) soldier should keep running toward enable attack trigger point
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
lsr
lsr ; shift bit 1 into the carry flag
lda #$10 ; a = #$10 (carry clear attack distance)
bcc @attack_if_close ; branch if carry flag clear
lda #$30 ; a = #$30 (carry flag set attack distance)
@attack_if_close:
sta $0f ; set minimum attack distance
jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01)
cmp $0f ; compare closest player x to $0f
bcs red_soldier_routine_exit ; exit if player too far away from enemy to attack
lda #$8f ; a = #$8f (sprite_8f) red soldier facing player
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
lda #$03 ; a = #$03
sta ENEMY_VAR_1,x ; set to fire #$03 bullets
lda #$10 ; a = #$10 (delay before first attack)
sta ENEMY_ATTACK_DELAY,x ; set delay between attacks
jmp advance_enemy_routine ; advance to red_soldier_routine_02
; fire ENEMY_VAR_1 times and then go back to red_soldier_routine_01
red_soldier_routine_02:
dec ENEMY_ATTACK_DELAY,x ; decrement delay between attacks
beq red_soldier_fire_weapon ; fire weapon if attack delay has elapsed
lda ENEMY_ATTACK_DELAY,x ; load delay between attacks
cmp #$2c ; see if attack delay is #$2c
bne red_soldier_routine_exit ; exit if attack delay isn't #$2c
lda ENEMY_SPRITE_ATTR,x ; attack delay is #$2c, load enemy sprite attributes
and #$f7 ; strip recoil effect flag
sta ENEMY_SPRITE_ATTR,x ; update enemy sprite attribute
red_soldier_routine_exit:
rts
red_soldier_fire_weapon:
lda #$90 ; a = #$90 (sprite_90) red soldier facing player with weapon
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
dec ENEMY_VAR_1,x ; decrement number of bullets to fire
bmi @set_routine_01 ; go back to red_soldier_routine_01 if all bullets have been fired
lda #$30 ; a = #$30 (delay when shooting)
sta ENEMY_ATTACK_DELAY,x ; set delay between attacks
lda ENEMY_SPRITE_ATTR,x ; load enemy sprite attributes
ora #$08 ; set bit 3 (recoil effect)
sta ENEMY_SPRITE_ATTR,x ; update sprite attribute
jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01)
sty $0a ; store closest player in $0a
jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position
lda #$00 ; a = #$00
ldy #$04 ; bullet speed code
jmp aim_and_create_enemy_bullet ; get firing dir based on enemy ($08, $09) and player pos ($0b, $0a)
; and creates bullet (type a) with speed y if appropriate
@set_routine_01:
inc ENEMY_VAR_2,x ; set flag indicating soldier has already fired at player
lda #$02 ; a = #$02
jmp set_enemy_routine_to_a ; set enemy routine index to red_soldier_routine_01
; pointer table for red/blue guys generator (#$3 * #$2 = #$6 bytes)
red_blue_soldier_gen_routine_ptr_tbl:
.addr red_blue_soldier_gen_routine_00 ; CPU address $a304
.addr red_blue_soldier_gen_routine_01 ; CPU address $a309
.addr remove_enemy ; CPU address $e809 from bank 7
red_blue_soldier_gen_routine_00:
lda #$80 ; a = #$80
jmp set_anim_delay_adv_enemy_routine_01 ; set generation delay to #$80 and advance enemy routine
red_blue_soldier_gen_routine_01:
lda WALL_PLATING_DESTROYED_COUNT ; number of boss platings destroyed
cmp #$03 ; compare to number of wall platings on level 4 boss screen
bcc @continue ; continue to generate red and blue soldiers if all plates haven't been destroyed
jmp remove_enemy ; all plates have been destroyed, remove enemy to stop generating red and blue soldiers
@continue:
lda FRAME_COUNTER ; load frame counter
lsr ; shift bit 0 to the carry flag
bcs red_blue_soldier_gen_routine_01_exit ; exit if odd frame
dec ENEMY_ANIMATION_DELAY,x ; even frame, decrement enemy animation frame delay counter
bne red_blue_soldier_gen_routine_01_exit ; exit if generation delay hasn't elapsed
; reads from red_blue_solider_data_tbl and based on byte, creates red or blue soldier, or waits
@read_soldier_byte:
ldy ENEMY_VAR_1,x ; load the soldier generation read offset
@read_soldier_data:
inc ENEMY_VAR_1,x ; increment the soldier generation read offset
lda red_blue_solider_data_tbl,y ; read the data byte for generating a red or blue soldier
cmp #$ff ; see if the last byte was read (end of data byte)
bne @eval_data_byte ; continue if didn't read the end of data byte
ldy #$00 ; finished reading data, reset read offset and start over
tya ; transfer y to a
sta ENEMY_VAR_1,x ; reset the soldier generation read offset to repeated the pattern
beq @read_soldier_data ; always branch to start from the beginning
@eval_data_byte:
lda red_blue_solider_data_tbl,y ; reload the data byte
bmi @set_delay_exit ; if the byte is negative, don't create soldier, instead delay for amount specified
and #$03 ; byte is positive, creating red or blue soldier, keep bits .... ..xx
sta $08 ; set red or blue soldier to generate's ENEMY_ATTRIBUTES
lda red_blue_solider_data_tbl,y ; reload the data byte
lsr
lsr ; bit 3 specifies which soldier to generate
sta $09 ; set whether to create a red or blue soldier (0 = red soldier, 1 = blue soldier)
jsr @find_slot_init_red_blue_soldier ; find enemy slot and create soldier
jmp @read_soldier_byte ; move to the next byte
@set_delay_exit:
asl
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
rts
@find_slot_init_red_blue_soldier:
jsr find_next_enemy_slot ; find next available enemy slot, put result in x register
bne @exit ; exit if no enemy slot available
lda $09 ; load whether to create a red or blue soldier (0 = red soldier, 1 = blue soldier)
lsr ; shift bit 0 to the carry flag
lda #$1f ; a = #$1f (enemy type for red soldier)
bcc @init_red_blue_soldier ; branch if
lda #$1e ; a = #$1e (enemy type for blue soldier)
@init_red_blue_soldier:
sta ENEMY_TYPE,x ; set current enemy type to either red or blue soldier
jsr initialize_enemy ; initialize enemy variables
lda $08 ; load blue or red soldier's initial position and velocity
; see red_blue_soldier_init_pos_tbl and red_blue_soldier_init_vel_tbl
sta ENEMY_ATTRIBUTES,x
@exit:
ldx ENEMY_CURRENT_SLOT ; restore x to red blue soldier generator enemy slot index
red_blue_soldier_gen_routine_01_exit:
rts
; table for red or blue soldier soldier generation (#$1c bytes)
; if byte is negative
; * a soldier isn't generated and the byte value shifted left is the delay
; if positive
; * bits 0, 1, and 2 - ENEMY_ATTRIBUTES for red or blue soldier to generate
; * bit 3 - 0 = red solider, 1 = blue soldier
red_blue_solider_data_tbl:
.byte $00,$01,$02,$03,$d0 ; red soldier (x4), #$a0 delay
.byte $06,$07,$a0 ; blue soldier (x2), #$40 delay
.byte $04,$05,$c0 ; blue soldier (x2), #$80 delay
.byte $00,$01,$b0 ; red soldier (x2), #$60 delay
.byte $02,$03,$d0 ; red soldier (x4), #$a0 delay
.byte $04,$05,$06,$07,$d0 ; blue soldier (x4), #$a0 delay
.byte $00,$01,$02,$03,$fe ; red soldier (x4), #$fc delay
.byte $ff
; pointer table for grenade generator (#$3 * #$2 = #$6 bytes)
ice_grenade_generator_routine_ptr_tbl:
.addr ice_grenade_generator_routine_00 ; CPU address $a38a
.addr ice_grenade_generator_routine_01 ; CPU address $a399
.addr remove_enemy ; CPU address $e809 from bank 7
ice_grenade_generator_routine_00:
jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll
lda ENEMY_X_POS,x ; load enemy x position on screen
cmp #$c8
bcs ice_grenade_exit ; exit if grenade generator not yet at trigger point (78% of screen)
lda #$01 ; a = #$01 (ENEMY_ANIMATION_DELAY)
jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to #$01; advance enemy routine
ice_grenade_generator_routine_01:
jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne ice_grenade_exit ; exit if delay hasn't elapsed
lda #$80 ; a = #$80 (delay between grenades)
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
lda #$11 ; a = #$11 (ice grenade)
jmp generate_enemy_a ; generate #$11 enemy (ice grenade)
; pointer table for grenade (#$5 * #$2 = #$a bytes)
ice_grenade_routine_ptr_tbl:
.addr ice_grenade_routine_00 ; CPU address $a3b5 - play sound, initialize velocity
.addr ice_grenade_routine_01 ; CPU address $a3d7 - animate, apply gravity, check for collision
.addr mortar_shot_routine_03 ; CPU address $e752 from bank 7 - play explosion sound, update collision, hide sprite
.addr enemy_routine_explosion ; CPU address $e7b0 from bank 7
.addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7
; play sound, initialize velocity
ice_grenade_routine_00:
lda #$1a ; a = #$1a ("piiuuu" sound) (sound_1a)
jsr play_sound ; play ice grenade whistling noise sound
lda #$20 ; a = #$20 (bg priority flag)
sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes
lda #$80 ; a = #$80 (.5)
sta ENEMY_X_VELOCITY_FRACT,x ; set ice grenade fractional x velocity
lda #$00 ; a = #$00
sta ENEMY_X_VELOCITY_FAST,x ; set ice grenade fast x velocity
lda #$00 ; a = #$00 (unnecessary)
sta ENEMY_Y_VELOCITY_FRACT,x ; set ice grenade fractional y velocity
lda #$fe ; a = #$fe (-2)
sta ENEMY_Y_VELOCITY_FAST,x ; set ice grenade fast y velocity
jmp advance_enemy_routine ; advance to ice_grenade_routine_01
ice_grenade_exit:
rts
; animate, apply gravity, check for collision
ice_grenade_routine_01:
lda FRAME_COUNTER ; load frame counter
and #$07 ; keep bits ... .xxx
bne @continue ; continue without changing sprite if #$08 frames haven't elapsed
inc ENEMY_FRAME,x ; #$08 frames have elapsed, increment enemy animation frame number
@continue:
lda ENEMY_FRAME,x ; load enemy animation frame number
and #$03 ; keep bit 0 and 1 (allows frame number to continue incrementing)
tay ; transfer ice_grenade_sprite_tbl offset to y
lda ice_grenade_sprite_tbl,y ; load appropriate sprite code
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
jsr update_enemy_pos ; apply velocities and scrolling adjust
lda #$0a ; a = #$0a (gravity)
jsr add_a_to_enemy_y_fract_vel ; add a to enemy y fractional velocity
bmi ice_grenade_exit ; exit if no overflow
lda #$00 ; a = #$00
sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes
ldy #$04 ; y = #$04 (distance from ground for explosion)
jsr add_y_to_y_pos_get_bg_collision ; add #$04 to enemy y position and gets bg collision code
beq ice_grenade_exit ; exit if no collision
lda #$24 ; collision, set a = #$24 (sound_24)
jsr play_sound ; play explosion sound
jmp advance_enemy_routine ; advance to mortar_shot_routine_03
; table for grenade sprite codes (#$4 bytes)
; sprite_74, sprite_75, sprite_76, sprite_77
ice_grenade_sprite_tbl:
.byte $74,$75,$76,$77
; pointer table for tank (#$6 * #$2 = #$c bytes)
; tank is actually in nametable and stationary (not a sprite)
; auto scroll makes it look like the tank is approaching the player (ice separators are actually sprites)
tank_routine_ptr_tbl:
.addr tank_routine_00 ; CPU address $a41a - initialize tank position and palettes
.addr tank_routine_01 ; CPU address $a448 - animate tank driving to player until gets to within #$a0 pixels of player
.addr tank_routine_02 ; CPU address $a4ee - tank stopped, fire at player
.addr tank_routine_03 ; CPU address $a5b5 - tank starts moving again after stopping in previous routine
.addr tank_routine_04 ; CPU address $a5e3 - disable collision, prep variables for next routine to remove tank, play
.addr tank_routine_05 ; CPU address $a5f8 - tank destroyed routine
; initialize tank position and palettes
tank_routine_00:
lda #$30 ; a = #$30
sta ENEMY_X_POS,x ; set enemy x position on screen
lda #$01 ; a = #$01
sta ENEMY_X_VEL_ACCUM,x ; specify that the tank is off the screen to the right
lda #$90 ; a = #$90
sta ENEMY_Y_POS,x ; enemy y position on screen
lda #$0c ; a = #$0c
sta ENEMY_VAR_1,x ; set turret aim direction (straight to the left)
lsr ; a = #$06
sta ENEMY_ATTACK_DELAY,x ; set delay between attacks (#$06)
lda #$3f ; a = #$3f
sta PAUSE_PALETTE_CYCLE ; disable palette color cycling
; tank is nametable tiles not a sprite, don't want its colors to change
sta TANK_ICE_JOINT_SCROLL_FLAG ; set the ice joint enemy move left while player walks right
sta LEVEL_PALETTE_INDEX+2 ; overwrite level palette to #$3f (offset into game_palettes)
; (COLOR_WHITE_20, COLOR_DARK_GRAY_00, COLOR_MED_ORANGE_17)
lda #$41 ; a = #$41 (COLOR_DARK_ORANGE_07, COLOR_DARK_GRAY_00, COLOR_MED_ORANGE_17)
sta LEVEL_PALETTE_INDEX+3 ; overwrite level background palette to #$41 (offset into game_palettes)
lda #$10 ; number of palettes to load
jsr load_palettes_color_to_cpu ; load #$10 palette colors into PALETTE_CPU_BUFFER based on LEVEL_PALETTE_INDEX
ldx ENEMY_CURRENT_SLOT ; load the current enemy slot number
jmp advance_enemy_routine ; move to tank_routine_01
; animate tank driving to player until gets to within #$a0 pixels of player
tank_routine_01:
lda FRAME_COUNTER ; load frame counter
and #$01 ; auto scroll every other frame
sta TANK_AUTO_SCROLL ; enable automatic screen scroll to simulate tank scrolling left
jsr tank_update_pos ; update the tank's position, enabling bullet collision when appropriate
lda ENEMY_X_VEL_ACCUM,x ; load tank visibility (#$00 = visible, #$01 = off screen to right, #$ff = off screen to left)
bne tank_move_logic ; branch if tank is not fully visible
lda ENEMY_X_POS,x ; tank is now on screen, load enemy x position on screen
cmp #$a0 ; compare to tank stopping point
bcc tank_stop ; stop the tank if it at the stopping point
tank_move_logic:
lda ENEMY_HP,x ; load enemy hp
beq @draw_tire ; branch if hp is 0
dec ENEMY_ATTACK_DELAY,x ; enemy hp is not 0, decrement delay between attacks
bne @draw_tire ; branch if attack delay hasn't elapsed
lda #$1e ; a = #$1e (sound_1e)
jsr play_sound ; play tank attack sound
lda #$06 ; a = #$06 (tank sound repeat frequency)
sta ENEMY_ATTACK_DELAY,x ; set delay between attacks
@draw_tire:
lda FRAME_COUNTER ; load frame counter
and #$03 ; keep bits .... ..xx (change tire animation every #$04 frames)
tay ; transfer to tank_wheel_supertile_tbl offset
lda tank_wheel_supertile_tbl,y ; load the correct tank tires based on frame
sta $10 ; tank tire super-tile
lda FRAME_COUNTER ; load frame counter
and #$01 ; keep bits .... ...x
bne @draw_back_tire ; draw different tire every other frame
lda ENEMY_X_POS,x ; load enemy x position on screen
sec ; set carry flag in preparation for subtraction
sbc #$0c ; subtract #$0c from enemy position to get front tire position
sta $09 ; set x position in $09
lda ENEMY_X_VEL_ACCUM,x ; load tank visibility (#$00 = visible, #$01 = off screen to right, #$ff = off screen to left)
sbc #$00 ; subtract 1
bne @exit ; exit if front tire is already off screen
beq @draw_specific_tire ; draw tire if tank front tire is visible on screen
@draw_back_tire:
lda ENEMY_X_POS,x ; load enemy x position on screen
clc ; clear carry in preparation for addition
adc #$14 ; add #$14 to get back tire position
sta $09 ; set x position in $09
lda ENEMY_X_VEL_ACCUM,x ; load tank visibility (#$00 = visible, #$01 = off screen to right, #$ff = off screen to left)
adc #$00 ; add any overflow when determining back tire x position (#$ff = right edge of screen)
bne @exit ; exit if back tire is off screen to the right
@draw_specific_tire:
ldy ENEMY_Y_POS,x ; tire y position on screen
lda $09 ; load tire x position
jsr load_bank_3_update_nametable_supertile ; draw super-tile $10 at position (a,y)
ldx ENEMY_CURRENT_SLOT ; restore tank slot index
@exit:
rts
tank_stop:
lda #$00 ; a = #$00
sta TANK_AUTO_SCROLL ; disabled automatic screen scroll
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
and #$01 ; keep bit 0 (attack delay offset)
tay ; transfer to offset register
lda tank_attack_delay_tbl,y ; load specific tank attack delay
sta ENEMY_VAR_4,x ; delay for entire attack sequence
lda #$47 ; a = #$47 (hp for tank)
sta ENEMY_HP,x ; set enemy hp
lda #$08 ; a = #$08 (initial delay before first attack)
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
tank_adv_routine:
jmp advance_enemy_routine
; updates the tanks position and when on screen, enables player bullet collision
; note that the tank is still 'invincible' until it stops
; before the tank is visible, its actual position is behind the player.
; it isn't until the actual tank position is off screen to the left before
; the bullet collision is enabled
tank_update_pos:
lda FRAME_SCROLL ; how much to scroll the screen (#00 - no scroll)
clc ; clear carry in preparation for addition
adc TANK_AUTO_SCROLL ; add amount to auto scroll
sta $00 ; store new frame scroll in $00
lda ENEMY_X_POS,x ; load enemy x position on screen
sec ; set carry flag in preparation for subtraction
sbc $00 ; subtract the frame scroll
sta ENEMY_X_POS,x ; update enemy x position on screen
bcs tank_routine_exit ; exit if no underflow when determining new x position
dec ENEMY_X_VEL_ACCUM,x ; tank visibility change state
; either going from #$01 to #$00 (appearing on screen from right)
; or going from #$00 to #$ff (offscreen to the left)
; time to 'enable' to allow it to be attacked
lda ENEMY_HP,x ; load enemy hp
beq tank_routine_exit ; exit if tank hp is 0
lda ENEMY_STATE_WIDTH,x ; load whether bullets affect and interact with tank
eor #$81 ; flip bits x... ...x
sta ENEMY_STATE_WIDTH,x ; enable bullet - tank collisions
tank_routine_exit:
rts
; table for time during which tank is immobile (#$2 bytes)
; offset determined by ENEMY_ATTRIBUTES bit 0
tank_attack_delay_tbl:
.byte $00,$f8
; tank stopped, fire at player
tank_routine_02:
jsr tank_set_palette_update_pos ; update position, set palette, remove if appropriate
bcc tank_routine_exit
lda FRAME_COUNTER ; load frame counter
and #$01 ; keep bits .... ...x
bne @check_aim_fire
dec ENEMY_VAR_4,x ; decrement tank stop timer
beq tank_adv_routine ; if timer has elapsed, advance routine
; attack time counter is decreased by 1 on even frames (every other frame)
; tank isn't ready to start moving again, should fire at player
@check_aim_fire:
lda ENEMY_STATE_WIDTH,x ; load whether bullets should travel through tank
bmi tank_routine_exit ; exit bullets should travel through tank (bit 7 is set)
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne tank_routine_exit ; exit if animation delay hasn't elapsed
lda ENEMY_VAR_3,x ; animation delay has elapsed, load remaining bullets to fire
bne @create_bullet ; branch if there are still bullets to fire
lda ENEMY_VAR_1,x ; fired all bullets in current round of attack, need to re-aim
sta $17 ; set aim direction in $17 (#$0c = straight left, #$0b = aiming down left, #$0a = down down left)
jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01)
sty $0a ; store closest player in $0a
ldy #$f4 ; set vertical offset from enemy position (param for add_with_enemy_pos)
lda #$00 ; set horizontal offset from enemy position (param for add_with_enemy_pos)
jsr add_with_enemy_pos ; stores absolute screen x position in $09, and y position in $08
jsr aim_var_1_for_quadrant_aim_dir_01 ; determine next aim direction [#$00-#$0b] ($0c), adjusts ENEMY_VAR_1 to get closer to that value using quadrant_aim_dir_01
lda ENEMY_VAR_1,x ; load calculated aim direction
cmp #$0a ; compare to the tank's lowest aiming direction
bcc @keep_aim_direction ; branch if calculated aim direction is not possible for tank to use previous aim direction
cmp #$0d ; compare calculated aim direction to one past the maximum aim direction (straight left)
bcc @set_bullets_draw_turret ; branch if aiming less than up-left
; calculated aim direction from aim_var_1_for_quadrant_aim_dir_01 is not possible for tank
; instead, re-use previous round of attack's aim direction
@keep_aim_direction:
lda $17 ; load last round of attack's aim direction
sta ENEMY_VAR_1,x ; store back in ENEMY_VAR_1
sta $0c ; store result in $0c as well
@set_bullets_draw_turret:
cmp $0c ; see if drawing the same turret as what's already drawn
; used to allow turret to slowly aim towards player, and only fire once aimed
bne @draw_tank_turret_supertile ; branch to draw new turret super-tile if different
lda #$03 ; a = #$03 (number of consecutive bullets)
sta ENEMY_VAR_3,x ; set number of bullets to fire in next round of attack
@draw_tank_turret_supertile:
lda ENEMY_VAR_1,x ; load tank turret aim direction
sec ; set carry flag in preparation for subtraction
sbc #$0a ; subtract smallest aim direction (turret can only aim from #$0a to #$0c inclusively)
tay ; transfer offset to y
lda tank_turret_supertile_code_tbl,y ; load appropriate super-tile
sta $10 ; set super-tile to draw
lda ENEMY_Y_POS,x ; load enemy y position on screen
sbc #$1c ; subtract #$1c from from enemy's y position (y location of super-tile to draw)
tay ; transfer result to y for load_bank_3_update_nametable_supertile
lda ENEMY_X_POS,x ; load enemy x position on screen
sbc #$2c ; subtract #$2c from from enemy's x position (x location of super-tile to draw)
sta $00 ; set result in $00, will be used later in load_bank_3_update_nametable_supertile
lda ENEMY_X_VEL_ACCUM,x ; load tank visibility (#$00 = visible, #$01 = off screen to right, #$ff = off screen to left)
sbc #$00 ; subtract #$00 or #$01 if carry clear !(WHY?)
; I couldn't get this to happen
bne tank_exit ; exit if result isn't #$00
lda $00 ; load x position to draw tank super-tile
jsr load_bank_3_update_nametable_supertile ; draw turret super-tile $10 at position (a,y)
lda #$01 ; a = #$01
bcs @set_delay_exit ; exit if unable to draw the super-tile for the turret
ldx ENEMY_CURRENT_SLOT ; restore x to current enemy slot
lda #$30 ; a = #$30 (delay before attack)
bne @set_delay_exit ; set animation delay and exit
@create_bullet:
lda ENEMY_VAR_1,x ; load turret aim direction [#$0a-#$0c]
sec ; set carry flag in preparation for subtraction
sbc #$0a ; subtract minimum aim direction to get relative index, e.g. aim direction #$0b becomes #$01
sta $00 ; store offset in $00
asl
adc $00 ; multiply offset by #$03 since each entry in tank_bullet_pos_vel_tbl is #$03 bytes
tay ; transfer to offset register
lda ENEMY_X_POS,x ; load enemy x position on screen
sbc tank_bullet_pos_vel_tbl,y ; subtract relative x offset
sta $09 ; set bullet initial x position
lda ENEMY_X_VEL_ACCUM,x ; load tank visibility (#$00 = visible, #$01 = off screen to right, #$ff = off screen to left)
sbc #$00 ; subtract #$00 if turret on screen, #$01 if turret offscreen
bcc tank_exit ; exit if turret is off screen to the left
lda ENEMY_Y_POS,x ; load enemy y position on screen
sbc tank_bullet_pos_vel_tbl+1,y ; subtract relative y offset
sta $08 ; set bullet initial y position
lda tank_bullet_pos_vel_tbl+2,y ; load bullet type (xxx. ....) and angle index (...x xxxx)
ldy ENEMY_ATTRIBUTES,x ; (tank bullets speed)
jsr create_enemy_bullet_angle_a ; create a bullet with speed y, bullet type a, angle a at position ($09, $08)
dec ENEMY_VAR_3,x ; decrement number of bullets to fire for the round
lda #$20 ; a = #$20 (delay between bullets)
@set_delay_exit:
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
tank_exit:
rts
; table for tank cannon tile codes (#$3 bytes)
; see level_5_nametable_update_supertile_data
tank_turret_supertile_code_tbl:
.byte $13,$12,$0f
; table for tank bullets (#$9 bytes)
; byte 0: x offset of bullet (to the left)
; byte 1: y offset of bullet (to the left)
; byte 2: bullet type and angle
tank_bullet_pos_vel_tbl:
.byte $24,$03,$09 ; position 0 (#$0a)
.byte $29,$09,$0a ; position 1 (#$0b)
.byte $2e,$14,$0c ; position 2 (#$0c)
; update position, set palette, remove if appropriate
tank_set_palette_update_pos:
jsr tank_set_palette ; set the tank's palette based on its hp
jsr tank_update_pos ; update the tank's position
jmp tank_check_removal ; check if tank is destroyed and at position to be removed
; tank starts moving again after stopping in previous routine
tank_routine_03:
lda FRAME_COUNTER ; load frame counter
and #$01 ; keep bits .... ...x
sta TANK_AUTO_SCROLL ; enable automatic screen scroll
jsr tank_set_palette_update_pos ; update position, set palette, remove if appropriate
bcc tank_routine_03_exit
jmp tank_move_logic
; output
; * carry flag - clear when tank removed, set when not removed
tank_check_removal:
lda ENEMY_X_VEL_ACCUM,x ; load whether or not the tank is visible on screen
sec ; set carry flag
bpl tank_routine_03_exit ; exit if tank is off screen
lda ENEMY_X_POS,x ; load enemy x position on screen
cmp #$d0 ; see if (invisible) destroyed tank has wrapped around and can be removed
bcs tank_routine_03_exit ; exit if tank hasn't gotten to removal point
jsr remove_enemy ; tank at removal point, remove enemy (from bank 7)
lda #$00 ; a = #$00
sta TANK_AUTO_SCROLL ; disable automatic screen scroll
sta PAUSE_PALETTE_CYCLE ; re-enable palette color cycling
sta TANK_ICE_JOINT_SCROLL_FLAG ; stop the pipe joints from autoscrolling left
ldx ENEMY_CURRENT_SLOT ; restore x to enemy offset
clc
tank_routine_03_exit:
rts
; table for tank update super-tiles (#$4 bytes)
tank_wheel_supertile_tbl:
.byte $10,$11,$14,$15
; disable collision, prep variables for next routine to remove tank, play
tank_routine_04:
jsr tank_update_pos ; update the tank's position
jsr disable_enemy_collision ; prevent player enemy collision check and allow bullets to pass through enemy
lda #$05 ; a = #$05
sta ENEMY_VAR_1,x ; set number of explosions and super-tiles to go through when destroying tank
lda #$55 ; a = #$55 (sound_55)
jsr play_sound ; play tank destroyed sound
lda #$00 ; a = #$00
jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a; advance enemy routine
; tank destroyed routine
tank_routine_05:
lda FRAME_COUNTER ; load frame counter
and #$01 ; keep bits .... ...x
sta TANK_AUTO_SCROLL ; randomly enable/disable automatic screen scroll
jsr tank_update_pos ; update the tank's position
jsr tank_check_removal ; check if tank is destroyed and at position to be removed
bcc @exit ; branch if tank was removed
lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter
bne @dec_delay_exit ; branch if animation delay hasn't elapsed
lda ENEMY_VAR_1,x ; load current explosion animation to do
bmi @exit ; exit if have finished explosion animations
asl
asl
adc ENEMY_VAR_1,x ; each entry is #$05 bytes, so double twice and add to itself
tay ; transfer to offset register
lda ENEMY_X_POS,x ; load enemy x position on screen
adc tank_destroy_tbl+1,y ; add relative x offset from enemy position
sta $00 ; store in $00
lda ENEMY_X_VEL_ACCUM,x ; load tank visibility (#$00 = visible, #$01 = off screen to right, #$ff = off screen to left)
adc tank_destroy_tbl,y
bne @dec_var_1_exit ; decrement explosion offset and exit
lda ENEMY_Y_POS,x ; load enemy y position on screen
adc tank_destroy_tbl+2,y ; add relative y offset from enemy position
sty $07 ; store explosion destruction offset (ENEMY_VAR_1,x * #$05) in $07
tay ; transfer y position to draw super-tile to y
lda #$9b ;all black supertile (level_5_nametable_update_supertile_data - #$1b)
sta $10 ; set black super-tile to draw in $10
lda $00 ; load x position to draw super-tile in a
jsr load_bank_3_update_nametable_supertile ; draw super-tile $10 at position (a,y)
bcs @exit ; exit if unable to draw super-tile so it can be attempted the next frame
ldx ENEMY_CURRENT_SLOT ; restore x to the current enemy slot
ldy $07 ; load explosion destruction offset (ENEMY_VAR_1,x * #$05) in $07
lda ENEMY_X_POS,x ; load enemy x position on screen
adc tank_destroy_tbl+3,y ; add relative x offset for explosion
sta $09 ; store x position for explosion in $09
lda ENEMY_Y_POS,x ; load enemy y position on screen
clc ; clear carry in preparation for addition
adc tank_destroy_tbl+4,y ; add relative y offset for explosion
sta $08 ; store y position for explosion in $08
dec ENEMY_VAR_1,x ; decrement destruction sequence offset
lda #$04 ; a = #$04
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
jmp create_two_explosion_89 ; create explosion #$89 at location ($09, $08)
@dec_var_1_exit:
dec ENEMY_VAR_1,x ; don't animate explosion, just decrement offset and exit
@exit:
rts
@dec_delay_exit:
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
rts
; table for data to use when a tank is destroyed (#$1e bytes)
; * byte 0 - used to help only animate part of the tank that is visible
; * byte 1 - x offset from tank position for blank super-tile to draw
; * byte 2 - y offset from tank position for blank super-tile to draw
; * byte 3 - x offset from tank position for explosion
; * byte 4 - y offset from tank position for explosion
tank_destroy_tbl:
.byte $00,$16,$04,$1c,$0e ; ( 22, 4)
.byte $00,$16,$e4,$1c,$f2 ; ( 22, -28)
.byte $ff,$f6,$04,$00,$0e ; (-10, 4)
.byte $ff,$f6,$e4,$00,$f2 ; (-10, -28)
.byte $ff,$d6,$04,$e4,$0e ; (-42, 4)
.byte $ff,$d6,$e4,$e4,$f2 ; (-42, -28)
; set tank palette based on HP
tank_set_palette:
lda ENEMY_HP,x ; load enemy hp
lsr
lsr
lsr
lsr
tay ; transfer to offset register, every 16 hp the palette changes
lda tank_palette_tbl,y ; load correct palette for tank
sta LEVEL_PALETTE_INDEX+2 ; set tank's palette
lda #$10 ; a = #$10
jsr load_palettes_color_to_cpu ; load #$10 palette colors into PALETTE_CPU_BUFFER based on LEVEL_PALETTE_INDEX
ldx ENEMY_CURRENT_SLOT ; restore x to enemy current slot (load_palettes_color_to_cpu overwrites x)
rts
; table for tank palette changes based on hp (#$5 bytes)
tank_palette_tbl:
.byte $61,$60,$5f,$3f,$3f
; pointer table for alien carrier - level 5 boss (#$18 bytes)
boss_ufo_routine_ptr_tbl:
.addr boss_ufo_routine_00 ; CPU address $a6b2 - set y position and initialize palette fade in effect timer
.addr boss_ufo_routine_01 ; CPU address $a6c3 - determine x position randomly, add #$20 to y (if overflow set y to #$30)
.addr boss_ufo_routine_02 ; CPU address $a6e6 - draw super-tiles for the boss ufo at correct location
.addr boss_ufo_routine_03 ; CPU address $a723 - animate opening top, enable collision
.addr boss_ufo_routine_04 ; CPU address $a761 - generate mini ufos and bombs
.addr boss_ufo_routine_05 ; CPU address $a7fd - animate closing of top, disable collision
.addr boss_ufo_routine_06 ; CPU address $a817 - make ufo immediately invisible, and set BG_PALETTE_ADJ_TIMER to being fade in effect
.addr boss_ufo_routine_07 ; CPU address $a82c - update nametable to black to get rid of old boss ufo drawn in nametable
.addr boss_ufo_routine_08 ; CPU address $a834 - wait for animation delay and then go to boss_ufo_routine_01
.addr boss_ufo_routine_09 ; CPU address $a83f - boss ufo destroyed routine, play sound disable collision, remove all enemies
.addr boss_ufo_routine_0a ; CPU address $a857 - animate boss ufo explosions
.addr boss_ufo_routine_0b ; CPU address $a8b5 - animate door explosion and opening
; set y position and initialize palette fade in effect timer
boss_ufo_routine_00:
lda #$10 ; a = #$10
sta ENEMY_Y_POS,x ; enemy y position on screen
lda #$10 ; a = #$10
sta BG_PALETTE_ADJ_TIMER ; create initial fade in effect for boss ufo
; gives #$08 frames to draw boss ufo super-tiles before fade in effect will start
jsr load_palettes_color_to_cpu ; load #$10 palette colors into PALETTE_CPU_BUFFER based on LEVEL_PALETTE_INDEX
ldx ENEMY_CURRENT_SLOT ; restore x to current enemy slot
jmp advance_enemy_routine ; advance to boss_ufo_routine_01
; determine x position randomly, add #$20 to y (if overflow set y to #$30)
boss_ufo_routine_01:
lda RANDOM_NUM ; load random number
and #$03 ; keep bits .... ..xx
tay ; transfer to offset register
lda boss_ufo_x_pos_tbl,y ; load random boss ufo initial random x position
sta ENEMY_X_POS,x ; set enemy x position on screen
lda ENEMY_Y_POS,x ; load enemy y position on screen
clc ; clear carry in preparation for addition
adc #$20
cmp #$71 ; compare to max y position
bcc @continue ; continue if not greater than max y position
lda #$30 ; past max y position, set a = #$30
@continue:
sta ENEMY_Y_POS,x ; set enemy y position on screen
lda #$03 ; ENEMY_ANIMATION_DELAY is used as an index to know which super-tile to draw
; and not really an animation delay
boss_ufo_set_delay_adv_routine:
jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a and advance enemy routine
; table for boss possible x positions (#$4 bytes)
boss_ufo_x_pos_tbl:
.byte $40,$60,$80,$80
; draw super-tiles for the boss ufo at correct location
boss_ufo_routine_02:
ldy ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter (index of which super-tile to draw)
lda boss_ufo_supertile_update_ptr_tbl,y ; load appropriate supertile based on delay [#$00-#$03]
; see level_5_nametable_update_supertile_data
; draw boss ufo supertiles as specified location index
; decrements ENEMY_ANIMATION_DELAY and see if result is positive,
; * if positive, exit. Otherwise, advance the routine
; input
; * a - index into level_5_nametable_update_supertile_data
; * y - location to draw super-tile (indexes into boss_ufo_supertile_pos_tbl)
boss_ufo_draw_supertile_a:
sta $10 ; store nametable super-tile update index in $10
tya ; transfer delay index to a
asl ; double since each entry in boss_ufo_supertile_pos_tbl is #$02 bytes
tay ; transfer to offset register
lda boss_ufo_supertile_pos_tbl,y ; load the relative x position of the super-tile to draw
adc ENEMY_X_POS,x ; add enemy x position to relative offset
sta $00 ; store result in $00
lda boss_ufo_supertile_pos_tbl+1,y ; load the relative y position of the super-tile to draw
clc ; clear carry in preparation for addition
adc ENEMY_Y_POS,x ; add enemy y position on screen to relative offset
tay ; transfer result to y for load_bank_3_update_nametable_supertile call
lda $00 ; load x position of super-tile to draw for load_bank_3_update_nametable_supertile
jsr load_bank_3_update_nametable_supertile ; draw super-tile $10 at position (a,y)
ldx ENEMY_CURRENT_SLOT ; restore the enemy current slot to x
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bpl boss_ufo_exit_00 ; exit if still have more super-tiles to draw
lda #$02 ; drawn all super-tiles, a = #$02
sta ENEMY_VAR_1,x ;
lda #$60 ; a = #$60 (delay before starting attacks)
bne boss_ufo_set_delay_adv_routine ; always branch to advance routine
boss_ufo_exit_00:
rts
; table for boss ufo super-tile data (see level_5_nametable_update_supertile_data) (#$4 bytes)
; #$0d - blue top fully open (left)
; #$0e - blue top fully open (right)
; #$07 - bottom thruster full throttle (left)
; #$08 - bottom thruster full throttle (right)
boss_ufo_supertile_update_ptr_tbl:
.byte $0d,$0e,$07,$08
; table for relative x/y positions of boss ufo tile parts (#$8 bytes)
boss_ufo_supertile_pos_tbl:
.byte $e4,$e4 ; top-left (-28, -28)
.byte $04,$e4 ; top-right ( 04, -28)
.byte $e4,$04 ; bottom-left (-28, 04)
.byte $04,$04 ; bottom-right ( 04, 04)
; animate opening top, enable collision
boss_ufo_routine_03:
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
beq @timer_elapsed ; branch if timer has elapsed
jmp boss_ufo_draw_thrusters ; draw thruster super-tiles at appropriate thrust
@timer_elapsed:
jsr enable_enemy_collision ; enable bullet-enemy collision and player-enemy collision checks
jsr boss_ufo_draw_blue_top ; draw blue top if the delay timer has elapsed
dec ENEMY_VAR_1,x ; decrement which frame of the blue top to draw
bpl boss_ufo_exit_00 ; exit if still more to animate
lda #$00 ; a = #$00
jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a; advance enemy routine
boss_ufo_draw_blue_top:
lda #$0a ; a = #$0a (delay when opening the side bays)
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
lda ENEMY_VAR_1,x ; load blue top super-tile index to draw
asl ; double since each entry is #$02 bytes
tay ; transfer to offset register
lda boss_ufo_supertile_update_ptr_tbl_2+1,y ; load left side blue top super-tile
sta $07 ; store super-tile to draw in $07
lda boss_ufo_supertile_update_ptr_tbl_2,y ; load right side blue top super-tile
ldy #$00 ; set boss_ufo_supertile_pos_tbl index to #$00 (top-left)
jsr boss_ufo_draw_supertile_a ; draw boss ufo super-tile a at location index y
lda $07 ; load left side blue top super-tile nametable update
ldy #$01 ; set boss_ufo_supertile_pos_tbl index to #$01 (top-right)
jmp boss_ufo_draw_supertile_a ; draw boss ufo super-tile a at location index y
; table for side bays opening/closing sequence super-tiles (#$8 bytes)
; see (see level_5_nametable_update_supertile_data)
boss_ufo_supertile_update_ptr_tbl_2:
.byte $0b,$05 ; top closing frame 1 (left), top closing frame 1 (right)
.byte $0a,$04 ; top closing frame 2 (left), top closing frame 2 (right)
.byte $09,$03 ; top closing frame 3 (left), top closing frame 3 (right)
.byte $0d,$0e ; blue top fully open (left), blue top fully open (right)
; generate mini ufos and bombs
boss_ufo_routine_04:
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
beq boss_ufo_set_routine_05 ; advance to boss_ufo_set_routine_05 if animation delay has elapsed
lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter
and #$0f ; keep bits .... xxxx
bne boss_ufo_draw_thrusters
lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter
lsr
lsr
and #$0c ; keep bits .... xx..
tay
lda boss_ufo_enemy_gen_tbl,y ; load enemy type to generate
sta $0a ; store enemy type to generate
lda boss_ufo_enemy_gen_tbl+1,y ; load x position of enemy to generate
sty $17 ; store x position of enemy to generate in $17
ldy #$f4 ; y = #$f4 (relative height of bombs and ufos)
jsr generate_enemy_at_pos ; generate enemy type $0a at relative position a,y
bne boss_ufo_exit_01 ; exit if unable to create enemy
ldx $17 ; load x position of generated enemy
lda boss_ufo_enemy_gen_tbl+2,x ; load x fast velocity for generated enemy
sta ENEMY_X_VELOCITY_FAST,y ; set x fast velocity for generated enemy
lda #$10 ; a = #$10 (delay between ufo appear and move)
sta ENEMY_ANIMATION_DELAY,y ; set delay for generated enemy
lda #$02 ; a = #$02
sta ENEMY_SCORE_COLLISION,y ; set enemy score collision code
txa ; transfer x position of generated enemy to a
and #$04 ; keep bit 2
bne @continue ; exit if x positions bit 2 is set (mini-ufo and not drop bomb)
lda #$80 ; a = #$80
sta ENEMY_X_VELOCITY_FRACT,y ; set mini-ufo fractional velocity to .5
@continue:
lda boss_ufo_enemy_gen_tbl+3,x ; load enemy sprite code
sta ENEMY_SPRITES,y ; write enemy sprite code to CPU buffer
ldx ENEMY_CURRENT_SLOT ; restore x to boss ufo enemy slot index
boss_ufo_exit_01:
rts
boss_ufo_set_routine_05:
lda #$01 ; a = #$01
sta ENEMY_VAR_1,x ; prep ENEMY_VAR_1 for drawing super-tiles in boss_ufo_routine_05
lda #$08 ; a = #$08
bne boss_ufo_adv_routine_00 ; advance routine to boss_ufo_routine_05
boss_ufo_draw_thrusters:
lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter
and #$07 ; keep bits .... .xxx
cmp #$03 ; compare to #$03
bne boss_ufo_exit_01 ; exit if not #$03
lda ENEMY_ANIMATION_DELAY,x ; reload enemy animation frame delay counter
sta $06 ; backup animation delay in $06
ldy #$00 ; y = #$00 (thrusters full throttle)
and #$08 ; keep bit 3 of animation delay
bne @load_rocket_thrusters_nametable ; throttle on thrusters changes every #$08 frames
ldy #$02 ; thrusters half throttle
@load_rocket_thrusters_nametable:
lda #$02 ; a = #$02
sta ENEMY_ANIMATION_DELAY,x ; set animation delay so that boss_ufo_draw_supertile_a doesn't advance routine
lda boss_ufo_supertile_update_ptr_tbl_3+1,y ; load right thruster super-tile index
sta $07 ; store right thruster super-tile in $07
lda boss_ufo_supertile_update_ptr_tbl_3,y ; load left thruster super-tile index
ldy #$02 ; set the position index to draw to #$02 (bottom left)
jsr boss_ufo_draw_supertile_a ; draw boss ufo super-tile a at location index y
lda $07 ; load right thruster super-tile index
ldy #$03 ; set the position index to draw to #$03 (bottom right)
jsr boss_ufo_draw_supertile_a ; draw boss ufo super-tile a at location index y
lda $06 ; restore animation delay to correct value
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
rts
; table for boss ufo enemy generation data (#$14 bytes)
; #$15 = enemy type
; #$14 = relative initial x position
; #$01 = initial x velocity (high byte)
; #$7c = enemy sprite code
boss_ufo_enemy_gen_tbl:
.byte $15,$14,$01,$7c ; mini ufo (#$15) from right side (sprite_7c)
.byte $16,$00,$00,$22 ; dropped bomb 1 (sprite_22)
.byte $15,$ec,$fe,$7c ; mini ufo (#$15) from left side (sprite_7c)
.byte $16,$00,$00,$22 ; dropped bomb 2 (sprite_22)
; #$07 = boss ufo - bottom thruster full throttle (left)
; #$08 = boss ufo - bottom thruster full throttle (right)
; #$0c = boss ufo - bottom thruster half throttle (left)
; #$06 = boss ufo - bottom thruster half throttle (right)
; see (see level_5_nametable_update_supertile_data)
boss_ufo_supertile_update_ptr_tbl_3:
.byte $07,$08,$0c,$06 ; rocket thrusters tile codes
; animate closing of top, become invisible after drawing
boss_ufo_routine_05:
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne boss_ufo_draw_thrusters ; draw thrusters super-tiles at appropriate thrust
jsr boss_ufo_draw_blue_top ; draw boss ufo top super-tiles
inc ENEMY_VAR_1,x ; move to
lda ENEMY_VAR_1,x
cmp #$04
bne boss_ufo_exit_02
jsr disable_enemy_collision ; prevent player enemy collision check and allow bullets to pass through enemy
lda #$20 ; a = #$20 (delay between bays closing and fade)
boss_ufo_adv_routine_00:
jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a; advance enemy routine
; make ufo immediately invisible, and set BG_PALETTE_ADJ_TIMER to being fade in effect
boss_ufo_routine_06:
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne boss_ufo_draw_thrusters ; continue to animate thrusters until animation delay has elapsed
lda #$18 ; delay has elapsed, a = #$18
sta BG_PALETTE_ADJ_TIMER ; create 'fade in' effect and make ufo immediately invisible
; every frame BG_PALETTE_ADJ_TIMER will be decremented, while outside the range of #$09 to #$01, black is drawn
; once in range [#$01-#$09], the ufo will be faded in, but it'll be at a new location
lda #$10 ; a = #$10 (loading #$10 nametable palettes)
jsr load_palettes_color_to_cpu ; load #$10 palette colors into PALETTE_CPU_BUFFER based on LEVEL_PALETTE_INDEX
ldx ENEMY_CURRENT_SLOT ; restore x to boss ufo enemy slot index
lda #$03 ; a = #$03
jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a and advance enemy routine
; clear nametable where boss ufo was so that when BG_PALETTE_ADJ_TIMER is back in range [#$01-#$09], there is only one boss ufo
; and it's in a new location
boss_ufo_routine_07:
ldy ENEMY_ANIMATION_DELAY,x ; load super-tile index location [#$03-#$00]
lda #$9b ; a = #$9b (#$1b - all black)
jmp boss_ufo_draw_supertile_a ; draw boss ufo super-tile a at location index y
; wait for animation delay and then go to boss_ufo_routine_01
boss_ufo_routine_08:
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne boss_ufo_exit_02 ; exit if animation delay hasn't elapsed
lda #$02 ; a = #$02
jmp set_enemy_routine_to_a ; set enemy routine index to boss_ufo_routine_01
boss_ufo_exit_02:
rts
; boss ufo destroyed routine, play sound disable collision, remove all enemies
boss_ufo_routine_09:
jsr init_APU_channels
lda #$55 ; a = #$55 (sound_55)
jsr play_sound ; play sound
jsr disable_enemy_collision ; prevent player enemy collision check and allow bullets to pass through enemy
lda #$04 ; a = #$04 (final explosions count)
sta ENEMY_VAR_1,x ; initialize explosion location index
jsr destroy_all_enemies ; destroy all mini-ufos and dropped bombs
lda #$10 ; a = #$10 (delay before final explosions)
boss_ufo_adv_routine_01:
jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a; advance enemy routine
; animate boss ufo explosions
boss_ufo_routine_0a:
lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter
bne boss_ufo_dec_delay_exit ; exit if animation delay hasn't elapsed
lda ENEMY_VAR_1,x ; load explosion location index
bmi boss_ufo_adv_to_0b ; branch if all explosions animated to advance to boss_ufo_routine_0b
asl ; double current explosion location index
tay ; transfer to offset register
lda ENEMY_X_POS,x ; load enemy x position on screen
sta $16 ; store enemy x position in $16
adc boss_ufo_explosion_rel_pos_tbl,y ; add relative x offset for explosion
sta ENEMY_X_POS,x ; set enemy x position on screen
lda ENEMY_Y_POS,x ; load enemy y position on screen
sta $17 ; store enemy y position in $17
clc ; clear carry in preparation for addition
adc boss_ufo_explosion_rel_pos_tbl+1,y ; add relative y offset for explosion
sta ENEMY_Y_POS,x ; store enemy y position on screen
cpy #$10 ; see if position index is >= #$04, not sure why this is here, never happens !(WHY?)
bcs boss_ufo_create_explosion ; prevent updating the super-tile with black if location index is greater #$04
lda #$9b ; a = #$9b (#$1b - all black)
jsr draw_enemy_supertile_a ; draw super-tile a (offset into level nametable update table) at position (ENEMY_X_POS, ENEMY_Y_POS)
bcs boss_ufo_move_explosion ; branch if unable to draw explosion, not sure why this is coded like this !(WHY?)
boss_ufo_create_explosion:
jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position
dec ENEMY_VAR_1,x ; decrement explosion location index
lda #$08 ; a = #$08 (delay between final explosions)
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
jmp create_two_explosion_89 ; create explosion #$89 at location ($09, $08)
boss_ufo_dec_delay_exit:
dec ENEMY_ANIMATION_DELAY,x
boss_ufo_exit_03:
rts
; moves the explosion by moving base enemy position
; backup for when unable to draw explosion due to CPU_GRAPHICS_BUFFER being full
; couldn't get to execute and not sure why this is implemented !(WHY?)
boss_ufo_move_explosion:
lda $16
sta ENEMY_X_POS,x ; set enemy x position on screen
lda $17
sta ENEMY_Y_POS,x
rts
boss_ufo_adv_to_0b:
lda #$02 ; a = #$02
sta ENEMY_VAR_1,x ; init door explosion/opening index
lda #$04 ; a = #$04
bne boss_ufo_adv_routine_01 ; advance routine to boss_ufo_routine_0b
; table for boss ufo final explosions relative offsets (#$a bytes)
; byte 0 - x relative offset
; byte 1 - y relative offset
boss_ufo_explosion_rel_pos_tbl:
.byte $e0,$00 ; (-32, 0)
.byte $00,$20 ; ( 0, 32)
.byte $20,$00 ; ( 20, 0)
.byte $f0,$f0 ; (-16, -16)
.byte $00,$00 ; ( 0 , 0)
; animate door explosion and opening
boss_ufo_routine_0b:
lda ENEMY_ANIMATION_DELAY,x ; load animation delay
bne boss_ufo_dec_delay_exit ; exit if animation delay hasn't elapsed
lda ENEMY_VAR_1,x ; load door explosion index
bmi @remove_boss ; branch to remove boss if all door explosions have happened
asl
adc ENEMY_VAR_1,x ; multiply by 3
tay ; transfer to offset register
lda boss_ufo_door_explosion_tbl,y ; load x position of door explosion
sta ENEMY_X_POS,x ; set enemy x position on screen of door explosion
lda boss_ufo_door_explosion_tbl+1,y ; load y position of door explosion
sta ENEMY_Y_POS,x ; set enemy y position on screen of door explosion
lda boss_ufo_door_explosion_tbl+2,y ; load super-tile to draw (see level_5_nametable_update_supertile_data)
jsr draw_enemy_supertile_a ; draw super-tile a (offset into level nametable update table) at position (ENEMY_X_POS, ENEMY_Y_POS)
bcs boss_ufo_exit_03 ; exit if unable to draw super-tile
jmp boss_ufo_create_explosion ; create explosion at enemy position, i.e. explosion position
@remove_boss:
jsr level_boss_defeated ; play sound a (#$ff) !(BUG?) and set auto-move delay to ff, and set boss defeated flag
; this does not occur in Japanese version of the game, because in that version
; level_boss_defeated doesn't call play_sound
lda #$30 ; a = #$30
jmp set_delay_remove_enemy
; table for boss ufo generation (#$9 bytes)
; byte 0 - x position for exit explosions
; byte 1 - y position for exit explosions
; byte 2 - super-tile to draw (see level_5_nametable_update_supertile_data)
boss_ufo_door_explosion_tbl:
.byte $c0,$80,$96 ; (192, 128) (#$16 - boss screen open door top)
.byte $c0,$a0,$97 ; (192, 160) (#$17 - boss screen open door)
.byte $d0,$c0,$98 ; (208, 192) (#$18 - boss screen open door bottom)
; pointer table for flying saucer (#$7 * #$2 = #$e bytes)
mini_ufo_routine_ptr_tbl:
.addr mini_ufo_routine_00 ; CPU address $a8fa
.addr mini_ufo_routine_01 ; CPU address $a905
.addr mini_ufo_routine_02 ; CPU address $a922
.addr mini_ufo_routine_03 ; CPU address $a94c
.addr enemy_routine_init_explosion ; CPU address $e74b from bank 7
.addr enemy_routine_explosion ; CPU address $e7b0 from bank 7
.addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7
; flying saucer - pointer 1
mini_ufo_routine_00:
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
beq mini_ufo_advance_routine ; go to mini_ufo_routine_01 if animation delay has elapsed
jmp set_mini_ufo_sprite ; go through sprites every #$04 frames to create rotation animation
mini_ufo_advance_routine:
jmp advance_enemy_routine ; advance to next routine
; flying saucer - pointer 2
mini_ufo_routine_01:
jsr dec_mini_ufo_anim_delay_set_sprite ; decrement ENEMY_ANIMATION_DELAY and update sprite (if needed)
jsr update_enemy_x_pos_rem_off_screen ; update X velocity; remove enemy if X position < #$08 (off screen to left)
lda ENEMY_X_POS,x ; load enemy x position on screen
cmp #$20 ; left moving mini ufo point where starts vertical descent
bcc @begin_descent ; if mini ufo is too far to the left, reverse direction
cmp #$e0 ; right moving mini ufo point where starts vertical descent
bcc mini_ufo_exit ; exit if not ready to descend
; flying saucer y velocity going down (1.5) (high byte and low byte)
@begin_descent:
lda #$80 ; a = #$80 (.5)
sta ENEMY_Y_VELOCITY_FRACT,x ; set fractional velocity to 1/2
lda #$01 ; a = #$01 (1)
sta ENEMY_Y_VELOCITY_FAST,x ; set fast velocity to 1
bne mini_ufo_advance_routine ; move to mini_ufo_routine_02
; flying saucer - pointer 3
mini_ufo_routine_02:
jsr dec_mini_ufo_anim_delay_set_sprite ; decrement ENEMY_ANIMATION_DELAY and update sprite (if needed)
jsr set_enemy_y_vel_rem_off_screen ; add velocity to enemy Y position; remove enemy if Y position >= #$e8 (off screen to bottom)
lda ENEMY_Y_POS,x ; load enemy y position on screen
cmp #$a8 ; flying saucer bottom limit
bcc mini_ufo_exit ; exit if not at lowest point of path
lda #$a9 ; a = #$a9 (y adjust when at bottom limit)
sta ENEMY_Y_POS,x ; enemy y position on screen
ldy #$01 ; set fast velocity x portion to 1 (go right)
lda ENEMY_X_POS,x ; load enemy x position on screen
bpl @set_vel_adv_routine ; branch if left mini ufo
ldy #$fe ; set right mini ufo fast x velocity to -1 (go left)
; x velocity is -1.5 when considering fast and fractional velocities
@set_vel_adv_routine:
tya ; transfer x velocity fast byte to a
sta ENEMY_X_VELOCITY_FAST,x ; set x velocity fast byte (1 for right, -1 for left)
lda #$80 ; a = #$80
sta ENEMY_X_VELOCITY_FRACT,x ; set x fractional velocity byte to .5 (1/2)
jsr set_enemy_y_velocity_to_0 ; set y velocity to zero (stop descent)
beq mini_ufo_advance_routine ; go to mini_ufo_routine_03
mini_ufo_exit:
rts
; flying saucer - pointer 4
mini_ufo_routine_03:
jsr dec_mini_ufo_anim_delay_set_sprite ; decrement ENEMY_ANIMATION_DELAY and update sprite (if needed)
set_mini_ufo_drop_bomb_pos:
jmp update_enemy_pos ; apply velocities and scrolling adjust
dec_mini_ufo_anim_delay_set_sprite:
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
set_mini_ufo_sprite:
lda ENEMY_ANIMATION_DELAY,x ; load animation delay
and #$03 ; keep bits .... ..xx
bne @exit ; exit if not the #$04th frame
inc ENEMY_SPRITES,x ; every #$04 frames, change sprite
lda ENEMY_SPRITES,x ; load new sprite code
cmp #$7f ; last sprite of mini ufo sprites
bcc @exit ; exit if sprite is a mini ufo sprite
sbc #$03 ; went to non mini ufo sprite, subtract #$03 to set sprite_7c
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
@exit:
rts
; pointer table for drop bomb (#$4 * #$2 = #$8 bytes)
boss_ufo_bomb_routine_ptr_tbl:
.addr boss_ufo_bomb_routine_00 ; CPU address $a974
.addr enemy_routine_init_explosion ; CPU address $e74b from bank 7
.addr enemy_routine_explosion ; CPU address $e7b0 from bank 7
.addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7
; drop bomb - pointer 1
boss_ufo_bomb_routine_00:
lda #$28 ; a = #$28 (gravity value for drop bomb)
jsr add_a_to_enemy_y_fract_vel ; add a to enemy y fractional velocity, added every frame
lda ENEMY_Y_POS,x ; load enemy y position on screen
cmp #$b0 ; drop bomb explosion height
bcc set_mini_ufo_drop_bomb_pos ; apply velocities and scrolling adjust
jmp advance_enemy_routine ; advance to next routine enemy_routine_init_explosion
; pointer table for pipe joint (2 bytes)
ice_separator_routine_ptr_tbl:
.addr ice_separator_routine_00 ; CPU address $a985
; pipe joint - pointer 1
ice_separator_routine_00:
lda #$c4 ; a = #$c4 (sprite_c4)
sta ENEMY_SPRITES,x ; write sprite to sprite buffer
lda TANK_ICE_JOINT_SCROLL_FLAG
beq @add_scroll
lda FRAME_SCROLL ; how much to scroll the screen (#00 - no scroll)
beq @exit ; exit if scroll is #$00
dec ENEMY_X_POS,x ; subtract sprite x position from scroll to keep it still on screen
@exit:
rts
@add_scroll:
jmp add_scroll_to_enemy_pos ; add scrolling to enemy position
; pointer table for energy fire down (#$4 * #$2 = #$8 bytes)
fire_beam_down_routine_ptr_tbl:
.addr fire_beam_down_routine_00 ; CPU address $a9a1
.addr fire_beam_down_routine_01 ; CPU address $a9c8
.addr fire_beam_down_routine_02 ; CPU address $aa0f
.addr fire_beam_down_routine_03 ; CPU address $aa2b
; fire beam down - pointer 1
fire_beam_down_routine_00:
lda #$04 ; a = #$04
sta ENEMY_FRAME,x ; set enemy animation frame number
lda #$80 ; a = #$80
fire_beam_add_pos_set_delay:
ora ENEMY_ATTRIBUTES,x ; merge flip bits with original enemy attributes
sta ENEMY_ATTRIBUTES,x ; update enemy attributes (to support flipping horizontally and/or vertically)
lda #$08 ; a = #$08
jsr add_a_to_enemy_y_pos ; add a to enemy y position on screen
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
lsr
lsr
and #$03 ; keep bits .... ..xx
tay
lda fire_beam_anim_delay_tbl,y ; load animation delay
sta ENEMY_VAR_A,x ; storey in ENEMY_VAR_A
fire_beam_set_delay_adv_routine:
jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a; advance enemy routine
; table for fire beams delay between bursts (#$4 bytes)
fire_beam_anim_delay_tbl:
.byte $00,$20,$40,$60
; fire beam down - pointer 2
fire_beam_down_routine_01:
jsr animate_small_flame ; animate small flame when fire beam isn't firing
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter
bne fire_beam_dec_delay_exit ; decrement animation delay and exit if delay hasn't elapsed
jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01)
cmp #$20 ; see if player is within #$20 pixels of fire beam
bcs fire_beam_down_exit ; branch if closest player is farther than #$20
; load fire beam length, play sound, clear variables for use
begin_fire_beam_attack:
lda ENEMY_X_POS,x ; load enemy x position on screen
cmp #$30 ; position at which fire beam stops firing (as player scrolls right)
bcc fire_beam_down_exit ; don't fire if too far to the left
lda #$09 ; a = #$09 (sound_09)
jsr play_sound ; play fire beam burning sound
jsr enable_enemy_player_collision_check ; enable player collision check with flame
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
and #$03 ; keep bits 0 and 1 (fire beam length offset)
tay ; move offset to y
lda fire_beam_length_tbl,y ; load fire beam length
sta ENEMY_VAR_2,x ; store fire beam length
; for transfer to either ENEMY_VAR_3 or ENEMY_VAR_4
; based on specific fire beam enemy type
lda #$01 ; a = #$01 (sprite_01 - blank)
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
lda #$00 ; a = #$00
sta ENEMY_VAR_1,x
sta ENEMY_VAR_3,x
sta ENEMY_VAR_4,x
beq fire_beam_set_delay_adv_routine
fire_beam_dec_delay_exit:
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
rts
; table for fire beams possible lengths (#$4 bytes)
fire_beam_length_tbl:
.byte $05,$09,$0d,$0f
; fire beam down - pointer 3
fire_beam_down_routine_02:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
ldy #$00 ; y = #$00
jsr draw_fire_beam_if_anim_elapsed
bcs fire_beam_down_exit
dec ENEMY_VAR_2,x ; decrement fire beam length
beq set_fire_beam_delay_10_adv_routine
lda ENEMY_VAR_4,x ; load current fire beam length
adc #$08 ; add #$08 to length of fire beam
sta ENEMY_VAR_4,x ; update fire beam length
fire_beam_down_exit:
rts
set_fire_beam_delay_10_adv_routine:
lda #$10 ; a = #$10 (delay for fire beam to stay at max)
bne fire_beam_set_delay_adv_routine
; fire beam down - pointer 4
fire_beam_down_routine_03:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
ldy #$02 ; y = #$02
jsr draw_fire_beam_if_anim_elapsed
bcs fire_beam_down_exit
lda ENEMY_VAR_4,x
sbc #$07
sta ENEMY_VAR_4,x
bpl fire_beam_down_exit
fire_beam_disable_collision_routine_01:
lda ENEMY_VAR_A,x ; load animation delay
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
jsr disable_enemy_collision ; prevent player enemy collision check and allow bullets to pass through enemy
lda #$02 ; a = #$02
jmp set_enemy_routine_to_a ; set enemy routine fire_beam_xx_routine_01
; pointer table for fire beam left (#$4 * #$2 = #$8 bytes)
fire_beam_left_routine_ptr_tbl:
.addr fire_beam_left_routine_00 ; CPU address $aa55
.addr fire_beam_left_routine_01 ; CPU address $aa5a
.addr fire_beam_left_routine_02 ; CPU address $aa6c
.addr fire_beam_left_routine_03 ; CPU address $aa84
fire_beam_left_routine_00:
lda #$40 ; a = #$40
jmp fire_beam_add_pos_set_delay
fire_beam_left_routine_01:
jsr animate_small_flame ; animate small flame when fire beam isn't firing
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda FRAME_COUNTER ; load frame counter
and #$7f ; keep bits .xxx xxxx
cmp ENEMY_VAR_A,x
bne fire_beam_left_exit
jmp begin_fire_beam_attack ; load fire beam length, play sound, clear variables for use
fire_beam_left_routine_02:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
ldy #$04 ; y = #$04
jsr draw_fire_beam_if_anim_elapsed
bcs fire_beam_left_exit
dec ENEMY_VAR_2,x ; fire beam length
beq set_fire_beam_delay_10_adv_routine
lda ENEMY_VAR_3,x
sbc #$07
sta ENEMY_VAR_3,x
fire_beam_left_exit:
rts
fire_beam_left_routine_03:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
ldy #$06 ; y = #$06
jsr draw_fire_beam_if_anim_elapsed
bcs fire_beam_left_exit
lda ENEMY_VAR_3,x
adc #$08
sta ENEMY_VAR_3,x
bmi fire_beam_left_exit
beq fire_beam_left_exit
bpl fire_beam_disable_collision_routine_01
; pointer table for fire beam right (#$4 * #$2 = #$8 bytes)
fire_beam_right_routine_ptr_tbl:
.addr fire_beam_right_routine_00 ; CPU address $aaa4
.addr fire_beam_right_routine_01 ; CPU address $aaae
.addr fire_beam_right_routine_02 ; CPU address $aac3
.addr fire_beam_right_routine_03 ; CPU address $aade
fire_beam_right_routine_00:
lda #$40 ; a = #$40
sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes (flip sprite horizontally)
lda #$00 ; a = #$00
jmp fire_beam_add_pos_set_delay
fire_beam_right_routine_01:
jsr animate_small_flame ; animate small flame when fire beam isn't firing
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne fire_beam_right_exit_00
lda RANDOM_NUM ; load random number
and #$3f ; keep bits ..xx xxxx
sta ENEMY_VAR_A,x ; set delay between bursts
jmp begin_fire_beam_attack ; load fire beam length, play sound, clear variables for use
fire_beam_right_routine_02:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
ldy #$08 ; y = #$08
jsr draw_fire_beam_if_anim_elapsed
bcs fire_beam_right_exit_00
dec ENEMY_VAR_2,x ; fire beam length
beq fire_beam_delay_10_adv_routine
lda ENEMY_VAR_3,x
adc #$08
sta ENEMY_VAR_3,x
fire_beam_right_exit_00:
rts
fire_beam_delay_10_adv_routine:
jmp set_fire_beam_delay_10_adv_routine
fire_beam_right_routine_03:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
ldy #$0a ; y = #$0a
jsr draw_fire_beam_if_anim_elapsed
bcs fire_beam_right_exit_00
lda ENEMY_VAR_3,x
sbc #$07
sta ENEMY_VAR_3,x
bpl fire_beam_right_exit_00
jmp fire_beam_disable_collision_routine_01
; input
; * x - current enemy offset
; * y - fire_beam_tile_tbl offset minus 1
draw_fire_beam_if_anim_elapsed:
lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter
bne set_fire_beam_anim_delay_exit
lda ENEMY_VAR_3,x ; either ENEMY_VAR_3 or ENEMY_VAR_4 contain fire beam length
ora ENEMY_VAR_4,x ; merge horizontal and vertical fire beam length
beq @draw_fire_beam_section ;
iny ; fire beam length isn't #$00, increment fire_beam_tile_tbl offset
@draw_fire_beam_section:
lda fire_beam_tile_tbl,y ; offset into level_6_tile_animation
sta $10 ; load fire beam section update tiles offset
lda ENEMY_VAR_4,x ; load vertical fire beam length
clc ; clear carry in preparation for addition
adc ENEMY_Y_POS,x ; add fire eam length to enemy y position
tay ; transfer result to y
dey ; subtract #$01 from result
lda ENEMY_X_POS,x ; load enemy x position on screen
sec ; set carry flag in preparation for subtraction
sbc #$07 ; subtract #$07 from fire beam x position
sta $00 ; store result in $00
lda ENEMY_VAR_3,x ; load horizontal fire beam length
clc
bmi fire_beam_add_x_length_exit ; add horizontal length, but don't draw if off screen
adc $00
bcc draw_fire_beam_tiles ; draw fire beam section at (a, y)
clc
fire_beam_exit:
rts
fire_beam_add_x_length_exit:
adc $00
bcc fire_beam_exit
; draws the fire beam tiles $10 at (a, y)
; input
; * $10 - level_6_tile_animation offset (tiles to draw)
; * a - x position to draw tile
; * y - y position to draw tile
draw_fire_beam_tiles:
jsr load_bank_3_update_nametable_tiles ; draw tile code $10 to nametable at (a, y)
bcs @exit
ldx ENEMY_CURRENT_SLOT ; restore current enemy slot
lda ENEMY_X_POS,x ; load enemy x position on screen
cmp #$30 ; see if fire beam is closer to the left edge
lda #$00 ; clear animation delay
bcc @set_vars_exit ; set animation delay and update ENEMY_VAR_1 to fire beam length
clc
lda #$01 ; a = #$01 (delay between steps of burst)
@set_vars_exit:
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
lda ENEMY_VAR_3,x
ora ENEMY_VAR_4,x
sta ENEMY_VAR_1,x
@exit:
rts
set_fire_beam_anim_delay_exit:
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
sec ; set carry flag
rts
; table for fire beams tile codes (#$c bytes)
; offset into level_6_tile_animation
fire_beam_tile_tbl:
.byte $87,$88,$80,$89 ; beam down
.byte $84,$85,$80,$86 ; beam left
.byte $81,$82,$80,$83 ; beam right
; animates small flame for when fire beam isn't firing
animate_small_flame:
dec ENEMY_ATTACK_DELAY,x ; decrement attack delay
lda ENEMY_ATTACK_DELAY,x ; load attack delay
and #$07 ; keep bits .... .xxx
bne fire_beam_exit ; exit if not 8th frame
lda ENEMY_ATTACK_DELAY,x ; every 8th frame, change to next small flame animation
lsr
lsr
lsr
and #$03 ; keep bits .... ..xx
ora ENEMY_FRAME,x ; enemy animation frame number
tay
lda fire_beam_not_firing_sprite_tbl,y ; load sprite for animating flame before firing
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
rts
; table for sprites for animating small flame before firing fire beam (#$8 bytes)
fire_beam_not_firing_sprite_tbl:
.byte $01,$bf,$c0,$bf
.byte $01,$c1,$c2,$c1
; pointer table for boss robot (#$a * #$2 = #$14 bytes)
boss_giant_soldier_routine_ptr_tbl:
.addr boss_giant_soldier_routine_00 ; CPU address $ab93 - set hp sprite, y position and animation delay
.addr boss_giant_soldier_routine_01 ; CPU address $abb3 - wait for animation delay
.addr boss_giant_soldier_routine_02 ; CPU address $abbf
.addr boss_giant_soldier_routine_03 ; CPU address $ac7b
.addr boss_giant_soldier_routine_04 ; CPU address $acd4
.addr boss_giant_soldier_routine_05 ; CPU address $ad16
.addr boss_giant_soldier_routine_06 ; CPU address $ad49
.addr boss_giant_soldier_routine_07 ; CPU address $ad61
.addr boss_giant_soldier_routine_08 ; CPU address $ad9b
.addr boss_giant_soldier_routine_09 ; CPU address $adad
; boss robot - pointer 0 - set hp sprite, y position and animation delay
boss_giant_soldier_routine_00:
.ifdef Probotector
stx ENEMY_CURRENT_SLOT ; backup current enemy slot number
lda #$5e ; 4th level sprite palette
sta LEVEL_PALETTE_INDEX+7 ; set 4th level sprite palette (offset into game_palettes)
lda #$20 ; a = #$20
jsr load_palettes_color_to_cpu ; load #$20 palette colors into PALETTE_CPU_BUFFER based on LEVEL_PALETTE_INDEX
ldx ENEMY_CURRENT_SLOT ; restore current enemy slot number
.endif
lda PLAYER_WEAPON_STRENGTH
asl
asl
asl
adc #$40
sta ENEMY_HP,x ; set enemy hp (40 + wsc * 8)
lda RANDOM_NUM ; load random number
sta ENEMY_VAR_1,x ; store random number in ENEMY_VAR_1
lda #$b8 ; a = #$b8 (sprite_b8) giant boss soldier standing
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
lda #$9b ; a = #$9b (initial boss y position)
sta ENEMY_Y_POS,x ; enemy y position on screen
lda #$31 ; a = #$31 (initial boss delay waiting)
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
bne giant_soldier_adv_enemy_routine
; boss robot - pointer 1 - wait for delay
boss_giant_soldier_routine_01:
jsr update_enemy_pos ; apply velocities and scrolling adjust
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
beq giant_soldier_adv_enemy_routine
rts
giant_soldier_adv_enemy_routine:
jmp advance_enemy_routine ; advance to next routine
; boss robot - pointer 2
boss_giant_soldier_routine_02:
jsr update_enemy_pos ; apply velocities and scrolling adjust
lda ENEMY_VAR_1,x ; load initialized random number
and #$03 ; keep bits .... ..xx
bne begin_giant_soldier_attack ; 3/4 chance of branching
jsr giant_soldier_face_random_player ; 1/4 chance of happening, walk towards player
lda #$f9 ; a = #$f9
sta ENEMY_Y_VELOCITY_FAST,x ; set initial y fast velocity when jumping
lda #$80 ; a = #$80
sta ENEMY_Y_VELOCITY_FRACT,x ; set initial y fractional velocity when jumping
lda RANDOM_NUM ; load random number
adc FRAME_COUNTER ; add random number to frame counter
and #$03 ; keep bits .... ..xx
asl ; strip #$00 to #$04 to just either #$00 or #$02
tay ; transfer #$00 or #$02 to y
lda boss_giant_soldier_x_vel_tbl,y ; load x velocity fast byte
sta ENEMY_X_VELOCITY_FAST,x ; store in x velocity fast byte
lda boss_giant_soldier_x_vel_tbl+1,y ; load x fractional velocity byte
sta ENEMY_X_VELOCITY_FRACT,x ; store in x fractional velocity byte
lda #$00 ; a = #$00
sta ENEMY_VAR_4,x ; initialize number of thrown saucers to #$00
lda #$ba ; a = #$ba (sprite_ba) sprite code while jumping
sta ENEMY_SPRITES,x ; set sprite code to sprite_ba
lda #$05 ; a = #$05 (advance to boss_giant_soldier_routine_04)
giant_soldier_set_enemy_routine_a:
jmp set_enemy_routine_to_a ; set enemy routine index to a
begin_giant_soldier_attack:
cmp #$01 ; probability of attacking (1/4)
bne @walk_to_player ; don't attack
jsr giant_soldier_face_random_player ; attack
inc ENEMY_VAR_4,x ; increment number of thrown saucers
lda ENEMY_VAR_4,x ; load number of thrown saucers
cmp #$04 ; max number of consecutive thrown saucers
bcs giant_soldier_stay_still ; stay still if thrown #$04 consecutive saucers
lda #$20 ; a = #$20 (delay before throwing position)
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
bne giant_soldier_adv_enemy_routine ; go to boss_giant_soldier_routine_03
@walk_to_player:
jsr giant_soldier_face_random_player
lda RANDOM_NUM ; load random number
and #$01 ; keep bits .... ...x
asl
tay
lda boss_giant_soldier_walk_x_vel_tbl,y ; load walking x velocity fast byte
sta ENEMY_X_VELOCITY_FAST,x ; store walking x velocity fast byte
lda boss_giant_soldier_walk_x_vel_tbl+1,y ; load walking x fractional velocity byte
sta ENEMY_X_VELOCITY_FRACT,x ; store walking x fractional velocity byte
lda #$0c ; a = #$0c
sta ENEMY_VAR_2,x ; delay for first step of walking
lda #$06 ; a = #$06
bne giant_soldier_set_enemy_routine_a ; go to boss_giant_soldier_routine_05
giant_soldier_stay_still:
lda #$b8 ; a = #$b8 (sprite_b8) giant boss soldier standing
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
jsr set_enemy_velocity_to_0 ; set x/y velocities to zero
lda RANDOM_NUM ; load random number
sta ENEMY_VAR_1,x ; store random number in ENEMY_VAR_1
adc FRAME_COUNTER ; add frame counter to random number
and #$80 ; keep bits x... ....
ora ENEMY_ANIMATION_DELAY,x ; (extend delay before next attack by 0 or 80)
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
lda #$03 ; a = #$03
bne giant_soldier_set_enemy_routine_a ; always branch to boss_giant_soldier_routine_02
; gets boss to face random non-game over player
giant_soldier_face_random_player:
ldy #$00 ; y = #$00 (player 1)
lda P2_GAME_OVER_STATUS ; load player 2 game over state (1 = game over)
bne @set_sprite_attr ; flip sprite horizontally if appropriate, then exit
lda RANDOM_NUM ; player 2 not in game over, load random number
and #$01 ; keep bit 0 (select random player)
tay ; transfer player index to y
lda P1_GAME_OVER_STATUS ; game over state of player 1
beq @set_sprite_attr ; flip sprite horizontally if appropriate, then exit
ldy #$01 ; y = #$01, player 1 in game over, use player 2
@set_sprite_attr:
lda SPRITE_X_POS,y ; load randomly selected non-dead player's current x position
sta $08 ; store in $08
lda ENEMY_X_POS,x ; load enemy current x position
cmp $08 ; compare enemy x position to player x position
lda #$00 ; a = #$00 (assume boss facing left)
bcs @set_sprite_attr_exit ; branch if enemy to right of player (boss face left)
lda #$40 ; player to right of enemy, flip sprite horizontally to face right
@set_sprite_attr_exit:
sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes
rts
; table for possible x velocities when jumping (#$8 bytes)
; seems to happen rarely, only at the beginning
; when he is not on his left or right limit (x position)
boss_giant_soldier_x_vel_tbl:
.byte $00,$80
.byte $00,$00
.byte $00,$00
.byte $ff,$80
; table for level 6 boss walking speed (#$4 bytes)
boss_giant_soldier_walk_x_vel_tbl:
.byte $01,$18
.byte $fe,$e8
; boss robot - pointer 3
; creates spiked projectiles
boss_giant_soldier_routine_03:
jsr update_enemy_pos ; apply velocities and scrolling adjust
lda ENEMY_ATTACK_FLAG ; see if enemies should attack
beq boss_giant_stay_still ; exit if enemies shouldn't attack
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
beq create_spiked_projectile ; create the spiked projectile
lda ENEMY_ANIMATION_DELAY,x ; attack delay not elapsed, load enemy animation frame delay counter
cmp #$0f ; (determines delay for throw stance)
bcs set_giant_soldier_palette ; change palette according to hp (every #$04 frames)
lda #$c3 ; a = #$c3 (sprite_c3) sprite code before throw
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
; change palette according to hp
set_giant_soldier_palette:
lda FRAME_COUNTER ; load frame counter
and #$07 ; keep bits .... .xxx
cmp #$03 ; only update palette every #$04 frames
bne @exit
lda ENEMY_HP,x ; every #$04 frames, check palette, load enemy hp
cmp #$20 ; normal palette if hp >= #$20
bcs @exit
ldy #$51 ; palette #$01 (palette for medium damage)
cmp #$10 ; medium damage if hp >= #$10 and < #$20
bcs @set_palette ; branch if hp > #$10, use palette #$01
ldy #$52 ; hp < #$10, palette #$02 (palette for critical damage)
; critical damage if hp < 10
@set_palette:
tya ; transfer palette (and sprite byte override) to a
sta LEVEL_PALETTE_INDEX+7 ; set sprite's 3rd palette
lda #$20 ; a = #$20
jsr load_palettes_color_to_cpu ; load #$20 palette colors into PALETTE_CPU_BUFFER based on LEVEL_PALETTE_INDEX
@exit:
ldx ENEMY_CURRENT_SLOT
rts
create_spiked_projectile:
lda #$14 ; a = #$14 (14 = spiked disk projectile)
sta $0a ; set enemy type for projectile
lda #$f0 ; a = #$f0 (initial relative x position)
ldy #$e8 ; y = #$e8 (initial relative y position)
jsr generate_enemy_at_pos ; generate enemy type $0a at relative position a,y
bne boss_giant_stay_still ; exit if unable to create spiked disk projectile
lda ENEMY_SPRITE_ATTR,x ; load boss giant's ENEMY_SPRITE_ATTR
and #$40 ; load boss giant's horizontal flip bit
beq boss_giant_stay_still ; branch if boss giant facing left
lda ENEMY_X_POS,y ; load spiked saucer x position on screen
adc #$30 ; add 30 to x position if facing right
sta ENEMY_X_POS,y ; set spiked saucer x position on screen
boss_giant_stay_still:
jmp giant_soldier_stay_still
; boss robot - pointer 4
boss_giant_soldier_routine_04:
jsr set_giant_soldier_palette ; change palette according to hp (every #$04 frames)
jsr @apply_gravity ; apply gravity and x boundaries check
jsr update_enemy_pos ; apply velocities and scrolling adjust
lda ENEMY_Y_POS,x ; load enemy y position on screen
cmp #$9b ; ground limit when landing after jump
bcs @boss_landing ; branch if landed on ground
rts
; boss robot landing
@boss_landing:
lda #$15 ; a = #$15 (sound_15)
jsr play_sound ; play boss robot landing sound
jsr set_enemy_velocity_to_0 ; set x/y velocities to zero
lda #$9b ; a = #$9b
sta ENEMY_Y_POS,x ; set enemy y position on screen to ground
lda #$b8 ; a = #$b8 (sprite_b8) (same as sprite_b7)
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
bne boss_giant_stay_still
; apply gravity and x boundaries check
@apply_gravity:
lda #$38 ; a = #$38 (gravity when jumping)
jsr add_a_to_enemy_y_fract_vel ; add a to enemy y fractional velocity (#$38)
lda ENEMY_X_POS,x ; load enemy x position on screen
cmp #$21 ; #$20 = left boundary
bcc @set_enemy_to_left_edge ; giant soldier is at left edge, hard code stop at boundary
cmp #$c0 ; #$c0 = right boundary
bcc @exit ; enemy hasn't reached left nor right boundary, exit
lda #$c0 ; reached right boundary hard code stop at boundary
bne @set_to_x_boundary ; always branch to set giant soldier to right boundary
@set_enemy_to_left_edge:
lda #$20 ; a = #$20
@set_to_x_boundary:
sta ENEMY_X_POS,x ; set enemy x position on screen
jsr set_enemy_x_velocity_to_0 ; set x velocity to zero
@exit:
rts
; boss robot - pointer 5
boss_giant_soldier_routine_05:
jsr set_giant_soldier_palette ; change palette according to hp (every #$04 frames)
jsr update_enemy_pos ; apply velocities and scrolling adjust
lda ENEMY_X_POS,x ; load enemy x position on screen
cmp #$20 ; left limit when walking
bcc @stay_still
cmp #$c0 ; right limit when walking
bcs @stay_still
dec ENEMY_VAR_2,x ; decrement delay between steps
beq @continue ; branch if delay has elapsed
rts
@stay_still:
jmp giant_soldier_stay_still
@continue:
lda #$00 ; a = #$00
sta ENEMY_VAR_4,x ; clear number of consecutive thrown saucers
lda #$0c ; a = #$0c (delay between steps)
sta ENEMY_VAR_2,x ; initialize delay between steps
inc ENEMY_VAR_3,x
lda ENEMY_VAR_3,x
and #$01 ; keep bits .... ...x
clc ; clear carry in preparation for addition
adc #$b8 ; load sprite_b8 (sprite_b7) or sprite_b9
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
rts
boss_giant_soldier_routine_06:
jsr init_APU_channels
lda #$55 ; a = #$55 (sound_55)
jsr level_boss_defeated ; play sound and initiate auto-move
jsr clear_enemy_custom_vars ; set ENEMY_VAR_1, ENEMY_VAR_2, ENEMY_VAR_3, ENEMY_VAR_4 to zero
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
sta $08 ; set relative y offset to #$00
sta $09 ; set relative x offset to #$00
jsr create_giant_boss_explosion ; create explosion at center of enemy
; $09 - relative x offset, $08 - relative y offset
jmp advance_enemy_routine ; advance enemy in slot x to next routine
; create explosion animations
boss_giant_soldier_routine_07:
lda #$08 ; a = #$08
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
lda ENEMY_VAR_1,x ; load explosion number
cmp #$04 ; number of explosions when boss is destroyed
bcc @create_explosion ; when not yet created all explosions branch
lda #$30 ; all explosions have been created, set delay and move to next routine
jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to #$30
; move to boss_giant_soldier_routine_08
@create_explosion:
inc ENEMY_VAR_1,x ; increment explosion number
asl ; double for offset
tay ; transfer boss_giant_explosion_loc_tbl offset to y
lda boss_giant_explosion_loc_tbl,y ; load relative y offset to enemy for explosion
sta $08 ; store relative y offset to enemy for explosion
lda boss_giant_explosion_loc_tbl+1,y ; load relative x offset to enemy for explosion
sta $09 ; store relative x offset to enemy for explosion
create_giant_boss_explosion:
lda ENEMY_Y_POS,x ; load enemy y position on screen
adc $08 ; add relative y offset for explosion y location
tay ; transfer result to y
lda ENEMY_X_POS,x ; load enemy x position on screen
adc $09 ; add relative x offset for explosion x location
sty $08 ; store absolute y location of explosion in $08
sta $09 ; store absolute x location of explosion in $09
jmp create_two_explosion_89 ; create explosion #$89 at location ($09, $08)
; table for explosions relative offsets (#$4 * #$2 = #$8 bytes)
; byte 0 - y offset
; byte 1 - x offset
boss_giant_explosion_loc_tbl:
.byte $f0,$f0 ; -16, -16
.byte $10,$10 ; 16, 16
.byte $f0,$10 ; -16, 16
.byte $10,$f0 ; 10, -10
boss_giant_soldier_routine_08:
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
beq @continue ; useless branching !(HUH)
@continue:
jsr clear_enemy_custom_vars ; set ENEMY_VAR_1, ENEMY_VAR_2, ENEMY_VAR_3, ENEMY_VAR_4 to zero
lda #$08 ; a = #$08 (number of steps for door opening)
sta ENEMY_VAR_3,x
lda #$0a ; a = #$0a (delay for 1st step of door opening)
jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a; advance enemy routine
; waits for animation timer to elapse, then opens next part of door, repeats until door opened
boss_giant_soldier_routine_09:
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
beq @open_door_section ; enemy destroyed delay elapsed, open door
rts
; updates the door nametable tiles for most of the opening animation when ENEMY_VAR_1 is #$01
; this is the majority of the door opening animation
@mode_1_open_door:
lda #$8c ; a = #$8c (blank spot and top of door tile code)
sta $10 ; set tile index offset for load_bank_3_update_nametable_tiles
lda ENEMY_VAR_2,x ; load the y position of the bottom of the end of level door (for animating)
tay ; set y position
lda #$d0 ; a = #$d0 (x position to draw tiles)
jsr load_bank_3_update_nametable_tiles ; draw the open door tile code $10 at position (#$d0, y)
ldx ENEMY_CURRENT_SLOT ; restore x's value to the current enemy (boss giant)
dec ENEMY_VAR_3,x ; decrement door opening animation timer
bne @set_door_next_open_pos ; if animation timer hasn't elapsed don't update ENEMY_VAR_1 to #$02
inc ENEMY_VAR_1,x ; animation timer has elapsed, increment ENEMY_VAR_1 to #$02 (top of door animation frame)
@set_door_next_open_pos:
lda ENEMY_VAR_2,x ; load current y position of the bottom of the end of level door
sec ; set carry flag in preparation for subtraction
sbc #$08 ; subtract #$08 from y position
sta ENEMY_VAR_2,x ; update y position
rts
; boss_giant_soldier_routine_09, delay elapsed, open door
; ENEMY_VAR_1 #$00 and #$02 uses boss_giant_door_open_ptr_tbl, #$01 uses @mode_1_open_door
@open_door_section:
lda #$0a ; a = #$0a (delay for each step of door opening)
sta ENEMY_ANIMATION_DELAY,x ; frame delay counter for door opening animation
lda ENEMY_VAR_1,x ; load door opening animation frame type
cmp #$01 ; see if use the most common nametable update tile
beq @mode_1_open_door ; ENEMY_VAR_1 uses #$8c for animating the door opening (most common)
asl ; ENEMY_VAR_1 is either #$00 or #$02, double since each entry is #$02 bytes
tay ; transfer to offset register
lda boss_giant_door_open_ptr_tbl,y ; load low byte of address
sta $04 ; set low byte of address in $04
lda boss_giant_door_open_ptr_tbl+1,y ; load high byte of address
sta $05 ; store high byte of address in $05
ldy #$00 ; initialize y = #$00
lda ($04),y ; load initial y position of door animation
clc ; clear carry in preparation for addition
adc #$10 ; add #$10 to the position (move down)
sta $06 ; update initial y position of door opening update
lda #$d0 ; a = #$d0
sta $07 ; set x position of door
stx ENEMY_CURRENT_SLOT
@update_door_tiles:
lda $07 ; load x position of door
iny ; increment boss_giant_door_open_xx read offset
lda ($04),y ; load tile to draw (offset into tile_animation table)
cmp #$ff ; see if end of data byte
beq @finished_drawing_door
sta $10 ; set nametable tile to draw
tya ; transfer to offset register
pha ; push a to stack
lda $07 ; load x position
ldy $06 ; load y position
jsr load_bank_3_update_nametable_tiles ; draw tile code $10 to nametable at (a, y)
pla ; restore a register (nametable tile to draw)
tay ; transfer a to offset register
lda $06 ; load y position of door animation
clc ; clear carry in preparation for addition
adc #$10 ; add #$10 to y position (move down)
sta $06 ; update $06 with new y position
jmp @update_door_tiles ; loop to next part of nametable to draw
@finished_drawing_door:
ldx ENEMY_CURRENT_SLOT
lda $06 ; load current door opening position
sec ; set carry flag in preparation for subtraction
sbc #$20 ;
sta ENEMY_VAR_2,x ; update door opening y location
inc ENEMY_VAR_1,x ; increment opening animation frame type
lda ENEMY_VAR_1,x ; load opening animation frame type
cmp #$03 ; compare to #$03
beq @remove_enemy ; if finished all three frame types (#$00, #$01, #$02)
rts
@remove_enemy:
lda #$01 ; a = #$01
jmp set_delay_remove_enemy
; pointer table for boss giant soldier door opening (#$3 * #$2 = #$6 bytes)
; related to ENEMY_VAR_1
; boss_giant_door_open_00 is for the beginning of the animation (lifting from floor)
; boss_giant_door_open_01 is for the end of the animation (lifting into top)
boss_giant_door_open_ptr_tbl:
.addr boss_giant_door_open_00 ; CPU address $ae3b
.addr boss_giant_door_open_00 ; CPU address $ae3b (unused, filler)
; filler because ENEMY_VAR_1 = 1 uses #$8c (see @mode_1_open_door)
.addr boss_giant_door_open_01 ; CPU address $ae3f
; table for boss giant door being opening tiles (#$4 bytes)
; byte 0 is initial y position. the rest of the bytes reference level_6_tile_animation
boss_giant_door_open_00:
.byte $90,$8b,$8a,$ff
; table for boss giant end door opening (#$3 bytes)
; byte 0 is initial y position. the rest of the bytes reference level_6_tile_animation
boss_giant_door_open_01:
.byte $58,$8d,$ff
; pointer table for spiked disk projectile (#$3 * #$2 = #$6 bytes)
boss_giant_projectile_routine_ptr_tbl:
.addr boss_giant_projectile_routine_00 ; CPU address $ae48 - set sprite code, and velocities
.addr boss_giant_projectile_routine_01 ; CPU address $ae8a - update position, if animation delay has elapsed, update sprite so the disk rotates
.addr remove_enemy ; CPU address $e809 from bank 7
; set sprite code, and velocities
boss_giant_projectile_routine_00:
lda #$06 ; a = #$06 (delay between frames when airborne)
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
lda #$bb ; a = #$bb (sprite_bb)
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
; assume boss giant facing left, set spiked velocity
; if not, will be changed later (+14 lines down)
lda #$fd ; a = #$fd (-3)
sta ENEMY_X_VELOCITY_FAST,x ; set spiked disk x fast velocity
lda #$00 ; a = #$00
sta ENEMY_X_VELOCITY_FRACT,x ; set spiked disk x fractional velocity
ldx #$0f ; set enemy slot offset to #$0f
@find_boss_giant:
lda ENEMY_TYPE,x ; load current enemy type
cmp #$13 ; see if current slot is boss giant
beq @continue ; branch if boss giant found
dex ; search next enemy slot
bne @find_boss_giant ; boss giant not found, loop
@continue:
lda ENEMY_SPRITE_ATTR,x ; load enemy sprite attributes
and #$40 ; keep bit 6 (horizontal flip flag)
beq @set_y_vel_adv_routine ; branch if boss giant facing left to set y vel and advance routine
ldx ENEMY_CURRENT_SLOT ; boss giant facing right, load spiked disk slot
lda #$03 ; a = #$03
sta ENEMY_X_VELOCITY_FAST,x ; set spiked disk x fast velocity going right
lda #$00 ; a = #$00
sta ENEMY_X_VELOCITY_FRACT,x ; set spiked disk x fractional velocity
; y velocity for spiked disk
@set_y_vel_adv_routine:
ldx ENEMY_CURRENT_SLOT ; restore spiked disk enemy slot
lda #$02 ; a = #$02
sta ENEMY_Y_VELOCITY_FAST,x ; set spiked disk fast y velocity to #$02
lda #$00 ; a = #$00
sta ENEMY_Y_VELOCITY_FRACT,x ; set spiked disk fractional y velocity to #$00
boss_giant_projectile_adv_routine:
jmp advance_enemy_routine ; advance spiked disk to next routine, boss_giant_projectile_routine_01 or remove_enemy
; update position, if animation delay has elapsed, update sprite so the disk rotates
; remove enemy if off screen
boss_giant_projectile_routine_01:
lda ENEMY_X_POS,x ; load enemy x position on screen
cmp #$e0 ; see if off screen to the right
bcs boss_giant_projectile_adv_routine ; advance routine to remove_enemy
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
beq @set_sprite_update_pos_exit ; update sprite if animation delay has elapsed
lda ENEMY_Y_POS,x ; animation delay hasn't elapse,d load enemy y position on screen
cmp #$af ; ground limit for spiked disk
bcs @stop_y_velocity ; branch if landed on ground to stop disk from falling down below ground
bcc @update_pos_exit ; update pos and exit if not yet landed on ground
; stops y velocity, keeping x velocity
@stop_y_velocity:
jsr set_enemy_y_velocity_to_0 ; set y velocity to zero
@update_pos_exit:
jmp update_enemy_pos ; apply velocities and scrolling adjust
@set_sprite_update_pos_exit:
lda #$06 ; a = #$06
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
inc ENEMY_VAR_1,x ; increment sprite code control flag
lda ENEMY_VAR_1,x ; load sprite code control flag
and #$01 ; keep bit 0
clc ; clear carry in preparation for addition
adc #$bb ; add to #$bb to determine wither sprite_bb or sprite_bc will be drawn
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
jmp update_enemy_pos ; apply velocities and scrolling adjust
; pointer table for mechanical claw (#$4 * #$2 = #$8 bytes)
claw_routine_ptr_tbl:
.addr claw_routine_00 ; CPU address $aec3 - set ENEMY_FRAME to frame counter trigger, strip ENEMY_ATTRIBUTES to just claw length, set delay, advance routine
.addr claw_routine_01 ; CPU address $aee5 - wait for descent, advance routine
.addr claw_routine_02 ; CPU address $af30 - animate claw descent
.addr claw_routine_03 ; CPU address $af4d - animate claw ascent
; set ENEMY_FRAME to frame counter trigger, strip ENEMY_ATTRIBUTES to just claw length, set delay, advance routine
claw_routine_00:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
lsr
lsr ; get rid of claw length
and #$03 ; keep bits 0 and 1 (descend delay)
tay ; transfer to offset register
lda claw_frame_trigger_tbl,y ; load frame counter number at which to trigger descent
sta ENEMY_FRAME,x ; store claw delay in ENEMY_FRAME
lda ENEMY_ATTRIBUTES,x ; reload enemy attributes
and #$03 ; keep bits 0 and 1 (claw length)
sta ENEMY_ATTRIBUTES,x ; update ENEMY_ATTRIBUTES to just store claw length
lda #$20 ; a = #$20 (initial delay before going descending)
claw_set_delay_adv_routine:
jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a; advance enemy routine
; table of frame counter triggers before going down (#$4 bytes)
; whenever (FRAME_COUNTER & #$7f) matches this value, the claw will descend
; used so that all claws of the same delay go down at the same time
; enemy attribute bits .... xx..
claw_frame_trigger_tbl:
.byte $00,$20,$40,$60
; wait for descent, advance routine
claw_routine_01:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
cmp #$03 ; see if claw length 3 (seeking claw - only attack when player near)
beq @check_delay_seek_player ; branch if seeking claw
lda FRAME_COUNTER ; not seeking claw, load frame counter to see if should descend
and #$7f ; strip bit 7
cmp ENEMY_FRAME,x ; compare to enemy animation frame number (frame counter trigger point)
bne claw_routine_01_exit ; branch if frame counter doesn't match and claw shouldn't descend
@descend_claw:
lda ENEMY_X_POS,x ; load enemy x position on screen
cmp #$2c ; compare to the left 17% of screen
bcc claw_routine_01_exit ; don't descend if exit if claw is far to the left
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
tay ; transfer maximum claw length to offset register
asl ; double since each entry in claw_update_nametable_ptr_tbl is #$02 bytes
sta ENEMY_VAR_4,x ; set claw_update_nametable_ptr_tbl offset
lda claw_length_tbl,y ; load claw length
sta ENEMY_VAR_2,x ; set claw length
lda #$00 ; a = #$00
sta ENEMY_VAR_3,x
beq claw_set_delay_adv_routine ; always branch
; claw length 3 - seeking claw
@check_delay_seek_player:
lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter
bne claw_dec_delay_exit ; decrement delay and exit if timer hasn't elapsed
lda FRAME_COUNTER ; load frame counter
cmp #$c0 ; seeking claws don't attack 25% of the time, i.e. between #$c0 and #$ff inclusively
bcs claw_routine_01_exit ; exit if claw shouldn't attack due to timing
jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01)
cmp #$10 ; see if player within #$10 horizontal pixels of claw
bcc @descend_claw ; descend claw if closest player is < #$10 distance away
rts ; exit if player too far from claw for it to attack
claw_dec_delay_exit:
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
sec ; set carry flag (indicates claw wasn't animated)
rts
; table for possible claw lengths (#$4 bytes)
; length code 3 makes the claw activate only when the player is near
claw_length_tbl:
.byte $04,$03,$08,$03
; animate claw descent
claw_routine_02:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
jsr animate_claw ; update the nametable tiles
bcs claw_routine_01_exit ; exit if unable to update the nametable tiles
dec ENEMY_VAR_2,x ; decrement remaining claw length for ascending
beq claw_inc_tile_adv_routine ; advance routine if claw fully extended
lda #$08 ; a = #$08
jsr add_a_to_enemy_y_pos ; add #$08 to enemy y position on screen
inc ENEMY_VAR_3,x ; increment current length of the claw
claw_routine_01_exit:
rts
claw_inc_tile_adv_routine:
inc ENEMY_VAR_4,x ; increment claw_update_nametable_ptr_tbl offset
lda #$08 ; a = #$08
bne claw_set_delay_adv_routine ; advance to claw_routine_03
; animate claw ascent
claw_routine_03:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
jsr animate_claw ; animate claw ascent
bcs claw_routine_01_exit ; exit if unable to update nametable tiles
dec ENEMY_VAR_3,x ; decrement current length of the claw
bmi @wait_for_descent ; go back to claw_routine_01 to wait for next descent if claw fully retracted
lda #$f8 ; a = #$f8 (-8)
jmp add_a_to_enemy_y_pos ; add a to enemy y position on screen
@wait_for_descent:
lda ENEMY_FRAME,x ; load enemy animation frame number
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
lda #$02 ; a = #$02
jmp set_enemy_routine_to_a ; set enemy routine index to claw_routine_01 to wait for next descent
; updates nametable tiles to animate ascending and descending the claw
; output
; * carry flag - clear when nametable updated, set when not
animate_claw:
lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter
bne claw_dec_delay_exit ; exit if animation delay hasn't elapsed
; with carry flag set indicating no update
lda ENEMY_VAR_4,x ; load current offset into claw_update_nametable_ptr_tbl
asl ; double since each entry is #$02 bytes
tay ; transfer to offset register
lda claw_update_nametable_ptr_tbl,y ; load low byte of address
sta $10 ; store low byte of address in $10
lda claw_update_nametable_ptr_tbl+1,y ; load high byte of address
sta $11 ; store high byte of address in $11
ldy ENEMY_VAR_3,x ; load current extension of the claw
lda ($10),y ; load tile code to draw
sta $10 ; store tile code to draw for load_bank_3_update_nametable_tiles
lda ENEMY_X_POS,x ; load enemy x position on screen
ldy ENEMY_Y_POS,x ; enemy y position on screen
jsr load_bank_3_update_nametable_tiles ; draw tile code $10 to nametable at (a, y)
bcs @exit ; exit if unable to update nametable tiles
ldx ENEMY_CURRENT_SLOT ; temporary storage for x register
lda ENEMY_X_POS,x ; load enemy x position on screen
cmp #$2c ; compare to the left 17% of screen
lda #$00 ; a = #$00 (no delay when claw wasn't updated)
bcc @set_anim_delay_exit ; if enemy is too far to the left, exit
clc ; clear carry to indicate success
lda #$02 ; a = #$02 (speed of claw)
@set_anim_delay_exit:
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
@exit:
rts
; pointer table for claw super-tile animation indexes (8 * 2 = 10 bytes)
; see level_7_tile_animation
claw_update_nametable_ptr_tbl:
.addr claw_tile_code_00 ; CPU address $afb2 - ascent
.addr claw_tile_code_01 ; CPU address $afb6 - descent
.addr claw_tile_code_00 ; CPU address $afb2 - ascent
.addr claw_tile_code_01 ; CPU address $afb6 - descent
.addr claw_tile_code_02 ; CPU address $afba - ascent
.addr claw_tile_code_03 ; CPU address $afc2 - descent
.addr claw_tile_code_00 ; CPU address $afb2 - ascent
.addr claw_tile_code_01 ; CPU address $afb6 - descent
; tables for claw tile codes
; see level_7_tile_animation
; this table is different from Trax source
claw_tile_code_00:
.byte $86,$86,$86,$86
claw_tile_code_01:
.byte $80,$80,$82,$84
claw_tile_code_02:
.byte $86,$86,$86,$86
; unused space
; same as claw_tile_code_00
claw_tile_code_unused_00:
.byte $86,$86,$86,$86
claw_tile_code_03:
.byte $80,$80,$80,$80
; unused space
; same as claw_tile_code_01
claw_tile_code_unused_01:
.byte $80,$80,$82,$84
; pointer table for raising spiked wall (enemy type #$11) (#$6 * #$2 = #$c bytes)
rising_spiked_wall_routine_ptr_tbl:
.addr rising_spiked_wall_routine_00 ; CPU address $afd6 - init variables
.addr rising_spiked_wall_routine_01 ; CPU address $b00c - wait for player to get close, and advance routine
.addr rising_spiked_wall_routine_02 ; CPU address $b025 - animate rising wall and configure collision box size
.addr rising_spiked_wall_routine_03 ; CPU address $b200 - ensure scroll up to date
.addr rising_spiked_wall_routine_04 ; CPU address $b087 - destroyed routine, set spiked_wall_destroyed_update_tbl offset, play sound
.addr rising_spiked_wall_routine_05 ; CPU address $b09c - animate wall destruction by updating super-tiles
; init variables
rising_spiked_wall_routine_00:
lda ENEMY_ATTRIBUTES,x ; load the rising spiked wall's attribute
and #$0c ; keep bits 2 and 3, for use in distance trigger
lsr ; only shift once since each entry is #$02 bytes
tay ; transfer offset to y
lda rising_spike_wall_trigger_dist_tbl,y ; load trigger distance
sta ENEMY_VAR_3,x ; store trigger distance
lda rising_spike_wall_trigger_dist_tbl+1,y ; load rising delay timer
sta ENEMY_VAR_4,x ; set rising delay timer
lda ENEMY_ATTRIBUTES,x ; load attributes again
and #$03 ; keep bits 0 and 1
tay ; transfer index to y
lda rising_spike_wall_delay_tbl,y ; load ENEMY_ATTACK_DELAY
sta ENEMY_ATTACK_DELAY,x ; store delay
; used for both rising spiked wall and spiked wall to set collision box index to 16
; this is to specify that the collision box grows upwards in heigh with a fixed width
spiked_wall_set_collision_box:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda #$c0 ; a = #$c0
sta ENEMY_ATTRIBUTES,x ; set negative collision code f values
; and use offset #$10 (16) into collision_code_f_adj_tbl (expand collision box upwards)
advance_spiked_wall_enemy_routine:
jmp advance_enemy_routine
; table for distance of emergence and subsequent delays (#$c bytes)
rising_spike_wall_trigger_dist_tbl:
.byte $30,$00 ; first wall (no delay)
.byte $50,$0f ; distance and delay before second wall
.byte $70,$1e ; distance and delay before third wall
.byte $40,$00
; table for possible delays between emerging steps (#$4 bytes)
; determines emergence speed
rising_spike_wall_delay_tbl:
.byte $0c,$08,$04,$02
; wait for player to get close, and advance routine
rising_spiked_wall_routine_01:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01)
cmp ENEMY_VAR_3,x ; compare the distance of the closest player to the trigger distance before the rising timer will start
bcs rising_spiked_wall_exit ; branch if closest player is farther than ENEMY_VAR_3,x
jsr enable_enemy_collision ; enable bullet-enemy collision and player-enemy collision checks
lda #$06 ; a = #$06
sta ENEMY_VAR_2,x ; set initial offset
lda ENEMY_VAR_4,x ; load the delay timer before the wall starts rising
jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a and advance enemy routine
; animate rising wall
rising_spiked_wall_routine_02:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter
bne dec_delay_enable_set_vel_exit ; if the animation delay hasn't elapsed, decrement and exit
lda ENEMY_VAR_2,x ; animation delay has elapsed, load rising_spiked_wall_data_tbl read offset
asl
adc ENEMY_VAR_2,x ; multiply by 3
tay ; transfer to offset register
lda rising_spiked_wall_data_tbl+1,y ; load nametable super-tile update index
sta $10 ; set nametable super-tile update index
lda rising_spiked_wall_data_tbl+2,y ; load collision box placeholder replacement amount (see collision_code_f_adj_tbl)
sta ENEMY_VAR_1,x ; store collision box placeholder replacement amount (see collision_code_f_adj_tbl)
; since bit 6 of ENEMY_ATTRIBUTES is set, the final value is actually (-1 * ENEMY_VAR_1,x) + #$08
; this value (or its negation) are used to replace placeholder values in collision_code_f_adj_tbl
; to support a dynamic collision box size that can grow upwards with a fixed width
lda rising_spiked_wall_data_tbl,y ; load y position offset
adc ENEMY_Y_POS,x ; add to enemy y position on screen
tay ; transfer result to offset register for load_bank_3_update_nametable_supertile
lda ENEMY_X_POS,x ; load enemy x position on screen
sbc #$0d ; subtract 13 from x position
jsr load_bank_3_update_nametable_supertile ; draw super-tile $10 at position (a,y)
bcs rising_spiked_wall_exit ; exit if unable to draw the rising wall
ldx ENEMY_CURRENT_SLOT ; restore x to rising wall enemy slot index
lda ENEMY_VAR_2,x ; load rising_spiked_wall_data_tbl read offset
cmp #$04 ; compare to the last #$04 super-tiles to draw
bcs @continue ; branch if drawing the first #$03 super-tiles
lda #$00 ; x <= 3, left side of super-tile bg collision (#$00 = empty collision codes)
ldy #$0f ; right side of super-tile bg collision (#$0f = solid collision codes)
jsr set_supertile_bg_collisions ; update bg collision codes for a single super-tile at PPU address $12 (low) $13 (high)
@continue:
lda ENEMY_ATTACK_DELAY,x ; load animation delay
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
dec ENEMY_VAR_2,x ; move to next rising_spiked_wall_data_tbl read offset
bpl rising_spiked_wall_exit ; exit if still more super-tiles to draw
bmi advance_spiked_wall_enemy_routine ; finished animation, move to rising_spiked_wall_routine_03
dec_delay_enable_set_vel_exit:
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
rising_spiked_wall_exit:
rts
; tile codes for raising spiked wall (#$15 bytes)
; byte 0 - enemy y position offset
; byte 1 - super-tile code (see level_7_nametable_update_supertile_data)
; byte 2 - collision box initial configuration (see collision_code_f_base_tbl)
rising_spiked_wall_data_tbl:
.byte $c0,$91,$d0 ; #$11 - rising wall (frame 5) (four rows of spikes visible) (collision box heigh = #$38)
.byte $d0,$91,$e0 ; #$11 - rising wall (frame 5) (four rows of spikes visible) (collision box heigh = #$20)
.byte $e0,$90,$f0 ; #$10 - rising wall (frame 4) (three rows of spikes visible) (collision box heigh = #$18)
.byte $e0,$8f,$f8 ; #$0f - rising wall (frame 3) (two rows of spikes visible) (collision box heigh = #$10)
.byte $f0,$8e,$00 ; #$0e - rising wall (frame 2) (first row of spikes visible) (collision box heigh = #$08)
.byte $f0,$8d,$09 ; #$0d - rising wall (frame 1) (slightly out of ground) (collision box heigh = #$ff)
.byte $f0,$8c,$09 ; #$0c - rising wall (frame 0) (barely out of ground) (collision box heigh = #$ff)
; destroyed routine, set spiked_wall_destroyed_update_tbl offset, play sound
rising_spiked_wall_routine_04:
lda #$00 ; a = #$00
sta ENEMY_VAR_4,x ; set spiked_wall_destroyed_update_tbl offset to #$00
lda #$03 ; a = #$03
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
; spiked wall destroyed routine - play sound advance routine
spiked_wall_routine_02:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda #$24 ; a = #$24 (sound_24)
jsr play_sound ; play explosion sound
jmp advance_enemy_routine ; advance to next routine
; animate wall destruction by updating super-tiles
rising_spiked_wall_routine_05:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda ENEMY_VAR_4,x ; load spiked_wall_destroyed_update_tbl read offset
asl
adc ENEMY_VAR_4,x ; multiple by 3
tay ; transfer to offset register
lda spiked_wall_destroyed_update_tbl+1,y ; load super-tile nametable update index (see level_7_nametable_update_supertile_data)
sta $10 ; store in $10 for load_bank_3_update_nametable_supertile call
lda spiked_wall_destroyed_update_tbl,y ; load relative y position
adc ENEMY_Y_POS,x ; add to enemy y position on screen
sty $f0 ; store read offset in $f0
tay ; transfer result y position to y
lda ENEMY_X_POS,x ; load enemy x position on screen
sbc #$0d ; subtract #$0d from x position
bcc remove_spiked_wall ; if underflow, remove spiked wall (far left of screen)
jsr load_bank_3_update_nametable_supertile ; draw super-tile $10 at position (a,y)
bcs rising_spiked_wall_exit ; exit if unable to draw destroyed spiked wall super-tile
ldx ENEMY_CURRENT_SLOT ; updated super-tile in graphics buffer
; restore rising spiked wall enemy slot index
lda ENEMY_VAR_4,x ; load the current entry in spiked_wall_destroyed_update_tbl
and #$03 ; keep bits .... ..xx
beq @continue ; branch if currently drawing first super-tile
jsr clear_supertile_bg_collision ; set background collision code to #$00 (empty) for a single super-tile at PPU address $12 (low) $13 (high)
@continue:
ldy $f0 ; restore spiked_wall_destroyed_update_tbl read offset
lda spiked_wall_destroyed_update_tbl+2,y ; load relative x position
tay ; set vertical offset from enemy position (param for add_with_enemy_pos)
lda #$fc ; set horizontal offset from enemy position (param for add_with_enemy_pos)
jsr add_with_enemy_pos ; stores absolute screen x position in $09, and y position in $08
jsr create_two_explosion_89 ; create explosion
inc ENEMY_VAR_4,x ; move to next super-tile to draw (higher up on wall)
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne rising_spiked_wall_exit ; exit if animation delay hasn't elapsed
remove_spiked_wall:
jmp remove_enemy ; remove enemy
; spiked walls block indexes after destruction and
; fixed walls of 3 tiles high (#$15 bytes)
; byte 0 - relative y position
; byte 1 - super-tile nametable update index (see level_7_nametable_update_supertile_data)
; byte 2 - relative x position (+#$0d)
spiked_wall_destroyed_update_tbl:
.byte $00,$84,$08 ; #$04 - spiked wall super-tile destroyed floor
.byte $e0,$8b,$f0 ; #$0b - fence
.byte $c0,$8a,$d0 ; #$0a - blank super-tile
.byte $a0,$86,$b0 ; #$06 - tall spiked wall destroyed top (parting hanging from ceiling)
.byte $00,$84,$08 ; #$04 - spiked wall super-tile destroyed floor
.byte $e0,$8b,$f0 ; #$0b - fence
.byte $c0,$86,$d0 ; #$06 - tall spiked wall destroyed top (parting hanging from ceiling)
; pointer table for spiked wall (12) (#$4 * #$2 = #$8 bytes)
spiked_wall_routine_ptr_tbl:
.addr spiked_wall_routine_00 ; CPU address $b103 - initialize collision box and wall destroyed variables
.addr rising_spiked_wall_routine_03 ; CPU address $b200 - ensure scroll up to date
.addr spiked_wall_routine_02 ; CPU address $b091 - spiked wall destroyed routine - play sound advance routine
.addr rising_spiked_wall_routine_05 ; CPU address $b09c - animate wall destruction by updating super-tiles
; initialize collision box and wall destroyed variables, advance routine
spiked_wall_routine_00:
lda #$b8 ; a = #$b8
sta ENEMY_VAR_1,x ; dynamic collision box height = #$50 ((-1 * #$b8) + #$08))
ldy ENEMY_ATTRIBUTES,x ; load enemy attributes
lda spiked_wall_destroyed_data_tbl,y ; load spiked_wall_destroyed_update_tbl offset
sta ENEMY_VAR_4,x ; set spiked_wall_destroyed_update_tbl offset
lda spiked_wall_destroyed_data_tbl+1,y ; load wall-destroyed enemy animation delay timer
sta ENEMY_ANIMATION_DELAY,x ; set wall-destroyed enemy animation delay timer
jmp spiked_wall_set_collision_box ; set collision code f dynamic mode to #$10 (grow upwards)
; and advance routine
; table for spiked wall routine (#$4 bytes)
; byte 0 - initial spiked_wall_destroyed_update_tbl offset
; byte 1 - wall destroyed enemy animation delay timer (timer before wall is removed after explosion)
spiked_wall_destroyed_data_tbl:
.byte $04,$03 ; half-screen wall on top half of screen
.byte $00,$04 ; full screen wall
; pointer table for cart generator (13) (#$2 * #$2 = #$4 bytes)
mine_cart_generator_routine_ptr_tbl:
.addr mine_cart_generator_routine_00 ; CPU address $b122
.addr mine_cart_generator_routine_01 ; CPU address $b12c
mine_cart_generator_routine_00:
lda #$80 ; a = #$80
sta ENEMY_FRAME,x ; set generated cart slot number to signify no cart generated
lda #$01 ; a = #$01
jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a; advance enemy routine
mine_cart_generator_routine_01:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda ENEMY_FRAME,x ; load generated cart's slot number, #$80 means no cart generated
bpl @check_generated_cart_status ; if generated cart exists, see how it is doing. did it get destroyed yet
dec ENEMY_ANIMATION_DELAY,x ; no cart generated, decrement enemy animation frame delay counter
bne cart_routine_exit ; exit if delay timer hasn't elapsed
inc ENEMY_ANIMATION_DELAY,x ; enemy animation frame delay counter
jsr find_next_enemy_slot ; find next available enemy slot, put result in x register
bne @exit ; exit when no enemy slot available
lda #$14 ; a = #$14 (14 = moving cart)
sta ENEMY_TYPE,x ; set current enemy type to moving cart
jsr initialize_enemy ; generate enemy
lda #$f8 ; a = #$f8
sta ENEMY_X_POS,x ; set enemy x position on screen to #$f8
lda #$ff ; a = #$ff
sta ENEMY_X_VELOCITY_FAST,x ; set to move 1 unit left every frame
lda #$02 ; a = #$02
sta ENEMY_VAR_4,x ; set cart direction to left
lda #$80 ; a = #$80
sta ENEMY_ATTRIBUTES,x ; specify the cart should blow up upon collision with background
jsr init_cart_vel_and_y_pos ; set cart initial velocity and y position
txa
ldx ENEMY_CURRENT_SLOT
sta ENEMY_FRAME,x ; set enemy animation frame number
@exit:
ldx ENEMY_CURRENT_SLOT
rts
; see status of generated cart, if no longer active, set to generate new cart after delay
@check_generated_cart_status:
ldy ENEMY_FRAME,x ; load generated cart's slot number
lda ENEMY_ROUTINE,y ; load current routine index for generated mining cart
bne cart_routine_exit ; generated cart has not been destroyed, exit
lda #$80 ; generated cart has been destroyed, start logic to generate a new cart
sta ENEMY_FRAME,x ; set generated cart slot number to signify no cart generated
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
cart_routine_exit:
rts
; pointer table for moving cart (14) (#$6 * #$2 = #$c bytes)
moving_cart_routine_ptr_tbl:
.addr moving_cart_routine_00 ; CPU address $b186
.addr moving_cart_routine_00 ; CPU address $b186
.addr moving_cart_routine_00 ; CPU address $b186
.addr enemy_routine_init_explosion ; CPU address $e74b from bank 7
.addr enemy_routine_explosion ; CPU address $e7b0 from bank 7
.addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7
moving_cart_routine_00:
lda FRAME_COUNTER ; load frame counter
lsr
lsr
and #$01 ; every 8 frames alternate sprite code to show wheel moving animation
clc ; clear carry in preparation for addition
adc #$2a ; either sprite code #$2a or #$2b depending if big 2 of FRAME_COUNTER is 1 or not
sta ENEMY_SPRITES,x ; save mine cart sprite to enemy sprite buffer
jsr update_enemy_pos ; apply velocities and scrolling adjust
ldy ENEMY_VAR_4,x ; cart direction (0 = right, 2 = left)
lda ENEMY_X_POS,x ; load enemy x position on screen
clc ; clear carry in preparation for addition
adc cart_collision_config_tbl+1,y ; load x offset for bg collision check depending on cart direction
sta $00 ; store in variable used in get_cart_bg_collision as x position
lda #$00 ; a = #$00
adc cart_collision_config_tbl,y ; add XOR value used in bg collision depending on cart direction
; note carry can be set from previous addition
sta $10 ; XOR for use in bg collision
lda $00 ; sprite x position
ldy ENEMY_Y_POS,x ; enemy y position on screen
jsr get_cart_bg_collision ; get enemy background collision
bne cart_bg_collision ; branch if cart has collided with anything (floor (#$01), water (#$02), or a solid object (#$80))
lda #$00 ; a = #$00
ldy #$09 ; y = #$09
jsr add_a_y_to_enemy_pos_get_bg_collision ; add a (#$00) to X position and y (#$09) to Y position; get bg collision code
bne cart_routine_exit ; exit if no collision
lda #$20 ; a = #$20 (gravity for cart)
jmp add_a_to_enemy_y_fract_vel ; add a to enemy y fractional velocity
; cart has collided with something, reverse direction
cart_bg_collision:
lda ENEMY_ATTRIBUTES,x ; generated moving carts explode on impact, but immobile carts will reverse direction
bmi set_cart_explosion ; whether or not the cart should turn around or explode upon collision
lda ENEMY_VAR_4,x ; load cart direction variable
eor #$02 ; swap cart direction by flipping bit 1 (0 = right, 2 = left)
sta ENEMY_VAR_4,x ; set new cart direction
jmp reverse_enemy_x_direction ; reverse x direction
; move to enemy_routine_init_explosion routine
set_cart_explosion:
lda #$04 ; a = #$04
jmp set_enemy_routine_to_a ; set enemy routine index to a
; byte 0 is the XOR value used to help level_screen_mem_offset_tbl_01 lookup ($10)
; byte 1 is the X offset from cart X position for use in collision detection
cart_collision_config_tbl:
.byte $00,$0f
.byte $ff,$f1
; pointer table for immobile cart (15), can start rolling when player lands on it (#$6 * #$2 = #$c bytes)
immobile_cart_generator_routine_ptr_tbl:
.addr immobile_cart_generator_routine_00 ; CPU address $b1e5
.addr immobile_cart_generator_routine_01 ; CPU address $b1fb
.addr moving_cart_routine_00 ; CPU address $b186
.addr enemy_routine_init_explosion ; CPU address $e74b from bank 7
.addr enemy_routine_explosion ; CPU address $e7b0 from bank 7
.addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7
; initialize x velocity, sprite, and y pos on screen
immobile_cart_generator_routine_00:
lda #$c0 ; a = #$c0
jsr init_cart_vel_and_y_pos ; set x velocity of cart when stepped on to #$c0
cart_advance_enemy_routine:
jmp advance_enemy_routine ; advance to next routine
init_cart_vel_and_y_pos:
sta ENEMY_X_VELOCITY_FRACT,x ; set x fractional velocity to a
lda #$2a ; a = #$2a (mining cart sprite code)
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
lda #$c8 ; a = #$c8
sta ENEMY_Y_POS,x ; enemy y position on screen
rts
immobile_cart_generator_routine_01:
lda ENEMY_FRAME,x ; load enemy animation frame number (set to #01 when @land_on_enemy executes)
bne cart_advance_enemy_routine ; player has landed on cart, start moving cart (moving_cart_routine_00)
; ensure scroll up to date
rising_spiked_wall_routine_03:
jmp add_scroll_to_enemy_pos ; add scrolling to enemy position
; pointer table for jungle armored door (level 7) (16) (#$7 * #$2 = #$e bytes)
boss_door_routine_ptr_tbl:
.addr boss_door_routine_00 ; CPU address $b211
.addr boss_door_routine_01 ; CPU address $b219
.addr boss_door_routine_02 ; CPU address $b228
.addr boss_defeated_routine ; CPU address $e740 from bank 7
.addr enemy_routine_explosion ; CPU address $e7b0 from bank 7
.addr boss_door_routine_05 ; CPU address $b240 - set tile sprite code to #$00, advance routine, add #$20 to y position
.addr boss_door_routine_06 ; CPU address $b248
; armored door - pointer 0
boss_door_routine_00:
lda #$1b ; a = #$1b (sound_1b)
jsr play_sound ; play level 1 jungle boss siren sound
jmp advance_enemy_routine ; advance to next routine
; armored door - pointer 1
boss_door_routine_01:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda ENEMY_HP,x ; load enemy hp
cmp #$05 ; stop spawning enemies when hp < 5
bcs @exit
lda #$02 ; a = #$02
sta BOSS_SCREEN_ENEMIES_DESTROYED ; set number of mortar launchers to be destroyed before soldiers stop being generated
@exit:
rts
; armored door - pointer 2
boss_door_routine_02:
lda ENEMY_VAR_1,x
bne @continue
lda #$08 ; a = #$08
jsr add_a_to_enemy_x_pos ; add #$08 to enemy x position on screen
inc ENEMY_VAR_1,x
@continue:
lda #$05 ; a = #$05
jsr draw_enemy_supertile_a ; draw super-tile a at position (ENEMY_X_POS, ENEMY_Y_POS)
bcc boss_door_adv_enemy_routine
boss_door_exit:
rts
boss_door_adv_enemy_routine:
jmp advance_enemy_routine ; advance to next routine
; set tile sprite code to #$00, advance routine, add #$20 to y position
boss_door_routine_05:
jsr shared_enemy_routine_clear_sprite ; set tile sprite code to #$00 and advance routine
boss_door_add_20_to_y_pos:
lda #$20 ; a = #$20
jmp add_a_to_enemy_y_pos ; add a to enemy y position on screen
boss_door_routine_06:
ldy ENEMY_VAR_2,x
lda boss_door_update_supertile_tbl,y ; load super tile to draw (offset into level_xx_nametable_update_supertile_data)
jsr draw_enemy_supertile_a ; draw super-tile a at position (ENEMY_X_POS, ENEMY_Y_POS)
bcs boss_door_exit
lda #$05 ; left side of super-tile bg collision (#$05 = ground collision codes)
ldy #$05 ; right side of super-tile bg collision (#$05 = ground collision codes)
jsr set_supertile_bg_collisions ; update bg collision codes for a single super-tile at PPU address $12 (low) $13 (high)
jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position
jsr create_two_explosion_89 ; create explosion #$89 at location ($09, $08)
jsr boss_door_add_20_to_y_pos
inc ENEMY_VAR_2,x
lda ENEMY_VAR_2,x
cmp #$02
bcc boss_door_exit
lda #$80 ; a = #$80 (delay before auto-move)
jmp set_delay_remove_enemy
; super-tile codes for destroyed door (#$2 bytes)
boss_door_update_supertile_tbl:
.byte $08,$04
; pointer table for hangar boss mortar launcher (17) (#$8 * #$2 = #$10 bytes)
boss_mortar_routine_ptr_tbl:
.addr boss_mortar_routine_00 ; CPU address $b284 - offset y position, set default aim direction, set initial delay, advance routine
.addr boss_mortar_routine_01 ; CPU address $b29a - wait for auto scroll, wait for animation delay, update nametable tiles, set delay, increment frame, advance routine if firing
.addr boss_mortar_routine_02 ; CPU address $b2c3 - fire once animation delay has elapsed
.addr boss_mortar_routine_03 ; CPU address $b2ef - animate closing of mortar launcher, set routine to boss_mortar_routine_01
.addr boss_mortar_routine_04 ; CPU address $b30f - draw destroyed nametable tiles, advance routine
.addr enemy_routine_init_explosion ; CPU address $e74b from bank 7
.addr enemy_routine_explosion ; CPU address $e7b0 from bank 7
.addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7
; offset y position, set default aim direction, set initial delay, advance routine
boss_mortar_routine_00:
lda #$04 ; a = #$04 to offset y position a little bit
jsr add_a_to_enemy_y_pos ; add #$04 to enemy y position on screen
lda #$04 ; a = #$04
sta ENEMY_VAR_1,x ; default mortar shot direction (see mortar_shot_velocity_tbl)
lda ENEMY_ATTRIBUTES,x ; load enemy attributes
lsr ; shift initial attack delay flag to carry
lda #$60 ; a = #$60 (initial delay)
bcc @set_delay_adv_routine ; continue with #$60 delay if ENEMY_ATTRIBUTES bit 0 is 0
lda #$10 ; ENEMY_ATTRIBUTES bit 0 is 1, use #$10 delay
@set_delay_adv_routine:
bne mortar_set_delay_adv_routine
; wait for auto scroll, wait for animation delay, update nametable tiles, set delay, increment frame, advance routine if firing
boss_mortar_routine_01:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda BOSS_AUTO_SCROLL_COMPLETE ; see if boss reveal auto-scroll has completed
beq @exit ; exit if boss auto scroll hasn't completed
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne @exit ; exit if animation delay hasn't elapsed
jsr boss_mortar_update_tiles ; update nametable tiles based on enemy frame
bcs @exit ; exit if unable to update the nametable tiles
lda ENEMY_FRAME,x ; load enemy animation frame number
cmp #$02 ; see if completed opening animation and should fire
bcs @enable_adv_routine ; if completed opening, branch to enable collision detection and advance to boss_mortar_routine_02
inc ENEMY_FRAME,x ; increment enemy animation frame number
@exit:
rts
; enable collision detection and advance to boss_mortar_routine_02
@enable_adv_routine:
jsr enable_enemy_collision ; enable bullet-enemy collision and player-enemy collision checks
lda #$10 ; a = #$10 (delay between opening and attack)
sta ENEMY_ATTACK_DELAY,x ; set delay between attacks
lda #$60 ; a = #$60 (total delay for open state)
mortar_set_delay_adv_routine:
jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a; advance enemy routine
; fire mortar after delay, advance routine when animation delay elapsed
boss_mortar_routine_02:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
dec ENEMY_ANIMATION_DELAY,x ; open launcher timer
beq @disable_collision_adv_routine ; if timer elapsed, disable collision and go to boss_mortar_routine_03 to begin closing
dec ENEMY_ATTACK_DELAY,x ; animation delay hasn't elapsed, decrement delay between attacks
bne @exit ; exit if attack delay hasn't elapsed
lda #$0b ; a = #$0b (0b = mortar shot)
jsr generate_enemy_a ; generate #$0b enemy (mortar shot)
bne @exit ; exit if unable to generate mortar shot
lda ENEMY_VAR_1,x ; load launcher's current aim direction [#$01-#$04]
sta ENEMY_VAR_1,y ; set mortar shot initial velocities index (aim direction)
dec ENEMY_VAR_1,x ; decrement launcher's current aim direction
bne @exit ; exit if still a valid velocity index (aim dir)
lda #$04 ; wrapped around, reset aim direction to #$04
sta ENEMY_VAR_1,x ; set next firing round's aim direction
@exit:
rts
@disable_collision_adv_routine:
jsr disable_enemy_collision ; prevent player enemy collision check and allow bullets to pass through enemy
lda #$01 ; a = #$01
bne mortar_set_delay_adv_routine ; set delay and set routine to boss_mortar_routine_02
; animate closing of mortar launcher, set routine to boss_mortar_routine_01
boss_mortar_routine_03:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne mortar_exit ; exit if animation delay hasn't elapsed
jsr boss_mortar_update_tiles ; update mortar nametable tiles based on ENEMY_FRAME
bcs mortar_exit ; exit if unable to update the nametable tiles
lda ENEMY_FRAME,x ; load enemy animation frame number
beq boss_mortar_set_routine_01 ; set #$a0 delay and set enemy routine to boss_mortar_routine_01
dec ENEMY_FRAME,x ; decrement enemy animation frame number
mortar_exit:
rts
boss_mortar_set_routine_01:
lda #$a0 ; a = #$a0 (delay between mortar shots)
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
lda #$02 ; a = #$02
jmp set_enemy_routine_to_a ; set enemy routine index to boss_mortar_routine_01
; draw destroyed nametable tiles, advance routine
boss_mortar_routine_04:
lda #$03 ; a = #$03 (8b mortar launcher - destroyed)
jsr boss_mortar_update_tiles_a ; set nametable tiles to show a destroyed mortar launcher
bcs mortar_exit ; exit if unable to update the nametable tiles
inc BOSS_SCREEN_ENEMIES_DESTROYED ; increment number of destroyed mortar launchers
; once both are destroyed soldiers stop being generated
jmp advance_enemy_routine ; advance to enemy_routine_init_explosion
; update nametable tiles based on ENEMY_FRAME
boss_mortar_update_tiles:
lda ENEMY_FRAME,x ; load enemy animation frame number
; update nametable tiles based on a register
boss_mortar_update_tiles_a:
clc ; clear carry in preparation for addition
adc #$08 ; convert to real offset into level_7_tile_animation
jsr update_enemy_nametable_tiles_no_palette ; draw the nametable tiles from level_7_tile_animation (a) at the enemy position
lda #$01 ; a = #$01
bcs @set_delay_exit ; exit if unable to update nametable tiles
lda #$08 ; a = #$08 (delay between door opening frames)
@set_delay_exit:
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
rts
; pointer table for hangar boss screen enemy generator door (enemy type = #$18) (#$5 * #$2 = #$a bytes)
boss_soldier_generator_routine_ptr_tbl:
.addr boss_soldier_generator_routine_00 ; CPU address $b338 - set delay to #$a0, advance routine
.addr boss_soldier_generator_routine_01 ; CPU address $b33c - animate door and, if appropriate, prep to generate soldiers before advancing the routine
.addr boss_soldier_generator_routine_02 ; CPU address $b38f - generate soldiers
.addr boss_soldier_generator_routine_03 ; CPU address $b3d0 - close door, set delay for next time to open, set routine to boss_soldier_generator_routine_01
.addr boss_soldier_generator_routine_04 ; CPU address $b3f5 - enemy destroyed routine, remove enemy
; set delay to #$a0, advance routine
boss_soldier_generator_routine_00:
lda #$a0 ; a = #$a0
bne boss_soldier_generator_adv_routine ; initial delay before first attack
; animate door and, if appropriate, prep to generate soldiers before advancing the routine
boss_soldier_generator_routine_01:
lda ENEMY_VAR_3,x ; load number of waves of soldiers generated
cmp #$1e ; see if 30 waves of soldiers have been generated
bcs @stop_soldier_gen ; branch to stop generating soldiers if 30 waves have occurred
lda BOSS_SCREEN_ENEMIES_DESTROYED ; load how many mortar launchers have been destroyed
cmp #$02 ; see if both mortar launchers have been destroyed
bcc @draw_door_check_if_gen_soldiers ; branch if at least one mortar launcher not destroyed
; updates door tiles, and see if should generate soldiers
; sets an animation delay so that boss_soldier_draw_door exits early and no soldier is generated
; soldiers aren't generated when there have been #$1e waves, or both mortar launchers have been destroyed
@stop_soldier_gen:
lda #$f0 ; a = #$f0
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
@draw_door_check_if_gen_soldiers:
jsr boss_soldier_draw_door ; draw the appropriate super-tiles for the armored door based on ENEMY_FRAME
bcs @exit ; branch if unable update the super-tiles
lda ENEMY_FRAME,x ; load enemy animation frame number
cmp #$02 ; see if last frame of animation, i.e. the door is open
bcs @init_gen_soldiers ; if open, determine from which side and how many soldiers to generate, advance routine
inc ENEMY_FRAME,x ; still opening door, increment enemy animation frame number
@exit:
rts
@init_gen_soldiers:
inc ENEMY_VAR_3,x ; increment number of waves of soldiers generated
ldy #$00 ; y = #$00
lda SPRITE_X_POS ; load player 1 x position
cmp #$a0 ; see if close to the door on the right
bcs @gen_from_left ; branch if close to the door on the right
lda SPRITE_X_POS+1 ; load if player 2 x position
cmp #$a0 ; see if close to the door on the right
bcc @prep_soldiers_to_gen_adv_routine ; branch if player 2 is not close to the door on the right
@gen_from_left:
iny ; increment facing direction so it's 1 (face right, attack from left)
@prep_soldiers_to_gen_adv_routine:
tya ; transfer soldier attack direction (0 = left, 1 = right) to a
sta ENEMY_VAR_2,x ; set soldier facing direction (0 = left, 1 = right)
lda RANDOM_NUM ; load random number
and #$03 ; keep bits 0 and 1
tay ; transfer to offset register
lda boss_soldier_num_soldiers_tbl,y ; load random number of soldiers to generate
sta ENEMY_VAR_1,x ; set the number of soldiers to generate
lda #$10 ; a = #$10 (delay between door open and attack)
sta ENEMY_ATTACK_DELAY,x ; set delay between attacks
lda #$80 ; a = #$80 (delay for door staying open)
boss_soldier_generator_adv_routine:
jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a and set routine to boss_soldier_generator_routine_02
; table for number of enemies generated (#$4 bytes)
boss_soldier_num_soldiers_tbl:
.byte $03,$04,$02,$04
; generate soldiers
boss_soldier_generator_routine_02:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
beq @set_delay_adv_routine ; if door open timer has elapsed, advance routine
dec ENEMY_ATTACK_DELAY,x ; decrement delay between attacks
bne @exit
lda #$05 ; a = #$05 (05 = running man)
sta $0a ; set type of generated enemy
ldy #$00 ; y = #$00
lda #$f8 ; a = #$f8
jsr generate_enemy_at_pos ; generate enemy type $0a at relative position a,y
lda ENEMY_VAR_2,x ; load soldier facing direction
bne @set_soldier_attr_delay_exit ; exit if soldiers will spawn from the left (facing right)
lda ENEMY_VAR_3,x ; soldiers to come from right, load the number of waves of attack
cmp #$14 ; see if there have been 20 waves of attack
bcs @init_random_soldier_exit ; branch if there have been 20 rounds of attacks to randomize soldier spawn direction
lda BOSS_SCREEN_ENEMIES_DESTROYED ; load how many mortar launchers have been destroyed
beq @set_soldier_attr_delay_exit ; branch if no mortar launchers have been destroyed
; soldiers will be generated from a random direction if
; there have been #$14 (20) waves of attack or if one of the two mortar launchers have been destroyed
@init_random_soldier_exit:
lda RANDOM_NUM ; load random number
lsr ; shift bit 0 to carry (!(WHY?) not sure why needed, still 50% odds)
and #$01 ; randomly decide the soldier's running direction
@set_soldier_attr_delay_exit:
sta ENEMY_ATTRIBUTES,y ; set soldier enemy attributes (facing direction)
; specifies which side of the screen the soldier spawns from
lda #$10 ; a = #$10 (delay between generated enemies)
dec ENEMY_VAR_1,x ; decrement number of soldiers to generate
bne @set_attack_delay_exit ; exit with #$10 attack delay if more soldiers to generate
lda #$ff ; no more soldiers to generate, exit with #$ff attack delay
@set_attack_delay_exit:
sta ENEMY_ATTACK_DELAY,x ; set delay between attacks
@exit:
rts
@set_delay_adv_routine:
lda #$01 ; a = #$01
bne boss_soldier_generator_adv_routine ; set routine to boss_soldier_generator_routine_03
; close door, set delay for next time to open, set routine to boss_soldier_generator_routine_01
boss_soldier_generator_routine_03:
jsr boss_soldier_draw_door ; draw the appropriate super-tiles for the armored door based on ENEMY_FRAME
bcs @exit ; exit if unable to update the door tiles
lda ENEMY_FRAME,x ; load enemy animation frame number
beq @door_closed ; branch if door fully closed
dec ENEMY_FRAME,x ; decrement enemy animation frame number to continue closing door
@exit:
rts
@door_closed:
lda RANDOM_NUM ; load random number
lsr
lsr
lsr ; !(WHY?) not sure why needed if number is random
and #$03 ; keep bits .... ..xx
tay ; transfer random number to offset register
lda boos_soldier_door_open_delay_tbl,y ; load delay for opening door back up
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
lda #$02 ; a = #$02
jmp set_enemy_routine_to_a ; set enemy routine index to boss_soldier_generator_routine_01
; possible delays between door openings (#$4 bytes)
boos_soldier_door_open_delay_tbl:
.byte $f0,$80,$a0,$c0
; enemy destroyed routine, remove enemy
boss_soldier_generator_routine_04:
jmp remove_enemy ; remove enemy
; draw the appropriate super-tiles for the armored door based on ENEMY_FRAME
; output
; * carry flag - clear when successful, set when CPU_GRAPHICS_BUFFER is full
boss_soldier_draw_door:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne @set_carry_exit ; exit if the animation delay hasn't elapsed
lda ENEMY_FRAME,x ; load enemy animation frame number
asl ; double since each entry is #$02 bytes
tay ; transfer to offset register
lda boss_soldier_nametable_update_tbl,y ; load first nametable update super-tile index
sta $10 ; store in $10 for update_2_enemy_supertiles
lda boss_soldier_nametable_update_tbl+1,y ; load second nametable update super-tile index
ldy #$00 ; y = #$00
jsr update_2_enemy_supertiles ; draw nametable update super-tile $10, then a at enemy position
lda #$08 ; a = #$08
bcc @set_delay_exit
lda #$01 ; a = #$01
@set_delay_exit:
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
rts
@set_carry_exit:
sec ; set carry flag
rts
; table for door nametable update super-tile index (#$6 bytes)
; index into level_7_nametable_update_supertile_data
boss_soldier_nametable_update_tbl:
.byte $03,$12 ; closed armored door
.byte $13,$14 ; partially open armored door
.byte $07,$15 ; opened armored door
; pointer table for alien guardian (#$d * #$2 = #$1a bytes)
alien_guardian_routine_ptr_tbl:
.addr alien_guardian_routine_00 ; CPU address $b44b - set enemy variables for super-tiles, hp and delays, advance routine
.addr alien_guardian_routine_01 ; CPU address $b47c - repeatedly open and close mouth
.addr alien_guardian_routine_02 ; CPU address $b545 - generates alien fetuses (enemy type #$11)
.addr alien_guardian_routine_03 ; CPU address $b69b - play alien guardian destroyed sound, create initial explosion
.addr alien_guardian_routine_04 ; CPU address $b6b2 - create series of explosions, each with a #$05 frame delay before next explosion
.addr alien_guardian_routine_05 ; CPU address $b572 - blank super-tiles for lower jaw
.addr alien_guardian_routine_06 ; CPU address $b5d8 - blank top jaw first call, second call will blank body portion
.addr alien_guardian_routine_07 ; CPU address $b601 - draws the destroyed alien guardian body super-tiles
.addr alien_guardian_routine_08 ; CPU address $b623 - blank more of the alien guardian body
.addr alien_guardian_routine_09 ; CPU address $b643 - destroys wall in front of alien guardian
.addr alien_guardian_routine_0a ; CPU address $b676 - remove lowest part of wall's collision and set floor for where wall was
.addr alien_guardian_routine_0b ; CPU address $bd02 - destroy all enemies
.addr remove_enemy ; CPU address $e809 from bank 7
; determine the game completion count multiplied by #$10
; output
; * $07 - GAME_COMPLETION_COUNT * #$10
set_game_completion_10x:
lda GAME_COMPLETION_COUNT ; load the number of times the game has been completed
jsr mv_low_nibble_to_high ; move low nibble of GAME_COMPLETION_COUNT to high nibble
sta $07 ; set result in $07
rts
; shift low nibble to high nibble, e.g. %01101100 -> %11000000
mv_low_nibble_to_high:
asl
asl
asl
asl
rts
; set enemy variables for super-tiles, hp, and delays, advance routine
alien_guardian_routine_00:
jsr set_guardian_and_heart_enemy_hp ; calculate and set alien guardian's ENEMY_HP
lda #$20 ; a = #$20
sta ENEMY_VAR_4,x ; delay between mouth movements
lda #$90 ; specify animated super-tiles for alien guardian
; #$90 -> super-tiles offset #$10, #$11 and #$12 (mouth closed) (see level_8_nametable_update_supertile_data)
sta ENEMY_VAR_1,x ; set super-tile code for drawing a closed mouth
lda #$40 ; set timer to #$40 to complete showing alien guardian with auto scroll
sta AUTO_SCROLL_TIMER_01 ; auto scroll counter
lda #$03 ; a = #$03
jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to #$03; advance enemy routine
; called to calculate alien guardian and heart's ENEMY_HP
; (weapon strength code * #$10) + #$37 + (completion count * #$10)
; if the result is >= #$a0, ENEMY_HP is set to #$a0
set_guardian_and_heart_enemy_hp:
jsr set_game_completion_10x ; $07 = #$10 * GAME_COMPLETION_COUNT
lda PLAYER_WEAPON_STRENGTH ; load player's weapon strength
jsr mv_low_nibble_to_high ; move low nibble into high nibble, setting low nibble to all 0, i.e. PLAYER_WEAPON_STRENGTH * #$10
clc ; clear carry in preparation for addition
adc #$37 ; add #$37 to a
bcs @set_max_hp ; if this caused an overflow set HP to #$a0
adc $07 ; no overflow occurred, add (#$10 * GAME_COMPLETION_COUNT)
bcs @set_max_hp ; if this caused an overflow set HP to #$a0
cmp #$a0 ; see if sum is larger than #$a0
bcc @set_enemy_hp ; branch if sum is smaller than #$a0 to set sum to result; otherwise use max HP of #$a0
@set_max_hp:
lda #$a0 ; a = #$a0 (max hp)
@set_enemy_hp:
sta ENEMY_HP,x ; set enemy hp
rts
; repeatedly open and close mouth
alien_guardian_routine_01:
lda ENEMY_X_POS,x ; load enemy x position on screen, decremented as frame scrolls right
cmp #$50 ; see if enemy is in the right 70% of the screen
bcs @continue ; branch if alien guardian is in the right 70% of the screen
; !(WHY?) not sure of use of this check, player cannot cause alien guardian to scroll
; this far due to wall in the way. Possibly wall wasn't originally there
jmp add_scroll_to_enemy_pos ; add scrolling to enemy position (doesn't occur in normal game play)
@continue:
lda ENEMY_VAR_2,x ; load whether or not the drawing routine was successful for top jaw (0 = success, 1 = failure)
beq @update_nametable_adv_routine ; branch if successfully drew top jaw
jsr draw_alien_guardian_top_jaw ; update nametable to draw top 2 super-tiles specified by ENEMY_VAR_1
@update_nametable_adv_routine:
lda ENEMY_VAR_3,x ; load whether or not the drawing routine was successful for bottom jaw (0 = success, 1 = failure)
beq @draw_mouth_adv_routine ; branch if successfully drew bottom jaw
jsr draw_alien_guardian_lower_jaw ; update nametable to animate the bottom super-tile of the mouth
; draw top and bottom of mouth
@draw_mouth_adv_routine:
dec ENEMY_VAR_4,x ; decrement delay alien guardian between mouth animation
bne @draw_lower_jaw_adv_routine ; branch if delay hasn't elapsed
lda #$20 ; a = #$20
sta ENEMY_VAR_4,x ; delay between mouth movements
lda ENEMY_VAR_1,x ; load the top-right super-tile code
cmp #$90 ; see if it is super-tile code #$10 (alien guardian jaw mouth closed)
bne @draw_alien_guardian_closed_mouth ; if not, then set it to closed and draw mouth closed
lda #$92 ; a = #$92
sta ENEMY_VAR_1,x ; super-tile code for open mouth
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
jmp @draw_alien_guardian_mouth
@draw_alien_guardian_closed_mouth:
lda #$90 ; a = #$90
sta ENEMY_VAR_1,x ; #$10 -> super-tile code for closed mouth
; input
; * ENEMY_VAR_1 - #$90 for an open mouth, #$92 for a closed mouth
@draw_alien_guardian_mouth:
jsr draw_alien_guardian_top_jaw ; update nametable to draw top 2 super-tiles specified by ENEMY_VAR_1
jsr draw_alien_guardian_lower_jaw ; update nametable to animate the bottom super-tile of the mouth
@draw_lower_jaw_adv_routine:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter
bne @exit
jmp draw_lower_jaw_open_adv_routine ; update nametable to animate the bottom super-tile of the mouth
; advance enemy routine to alien_guardian_routine_02
@exit:
rts
; draw super-tile $08 at position ($0a - #$0e, $09 - #$10)
; input
; * $08 - super-tile code to draw
; * $09 - y position of the super-tile to draw (#$10 is subtracted from this point)
; * $0a - x position of the super-tile to draw (#$0e is subtracted rom this point)
; output
; * $0b - clear when successful, set when CPU_GRAPHICS_BUFFER is full
draw_alien_guardian_supertile:
stx ENEMY_CURRENT_SLOT ; temporarily save value of x
lda $08 ; load super-tile number to draw
sta $10 ; store in $10 for load_bank_3_update_nametable_supertile call
lda $09 ; load tile relative y position
sec ; set carry flag in preparation for subtraction
sbc #$10 ; subtract #$10 from y position
tay ; transfer to y position input for load_bank_3_update_nametable_supertile
lda $0a ; load x position into nametable
sec ; set carry flag in preparation for subtraction
sbc #$0e ; subtract #$0e
jsr load_bank_3_update_nametable_supertile ; draw super-tile $10 at position (a,y)
ldx ENEMY_CURRENT_SLOT ; restore value of x
lda #$00 ; a = #$00
rol ; move carry result from load_bank_3_update_nametable_supertile
sta $0b ; set whether call to update nametable was successful (0 success, 1 failure)
rts
; draws specified ENEMY_VAR_1 super-tile and its subsequent value for the top two super-tiles
; of the alien guardian mouth
draw_alien_guardian_top_jaw:
lda ENEMY_VAR_1,x ; load super-tile code for alien guardian
sta $08 ; store top-right super-tile code in $08
clc ; clear carry in preparation for addition
adc #$01
sta $0c ; set the top-left jaw super-tile to draw
lda #$10 ; a = #$10
jsr set_nametable_x_pos_for_alien_guardian ; add #$10 to enemy x position and stores result in $0a
jsr draw_alien_boss_supertiles ; draw top 2 super-tiles of alien guardian jaw ($08, $0c)
lda $0b ; load whether or not the top jaw was drawn (0 = success, 1 = failure)
sta ENEMY_VAR_2,x ; set whether or not the supertile was updated for the top jaw (0 = success, 1 = failure)
rts
; draws either a blank super-tile (mouth closed), or the lower jaw of alien guardian (mouth open)
; based on ENEMY_VAR_1
; this is the bottom right of the 3 super-tiles that are animated for the alien guardian
draw_alien_guardian_lower_jaw:
lda ENEMY_VAR_1,x ; load the super-tile code for the top right, use to determine bottom super-tile code
cmp #$92 ; super-tile #$14 (alien guardian lower jaw mouth open)
beq @continue
lda #$81 ; super-tile #$03 (blank super-tile used to clear before drawing new super-tile)
@continue:
clc ; clear carry in preparation for addition
adc #$02 ; add #$02 to super-tile index (level_8_nametable_update_supertile_data)
sta $08 ; set super-tile to draw
ldy #$20 ; y = #$20
lda #$11 ; a = #$11
jsr set_nametable_pos_for_alien_guardian ; add #$11 to enemy x position and #$20 to enemy y position. store result x in $0a, y in $09
jsr draw_alien_guardian_supertile ; draw super-tile $08 at position ($0a - #$0e, $09 - #$10)
lda $0b ; set whether or not the supertile was updated for the bottom jaw (0 = success, 1 = failure)
sta ENEMY_VAR_3,x ; store in ENEMY_VAR_3
rts
; updates 2 super-tiles (#$20 tiles apart) for both alien guardian and alien heart
; draws the top 2 super-tiles of the 3 super-tiles that are animated for the alien guardian
; all super-tiles are indexes into level_8_nametable_update_supertile_data
; top left super-tiles
; * #$11 - alien guardian top teeth and lower left jaw mouth closed
; * #$13 - alien guardian top teeth mouth open
; top right super-tiles
; * #$10 - alien guardian jaw mouth closed
; * #$12 - alien guardian jaw mouth open
; input
; * $09 - relative y position of super-tiles to draw
; * $0a - relative x position of the top-right super tile to draw
; * $08 - top right super-tile to draw
; * $0c - top left super-tile to draw
; output
; * $0b - clear when successful, set when CPU_GRAPHICS_BUFFER is full
draw_alien_boss_supertiles:
lda $09 ; load relative y position of tile
sta $05 ; store in $05 as backup
lda $0a ; load relative x position of tile
sta $06 ; store in $06 as backup
lda $0c ; load top right super tile code
sta $04 ; store in $04
jsr draw_alien_guardian_supertile ; draw top right super-tile $08 at position ($0a - #$0e, $09 - #$10)
lda $04 ; restore value of $0c (top right super-tile code)
sta $08 ; set as super-tile code to draw
lda $05 ; load saved relative y position of super-tile to draw
sta $09 ; set as relative y position of super-tile to draw
lda $06 ; load relative x position of top-left super tile to draw
sec ; set carry flag in preparation for subtraction
sbc #$20 ; subtract #$20 to get relative x position of top left super-tile
sta $0a ; store in relative x position of super-tile to draw
jmp draw_alien_guardian_supertile ; draw top left super-tile $08 at position ($0a - #$0e, $09 - #$10)
alien_guardian_exit_00:
rts
; draws the bottom right super-tile of the alien guardian
; sets delay to #$20 and advance enemy routine
draw_lower_jaw_open_adv_routine:
jsr draw_alien_guardian_lower_jaw ; update nametable to animate the bottom super-tile of the mouth
lda #$20 ; a = #$20 (delay between mouth open and attack)
jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a. advance enemy routine to #$03
; generates alien fetuses
alien_guardian_routine_02:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda ENEMY_ATTACK_FLAG ; see if enemies should attack
beq @set_delay_routine_01 ; branch if attack flag is disabled to set delay to #$03 and go to alien_guardian_routine_01
dec ENEMY_ANIMATION_DELAY,x ; enemy attack flag set to off, decrement enemy animation frame delay counter
lda ENEMY_ANIMATION_DELAY,x
cmp #$02 ; see if ENEMY_ANIMATION_DELAY is almost elapsed
bcc @create_fetus_exit ; timer is close to elapsing, create fetus and exit (creates #$02 fetuses)
bne alien_guardian_exit_00 ; timer is not about to elapse, exit
lda PLAYER_WEAPON_STRENGTH ; timer is 2 cycles from elapsing
cmp #$03
bcc alien_guardian_exit_00
@create_fetus_exit:
lda #$11 ; a = #$11 (11 = alien fetus)
jsr generate_enemy_a ; generate #$11 enemy (alien fetus)
lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter
bne alien_guardian_exit_00
@set_delay_routine_01:
lda #$03 ; a = #$03 (mouth movements before next attack)
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
lda #$02 ; a = #$02
jmp set_enemy_routine_to_a ; set enemy routine to alien_guardian_routine_01
; blank super-tiles for lower jaw
; called by alien_guardian_routine_06 to blank out body portion above top jaw
alien_guardian_routine_05:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda #$00 ; a = #$00 (blank super-tile entry in alien_boss_supertile_tbl)
jsr update_alien_boss_supertiles ; updates 2 nametable super-tiles for alien guardian animation
lda $0b ; load whether successfully able to update the super-tiles
bne alien_guardian_exit_00 ; exit if unable to update the super-tiles
lda #$01 ; updated super-tiles, load a = #$01
sta ENEMY_VAR_2,x ; prep variable for alien_guardian_routine_05 to draw part of alien guardian
jmp advance_enemy_routine ; advance to alien_guardian_routine_06
; updates 2 nametable tiles for alien heart and alien guardian animation
; input
; * a - entry in alien_boss_supertile_tbl
; output
; * $0b - clear when successful, set when CPU_GRAPHICS_BUFFER is full
update_alien_boss_supertiles:
asl
asl ; quadruple since each entry is #$04 bytes
tay ; transfer to offset register
lda alien_boss_supertile_tbl,y ; load pattern table tile code
sta $08 ; store left supertile code
lda alien_boss_supertile_tbl+1,y ; load pattern table tile code
sta $0c ; store right supertile code
lda alien_boss_supertile_tbl+2,y ; load relative y position
clc ; clear carry in preparation for addition
adc ENEMY_Y_POS,x ; add to enemy y position on screen
sta $09 ; store y position
lda alien_boss_supertile_tbl+3,y ; load relative x position
clc ; clear carry in preparation for addition
adc ENEMY_X_POS,x ; add to enemy x position on screen
sta $0a ; store x position
jmp draw_alien_boss_supertiles ; draw 2 super-tiles ($08, $0c) of alien guardian jaw or alien heart
; tile codes for alien guardian and heart (#$c * #$4 = #$30)
; byte 0: super-tile code 1 - right
; byte 1: super-tile code 2 - left
; byte 2: relative y position
; byte 3: relative x position
alien_boss_supertile_tbl:
.byte $83,$83,$00,$10 ; #$03, #$03 - 2 blank super-tiles
.byte $95,$96,$c0,$30 ; #$15, #$16 - alien guardian body destroyed
.byte $97,$83,$e0,$50 ; #$17, #$03 - alien guardian body destroyed and blank super-tile
.byte $84,$85,$f0,$10 ; heart - frame 0 - top-right and top-left
.byte $86,$87,$10,$10 ; heart - frame 0 - bottom-right and bottom-left
.byte $88,$89,$f0,$10 ; heart - frame 1 - top-right and top-left
.byte $8a,$8b,$10,$10 ; heart - frame 1 - bottom-right and bottom-left
.byte $8c,$8d,$f0,$10 ; heart - destroyed - top-right and top-left
.byte $8e,$8f,$10,$10 ; heart - destroyed - bottom-right and bottom-left
.byte $83,$83,$20,$f0 ; #$03, #$03 - 2 blank super-tiles (used for wall in front of alien guardian)
.byte $83,$83,$40,$f0 ; #$03, #$03 - 2 blank super-tiles
.byte $29,$29,$60,$f0 ; #$29, #$29 - empty ground
; blank top jaw first call, second call will blank body portion
alien_guardian_routine_06:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda ENEMY_VAR_2,x ; load whether or not the top jaw has been successfully blanked
beq @blank_body ; branch to blank out the body portion if already blanked out top of jaw
lda #$83 ; a = #$83 (blank super-tile from level_8_nametable_update_supertile_data)
sta $08 ; set super-tile to draw (blank super-tile)
ldy #$20 ; y = #$20
lda #$10 ; a = #$10
jsr set_nametable_pos_for_alien_guardian ; add #$10 to enemy x position and #$20 to enemy y position. store result x in $0a, y in $09
jsr draw_alien_guardian_supertile ; draw super-tile $08 at position ($0a - #$0e, $09 - #$10)
lda $0b ; load whether or not the top jaw was drawn (0 = success, 1 = failure)
sta ENEMY_VAR_2,x ; store value in ENEMY_VAR_2,x so that next loop @blank_body can run if drawn successfully
; or another attempt at drawing can happen
rts
@blank_body:
lda #$e0 ; a = #$e0
jsr add_a_to_enemy_y_pos ; add a to enemy y position on screen
jsr alien_guardian_routine_05 ; blank out body portion
lda #$20 ; a = #$20
jmp add_a_to_enemy_y_pos ; add a to enemy y position on screen for alien_guardian_routine_07
; draws the destroyed alien guardian body super-tiles
alien_guardian_routine_07:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda ENEMY_VAR_2,x ; load whether or not the body portion of alien guardian was blanked
beq alien_guardian_destroy_body ; draw the portion of the body that is destroyed (not blanked)
lda #$01 ; a = #$01 (entry in alien_boss_supertile_tbl) - top portion of destroyed body
jsr update_alien_boss_supertiles ; updates 2 nametable super-tiles for alien guardian animation
lda $0b ; load whether or not the top destroyed body was drawn (0 = success, 1 = failure)
sta ENEMY_VAR_2,x ; store value in ENEMY_VAR_2,x so that next loop alien_guardian_routine_07 can run if drawn successfully
; or another attempt at drawing can happen
alien_guardian_exit_02:
rts
alien_guardian_destroy_body:
lda #$02 ; a = #$02 (entry in alien_boss_supertile_tbl)
jsr update_alien_boss_supertiles ; updates 2 nametable super-tiles for alien guardian animation
; advance routine if successfully drew alien guardian super-tiles
alien_guardian_adv_routine_if_drawn:
lda $0b ; load whether or not the super-tile was drawn (0 = success, 1 = failure)
bne alien_guardian_exit_02 ; exit if unable to draw super-tiles so that next frame can retry
alien_guardian_set_draw_adv_routine:
inc ENEMY_VAR_2,x ; set flag indicating not drawn for next routine to draw
jmp advance_enemy_routine
; blank more of the alien guardian body
alien_guardian_routine_08:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda #$83 ; a = #$83 (blank super-tile)
sta $08 ; set super-tile to draw
lda #$30 ; a = #$30 (amount to add to enemy x position)
jsr set_nametable_x_pos_for_alien_guardian ; add #$30 to enemy x position and stores result in $0a
jsr draw_alien_guardian_supertile ; draw super-tile $08 at position ($0a - #$0e, $09 - #$10)
ldy #$c0 ; y = c0
lda #$f0 ; a = #$f0
jsr set_nametable_pos_for_alien_guardian ; subtract #$10 from enemy x position and add #$c0 to enemy y position. store result x in $0a, y in $09
lda #$83 ; a = #$83 (blank super-tile)
sta $08 ; set super-tile to draw
jsr draw_alien_guardian_supertile ; draw super-tile $08 at position ($0a - #$0e, $09 - #$10)
jmp alien_guardian_adv_routine_if_drawn ; advance routine if updated super-tiles, otherwise just exit to retry next frame
; destroys wall in front of alien guardian
alien_guardian_routine_09:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda ENEMY_VAR_2,x ; load whether or not successfully updated super-tiles for current routine
beq @delete_bottom_wall_and_bg_collision
lda #$09 ; a = #$09 (#$02 blank super-tiles) - entry in alien_boss_supertile_tbl
@delete_wall_and_bg_collision:
jsr update_alien_boss_supertiles ; updates 2 nametable super-tiles for alien guardian animation
pha ; backup a on the stack
jsr alien_guardian_clear_wall_bg_collision
pla ; restore a from the stack
sta ENEMY_VAR_2,x ; store whether or not was able to update super-tiles
rts
@delete_bottom_wall_and_bg_collision:
lda #$0a ; a = #$0a (#$02 blank super-tiles) - entry in alien_boss_supertile_tbl
jsr @delete_wall_and_bg_collision ; draw blank super-tiles over wall, and clear bg collision
beq alien_guardian_set_draw_adv_routine ; advance routine if updated super-tiles, otherwise just exit to retry next frame
rts
alien_guardian_clear_wall_bg_collision:
lda $12 ; backup $12 on stack
pha ; store $12 on stack for backing up
jsr clear_supertile_bg_collision ; set background collision code for wall to #$00 (empty) for super-tile at PPU address $12 (low) $13 (high)
pla ; pop $12 off of stack
clc ; clear carry in preparation for addition
adc #$04 ; add #$04 to PPU address low byte
sta $12 ; set PPU address low byte
lda $13 ; load PPU address high byte
adc #$00 ; add any carry from $12
sta $13 ; set PPU address high byte
jmp clear_supertile_bg_collision ; set background collision code to #$00 (empty) for single super-tile at PPU address $12 (low) $13 (high)
; remove lowest part of wall's collision and set floor for where wall was
alien_guardian_routine_0a:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda ENEMY_VAR_2,x ; load whether or not successfully updated super-tiles for current routine
beq alien_guardian_adv_routine ; advance routine if successfully drawn
lda #$0b ; a = #$0b (empty ground) (alien_boss_supertile_tbl)
; regular ground where wall was in the way
jsr update_alien_boss_supertiles ; updates 2 nametable super-tiles for alien guardian animation
pha ; backup a on the stack
lda $12 ; load background collision code location
sec ; set carry flag in preparation for subtraction
sbc #$20
sta $12
lda $13
sbc #$00
sta $13
jsr alien_guardian_clear_wall_bg_collision ; clear lowest part of wall's collision code
pla ; restore a on the stack
beq alien_guardian_adv_routine ; advance routine to alien_guardian_routine_0b
alien_guardian_exit_01:
rts
alien_guardian_adv_routine:
jmp advance_enemy_routine
; play alien guardian destroyed sound, create initial explosion
alien_guardian_routine_03:
lda #$55 ; a = #$55 (sound_55)
jsr play_sound ; play alien guardian destroyed sound
create_boss_heart_explosion:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
jsr clear_enemy_custom_vars ; set ENEMY_VAR_1, ENEMY_VAR_2, ENEMY_VAR_3, ENEMY_VAR_4 to zero
sta $08 ; set vertical offset from enemy position to #$00
sta $09 ; set horizontal offset from enemy position to #$00
jsr create_explosion_at_x_y
lda #$05 ; a = #$05
jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to #$05 and advance enemy routine
; alien guardian - pointer 5
; heart - pointer 5
; create series of explosions over screen, each with a #$05 frame delay before next explosion
alien_guardian_routine_04:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne alien_guardian_exit_01 ; exit if initial explosion from alien_guardian_routine_03 hasn't completed
lda #$05 ; a = #$05
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
inc ENEMY_VAR_1,x ; increment explosion number (!(BUG?) #$00 isn't used)
lda ENEMY_VAR_1,x ; load explosion number
cmp #$0c ; see if drawn all explosions
beq alien_guardian_adv_routine ; advance routine if drawn all explosions
jmp @continue ; why jmp? doesn't seem necessary !(HUH)
@continue:
asl ; double since each entry in alien_guardian_explosion_offset_tbl is #$02 bytes
tay ; transfer to offset register
lda alien_guardian_explosion_offset_tbl,y ; load x position of explosion
sta $08 ; set x position of explosion
lda alien_guardian_explosion_offset_tbl+1,y ; load y position of explosion
sta $09 ; set y position of explosion
; input
; * y - vertical offset
; * x - horizontal offset
create_explosion_at_x_y:
ldy $08 ; set vertical offset from enemy position (param for add_with_enemy_pos)
lda $09 ; set horizontal offset from enemy position (param for add_with_enemy_pos)
jsr add_with_enemy_pos ; stores a + enemy x position in $09, and y + enemy y position in $08
jmp create_two_explosion_89 ; create explosion #$89 at location ($09, $08)
; pointer table for alien fetus (11) (#$5 * #$2 = #$a bytes)
alien_fetus_routine_ptr_tbl:
.addr alien_fetus_routine_00 ; CPU address $b6ec
.addr alien_fetus_routine_01 ; CPU address $b736
.addr enemy_routine_init_explosion ; CPU address $e74b from bank 7
.addr enemy_routine_explosion ; CPU address $e7b0 from bank 7
.addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7
; alien fetus - pointer 0
alien_fetus_routine_00:
jsr alien_fetus_get_aim_timer ; set ENEMY_VAR_3,x to the delay before re-aiming towards player's current location
asl ENEMY_VAR_3,x ; double the value
lda GAME_COMPLETION_COUNT ; load the number of times the game has been completed
clc ; clear carry in preparation for addition
adc #$02 ; add #$02 to game completion count
sta ENEMY_HP,x ; set enemy hp = completion count + #$02
lda #$ac ; a = #$ac (sprite_ac)
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
lda #$06 ; a = #$06
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
lda P2_GAME_OVER_STATUS ; player 2 game over state (1 = game over)
bne @calc_velocity ; branch if player 2 is in game over state
lda RANDOM_NUM ; player 2 is game over or not playing, load random number
adc FRAME_COUNTER ; add frame counter to random number
; !(WHY?) not sure why adding to random number. it is already random
; perhaps random number is used for something else this frame and
; developers didn't want the same number
and #$1f ; keep bits ...x xxxx
clc ; clear carry in preparation for addition
adc #$0e ; add #$0e to random number
sta ENEMY_VAR_4,x ; set to random number [#$0e-#$2d]
lda P1_GAME_OVER_STATUS ; player 1 game over state (1 = game over)
beq @calc_velocity ; branch if player 1 not in game over state
lda #$01 ; player 1 in game over state, set ENEMY_VAR_4,x to #$01
sta ENEMY_VAR_4,x
@calc_velocity:
lda RANDOM_NUM ; load random number
and #$03 ; random number between #$00 and #$03
bne @check_attr ; branch if not #$00 (3/4 probability)
lda #$03 ; random number was #$00, set to #$03
; 50% chance of #$03, 25% of #$01, 25% of #$02
@check_attr:
asl ; double random number between #$00 and #$03
ldy ENEMY_ATTRIBUTES,x ; load the enemy attributes
beq @set_velocity
lda #$06 ; a = #$06
@set_velocity:
sta ENEMY_VAR_1,x ; store enemy aim direction
inc ENEMY_ROUTINE,x ; advance to alien_fetus_routine_01
jmp alien_fetus_set_velocity ; set velocity based on ENEMY_VAR_1,x
; alien fetus - pointer 1
alien_fetus_routine_01:
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne @check_velocity
lda #$06 ; animation delay has elapsed, a = #$06
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
ldy #$ff ; y = #$ff (-1)
lda ENEMY_VAR_1,x ; load enemy direction, #$00 = facing right incrementing clockwise, max #$0b
clc ; clear carry in preparation for addition
adc #$01 ; add one to the aim direction
cmp #$0c ; compare to #$0c (max facing direction)
bne @get_sprite_offset ; branch if not facing down
lda #$00 ; reset to facing right
@get_sprite_offset:
sec ; set carry flag in preparation for subtraction
sbc #$03 ; subtract #$03 from facing direction
iny ; increment sprite code offset
bcs @get_sprite_offset ; loop until value of a is negative, this determines which sprite to show
tya ; transfer sprite code offset to a [#$00-#$01]
asl ; double value [#$00-#$02]
sta $08 ; set sprite code offset
lda ENEMY_VAR_2,x ; load whether or not the mouth is open (0 = closed, 1 = open)
eor #$01 ; flip bit 0 (close mouth if open, open if closed)
sta ENEMY_VAR_2,x ; set new value of whether or not the mouth is open (0 = closed, 1 = open)
lda ENEMY_SPRITE_ATTR,x ; enemy animation frame delay counter
and #$3f ; strip sprite flip flags
sta ENEMY_SPRITE_ATTR,x ; enemy animation frame delay counter
lda $08 ; load sprite code
cmp #$04 ; see if past the last sprite (sprite_af)
bcc @set_sprite ; branch if not past the last sprite
lda ENEMY_SPRITE_ATTR,x ; load sprite attributes
ora #$c0 ; set bits xx.. .... (flip sprite horizontally and vertically)
sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes
lda $08 ; load sprite code offset
sec ; set carry flag in preparation for subtraction
sbc #$04 ; subtract #$04 from offset to get back to #$00 (sprite_ac)
@set_sprite:
clc ; clear carry in preparation for addition
adc #$ac ; add #$ac to to base sprite offset [#$00-#$02] (sprite_ac)
adc ENEMY_VAR_2,x ; add 0 or 1 depending on whether mouth is open
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
@check_velocity:
dec ENEMY_VAR_3,x ; decrement velocity adjustment timer
bne @maintain_current_velocity ; maintain current velocity if timer not elapsed
jsr @aim_towards_player ; timer elapsed, re-aim towards player
@maintain_current_velocity:
jmp update_enemy_pos ; apply velocities and scrolling adjust
@aim_towards_player:
lda ENEMY_VAR_4,x
and #$3e ; keep bits ..xx xxx.
beq alien_fetus_exit
jsr alien_fetus_get_aim_timer
jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position
lda ENEMY_VAR_4,x ; load target player index control, bit 0 specifies which player to target
and #$01 ; keep bit 0
sta $0a ; store player index in $0a
jsr aim_var_1_for_quadrant_aim_dir_00 ; determine next aim direction [#$00-#$0b] ($0c)
; adjusts ENEMY_VAR_1 to get closer to that value using quadrant_aim_dir_00
alien_fetus_set_velocity:
lda ENEMY_VAR_1,x ; load calculated enemy aim direction
asl
asl ; quadruple since each entry in set_white_blob_alien_fetus_vel is #$04 bytes
tay ; transfer to offset register
jsr set_white_blob_alien_fetus_vel ; set the alien fetus velocity based on a
lda ENEMY_VAR_4,x ; load target player index control
clc ; clear carry so that #$03 is subtracted and not #$02
sbc #$02 ; subtract #$03 from target player index control (carry is clear)
sta ENEMY_VAR_4,x ; set new value for target player index control
alien_fetus_exit:
rts
; input
; * y - the offset into white_blob_alien_fetus_vel_tbl
set_white_blob_alien_fetus_vel:
lda white_blob_alien_fetus_vel_tbl,y ; load y fast velocity
sta ENEMY_Y_VELOCITY_FAST,x ; set y fast velocity
lda white_blob_alien_fetus_vel_tbl+1,y ; load y fractional velocity
sta ENEMY_Y_VELOCITY_FRACT,x ; set y fractional velocity
lda white_blob_alien_fetus_vel_tbl+12,y ; load x fast velocity
sta ENEMY_X_VELOCITY_FAST,x ; set x fast velocity
lda white_blob_alien_fetus_vel_tbl+13,y ; load x fractional velocity
sta ENEMY_X_VELOCITY_FRACT,x ; set x fractional velocity
rts
; determine how frequently to re-aim towards player
alien_fetus_get_aim_timer:
ldy ALIEN_FETUS_AIM_TIMER_INDEX ; load current
inc ALIEN_FETUS_AIM_TIMER_INDEX ; increment read offset for next round
lda alien_fetus_aim_timer_tbl,y ; load amount of time between re-aiming towards player
cmp #$ff ; see if read last byte
bne @set_aim_timer_exit ; set ENEMY_VAR_3,x and exit if not end of data stream
lda #$00 ; read past last byte, reset offset to get first byte
sta ALIEN_FETUS_AIM_TIMER_INDEX ; reset offset back to #$00
jmp alien_fetus_get_aim_timer ; jump to read the first byte (#$16) from alien_fetus_aim_timer_tbl
@set_aim_timer_exit:
sta ENEMY_VAR_3,x
rts
; table for delay amount between re-aiming alien fetus toward player (#$e bytes)
; CPU address $b7e8
alien_fetus_aim_timer_tbl:
.byte $16,$0f,$08,$13,$3a,$06,$21
.byte $3a,$1d,$14,$12,$28,$48,$ff
; pointer table for alien mouth (12) (#$6 * #$2 = #$c bytes)
alien_mouth_routine_ptr_tbl:
.addr alien_mouth_routine_00 ; CPU address $b802 - set mouth hp and draw open mouth super-tile
.addr alien_mouth_routine_01 ; CPU address $b81f - opens and closes while generating white blobs
.addr alien_mouth_routine_02 ; CPU address $b85e - draw destroyed mouth super-tile
.addr enemy_routine_init_explosion ; CPU address $e74b from bank 7
.addr enemy_routine_explosion ; CPU address $e7b0 from bank 7
.addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7
; set mouth hp and draw open mouth super-tile
; alien mouth hp = (#$02 * GAME_COMPLETION_COUNT) + (PLAYER_WEAPON_STRENGTH + #$04)
alien_mouth_routine_00:
lda PLAYER_WEAPON_STRENGTH ; load player's weapon strength
adc #$03 ; add #$04 to player weapon strength (carry is always set here)
; because the carry flag is set from the cmp #$10 check before to run `exec_level_enemy_routine`
sta $08 ; store PLAYER_WEAPON_STRENGTH + #$03 in $08
lda GAME_COMPLETION_COUNT ; load the number of times the game has been completed
asl ; double the number of times the game has been completed
adc $08 ; (#$02 * GAME_COMPLETION_COUNT) + (PLAYER_WEAPON_STRENGTH + #$03)
sta ENEMY_HP,x ; set alien mouth's hp to this computed result
lda #$20 ; a = #$20 (delay after opening mouth for white blob to generate)
sta ENEMY_VAR_3,x ; set initial animation delay
lda #$01 ; a = #$01 (level_8_nametable_update_supertile_data - alien mouth (wadder) open)
jsr draw_enemy_supertile_a ; draw super-tile a at position (ENEMY_X_POS, ENEMY_Y_POS)
lda #$0a ; a = #$0a (delay before first white blob is generated)
jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a and advance enemy routine
; opens and closes while generating white blobs
alien_mouth_routine_01:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda ENEMY_X_POS,x ; load enemy x position on screen
cmp #$20 ; stop moving/attacking past this x position
bcs @continue ; branch if the alien mouth isn't about to be scrolled off screen
rts ; exit if alien mouth is about to be scrolled off screen
@continue:
lda ENEMY_ATTACK_FLAG ; see if enemies should attack
beq @check_supertile ; branch if enemies shouldn't attack
dec ENEMY_ANIMATION_DELAY,x ; decrement white blob spawning delay
bne @check_supertile ; branch if animation delay hasn't elapsed
lda #$13 ; enemy type #$13 = white sentient blob
jsr generate_enemy_a ; generate #$13 enemy (white sentient blob)
lda RANDOM_NUM ; load random number
and #$1f ; load random number between #$00 and #$1f
adc #$c0 ; add #$c0 to random number
sta ENEMY_ANIMATION_DELAY,x ; set delay before next white sentient blob will be spawned
@check_supertile:
dec ENEMY_VAR_3,x ; decrement nametable update timer
bne alien_mouth_exit ; exit if the super-tile shouldn't be changed
lda ENEMY_VAR_4,x ; load which super-tile to draw (#$00 = alien mouth closed, #$01 = alien mouth open)
and #$01 ; keep bit 0
clc ; clear carry in preparation for addition
adc #$00 ; !(HUH) carry is explicitly clear, this line of code doesn't do anything
jsr draw_enemy_supertile_a ; draw super-tile a at position (ENEMY_X_POS, ENEMY_Y_POS)
lda #$01 ; a = #$01
bcs @set_anim_delay_exit ; branch if unable to draw super-tile to retry next frame
inc ENEMY_VAR_4,x ; successfully drew super-tile, set next super-tile
; (only bit 0 matters and that alternates between #$00 and #$01)
lda #$20 ; a = #$20 (delay between mouth open/closed)
@set_anim_delay_exit:
sta ENEMY_VAR_3,x ; set nametable update timer to #$20
alien_mouth_exit:
rts
; draw destroyed mouth super-tile
alien_mouth_routine_02:
lda #$02 ; level_8_nametable_update_supertile_data - #$02 - alien mouth (wadder) destroyed
jsr draw_enemy_supertile_a ; draw super-tile a at position (ENEMY_X_POS, ENEMY_Y_POS)
bcs alien_mouth_exit ; exit if unable to drwa destoryed mouht to try again next frame
jmp advance_enemy_routine ; advance to next routine
; pointer table for white sentient blob (13) (#$6 * #$2 = #$c bytes)
white_blob_routine_ptr_tbl:
.addr white_blob_routine_00 ; CPU address $b874 - find player to target
.addr white_blob_routine_01 ; CPU address $b8b3 - float down until the blob 'gains sentience' and begins to target/hone in on the player
.addr white_blob_routine_02 ; CPU address $b940 - targets player and rushes towards them at 3x speed, retargeting every so often
.addr enemy_routine_init_explosion ; CPU address $e74b from bank 7
.addr enemy_routine_explosion ; CPU address $e7b0 from bank 7
.addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7
; find player to target
white_blob_routine_00:
lda RANDOM_NUM ; load random number
and #$1f ; keep bits ...x xxxx
adc #$50 ; add #$50
sta ENEMY_VAR_2,x ; delay before sentience starts, e.g. before the white blob pauses
; then targets player quickly [#$50-#$6f]
lda #$c0 ; a = #$c0
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
lda P2_GAME_OVER_STATUS ; player 2 game over state (1 = game over)
bne @continue ; branch if player 2 is game over
lda RANDOM_NUM ; load random number
adc FRAME_COUNTER ; add frame counter to random number
and #$01 ; keep bit 0
sta ENEMY_VAR_3,x ; store which player should be targeted
lda P1_GAME_OVER_STATUS ; player 1 game over state (1 = game over)
beq @continue ; branch if player 1 is not in game over
lda #$01 ; player 1 game over, have blob target player 2
sta ENEMY_VAR_3,x ; set to target player 2
@continue:
lda #$b0 ; a = #$b0 (sprite_b0 - poisonous insect gel)
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
lda ENEMY_VAR_3,x ; load which player to target
sta $0a ; store player to target index in $0a
jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position
jsr get_rotate_01 ; get enemy aim direction and rotation direction using quadrant_aim_dir_01
lda $0c ; load new aim direction
sta ENEMY_VAR_1,x ; store the aim direction in ENEMY_VAR_1
inc ENEMY_ROUTINE,x ; enemy routine index to white_blob_routine_01
jmp white_blob_init_velocity ; initialize the x and y velocities
; float down until the blob 'gains sentience' and begins to target/hone in on the player
white_blob_routine_01:
lda #$b0 ; load base sprite offset
; white blob's use sprites sprite_b0, sprite_b1, sprite_b2
jsr white_blob_spider_set_sprite ; check if ENEMY_ANIMATION_DELAY elapsed, and if so update the sprite
jsr update_enemy_pos ; apply velocities and scrolling adjust
lda ENEMY_VAR_4,x ; see if ENEMY_VAR_2,x (freeze delay) has elapsed, once ENEMY_VAR_2,x elapses ENEMY_VAR_4,x is set
bne white_blob_freeze ; branch if freeze delay has elapsed to freeze and advance to white_blob_routine_02
jsr blob_spider_ld_delay_timer ; set a to the timer portion of ENEMY_ANIMATION_DELAY (high nibble), e.g. #$74 -> #$07
; loops from #$08 to #$01
cmp #$08 ; see if sprite has just changed and timer has reset
bne @dec_delay ; skip adjusting the velocity if sprite just changed
jsr @adjust_velocity ; ajust the velocity based on the aiming direction
@dec_delay:
dec ENEMY_VAR_2,x ; decrement delay before advancing to white_blob_routine_02
beq white_blob_set_freeze_length ; branch if timer has elapsed to determine freeze delay before attacking
rts
@adjust_velocity:
ldy ENEMY_VAR_1,x ; load the aim direction
lda ENEMY_Y_VELOCITY_FRACT,x ; load the fractional y velocity
clc ; clear carry in preparation for addition
adc white_blob_y_vel_adj_tbl,y ; add velocity adjument amount per frame based on aim direction
sta ENEMY_Y_VELOCITY_FRACT,x ; set new y fractional velocity
lda ENEMY_X_VELOCITY_FRACT,x ; load fractional x velocity
clc ; clear carry in preparation for addition
adc white_blob_x_vel_adj_tbl,y ; add x velocity adjument amount per frame based on aim direction
sta ENEMY_X_VELOCITY_FRACT,x ; set new x fractional velocity
rts
white_blob_init_velocity:
lda ENEMY_VAR_1,x ; load enemy aim direction
asl ; double value since each entry in white_blob_alien_fetus_vel_tbl is #$02 bytes
tay ; transfer to offset register
jmp set_white_blob_alien_fetus_vel ; set the x and y velocities
; set a to the timer portion of ENEMY_ANIMATION_DELAY (high nibble), e.g. #$74 -> #$07
blob_spider_ld_delay_timer:
lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter
lsr
lsr
lsr
lsr ; transfer high nibble to low nibble
rts
; table for amount to add to y fractional velocity each frame based on aim rotation direction [#$00-#$16] (#$6 bytes)
; spills over into next table
white_blob_y_vel_adj_tbl:
.byte $00,$fd,$fa,$f8,$f6,$f5
; table for amount to add to x fractional velocity each frame based on aim rotation direction [#$00-#$16] (#$6 bytes)
; spills over into next table
white_blob_x_vel_adj_tbl:
.byte $f4,$f5,$f6,$f8,$fa,$fd
.byte $00,$03,$06,$08,$0a,$0b
.byte $0c,$0b,$0a,$08,$06,$03
.byte $00,$fd,$fa,$f8,$f6,$f5
; randomly chooses a freeze duration of #$02 or #$20 frames
white_blob_set_freeze_length:
lda RANDOM_NUM ; load random number
and #$20 ; keep bit 5
clc ; clear carry in preparation for addition
adc #$02 ; add #$02
sta ENEMY_VAR_4,x ; store either #$02 or #$22 as the 'freeze' duration before attacking
white_blob_exit:
rts
; freezes white blob and quickly target player within 4 directions and advance to white_blob_routine_02
; sets ENEMY_VAR_2,x to #$08 to freeze white blob for additional #$08 frames
white_blob_freeze:
jsr set_enemy_velocity_to_0 ; set x/y velocities to zero
dec ENEMY_VAR_4,x ; decrement freeze timer
bne white_blob_exit ; exit if freeze timer hasn't elapsed
lda #$08 ; freeze timer elapsed, target player with a #$08 frame delay
sta ENEMY_VAR_4,x ; set value for use in white_blob_routine_02
sta ENEMY_VAR_2,x ; set amount of additional frames to freeze blob for before attacking
inc ENEMY_ROUTINE,x ; advance to white_blob_routine_02
jsr white_blob_aim_to_player ; hone towards player by calling #$04 times
jsr white_blob_aim_to_player
jsr white_blob_aim_to_player
jmp white_blob_aim_to_player
; targets player and rushes towards them at 3x speed, retargeting every so often
white_blob_routine_02:
lda #$b0 ; set base sprite code offset for alien spiders (sprite_b0, sprite_b1, sprite_b2)
jsr white_blob_spider_set_sprite ; check if ENEMY_ANIMATION_DELAY elapsed, and if so update the sprite
lda ENEMY_VAR_4,x ; load initial value of the velocity/direction update pause timer
beq @update_enemy_pos ; !(WHY?) not sure the real reason for this check, ENEMY_VAR_4, starts at #$08 and increments by #$02
dec ENEMY_VAR_2,x ; decrement velocity/direction update pause timer
beq @update_velocity ; branch if velocity/direction update pause timer elapsed to adjust velocity
jmp @update_enemy_pos ; additional freeze timer hasn't elapsed
; targets player and updates velocity to be 3x the standard velocities
@update_velocity:
inc ENEMY_VAR_4,x ; add #$01 to ENEMY_VAR_4
inc ENEMY_VAR_4,x ; add #$01 to ENEMY_VAR_4
lda ENEMY_VAR_4,x ; re-load ENEMY_VAR_4,x, this is the new velocity/direction update pause timer
sta ENEMY_VAR_2,x ; set velocity/direction update pause timer value for new velocity that is about to be calculated
jsr white_blob_aim_to_player ; aim towards player one increment towards correct direction
lda ENEMY_VAR_1,x ; load aim direction [#$00-#$16]
asl ; double since each entry is #$02 bytes
tay ; transfer to offset register
lda white_blob_alien_fetus_vel_tbl+3,y ; load fractional y velocity to multiply by #$03
sta $08 ; set fractional y velocity before multiplication
lda white_blob_alien_fetus_vel_tbl+2,y ; load fast y velocity to multiply by #$03
jsr mult_velocity_by_3 ; multiply velocity by #$03 (a = fast velocity, $08 = fractional velocity)
sta ENEMY_Y_VELOCITY_FAST,x ; set y fast velocity (sped up 3x from table value)
lda $08 ; load new fractional y velocity
sta ENEMY_Y_VELOCITY_FRACT,x ; set y fractional velocity (sped up 3x from table value)
lda white_blob_alien_fetus_vel_tbl+9,y ; load fractional x velocity to multiply by #$03
sta $08 ; set fractional x velocity before multiplication
lda white_blob_alien_fetus_vel_tbl+8,y ; load fast x velocity to multiply by #$03
jsr mult_velocity_by_3 ; multiply velocity by #$03 (a = fast velocity, $08 = fractional velocity)
sta ENEMY_X_VELOCITY_FAST,x ; set x fast velocity (sped up 3x from table value)
lda $08 ; load new fractional x velocity
sta ENEMY_X_VELOCITY_FRACT,x ; set x fractional velocity (sped up 3x from table value)
@update_enemy_pos:
jmp update_enemy_pos ; apply velocities and scrolling adjust
; multiply loaded velocity by 3x
; e.g. fast vel: #$ff, fractional vel: #$23 (-.863) becomes fast vel: #$fd, fractional vel: #$69 (-2.589)
; input
; * a - fast velocity component (either #$00 or #$ff)
; * $08 - fractional velocity component
; output
; * a - sped up fast velocity value
; * $08 - sped up fractional velocity value
mult_velocity_by_3:
sta $09 ; initialize $09 to fast velocity component
sta $0a ; initialize $0a to fast velocity component
lda $08 ; load fractional velocity value
asl ; double fractional component
rol $09 ; move any carry from addition to $09 while also doubling $09
clc ; clear carry in preparation for addition
adc $08 ; add shifted-left $08 to its original value, i.e. 2 * $08 + $08 -> #$03 * $08
sta $08 ; store new value in $08
lda $09 ; load doubled fast velocity component and any overflow from the fractional velocity doubling
adc $0a ; add to original fast velocity (along with any additional overflow when getting the tripled fractional velocity)
rts
; aim towards player one increment towards correct direction
white_blob_aim_to_player:
jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position
lda ENEMY_VAR_3,x ; load which player to attack
sta $0a ; store value in $0a
jmp aim_var_1_for_quadrant_aim_dir_01 ; determine next aim direction [#$00-#$0b] ($0c), adjusts ENEMY_VAR_1 to get closer to that value using quadrant_aim_dir_01
; alien spider and white blob
; check if ENEMY_ANIMATION_DELAY has elapsed, and if so update the sprite
; ENEMY_ANIMATION_DELAY timer is just high nibble portion, low nibble is for knowing which sprite to show next
; input
; * a - base sprite offset to be added to that is specific to enemy type
white_blob_spider_set_sprite:
sta $08 ; set enemy type's base sprite offset
lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter
; preparing to move high nibble (timer portion) to the low nibble, e.g. #$73 -> #$07
and #$0f ; keep low nibble
tay ; transfer sprite index to offset register
jsr blob_spider_ld_delay_timer ; set a to the timer portion of ENEMY_ANIMATION_DELAY (high nibble), e.g. #$74 -> #$07
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter to be just the high nibble moved to low nibble
dec ENEMY_ANIMATION_DELAY,x ; decrement timer portion of ENEMY_ANIMATION_DELAY
bne @set_delay_and_sprite_index ; branch to skip changing sprite, if the animation delay hasn't elapsed
@get_sprite_offset:
lda white_blob_spider_sprite_tbl,y ; load offset from $08 to determine sprite code
cmp #$ff ; see if reached end of table data
bne @set_sprite_and_delay ; branch if haven't reached end of table data, otherwise point to first entry in table
ldy #$00 ; reset sprite offset index to first entry
jmp @get_sprite_offset ; jump to load the first table entry's sprite offset value
@set_sprite_and_delay:
iny ; increment sprite index for next animation
clc ; clear carry in preparation for addition
adc $08 ; add enemy type-specific sprite base offset
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
lda #$08 ; reset enemy animation frame delay counter to #$08
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter (#$08 frames until sprite changes)
; moves low nibble of ENEMY_ANIMATION_DELAY into high nibble (animation timer)
; then adds y to result, setting the sprite index for the next frame
@set_delay_and_sprite_index:
lda ENEMY_ANIMATION_DELAY,x ; load the timer portion of ENEMY_ANIMATION_DELAY
jsr mv_low_nibble_to_high ; move low nibble (timer nibble) into high nibble, setting low nibble to all 0
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
tya ; transfer the next white_blob_spider_sprite_tbl index to a
clc ; clear carry in preparation for addition
adc ENEMY_ANIMATION_DELAY,x ; set the low nibble to the sprite index
sta ENEMY_ANIMATION_DELAY,x ; set full ENEMY_ANIMATION_DELAY with timer (high nibble) and index (low nibble)
rts
; alien spider and white blob sprite code offsets (#$5 bytes)
; white blob - sprite_b0, sprite_b1, sprite_b2, sprite_b1
white_blob_spider_sprite_tbl:
.byte $00,$01,$02,$01,$ff
; white blob velocities based on aim rotation direction (#$c bytes)
white_blob_alien_fetus_vel_tbl:
.byte $00,$00
.byte $00,$42 ; aim rotation dir - #$00 - facing right
.byte $00,$7f ; aim rotation dir - #$01
.byte $00,$b2
.byte $00,$dd
.byte $00,$f7
.byte $00,$ff
.byte $00,$f7
.byte $00,$dd
.byte $00,$b2
.byte $00,$7f
.byte $00,$42
.byte $00,$00
.byte $ff,$be
.byte $ff,$81
.byte $ff,$4e
.byte $ff,$23
.byte $ff,$09
.byte $ff,$01
.byte $ff,$09
.byte $ff,$23
.byte $ff,$4e
.byte $ff,$81
.byte $ff,$be
.byte $00,$00
.byte $00,$42
.byte $00,$7f
.byte $00,$b2
.byte $00,$dd
.byte $00,$f7
; pointer table for alien spider (14) (#$8 * #$2 = #$10 bytes)
alien_spider_routine_ptr_tbl:
.addr alien_spider_routine_00 ; CPU address $ba3b - set spider hp, velocity, set whether will jump, advance routine to alien_spider_routine_03
.addr alien_spider_routine_01 ; CPU address $bb44 - set score and collision code, set sprite to egg, set x velocity to -.3125 and y velocity to +-4, advance to alien_spider_routine_02
.addr alien_spider_routine_02 ; CPU address $bb68 - alien spider while it's still an egg, float to ceiling or fall to ground, once close spawn from egg
.addr alien_spider_routine_03 ; CPU address $ba8c - alien spider is on the ground/ceiling, or just landing on the ground/ceiling
.addr alien_spider_routine_04 ; CPU address $bb2a - spider is jumping, check for groud/ceiling collision and if collided, go back to alien_spider_routine_03
.addr enemy_routine_init_explosion ; CPU address $e74b from bank 7
.addr enemy_routine_explosion ; CPU address $e7b0 from bank 7
.addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7
; alien spider - pointer 1
; spider is on the ground from spawn as generated by level enemies, not by heart boss
alien_spider_routine_00:
jsr set_alien_spider_hp_sprite_attr ; calculate alien spider hp and other variables
; set x velocity to -2.5, set y velocity to 0, choose player to target
; determine whether spider will jump, and set routine to alien_spider_routine_03
alien_spider_set_ground_vel_and_routine:
jsr clear_enemy_custom_vars ; set ENEMY_VAR_1, ENEMY_VAR_2, ENEMY_VAR_3, ENEMY_VAR_4 to zero
lda #$b3 ; a = #$b3 (sprite_b3)
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer (sprite_b3)
lda #$fe ; a = #$fe
sta ENEMY_X_VELOCITY_FAST,x ; set fast x velocity of alien spider to -2
lda #$80 ; a = #$80
sta ENEMY_X_VELOCITY_FRACT,x ; set enemy fractional velocity to .5
jsr set_enemy_y_velocity_to_0 ; set y velocity to zero
lda P2_GAME_OVER_STATUS ; player 2 game over state (1 = game over or player 2 not playing)
bne @set_jump_flag_routine_03 ; branch if player 2 game over or not playing
lda RANDOM_NUM ; player 2 playing, load random number
adc FRAME_COUNTER ; add frame counter to random number
and #$01 ; keep bit 0
sta ENEMY_VAR_1,x ; store player to attack (0 = player 1, 1 = player 2)
; determine whether or not the alien spider will jump randomly and then set routine to alien_spider_routine_03
@set_jump_flag_routine_03:
lda RANDOM_NUM ; load random number
lsr ; shift bit 0 to carry
adc FRAME_COUNTER ; add frame counter
and #$02 ; keep bit 1
sta ENEMY_VAR_2,x ; store whether spider will jump (#$00 = will not jump, #$02 = will jump)
lda #$04 ; a = #$04
jmp set_enemy_routine_to_a ; set routine to alien_spider_routine_03
; alien spider hp = weapon strength + completion count + 2
set_alien_spider_hp_sprite_attr:
lda PLAYER_WEAPON_STRENGTH ; load player weapon strength
adc GAME_COMPLETION_COUNT ; add with the number of times the game has been completed
adc #$01 ; add #$01
sta ENEMY_HP,x ; set enemy hp (weapon strength + completion count + 2)
; 2 because the carry flag is set from the cmp #$10 check before to run `exec_level_enemy_routine`
lda #$60 ; a = #$60
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
lda ENEMY_Y_POS,x ; load enemy y position on screen
cmp #$80 ; see if alien spider in the bottom half of the screen
lda #$00 ; a = #$00 (no horizontal flip, no vertical flip)
bcs @set_attr_exit ; branch if in the bottom half of the screen
lda #$80 ; a = #$80 (vertical flip, no horizontal flip)
@set_attr_exit:
sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes
rts
; alien spider is on the ground/ceiling, or just landing on the ground/ceiling
alien_spider_routine_03:
lda #$b3 ; set base sprite code offset for alien spiders (sprite_b3, sprite_b4, sprite_b5)
jsr white_blob_spider_set_sprite ; check if ENEMY_ANIMATION_DELAY elapsed, and if so update the sprite
lda ENEMY_VAR_3,x ; load spider y fast velocity
bne @walk_on_ground_ceiling ; branch if spider has a y velocity, indicating it has jumped from ceiling and landed on ground
; or jumped from ground and landed on ceiling
lda ENEMY_VAR_2,x ; load whether or not the spider should jump
beq @walk_on_ground_ceiling ; branch if spider isn't jumping
lda ENEMY_VAR_1,x ; spider should jump, load targeted player (selected at random)
tay ; transfer targeted player index to y
lda SPRITE_Y_POS,y ; load targeted player y position on screen
cmp #$20 ; see if towards the top 12.5% of the screen
bcc @walk_on_ground_ceiling ; don't jump if player is so high
lda ENEMY_X_POS,x ; load enemy x position on screen
sec ; set carry flag in preparation for subtraction
sbc SPRITE_X_POS,y ; subtract targeted player x position from enemy x position
cmp #$30 ; distance for initiating jump
bcs @walk_on_ground_ceiling ; branch if player is too far to not jump yet
lda ENEMY_Y_POS,x ; spider should jump, load enemy y position on screen
cmp SPRITE_Y_POS,y ; player y position on screen
bcs @jump_from_ground ; branch if targeted player is above or at same level as spider
; to jump if player is within #$20 pixels
lda #$02 ; alien spider y velocity while descending to ground from ceiling
sta ENEMY_Y_VELOCITY_FAST,x ; set y fast velocity to 2
lda #$40 ; a = #$40
sta ENEMY_Y_VELOCITY_FRACT,x ; set y fractional velocity to .25
; new y velocity is 2.25
lda PLAYER_WEAPON_STRENGTH ; load player weapon strength
jsr mv_low_nibble_to_high ; move low nibble into high nibble, setting low nibble to all 0
adc ENEMY_Y_VELOCITY_FRACT,x ; add weapon strength * 10 to y velocity
sta ENEMY_Y_VELOCITY_FRACT,x ; set new y fractional velocity, more powerful the weapon, the faster the y velocity
lda ENEMY_Y_VELOCITY_FAST,x ; load spider y fast velocity
adc #$00 ; add any overflow from fractional y velocity
sta ENEMY_Y_VELOCITY_FAST,x ; set new spider y fast velocity
jmp @set_vars_for_jump ; set sprite, velocity, has jumped flag, and set routine to alien_spider_routine_04
@jump_from_ground:
lda ENEMY_X_POS,x ; load enemy x position on screen
sec ; set carry flag in preparation for subtraction
sbc SPRITE_X_POS,y ; subtract sprite y's x position from enemy x position
cmp #$20 ; distance for initiating take off when jumping from ground
; (compare to #$30 when descending from ceiling)
bcs @walk_on_ground_ceiling ; branch to continue alien spider crawling on ground if not close enough
lda #$ff ; a = #$ff
sta ENEMY_Y_VELOCITY_FAST,x ; set alien spider y velocity to -1
lda #$00 ; a = #$00
sta ENEMY_Y_VELOCITY_FRACT,x ; set alien spider y fractional velocity to 0
; set sprite, velocity, has jumped flag, and set routine to alien_spider_routine_04
@set_vars_for_jump:
lda #$b3 ; a = #$b3
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
lda ENEMY_SPRITE_ATTR,x ; load enemy sprite attributes
and #$3f ; keep bits ..xx xxxx (no flip)
sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes
lda ENEMY_Y_VELOCITY_FAST,x
bmi @set_jump_adv_routine
lda #$ff ; a = #$ff
sta ENEMY_X_VELOCITY_FAST,x
lda #$80 ; a = #$80
sta ENEMY_X_VELOCITY_FRACT,x
@set_jump_adv_routine:
inc ENEMY_VAR_3,x ; set spider to jump
inc ENEMY_ROUTINE,x ; move to enemy routine index to alien_spider_routine_04
jmp update_enemy_pos ; apply velocities and scrolling adjust
; spider is landing on ground or ceiling, set y position and stop y velocity
@walk_on_ground_ceiling:
lda #$b8 ; a = #$b8 (spider on ground y position)
cmp ENEMY_Y_POS,x ; compare #$b8 with enemy y position on screen
bcc @continue ; branch to continue if higher than #$b8 on screen (smaller y)
lda #$38 ; spider higher than #$b8, set a = #$38 (spider on ceiling y position)
cmp ENEMY_Y_POS,x ; compare #$38 to enemy y position on screen
bcc @update_pos_stop_y ; branch if spider's y position is greather than #$38 (below ceiling)
; spider's y position is either
; 1. higher than #$b8 (above the ground)
; 2. higher than #$38 (below ceiling)
@continue:
sta ENEMY_Y_POS,x ; enemy y position on screen
@update_pos_stop_y:
jsr update_enemy_pos ; apply velocities and scrolling adjust
jmp set_enemy_y_velocity_to_0 ; set y velocity to zero
; spider is jumping, check for groud/ceiling collision and if collided, go back to alien_spider_routine_03
alien_spider_routine_04:
jsr init_vars_get_enemy_bg_collision ; initialize required memory and call get_enemy_bg_collision to determine bg collision
bcc @update_pos ; branch if no collision with the ground
jsr set_enemy_y_velocity_to_0 ; collided with ground or ceiling, set y velocity to zero
lda #$fe ; a = #$fe, alien spider x velocity after landing (-2)
sta ENEMY_X_VELOCITY_FAST,x ; set enemy fast velocity
lda #$80 ; a = #$80 (.5)
sta ENEMY_X_VELOCITY_FRACT,x ; set enemy fractional velocity
lda #$04 ; a = #$04
sta ENEMY_ROUTINE,x ; set routine back to alien_spider_routine_03
@update_pos:
jmp update_enemy_pos ; apply velocities and scrolling adjust
; alien spider - pointer 2
; set score and collision code, set sprite to egg, set x velocity to -.3125 and y velocity to +-4, advance to alien_spider_routine_02
alien_spider_routine_01:
lda #$33 ; a = #$33
sta ENEMY_SCORE_COLLISION,x ; set score code to 3 (500 points), collision code to 3
jsr set_alien_spider_hp_sprite_attr ; alien spider x/y velocities when out of spider spawn
lda #$b6 ; a = #$b6 (sprite_b6 - alien egg)
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
lda #$ff ; a = #$ff
sta ENEMY_X_VELOCITY_FAST,x ; set fast x velocity to -1
lda #$b0 ; a = #$b0
sta ENEMY_X_VELOCITY_FRACT,x ; set fractional velocity to
; x velocity result in decimal is -0.3125
lda #$fc ; a = #$fc
sta ENEMY_VAR_3,x ; enemy y fast velocity (-4)
lda #$00 ; a = #$00
sta ENEMY_VAR_4,x ; enemy y fractional velocity
jmp advance_enemy_routine ; advance to alien_spider_routine_02
; alien spider while it's still an egg, float to ceiling or fall to ground, once close spawn from egg
alien_spider_routine_02:
lda ENEMY_VAR_4,x ; load y fractional velocity
clc ; clear carry in preparation for addition
adc #$28 ; add gravity when out of spider spawn
sta ENEMY_VAR_4,x ; add #$28 to y fractional velocity
lda ENEMY_VAR_3,x ; load y fast velocity
adc #$00 ; add any overflow from y velocity accumulator
sta ENEMY_VAR_3,x ; update y fast velocity
lda ENEMY_Y_POS,x ; load enemy y position on screen
cmp #$80 ; compare to midway down the screen
bcc @float_to_ceiling ; branch if above the top of the screen to get 'sucked' up to the ceiling
lda ENEMY_VAR_3,x ; below the middle if the screen, gravity pulls spider down, load y fast velocity
sta ENEMY_Y_VELOCITY_FAST,x ; set y fast velocity
lda ENEMY_VAR_4,x ; load updated fractional velocity
sta ENEMY_Y_VELOCITY_FRACT,x ; set y fractional velocity
jmp @check_if_spawn ; jump to apply velocity and see if should 'spawn' from egg
@float_to_ceiling:
lda ENEMY_VAR_3,x ; load y fast velocity
eor #$ff ; flip all bits
sta ENEMY_Y_VELOCITY_FAST,x ; set y fast velocity
lda #$00 ; a = #$00
sec ; set carry flag in preparation for subtraction
sbc ENEMY_VAR_4,x ; subtract y fractional velocity (inverted gravity applied)
sta ENEMY_Y_VELOCITY_FRACT,x ; set new y fractional velocity
@check_if_spawn:
jsr update_enemy_pos ; apply velocities and scrolling adjust
lda ENEMY_Y_POS,x ; load enemy y position on screen
cmp #$c1 ; see if close to the ceiling
bcs @spawn_spider_from_egg ; spawn from egg if close to ceiling
cmp #$30 ; see if close to ground
bcc @spawn_spider_from_egg ; spawn from egg if close to ground
rts ; exit if not near ceiling nor ground
@spawn_spider_from_egg:
lda #$b3 ; a = #$b3 (sprite_b3) boss alien bugger insect/spider (frame 1)
sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer
jmp alien_spider_set_ground_vel_and_routine ; set x velocity to -2.5, set y velocity to 0, choose player to target
; determine whether spider will jump, and set routine to alien_spider_routine_03
; pointer table for spider spawn (15) (#$6 * #$2 = #$c bytes)
alien_spider_spawn_routine_ptr_tbl:
.addr alien_spider_spawn_routine_00 ; CPU address $bbc3 - set spider spawn hp and nametable update supertile index
.addr alien_spider_spawn_routine_01 ; CPU address $bbf0 - cycle animating the opening of the flower and the generation of alien spiders
.addr alien_spider_spawn_routine_02 ; CPU address $bc6d - destroyed routine, draw destroyed super-tile and advance routine
.addr enemy_routine_init_explosion ; CPU address $e74b from bank 7
.addr enemy_routine_explosion ; CPU address $e7b0 from bank 7
.addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7
; set spider spawn hp and nametable update supertile index
; spider spawn hp = (completion count * 2) + *weapon strength * 2) + #$18
alien_spider_spawn_routine_00:
lda GAME_COMPLETION_COUNT ; load the number of times the game has been completed
asl ; double the number of times the game has been completed
sta $08 ; store result in $08
lda PLAYER_WEAPON_STRENGTH ; load player's weapon strength
asl ; double player's weapon strength
adc $08 ; add to doubled game completion count
adc #$18 ; add a base hp of #$18
sta ENEMY_HP,x ; set enemy hp (#$18 + (2 * GAME_COMPLETION_COUNT) + (2 * PLAYER_WEAPON_STRENGTH))
lda RANDOM_NUM ; load random number
adc FRAME_COUNTER ; add frame counter
and #$3f ; keep bits ..xx xxxx
adc #$a0 ; add #$a0
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation delay before spider spawn opens and starts spawning enemies
; random between 0 and #$3f + #$a0
ldy #$a5 ; #$25 - alien spider spawn on ground closed (frame 1) (see level_8_nametable_update_supertile_data)
lda ENEMY_Y_POS,x ; load enemy y position on screen
cmp #$80 ; compare to the middle of the screen
bcs @continue ; branch if towards the ground
ldy #$a1 ; #$21 - alien spider spawn on ceiling closed (frame 1) (see level_8_nametable_update_supertile_data)
@continue:
tya ; transfer to offset register
sta ENEMY_VAR_1,x ; store nametable tile update supertile index (see level_8_nametable_update_supertile_data)
inc ENEMY_ROUTINE,x ; enemy routine index (0 = empty enemy slot)
rts
; cycle animating the opening of the flower and the generation of alien spiders
alien_spider_spawn_routine_01:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda ENEMY_VAR_4,x ; load delay between generating spiders
bne @check_frame_gen_spider ; branch if spider generation delay not elapsed to advance the animation frame if animation delay elapsed
; and possibly generate spider
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
lda ENEMY_ANIMATION_DELAY,x ; load frame animation delay
beq @gen_spider ; branch if delay has elasped, can now generate a spider
cmp #$30 ; compare delay to #$30 (animation happens at #$30 and #$10)
beq @adv_frame ; move to next super-tile in the animation (closed -> partially open -> open)
cmp #$10 ; compare delay to #$10 (animation happens at #$30 and #$10)
beq @adv_frame ; move to next super-tile in the animation (closed -> partially open -> open)
rts
@gen_spider:
lda ENEMY_ATTACK_FLAG ; see if enemies should attack (1 = yes, 0 = no)
beq @continue ; branch if shouldn't attack
lda #$14 ; a = #$14 (14 = alien spider)
jsr generate_enemy_a ; generate #$14 enemy (alien spider)
bne @continue ; branch if unable to generate alien spider
lda #$02 ; generated alien spider, a = #$02
sta ENEMY_ROUTINE,y ; set newly created alien spider's enemy routine index to alien_spider_routine_01
; this skips alien_spider_routine_00, which sets initial ground/ceiling velocities
; determine delay between generating spiders based on weapon code
@continue:
lda P1_CURRENT_WEAPON ; load player 1 current weapon code (and rapid fire flag)
ora P2_CURRENT_WEAPON ; merge with player 2 current weapon code (and rapid fire flag)
and #$07 ; keep bits .... .xxx
sta $08 ; store merged weapon code in $08
lda #$0a ; a = #$0a
sec ; set carry flag in preparation for subtraction
sbc $08 ; #$0a - merged weapon code
sta ENEMY_VAR_4,x ; store calculated delay between alien spider generation
lda FRAME_COUNTER ; load frame counter
adc RANDOM_NUM ; randomizer
and #$03 ; keep bits .... ..xx
adc ENEMY_VAR_4,x ; add random number between #$00 and #$3 inclusively to delay
sta ENEMY_VAR_4,x ; update delay between alien spider generation
lda #$14 ; a = #$14 (related to delay between spawns)
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter before starting cycle over
@exit:
rts
@adv_frame:
inc ENEMY_VAR_1,x ; increment nametable super-tile index (see level_8_nametable_update_supertile_data)
inc ENEMY_VAR_3,x ; increment animation frame number (easier to keep track of which super-tile is being drawn)
lda ENEMY_VAR_3,x ; load animation frame number
cmp #$03 ; see if past the last frame (alien spider spawn fully open)
bcc @draw_supertile ; branch if super-tile to draw isn't past last animation frame
dec ENEMY_VAR_1,x ; go back to frame 1 by subtracting 2 from ENEMY_VAR_1 (actual index) and ENEMY_VAR_3 (frame number)
dec ENEMY_VAR_1,x ; go back to first super-tile (alien spider spawn closed (frame 1))
dec ENEMY_VAR_3,x
dec ENEMY_VAR_3,x
@draw_supertile:
lda ENEMY_VAR_1,x ; load super-tile to draw
jmp draw_enemy_supertile_a ; draw super-tile a at position (ENEMY_X_POS, ENEMY_Y_POS)
; advance the animation frame if delay elapsed and generate spider if generation delay elapsed
@check_frame_gen_spider:
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
bne @exit ; exit if delay hasn't elapsed
lda #$14 ; delay elapsed, a = #$14
sta ENEMY_ANIMATION_DELAY,x ; reset enemy animation frame delay counter
jsr @adv_frame ; move to the next super-tile
dec ENEMY_VAR_4,x ; decrement delay between generating spiders
beq @gen_spider ; generate a spider if delay has elapsed
rts
; destroyed routine, draw destroyed super-tile and advance routine
alien_spider_spawn_routine_02:
ldy #$a8 ; #$28 - destroyed alien spider spawn on ground (see level_8_nametable_update_supertile_data)
lda ENEMY_Y_POS,x ; load enemy y position on screen
cmp #$80 ; see if spawn is above or below the middle of the screen
bcs @draw_supertile_adv_routine ; branch if below
ldy #$a4 ; #$24 - destroyed alien spider spawn on ceiling (see level_8_nametable_update_supertile_data)
@draw_supertile_adv_routine:
tya ; transfer to offset register
jsr draw_enemy_supertile_a ; draw super-tile a at position (ENEMY_X_POS, ENEMY_Y_POS)
bcc @adv_enemy_routine ; advance routine if able to update super-tile
rts ; exit to try again next loop
@adv_enemy_routine:
jmp advance_enemy_routine ; advance to next routine
; pointer table for final boss heart (#$8 * #$2 = #$10 bytes)
boss_heart_routine_ptr_tbl:
.addr boss_heart_routine_00 ; CPU address $bc92 - set heart hp, animation delay, and advance routine
.addr boss_heart_routine_01 ; CPU address $bc9d
.addr boss_heart_routine_02 ; CPU address $bcb4
.addr boss_heart_routine_03 ; CPU address $bd4c
.addr alien_guardian_routine_04 ; CPU address $b6b2 - create series of explosions, each with a #$05 frame delay before next explosion
.addr boss_heart_routine_05 ; CPU address $bceb
.addr boss_heart_routine_06 ; CPU address $bcf8
.addr alien_guardian_routine_0b ; CPU address $bd02
; set heart hp, animation delay, and advance routine
boss_heart_routine_00:
jsr set_guardian_and_heart_enemy_hp ; calculate heart hp
lda #$0a ; a = #$0a (delay before first heartbeat)
sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter
; (wait for AUTO_SCROLL_TIMER_00 set in set_boss_auto_scroll)
jmp advance_enemy_routine ; advance to next routine
; heart - pointer 2
; wait for boss auto scroll, advance routine
boss_heart_routine_01:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter
beq @animation_delay_elapsed
rts ; exit if animation time has not elapsed
@animation_delay_elapsed:
inc ENEMY_VAR_2,x
lda ENEMY_HP,x ; load enemy hp
lsr
bne @set_delay_adv_routine
lda #$01 ; set animation delay counter to #$01
@set_delay_adv_routine:
jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a
; advance enemy routine
; heart - pointer 3
boss_heart_routine_02:
jsr add_scroll_to_enemy_pos ; add scrolling to enemy position
lda ENEMY_VAR_2,x
beq @continue
inc ENEMY_VAR_4,x
lda ENEMY_VAR_4,x
and #$01 ; keep bits .... ...x
asl
clc ; clear carry in preparation for addition
adc #$03
jsr update_alien_boss_supertiles ; updates 2 nametable tiles for boss heart animation
lda $0b
sta ENEMY_VAR_2,x
rts
@continue:
lda ENEMY_VAR_4,x
and #$01 ; keep bits .... ...x
asl
clc ; clear carry in preparation for addition
adc #$04 ; determine correct entry in alien_boss_supertile_tbl
jsr update_alien_boss_supertiles ; updates 2 nametable tiles for boss heart animation
lda $0b
sta ENEMY_VAR_2,x
beq @set_boss_heart_routine_01
rts
@set_boss_heart_routine_01:
lda #$02 ; a = #$02 (boss_heart_routine_01)
sta ENEMY_ROUTINE,x ; enemy routine index
rts
; heart - pointer 6
boss_heart_routine_05:
lda #$07 ; a = #$07
jsr update_alien_boss_supertiles ; updates 2 nametable tiles for boss heart animation (destroyed)
lda $0b
beq boss_heart_destroyed_adv_routine
rts
boss_heart_destroyed_adv_routine:
jmp advance_enemy_routine ; advance to next routine
; heart - pointer 7
boss_heart_routine_06:
lda #$08 ; a = #$08
jsr update_alien_boss_supertiles ; updates 2 nametable tiles for boss heart animation (destroyed)
lda $0b
beq boss_heart_destroyed_adv_routine
rts
; alien guardian - pointer c
; heart - pointer 8
; destroy all enemies
alien_guardian_routine_0b:
ldx #$0f ; x = #$0f
; looks for specific enemies and runs routine appropriate destroy routine
@destroy_enemy_loop:
lda ENEMY_TYPE,x ; load current enemy type
cmp #$14 ; ENEMY_TYPE #$14 (alien spider)
beq @spider_destroy ; branch if alien spider
cmp #$15 ; ENEMY_TYPE #$15 (spider spawn)
beq @spawn_mouth_fetus_destroy ; branch if spider spawn
cmp #$12 ; ENEMY_TYPE #$12 (alien mouth)
beq @spawn_mouth_fetus_destroy ; branch if alien mouth
cmp #$13 ; ENEMY_TYPE #$13 (white blob)
beq @white_blob_destroy ; branch if white blob
cmp #$11 ; ENEMY_TYPE #$11 (alien fetus)
beq @spawn_mouth_fetus_destroy ; alien fetus
@move_next_enemy:
dex ; move down to next enemy slot
bne @destroy_enemy_loop ; move to next enemy to see if should handle
ldx ENEMY_CURRENT_SLOT ; restore enemy current slot back to x
lda #$a0 ; a = #$a0
jmp set_delay_remove_enemy ; set delay to a and remove enemy
; alien_spider_spawn_routine_02
; alien_mouth_routine_02
; enemy_routine_init_explosion
@spawn_mouth_fetus_destroy:
lda #$03 ; a = #$03
bne @set_routine_move_next_enemy ; set routine to start destroying enemy
; enemy_routine_init_explosion
@spider_destroy:
lda #$06 ; a = #$06
@set_routine_move_next_enemy:
sta ENEMY_ROUTINE,x ; set enemy slot (0 = empty)
bne @move_next_enemy
; enemy_routine_init_explosion
@white_blob_destroy:
lda #$04 ; a = #$04
bne @set_routine_move_next_enemy
; table for explosions relative positions for heart (#$c * #$2 = #$18 bytes)
alien_guardian_explosion_offset_tbl:
.byte $10,$10 ; #$10 , #$10 - unused !(UNUSED) (see alien_guardian_routine_04)
.byte $f0,$10 ; -#$10 , #$10
.byte $f0,$f0 ; -#$10 , -#$10
.byte $10,$f0 ; #$10 , -#$10
.byte $20,$20 ; #$20 , #$20
.byte $e0,$20 ; -#$20 , #$20
.byte $e0,$e0 ; -#$20 , -#$20
.byte $20,$e0 ; #$20 , -#$20
.byte $40,$40 ; #$40 , #$40
.byte $c0,$40 ; -#$40 , #$40
.byte $c0,$c0 ; -#$40 , -#$40
.byte $50,$00 ; #$50 , #$00
; heart - pointer 4
boss_heart_routine_03:
jsr init_APU_channels
lda #$57 ; a = #$57 (sound_57) - boss destroyed
jsr level_boss_defeated ; play boss destroyed sound and initiate auto-move
jmp create_boss_heart_explosion ; start animation of heart explosion
; adds a to ENEMY_X_POS and stores result in $0a
; $0a and $09 are used in draw_alien_guardian_supertile
set_nametable_x_pos_for_alien_guardian:
ldy #$00 ; y = #$00
beq set_nametable_pos_for_alien_guardian ; always jump
lda #$00 ; dead code, never called !(UNUSED)
; adds a to ENEMY_X_POS and stores result in $0a
; adds y to ENEMY_Y_POS and stores result in $09
; $0a and $09 are used in draw_alien_guardian_supertile
set_nametable_pos_for_alien_guardian:
clc ; clear carry in preparation for addition
adc ENEMY_X_POS,x ; add to enemy x position on screen
sta $0a
tya
clc ; clear carry in preparation for addition
adc ENEMY_Y_POS,x ; add to enemy y position on screen
sta $09
rts
; unused #$295 bytes out of #$4,000 bytes total (95.96% full)
; unused 661 bytes out of 16,384 bytes total (95.96% full)
; filled with 661 #$ff bytes by contra.cfg configuration
bank_0_unused_space: