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.
Enemies can be generated in the level in one of two ways: random generation, and level-specific hard-coded locations.
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.
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 offsetR
- RepeatT
- Enemy TypeY
- Y OffsetA
- Enemy Attribute
The first byte, #$10, from the example above specifies the x position of the enemy.
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
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
.
XXXX YYYY CDTT TTTT AAAA AAAA
X
- X offsetY
- Y OffsetC
- Whether or not to add #$08 to Y positionD
- Whether or not to add #$08 to X positionT
- Enemy TypeA
- Enemy Attribute
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
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
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
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
.
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.