The missile block is an enemy that deals no damage and behaves like a normal solid block. When it is shot at with a missile, the missile block starts falling to the left (to the right if the missile faced right), and explodes when touching solid ground. Contrary to most enemies, the missile block's graphics are loaded at all times in VRAM. My goal is to turn the missile block into a POW Block: when samus shoots a missile on the block, it deletes all enemies.
We will take a look at its AI, but first, I want to place one near Samus's spawn point for ease of testing. Enemy placement on the map is defined in “SRC/maps/enemyData.asm”. For each screen in each map bank, there is a list of enemies for that screen, ending in $FF. Each enemy entry has 4 bytes: spawn number, sprite number, x, y. I'll replace the missile refill in the gunship with a missile block.
Challenge: Using “SRC/data/initialSave.asm” to learn Samus's position when starting a new game, figure out which screen contains the missile refill in the gunship. You should find whick bank the screen is in, as well as its x and y coordinates within that bank. The answer is in this footnote 1).
Based on the screen coordinates you've either found by yourself or read from the footnote, you can find the enemy entry for the missile refill in “SRC/maps/enemyData.asm”. To replace the missile refill with a missile block, we can change the second byte to $75, because in “SRC/data/enemies.csv”, this is the sprite ID for the missile block. Then, we can decrease the fourth byte to $60, to move it above the gunship. Compile and test that it works.
The missile block AI enAI_missileBlock
is more complex than the Yumbo's AI. It is a state machine: it can only be in one of 4 states at a time, depending on hEnemy.state
's value. For my POW Block, I only need the default state of the missile block, state $00.
The first thing the enemy AI does is calling enemy_getSamusCollisionResults
. This function loads which one of Samus's weapons just collided with the enemy into the enemy_weaponType
variable. In order for enemies to have abnormal reactions to weaponry like the missile block does, they typically are invincible, and use this function to handle collisions with specific weapons themselves. Go see the comment above enemy_getSamusCollisionResults
's definition to see the possible values for each weapon type.
After the state checks comes the default state. If enemy_weaponType
is greater or equal to $20, meaning if Samus either touches the enemy with her body or if she doesn't attack it in any way, return from the routine.
Then a request to the sound engine to start playing the “plink” sound effect of sound channel 1 is put in. The sound engine lives in bank 4 and handles both music and sound effects. For example, the list of sound effects of channel 1 can be found in the comments next to each pointer in the square1Sfx_initPointers
table. Since the sound engine runs only at the end of each frame, it will not notice the request until after the frame ends. This means that the value of sfxRequest_square1
may change without consequence any number of times before then.
Return if Samus didn't shoot a missile at the enemy, then clear the “plink” sound effect request and request the “missile hit” sound effect of sound channel 4 (noise channel) instead. This means that if Samus attacked the enemy with the wrong weapon, the “plink” sound effect request will still be there, because we returned before we cleared it. After this, there is the code to start falling, rightwards only if the missile faced right, and change state to the falling state.
I have read the rest of the code in the enemy AI routine and I can tell none of it will be useful to me. I will delete all code from that routine starting after the request of the “missile hit” sound effect and put a ret
to return from the AI routine after that request. I will also delete the check for state, since it is now useless. Compile and test. The missile block should no longer start falling when shot at with a missile, but should still play the “missile hit” sound effect.
OK, now for the last step: deleting all enemies when missile hits the block. deactivateAllEnemies
is the function called when room transitions occur to delete all enemies before moving to the next room. We're going to call this function in the enemy AI, just before returning.
Compile and test. Samus shoots the block with a missile and… the game crashes.
Well that didn't go as planned. What happened? Why did adding that call crash the game? We don't know yet, but this is something we will figure out ourselves in the next part of this tutorial, where we learn the basics of a debugger.