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

192 lines
11 KiB
Markdown

# 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 | 0° |
| #$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
```mermaid
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;
```