nes-contra-us/docs/Aim Documentation.md

11 KiB

Aiming

16 enemies aim towards the player to target them. Some of those enemies, for example a sniper (enemy type = #$06), will aim towards the closest player and fire bullets. Others will track the player actively. For example, the white blobs (enemy type = #$13) will follow the player for a specified amount of time. Note that some enemies will fire, but they aren't aiming like the scuba soldier, or even the regular soldier.

Some enemies can aim in more directions than others. For example, snipers (enemy type = #$06) can aim in #$17 directions, whereas rotating guns (enemy type = #$04) can only aim in #$0b directions. The number of directions that an enemy can aim are determined by which table is used when aiming.

  • quadrant_aim_dir_00 is used for outdoor enemies and has #$03 aiming directions per quadrant for a total of #$0b aiming directions.
  • quadrant_aim_dir_01 is used for all indoor/base enemies, tank, sniper, white blob and spinning bubbles. The table has values #$06 aiming directions per quadrant for a total of #$17 aiming directions.
  • quadrant_aim_dir_02 is only used by dragon arm when seeking (stage 3 boss) and has #$0f aiming directions per quadrant for a total of #$3b aiming directions.

Aiming direction values start at #$00, which represents 3 o'clock. Incrementing the aiming directions moves the direction clockwise.

The main method to determine what value within a quadrant to aim is get_quadrant_aim_dir. I refer to this value as the "quadrant aim direction" as specifies the index within a quadrant to aim. The quadrant aim direction along with the quadrant is combined to determine the full aim direction.

The get_quadrant_aim_dir method targets the location ($0b, $0a) from position ($09, $08). This location is almost always the location of the closest player to the enemy on the x-axis, which is determined by the method player_enemy_x_dist. However, white blobs (enemy type = #$13) will target players randomly based on the frame counter. get_quadrant_aim_dir determines where the player is in relation to the enemy, i.e. above or below, to the left or to the right. It will also calculate an index into a single quadrant that most closely targets the player position based on quadrant_aim_dir_xx. The quadrant aim direction is then converted to a full aim direction according to the table below. Essentially, the quadrant code is either added or subtracted from the x-axis aim direction depending on the relative quadrant of the player in relation to the enemy.

Quadrant Math Description
I new_aim_dir = max_aim_dir - new_aim_dir result subtracted from 3 o'cock aim direction
II new_aim_dir = max_aim_dir - mid_aim_dir + new_aim_dir result added to 9 o'clock aim direction
III new_aim_dir = mid_aim_dir - new_aim_dir result subtracted from 9 o'clock aim direction
IV new_aim_dir no math

Many enemies will use the full aim direction value to set their graphic to the appropriate value. For example, the rotating gun will take the calculated full aim direction from aim_var_1_for_quadrant_aim_dir_00, adjust it by adding or subtracting, then setting the super-tile to the value as an offset into level_xx_nametable_update_supertile_data.

Bullets

When creating bullets, the value returned by get_quadrant_aim_dir is converted into the full aim direction. Then the full aim direction is used by calc_bullet_velocities as an offset into bullet_fract_vel_dir_lookup_tbl. This value is then used as an offset into bullet_fract_vel_tbl. These values, along with the bullet speed code and quadrant, are used to determine the bullet x and y velocities.

For most enemy generated bullets, the method aim_and_create_enemy_bullet is used. This method calls get_quadrant_aim_dir and calc_bullet_velocities. However, some enemies instead call get_quadrant_aim_dir_for_player directly and then call calc_bullet_velocities separately to set the bullet velocities.

Enemy Type Calling Method
Wall Turret #$13 aim_and_create_enemy_bullet
Core #$14 aim_and_create_enemy_bullet
Dragon Tentacle Orb #$15 aim_and_create_enemy_bullet
Jumping Soldier #$16 aim_and_create_enemy_bullet
Boss Eye Fire Ring Projectile #$1b get_quadrant_aim_dir_for_player
Gardegura #$1d get_quadrant_aim_dir_for_player
Rangel #$1f aim_and_create_enemy_bullet

All of the enemies using this pattern to create bullets use quadrant_aim_dir_01, which gives quadrant offsets between 0-6 inclusively. Since each quadrant has 6 values, there are 24 (#$18) possible aim directions. Below is the table that is used to to determine the velocity from the calculated full aim direction. Once the velocity is obtained from this table, the quadrant is incorporated to adjust the signs (+/-) on the direction. Finally, the velocity is adjusted again based on bullet speed code (adjust_bullet_velocity).

bullet_fract_vel_dir_lookup_tbl bullet_fract_vel_tbl x vel y vel degree off axis
#$00 #$00 #$ff (.9961) #$00
#$01 #$02 #$f7 (.9648) #$42 (.2578) 15°
#$02 #$04 #$dd (.8633) #$80 (.5) 30°
#$03 #$06 #$b5 (.7070) #$b5 (.7070) 45°
#$04 #$08 #$80 (.5) #$dd (.8633) 60°
#$05 #$0a #$42 (.2578) #$f7 (.9648) 75°
#$06 #$0c #$00 #$ff (.9961) 90°
#$07 #$0a #$42 (.2578) #$f7 (.9648) 75°
#$08 #$08 #$80 (.5) #$dd (.8633) 60°
#$09 #$06 #$b5 (.7070) #$b5 (.7070) 45°
#$0a #$04 #$dd (.8633) #$80 (.5) 30°
#$0b #$02 #$f7 (.9648) #$42 (.2578) 15°
#$0c #$00 #$00 #$ff (.9961) 90°
#$0d #$02 #$f7 (.9648) #$42 (.2578) 15°
#$0e #$04 #$dd (.8633) #$80 (.5) 30°
#$0f #$06 #$b5 (.7070) #$b5 (.7070) 45°
#$10 #$08 #$80 (.5) #$dd (.8633) 60°
#$11 #$0a #$42 (.2578) #$f7 (.9648) 75°
#$12 #$0c #$00 #$ff (.9961) 90°
#$13 #$0a #$42 (.2578) #$f7 (.9648) 75°
#$14 #$08 #$80 (.5) #$dd (.8633) 60°
#$15 #$06 #$b5 (.7070) #$b5 (.7070) 45°
#$16 #$04 #$dd (.8633) #$80 (.5) 30°
#$17 #$02 #$f7 (.9648) #$42 (.2578) 15°

Dragon seeking

Dragon Tentacle Orb (enemy type = #$15) is the only enemy type that uses quadrant_aim_dir_02. It uses this during the 'arm seeking player' attack pattern (attack pattern #$04). quadrant_aim_dir_02 has the most precision with #$0f aim directions per quadrant.

Aim Call Flow

flowchart TD
    get_rotate_dir_for_index --> get_quadrant_aim_dir
    get_rotate_00 --> get_rotate_dir_for_index
    get_rotate_01 --> get_rotate_dir_for_index
    rotating_gun_routine_03:::enemy --> aim_var_1_for_quadrant_aim_dir_00
    alien_fetus_routine_01:::enemy --> aim_var_1_for_quadrant_aim_dir_00
    red_turret_routine_03:::enemy --> get_rotate_00
    white_blob_routine_00:::enemy --> get_rotate_01
    sniper_routine_02:::enemy --> get_rotate_01
    aim_var_1_for_quadrant_aim_dir_00 --> get_rotate_00
    aim_var_1_for_quadrant_aim_dir_01 --> get_rotate_01
    spinning_bubbles_routine_01:::enemy --> aim_var_1_for_quadrant_aim_dir_01
    tank_routine_02:::enemy --> aim_var_1_for_quadrant_aim_dir_01
    white_blob_aim_to_player:::enemy --> aim_var_1_for_quadrant_aim_dir_01
    spinning_bubbles_routine_00:::enemy --> get_quadrant_aim_dir_for_player
    eye_projectile_routine_00:::enemy --> get_quadrant_aim_dir_for_player
    dragon_arm_orb_seek_should_move:::enemy --> get_quadrant_aim_dir_for_player
    dragon_arm_orb_fire_projectile:::enemy --> aim_and_create_enemy_bullet
    aim_and_create_enemy_bullet --> get_quadrant_aim_dir_for_player
    aim_and_create_enemy_bullet --> get_quadrant_aim_dir
    get_quadrant_aim_dir_for_player --> get_quadrant_aim_dir
    wall_turret_routine_03:::enemy --> aim_and_create_enemy_bullet
    wall_core_routine_03:::enemy --> aim_and_create_enemy_bullet
    jumping_soldier_routine_01:::enemy --> aim_and_create_enemy_bullet
    red_soldier_routine_02:::enemy --> aim_and_create_enemy_bullet
    get_rotate_dir_for_index --> get_quadrant_aim_dir_for_player

classDef enemy fill:#f96

Pseudocode

Below is a c-like pseudo-code showing how the quadrant aim direction from get_quadrant_aim_dir value is converted to a full aim direction in get_rotate_dir.

int quadrant_aim_dir = get_quadrant_aim_dir();

// mid_aim_dir is 9 o'clock
// max_aim_dir is 3 o'clock
int mid_aim_dir, max_aim_dir, new_aim_dir;
if (quadrant_aim_dir_00 || quadrant_aim_dir_02) {
  mid_aim_dir = 0x06;
  max_aim_dir = 0x0c;
} else {
  mid_aim_dir = 0x0c;
  max_aim_dir = 0x18;
}

if(player_left_of_enemy) {
  new_aim_dir = mid_aim_dir - quadrant_aim_dir;
}

if(player_above_enemy) {
  if(quadrant_aim_dir != 0x00) {
    new_aim_dir = max_aim_dir - new_aim_dir;
  } else {
    new_aim_dir = 0x00;
  }
}

// new full aim direction calculated
new_aim_dir = quadrant_aim_dir;