210 lines
9.8 KiB
Markdown
210 lines
9.8 KiB
Markdown
# Overview
|
|
|
|
Enemy logic is controlled by enemy routines, every enemy has a set of routines
|
|
that it can execute. The list of routines per enemy is specified in the
|
|
`*_routine_ptr_tbl` tables all in bank 7. Which enemies are in which screen of
|
|
a level are specified in the `enemy_routine_ptr_tbl` (for shared enemies) or one
|
|
of the 7 `enemy_routine_level_x` tables. Level 2 and level 4 share the same
|
|
enemy routine `enemy_routine_level_2_4` table.
|
|
|
|
Upon execution of `level_routine_04` and `level_routine_0a`, every enemy is
|
|
given the opportunity to execute logic specific to that enemy type. This is
|
|
done by looping down #$f to #$0 through the `ENEMY_ROUTINE` memory addresses in
|
|
the `exe_all_enemy_routine` method. Enemies are added to the end going down,
|
|
i.e. they start at #$f and go down.
|
|
|
|
# Generation
|
|
|
|
Enemies can be generated in the level in one of two ways: random generation, and
|
|
level-specific hard-coded locations.
|
|
|
|
# Level Enemies
|
|
Each level defines hard-coded locations on each screen where an enemy will be
|
|
generated. These enemies are always at that location in the level. This is
|
|
defined in the `level_enemy_screen_ptr_ptr_tbl` in bank 2. There are #$08
|
|
2-byte entries in this table, one for each level, e.g.
|
|
`level_1_enemy_screen_ptr_tbl`. Each 2-byte entry is a memory address to
|
|
another table of addresses, e.g. `level_1_enemy_screen_02`. Each entry here
|
|
defines the enemies within a single screen of a level. Screen 0 does never has
|
|
enemies, so the first entry in this table is associated to the second screen of
|
|
the level. There is always one more entry for a level than there are screens.
|
|
|
|
## Data Structure
|
|
|
|
### Outdoor levels
|
|
For a given screen, each enemy is defined with at least #$03 bytes. For
|
|
example, the first enemy defined on `level_1_enemy_screen_00` is
|
|
`.byte $10,$05,$60`. These three bytes define a soldier who runs left, but
|
|
doesn't shoot. These bytes need to be broken up into bits to further understand
|
|
their meaning.
|
|
|
|
```
|
|
0001 0000 0000 0101 0110 0000
|
|
XXXX XXXX RRTT TTTT YYYY YAAA
|
|
```
|
|
|
|
* `X` - X offset
|
|
* `R` - Repeat
|
|
* `T` - Enemy Type
|
|
* `Y` - Y Offset
|
|
* `A` - Enemy Attribute
|
|
|
|
### Byte 1 - XX byte
|
|
The first byte, #$10, from the example above specifies the x position of the
|
|
enemy.
|
|
|
|
### Byte 2 - Repeat and Enemy Type
|
|
The second byte, #$05, from the example above defines two things: repeat, and
|
|
enemy type. The most significant 2 bits define the number of times to repeat
|
|
an enemy, the least significant 6 bits define the enemy type. To see a list of
|
|
all enemy types and what they are, see `Enemy Glossary.md`. For example, #$05
|
|
has a repeat of 0 and a enemy type of #$05. #$05 is the soldier.
|
|
|
|
If the repeat value is 0, then the enemy is not repeated and will take a total
|
|
of #$3 bytes. However, if there is a repeat, for each repetition, one more byte
|
|
is added and has the same structure as the `Y Offset and Attribute` byte. This
|
|
means an enemy with a repeated enemy will have the same XX position and the same
|
|
type, but have its own Y position and attributes.
|
|
|
|
Here is an example of a screen enemy definition with a repeat
|
|
|
|
```
|
|
level_1_enemy_screen_09:
|
|
.byte $10,$43,$40,$b4 ; flying capsule (enemy type #$03), attribute: 000 (R), location: (#$10, #$40)
|
|
; repeat: 1 [(y = #$b0, attr = 100)]
|
|
.byte $e0,$07,$81 ; red turret (enemy type #$07), attribute: 001, location: (#$e0, #$80)
|
|
.byte $ff
|
|
```
|
|
|
|
### Byte 3 - Y Offset and Attribute
|
|
The third byte, #$60, from the example above defines the vertical position of
|
|
the enemy as well as that enemy's attributes. The #$05 most significant bits
|
|
specify the vertical offset and the least significant 3 bits are for the
|
|
attributes. Each enemy can use the 3 attribute bits however they see fit. For
|
|
example, a soldier uses the attributes to know which way to start running, and
|
|
whether or not the soldier fires bullets from their gun. For a detailed list of
|
|
each enemy type and their attributes, see `Enemy Glossary.md`.
|
|
|
|
### Indoor/Base Levels
|
|
|
|
```
|
|
XXXX YYYY CDTT TTTT AAAA AAAA
|
|
```
|
|
|
|
* `X` - X offset
|
|
* `Y` - Y Offset
|
|
* `C` - Whether or not to add #$08 to Y position
|
|
* `D` - Whether or not to add #$08 to X position
|
|
* `T` - Enemy Type
|
|
* `A` - Enemy Attribute
|
|
|
|
# Enemy Destroyed
|
|
|
|
When an enemy is determined to be destroyed, e.g. their `ENEMY_HP` has gone to 0
|
|
after collision with a bullet, then the enemy routine for the active enemy is
|
|
immediately adjusted to a routine index specified by
|
|
`enemy_destroyed_routine_xx`. These are grouped in the
|
|
`enemy_destroyed_routine_ptr_tbl`.
|
|
|
|
For example, when a soldier is destroyed, `enemy_destroyed_routine_01` specifies
|
|
byte #$05 for the soldier. This corresponds to `soldier_routine_04`
|
|
|
|
# Soldier Generation
|
|
|
|
In addition to the hard-coded screen-specific enemies that appear in the same
|
|
location every play through (specified in the `level_x_enemy_screen_xx` data
|
|
structures). _Contra_ generates soldiers at regular intervals with slightly
|
|
random enemy logic so that each play through has a different experience.
|
|
|
|
When playing a level, the game state is in `game_routine_05`, and specifically
|
|
in `level_routine_04`. `level_routine_04` is run every frame. One part of
|
|
`level_routine_04` is to run the logic to determine if an enemy soldier (enemy
|
|
type #$05) should be created. This logic is in bank 2's
|
|
`exe_soldier_generation` method.
|
|
|
|
`exe_soldier_generation` runs one of three soldier generation routines depending
|
|
on the current value of `SOLDIER_GENERATION_ROUTINE`. Initially, this value is
|
|
#$00 and `soldier_generation_00` is executed.
|
|
|
|
## soldier_generation_00
|
|
`soldier_generation_00` initializes a level-specific timer that controls the
|
|
speed of soldier generation. This timer is subsequently adjusted based on the
|
|
number of times the game has been completed, and the player's current weapon
|
|
strength. Every time the game has been completed (max of 3 times), #$28 is
|
|
subtracted from the initial level-specific soldier generation timer.
|
|
Additionally, the player weapon strength multiplied by #$05 is subtracted from
|
|
the soldier generation timer.
|
|
|
|
For example, level 3 (waterfall) has an initial level-specific timer value of
|
|
#$d8 (specified in the `level_soldier_generation_timer` table). If the player
|
|
has beaten the game once and has a `PLAYER_WEAPON_STRENGTH` of #$03 (S weapon),
|
|
then the computed soldier generation timer would be #$a1.
|
|
|
|
```
|
|
#$a1 = #$d8 - (#$01 * #$28) - (#$05 * #$03)
|
|
```
|
|
|
|
Soldier generation is disabled on the indoor/base levels (level 2 and level 4)
|
|
along with level 8 (alien's lair). They are disabled by a value of #$00 being
|
|
specified in the `level_soldier_generation_timer` table. For these levels, no
|
|
other soldier generation routine will be run, only `soldier_generation_00`.
|
|
|
|
Once the soldier generation timer has been initialized and adjusted, the
|
|
`SOLDIER_GENERATION_ROUTINE` is incremented so that the next game loop's
|
|
`exe_soldier_generation` causes `soldier_generation_01` to execute.
|
|
|
|
## soldier_generation_01
|
|
|
|
`soldier_generation_01` is responsible for decrementing the soldier generation
|
|
timer until it elapses. Then it is responsible for creating the soldier, if
|
|
certain conditions are met. This includes randomizing the soldier's location and
|
|
enemy attributes.
|
|
|
|
`soldier_generation_01` will first look at the current soldier generation timer,
|
|
if the timer is not yet less than #$00, then the timer is decremented by #$02,
|
|
unless the frame is scrolling on an odd frame number. Then the timer is only
|
|
decremented by #$01.
|
|
|
|
Once the soldier generation timer has elapsed, the routine looks for an
|
|
appropriate location to generate the soldier on the screen. Soldiers are
|
|
always generated from the left or right edge of the screen. First the starting
|
|
horizontal position is determined. This is essentially determined randomly by
|
|
the current frame number and values in the `gen_soldier_initial_x_pos` table.
|
|
The result will be either the left edge (#$0a) or the right edge (#$fa or #$fc).
|
|
|
|
There is an exception for level one. Until a larger number of soldiers have
|
|
already been generated, soldiers will only appear from the right, probably to
|
|
make the beginning of the game slightly easier.
|
|
|
|
Once the x position is decided, the routine will start looking for a vertical
|
|
location that has a ground for the soldier to stand on. It does this in one of
|
|
3 ways randomly to ensure soldiers are generated from multiple locations if
|
|
possible. The 3 methods are from top of the screen to the bottom, from the
|
|
bottom of the screen to the top, and from the player vertical position up to the
|
|
top.
|
|
|
|
If a horizontal and vertical position is found where a soldier can be placed on
|
|
the ground, then some memory is updated to specify the location and the soldier
|
|
generation routine is incremented to `soldier_generation_02`.
|
|
|
|
## soldier_generation_02
|
|
|
|
At this point, a location is found for the soldier to generate.
|
|
`soldier_generation_02` is responsible for actually initializing and creating
|
|
the soldier. Some checks are performed to make sure it's appropriate to
|
|
generate a soldier, for example, when `ENEMY_ATTACK_FLAG` is set to #$00 (off),
|
|
then a soldier will not be generated. Other checks include that there are no
|
|
solid blocks (collision code #$80) right in front of the soldier to generate,
|
|
and that there is no player right next to the edge of the screen where the
|
|
soldier would be generated from (this check doesn't happen after beating the
|
|
game at least once). If any checks determine that the soldier should not be
|
|
generated, then the routine resets the `SOLDIER_GENERATION_ROUTINE` back to #$00
|
|
and stops.
|
|
|
|
To randomize the various behaviors of the generated soldiers, this routine will
|
|
look up initial behavior from one of the `soldier_level_attributes_xx` tables
|
|
based on the level. This will randomize the soldier direction, whether or not
|
|
the soldier will shoot and how frequently, and a value specifying the
|
|
probability of ultimately not generating the soldier. Finally, the soldier is
|
|
generated and the values are moved into the standard enemy memory location
|
|
addresses, creating the soldier. |