commit 8db7384d89ad3021448337db777c44e1ac3ce19b Author: Michael Miceli Date: Wed Apr 26 20:04:47 2023 -0400 v1.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f99d307 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.o +*.dbg +*.deb +*.nes +*.prg +*.bin \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..98d3203 --- /dev/null +++ b/README.md @@ -0,0 +1,232 @@ +# Overview +This repository contains an annotated disassembly of the _Contra_ (US) NES ROM +and the build script(s) to reassemble the assembly into a byte-for-byte match of +the game. This repo also contains supplemental documentation, diagrams, +scripts, and tools to further understand the game. + +A special thanks goes to Trax. This project would not have gotten started +without the amazing initial disassembly by him for his [Revenge of the Red +Falcon](https://www.romhacking.net/hacks/2701/) project. + +``` +|-- docs - supplemental documentation +| |-- attachments - files used in other documentation +| |-- diagrams - mermaid diagram files documenting program flow +| |-- lua_scripts - lua scripts for mesen and fceux +| |-- sprite_library - extracted sprites for ease of viewing +|-- src - the source code for the game +| |-- assets - the compressed graphics data and encoded audio for the game +|-- assets.txt - a list of assets, their offset in baserom.nes and their length +|-- build.bat - build script for Windows cmd (no PowerShell) +|-- build.ps1 - recommended build script for Windows (PowerShell) +|-- build.sh - bash build script for Linux/mac +|-- contra.cfg - memory mapping of PRG banks used when building +|-- README.md +|-- set_bytes.vbs - script used by build.bat to extract data from baserom.nes +``` +# Building + +## Prerequisites + * This repo does not include all assets (graphics data and audio data) + necessary for assembling the ROM. An existing copy of the game is required. + Place a copy of the US NES version of _Contra_ with the name `baserom.nes` in + the project root folder. The file will be used by the build script to extract + the necessary assets. The MD5 hash of `baserom.nes` ROM should be + `7BDAD8B4A7A56A634C9649D20BD3011B`. + * Building requires the [cc65 compiler suite](https://cc65.github.io/) to + assemble and link the 6502 assembly files. Please install it and ensure the + bin folder is added to your path. + +## Instructions +There are 3 build scripts in this repository. All of them do the same thing. To +build the resulting .nes rom file, simply execute the appropriate build script +based on your environment. + +``` +.\build.ps1 <-- Windows +.\build.bat <-- Windows (no PowerShell) +./build.sh <-- Unix +``` + +* `build.ps1` - PowerShell script recommended for building on Windows machines. +* `build.bat` - bat script that can be used on windows machines without + PowerShell, but requires VBScript support. +* `build.sh` - bash script to be used in unix environments, or on Windows + environments with bash support (Git bash, WSL, etc.) + +## Documentation + +Supplemental materials have been added that help explain interesting features of +the code. Below are some of the more important documents. + +* `docs/Aim Documentation.md` - documentation on how enemy aiming works +* `docs/Bugs.md` - bugs identified while disassembling +* `docs/Contra Control Flow.md` - detailed look at the game routine and level + routine flows. +* `docs/Enemy Glossary.md` - documentation on every enemy type in the game +* `docs/Enemy Routines.md` - documentation on level enemy configuration and + random soldier generation. +* `docs/Graphics Documentation.md` - documentation on pattern tables, + nametables, palettes, palette cycling, super-tiles, background collision, and + compression. +* `docs/Sound Documentation.md` - documentation on the audio engine used as well + as information on all sounds from the game. + +All sprites were captured and labeled for easy reference in +`docs/sprite_library/README.md` + +### Getting Started + +At first, there is a lot to take in and it may be confusing where to start. +The files in the repo under the `src/` folder are broken up based on the memory +banks in the game. The _Contra_ cartridge is a +[UxROM](https://www.nesdev.org/wiki/UxROM) cartridge. The game has 8, 16 KiB +banks of memory for a total size of 128 KiB. Banks 0 through 6 are swapped in +and out of memory at address $8000-$bfff and bank 7 is always in memory at +$c000-$ffff. Knowing this helps understand the code since only 2 banks are +available at any given time. The start of the game and most of the game engine +logic exists in `bank7.asm` since that's in memory all of the time and where +the 3 NES interrupts are. + +I think a good way to start reading this code is to look into +`docs/Contra Control Flow.md`. That file shows the responsibilities of the game +engine loop routines and in what order those routines are called. + +* `bank0.asm` - used exclusively for enemy routines. Enemy routines are the + logic controlling enemy behaviors and settings: AI, movements, attack + patterns, health, etc. Almost every enemy is coded in bank 0, but some + enemy routines, usually those who appear in more than one level, are in + `bank7.asm`. +* `bank1.asm` - responsible for audio and sprites. The audio code takes up + about 3/4 of the bank. The remaining 1/4 of the bank is for sprite data and + code to draw sprites. +* `bank2.asm` - starts with RLE-encoded level data (graphic super tiles for the + level screens). It then contains compressed tile data and alternate tile data + and occasional attribute table data. Then, bank 2 contains logic for setting + the players' sprite based on player state. Next, bank 2 contains the level + headers, which define specifics about each level. Bank 2 then has the data + that specifies which enemies are on which screen and their attributes. Bank + 2 also contains the soldier enemy generation code. +* `bank3.asm` - starts with the data that specifies which pattern table tiles + comprises super-tiles along with the color palettes. This bank also has the + routines to manage the end of levels. +* `bank4.asm` - mostly contains compressed graphic data. The rest of bank 4 is + the code for the ending scene animation and the ending credits, including the + ending credits text data. +* `bank5.asm` - mostly contains compressed graphic data. The rest of bank 5 is + the code and lookup tables for automated input for the 3 demo (attract) + levels. +* `bank6.asm` - contains compressed graphics data, data for short text sequences + like level names and menu options. Bank 6 also contains the code for the + players' weapons and bullets. +* `bank7.asm` - the core of the game's programming. Reset, NMI, and IRQ vectors + are in this bank and is the entry point to the game. Bank 7 contains the code + for drawing of nametables and sprites, bank switching, routines for the intro + sequence, controller input, score calculation, graphics decompression + routines, palette codes, collision detection, pointer table for enemy + routines, shared enemy logic, score table, enemy attributes, and bullet angles + and speeds, and the NES undocumented footer, among other things. + +### Annotations + +While reviewing the assembly, I used a notation to categorize unexpected, or +interesting observations. You can search the code for these annotations. + +* `!(BUG?)` - a possible bug +* `!(HUH)` - unexpected code or logic used +* `!(WHY?)` - unsure of why the code is the way it is +* `!(UNUSED)` - unused code or data that is never executed nor read +* `!(OBS)` - observation or note + +# Project History +I first became interested in this project after watching the +[Summoning Salt](https://www.youtube.com/channel/UCtUbO6rBht0daVIOGML3c8w) video +[The History of Contra World Records](https://www.youtube.com/watch?v=GgOE64kgjjo). +In the video starting at 35m 19s, there was a section where a speedrunner named +`dk28` died in the middle of level 1, and was advanced to level 2 for their next +life. The video explains that there still isn't a known explanation as to why +this happened. This got me interested in the assembly and thus this project +began and has since changed in scope as my interest have changed. + +The _Contra_ (US) NES ROM was disassembled using a NES disassembly tool. This +provided a starting point for code investigation. The disassembler incorrectly +marked many lines of code as data and this had to be manually corrected. At +this point, there was an .asm file for each ROM bank in _Contra_. + +Build scripts were then created for both windows console (.bat), and powershell +(.ps1). These scripts are equivalent and use ca65 to assemble the .asm files +and cl65 to link the .o files into a single ROM file. Creating these build +scripts required defining the memory addresses and bank layouts in the +`contra.cfg` file. + +At this point, I had a repository that could be built that matched the NES rom +byte for byte. Then, I found and was able to incorporate the comments from +Trax's IDA Pro disassembly. This was incredibly helpful, but more work had to +be done as Trax's disassembly wasn't appropriate for disassembly. For instance, +it had a label for every line and all jump and branch statements were +hard-coded memory address offsets and not label references. + +I then worked on updating all branch and jump offsets to point to label +addresses. This helped ensure that the code was more readable. At this point, +I also started updating data blocks that included memory offsets to use label +offsets. The goal here was twofold: + + * make the code more readable and similar to what the original developers would + have written + * allow rom hacking without breaking the entire build due to breaking + hard-coded memory offsets + +When all of this was done, the project could assemble and be byte-for-byte +exactly as the _Contra_ (US) ROM. However, this was just the prerequisite to +documenting the codebase. Every label had to be given an appropriate name, and +each line of assembly had to be documented. + +# Build Details + +The build scripts accomplish the following tasks: + * extracts necessary data from `baserom.nes` into `src/assets` + * assemble each bank .asm file into a .o file + * assemble constants.asm and ines_header into .o files + * link all output .o files into a single .nes rom file + +The build scripts all utilize [cc65 compiler suite](https://cc65.github.io/) to +assemble and link the 6502 assembly files. The asset data is pulled from +`baserom.nes` as specified in `assets.txt`. Each line in `assets.txt` specifies +the file name of the asset, its offset into `baserom.nes`, and its length. + +## 1. Assembly +Each .asm file is assembled into a .o object file. This section outlines what +is happening as part of that process. + +### Import Export Validation +During assembly, if any symbol is undefined and not explicitly defined in an +`.import` directive, an error will be generated. + +## 2. Linking +Linking is the concept of combining the various .o object files into a single +.nes rom file. This is done by the cl65 linker. Its job is to replace labels +with actual CPU memory addresses where the labels will exist when loaded. These +are then stored in the resulting contra.nes rom file. This is aided by the +linker configuration file contra.cfg. + +This file specifies the layout of the .net rom file. Without this file, or with +a misconfigured file, the linker would not generate an identical .nes file. +There are two parts to the contra.cfg file: MEMORY layout and SEGMENTS layout. + +### Contra.cfg MEMORY Layout +This section tells the linker where in memory each bank will exist. The linker +needs this to know to replace a label with the correct address. For example, +the opcodes that get generated for `jsr zero_out_nametables` depend on where in +memory the label `zero_out_nametables` will exist. This is what the +configuration file memory section specifies. + +### Contra.cfg SEGMENTS Layout +This section specifies defines in which order the .o files should appear in the +resulting .nes rom file. + +# References + * Trax's [Disassembly of _Contra (US)_](https://www.bwass.org/romhack/contra/) + * Trax's [documentation on romhacking.net](https://www.romhacking.net/documents/713/) + * [Tomorrow Corporation's Retro Game Internal Series on Contra](http://tomorrowcorporation.com/posts/retro-game-internals) + * [Overview of NES Rendering by Austin Morlan](https://austinmorlan.com/posts/nes_rendering_overview/) + * [Rom Detective article on Contra](http://www.romdetectives.com/Wiki/index.php?title=Contra_(NES)) \ No newline at end of file diff --git a/assets.txt b/assets.txt new file mode 100644 index 0000000..a7b34d6 --- /dev/null +++ b/assets.txt @@ -0,0 +1,347 @@ +src\assets\graphic_data\alt_graphic_data_00.bin 37474 1408 +src\assets\graphic_data\alt_graphic_data_01.bin 38882 2976 +src\assets\graphic_data\alt_graphic_data_02.bin 41858 928 +src\assets\graphic_data\alt_graphic_data_03.bin 42786 1088 +src\assets\graphic_data\alt_graphic_data_04.bin 43874 1184 +src\assets\graphic_data\graphic_data_01.bin 76349 3724 +src\assets\graphic_data\graphic_data_02.bin 37031 443 +src\assets\graphic_data\graphic_data_03.bin 65553 1453 +src\assets\graphic_data\graphic_data_04.bin 67006 499 +src\assets\graphic_data\graphic_data_05.bin 81937 2656 +src\assets\graphic_data\graphic_data_06.bin 72204 1543 +src\assets\graphic_data\graphic_data_07.bin 84593 2431 +src\assets\graphic_data\graphic_data_08.bin 67708 4449 +src\assets\graphic_data\graphic_data_09.bin 72157 47 +src\assets\graphic_data\graphic_data_0a.bin 73749 833 +src\assets\graphic_data\graphic_data_0b.bin 87024 3899 +src\assets\graphic_data\graphic_data_0c.bin 98321 3291 +src\assets\graphic_data\graphic_data_0d.bin 101612 3834 +src\assets\graphic_data\graphic_data_0e.bin 105446 5284 +src\assets\graphic_data\graphic_data_0f.bin 74582 161 +src\assets\graphic_data\graphic_data_11.bin 74743 1369 +src\assets\graphic_data\graphic_data_12.bin 76112 237 +src\assets\graphic_data\graphic_data_13.bin 67505 203 +src\assets\graphic_data\graphic_data_14.bin 92196 1483 +src\assets\graphic_data\graphic_data_15.bin 110730 226 +src\assets\graphic_data\graphic_data_16.bin 110956 262 +src\assets\graphic_data\graphic_data_17.bin 93679 1326 +src\assets\graphic_data\graphic_data_18.bin 95005 81 +src\assets\graphic_data\graphic_data_19.bin 90923 485 +src\assets\graphic_data\graphic_data_1a.bin 91408 788 +src\assets\audio_data\sound_02.bin 18962 8 +src\assets\audio_data\sound_03.bin 18997 17 +src\assets\audio_data\sound_04.bin 19014 11 +src\assets\audio_data\sound_05.bin 19025 55 +src\assets\audio_data\sound_06.bin 19080 10 +src\assets\audio_data\sound_07.bin 19090 14 +src\assets\audio_data\sound_08.bin 19104 8 +src\assets\audio_data\sound_08_part_00.bin 19114 25 +src\assets\audio_data\sound_09.bin 19601 57 +src\assets\audio_data\sound_0a.bin 19139 41 +src\assets\audio_data\sound_0b.bin 19180 16 +src\assets\audio_data\sound_0b_part_00.bin 19198 23 +src\assets\audio_data\sound_0c.bin 19221 14 +src\assets\audio_data\sound_0c_part_00.bin 19237 29 +src\assets\audio_data\sound_0d.bin 19266 14 +src\assets\audio_data\sound_0d_part_00.bin 19282 23 +src\assets\audio_data\sound_0e.bin 19305 55 +src\assets\audio_data\sound_0f.bin 19360 99 +src\assets\audio_data\sound_10.bin 19459 12 +src\assets\audio_data\sound_10_part_00.bin 19473 6 +src\assets\audio_data\sound_11.bin 19479 10 +src\assets\audio_data\sound_11_part_00.bin 19491 6 +src\assets\audio_data\sound_12.bin 19497 14 +src\assets\audio_data\sound_13.bin 19511 90 +src\assets\audio_data\sound_14.bin 19658 10 +src\assets\audio_data\sound_14_part_00.bin 19670 14 +src\assets\audio_data\sound_15.bin 19801 45 +src\assets\audio_data\sound_16.bin 19684 35 +src\assets\audio_data\sound_17.bin 19719 37 +src\assets\audio_data\sound_18.bin 19756 13 +src\assets\audio_data\sound_18_part_00.bin 19771 8 +src\assets\audio_data\sound_19.bin 19779 22 +src\assets\audio_data\sound_1a.bin 19949 82 +src\assets\audio_data\sound_1b.bin 19846 5 +src\assets\audio_data\sound_1b_part_00.bin 19860 42 +src\assets\audio_data\sound_1b_part_02.bin 19910 39 +src\assets\audio_data\sound_1c.bin 20149 8 +src\assets\audio_data\sound_1c_part_00.bin 20159 23 +src\assets\audio_data\sound_1d.bin 20182 6 +src\assets\audio_data\sound_1d_part_00.bin 20190 21 +src\assets\audio_data\sound_1e.bin 20031 24 +src\assets\audio_data\sound_1f.bin 20055 23 +src\assets\audio_data\sound_20.bin 20078 71 +src\assets\audio_data\sound_21.bin 20211 13 +src\assets\audio_data\sound_21_part_00.bin 20224 11 +src\assets\audio_data\sound_21_part_02.bin 20250 11 +src\assets\audio_data\sound_21_part_03.bin 20263 11 +src\assets\audio_data\sound_21_part_04.bin 20276 11 +src\assets\audio_data\sound_21_part_05.bin 20289 11 +src\assets\audio_data\sound_21_part_06.bin 20302 11 +src\assets\audio_data\sound_21_part_07.bin 20315 11 +src\assets\audio_data\sound_21_part_08.bin 20328 12 +src\assets\audio_data\sound_21_part_09.bin 20342 12 +src\assets\audio_data\sound_21_part_0a.bin 20356 12 +src\assets\audio_data\sound_23.bin 20371 2 +src\assets\audio_data\sound_23_part_00.bin 20373 8 +src\assets\audio_data\sound_23_part_01.bin 20383 6 +src\assets\audio_data\sound_23_part_02.bin 20391 6 +src\assets\audio_data\sound_23_part_03.bin 20399 6 +src\assets\audio_data\sound_23_part_04.bin 20407 6 +src\assets\audio_data\sound_23_part_05.bin 20415 6 +src\assets\audio_data\sound_23_part_06.bin 20423 8 +src\assets\audio_data\sound_24.bin 20434 8 +src\assets\audio_data\sound_24_part_00.bin 20444 53 +src\assets\audio_data\sound_25.bin 20497 8 +src\assets\audio_data\sound_25_part_00.bin 20507 42 +src\assets\audio_data\sound_25_part_01.bin 20549 7 +src\assets\audio_data\sound_26.bin 20901 22 +src\assets\audio_data\sound_27.bin 20923 24 +src\assets\audio_data\sound_28.bin 20947 16 +src\assets\audio_data\sound_29.bin 20963 10 +src\assets\audio_data\sound_2a.bin 21560 29 +src\assets\audio_data\sound_2a_part_00.bin 21589 21 +src\assets\audio_data\sound_2a_part_01.bin 21612 361 +src\assets\audio_data\sound_2b_part_00.bin 21115 26 +src\assets\audio_data\sound_2b_part_01.bin 21143 415 +src\assets\audio_data\sound_2c.bin 21975 24 +src\assets\audio_data\sound_2c_part_00.bin 21999 39 +src\assets\audio_data\sound_2c_part_01.bin 22040 180 +src\assets\audio_data\sound_2c_part_02.bin 22220 5 +src\assets\audio_data\sound_2c_part_03.bin 22227 6 +src\assets\audio_data\sound_2c_part_04.bin 22235 168 +src\assets\audio_data\sound_2d.bin 22405 7 +src\assets\audio_data\sound_2d_part_00.bin 22414 4 +src\assets\audio_data\sound_2d_part_01.bin 22418 34 +src\assets\audio_data\sound_2d_part_02.bin 22454 176 +src\assets\audio_data\sound_2d_part_03.bin 22630 18 +src\assets\audio_data\sound_2d_part_04.bin 22650 13 +src\assets\audio_data\sound_2d_part_05.bin 22663 17 +src\assets\audio_data\sound_2d_part_06.bin 22682 16 +src\assets\audio_data\sound_2d_part_07.bin 22698 17 +src\assets\audio_data\sound_2d_part_08.bin 22717 12 +src\assets\audio_data\sound_2d_part_09.bin 22729 3 +src\assets\audio_data\sound_2d_part_0a.bin 22734 10 +src\assets\audio_data\sound_2e.bin 22933 20 +src\assets\audio_data\sound_2e_part_00.bin 22841 57 +src\assets\audio_data\sound_2e_part_01.bin 22900 33 +src\assets\audio_data\sound_2e_part_03.bin 22956 53 +src\assets\audio_data\sound_2e_part_04.bin 23011 85 +src\assets\audio_data\sound_2e_part_05.bin 23098 42 +src\assets\audio_data\sound_2e_part_06.bin 23142 27 +src\assets\audio_data\sound_2f.bin 23169 4 +src\assets\audio_data\sound_2f_part_00.bin 23173 17 +src\assets\audio_data\sound_2f_part_02.bin 23195 4 +src\assets\audio_data\sound_2f_part_03.bin 23199 26 +src\assets\audio_data\sound_2f_part_04.bin 23230 92 +src\assets\audio_data\sound_2f_part_05.bin 23324 32 +src\assets\audio_data\sound_2f_part_06.bin 23358 57 +src\assets\audio_data\sound_30.bin 23415 3 +src\assets\audio_data\sound_30_part_00.bin 23418 3 +src\assets\audio_data\sound_30_part_02.bin 23426 28 +src\assets\audio_data\sound_30_part_03.bin 23459 57 +src\assets\audio_data\sound_31_part_01.bin 23524 3 +src\assets\audio_data\sound_31_part_02.bin 23529 31 +src\assets\audio_data\sound_31_part_03.bin 23562 71 +src\assets\audio_data\sound_32.bin 23732 6 +src\assets\audio_data\sound_32_part_00.bin 23681 33 +src\assets\audio_data\sound_32_part_01.bin 23714 9 +src\assets\audio_data\sound_32_part_02.bin 23725 7 +src\assets\audio_data\sound_32_part_03.bin 23738 3 +src\assets\audio_data\sound_32_part_05.bin 23744 17 +src\assets\audio_data\sound_32_part_06.bin 23763 20 +src\assets\audio_data\sound_32_part_07.bin 23783 34 +src\assets\audio_data\sound_32_part_08.bin 23822 3 +src\assets\audio_data\sound_32_part_09.bin 23827 3 +src\assets\audio_data\sound_32_part_0a.bin 23832 42 +src\assets\audio_data\sound_33.bin 23874 5 +src\assets\audio_data\sound_33_part_00.bin 23879 3 +src\assets\audio_data\sound_33_part_01.bin 23884 18 +src\assets\audio_data\sound_33_part_02.bin 23904 21 +src\assets\audio_data\sound_33_part_03.bin 23925 36 +src\assets\audio_data\sound_33_part_04.bin 23966 4 +src\assets\audio_data\sound_33_part_05.bin 23972 6 +src\assets\audio_data\sound_34.bin 23978 3 +src\assets\audio_data\sound_34_part_00.bin 23981 3 +src\assets\audio_data\sound_34_part_01.bin 23986 16 +src\assets\audio_data\sound_34_part_02.bin 24004 16 +src\assets\audio_data\sound_34_part_03.bin 24022 30 +src\assets\audio_data\sound_34_part_04.bin 24022 3 +src\assets\audio_data\sound_34_part_05.bin 24057 3 +src\assets\audio_data\sound_34_part_06.bin 24062 4 +src\assets\audio_data\sound_34_part_07.bin 24068 3 +src\assets\audio_data\sound_34_part_08.bin 24071 3 +src\assets\audio_data\sound_34_part_09.bin 24076 4 +src\assets\audio_data\sound_34_part_0a.bin 24086 11 +src\assets\audio_data\sound_34_part_0b.bin 24099 11 +src\assets\audio_data\sound_35_part_00.bin 24111 3 +src\assets\audio_data\sound_35_part_02.bin 24122 11 +src\assets\audio_data\sound_35_part_03.bin 24135 10 +src\assets\audio_data\sound_35_part_04.bin 24145 11 +src\assets\audio_data\sound_35_part_05.bin 24158 9 +src\assets\audio_data\sound_35_part_06.bin 24167 11 +src\assets\audio_data\sound_35_part_07.bin 24180 11 +src\assets\audio_data\sound_36_part_00.bin 24255 54 +src\assets\audio_data\sound_36_part_01.bin 24309 17 +src\assets\audio_data\sound_36_part_02.bin 24328 19 +src\assets\audio_data\sound_36_part_03.bin 24349 55 +src\assets\audio_data\sound_37.bin 24406 6 +src\assets\audio_data\sound_37_part_00.bin 24412 8 +src\assets\audio_data\sound_37_part_01.bin 24420 20 +src\assets\audio_data\sound_37_part_02.bin 24442 4 +src\assets\audio_data\sound_37_part_03.bin 24446 9 +src\assets\audio_data\sound_37_part_04.bin 24457 11 +src\assets\audio_data\sound_37_part_05.bin 24470 48 +src\assets\audio_data\sound_38.bin 24520 4 +src\assets\audio_data\sound_38_part_00.bin 24524 6 +src\assets\audio_data\sound_38_part_01.bin 24530 5 +src\assets\audio_data\sound_38_part_02.bin 24537 3 +src\assets\audio_data\sound_38_part_03.bin 24540 5 +src\assets\audio_data\sound_38_part_04.bin 24547 5 +src\assets\audio_data\sound_38_part_05.bin 24552 7 +src\assets\audio_data\sound_38_part_06.bin 24561 10 +src\assets\audio_data\sound_38_part_07.bin 24573 20 +src\assets\audio_data\sound_39_part_00.bin 24596 3 +src\assets\audio_data\sound_39_part_01.bin 24601 4 +src\assets\audio_data\sound_39_part_02.bin 24605 12 +src\assets\audio_data\sound_39_part_03.bin 24619 10 +src\assets\audio_data\sound_39_part_04.bin 24629 12 +src\assets\audio_data\sound_39_part_05.bin 24643 10 +src\assets\audio_data\sound_39_part_06.bin 24653 12 +src\assets\audio_data\sound_39_part_07.bin 24667 11 +src\assets\audio_data\sound_39_part_08.bin 24678 5 +src\assets\audio_data\sound_39_part_09.bin 24685 5 +src\assets\audio_data\sound_3a_part_00.bin 24747 33 +src\assets\audio_data\sound_3a_part_01.bin 24782 7 +src\assets\audio_data\sound_3a_part_02.bin 24789 15 +src\assets\audio_data\sound_3a_part_03.bin 24806 16 +src\assets\audio_data\sound_3a_part_04.bin 24822 6 +src\assets\audio_data\sound_3a_part_05.bin 24830 38 +src\assets\audio_data\sound_3a_part_06.bin 24868 6 +src\assets\audio_data\sound_3a_part_07.bin 24876 39 +src\assets\audio_data\sound_3a_part_08.bin 24915 6 +src\assets\audio_data\sound_3a_part_09.bin 24923 15 +src\assets\audio_data\sound_3a_part_0a.bin 24938 6 +src\assets\audio_data\sound_3a_part_0b.bin 24946 67 +src\assets\audio_data\sound_3b.bin 25015 12 +src\assets\audio_data\sound_3b_part_00.bin 25027 30 +src\assets\audio_data\sound_3b_part_01.bin 25059 19 +src\assets\audio_data\sound_3b_part_02.bin 25080 171 +src\assets\audio_data\sound_3c.bin 25253 11 +src\assets\audio_data\sound_3c_part_00.bin 25266 8 +src\assets\audio_data\sound_3c_part_01.bin 25276 26 +src\assets\audio_data\sound_3c_part_02.bin 25302 20 +src\assets\audio_data\sound_3c_part_03.bin 25324 46 +src\assets\audio_data\sound_3c_part_04.bin 25370 6 +src\assets\audio_data\sound_3c_part_05.bin 25378 27 +src\assets\audio_data\sound_3d_part_00.bin 25408 3 +src\assets\audio_data\sound_3d_part_02.bin 25413 8 +src\assets\audio_data\sound_3d_part_03.bin 25421 24 +src\assets\audio_data\sound_3d_part_04.bin 25447 12 +src\assets\audio_data\sound_3d_part_05.bin 25461 10 +src\assets\audio_data\sound_3d_part_06.bin 25471 14 +src\assets\audio_data\sound_3d_part_07.bin 25487 90 +src\assets\audio_data\sound_3e.bin 25720 20 +src\assets\audio_data\sound_3e_part_00.bin 25644 18 +src\assets\audio_data\sound_3e_part_01.bin 25664 17 +src\assets\audio_data\sound_3e_part_02.bin 25681 20 +src\assets\audio_data\sound_3e_part_03.bin 25703 17 +src\assets\audio_data\sound_3e_part_04.bin 25742 17 +src\assets\audio_data\sound_3e_part_05.bin 25759 20 +src\assets\audio_data\sound_3e_part_06.bin 25781 16 +src\assets\audio_data\sound_3e_part_07.bin 25797 68 +src\assets\audio_data\sound_3e_part_08.bin 25867 11 +src\assets\audio_data\sound_3e_part_09.bin 25880 10 +src\assets\audio_data\sound_3e_part_0a.bin 25899 21 +src\assets\audio_data\sound_3e_part_0b.bin 25922 19 +src\assets\audio_data\sound_3e_part_0c.bin 25941 21 +src\assets\audio_data\sound_3e_part_0d.bin 25964 20 +src\assets\audio_data\sound_3f.bin 25984 3 +src\assets\audio_data\sound_3f_part_00.bin 25989 66 +src\assets\audio_data\sound_3f_part_01.bin 26057 14 +src\assets\audio_data\sound_3f_part_02.bin 26073 14 +src\assets\audio_data\sound_3f_part_03.bin 26093 3 +src\assets\audio_data\sound_3f_part_04.bin 26096 3 +src\assets\audio_data\sound_3f_part_05.bin 26101 3 +src\assets\audio_data\sound_40.bin 26107 10 +src\assets\audio_data\sound_40_part_00.bin 26119 4 +src\assets\audio_data\sound_40_part_01.bin 26125 49 +src\assets\audio_data\sound_40_part_02.bin 26176 9 +src\assets\audio_data\sound_40_part_03.bin 26187 10 +src\assets\audio_data\sound_40_part_04.bin 26206 10 +src\assets\audio_data\sound_40_part_05.bin 26218 9 +src\assets\audio_data\sound_40_part_06.bin 26227 10 +src\assets\audio_data\sound_40_part_07.bin 26239 11 +src\assets\audio_data\sound_41.bin 26250 8 +src\assets\audio_data\sound_41_part_00.bin 26260 7 +src\assets\audio_data\sound_41_part_01.bin 26267 10 +src\assets\audio_data\sound_41_part_02.bin 26279 9 +src\assets\audio_data\sound_41_part_03.bin 26288 14 +src\assets\audio_data\sound_41_part_04.bin 26304 16 +src\assets\audio_data\sound_41_part_05.bin 26322 34 +src\assets\audio_data\sound_41_part_06.bin 26356 6 +src\assets\audio_data\sound_41_part_07.bin 26364 4 +src\assets\audio_data\sound_41_part_08.bin 26368 6 +src\assets\audio_data\sound_41_part_09.bin 26376 8 +src\assets\audio_data\sound_42.bin 26531 91 +src\assets\audio_data\sound_42_part_00.bin 26624 134 +src\assets\audio_data\sound_43.bin 26760 5 +src\assets\audio_data\sound_43_part_00.bin 26437 15 +src\assets\audio_data\sound_43_part_01.bin 26454 17 +src\assets\audio_data\sound_43_part_02.bin 26473 19 +src\assets\audio_data\sound_43_part_03.bin 26494 37 +src\assets\audio_data\sound_43_part_04.bin 26770 119 +src\assets\audio_data\sound_44.bin 26891 135 +src\assets\audio_data\sound_44_part_00.bin 27028 119 +src\assets\audio_data\sound_45.bin 27166 27 +src\assets\audio_data\sound_45_part_00.bin 27149 6 +src\assets\audio_data\sound_45_part_01.bin 27155 8 +src\assets\audio_data\sound_45_part_02.bin 27195 25 +src\assets\audio_data\sound_45_part_03.bin 27231 14 +src\assets\audio_data\sound_46.bin 27298 33 +src\assets\audio_data\sound_47.bin 27331 33 +src\assets\audio_data\sound_48.bin 27364 27 +src\assets\audio_data\sound_49.bin 27391 21 +src\assets\audio_data\sound_4a.bin 27823 2 +src\assets\audio_data\sound_4a_part_00.bin 27644 179 +src\assets\audio_data\sound_4a_part_01.bin 27827 4 +src\assets\audio_data\sound_4a_part_02.bin 27833 4 +src\assets\audio_data\sound_4a_part_03.bin 27837 109 +src\assets\audio_data\sound_4b_part_00.bin 27949 3 +src\assets\audio_data\sound_4b_part_01.bin 27954 3 +src\assets\audio_data\sound_4b_part_02.bin 27957 15 +src\assets\audio_data\sound_4b_part_03.bin 27974 35 +src\assets\audio_data\sound_4b_part_04.bin 28009 14 +src\assets\audio_data\sound_4b_part_05.bin 28025 15 +src\assets\audio_data\sound_4b_part_06.bin 28042 35 +src\assets\audio_data\sound_4b_part_07.bin 28077 14 +src\assets\audio_data\sound_4b_part_08.bin 28093 88 +src\assets\audio_data\sound_4c_part_00.bin 28184 14 +src\assets\audio_data\sound_4c_part_01.bin 28200 6 +src\assets\audio_data\sound_4c_part_02.bin 28206 12 +src\assets\audio_data\sound_4c_part_03.bin 28220 10 +src\assets\audio_data\sound_4c_part_04.bin 28230 11 +src\assets\audio_data\sound_4c_part_05.bin 28243 9 +src\assets\audio_data\sound_4c_part_06.bin 28252 11 +src\assets\audio_data\sound_4c_part_07.bin 28265 10 +src\assets\audio_data\sound_4c_part_08.bin 28275 11 +src\assets\audio_data\sound_4c_part_09.bin 28288 9 +src\assets\audio_data\sound_4c_part_0a.bin 28297 11 +src\assets\audio_data\sound_4d_part_00.bin 28314 11 +src\assets\audio_data\sound_4d_part_01.bin 28327 6 +src\assets\audio_data\sound_4e.bin 27460 40 +src\assets\audio_data\sound_4f.bin 27500 42 +src\assets\audio_data\sound_50.bin 27542 44 +src\assets\audio_data\sound_51.bin 27586 32 +src\assets\audio_data\sound_52.bin 20775 83 +src\assets\audio_data\sound_53.bin 20858 43 +src\assets\audio_data\sound_54.bin 18970 26 +src\assets\audio_data\sound_55.bin 20556 25 +src\assets\audio_data\sound_56.bin 20581 8 +src\assets\audio_data\sound_56_part_00.bin 20591 53 +src\assets\audio_data\sound_57.bin 20644 4 +src\assets\audio_data\sound_58.bin 20648 68 +src\assets\audio_data\sound_59.bin 20716 8 +src\assets\audio_data\sound_59_part_00.bin 20726 49 +src\assets\audio_data\dpcm_sample_00.bin 130064 81 +src\assets\audio_data\dpcm_sample_01.bin 130256 593 diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..28f051e --- /dev/null +++ b/build.bat @@ -0,0 +1,54 @@ +@echo off + +rem Assembles and links the source assemblies into a .nes ROM. +rem Run this script from the windows command prompt. +rem If desired, there is also a build.ps1 powershell script +rem that can be used as well. + +IF EXIST contra.nes ( + echo Deleting contra.nes. + del contra.nes +) + +IF NOT EXIST "obj" ( + mkdir "obj" +) + +IF EXIST "obj\*.o" ( + echo Deleting object files. + del "obj\*.o" +) + +IF NOT EXIST base.nes ( + echo No baserom.nes file found. If assets are missing, then the build will fail. +) + +rem show commands run in output +echo Assembling PRG Rom Banks + +rem loop through assets defined in assets.txt and extract bytes from baserom.nes +echo Extracting binary data from baserom.nes +for /f "tokens=1,2,3 delims= " %%i in (assets.txt) do ( + cscript /nologo set_bytes.vbs %%j %%k %%i +) + +@echo on +ca65 -g --debug-info -o obj\constants.o src\constants.asm +ca65 -g --debug-info -o obj\ines_header.o src\ines_header.asm +ca65 -g --debug-info -o obj\bank0.o src\bank0.asm +ca65 -g --debug-info -o obj\bank1.o src\bank1.asm +ca65 -g --debug-info -o obj\bank2.o src\bank2.asm +ca65 -g --debug-info -o obj\bank3.o src\bank3.asm +ca65 -g --debug-info -o obj\bank4.o src\bank4.asm +ca65 -g --debug-info -o obj\bank5.o src\bank5.asm +ca65 -g --debug-info -o obj\bank6.o src\bank6.asm +ca65 -g --debug-info -o obj\bank7.o src\bank7.asm +@echo off + +rem link assemblies together to single .nes ROM + +echo "Creating .nes ROM" + +@echo on +ld65 -C contra.cfg --dbgfile contra.dbg .\obj\constants.o .\obj\ines_header.o .\obj\bank0.o .\obj\bank1.o .\obj\bank2.o .\obj\bank3.o .\obj\bank4.o .\obj\bank5.o .\obj\bank6.o .\obj\bank7.o -o contra.nes +@echo off \ No newline at end of file diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..1ee75aa --- /dev/null +++ b/build.ps1 @@ -0,0 +1,97 @@ +# Assembles and links the source assemblies into a .nes ROM. +# Run this script from powershell if available, if not available use build.bat +# batch file from the windows command prompt. + +$global:SOURCE_CONTRA = $null +$US_CONTRA_HASH = "1C747C78C678F14A68D4E5FCAE065298A103F833638775860F0E5C5FFAA061F62D45FD8942148B1507C1FD57FDE950A5D83F9F84A9782EC048A56067740C48E9" + +<# +.SYNOPSIS + +Copies bytes from the Contra US NES rom file into binary files for use when +assembling. +#> +function Set-Bytes { + param ($Skip, $Take, $Output) + + IF (Test-Path -Path $Output) { + return + } + + # only read baserom.nes once for speed improvements + IF ($global:SOURCE_CONTRA -eq $null) { + Write-Output " Reading input file baserom.nes." + $global:SOURCE_CONTRA = Get-Content .\baserom.nes -AsByteStream + } + + Write-Output " Writing file $Output." + $global:SOURCE_CONTRA | Select-Object -Skip $Skip -First $Take | Set-Content $Output -AsByteStream +} + +IF (Test-Path -Path "contra.nes") { + Write-Output "Deleting contra.nes." + Remove-Item -Path "contra.nes" +} + +IF (-not (Test-Path -Path "obj")) { + New-Item -ItemType Directory -Path obj +} + +IF (Test-Path -Path obj\*.o) { + Write-Output "Deleting object files." + Remove-Item -Path obj\*.o +} + +IF (-not (Test-Path -Path "baserom.nes")) { + Write-Output "No baserom.nes file found. If assets are missing, then the build will fail." +} ELSE { + $SHA512_HASH = (Get-FileHash baserom.nes -Algorithm SHA512).Hash + IF ($SHA512_HASH -ne $US_CONTRA_HASH) { + Write-Warning "baserom.nes file integrity does NOT match expected result." + } +} + +# loop through assets defined in assets.txt and extract bytes from baserom.nes +Write-Output "Extracting binary data from baserom.nes" +ForEach ($line in Get-Content -Path assets.txt) { + $tokens = -split $line + Set-Bytes -Skip $tokens[1] -Take $tokens[2] -Output $tokens[0] +} + +# prevent write race condition +Start-Sleep -Milliseconds 100 + +Write-Output "Assembling PRG Rom Banks" + +# show commands run in output +Set-PSDebug -Trace 1 +ca65 -g --debug-info -o obj\constants.o src\constants.asm +ca65 -g --debug-info -o obj\ines_header.o src\ines_header.asm +ca65 -g --debug-info -o obj\bank0.o src\bank0.asm +ca65 -g --debug-info -o obj\bank1.o src\bank1.asm +ca65 -g --debug-info -o obj\bank2.o src\bank2.asm +ca65 -g --debug-info -o obj\bank3.o src\bank3.asm +ca65 -g --debug-info -o obj\bank4.o src\bank4.asm +ca65 -g --debug-info -o obj\bank5.o src\bank5.asm +ca65 -g --debug-info -o obj\bank6.o src\bank6.asm +ca65 -g --debug-info -o obj\bank7.o src\bank7.asm + +Set-PSDebug -Trace 0 + +# link assemblies together to single .nes ROM +Write-Output "Creating .nes ROM" + +Set-PSDebug -Trace 1 +ld65 -C contra.cfg --dbgfile contra.dbg .\obj\constants.o .\obj\ines_header.o .\obj\bank0.o .\obj\bank1.o .\obj\bank2.o .\obj\bank3.o .\obj\bank4.o .\obj\bank5.o .\obj\bank6.o .\obj\bank7.o -o contra.nes + +# compare assembled ROM hash to expected hash if file exists +Set-PSDebug -Trace 0 +IF (Test-Path -Path "contra.nes") { + $SHA512_HASH = (Get-FileHash contra.nes -Algorithm SHA512).Hash + + IF ($SHA512_HASH -eq $US_CONTRA_HASH) { + Write-Output "File integrity matches." + } ELSE { + Write-Warning "File integrity does NOT match." + } +} diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..a1ab1b7 --- /dev/null +++ b/build.sh @@ -0,0 +1,88 @@ +#!/bin/bash + +# Assembles and links the source assemblies into a .nes ROM. + +# Run this script from a bash terminal if on linux or mac. +# If you are on windows, use either build.ps1, or build.bat + +us_contra_hash=1c747c78c678f14a68d4e5fcae065298a103f833638775860f0e5c5ffaa061f62d45fd8942148b1507c1fd57fde950a5d83f9f84a9782ec048a56067740c48e9 + +setBytes(){ + if test -f $3 + then + return + fi + + echo " Writing file $3." + dd bs=1 skip=$1 count=$2 if=baserom.nes of=$3 status=none +} + +if ! ld65 --version &> /dev/null +then + echo "cc65 compiler suite could not be found. Please install cc65 and add it to your path." + exit +fi + +mkdir -p obj + +if test -f "contra.nes" +then + echo "Deleting contra.nes." + rm contra.nes +fi + +if test -f "obj/*.o" +then + echo "Deleting object files." + rm obj/*.o +fi + +if ! test -f "baserom.nes" +then + echo "No baserom.nes file found. If assets are missing, then the build will fail." +else + ROM_HASH=$(sha512sum baserom.nes | awk '{print $1}') + if [[ "$ROM_HASH" != $us_contra_hash ]] + then + echo "baserom.nes file integrity does NOT match expected result." + fi +fi + +# loop through assets defined in assets.txt and extract bytes from baserom.nes +echo "Extracting binary data from baserom.nes" +while read -r line || [ -n "$p" ] +do + set $line + file=$1 + start=$2 + length=$3 + length=$(echo $length | tr -d '\r') + setBytes $start $length $file +done < assets.txt + +echo "Assembling PRG Rom Banks" +ca65 -g --debug-info -o obj/constants.o src/constants.asm +ca65 -g --debug-info -o obj/ines_header.o src/ines_header.asm +ca65 -g --debug-info -o obj/bank0.o src/bank0.asm +ca65 -g --debug-info -o obj/bank1.o src/bank1.asm +ca65 -g --debug-info -o obj/bank2.o src/bank2.asm +ca65 -g --debug-info -o obj/bank3.o src/bank3.asm +ca65 -g --debug-info -o obj/bank4.o src/bank4.asm +ca65 -g --debug-info -o obj/bank5.o src/bank5.asm +ca65 -g --debug-info -o obj/bank6.o src/bank6.asm +ca65 -g --debug-info -o obj/bank7.o src/bank7.asm + +echo "Creating .nes ROM" +ld65 -C contra.cfg --dbgfile contra.dbg ./obj/constants.o ./obj/ines_header.o ./obj/bank0.o ./obj/bank1.o ./obj/bank2.o ./obj/bank3.o ./obj/bank4.o ./obj/bank5.o ./obj/bank6.o ./obj/bank7.o -o contra.nes + +if test -f "contra.nes" +then + # compare assembled ROM hash to expected hash + ROM_HASH=$(sha512sum contra.nes | awk '{print $1}') + if [[ "$ROM_HASH" == $us_contra_hash ]] + then + echo "File integrity matches." + else + echo "File integrity does NOT match." + fi +fi \ No newline at end of file diff --git a/contra.cfg b/contra.cfg new file mode 100644 index 0000000..c3963e8 --- /dev/null +++ b/contra.cfg @@ -0,0 +1,37 @@ +# defines where the banks will be loaded into CPU address space +MEMORY { + # INES Cartridge Header, not loaded into CPU memory + HEADER: start = $0000, size = $0010, fill = yes; + + OAMRAM: file="", start = $0200, size = $0100, type = rw; + + # UxROM (002) bank layout + BANK_0: start = $8000, size = $4000, type = ro, fill = yes, fillval = $FF; + BANK_1: start = $8000, size = $4000, type = ro, fill = yes, fillval = $FF; + BANK_2: start = $8000, size = $4000, type = ro, fill = yes, fillval = $FF; + BANK_3: start = $8000, size = $4000, type = ro, fill = yes, fillval = $FF; + BANK_4: start = $8000, size = $4000, type = ro, fill = yes, fillval = $FF; + BANK_5: start = $8000, size = $4000, type = ro, fill = yes, fillval = $FF; + BANK_6: start = $8000, size = $4000, type = ro, fill = yes, fillval = $FF; + BANK_7: start = $C000, size = $3FFA, type = ro, fill = yes, fillval = $FF; + # BANK_7 is actually still $4000 bytes large, but the hardware vectors (ROMV), + # which are on BANK7, are counted separately + + # Hardware Vectors at End of 2nd 8K ROM (NES hard-coded location) + ROMV: start = $FFFA, size = $0006, type = ro, fill = yes; +} + +# defines the order of the segments as they are stored in the .nes ROM file +SEGMENTS { + HEADER: load = HEADER, type = ro; + OAMRAM: load = OAMRAM, type = bss, define=yes, optional=yes; + BANK_0: load = BANK_0, type = ro; + BANK_1: load = BANK_1, type = ro; + BANK_2: load = BANK_2, type = ro; + BANK_3: load = BANK_3, type = ro; + BANK_4: load = BANK_4, type = ro; + BANK_5: load = BANK_5, type = ro; + BANK_6: load = BANK_6, type = ro; + BANK_7: load = BANK_7, type = ro; + VECTORS: load = ROMV, type = ro; +} \ No newline at end of file diff --git a/docs/Aim Documentation.md b/docs/Aim Documentation.md new file mode 100644 index 0000000..9251e36 --- /dev/null +++ b/docs/Aim Documentation.md @@ -0,0 +1,189 @@ +# 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`. This 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 - quadrant_aim_dir; + } else { + new_aim_dir = 0x00; + } +} + +// new full aim direction calculated +new_aim_dir = quadrant_aim_dir; +``` \ No newline at end of file diff --git a/docs/Bugs.md b/docs/Bugs.md new file mode 100644 index 0000000..7abfc2a --- /dev/null +++ b/docs/Bugs.md @@ -0,0 +1,68 @@ +# Overview + +This document outlines the bugs discovered in the code during the disassembly +process. + +# 1. Missing Animation Entering Water + +When animating the player falling into water, a comparison was missed which +causes `sprite_18` to not be used for the animation sequence. This bug also +exists in the Japanese version as well as _Probotector_. It's clear from the +code that there was an attempt to show `sprite_18` (frame 3 in the table below) +as part of the animation sequence. `sprite_18` is the same sprite as when the +player is in water and presses the d-pad down button. + +| Frame 1 | Frame 2 | Frame 3 (missing) | Frame 4 | +|-------------------------------------|-------------------------------------|-------------------------------------|-------------------------------------| +| ![0](attachments/pw_0.png?raw=true) | ![1](attachments/pw_1.png?raw=true) | ![2](attachments/pw_2.png?raw=true) | ![3](attachments/pw_3.png?raw=true) | + + +``` +@set_enter_water_sprite: + ... + lda PLAYER_WATER_TIMER,x ; load water animation timer + beq ... ; if timer has elapsed, branch + cmp #$0c ; see if timer is greater than or equal to #$0c + bcs ... ; branch if timer >= #$0c (keep current sprite) + lda #$73 ; PLAYER_WATER_TIMER less than #$0c, update sprite + sta PLAYER_SPRITE_CODE,x ; set player sprite (sprite_73) water splash + cmp #$08 ; !(BUG?) always branch, doesn't compare to PLAYER_WATER_TIMER + ; but instead compares against #$73 + ; no lda PLAYER_WATER_TIMER,x before this line, so part of splash animation is missing + bcs ... ; branch using sprite_73 + lda #$18 ; dead code - a = #$18 (sprite_18) water splash/puddle + sta PLAYER_SPRITE_CODE,x ; dead code - set player animation frame to water splash/puddle +``` + +# 2. Accidental Sound Sample In Snow Field + +After defeating the boss UFO (Guldaf), before calling the method +`level_boss_defeated`, the developers forgot to load into the accumulator the +appropriate sound code, usually #$57 for `sound_57`. So bytes in bank 7 are +interpreted as an audio sample incorrectly and a short DMC channel audio clip is +played. + +`level_boss_defeated` uses whatever is in the accumulator as the sound code to +play. Since the accumulator wasn't set, it has the previous value of #$ff (see +`boss_ufo_routine_0b`). This is interpreted by the audio engine as a DMC sound +code that doesn't exist. + +The audio engine will incorrectly read bytes from `sound_table_00` as the dmc +data below. + + * sampling rate #$03 (5593.04 Hz), no loop + * counter length #$77 + * offset #$97 --> $c000 + (#$97 * #$40) --> $e5c0 + * sample length #$19 + +The data that is interpreted as a DPCM-encoded DMC sample is $e5c0, which is +in bank 7's `collision_box_codes_03` data. This bug does not exist in the +Japanese version of the game because the `level_boss_defeated` is not +responsible for playing the boss defeated sound. This bug does exist in +_Probotector_. + +Extracted sound sample: [sound_ff.mp3](attachments/sound_ff.mp3?raw=true) + +Note that the boss defeated audio (`sound_55`) is still played because the enemy +defeated routine is set to `boss_ufo_routine_09` (see +`enemy_destroyed_routine_05`). `boss_ufo_routine_09` plays `sound_55`. \ No newline at end of file diff --git a/docs/Contra Control Flow.md b/docs/Contra Control Flow.md new file mode 100644 index 0000000..c629d4b --- /dev/null +++ b/docs/Contra Control Flow.md @@ -0,0 +1,253 @@ +# Game Loop + +The control flow of _Contra_ is dictated by the CPU memory address +`GAME_ROUTINE_INDEX` ($18). You can think of the `game_routine_xx` methods like +canal locks. As the game progresses, the flow moves to the next +`game_routine`. + + * `game_routine_00` and `game_routine_01` are for setting up the game intro + and player select UI. + * `game_routine_02` is for showing the demo. + * `game_routine_03` loads and auto-plays the demo level + * `game_routine_04` clears level memory and player-specific memory + * `game_routine_05` is where majority of level logic is executed + * `game_routine_06` runs at the end of the game after defeating the final boss + alien + +## game_routine_00 +This label is only run once to initialize the NES's graphics and CPU memory that +is used for the introduction animation and player select. + + * Set nametable tiles all to #$00 - `zero_out_nametables` (ROM: $1c9a2, MEM: + Bank 7 $c9a2) + * Load intro pattern table, nametable, and palette (`load_intro_graphics`) + * Reset Komani code number of correct sequence inputs + (`KONAMI_CODE_NUM_CORRECT`) to back to #$00 + * Initialize the horizontal scroll to #$01 for the sliding intro animation + * Initialize the high byte of the delay timer that is used for waiting before + showing the demo + +## game_routine_01 +This label is executed once per frame repeatedly while the _Contra_ logo is +scrolled across the screen (intro animation). This label also checks for the +Konami code is checked. Once the logo is finished scrolling from right to left, +the label will load the sprites needed to show Bill and Lance as well as the +player select UI and cursor. Then the intro theme music will be played. +Finally, this label waits for both timers to complete before either starting +the game (if the player has made a selection), or show a demo level. + + * Check for Konami code (`konami_input_check`) + * Scroll intro graphic + * This is executed repeatedly until scrolling stops, the the logic below is + executed + * Load assets after scrolling is complete (`game_routine_01_scroll_complete`) + * Load player select menu + * Play intro explosion sound + * Load sprite for yellow falcon cursor and sprites for Bill and Lance + * Wait for timer to complete before moving on to `game_routine_02` + (`dec_theme_delay_check_user_input`) + * The timer resets if select is pressed + +## game_routine_02 + * Load demo level and plays the level + * Stop level when demo timer elapsed and loads next level to demo (only + levels 0-2) + * Reset `GAME_ROUTINE_INDEX` to #$0 between demo levels to reshow intro + scroll and player select + +## game_routine_03 +This label is executed once the player has pressed start to begin a game while +in `game_routine_02`. It simply ensures the intro theme is finished playing and +then flashes the player selection until all timers are elapsed + + * Wait for intro theme to finish playing + * Flash "1 PLAYER" or "2 PLAYER" for a bit + +## game_routine_04 +This label clears level memory and player-specific memory like number of lives, +number of continues, as well as level header data. + + * `init_score_player_lives` - clears memory addresses $0028 to $00f0 then + `CPU_SPRITE_BUFFER` ($300) up to but not including `CPU_GRAPHICS_BUFFER` + ($700) + +## game_routine_05 +This is the where the majority of the game logic is executed from. +`game_routine_05` maintains its own separate set of routines for managing the +level state. See Level Routines. There are #$0a level routines. + +## game_routine_06 +This routine runs at the end of the game after defeating the final boss alien. +Begins the `game_end_routine_XX` routine execution flow and runs through each +routine + + * `game_end_routine_00` + * `game_end_routine_01` + * `game_end_routine_02` + * `game_end_routine_03` + * `end_game_sequence_00` + * `end_game_sequence_01` + * `end_game_sequence_02` + * `game_end_routine_04` + * `game_end_routine_05` + +# Level Routines +`game_routine_05` maintains a `LEVEL_ROUTINE_INDEX` ($2c). This is an offset +into the `level_routine_ptr_tbl` (ROM: $1ce35, MEM: Bank 7 $ce35). These +routines manage the state of the level as the player progresses. These routines +are not necessarily executed in order like a waterfall. There are #$0a level +routines in total. + +These routines manage + * level initialization + * showing score before starting a level + * handle player input, including pause + * level completion, advancing level + * game over screen - continue / end + * game over with no more continues + * showing game ending sequence + +Below is a mermaid diagram of the logic that dictates the state changes among +the level routines. + +```mermaid +graph TD + level_routine_00 --> level_routine_01 + level_routine_01 --> level_routine_02 + level_routine_02 --> level_routine_03 + level_routine_03 --> level_routine_04 + level_routine_04 --> |Level Complete|level_routine_08 + level_routine_04 --> |Game Over|level_routine_0a + level_routine_05 --> |Level Complete|level_routine_00 + level_routine_05 --> |Complete Last Level/Game Over|level_routine_06 + level_routine_05 --> |Game Over No Continues|level_routine_07 + level_routine_06 --> |Continue/End Selected|level_routine_00 + level_routine_08 --> |Game Over|level_routine_0a + level_routine_08 --> |Level Complete|level_routine_09 + level_routine_0a --> |Game Over Timer Expired|level_routine_05 + level_routine_09 --> |Level Complete|level_routine_05 +``` + +## level_routine_00 +This routine is responsible for loading the level header data into memory. This +specifies things like what type of level (indoor/base, outdoor), where graphics +data for the level are (super-tile pattern tiles, which super-tiles are on each +screen, and palette data). Then the routine initializes many PPU write +addresses, and loads the data which specifies the super-tiles on the first +screen. Note that the actual super-tile pattern tiles aren't loaded into memory +until `level_routine_03`. + + * Load the level header data + * Load palette for screen that displays number of lives remaining and level/ + stage name. + * Initialize `BOSS_DEFEATED_FLAG` and `LEVEL_END_PLAYERS_ALIVE` to #$00 + * Call `init_ppu_write_screen_supertiles` to initialize PPU scroll offset, + PPU write offsets and call `load_current_supertiles_screen_indexes` to + decompress super-tiles indexes for level's screen to load into CPU memory at + `LEVEL_SCREEN_SUPERTILES` + * Loads bank 0 where the enemy routines are and initializes the memory address + $80 to point to where the enemy routines are for the current level + +## level_routine_01 +Responsible for displaying the number of lives remaining for players before the +level loads. This routine does not execute when demoing (in demo mode). It is +only executed once, whereas `level_routine_02` is executed repeatedly to flash +the score. The number of lives does not flash. + +## level_routine_02 + * Flash the score text until timer elapses + * Load the theme music for the level `level_vert_scroll_and_song` when not in + demo mode + * Sets the vertical scroll + +## level_routine_03 +Responsible for rendering the nametable super-tiles. + +## level_routine_04 + * Sees if the player is pausing or un-pausing by reading input. + * If the player is pausing, the pause sound is played and the game state is + updated + * If the player is paused, then the routine ends + * Check if BOSS_DEFEATED_FLAG is set, the the current level routine is + updated to `level_routine_08` and the current routine ends + * Check if player(s) have entered game over state, and if so, updates the + current level routine to `level_routine_0a` and the current routine ends + * Run all enemy logic + * Run soldier generation + * Update palette for flashing palettes, see palette cycling documentation in + `Graphics Documentation.md` + * Load alternate graphics if necessary + +## level_routine_05 +Handles when the the level is complete or ended due to game over. If it's not +the last level, then the sets things up so the next level is loaded. If it's the +last level, then configures things for the end of game sequences. During game +over, after GAME_OVER_DELAY_TIMER elapses, level routine #$0a sets the next +routine to be level_routine_05 so that the game over high score screen can be +shown + + * Clear memory $40 to $f0, then $300 to $600 (exclusively) + * If game over + * Load game over screen pattern table and show game over screen + `show_game_over_screen` + * Increment `CURRENT_LEVEL` + * If the last level + * Start game ending sequence `inc_routine_index_set_timer` + * Increment `GAME_COMPLETION_COUNT` so next play through is more challenging + * Reset `LEVEL_ROUTINE_INDEX` back to level_routine_00 + * If not the last level + * Loads graphics for current score screen and level intro screen in + `load_level_intro` + * Increments `LEVEL_ROUTINE_INDEX` to go to level_routine_06 + `inc_routine_index_set_timer` + +## level_routine_06 +This level routine is the game over screen routine. It is executed after the +player has died. This routine shows the player scores, the high score. It also +shows the text "GAME OVER" and gives the player the option to either "CONTINUE" +or "END". + + * Display score and "CONTINUE"/"END" + * Handle player input to change cursor between "CONTINUE" and "END" (select + button) + * Handle player input to select either "CONTINUE" and "END" (start button) + +## level_routine_07 +This routine is executed when there are no continues remaining. It shows the +score and the text "GAME OVER" while waiting for the player to press start. Once +the player presses start, the level routine is set to #$00 + +`level_routine_07` is executed after checking the number of remaining continues +in `level_routine_05` (`@no_continues_remaining`). + +## level_routine_08 +Boss destroy animation. Plays level end music + + * Check if player(s) have entered game over state, and if so, updates the + current level routine to `level_routine_0a` and the current routine ends. + This seems unlikely as this check happens in `level_routine_04` as well. + This same code is part of `level_routine_07`, and seems equally unlikely to + happen for that level routine as well. + +## level_routine_09 +This routine runs the appropriate level routine from the +`end_level_sequence_ptr_tbl` table. After each sequence is finished executing, +control is sent back to `level_routine_05`. + + * `end_level_sequence_00` + * Wait for the players to land if they were jumping + * Set a small delay before `end_level_sequence_01` does its logic + * `end_level_sequence_01` + * Wait for timer to elapse + * Run level-specific level routine ending animation from + `end_of_lvl_lvl_routine_ptr_tbl` + * `end_level_sequence_02` + +## level_routine_0a +This is the last level routine, but is executed as part of a game over sequence. +After the player(s) get a game over, this is the routine that waits until +`GAME_OVER_DELAY_TIMER` has elapsed before setting the level routine to #$05 +(`level_routine_05`) to reinitialize the level. + +The `GAME_OVER_DELAY_TIMER` delay timer starts at #60 and is used to wait a bit +before showing the player scores. \ No newline at end of file diff --git a/docs/Enemy Glossary.md b/docs/Enemy Glossary.md new file mode 100644 index 0000000..f603474 --- /dev/null +++ b/docs/Enemy Glossary.md @@ -0,0 +1,1178 @@ +# Enemies + +In _Contra_ there are various enemies, each enemy type is assigned a number. +Enemy types whose number is larger than #$09 are level specific. + +For outdoor levels, each enemy has 3 bits available to modify their attributes +in some way. For indoor/base levels, and generated enemies, each enemy has an +entire byte for their attributes. This page documents this data as well. + +For some enemies, the last bit of the Y position also serves as bit 3 of the +attributes. An odd Y position will make bit 3 of attributes set, and an even Y +position will make bit 3 of attributes clear. Examples include the pill box +sensor (common), and the claw (hangar). + +## Common + +These enemies are shared and can be used among all levels. + +### 00 - Weapon Item + +Gives the player various power-ups/weapons upgrades. It is a letter +representing the weapon type surrounded by wings. + +#### Attributes + +* `000` - Rapid Fire (R) - Modifier that speeds up the bullet velocity of all + weapons with the exception of the laser rifle. +* `001` - Machine Gun (M) - On-screen capacity of six bullets, which are faster + than the default weapon. +* `010` - Flame Thrower/Fire Gun/Fire Ball (F) - Small fireball that moves + forward in a winding pattern, making small loops. It has an on-screen capacity + of four fireballs. +* `011` - Spray Gun/Spread Gun (S) - Fires several bullets in a wide arc, with + an on-screen capacity of ten bullets. Each bullet is as powerful as a machine + gun bullet. +* `100` - Laser (L) - The most powerful weapon per shot, but also the slowest. + This drawback is mitigated by the fact that the beam will continue to pass + through defeated enemies, so you can take out an entire line of them in one + shot. +* `101` - Invincibility/Barrier (B) - Modifier that protects from anything + (except falling in pits) for about twenty seconds. Any infantry that touches + the player will be instantly killed, but vehicles and heavy guns will be + unaffected. +* `110` - Falcon - destroys every basic enemy on the screen. It does little to + no damage to vehicles, artillery, and other machines. + Also known as Mega Shell, Mass Destruction, or Eagle Weapon +* `111` - Unused same as `110`, but no flying capsule is visible at all + +#### Logic + +* `ENEMY_VAR_1` for indoor levels, stores initial y position +* `ENEMY_VAR_B` is the vertical velocity coefficient for indoor arcs when + falling towards the player. Negative goes up, positive goes down. + +### 01 - Bullet + +This enemy type is generated by various enemies and not be used alone. If used +alone in a level, a single immobile bullet will be rendered. Attributes are not +used for this enemy type. + +#### Logic + +* `ENEMY_VAR_1` specifies the bullet type + +| Bullet Type | Description | Sprite | Palette | Collision Code | +|-------------------|-----------------------------------------------|-----------|---------|----------------| +| #$00 | regular bullet | sprite_1e | #$01 | #$01 | +| #$00 (snow field) | red regular bullet (see bullet type #$05) | sprite_07 | #$02 | #$01 | +| #$01 | level 1 boss large cannonball | sprite_21 | #$02 | #$05 | +| #$02 | indoor large cannonball (boss screen) | sprite_21 | #$02 | #$05 | +| #$03 | indoor regular bullet | sprite_1e | #$01 | #$01 | +| #$04 | level 3 dragon boss fire ball | sprite_79 | #$01 | #$02 | +| #$05 (snow field) | #$00 override (see `enemy_bullet_routine_01`) | sprite_07 | #$02 | #$00 | + +### 02 - Pill Box Sensor + +* Other Names: Weapon Box + +Creates 'Weapon Items' when destroyed + +#### Attributes + +For the 3 attribute bits, this enemy uses the same attributes as the 'Weapon +Item' enemy type. Unlike most enemies, this enemy's least significant bit of +the vertical position is also used to specify which tile is shown after being +destroyed. + +### 03 - Flying Capsule + +* Other Names: Weapon Zeppelin, Power-Up Capsule, Weapon Wings, Weapon Box, + Weapon Drop, or Flying Item Pod + +Weapon Item transportation device that which travels from the left end of the +screen to the other in a wave-like pattern. + +#### Attributes + +The flying capsule enemy type uses the same attributes as the 'Weapon Item' +enemy type. + +#### Logic + +* `ENEMY_VAR_2` + +### 04 - Rotating Gun + +* Other Names: Gray Turret, Rotating Turret + +Shoots in 360 direction. + +#### Attributes + +Only least 2 significant bits are used for attributes + +* `000` - 1 bullet per attack +* `001` - 2 bullets per attack +* `010` - 3 bullets per attack +* `011` - 3 bullets per attack + +#### Logic + +* `ENEMY_VAR_1` - [#$00-#$0b] aim direction starting #$00 facing right + incrementing clockwise, e.g. #$06 is facing left +* `ENEMY_VAR_2` - bullets per attack +* `ENEMY_FRAME` - super-tile to draw, not a sprite. Relative offset into + `level_1_nametable_update_supertile_data`, or + `level_3_nametable_update_supertile_data`. + +### 05 - Soldier + +* Other Names: Running Man + +A soldier that can move to attack + +#### Attributes + +* `.... ...x` - running direction - 0 is left, 1 is right +* `.... ..x.` - whether the soldier turns around on edges, set means to turn + around once soldier reaches edge, clear means to walk off edge. Soldiers will + only turn around a maximum of #$02 times +* `.... .x..` - whether or not the enemy shoots bullets +* `.xxx ....` - initial animation delay offset + (`soldier_initial_anim_delay_tbl`) + +#### Logic + +* `ENEMY_VAR_1` - soldier fire recoil timer +* `ENEMY_VAR_2` - soldier x direction #$00 is left, #$01 is right +* `ENEMY_VAR_3` + * `soldier_routine_02` - whether or not the soldier is jumping + * `soldier_routine_03` - track number of bullets to soldier will fire +* `ENEMY_VAR_4` - the number of times the soldier has changed directions +* `ENEMY_VAR_A` - controls which sprite to load, causes sprite to increment + every #$08 frames running +* `ENEMY_FRAME` - sprite to draw. Offset into `soldier_sprite_codes` + +Soldiers can be randomly generated based on a formula + +* How many times the player has beat the game +* What is the player's current weapon strength +* Which level they are on + +### 06 - Sniper + +* Other Names: Ledder, Rifle Man + +Stand in place and can fire in any direction + +#### Attributes + +* `.... .xxx` - sniper type/behavior + * `.... .000` - stands and shoots bullets three at a time + * `.... .001` - crouch (hide) and shoot one bullet at a time + * `.... .010` - crouch (hide) and shoot bullets three at a time (boss screen + sniper). + +#### Logic + +* `ENEMY_VAR_2` - sniper firing angle +* `ENEMY_VAR_3` - set to #$06 when sniper is firing +* `ENEMY_VAR_4` - The number of bullets to fire (see + `sniper_bullet_attack_count_tbl`) +* `ENEMY_FRAME` - sprite to draw. Offset into `sniper_sprite_00` for sniper + types #$00 and #$01, `sniper_sprite_01` for sniper type #$04 (boss screen + sniper) + +Other values for the attribute yield unpredictable results + +### 07 - Red Turret + +* Other Names: Gulcan, Cannon + +Rises from the ground and shoots at the player. Once past by the players, and +scrolled enough to the edge of the screen, goes back into the ground. + +#### Attributes + +Only least significant bit is used + +* `000` - Rocky background +* `001` - Forrest background + +#### Logic + +* `ENEMY_VAR_1` - bullet initial x, y offsets (#$6 bytes) + +### 08 - Wall Cannon + +* Other Names: Triple Turret, Triple Cannon, Gelguge, Zark Turret + +Exists only in the indoor (base) levels. Shielded turrets. + +No attributes exist for this enemy. + +#### Logic + +* `ENEMY_VAR_1` - hp + +### 09 - Unused + +### 0A - Wall Plating + +* Other Names: Shoud + +Indoor boss screen shielded cores + +### 0B - Mortar Shot + +Generated by the scuba diver (enemy type #$0c) in waterfall level and ice field +levels. Also generated by mortar launcher (enemy type #$17) on hangar zone boss +screen. These enemies generate a single mortar shot, but once the mortar shot +reaches its vertical apex, 3 other mortar shots are generated. The original +mortar shot becomes an explosion. + +#### Attributes + +* `.... ..xx` - mortar shot type [#$00-#$04]. + * `000` - initial mortar shot. Travels straight up quickly + * `001` - middle of the 3 split mortar shots. Travels straight up small amount + * `010` - right of the 3 split mortar shots. + * `011` - left of the 3 split mortar shots. + +#### Logic + +* `ENEMY_VAR_1` - [#$01-#$04] when non-zero specifies the direction of the + initial mortar shot, starting at the 4th entry of `mortar_shot_velocity_tbl`. + The split mortar shots are always from the `ENEMY_ATTRIBUTES`. Used on the + hangar zone boss screen by the Mortar Launcher (#$17) to aim mortar shots. + +### 0C - Scuba Diver + +* Other Names: Acuba + +A soldier hiding in water until activated, then fires mortar shots (enemy type +#$0b). + +No attributes exist for this enemy. + +#### Logic + +* `ENEMY_VAR_1` - stores recoil timer before firing mortar shot + +### 0D - Unused + +### 0E - Basquez + +* Other Names: Turret Man, Mounted Soldier + +#### Attributes + +* `.... ..xx` - delay between shots. Actual delay is this value multiplied by + 16 and then 1 is added. + +### 0F - Basquez Bullet + +* Other Names: Turret Man Bullet + +## Level 1 - Jungle + +There are 3 enemies that are specific to Level 1 - Jungle. + +### 10 - Bomb Turret + +* Other Names: Blaster Cannon, Repeater Cannon, Destroi + +One of 2 cannons on the Jungle level boss defense wall. Shoots projectiles that +fall in an arching motion. + +#### Attributes + +Only least significant bit is used + +* `000` - Wall background +* `001` - Black (Jungle) background + +The wall background is used for the bomb turret closest to the screen so it +is part of the defense wall. The black background is used on the other bomb +turret and matches the black background. + +### 11 - Plated Door + +* Other Names: Wall Plated Door, Boss Wall Plated Door + +The target shot on the Jungle level boss defense wall. When shown it causes +a quick siren sound to play. It does not fire back and has an initial health of +0x20 the first time you play the game. + +No attributes exist for this enemy. + +### 12 - Exploding Bridge + +There are two bridges throughout level 1 that explode as the players cross them. +The bridge is 4 super-tiles wide and explodes as the players cross it. + +They are no attributes for this enemy type. + +#### Logic +* `ENEMY_VAR_1` - the current bridge section being exploded, goes from #$00 to + #$03. Once #$04 is encountered, the bridge is removed. +* `ENEMY_VAR_2` - the current number of the small sprite explosion. #$03 small + explosions happen before the generic explosion animation begins. Each section + follows the same pattern: #$03 small explosions, then the generic explosion. + +The animation for the bridge explosion happens section by section with the first +section being a little different. In general, when a section starts exploding, +the nametable super-tile is updated for the previous bridge section, and the +first mini-explosion cloud is drawn. On the next animation (#$04 frames later), +the current bridge section is updated to its second super-tile value and the +second mini-cloud created. After an addition #$04 frames, the 3rd cloud is +drawn with no super-tile changed. Once the third mini-explosion is created, +the routine is updated to `exploding_bridge_routine_04` to move to the next +bridge section. This loop continues until all sections are exploded. The first +section is special in that there is no previous bridge section to update. The +last section is special in that it only has 1 super-tile animation. + +## Level 2 and 4 - Indoor/Base Levels + +### 10 - Boss Eye + +* Other Names: Garmakilma, Boss Fire Eye + +Boss for level 2. + +### 11 - Roller + +* Other Names: Darr, Rolling Grenade + +### 12 - Grenade + +* Other Names: Hand Grenade + +Grenades are thrown by indoor soldiers (15) and grenade launchers (17) on indoor +levels. + +No attributes exist for this enemy. + +#### Logic + +* `ENEMY_VAR_1` - grenade y position +* `ENEMY_VAR_2` - used in calculating falling arc position +* `ENEMY_VAR_3` - used in calculating falling arc position +* `ENEMY_VAR_4` - used in calculating falling arc position +* `ENEMY_VAR_B` - used in calculating falling arc position +* `ENEMY_FRAME` - sprite to draw. Offset into `grenade_sprite_codes_00`, + `grenade_sprite_codes_01`, or `grenade_sprite_codes_02` + +### 13 - Wall Turret + +Similar to #$08 Wall Cannon. Exists only in the indoor (base) levels. + +#### Attributes + +* `.... ..xx` - initial wall turret animation delay (offset into + `wall_turret_initial_delay_tbl`) + +#### Logic + +* `ENEMY_VAR_1` - used to know if closed wall turret super-tile has been drawn +* `ENEMY_FRAME` - offset into `wall_turret_tile_animation_tbl`, which offsets + `level_2_4_tile_animation` + +### 14 - Core + +* Other Names: Wall Core, Shoud, Sensor + +An energy core target on indoor/base levels. Don't attack until 7 rounds of +attack from generated soldiers have happened. Destroying all of this enemy type +allows advancement to next screen by disabling the electric fence. These +enemies have an delay before opening. Before opening, they are not able to be +attacked. Cores can be 'plated', meaning there is a protective wall that must +be destroyed before you can attack the core directly. + +#### Attributes + +* `.... x...` - Big Core +* `.... .x..` - Plated (0 = not plated, 1 = plated) +* `.... ..xx` - Opening Delay - index into `core_opening_delay` + +Opening delay is not used if the core is plated. Instead a default delay of +#$20 is used. + +#### Logic + +* `ENEMY_VAR_A` - bullet collision sound code +* `ENEMY_VAR_1` - ensure wall core nametable is updated +* `ENEMY_VAR_2` - index into `level_2_4_tile_animation` specifying nametable + tiles to update (see `wall_core_tile_anim_tbl`) +* `ENEMY_VAR_3` - after wall cores are all destroyed, the back wall is destroyed + as well. This variable tracks which destroyed back wall portion to draw + +### 15 - Indoor Soldier + +* Other Names: Running Soldier, Running Guy + +Runs from one side of the screen to the other + +#### Attributes + +* `... ..xx.` - weapon type (00 = shoot, 01 = grenades, 10 and 11 = drop + rollers) +* `.... ...x` - direction (0 = from right, 1 = from left) + +### 16 - Jumping Soldier + +* Other Names: Jumping Guy, Jumping Guy + +Travels across the screen while jumping + +#### Attributes + +* `...x xx..` - weapon type +* `.... ..x.` - soldier will be red in second enemy cycle (drop rapid fire + weapon item) +* `.... ...x` - direction (0 = from right, 1 = from left) + +#### Logic + +* `ENEMY_VAR_1` - y velocity index. Offset into `jumping_soldier_y_vel_tbl` + +### 17 - Grenade Launcher + +* Other Names: Seeking Guy + +Launches bomb grenades at user + +#### Attributes + +* `.... ...x` - direction (0 = from right, 1 = from left) +* `.... .xx.` - number of grenades per attack + +#### Logic + +* `ENEMY_VAR_1` - number of grenades to fire +* `ENEMY_VAR_2` - closest player index to grenade launcher +* `ENEMY_VAR_3` + +### 18 - Group of Four Soldiers + +* Other Names: Soldier Squad + +Always created in groups of 4 that act as one enemy. All 4 soldiers come in +from one side of the screen, stop in middle to fire at the player, then split up +into 2 groups going in opposite directions. Then the 2 groups fire again at the +player, and finally they walk off screen. + +#### Attributes + +* `.... ...x` - direction (0 = from right, 1 = from left) + +#### Logic + +* `ENEMY_VAR_1` - the individual number of the soldier within the group of four + soldiers, i.e. #$00 to #$03 +* `ENEMY_VAR_2` - number of times soldier has stopped to fire at the player. + The maximum is #$02. + +### 19 - Indoor Soldier Generator + +* Other Names: Green Soldier Generator + +Generates soldiers depending on the whether level 2 or level 4 and the level +screen number. Is able to generate #$04 different enemy types. + + * #$15 - Indoor Soldier + * #$16 - Jumping Soldier + * #$17 - Grenade Launcher + * #$18 - Group of Four Soldiers + +#### Attributes + +* `.... ...x` - level number, 0 = level 2, 1 = level 4 + +#### Logic + +* `ENEMY_VAR_1` - used to keep track of which enemy to generate on the current + screen. + +### 1A - Indoor Roller Generator + +Creates roller enemies (#$11). + +#### Attributes + +* `.... .xxx` - offset into `roller_gen_init_tbl` + * #$00 - `roller_gen_init_00` + * #$02 - `roller_gen_init_01` + +### 1B - Boss Eye Fire Ring Projectile + +Other Names: Sphere Projectile, Eye Projectile, Ring of Fire + +Fire ring emitted from #$10 Boss Eye (Garmakilma). + +### 1C - Godomuga + +Other Names: Boss Gemini, Boss Metal Helmet + +No attributes exist for this enemy. + +#### Logic + +The health of the boss gemini helmets are #$01 and each hit 'destroys' them. +However, the destroyed routine `boss_gemini_routine_03` will check `ENEMY_VAR_4` +for the boss gemini helmet's actual HP. + +* `ENEMY_FRAME` - offset into `boss_gemini_sprite_tbl`, which contains the exact + sprite code: `sprite_68`, `sprite_69`, `sprite_6b`, `sprite_6c`. +* `ENEMY_VAR_1` - initial x position +* `ENEMY_VAR_2` - timer after being hit - #$00 down to #$00 +* `ENEMY_VAR_3` - whether or not the boss gemini's health is low (less than + #$07). Used to show a red brain instead of a green one. +* `ENEMY_VAR_4` - actual representation of ENEMY_HP +* `ENEMY_X_VELOCITY_FRACT` - always #$80 (.50). Used with + `ENEMY_Y_VELOCITY_FRACT` to move gemini by 1 every #$02 frames +* `ENEMY_X_VELOCITY_FAST` - x direction of boss gemini + * #$00 - boss gemini are travelling away from center + * #$ff - boss gemini are travelling towards center +* `ENEMY_Y_VELOCITY_FRACT` - alternates every frame between #$00 and #$80. Used + with `ENEMY_Y_VELOCITY_FRACT` to move gemini by 1 every #$02 frames +* `ENEMY_Y_VELOCITY_FAST` - offset from initial x position. Either added to or + subtracted `ENEMY_VAR_1` based on whether the frame is even or odd. Always + either #$00 or #$80 +* `ENEMY_HP` - always #$01 until hit by bullet. The 'enemy destroyed' routine + will reset `ENEMY_HP` back to #$01 until `ENEMY_VAR_4` is #$00. + +### 1D - Gardegura + +Other Names: Spinning Bubbles + +Red and blue molecule-like orbs that are created by #$1c Boss Gemini (Godomuga). +They seek the player for a determined amount of time. If after set number of +seeks (#$13), then the bubble pair will continue in last direction. + +#### Attributes + +* `.... .xxx` - specifies how frequently the bubbles will spin. Initialized in +`spinning_bubbles_routine_00` to a random value between 0 and 3 inclusively. +Value is an offset into `spinning_bullet_spin_tbl`. + +#### Logic + +* `ENEMY_VAR_1` - enemy aim direction, offset into `spinning_bullet_vel_tbl` +* `ENEMY_VAR_2` - closest player to spinning bubble +* `ENEMY_VAR_3` - number of times that the spinning bubble has checked to see if + it should readjust its aiming direction, after #$13 checks, the bubble will + stop targeting player and continue in last direction. +* `ENEMY_FRAME` - #$6d is added to this value to get actual sprite code + * `sprite_6d`, `sprite_6e`, `sprite_6f`, `sprite_70`, `sprite_71`, `sprite_72` + +### 1E - Garth + +* Other Names: Blue Jumping Guy, Boss Blue Soldier, Blue Soldier + +#### Attributes + +* `.... ..xx` - specifies initial position (see `red_blue_soldier_init_pos_tbl`) + and initial x velocity (see `red_blue_soldier_init_vel_tbl`). When bit 0 is + clear, the blue soldier is coming from the right. When bit 0 is set, the blue + soldier is coming from the left. + +#### Logic + +* `ENEMY_FRAME` + * `blue_soldier_routine_01` - offset from `sprite_85` up to `sprite_87`. Used + to animate running horizontally. + * `blue_soldier_routine_02` - offset from `sprite_88` up to `sprite_8a`. Used + to animate jumping down to attack. + * `blue_soldier_routine_03` - `ENEMY_FRAME` is not used, rather `ENEMY_SPRITE` + is updated directly to either `sprite_8a` or `sprite_8b` + +### 1F - Rangel + +* Other Names: Red Shooting Guy, Boss Red Soldier, Red Soldier + +#### Attributes + +* `.... ..xx` - specifies initial position (see `red_blue_soldier_init_pos_tbl`) + and initial x velocity (see `red_blue_soldier_init_vel_tbl`). When bit 0 is + clear, the red soldier is coming from the right. When bit 0 is set, the red + soldier is coming from the left. When bit 1 is clear, the horizontal distance + between the player and the enemy before attacking is #$10 pixels. Otherwise, + the distance is #$30 pixels. + +#### Logic + +* `ENEMY_VAR_1` - number of bullets to fire per round +* `ENEMY_VAR_2` - whether or not the red soldier has fired at the player. Used + to ensure red soldier only fires once, then runs off screen. +* `ENEMY_FRAME` - offset from `sprite_8c`, goes up to `sprite_90`. + +### 20 - Garth and Rangel Generator + +* Other Names: Red Blue Soldier Generator + +Generates Garth and Rangel enemies following a pattern specified in +`red_blue_solider_data_tbl`. + +* red soldier (x4), #$a0 delay +* blue soldier (x2), #$40 delay +* blue soldier (x2), #$80 delay +* red soldier (x2), #$60 delay +* red soldier (x4), #$a0 delay +* blue soldier (x4), #$a0 delay +* red soldier (x4), #$fc delay + +No attributes exist for this enemy. + +#### Logic + +* `ENEMY_VAR_1` - the read offset into the table specifying the red and blue + soldiers generation behavior. After all the soldiers are generated (based on + `red_blue_solider_data_tbl`), then the number is reset and the soldier + generation pattern is repeated. + +## Level 3 - Waterfall + +### 10 - Floating Rock Platform + +Floats back and forth to allow players to jump higher in level. This enemy is +very similar to the moving flame enemy, which is also in the vertical level. + +#### Attributes + +* `000` - platform starts out moving left at 1 unit per frame +* `001` - platform starts out moving right at .75 units per frame on average + +### 11 - Moving Flame + +Flame that goes back and forth over bridge. This enemy is very similar to the +floating rock platform enemy, which is also in the vertical level + +#### Attributes + +* `010` - Flame starts out moving left at 1 unit per frame +* `011` - Flame starts out moving right at 1 unit per frame + +### 12 - Rock Cave + +Generates #13 Falling Rocks + +### 13 - Falling Rock + +* Other Names: Boulder + +#### Logic + +* `ENEMY_VAR_1` - y position of most recent ground collision + +### 14 - Dragon + +* Other Names: Boss Mouth, Gromaides + +#### Logic + +* `ENEMY_VAR_1` - dragon HP, used to keep track of HP between opening and + closing of mouth +* `ENEMY_VAR_2` - when dragon destroyed, keeps track of current explosion to + draw +* `ENEMY_VAR_3` - used in `boss_mouth_routine_08` to ensure the that the enemy + destroying animation starts on second execution of `boss_mouth_routine_08` and + not the first + +### 15 - Dragon Tentacle Orb + +* Other Names: Boss Arm, Boss Tentacle, Dragon Arm, Dragon Arm Orb + +5 of these enemies exist on each tentacle of the Dragon. Level specifies 2, one +for each arm. Each side then spawns 4 additional dragon arm orb enemies. Loops +through 5 different attack patterns. This is the most complicated enemy in the +game. + +#### Attributes + +* `.... ...x` - dragon arm orb side + * 0 - right side of screen (dragon's left arm) + * 1 - left side of screen (dragon's right arm) + +#### Logic + +To simplify understanding, I've named some some of the parts of the arm. +* shoulder - the 2 'parent' orbs that are specified in the enemy level screens. + The shoulder orbs generate/spawn the rest of the arm orbs (enemy type #$15). + These are the two orbs on either side of the dragon that are closest to the + body +* hand - the red orb at the end of the arm. This is the only orb with collision + enabled. These are the two orbs on either side of the dragon that are + farthest from the body. + +Below is an example ASCII drawing of enemy slot index for each dragon arm orb to +simplify defining the enemy vars. + +`#$06 $08 $0d $0f $0c (o) MMM (o) $0b $0e $09 $07 $0f` + +* `ENEMY_X_VELOCITY_FRACT` - dragon arm orb shoulder value specifies the 'angle' + and other orbs specify how curled for the arm to be + * shoulder - `ENEMY_X_VELOCITY_FRACT` is set to equal `ENEMY_VAR_1`. This + value is an absolute index into `dragon_arm_orb_pos_tbl` + * all other orbs - the running total of index into `dragon_arm_orb_pos_tbl`. + Each farther orb has the next value. Then the orb's `ENEMY_VAR_1` is added to + get position. +* `ENEMY_VAR_1` - used in correlation wiht shoulder's `ENEMY_X_VELOCITY_FRACT` + to set position. `ENEMY_VAR_1` is the distance from the previous orb's + position index. +* `ENEMY_VAR_2` - duration timer for rotation direction. positive = clockwise, + negative = counterclockwise +* `ENEMY_VAR_3` - specifies the next dragon arm orb enemy slot index, e.g. + dragon arm orb enemy slot #$0c will have `ENEMY_VAR_3` set to #$0f, which is + the next farther dragon arm orb in the dragon's arm (see ascii diagram above). + The last orb in the arm (the hand) (#$06 and #$0f) will have a value of #$ff. +* `ENEMY_VAR_4` - specifies the previous dragon arm orb enemy slot index, e.g. + dragon arm orb enemy slot #$07 will have `ENEMY_VAR_4` set to #$09, which is + the previous dragon arm orb in the dragon's arm (closer to the body) (see + ascii diagram above). The first orb in the arm (the shoulder) will have a + value of #$ff. +* `ENEMY_VAR_A` - used by shoulder orbs as a timer for firing dragon boss fire + balls from arms. +* `ENEMY_FRAME` - in `dragon_arm_orb_routine_01`, used to keep track of + remaining boss arms to spawn. Once child dragon arm orbs are created, then + used on parent dragon arm orb only to specify attack pattern behavior. Unlike, + most other enemies, `ENEMY_FRAME` is not actually used to determine any sprite + codes, tiles, nor super-tiles. + * #$00 - wave arm up and down + * #$01 - spin toward center + * #$02 - spin away from center + * #$03 - hook shape + * #$04 - arm seeking player, reaching down + +## Level 5 - Snow Field + +### 10 - Ice Grenade Generator + +No attributes exist for this enemy. + +### 11 - Ice Grenade + +No attributes exist for this enemy. + +#### Logic + +* `ENEMY_FRAME` - value modulus #$03 offset into `ice_grenade_sprite_tbl` + +### 12 - Tank + +* Other Names: Dogra + +#### Attributes + +* `.... ...x` - tank attack delay index into `tank_attack_delay_tbl` + +#### Logic + +* `ENEMY_VAR_1` - tank super-tile aim direction, used also to load correct tank + turret super-tile. + * #$0c = straight left + * #$0b = aiming down left + * #$0a = down down left +* `ENEMY_VAR_3` - remaining bullets to fire when stopped in current round of + attack before re-aiming. Starts at #$03 and goes to #$00 during attack phase + repeatedly until `ENEMY_VAR_4` elapsed. +* `ENEMY_VAR_4` - timer before stopped tank continues advancing +* `ENEMY_X_VEL_ACCUM` - whether or not the tank is on the screen + * #$01 - off screen to the right + * #$00 - visible on screen + * #$ff - off screen to the left (starts when first wheel is off screen) + +### 13 - Ice Separator + +* Other Names: Pipe Joint, Ice Joint + +No attributes exist for this enemy. + +### 14 - Alien Carrier + +* Other Names: Guldaf, Boss UFO + +No attributes exist for this enemy. + +Appears randomly and creates flying saucers (enemy type - #$15) and drop bombs +(enemy type - #$16). One of the few enemies that use BG_PALETTE_ADJ_TIMER to +create a fadeing in effect. + +#### Logic + +* `ENEMY_VAR_1` - used when drawing to know which super-tile to draw, and where + to draw explosions when boss ufo is destroyed +* `ENEMY_ANIMATION_DELAY` - used as an index to know which part of the boss ufo + to update, [#$00-#$03]. Indexes into `boss_ufo_supertile_pos_tbl` + +### 15 - Flying Saucer + +* Other Names: Mini UFO + +No attributes exist for this enemy. Direction of flying saucer is determined by +Alien Carrier. + +### 16 - Drop Bomb + +Bombs dropped from the Alien Carrier + +## Level 6 - Energy Zone + +### 10 - 12 - Fire Beam + +* Other Names: Energy Beam + +These 4 enemy types are very similar. They differ in the direction they fire +* #$10 - down +* #$11 - left +* #$12 - right + +#### Attributes +* `.... ..xx` - beam length code (0-3) (index, not actual amount) +* `.... xx..` - beam delay between bursts (index into + `fire_beam_anim_delay_tbl`) +* `xx.. ....` - collision code logic, see `collision_code_f`, offset into + `collision_code_f_adj_tbl` (0, 4, 8, or 16) + +#### Logic + +* `ENEMY_VAR_A` - beam delay between bursts +* `ENEMY_VAR_2` - fire beam length before for both horizontal and downward fire + beams before moving to either `ENEMY_VAR_3` or `ENEMY_VAR_4` depending on + specific fire beam type +* `ENEMY_VAR_3` - horizontal fire beam length +* `ENEMY_VAR_4` - vertical fire beam length + +### 13 - Giant Boss Soldier + +* Other Names: Gordea, Giant Boss Robot, Giant Armored Soldier, Boss Giant + +No attributes exist for this enemy. + +#### Logic + +* `ENEMY_VAR_1` - random number used to control boss action: jump, attack, + nothing. For routines #$06 and #$07, used to specify current explosion number. + For `boss_giant_soldier_routine_09` used to animate door opening. +* `ENEMY_VAR_2` + * delay between steps + * y position of door when opening +* `ENEMY_VAR_3` - delay timer between sections of the door before opening +* `ENEMY_VAR_4` - number of consecutive thrown saucers + +### 14 - Spiked Projectile + +* Other Names: Saucer, Spiked Disk Projectile + +#### Logic + +* `ENEMY_VAR_1` - bit 0 specifies sprite code offset from #$bb, i.e. + `sprite_bb`, or `sprite_bc`. Increments after every animation delay has + elapsed. + +## Level 7 - Hangar + +### 10 - Claw + +* Other Names: Moving Claw + +Mechanical claw similar to arcade claw machines + +#### Attributes + +* `xx..` - high two bits specify at which frame counter number to begin + descending, linked to `claw_frame_trigger_tbl`. After `claw_routine_00`, these + two bits are stripped away from the attributes. +* `..xx` - during initialization in `claw_routine_00`, these 2 bits specify the + claw length code, linked to `claw_length_tbl` + * claw length code #$03 (`11`) also specifies that the claw is a 'seeking' + claw and will attack when the player is near. + +#### Logic + +* `ENEMY_VAR_2` - renaming iterations of extension when ascending. #$00 for + fully extended and when descending +* `ENEMY_VAR_3` - current length of claw, #$ff for fully retracted claw +* `ENEMY_VAR_4` - offset into `claw_update_nametable_ptr_tbl` +* `ENEMY_FRAME` - stores the frame counter number at which the claw descends + `ENEMY_ATTRIBUTES`. Unlike other enemy types, it doesn't store graphic + information. + +### 11 - Rising Spiked Wall + +* Other Names: Spiked Wall + +A spiked wall that rises from the ground in front of the player + +#### Attributes + +* `.... ..xx` - bits 0 and 1 are an index into `rising_spike_wall_delay_tbl`, +which specify how long to delay (in frames) before rising once the player has +gotten close to the spiked wall location. +* `.... xx..` - bits 2 and 3 are an index into + `rising_spike_wall_trigger_dist_tbl`, which specify how close the player has + to be to the wall before it is 'activated' + +Note that bit 6 and 7 of `ENEMY_ATTRIBUTES` are both set to 1 during the first +routine. This is to allow adjusting the collision box to grow upward with the +rising spiked wall by using offset 16 into `collision_code_f_adj_tbl`. + +#### Logic + +* `ENEMY_VAR_1` - used for dynamic collision box size logic (see + `set_enemy_collision_box`) +* `ENEMY_VAR_2` - used to initialize wall super-tiles, offset into + `rising_spiked_wall_data_tbl` +* `ENEMY_VAR_3` - used to store the distance the closest player has to be before + the wall will start rising delay timer (`ENEMY_VAR_4`). +* `ENEMY_VAR_4` - the delay that controls when the wall will start rising. This + delay timer is started once the closest player gets within a certain range of + the wall + +### 12 - Tall Spiked Wall + +Similar to Rising Spiked Wall (#$11), but taller and stationary + +#### Attributes + +* `.... ..xx` - index into `spiked_wall_destroyed_data_tbl` + * #$00 - small spiked wall that is half of the screen on a platform + * #$02 - larger spiked wall that is about 3/4 of the screen + +Note that bit 6 and 7 of `ENEMY_ATTRIBUTES` are both set to 1 during the first +routine. This is to allow adjusting the collision box to grow upward with the +rising spiked wall by using offset 16 into `collision_code_f_adj_tbl`. + +#### Logic + +* `ENEMY_VAR_1` - used for dynamic collision box size logic (see + `set_enemy_collision_box`) + +### 13 - Mining Cart Generator + +* Other Names: Mine Cart Generator + +### 14 - Mining Cart + +* Other Names: Moving Cart + +Moving mining cart that can be destroyed. + +#### Attributes + +This enemy is only ever created by #$13 (Mining Cart Generator), so enemy +attributes are encoded in code during generation. + + * #$80 - the cart should be destroyed when colliding with background + * bit 7 0 - specifies the cart should reverse direction after collision with + background + +### 15 - Stationary Mining Cart + +* Other Names: Immobile Cart + +Stationary mining cart that can start moving when a player lands on it + +#### Attributes + +* #$80 - the cart should be destroyed when colliding with background (only + used for generated moving carts and not stationary carts) +* bit 7 0 - specifies the cart should reverse direction after collision with + background + +### 16 - Armored Door + +* Other Names: Armored Door With Siren + +### 17 - Mortar Launcher + +* Other Names: Boss Mortar + +Launches mortar shots (enemy type #$0b) on the boss screen. + +### Attributes + +* `...x` - initial attack delay. bit 0 set is a #$10 delay, bit 0 clear is a + #$60 delay. + +#### Logic + +* `ENEMY_VAR_1` - direction of generated launched mortar shot. After creating + mortar shot, the launcher updates the mortar shot's `ENEMY_VAR_1` to match + launcher's value. Ultimately, is used as offset into + `mortar_shot_velocity_tbl`. +* `ENEMY_FRAME` - relative offset into `level_7_tile_animation`, to get real + offset add #$08 to this value. + +### 18 - Soldier Generator + +* Other Names: Boss Soldier Generator, Enemy Generator + +Generates waves of soldiers on the hangar boss screen. Stops generating +soldiers once both mortar launchers (enemy type #$17) are destroyed or 30 waves +of soldier attacks have occurred. Soldiers are generated on either side of the +screen depending on players' location. To make things more challenging, once +one mortar launcher is destroyed, or if 20 waves of soldier attack have +occurred, then even if the player is close to the left side, soldiers will +randomly be generated on the left side. + +#### Logic + +* `ENEMY_VAR_1` - number of soldiers to generate for current attack wave +* `ENEMY_VAR_2` - facing direction of the soldiers to generate + * 0 - left (comes from right side of screen) + * 1 - right (comes from left side of screen) +* `ENEMY_VAR_3` - number of waves of soldiers generated +* `ENEMY_FRAME` - offset into `boss_soldier_nametable_update_tbl`, which + references `level_7_nametable_update_supertile_data`. Specifies the the + super-tiles to update for the opening of the armored door to let soldiers + advance. +* `BOSS_SCREEN_ENEMIES_DESTROYED` - number of destroyed mortar launchers. Used + to know when to stop generating soldiers. The actual memory location is $85. + +## Level 8 - Alien's Lair + +### 10 - Emperor Demon Dragon God Java + +* Other Names: Alien Guardian, Red Falcon (formerly) + +Worm-like alien who generates bundles (enemy type #$11). + +#### Logic + +* `ENEMY_VAR_1` - super-tile code for mouth relative references to entries in + `level_8_nametable_update_supertile_data` + * `alien_guardian_routine_04` +* `ENEMY_VAR_2` - whether or not the super-tiles were able to be drawn +* `ENEMY_VAR_3` - whether or not the super-tiles were able to be drawn +* `ENEMY_VAR_4` - delay between open mouth attacks + +### 11 - Bundle + +* Other Names: Alien Fetus + +### Attributes + +* `.... ...x` + * 1 - start enemy aim direction at #$18 (see `alien_fetus_routine_00`) + +#### Logic + +* `ENEMY_VAR_1` - [#$00-#$0b] aim direction starting #$00 facing right + incrementing clockwise. Every #$03 values causes a different sprite to show. +* `ENEMY_VAR_2` - whether or not the mouth is open +* `ENEMY_VAR_3` - timer between re-aiming towards player + `alien_fetus_aim_timer_tbl` +* `ENEMY_VAR_4` - bit 0 specifies which player to aim at. Decremented by #$03 + each time, so the targeted player changes. + +### 12 - Wadder + +* Other Names: Alien Mouth + +Generates Poisonous Insect Gel (enemy type #$13). Note that the wadder's mouth +does not need to be open for it to generate poisonous insect gels. + +They are no attributes for this enemy type. + +#### Logic + +* `ENEMY_ANIMATION_DELAY` - timer for generating poisonous insect gel creatures + (enemy type #$13). This is a random number between #$1f and #$df inclusively. +* `ENEMY_VAR_3` - animation timer between updating super-tile. The mouth will be + open for #$20 frames, then closed for #$20 frames. +* `ENEMY_VAR_4` - only bit 0 is used and it specifies which super-tile to draw, + either mouth open or closed, see `level_8_nametable_update_supertile_data`. + * #$00 = alien mouth closed + * #$01 = alien mouth open + +### 13 - Poisonous Insect Gel + +* Other Names: White Sentient Blob, White Blob + +They are no attributes for this enemy type. + +#### Logic + +* `ENEMY_VAR_1` - aim direction of white blob [#$00-#$16]. #$00 is straight to + the right (3 o'clock), increment clockwise until #$16, which is about + 2 o'clock. +* `ENEMY_VAR_2` + * `white_blob_routine_01`random delay before moving to next routine, e.g. + delay before freezing to then hone in on player. [#$50-#$6f] + * `white_blob_routine_02` - timer tracking current number of frames to keep + the same velocity before retargeting the player +* `ENEMY_VAR_3` - which player to attach (0 = p1, 1 = p2) +* `ENEMY_VAR_4` + * `white_blob_routine_01` - how many frames for the white blob to 'freeze' + before attacking, either #$02 or #$22, see `white_blob_set_freeze_length`. + * `white_blob_routine_02` - how many frames to keep the same velocity before + retargeting the player +* `ENEMY_ANIMATION_DELAY` - interpreted as 2 nibbles + * high nibble - timer for when to change to the next sprite. Sprite changes + every #$08 frames + * low nibble - keeps track of `white_blob_spider_sprite_tbl` index. Used to + know which sprite to show next + +### 14 - Bugger + +* Other Names: Alien Spider + +This enemy's routine flow is different depending on how it was created. When +defined in the screen enemies data structure, `alien_spider_routine_00` is the +first routine. When spawned by Eggron (enemy type #$15), the first routine is +`alien_spider_routine_01`. + +```mermaid + graph TD + level_screen_enemy(level screen enemies) --> alien_spider_routine_00 + alien_spider_routine_00 --> alien_spider_routine_03 + alien_spider_routine_02 --> |egg spawns into spider|alien_spider_routine_03 + alien_spider_routine_03 --> |spider jumps|alien_spider_routine_04 + alien_spider_routine_04 --> |spider collides with ground/ceiling|alien_spider_routine_03 + + spawned_by_heart_boss(spawned by heart boss) --> alien_spider_routine_01 + alien_spider_routine_01 --> alien_spider_routine_02 +``` + +#### Logic + +* `ENEMY_VAR_1` - the player to attack. If two players, will attack either + player 1 or player 2 randomly + * #$00 - player 1 + * #$01 - player 2 +* `ENEMY_VAR_2` - whether or not the alien spider will jump + * #$00 - will not jump + * #$02 - will jump +* `ENEMY_VAR_3` - y fast velocity of spider +* `ENEMY_VAR_4` - y fractional velocity of spider + +### 15 - Eggron + +* Other Names: Spider Spawn, Cocoon + +Enemy that appears with the final boss of the game, Gomeramos King. It produces +buggers to defend the alien heart. + +#### Logic + +* `ENEMY_VAR_1` - nametable update super-tile index to draw, all values have + bit 7 set, indicating to not update the palette. Indexes into + `level_8_nametable_update_supertile_data`. Used to animate spewing out an + alien spider. +* `ENEMY_VAR_3` - frame number [#$00-#$02], used to keep track of which + super-tile to draw. Directly correlates to `ENEMY_VAR_1`. +* `ENEMY_VAR_4` - delay between generating alien spiders + +### 16 - Gomeramos King + +* Other Names: Alien Heart, Heart, Boss Heart + +Gomeramos King is the final boss of the game. It is the beating heart of the +Emperor Demon Dragon God Java. + +#### Logic + +* `ENEMY_VAR_2` - used to increment `ENEMY_VAR_4` +* `ENEMY_VAR_4` - used to specify super-tiles for heart beating animation \ No newline at end of file diff --git a/docs/Enemy Routines.md b/docs/Enemy Routines.md new file mode 100644 index 0000000..776d957 --- /dev/null +++ b/docs/Enemy Routines.md @@ -0,0 +1,210 @@ +# 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. \ No newline at end of file diff --git a/docs/Graphics Documentation.md b/docs/Graphics Documentation.md new file mode 100644 index 0000000..a645412 --- /dev/null +++ b/docs/Graphics Documentation.md @@ -0,0 +1,517 @@ +# Overview + +In the NES, a frame consists of the background and the sprites. Both the +background and the sprites are composed of 8x8 pixel tiles from the pattern +table. The background is 32 tiles wide and 30 tiles tall, for a total of +256x240 pixels. This data structure is called the nametable. Each byte in the +nametable references an index into the pattern table. + +The pattern table is the data structure that describes what the 8x8 tiles in the +nametable and sprites look like. Each pattern table entry is 16 bytes. This is +the fundamental drawing block of the NES. + +There are 4 nametables in total, but only 2 are written to and the remaining two +are copies of the initial 2 nametables (mirroring). Additionally, each +nametable has 64-bytes at the end to specify which palette is assigned each part +of the nametable. This is called the attribute table. Each entry in the +attribute table specifies the palette for a 4x4 set of tiles in the nametable. + +Finally, there are sprites, which are drawn on top of the nametable and can be +easily moved around the screen. + +Addresses specified in this article include both the address that is used by the +NES CPU to locate data, as well as the offset address in the PRG ROM where the +referenced address is. Note that a ROM file has a #$10 byte iNES header that +doesn't exist in actual PRG ROM. So when using the ROM address to look for the +location in a NES ROM file, you will need to add an addition #$10 bytes to the +ROM address. + +## Terms + + * Attribute Table - one of 2 64-byte tables (one for each nametable) that + specifies which palettes to use for each part of the nametable + * Background - A combination of the nametable and the attribute table + * Graphic data - sections of compressed graphic data, typically pattern data, + but can be nametable data, or even attribute data as well + * Nametable - matrix of bytes referencing items in the pattern table. Can + generally be thought of as the background. + * Palette - a set of 4 colors that can be used for coloring a tile + * Pattern table - a 16 byte object defining the look of an 8px by 8px tile + * Screen - a set of super-tiles for a level that is 256 pixels wide + * For horizontal levels, the size of a screen is 8x7 super-tiles, and for a + vertical level it is 8x8 super-tiles + * Sprite - a graphical element composed of pattern table tiles. Some + documentation refers to these as 'objects'. + * Sprites in _Contra_ are 8x16, meaning 2 pattern table tiles make up each + component of a sprite. + * Super-tile - a group of 4x4 set of tiles that are used to construct a level's + nametable. This term isn't standard and was borrowed from part 2 of Allan + Blomquist's 7-part series on _Contra_ called [Retro Game Internals](https://tomorrowcorporation.com/posts/retro-game-internals-contra-levels). + Some people may refer to this as a meta tile. + * Tile - an element of the nametable, represents an 8x8 pixel from the pattern + table + +# Background + +Usually, for attribute, palette, and sprite data, the data is first loaded from +the PRG ROM and then moved into CPU memory. Then, every frame that data is read +from CPU memory into the PPU to display. Pattern table data is read directly +from PRG ROM into the PPU. + +The graphic data is in various places throughout the PRG ROM. For example +(with the exception of the intro sequence and ending sequence), the palette +colors are in bank 7, while the attribute table data is mostly in bank 3. For +the game pattern table data, this is stored in compressed blocks in banks 2, 4, +5, and 6. I refer to these blocks of data as 'graphic data', because while it +mostly stores pattern table data, in some cases (intro, outro, and clearing +screen) it does store nametable, and attribute data. + +The graphic data in the game ROM are compressed using a encoding system known as +[Run-length encoding](https://en.wikipedia.org/wiki/Run-length_encoding) (RLE). +This algorithm is covered in another section in this document. As the graphic +data are loaded into PPU memory from ROM memory, they are decompressed. + +A level contains multiple "graphic data". These make up all the graphics (minus +sprites) necessary for a level. + +As part of loading graphics a level, the `load_level_graphic_data` label is +executed, this looks up the appropriate graphic data from the +`level_graphic_data_tbl` and then immediately proceeds to decompress and write +the data to the PPU with label `write_graphic_data_to_ppu`. + +As an example, the 7 graphic data used for level 1 `level_1_graphic_data` +(ROM: $1c8fd, MEM: $c8fd) are as follows: #$03, #$13, #$19, #$1a, #$14, #$16, +#$05, #$ff. Each byte is an offset entry into the `graphic_data_ptr_tbl` table. + +`load_level_graphic_data` will load each of those graphic data offsets into the +A register and then call `write_graphic_data_to_ppu`, which decompresses and +writes the graphics into the PPU. This is repeated by `load_level_graphic_data` +until the offset specified is #$ff, which signifies there are no more graphics +data for the level to load. + +While in theory you could specify the nametable with this same scheme, the +level's nametables are not specified in these graphics data in general and +instead graphics data is used only for pattern and attribute data. The only +time the graphics data contains nametable data is for either blanking the +nametable (`graphic_data_00` (ROM: $1cb36, MEM: Bank 7 $cb36)), or setting up +the nametable for the intro and ending scenes (`graphic_data_02` and +`graphic_data_18` respectively). Level nametable information is populated by +"Super-Tiles". Read that section for more information. + +In general, the left pattern table, PPU addresses [$0000-$1000), is used for +storing pattern tiles associated with sprites. The right pattern table, PPU +addresses [$1000-$2000), is used for storing pattern tiles associated with the +nametable. This is useful to understand, because sprites are displayed with 2 +pattern table tiles, making up a 8x16 sprite. So, when viewing the PPU during +a level with any debugging tools, it's appropriate to view the left pattern +table as 8x16 sprites if that option is available. + +## Graphics Data Locations + +Below is a table of all graphic data locations in the ROM (excluding alternate +graphics data). + +The PPU Address ranges are specified in interval notation. + * The square brackets [] represent 'closed interval' and the address is + included in the range + * The parentheses () represent 'open interval' and are not included in the + range + +| Label Name | Bank | Label In-Memory Address | PRG ROM Address | Graphics Data | PPU Addresses | Comments | +|-------------------|------|-------------------------|-----------------|-----------------------------------------|------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------| +| `graphic_data_00` | 7 | $cb36 | $1cb36 | Both nametables + Both Attribute tables | [$2000-$2800) | Sets all Nametable + Attribute table data to #$00 | +| `graphic_data_01` | 4 | $aa2d | $12a2d | Left and Right Pattern tables | [$0ce0-$1f80) | Used for intro screen, level title screens, and game over screens | +| `graphic_data_02` | 2 | $9097 | $9097 | Nametable 0 + Attribute table 0 | [$2000-$2400) | Used for intro screen | +| `graphic_data_03` | 4 | $8001 | $10001 | Left Pattern table | [$0000-$0680) | Used in every level. Contains Bill and Lance blocks, game over letters, lives medals, power-ups (SBFLRM), and explosions | +| `graphic_data_04` | 4 | $85ae | $105ae | Left Pattern table | [$0680-$08c0) | | +| `graphic_data_05` | 5 | $8001 | $14001 | Left and Right Pattern tables | [$09a0-$0a80), [$0dc0-$1200), [$1320-$1600), [$1bd0-$2000) | Character when immobile, and prone. Also has weapon zeppelin. writes to same PPU addresses as `graphic_data_07` and `graphic_data_0b` | +| `graphic_data_06` | 4 | $99fc | $119fc | Left and Right Pattern tables | [$08c0-$1100) | Most Base graphics | +| `graphic_data_07` | 5 | $8a61 | $14a61 | Left and Right Pattern tables | [$09a0-$0a80), [$0dc0-$1200), [$1320-$1600), [$1bd0-$2000) | Writes to same PPU addresses as `graphic_data_05` and `graphic_data_0b` | +| `graphic_data_08` | 4 | $886c | $1086c | Left and Right Pattern tables | [$09a0-$2000) | | +| `graphic_data_09` | 4 | $99cd | $119cd | Left Pattern table | [$0b00-$0b40) | | +| `graphic_data_0a` | 4 | $a005 | $12005 | Right Pattern table | [$1100-$1520) | | +| `graphic_data_0b` | 5 | $93e0 | $153e0 | Left and Right Pattern tables | [$09a0-$0a80), [$0dc0-$1200), [$1320-$1600), [$1bd0-$2000) | Writes to same PPU addresses as `graphic_data_05` and `graphic_data_07` | +| `graphic_data_0c` | 6 | $8001 | $18001 | Left and Right Pattern tables | [$09a0-$0a80), [$0dc0-$0ee0), [$0fc0-$1200), [$1320-$2000) | | +| `graphic_data_0d` | 6 | $8cdc | $18cdc | Left and Right Pattern tables | [$09a0-$0a80), [$0dc0-$0ee0), [$0fc0-$1200), [$1320-$2000) | | +| `graphic_data_0e` | 6 | $9bd6 | $19bd6 | Left and Right Pattern tables | [$09a0-$2000) | | +| `graphic_data_0f` | 4 | $a346 | $12346 | Right Pattern table | [$1520-$1600) | | +| `graphic_data_10` | 4 | $a003 | $12003 | Right Pattern table | [$1a20-$2000) | Same as `graphic_data_11`, but horizontally flipped | +| `graphic_data_11` | 4 | $a3e7 | $123e7 | Right Pattern table | [$1a20-$2000) | | +| `graphic_data_12` | 4 | $a940 | $12940 | Right Pattern table | [$1b90-$1ca0) | | +| `graphic_data_13` | 4 | $87a1 | $107a1 | Left Pattern table | [$08c0-$09a0) | | +| `graphic_data_14` | 5 | $a814 | $16814 | Right Pattern table | [$1600-$1bd0) | | +| `graphic_data_15` | 6 | $b07a | $1b07a | Left Pattern table | [$0ee0-$0fc0) | | +| `graphic_data_16` | 6 | $b15c | $1b15c | Right Pattern table | [$1200-$1320) | | +| `graphic_data_17` | 5 | $addf | $16ddf | Left and Right Pattern tables | [$0a60-$0fe0), [$15b0-$18a0) | | +| `graphic_data_18` | 5 | $b30d | $1730d | Nametable 0 + Attribute table 0 | [$2000-$2400) | Used in ending scene | +| `graphic_data_19` | 5 | $a31b | $1631b | Left Pattern table | [$0680-$08c0) | | +| `graphic_data_1a` | 5 | $a500 | $16500 | Left Pattern table | [$0a80-$0dc0) | | + +## Background Collision Data + +The tiles in the pattern tables are ordered by which collision group they are +in. There are 4 collision groups in Contra. The level header byte offsets +#$09, #$0a, and #$ab define the boundaries of each collision group. For +example, level 1's tile collision boundaries are $06, $f9, and $ff. This means + + * Pattern tile index $00 has collision code 0 (empty). This is always true + * Pattern tile indexes $01-$05 have collision code 1 (floor) + * Pattern tile indexes $06-$f8 have collision code 0 (empty) + * Pattern tile indexes $f9-$fe have collision code 2 (water) + * Pattern tile index $ff has collision code 3 (solid) + +For this reason, sometimes there are duplicate pattern table tiles, simply to +create barriers. + +The collision codes are defined as follows. Technically Collision Code 2 is +treated as Collision code 0 #$00 + * Collision Code 0 (Empty) - tiles that have no collision data. Objects can + freely pass through the tile + * Collision Code 1 (Floor) - tiles that objects can land on when falling but + pass through when moving left, up and right. Can fall through (drop down) + with d-pad down, plus A only when there is another non-empty object below + (or on vertical level). + * Collision Code 2 (Water) - similar to code 1, but for water, prevents + jumping, and causes player/enemy sprites to show only top half + * Collision Code 3 (Solid) - The tile is solid and cannot be gone through + regardless of directions or side. + +For outdoor levels, the nametable collision codes are stored in CPU memory +`BG_COLLISION_DATA` [$0680-$0700). Nametable collision codes are only stored +for every other column and every other row of the nametable. Since a super-tile +is 4 columns wide and 4 columns tall, a single super-tile has 4 collision code +points: top left, middle left, middle top, and middle middle. + +Each byte in `BG_COLLISION_DATA` can be broken down into 4 collision codes. +Every 2 bits represents the collision code (0, 1, 2, or 3) for one of the 4 +points in the super-tile. The first 8 bytes are for the top rows of collision +points for a single nametable row. The next 8 bytes are the middle collision +points. This pattern repeats until all of the first nametable data is specified +and then is repeated for the 2nd nametable. For a given background collision +entry, add #$04 to determine the next row down. The following is an example of +the byte offsets from `BG_COLLISION_DATA` for the top left nametable. Note that +next nametable starts at offset #$40 and not #$38. + +Note that this is similar in structure to the attribute table. + * Each byte contains data for a #$0f (16) nametable tiles + * Each 2 bits within the byte specifies data for 4 nametable table tiles (2x2 + area). + +However, the offsets within the structure refer to different areas of the +nametable. Unlike the attribute table, where each byte specifies a 4x4 area of +pattern table tiles, the background collision data byte specifies data for a 2x8 +area of pattern table tiles. The table below shows how the background collision +data is arranged on the nametable. + +``` +00 00 00 00 01 01 01 01 02 02 02 02 03 03 03 03 +04 04 04 04 05 05 05 05 06 06 06 06 07 07 07 07 +08 08 08 08 09 09 09 09 0a 0a 0a 0a 0b 0b 0b 0b +0c 0c 0c 0c 0d 0d 0d 0d 0e 0e 0e 0e 0f 0f 0f 0f +10 10 10 10 11 11 11 11 12 12 12 12 13 13 13 13 +14 14 14 14 15 15 15 15 16 16 16 16 17 17 17 17 +18 18 18 18 19 19 19 19 20 20 20 20 21 21 21 21 +22 22 22 22 23 23 23 23 24 24 24 24 25 25 25 25 +26 26 26 26 27 27 27 27 28 28 28 28 29 29 29 29 +30 30 30 30 31 31 31 31 32 32 32 32 33 33 33 33 +34 34 34 34 35 35 35 35 36 36 36 36 37 37 37 37 +``` + +Indoor/base levels do not really rely on the `BG_COLLISION_DATA` for background +collision detection. The level header byte offsets #$09, #$0a, and #$ab for +the indoor/base levels are #$00, #$ff, #$ff, which means all tiles are collision +code 0, i.e. empty. The player is prevented from walking past the left and +right on indoor/base levels walls manually in the code, e.g. see +`level_right_edge_x_pos_tbl`, and `level_left_edge_x_pos_tbl`. + +## Super-Tiles +_Contra_'s level nametable data is made up of "Super-tiles". Each super-tile is +#$10 bytes long and made of 4 rows of 4 pattern table entries each. Super-tiles +are not RLE-encoded. A super-tile is how the level nametable's are defined and +can be thought of as smallest unit of art that can be used for nametable drawing +in a level. This isn't technically true, because a nametable can be updated +after it's drawn for certain animations (claw, fire beam, etc). For horizontal +levels, a background is composed of #$38 super-tiles (8x7). For the vertical +level, a background is composed of #$40 super-tiles (8x8). + +Within a super-tile, there are 4 2x2 sections each #$4 bytes long. + +The location of the pattern table tiles of each super-tile pattern is specified +in the 2-byte level header data byte #$04 (`LEVEL_SUPERTILE_DATA_PTR`). For +example, level 1's super-tile data is defined at `level_1_supertile_data` +(ROM: $c001, MEM: Bank 3 $8001). Each #$10 bytes is a single super-tile. Level +1 has a total of #$3b distinct super-tiles. + +For each level, the 2-byte level header offset byte #$02 +(`LEVEL_SCREEN_SUPERTILES_PTR`) specifies which super-tiles are part of each +'screen'. A 'screen' is the set of #$38 or #$40 super-tiles that make up a +single background. For an example, level 1's screen super-tile index data is +located at `level_1_supertiles_screen_ptr_table` +(ROM: $8001, MEM: Bank 2 $8001). Each entry specifies the super-tiles used for a +single screen. The super-tiles used in the first screen of level 1 are +specified at `level_1_supertiles_screen_00` (ROM: $801d, MEM: Bank 2 $801d). +This data is compressed with RLE-encoding + +Super-tiles indexes are stored in CPU memory and referenced when drawing the +background. 2 screens of super-tile indexes are stored in memory at a time. +This is to support scrolling. The super-tiles indexes for the screens are +loaded into CPU memory at location $0600 (`LEVEL_SCREEN_SUPERTILES`) by +`load_supertiles_screen_indexes` and used when writing the pattern table tile +data to CPU memory by `load_level_supertile_data`. The writing of the +super-tiles to the nametable takes place incrementally, one nametable column at +a time. + +# Sprites +Sprites are movable objects that are composed of elements from the pattern +table. This documentation refers to a group of sprites together as a single +sprite. For example, Bill is composed of 5 sprites, but will spoken about as +a single sprite. Sprites in _Contra_ are composed of 2-block pattern table +entries stacked on top of each other, making the smallest part of a sprite 8x16. +Like most NES games, _Contra_ does not write sprite data directly to the PPU +byte by byte via the `$2004` (OAMDATA) address but instead utilizes the `$4114` +(OAMDMA) address. OAMDMA stands for Object Attribute Memory Direct Memory +Access. The OAMDMA address is set to #$02, which tells the PPU to load sprite +data from the address range `$0200` to `$02ff` inclusively every frame. + +All of the sprites in _Contra_ are in bank 1 starting with `sprite_00` (ROM: +$71ce, MEM: Bank 1 $b1ce). Each sprite can be referred to by an index starting +from 00. I refer to these as the "sprite code" or "sprite number". There are +two large pointer tables that point to each sprite address: `sprite_ptr_tbl_0` +and `sprite_ptr_tbl_1`. + +## Sprite CPU Buffer +Sprites are drawn on the screen every frame after the current game routine is +run. This done in the `draw_sprites` label (ROM: $6e97, MEM: Bank 1 $ae97). +`draw_sprites` uses data stored in `$0300` to `$0367` inclusively. I refer to +this area of memory as the sprite cpu buffer. _Contra_ supports a maximum of +#$19 sprites on screen simultaneously. However, the certain addresses are +reserved for "player" sprites, whereas other addresses are reserved for enemy +sprites. The list below shows how the sprite CPU buffer is partitioned + +| Address Range | Length | Alias | Usage | +|--------------------|------------|-------------------|------------------------------------------------------------------------------------| +| `$0300` to `0309` | #$0a bytes | PLAYER_SPRITES | sprite number for player and player bullets (`sprite_ptr_tbl` or `sprite_ptr_tbl_1`| +| `$030a` to `0319` | #$10 bytes | ENEMY_SPRITES | enemy sprite number (`sprite_ptr_tbl` or `sprite_ptr_tbl_1`) | +| `$031a` to `0323` | #$0a bytes | SPRITE_Y_POS | y position on screen of the each sprite | +| `$0324` to `$0333` | #$10 bytes | ENEMY_Y_POS | y position of each enemy sprite | +| `$0334` to `$033d` | #$0a bytes | SPRITE_X_POS | x position on screen of the each sprite | +| `$033e` to `$034d` | #$10 bytes | ENEMY_X_POS | x position on screen of the each enemy sprite | +| `$034e` to `$0357` | #$0a bytes | SPRITE_ATTR | sprite attributes (palette, whether to flip horizontally, vertically) | +| `$0358` to `$0367` | #$10 bytes | ENEMY_SPRITE_ATTR | sprite attributes (palette, whether to flip horizontally, vertically) | + +`draw_sprites` loops from #$19 to #$00 and uses the data from the sprite CPU +buffer to populate the #$100 bytes `$0200` to `$02ff`. This data is then moved +to the PPU with OAMDMA when in vertical blank `nmi_start`. + +One neat trick that's done when loading data from the sprite CPU buffer to the +`$0200` page memory is that every time the `draw_sprites` label is called, an +offset of #$4c is added to the write address. This causes the sprites to be +placed in a different starting spot in the `$0200` page every frame. This is +done to work around a limitation of the NES that restricts a scan line from +having more than 8 sprites. By moving the sprites around in memory, if there are +too many sprites on a scan line, different sprites will be drawn on different +frames. The player shouldn't be able to detect a sprite missing if drawn quickly +enough. + +When filling the OAMDMA data, the sprite tiles aren't written sequently, but +rather spaced every 49 sprite tiles apart (#$c4 bytes) wrapping around. I'm not +sure why this was done. Perhaps it has to do with concern about dynamic RAM +decay that OAM has. + +## Sprite Number Encoding +The sprite data in bank 1 is encoded. Except when #$fe, the first byte +specifies the number of entries in the sprite. There there are that many groups +of 4-bytes. Each #$4 bytes specify two tiles that are stacked vertically. +Except when the first byte is #$80, these four bytes follow the PPU OAM byte +specification. For details on this data structure, +[NES Dev Wiki](https://wiki.nesdev.org/w/index.php?title=PPU_OAM) has a good +article on this. These specifics are also outlined below. + + * Byte 0 specifies the y position of the tile relative to where the sprite is + placed on the screen. This can be negative. + * If Byte 0 is #$80, then it is a signal to change the read address. The + next two bytes specify the CPU address to move to. This functionality + allows sprites to share pieces from other sprites. + * Byte 1 is the tile number, i.e. which tile in the pattern table to use. + * Since sprites are built from 8x16 components, and byte 1 only specifies + the first tile, the second tile stacked immediately below this tile is + always the next one in the pattern table. For example, if byte 1 is #$05, + then the two tiles will be #$05 and #$06. If the tile specified is even, + then the tile + * All of _Contra_'s sprites are composed of even bytes, meaning the tile is + pulled from the left pattern table ($0000-$0fff). + * Byte 2 specifies various attributes that can be used to modify the tiles + * Bit 7 (`x... ....`) specifies vertical flip + * When the vertical flip bit is set, the top and bottom tiles are drawn + vertically flipped. Also, the top tile is drawn at the bottom and the + bottom tile is drawn at the top. + * Bit 6 (`.x.. ....`) specifies horizontal flip + * Whether or not to flip the tile horizontally + * Bit 5 (`..x. ....`) specifies drawing priority + * 0 = in front of background; 1 = behind background + * Bits 1 and 0 (`.... ..xx`) specify the palette code + * These bits specify the palette to use for the sprite + * Byte 3 specifies the x position of the tile relative to where the sprite is + placed on the screen. This can be negative. + +When the first byte of the sprite code is #$fe, the sprite is a "small" sprite. +Small sprites are composed of #$3 bytes, including the #$fe byte. Small sprites +always have their X position set to #$fc (-4) and their Y position set to #$f8 +(-8). + + * Byte 0 is #$fe and signifies the sprite is a small sprite + * Byte 1 specifies the tile number + * Byte 2 is the sprite attributes + +Note: Sprites are also referred to as objects. This explains the names of some +addresses in NES like Object Attribute Memory (OAM). + +# Palette +The NES supports 4 palettes for the nametables (background) and 4 palettes for +sprites. A palette is a set of 3 colors. For nametables, one palette is shared +for each square 16x16 set of 4 pattern table tiles. For sprites, each tile in a +sprite has its own palette. + +Defining which palette is assigned to which super-tile is located in bank 3. The +level headers specify the location in bank 3 of where the super-tile palette +data is defined (see `LEVEL_SUPERTILE_PALETTE_DATA`). This data is used to +populate the attribute table. + +For each sprite tile, the palette used is specified in the two least significant +bits of byte 2 of the sprite OAM data. For details about how sprites are stored +in the PRG ROM, see Sprite Number Encoding. + +The initial colors that are associated with each palette are specified in the +level header starting at byte offset 15 (see `LEVEL_PALETTE_INDEX`). Each of +the next 8 bytes specify an entry into `game_palettes`. The first 4 bytes are +the nametable palette colors, and the next 4 bytes are the sprite palette +colors. Each entry in `game_palettes` specifies the 3 colors that make up the +palette. + +## Fade-In Effect +_Contra_ supports a fade in effect for nametable palettes. This effect is used +various parts of the game like base/indoor boss screens, dragon, and boss ufo. +This is implemented by adjusting the specified nametable palette colors by an +amount specified in `palette_shift_amount_tbl` when `BG_PALETTE_ADJ_TIMER` is +specified and within range [#$01-#$09]. To utilize this effect, the code sets +the `BG_PALETTE_ADJ_TIMER` to a value larger than #$09. Every frame, this value +is decremented. When loading the palette colors for the frame, if +`BG_PALETTE_ADJ_TIMER` is greater than #$09, then the palette color will be +black, but once the value is in range, the palette colors will start to be +modified. For example, to create the fade-in effect of the base/indoor boss +screen background, the value of `BG_PALETTE_ADJ_TIMER` is set to #$0c. This +gives #$03 frames of black, before the fade in effect starts. + +## Cycling +_Contra_ cycles the colors within nametable palettes during game play. The +palettes specified by the nametable don't change, but rather the colors within +the palettes are changed. By swapping out the colors, _Contra_ can create +animations like blinking stars, waves in water, waterfalls, etc. Each level +natively supports having its 4th nametable (background) palette cycle through up +to 4 different sets of colors, where each set of colors is represented by an +index into the `game_palettes` table. The specific indexes to cycle through are +in the level headers (`LEVEL_PALETTE_CYCLE_INDEXES`). Level 3 (Waterfall) is +special in that it only cycles among 3 different sets of colors instead of 4 +(see `lvl_palette_animation_count`). + +Additionally, all levels cycle their 3rd nametable palette colors through a +single, shared set of palette indexes (`level_palette_2_index`). This cycle of +colors is a flashing red. It is used so that enemies have flashing red lights. + +Finally, there are a few special nametable cycles, like for indoor (base) boss +screens (`indoor_boss_palette_2_index`), and ending screen +(`ending_palette_2_index`). + +Typically sprites don't have any palette color cycling support natively, but +when invincible, the player sprites will cycle the palette colors. + +Like sprite data and nametable data, the palette color data is loaded from PRG +ROM into CPU memory first. Then every frame, that data is read and loaded into +PPU memory. Every 8 frames, the `LEVEL_PALETTE_CYCLE` is incremented so that the +palettes colors are changed. + +Every frame, the `load_palette_indexes` looks at the current +`LEVEL_PALETTE_CYCLE` and uses that to determine the appropriate indexes into +`game_palettes` to store into CPU memory $52 and $53 (`LEVEL_PALETTE_INDEX`). +Then `load_palette_colors_to_cpu` will look up the colors from the +`game_palettes` table and store the colors in CPU memory starting at $07c0 +(`PALETTE_CPU_BUFFER`). Then in `write_palette_colors_to_ppu`, the data is read +from $07c0 to $07df and written to the PPU starting at $3f00 + +# _Contra_ Compression + +The graphic data for _Contra_ is largely compressed with a relatively basic +compression algorithm known as +[Run-length encoding](https://en.wikipedia.org/wiki/Run-length_encoding) (RLE). + +The idea of this algorithm is that the graphics data will have long "runs" of +repeated information. So instead of specifying the same byte over and over, it +can be encoded so that the number of repetitions is specified. Essentially, +instead of + * #$00 #$00 #$00 #$00 #$00 #$00 #$00 #$00 #$00 +It can be represented like + * #$89 #$00 + +Interestingly, this algorithm is implemented twice in _Contra_: once for pattern +table tiles (`write_graphic_data_to_ppu` and once again for super-tile +screen indexes (`load_supertiles_screen_indexes`). + +## Pattern Table Decompression +The graphic data is loaded from ROM directly to the PPU. Not only is the +graphic data compressed, it also includes command bytes that specify where in +the PPU to write the data to. This section outlines how the data is read and +decompressed. This logic is all handled by the `write_graphic_data_to_ppu` +label in bank 7. + +The graphic data are simply parts of the ROM that contain bytes. The first two +bytes of any graphic data are the starting address of where to write the graphic +data to on the PPU. For example, the first two bytes of `graphic_data_04` are +#$80, #$06. This means that the graphics data will be written to the PPU +starting at PPU address $0680 (pattern table). + +After the first two bytes are read, the following algorithm reads the graphics +data section. When reading the graphic data, if the most significant bit of the +graphic data byte is set, i.e 1, then the byte is treated as a command byte. +There are 3 command byte types + + * #$ff - specifies the end of graphic data + * #$7f - tells the algorithm to change PPU write address to the next 2 bytes + specified. + * bit 7 set - any byte larger than #$7f, or alternatively, any byte that has + its most significant bit set to 1 is treated as a RLE command to write the + next byte of data multiple times to the PPU. This excludes #$ff, which + always means end of graphic data + +Level section data not encoded exactly the same way +`level_1_supertiles_screen_ptr_table` + +# Quick Reference + +## Graphic Locations + * `level_graphic_data_tbl` (ROM: $1c8e3, MEM: Bank 7 $c8e3) specifies which + pattern table data is loaded for a level, indexes into `graphic_data_ptr_tbl` + * `graphic_data_ptr_tbl` (ROM: $1c950, MEM: $c950) specifies locations in banks + 2, 4, 5, 6, or 7 of RLE compressed graphic data (typically pattern table + data) + * `LEVEL_SUPERTILE_DATA_PTR` is specified in the level headers and points to + the compressed makeup of the level's super-tiles, entries are into the + pattern table. It is a 2-byte pointer stored in the CPU at address $44. + * `LEVEL_SCREEN_SUPERTILES_PTR` is specified in the level headers and it + points to a table whose entries are pointers to data specifying which + super-tiles are loaded in each screen of the level. That data is + RLE-compressed. It is a 2-byte pointer stored in the CPU at address $42. + +## Important Labels + * `load_level_graphic_data` (ROM: $1c8c6, MEM: Bank 7 $c8c6) decompresses and + loads the "Graphic data" for the current level + * `load_current_supertiles_screen_indexes` (ROM: $1e169, MEM: Bank 7 $e169) + decompresses and loads the super-tile indexes into memory + * `load_level_supertile_data` (ROM: $1df48, MEM: Bank 7 $df48) decompresses + and loads the super-tiles from the pattern table + * `write_graphic_data_to_ppu` (ROM: $1c9a1, MEM: Bank 7 $c9a1) loads and + decompresses the entire graphic data specified by the A register as offset + into `graphic_data_ptr_tbl` + * `@flush_cpu_graphics_buffer` (ROM: $1cc3f, MEM: $cc3f) writes all the data in + the CPU graphics buffer to the PPU + * `draw_sprites` (ROM: $6e97, MEM: Bank 1 $ae97) populates $0200-$02ff with + sprite data for OAMDMA. diff --git a/docs/How Contra Prints High Score.md b/docs/How Contra Prints High Score.md new file mode 100644 index 0000000..207a49a --- /dev/null +++ b/docs/How Contra Prints High Score.md @@ -0,0 +1,178 @@ +# How Contra Prints the High Score + +## Overview +There are 3 scores stored in CPU memory for _Contra_: high score, player 1 +score, and player 2 score. Each score is 2 bytes and stored in addresses +0x07e0-0x07e1, 0x07e2-0x07e3, and 0x07e4-0x07e5 respectively. These 2-byte +scores are little-endian, i.e. the least significant byte is the lower-addressed +byte. All scores have 00 appended when displayed, for example a score of 100 +will show as 10000 on the screen. The default initial high score stored in +0x07e2 is 0xc8, or 200 in decimal. This will show as 20000. The exception to +this is a score of 0. 0 points will show as 0 and not 000. + +The logic to display the score on the screen is non-trivial and worth properly +documenting. To display the score on the screen, the code needs to convert an +in-memory hexadecimal number to a decimal number. + +At a high level, _Contra_ recursively calculates each decimal digit to display +one at a time from right to left. If the player's score is 123 in decimal, or +0111 1011 in binary. _Contra_ will call the same routine 3 times, each time +producing the next digit in the score from right to left: 3, then 2, then 1. + +|Call # | Rest | Right Digit | +|-------|------|-------------| +| 1 | 12 | 3 | +| 2 | 1 | 2 | +| 3 | 0 | 1 | + +As you can see, each row can be read as Score = (10 * Rest) + Right_Digit +The Rest column can be conceptually though of as 10 times larger than what is +stored in memory. So 12 really means 120, and 1 really means 10. This will make +conceptualizing the algorithm easier. + +## Examples +This document's examples are simplified slightly in that these examples are +using 1-byte scores and not 2-byte scores like _Contra_. The source code shown +works against 2-byte scores. + +### Example of Routine +As shown above, the routine is called a number of times, each time retrieving +the next digit of the score to display. Below is a step-by-step walk through of +calculating a single digit of the score to display. + + Score (0x01): 0x7b 0111 1011 (123 in decimal) + + Right Digit (0x02): 0x00 0000 0000 (0) + +|Shift # | Rest | Right Digit | Input | Explanation | +|--------|-----------|-------------|----------|------------------------------| +| 1 | | 0 | 111 1011 | left shift | +| 2 | | 01 | 111 011 | left shift | +| 3 | | 011 | 11 011 | left shift | +| 4 | | 0111 | 1 011 | left shift | +| 5 | | 1111 | 011 | left shift | +| | | 0101 | 011 | subtract 10 from right digit | +| 6 | 1 | 1010 | 11 | shift left, mark 1 into Rest | +| | 1 | 0000 | 11 | subtract 10 | +| 7 | 11 | 0001 | 1 | shift left, mark 1 into Rest | +| 8 | 110 | 0011 | | shift left | +| 9 | 1100 | 0011 | | shift left Rest only | + + Rest (0x01): 0000 1100 (12 decimal) + + Right Digit (0x02): 0000 0011 (3 decimal) + +The way _Contra_ determines the right-most digit recursively is very +interesting. Every time the algorithm shifts left, the input is passed through +the "Right Digit" column which will track when the single-digit is more than 10. +Every time the right digit's column is more than 10, 10 is subtracted. On the +subsequent left shift, 1 is placed on the right-most bit of the Rest column. +That 1 signifies that a subtraction of 10 was performed. Remember that the Rest +column's value is actually 10 times larger, e.g. 12 really means 120. So when +the 1 is carried to the "Rest" column, it is really a 10 being carried, which +matches what was subtracted from the "Right Digit" column. + +### Memory-accurate Example With a 1-byte Score +While the previous example helps understand at a conceptual level, it hides some +of the technical ingenuity. _Contra_ doesn't split the score ("Input") into +"Right Digit" and "Rest". _Contra_ actually shifts the "Rest" back into the +"Input". This is useful because it allows the routine to be called +consecutively until there are no more digits to display. + +Again, _Contra_ uses 2-byte scores, but if _Contra_ had 1-byte scores, this is +how the algorithm would work, while being memory accurate. 0x01 is shifted into +0x02. Right Digit (0x02) is always initialized to zero. + + Score (0x01): 0x7b 0111 1011 (123) + + Right Digit (0x02): 0x00 0000 0000 (0) + +|Shift # | 0x01 | 0x02 | Explanation | +|--------|-------------|-------------|------------------------------| +| 1 | 1111 0110 | 0000 0000 | left shift 0x01 only | +| 2 | 1110 1100 | 0000 0001 | left shift 0x02, 0x01 | +| 3 | 1101 1000 | 0000 0011 | left shift 0x02, 0x01 | +| 4 | 1011 0000 | 0000 0111 | left shift 0x02, 0x01 | +| 5 | 0110 0000 | 0000 1111 | left shift 0x02, 0x01 | +| | 0110 0000 | 0000 0101 | subtract 10 | +| 6 | 1100 0001 | 0000 1010 | shift left, mark 1 into Rest | +| | 1100 0001 | 0000 0000 | subtract 10 | +| 7 | 1000 0011 | 0000 0001 | shift left, mark 1 into Rest | +| 8 | 0000 0110 | 0000 0011 | shift left 0x02, 0x01 | +| 9 | 0000 1100 | 0000 0011 | shift left 0x01 only | + + Rest (0x01): 0000 1100 (12 decimal) + + Right Digit (0x02): 0000 0011 (3 decimal) + +You can see that after this routine is called, it can be called again +immediately to determine the next right-most digit since the score address +(0x01) is overwritten with the new score. 0x02 is never more than 4-bits, and no +additional memory locations are needed. I believe this is the reason this +algorithm was used over other algorithms that can convert from hexadecimal to +decimal with a fixed number of steps. + +## _Contra_'s Score Display Algorithm in Assembly from Source +The code for converting the scores for display is in the last (8th) PRG ROM +bank, bank 7. The logic begins at address $caf8 in CPU memory. _Contra_ will +recursively determine the value of the "Right Digit" place, which is then +displayed on the screen to the player. + +CPU memory addresses used + * 0x00 - low byte of the current score being calculated + * 0x01 - high byte of the current score being calculated + * 0x02 - the next decimal digit to display + * 0x03 - hard-coded 10 in decimal + +``` +calculate_score_digit: + lda #$00 ; set the accumulator register A to zero (#$00) + sta $02 ; zero out any previously calculated digit + ldy #$10 ; set the left-shift loop counter back to #$10 (16 decimal) + rol $00 ; shift the score low byte to the left by one bit + ; push the most significant bit (msb) to the carry flag + rol $01 ; shift the score high byte to the left by one byte + ; push the msb to the carry flag + ; pull in carry flag to least significant bit (lsb) + +shift_and_check_digit_carry: + rol $02 ; shift score high byte to the left by one bit + ; if the msb of the score high byte was 1, then carry into lsb + lda $02 ; load current digit into the accumulator register A + cmp $03 ; compare #$0a (10 decimal) to the current digit + + ; branch if current digit is less than #$0a (10 decimal) + ; - this means no subtraction and carry is needed + ; if greater than #$0a (10 decimal), don't jump + ; - subtract 10 and carry + bcc continue_shift_score + sbc $03 ; the current digit is greater than 10, subtract 10 + ; this also sets the carry flag, which will be moved to the + ; low byte of the score, which is the "Rest" of the number + ; this carry represents adding 10 to the "Rest" + sta $02 ; store the difference (new current digit) back in $02 + +; $02 (current digit) is less than #$0a, or has just been subtracted +; continue algorithm by shifting score left +continue_shift_score: + rol $00 ; if just set $02 to digit by subtraction, this will put 1 + ; in $00's lsb, signifying adding 10 to "Rest" + rol $01 ; if $00's msb is 1, then it'll carry to the lsb of $01 + dey ; Y goes from $10 to $00, once Y is $00, the algorithm is done + bne shift_and_check_digit_carry + rts +``` + +### See Also +Converting a hexadecimal number to decimal can be done in multiple ways. A +common algorithm for doing this is +[double dabble algorithm](https://en.wikipedia.org/wiki/Double_dabble). _Contra_ +does not use this method. This is probably because the double dabble algorithm +requires more CPU memory space, whereas _Contra_'s algorithm uses the same, +fixed 2 bytes of CPU memory (0x01 and 0x02) for all score calculation. +Consequently, instead of a fixed number of operations like double dabble, +_Contra_ uses recursion, which requires a variable number of CPU cycle at the +benefit of fixed memory usage. + +Another interesting and related concept is the +[binary-coded decimal](https://en.wikipedia.org/wiki/Binary-coded_decimal). \ No newline at end of file diff --git a/docs/Level Headers.md b/docs/Level Headers.md new file mode 100644 index 0000000..68f2e8e --- /dev/null +++ b/docs/Level Headers.md @@ -0,0 +1,178 @@ +# Overview + +_Contra_ loads various level-specific configuration data from in bank 2. These +are called "level headers". These headers are #$20 (32) bytes for each level +and contain information about the level, for example whether the level is and +indoor level, or an outdoor level, vertical level or horizontal level, etc. + +In the code, the level headers are labeled `level_headers` and each level is +labeled with an appropriate `level_x_header` label. + +# Byte Offset 0 - Location Type + + * Offset: 0 + * Memory Location: $40 + * Variable Name: `LEVEL_LOCATION_TYPE` + +The first byte is the level's "location type". There are 3 location types + + * Outdoor - #$00 + * Indoor/Base - #$01 + * Indoor Boss - #$80 + +Outdoor levels are typical platformer side-scrolling levels and scrolls +horizontally or vertically. Indoor levels are also known as base levels and are +the levels where Bill and Lance are facing away from the player in a pseudo 3D +experience (Levels 2 and 4). + +Although the value is never #$80 in the level headers table, for the boss fights +on the indoor levels (2 and 4), the location type will be set to $80. It is +similar to the indoor location type (#$01), except the player cannot go prone +and shooting up looks like shooting forward (z-plane). When animating the +players walking into the screen between 3D screens, the value is also set to +#$80. + +# Byte Offset 1 - Outdoor Scrolling Type + + * Offset: 1 + * Memory Location: $41 + * Variable Name: `LEVEL_SCROLLING_TYPE` + +The second byte is the level's scrolling type. It is only used in outdoor +levels. + + * Horizontal - #$00 + * Vertical - #$01 + +# Byte Offsets 2 and 3 - Screen Super-Tile Pointer + + * Offset: 2 and 3 + * Memory Location: 2-byte address $42 and $43 + * Variable Name: `LEVEL_SCREEN_SUPERTILES_PTR` + +The 3rd and 4th bytes make up the pointer that point to the data that specifies +which super-tiles are on each screen. This is an address into Bank 2. The +address is 2 bytes and stored in little-endian, i.e. the least significant byte +first. + +For example, on level 3, the screen super tile pointer points to +`level_3_supertiles_screen_ptr_table` (ROM: $84ce, MEM: Bank 2 $84ce). This +address contains a list of pointers for each "screen" of the level. A screen is +a set of super-tiles for a level that is 256 pixels wide. Since level 3 is a +vertical level, each screen is 64 super-tiles (8x8). Looking at the data +specified at the address of a specific screen pointer, e.g. +`level_3_supertiles_screen_01` (ROM: $8635, MEM: Bank 2 $8635), you will find an +RLE-encoded set of bytes that specify the super-tile indexes used. After +decompressing, each byte is the offset into the +`level_3_supertile_data` (ROM: $cef8, MEM: Bank 3 $8ef8). + +# Byte Offsets 4 and 5 - Super-Tile Data + + * Offset: 4 and 5 + * Memory Location: 2-byte address $44 and $45 + * Variable Name: `LEVEL_SUPERTILE_DATA_PTR` + +The 5th and 6th bytes of the level headers are a pointer to the level's +super-tiles. Each entry in table at the specified address is #$10 bytes long +and is a single super-tile. This data is not RLE-encoded. Each byte in each +super-tile represent a pattern table tile. An example pointer is +`level_3_supertile_data` (ROM: $cef8, MEM: Bank 3 $8ef8) + +# Byte Offsets 6 and 7 - Super-Tile Palette Data + + * Offset: 6 and 7 + * Memory Location: 2-byte address $46 and $47 + * Variable Name: `LEVEL_SUPERTILE_PALETTE_DATA` + +Address to byte table that specifies the palette used for each super-tile in the +level. Each byte describes the 4 palettes for a single super-tile. An example +is `level_2_palette_data` + +# Byte Offset 8 - Alternate Graphics Loading Location + + * Offset: 8 + * Location: $48 + * Variable Name: `LEVEL_ALT_GRAPHICS_POS` + +This byte specifies the alternate graphics loading location. This is the screen +where the game has the ability to change out the pattern table tiles for a +level. + +# Byte Offsets 9, 10, and 11 - Tile Collision Limits + + * Offset: 9, 10, and 11 + * Location: $49, $4a, $4b + * Variable Name: `COLLISION_CODE_1_TILE_INDEX`, `COLLISION_CODE_0_TILE_INDEX`, + and `COLLISION_CODE_2_TILE_INDEX` (in that order) + +These three bytes are used to specify which pattern table tiles are which +collision codes. + +* Pattern table tiles below the value in $49 (excluding #$00) are considered +Collision Code 1 (floor). +* Pattern table tiles >= the value in $49, but less than the value in $4a are +considered Collision Code 0 (empty) +* Pattern table tiles >= $4a but less than this tile index are considered +Collision Code 2 (water) + +Pattern table tiles above the last entry, are considered Collision Code 3 +(Solid). + +# Byte Offset 12, 13, 14, and 15 - Cycling Palette Colors + + * Offset: 12, 13, 14, and 15 + * Location: $4c, $4d, $4e, and $4f + * Variable Name: `LEVEL_PALETTE_CYCLE_INDEXES` + +Each level natively supports having its 4th nametable (background) palette cycle +through up to 4 different sets of colors, where each set of colors is +represented by an index into the `game_palettes` table. This header section +contains those specific indexes to cycle through. + +For more details see `Graphics Documentation.md`. There is a section titled +`Palette -> Cycling`. + +# Byte Offset 16, 17, 18, and 19 - Nametable Palette Initial Colors + + * Offset: 16, 17, 18, and 19 + * Location: $50, $51, $52, and $53 + * Variable Name: `LEVEL_PALETTE_INDEX` + +Initial background tile palette colors for the level. Indexes into +`game_palettes`. + +# Byte Offset 20, 21, 22, and 23 - Nametable Palette Initial Colors + + * Offset: 20, 21, 22, and 23 + * Location: $54, $55, $56, and $57 + * Variable Name: `LEVEL_PALETTE_INDEX` + +Initial sprite tile palette colors for the level. Indexes into `game_palettes`. + +# Byte Offset 24 - Scroll Stop Screen + + * Offset: 24 + * Location: $58 + * Variable Name: `LEVEL_STOP_SCROLL` + +The screen of the level to stop scrolling. The value in memory is set to #$ff +when boss auto scroll starts + +# Byte Offset 25 - Solid Background Collision + + * Offset: 25 + * Location: $59 + * Variable Name: `LEVEL_SOLID_BG_COLLISION_CHECK` + +Specifies whether to check for bullet and weapon item solid bg collisions. + + 1. When non-zero, specifies that the weapon item should check for solid bg + collisions (see `weapon_item_check_bg_collision`) + 2. When negative (bit 8 set), specifies to let bullets for both players and + enemies to check for bullet-solid background collisions. This is used on + levels 6 - energy zone and 7 - hangar. (see + `check_bullet_solid_bg_collision` and `enemy_bullet_routine_01`) + +# Byte Offset 26 through 32 - Unused + +The last 6 bytes in each level's level header are unused and set to #$00. \ No newline at end of file diff --git a/docs/Sound Documentation.md b/docs/Sound Documentation.md new file mode 100644 index 0000000..ba8db9e --- /dev/null +++ b/docs/Sound Documentation.md @@ -0,0 +1,306 @@ +# Overview +A song or sound can be composed of multiple instruments or channels. The NES +has 5 channels: 2 pulse (square wave) channels, 1 triangle channel, 1 noise +channel, and 1 delta modulation channel. _Contra_ maintains 6 slots of data in +memory that are used in priority order to play sounds. Higher slots are played +before the lower slots. Each slot is linked to an NES sound channel. + + * #$00 = pulse 1 channel + * #$01 = pulse 2 channel + * #$02 = triangle channel + * #$03 = noise and dmc channel + * #$04 = pulse 1 + * #$05 = noise channel + +For example, if a sound is loaded in slot #$00 and slot #$04 (both pulse 1 +channel), then the sound in slot #$04 will be played since it is higher. The +code that converts from sound slot to channel is `@load_sound_channel_offset`. + +When the game loads a sound to play, it first determines which slots are needed +by loading data from `sound_table_00`. This table specifies the number of slots +needed to play the sound and where the instructions to play the sound exists, +i.e. the 2 byte cpu address where the sound channel instructions are. + +# Sound Codes + +Below is a table of all the sounds that exist in _Contra_, including unused +sounds. The Japanese names were obtained from the "sound mode" feature in the +Famicom version of the game. Each sound is related to one or more sound codes. +For example, the level 3 waterfall music uses 4 sound codes, which means that +sound uses 4 sound channels. + +At the bottom of the table are the DPCM samples that are used throughout the +game by various other sounds. + +| Sound | Japanese Name | Description | sound_code(s) | Slot | Command Type | Channel | +|-------|---------------|------------------------------------------------------|---------------|------|--------------|------------------| +| #$01 | | empty/silence, used to initialize channel | `sound_01` | | | | +| #$02 | | percussive tick (bass drum/tom drum) | `sound_02` | 5 | low | noise | +| #$03 | FOOT | player landing on ground or water | `sound_03` | 4 | low | pulse 1 | +| | | | `sound_04` | 5 | low | noise | +| #$05 | ROCK | waterfall rock landing on ground | `sound_05` | 4 | low | pulse 1 | +| #$06 | TYPE 1 | unused, keyboard typing in Japanese version of game | `sound_06` | 4 | low | pulse 1 | +| | | | `sound_07` | 5 | low | noise | +| #$08 | | unused, rumbling | `sound_08` | 5 | low | noise | +| #$09 | FIRE | energy zone fire beam | `sound_09` | 5 | low | noise | +| #$0a | SHOTGUN1 | default weapon | `sound_0a` | 4 | low | pulse 1 | +| | | | `sound_0b` | 5 | low | noise | +| #$0c | SHOTGUN2 | M weapon, turret man | `sound_0c` | 4 | low | pulse 1 | +| | | | `sound_0d` | 5 | low | noise | +| #$0e | LASER | L weapon | `sound_0e` | 4 | low | pulse 1 | +| | | | `sound_0f` | 5 | low | noise | +| #$10 | PL FIRE | F weapon | `sound_10` | 4 | low | pulse 1 | +| | | | `sound_11` | 5 | low | noise | +| #$12 | SPREAD | S weapon | `sound_12` | 4 | low | pulse 1 | +| | | | `sound_13` | 5 | low | noise | +| #$14 | HIBIWARE | bullet shielded wall plating ting | `sound_14` | 4 | low | pulse 1 | +| #$15 | CHAKUCHI | energy zone boss landing | `sound_15` | 4 | low | pulse 1 | +| #$16 | DAMEGE 1 | bullet to metal collision ting | `sound_16` | 4 | low | pulse 1 | +| | | | `sound_17` | 5 | low | noise | +| #$18 | DAMEGE 2 | alien heart boss hit | `sound_18` | 4 | low | pulse 1 | +| #$19 | TEKI OUT | enemy destroyed | `sound_19` | 4 | low | pulse 1 | +| #$1a | HIRAI 1 | ice grenade whistling noise | `sound_1a` | 4 | low | pulse 1 | +| #$1b | SENSOR | level 1 jungle boss siren | `sound_1b` | 4 | low | pulse 1 | +| #$1c | KANDEN | electrocution sound | `sound_1c` | 4 | low | pulse 1 | +| | | | `sound_1d` | 5 | low | noise | +| #$1e | CAR | tank advancing | `sound_1e` | 4 | low | noise | +| #$1f | POWER UP | pick up weapon item | `sound_1f` | 4 | low | pulse 1 | +| #$20 | 1UP | extra life | `sound_20` | 4 | low | pulse 1 | +| #$21 | HERI | helicopter rotors | `sound_21` | 4 | low | pulse 1 | +| | | | `sound_21` | 1 | low | pulse 2 | +| | | | `sound_23` | 5 | low | noise | +| #$24 | BAKUHA 1 | explosion | `sound_24` | 5 | low | noise | +| #$25 | BAKUHA 2 | game intro, indoor wall, and island explosion | `sound_25` | 5 | low | noise | +| #$26 | TITLE | game intro tune | `sound_26` | 0 | high | pulse 1 | +| | | | `sound_27` | 1 | high | pulse 2 | +| | | | `sound_28` | 2 | high | triangle | +| | | | `sound_29` | 3 | high | noise | +| #$2a | BGM 1 | level 1 jungle and level 7 hangar music | `sound_2a` | 0 | high | pulse 1 | +| | | | `sound_2b` | 1 | high | pulse 2 | +| | | | `sound_2c` | 2 | high | triangle | +| | | | `sound_2d` | 3 | high | noise | +| #$2e | BGM 2 | level 3 waterfall music | `sound_2e` | 0 | high | pulse 1 | +| | | | `sound_2f` | 1 | high | pulse 2 | +| | | | `sound_30` | 2 | high | triangle | +| | | | `sound_31` | 3 | high | noise | +| #$32 | BGM 3 | level 5 snow field music | `sound_32` | 0 | high | pulse 1 | +| | | | `sound_33` | 1 | high | pulse 2 | +| | | | `sound_34` | 2 | high | triangle | +| | | | `sound_35` | 3 | high | noise | +| #$36 | BGM 4 | level 6 energy zone | `sound_36` | 0 | high | pulse 1 | +| | | | `sound_37` | 1 | high | pulse 2 | +| | | | `sound_38` | 2 | high | triangle | +| | | | `sound_39` | 3 | high | noise | +| #$3a | BGM 5 | level 8 alien's lair music | `sound_3a` | 0 | high | pulse 1 | +| | | | `sound_3b` | 1 | high | pulse 2 | +| | | | `sound_3c` | 2 | high | triangle | +| | | | `sound_3d` | 3 | high | noise | +| #$3e | 3D BGM | indoor/base level music | `sound_3e` | 0 | high | pulse 1 | +| | | | `sound_3f` | 1 | high | pulse 2 | +| | | | `sound_40` | 2 | high | triangle | +| | | | `sound_41` | 3 | high | noise | +| #$42 | BOSS | indoor/base boss screen music | `sound_42` | 0 | high | pulse 1 | +| | | | `sound_43` | 1 | high | pulse 2 | +| | | | `sound_44` | 2 | high | triangle | +| | | | `sound_45` | 3 | high | noise | +| #$46 | PCLR | end of level tune | `sound_46` | 0 | high | pulse 1 | +| | | | `sound_47` | 1 | high | pulse 2 | +| | | | `sound_48` | 2 | high | triangle | +| | | | `sound_49` | 3 | high | noise | +| #$4a | ENDING | end credits | `sound_4a` | 0 | high | pulse 1 | +| | | | `sound_4b` | 1 | high | pulse 2 | +| | | | `sound_4c` | 2 | high | triangle | +| | | | `sound_4d` | 3 | high | noise | +| #$4e | OVER | game over/after end credits, presented by Konami | `sound_4e` | 0 | high | pulse 1 | +| | | | `sound_4f` | 1 | high | pulse 2 | +| | | | `sound_50` | 2 | high | triangle | +| | | | `sound_51` | 3 | high | noise | +| #$52 | PL OUT | player death | `sound_52` | 4 | low | pulse 1 | +| | | | `sound_53` | 5 | low | noise | +| #$54 | | game pausing | `sound_54` | 4 | low | pulse 1 | +| #$55 | BOSS BK | tank, boss ufo, boss giant, alien guardian destroyed | `sound_55` | 4 | low | pulse 1 | +| | | | `sound_56` | 5 | low | noise | +| #$57 | BOSS OUT | boss destroyed | `sound_57` | 4 | low | pulse 1 | +| | | | `sound_58` | 1 | low | pulse 2 | +| | | | `sound_59` | 5 | low | noise | +| #$5a | n/a | high hat | n/a | n/a | low | delta modulation | +| #$5b | n/a | snare | n/a | n/a | low | delta modulation | +| #$5c | n/a | high hat | n/a | n/a | low | delta modulation | +| #$ff | n/a | snowfield boss defeated door open (bug) | n/a | n/a | low | delta modulation | + + +The sound for pausing the game is not in the sound mode menu, presumably to not +confuse players into thinking the game is paused. As for names, I can guess at +some of the abbreviations and name meanings. + + * BAKUHA - ばくはつ (爆発) - Japanese for explosion + * BGM - background music + * BK - BAKUHA, i.e. explosion + * CHAKUCHI - ちゃくち (着地) - Japanese for landing/touching the ground + * HIBIWARE - ひびわれ (罅割れ, ひび割れ) - Japanese for crack; crevice; fissure + * PCLR - player clear, or pattern clear + * PL - player + * TYPE - keyboard typing + +# sound_code Parsing + +Every video frame, the game loops through each sound slot to see if a sound is +currently playing, see `@sound_slot_loop`. If a sound slot is populated, i.e. +a sound is playing, then `handle_sound_code` will be called on that slot. +`handle_sound_code` will first check if the game is paused, as the music and +sound effects are paused when the game is paused. If the game isn't paused, the +`handle_sound_code` will decrement the current sound slot's sound length +(`SOUND_CMD_LENGTH`) and if the sound is finished, move to read the next command +(`read_sound_command_00`). If the sound isn't finished, then +`@pulse_vol_and_vibrato` is called to possibly adjust the volume and frequency +of the current playing sound. Note frequency adjustments, i.e. vibrato isn't +used by _Contra_. + +A `sound_code` is composed of 1 or more sound commands. Each sound command will +configure variables or set APU registers. Not every sound command will make a +sound. Some just configure variables for the subsequent sound code, for example +setting `SOUND_LENGTH_MULTIPLIER` for use by a subsequent sound command. The +sound commands are parsed according to the following logic. + +The first byte of the entire sound code determines how the rest of the commands +for the sound code will be parsed. When the byte is less than #$30, the the +sound code is considered a 'low sound' code. Otherwise, the code will be parsed +as a 'high sound' code. + +## sound_code Sharing + +All sound command types can reference addresses to other sound commands. When a +sound command moves to another command, once that command is finished executing, +then the sound read pointer goes back to the next bytes in the original command. + + * #$fd - move to child sound command for playing shared sound data across + different `sound_xx` commands, or shared parts within the same sound code. + Move to execute sound command at address specified by next 2 bytes. + * #$fe - repeat the next sound command at address `a` `n` times, where `a` is + the next byte and `n` is the 2nd and 3rd byte. + * #$ff - finished reading sound command, exit to previous command if child + sound command, otherwise, finished entire sound code + +## Low Sound Command + +In _Contra_, low sound commands are used by sound slots #$01 (pulse 1), #$04 +(pulse 2) and #$05 (noise). Low sound commands are used for sound effects. +The method in code for parsing low sound commands is `read_low_sound_cmd`. In +general, low sound commands set the length, decrescendo start, pitch, and duty. + +The first byte of the sound command dictates how the subsequent bytes are +interpreted. The command is read recursively until the note period is set, then +the parsing exits. + + * **Case 1** - #$2x - sets the number of video frames to wait before reading + the next sound command (`SOUND_LENGTH_MULTIPLIER`) as well as the high + nibble of the APU configuration register for the sound channel + (`SOUND_CFG_HIGH`). + * when low nibble is not #$f, then `SOUND_LENGTH_MULTIPLIER` is set to low + nibble. + * when low nibble is #$f, then the next full byte is used to set + `SOUND_LENGTH_MULTIPLIER` + * the following byte is then used to set high nibble of the APU + configuration register for the sound channel (`SOUND_CFG_HIGH`) + * **Case 2** - #$10 - enable/disable sweep and set volume decrescendo. What + is set is based on the next byte. Additionally, if the sound slot is #$04, + then the pulse 1 channel `PULSE_VOL_DURATION` will also be set to the sweep + value. + * non-zero - sweep will be enabled and set to the value of the byte. + * #$00 - if the byte after #$10 is #$00, then sweep will be disabled by + setting the APU register $4001 to #$7f. + be set to #$7f. + * **Case 3** - #$1x - slightly flatten the note that will be played by less + than 1Hz by setting bit 4 of `SOUND_FLAGS`. The low nibble is not used. + This case is not used in _Contra_. However, notes are flattened in another + flow, see `@flip_flatten_note_adv`. + * **Case 4** - #$xx - if not #2x, or #$1x, then byte high nibble is used as + the low nibble (volume) for APU channel config. The high nibble and low + nibble from memory are merged (`SOUND_CFG_HIGH` and `SOUND_CFG_LOW` + respectively), unless volume is constant, then only the high nibble is used + when setting the sound channel configuration ($4000). Also, the sound length + (`SOUND_CMD_LENGTH`), value is set based on `SOUND_LENGTH_MULTIPLIER`. + Finally, the note frequency/counter/pitch is set based off the next two + bytes and then the parsing is complete. + +After case 4 is parsed, the read low sound command method exits. Then the game +logic will continue. The next frame, a standard `@sound_slot_loop` will pick up +the sound slot is populated and play possibly modify the sound's volume and +frequency for vibrato (not used in _Contra_). Every video frame, the game logic +repeats this until the sound is completed, the game logic will then continue +by reading the next byte of the next low sound code. This entire process +repeats until a #$ff is read. + +## High Sound Command + +In _Contra_, high sound commands are used by sound slots #$00 (pulse 1), #$01 +(pulse 2), #$02 (triangle) and #$03 (noise & dmc channel). High sound commands +are used for the 'music' of the game: the level background music, the intro +tune, end credits song, and after credits/game over music. The method in code +for parsing low sound commands is `read_high_sound_cmd`. + +The first byte of the sound command dictates how the subsequent bytes are +interpreted. + + * **Case 1** - if the sound slot is #$03 (noise and dmc channel), then + `parse_percussion_cmd` is called to handle the percussion. See notes below + in section titled `Percussion Command`. + * **Case 2** - if byte 0 high nibble is less than #$c, then `simple_sound_cmd` + is used. This plays a single note with a specified length change from + previous note with a volume envelope specified by `lvl_config_pulse`. + * **Case 3** - if byte 0 high nibble is greater than or equal to #$0c, then + `@regular_sound_cmd` is used. `@regular_sound_cmd` looks at bits 4 and 5 to + know which entry in `sound_cmd_ptr_tbl` to use to handle the sound command. + For details of what each method does, see section `sound_cmd_routine_xx`. + +### sound_cmd_routine_xx + * `sound_cmd_routine_00` - sets sound channel config to mute, marks channel + as muted by setting bit 6 of `SOUND_FLAGS`. + * `sound_cmd_routine_01` - sets sound length multiplier + (`SOUND_LENGTH_MULTIPLIER`) to low nibble. Sets in memory low nibble of + sound channel. Can initialize channel by calling + `exe_channel_init_ptr_tbl_routine`. Otherwise, recursively calls back to + `read_high_sound_cmd` to handle next byte. + * `sound_cmd_routine_02` + * If the low nibble is less than #$5, then sets note adjustment flag + (`SOUND_PERIOD_ROTATE`) and recursively calls back to `read_high_sound_cmd`. + * If the low nibble is #$8, set bit 4 of `SOUND_FLAGS` to mark note as + slightly flattened from original value. + * If the low nibble is #$b, set vibrato variables and recursively call + `read_high_sound_cmd`. + * If the low nibble is #$c, set the pitch based on next sound byte, which + (when doubled) is an offset into `note_period_tbl`. + * If the low nibble isn't any known value, just ignore it and recursively + call `read_high_sound_cmd`. + * `sound_cmd_routine_03` - this function handles the end of a sound command + and determines where to go next based on the byte value. See section + above titled `sound_code Sharing`. + +### Percussion Command + +Percussion commands are recursively read until Case 1 or Case 3 is reached. + + * **Case 1** - The byte's high nibble is #$f. This is an end of sound command, + the sound command will either end, repeat, or move back to parent sound + command. See the section titled `sound_code Sharing`. + * **Case 2** - The byte's high nibble is #$d. The low nibble is used to set + the sound length multiplier (`SOUND_LENGTH_MULTIPLIER`). + * **Case 3** - The byte high nibble isn't #$f, nor #$d. In this case, call + `calc_cmd_len_play_percussion` to determine sound command length + (`SOUND_CMD_LENGTH`) based on the low nibble and the value determined from + Case 2. Then call `play_percussive_sound`. This method will use the high + nibble (shifted into low nibble) of the byte value to get offset into + `percussion_tbl`, which specifies which DMC sound sample code to play. This + value is passed to `play_sound` to play the sound code. Then, if the value + was greater than or equal to #$3, `sound_02` is also played with other sound + code. The offsets are defined and which sound(s) is/are played are below. + Note that in offset 5, `sound_02` is not played because there is a check in + `load_sound_code_entry` and `sound_25` is already playing in slot #$05. + * 0 - `sound_02` + * 1 - `sound_5a` + * 2 - `sound_5b` + * 3 - `sound_5a` and `sound_02` + * 4 - `sound_5b` and `sound_02` + * 5 - `sound_25` + * 6 - `sound_5c` and `sound_02` + * 7 - `sound_5d` and `sound_02` \ No newline at end of file diff --git a/docs/attachments/pw_0.png b/docs/attachments/pw_0.png new file mode 100644 index 0000000..90ae43a Binary files /dev/null and b/docs/attachments/pw_0.png differ diff --git a/docs/attachments/pw_1.png b/docs/attachments/pw_1.png new file mode 100644 index 0000000..8d919d8 Binary files /dev/null and b/docs/attachments/pw_1.png differ diff --git a/docs/attachments/pw_2.png b/docs/attachments/pw_2.png new file mode 100644 index 0000000..32f5e3b Binary files /dev/null and b/docs/attachments/pw_2.png differ diff --git a/docs/attachments/pw_3.png b/docs/attachments/pw_3.png new file mode 100644 index 0000000..b070bda Binary files /dev/null and b/docs/attachments/pw_3.png differ diff --git a/docs/attachments/sound_ff.mp3 b/docs/attachments/sound_ff.mp3 new file mode 100644 index 0000000..2fbc969 Binary files /dev/null and b/docs/attachments/sound_ff.mp3 differ diff --git a/docs/diagrams/game_flow.mmd b/docs/diagrams/game_flow.mmd new file mode 100644 index 0000000..19e34e8 --- /dev/null +++ b/docs/diagrams/game_flow.mmd @@ -0,0 +1,15 @@ +%% This is a mermaid file (https://github.com/mermaid-js/mermaid) +%% This shows the very high level of the game engine + +graph TD + nmi_start --> handle_sound_slots + handle_sound_slots --> load_controller_state + load_controller_state --> exe_game_routine + exe_game_routine --> game_routine_01 --> draw_sprites + exe_game_routine --> game_routine_02 --> draw_sprites + exe_game_routine --> game_routine_03 --> draw_sprites + exe_game_routine --> game_routine_04 --> draw_sprites + exe_game_routine --> game_routine_05 + game_routine_05 --> run_level_routine --> draw_sprites + exe_game_routine --> game_routine_06 --> draw_sprites + draw_sprites --> remove_registers_from_stack_and_rti \ No newline at end of file diff --git a/docs/diagrams/graphics_flow.mmd b/docs/diagrams/graphics_flow.mmd new file mode 100644 index 0000000..20f050f --- /dev/null +++ b/docs/diagrams/graphics_flow.mmd @@ -0,0 +1,42 @@ +%% This is a mermaid file (https://github.com/mermaid-js/mermaid) +%% This shows the paths from which the graphics are updated +flowchart TD + level_routine_01:::entry + level_routine_01 --> draw_stage_and_level_name + draw_stage_and_level_name --> load_bank_6_write_text_palette_to_mem + level_routine_01 --> draw_player_num_lives + draw_player_num_lives --> load_bank_6_write_text_palette_to_mem + + level_routine_00:::entry + level_routine_00 --> |load transition_screen_palettes|load_bank_6_write_text_palette_to_mem + load_bank_6_write_text_palette_to_mem --> write_text_palette_to_mem + level_routine_00 --> zero_out_nametables + zero_out_nametables --> |graphic_data_0|write_graphic_data_to_ppu + + level_routine_04:::entry + level_routine_04 --> handle_scroll + handle_scroll --> load_column_of_tiles_to_cpu_buffer + load_column_of_tiles_to_cpu_buffer --> load_level_supertile_data + + level_routine_05:::entry + level_routine_05 --> load_level_intro + load_level_intro --> load_A_offset_graphic_data + load_A_offset_graphic_data --> load_level_graphic_data + load_level_graphic_data --> write_graphic_data_to_ppu + + level_routine_03:::entry + level_routine_03 --> init_lvl_nametable_animation + init_lvl_nametable_animation --> load_column_of_tiles_to_cpu_buffer + init_lvl_nametable_animation --> write_col_attribute_to_cpu_memory + + level_routine_02:::entry + level_routine_02 --> zero_out_nametables + level_routine_02 --> load_level_graphics + load_level_graphics --> load_current_level_graphic_data + load_current_level_graphic_data --> write_graphic_data_to_ppu + level_routine_02 --> load_palettes_color_to_cpu + + nmi_start:::entry + nmi_start --> write_cpu_graphics_buffer_to_ppu + +classDef entry fill:#f96 \ No newline at end of file diff --git a/docs/diagrams/level_routine_flow.mmd b/docs/diagrams/level_routine_flow.mmd new file mode 100644 index 0000000..9080ba1 --- /dev/null +++ b/docs/diagrams/level_routine_flow.mmd @@ -0,0 +1,18 @@ +%% This is a mermaid file (https://github.com/mermaid-js/mermaid) +%% This shows the ordering of the states among the level routines + +graph TD + level_routine_00 --> level_routine_01 + level_routine_01 --> level_routine_02 + level_routine_02 --> level_routine_03 + level_routine_03 --> level_routine_04 + level_routine_04 --> |Level Complete|level_routine_08 + level_routine_04 --> |Game Over|level_routine_0a + level_routine_05 --> |Level Complete|level_routine_00 + level_routine_05 --> |Complete Last Level/Game Over|level_routine_06 + level_routine_05 --> |Game Over No Continues|level_routine_07 + level_routine_06 --> |Continue/End Selected|level_routine_00 + level_routine_08 --> |Game Over|level_routine_0a + level_routine_08 --> |Level Complete|level_routine_09 + level_routine_0a --> |Game Over Timer Expired|level_routine_05 + level_routine_09 --> |Level Complete|level_routine_05 \ No newline at end of file diff --git a/docs/diagrams/read_sound_command_00 simplified.mmd b/docs/diagrams/read_sound_command_00 simplified.mmd new file mode 100644 index 0000000..ee779c3 --- /dev/null +++ b/docs/diagrams/read_sound_command_00 simplified.mmd @@ -0,0 +1,25 @@ +%% This is a mermaid file (https://github.com/mermaid-js/mermaid) +%% This shows the simplified overview of the audio engine +flowchart TD + + 6(read_sound_command_00) -->|< #$30|8(read_low_sound_cmd) + 6 --> |>= #$30|9(read_high_sound_cmd) + 9 --> |sound slot 3|11(parse_percussion_cmd) + 11 --> 16 + 9 --> 13("@regular_sound_cmd") + 9 --> 14(simple_sound_cmd) + 14 --> |set note|42[rts] + 8 --> 16(sound_cmd_routine_03) + 16 --> 43[rts] + 16 -.-> |next sound command|6 + 8 --> 17(interpret_sound_byte) + 17 --> |set note|46[rts] + 17 -.-> 8 + 13 --> 39(sound_cmd_routine_00) + 39 --> 44[rts] + 13 --> 40(sound_cmd_routine_01) + 13 --> 41(sound_cmd_routine_02) + 13 --> 16 + 41 -.-> 9 + 40 --> 45[rts] + 40 -.-> 9 \ No newline at end of file diff --git a/docs/diagrams/read_sound_command_00.mmd b/docs/diagrams/read_sound_command_00.mmd new file mode 100644 index 0000000..668ea36 --- /dev/null +++ b/docs/diagrams/read_sound_command_00.mmd @@ -0,0 +1,49 @@ +%% This is a mermaid file (https://github.com/mermaid-js/mermaid) +%% This shows in detail the logic flow for the audio engine + +flowchart TD + + 5["@check_sound_command"] --> |read next command|6(read_sound_command_00) + 6 -->|command byte < #$30|8(read_low_sound_cmd) + 6 -->|command byte >= #$30|9(read_high_sound_cmd) + 9 --> |noise channel|11(parse_percussion_cmd) + 9 --> |"not noise regular command (>=#$c0)"|13("@regular_sound_cmd") + 9 --> |"not noise simple command (< #$c0)"|14(simple_sound_cmd) + 8 --> |not control byte|17(interpret_sound_byte) + 8 --> |control byte\n#$fd, #$fe, #$ff|16(sound_cmd_routine_03) + 11 --> |high nibble #$f|16 + 11 --> |high nibble #$d|20(control_nibble_d) + 11 --> |high nibble not #$d not #$f|21(sound_byte_calc_cmd_delay) + 17 --> |high nibble not #$2|24("@high_nibble_not_2") + 17 --> |high nibble #$2|52[set length multiplier and config] + 52 -.-> 8 + 24 --> |high nibble #$1 low nibble not #$0|54[flatten note] + 54 -.-> 8 + 24 --> |high nibble #$1 low nibble #$0|26[set sweep if specified] + 24 --> |high nibble not #$1|27("@high_nibble_not_1") + 26 -.-> 8 + 27 --> 55[set sound multiplier] + 55 --> 56(set_note) + 56 -.-> 8 + 20 --> 28["SOUND_LENGTH_MULTIPLIER = low nibble"] + 28 -.-> |read next byte|11 + 16 --> |low nibble < #$0e|33[set NEW_SOUND_CODE_LOW_ADDR, and sound flag bit 3] + 16 --> |low nibble #$f|32(low_nibble_f) + 16 --> |low nibble #$e|38(read_sound_command_01) + 33 --> 34(load_sound_code_addr) + 32 --> |SOUND_FLAGS bit 3 set|36(move_new_sound_code) + 32 --> |SOUND_FLAGS bit 3 clear|37(exe_channel_init_ptr_tbl_routine) + 38 -.-> 6 + 13 --> 16 + 13 --> 39(sound_cmd_routine_00) + 13 --> 40(sound_cmd_routine_01) + 13 --> 41(sound_cmd_routine_02) + 41 --> |low nibble >= #$05|43("@high_val") + 41 --> |low nibble < #$05|44(set SOUND_PERIOD_ROTATE) + 44 -.-> |advance sound byte|9 + 43 --> |low nibble #$08|46("@flip_flatten_note_adv") + 43 --> |low nibble #$0b|48("@set_vibrato_vars_adv") + 43 --> |low nibble #$0c|50("@set_pitch_adj_adv") + 43 -.-> |unsupported value, advance sound byte|9 + 50 --> |advance sound byte|51(set SOUND_PITCH_ADJ) + 51 -.-> |advance sound byte|9 \ No newline at end of file diff --git a/docs/lua_scripts/fceux/Always Invincible.lua b/docs/lua_scripts/fceux/Always Invincible.lua new file mode 100644 index 0000000..04bba9f --- /dev/null +++ b/docs/lua_scripts/fceux/Always Invincible.lua @@ -0,0 +1,9 @@ +while (true) do + memory.writebyte(0x00b0, 0xff) -- player 1 + memory.writebyte(0x00b1, 0xff) -- player 2 + + -- 0xb0/b1 in memory store the amount of time the B weapon (barrier) lasts. + -- By continually overwriting the timer to the max value (FF), the players + -- will always be invincible + emu.frameadvance(); +end; \ No newline at end of file diff --git a/docs/lua_scripts/fceux/Show HUD Info.lua b/docs/lua_scripts/fceux/Show HUD Info.lua new file mode 100644 index 0000000..663d1d6 --- /dev/null +++ b/docs/lua_scripts/fceux/Show HUD Info.lua @@ -0,0 +1,24 @@ +while (true) do + local p1score1 = memory.readbyte(0x07e2) + local p1score2 = memory.readbyte(0x07e3) + p1score = (bit.lshift(p1score2, 8) + p1score1) * 100 + + local weapon_hex = memory.readbyte(0x00aa) + local rapid_fire = "" + if bit.band(weapon_hex, 0x10) == 0x10 then + rapid_fire = " Rapid " + else + rapid_fire = " " + end + + weapon_hex = bit.band(weapon_hex, 0x0f) + local weapon_name = "" + + if weapon_hex == 0 then weapon_name = "Default Gun" + elseif weapon_hex == 1 then weapon_name = "Machine Gun" + elseif weapon_hex == 2 then weapon_name = "Flame Thrower" + elseif weapon_hex == 3 then weapon_name = "Spray" + elseif weapon_hex == 4 then weapon_name = "Laser" end + gui.text(5, 40, "P1: " .. p1score .. rapid_fire .. weapon_name); + emu.frameadvance(); +end; \ No newline at end of file diff --git a/docs/lua_scripts/mesen/Always Invincible.lua b/docs/lua_scripts/mesen/Always Invincible.lua new file mode 100644 index 0000000..04bba9f --- /dev/null +++ b/docs/lua_scripts/mesen/Always Invincible.lua @@ -0,0 +1,9 @@ +while (true) do + memory.writebyte(0x00b0, 0xff) -- player 1 + memory.writebyte(0x00b1, 0xff) -- player 2 + + -- 0xb0/b1 in memory store the amount of time the B weapon (barrier) lasts. + -- By continually overwriting the timer to the max value (FF), the players + -- will always be invincible + emu.frameadvance(); +end; \ No newline at end of file diff --git a/docs/lua_scripts/mesen/Enemy Debug.lua b/docs/lua_scripts/mesen/Enemy Debug.lua new file mode 100644 index 0000000..327d2b4 --- /dev/null +++ b/docs/lua_scripts/mesen/Enemy Debug.lua @@ -0,0 +1,37 @@ +-- used for testing to show enemy data + +function display_enemy_data() + for i = 0,0xf do + local ENEMY_X_POS = emu.read(0x033e + i, emu.memType.cpu) + local ENEMY_Y_POS = emu.read(0x0324 + i, emu.memType.cpu) + local ENEMY_ANIMATION_DELAY = emu.read(0x0538 + i, emu.memType.cpu) + local ENEMY_ATTRIBUTES = emu.read(0x05a8 + i, emu.memType.cpu) + local ENEMY_FRAME = emu.read(0x0568 + i, emu.memType.cpu) + local ENEMY_HP = emu.read(0x0578 + i, emu.memType.cpu) + local ENEMY_STATE_WIDTH = emu.read(0x0598 + i, emu.memType.cpu) + local ENEMY_VAR_A = emu.read(0x0548 + i, emu.memType.cpu) + local ENEMY_VAR_B = emu.read(0x0558 + i, emu.memType.cpu) + local ENEMY_VAR_1 = emu.read(0x05b8 + i, emu.memType.cpu) + local ENEMY_VAR_2 = emu.read(0x05c8 + i, emu.memType.cpu) + local ENEMY_VAR_3 = emu.read(0x05d8 + i, emu.memType.cpu) + local ENEMY_VAR_4 = emu.read(0x05e8 + i, emu.memType.cpu) + local ENEMY_SPRITES = emu.read(0x030a + i, emu.memType.cpu) + local ENEMY_ROUTINE = emu.read(0x04b8 + i, emu.memType.cpu) + local ENEMY_ATTACK_DELAY = ENEMY_VAR_B + local ENEMY_X_VELOCITY_FAST = emu.read(0x0508 + i, emu.memType.cpu) + local ENEMY_X_VELOCITY_FRACT = emu.read(0x0518 + i, emu.memType.cpu) + local ENEMY_X_VEL_ACCUM = emu.read(0x04d8 + i, emu.memType.cpu) + local ENEMY_Y_VELOCITY_FAST = emu.read(0x04e8 + i, emu.memType.cpu) + local ENEMY_Y_VELOCITY_FRACT = emu.read(0x04f8 + i, emu.memType.cpu) + local ENEMY_Y_VEL_ACCUM = emu.read(0x04c8 + i, emu.memType.cpu) + + -- change variable to interested variable for studying + emu.drawString(ENEMY_X_POS, ENEMY_Y_POS, string.format("%x", ENEMY_HP)) + end +end + +function Main() + display_enemy_data() +end + +emu.addEventCallback(Main, emu.eventType.endFrame) \ No newline at end of file diff --git a/docs/lua_scripts/mesen/Show Background Collisions.lua b/docs/lua_scripts/mesen/Show Background Collisions.lua new file mode 100644 index 0000000..5635e23 --- /dev/null +++ b/docs/lua_scripts/mesen/Show Background Collisions.lua @@ -0,0 +1,72 @@ +-- shows the background collision codes +-- floor, water, solid, or empty + +function get_bg_collision(x, y) + local VERTICAL_SCROLL = emu.read(0xfc, emu.memType.cpu) + local HORIZONTAL_SCROLL = emu.read(0xfd, emu.memType.cpu) + local PPUCTRL_SETTINGS = emu.read(0xff, emu.memType.cpu) + local adjusted_y = y + VERTICAL_SCROLL + local adjusted_x = x + HORIZONTAL_SCROLL + + if adjusted_y >= 0xf0 then + adjusted_y = adjusted_y + 0x0f + adjusted_y = adjusted_y - 255 + end + + -- $10 is always #$00, except when moving cart is calling get_bg_collision + local nametable_number = (PPUCTRL_SETTINGS ~ 0x00) & 0x01 + if adjusted_x > 255 then + nametable_number = nametable_number ~ 1 + adjusted_x = adjusted_x - 255 + end + + adjusted_y = (adjusted_y >> 2) & 0x3c + adjusted_x = adjusted_x >> 4 + local bg_collision_offset = (adjusted_x >> 2) | adjusted_y + if nametable_number == 1 then + bg_collision_offset = bg_collision_offset | 0x40; + end + + local collisionCodeByte = emu.read(0x680 + bg_collision_offset, emu.memType.cpu) + adjusted_x = adjusted_x & 0x03; + local collisionCode = 0 + if adjusted_x == 0 then + collisionCode = collisionCodeByte >> 6 + elseif adjusted_x == 1 then + collisionCode = collisionCodeByte >> 4 + elseif adjusted_x == 2 then + collisionCode = collisionCodeByte >> 2 + else + collisionCode = collisionCodeByte + end + + collisionCode = collisionCode & 0x03; + + local floorColor = 0x508fbc8f + local waterColor = 0x500096FF + local solidColor = 0x50A9A9A9 + local tileColor = 0x0 + if collisionCode == 0x01 then + tileColor = floorColor + elseif collisionCode == 0x02 then + tileColor = waterColor + elseif collisionCode == 0x03 then + tileColor = solidColor + end + + if collisionCode ~= 0 then + emu.drawRectangle(x, y, 16, 16, tileColor, true) + end +end + +function Main() + local VERTICAL_SCROLL = emu.read(0xfc, emu.memType.cpu) + local HORIZONTAL_SCROLL = emu.read(0xfd, emu.memType.cpu) + for i = 0,300,16 do + for j = 0,300,16 do + get_bg_collision(i - math.fmod(HORIZONTAL_SCROLL, 16), j - math.fmod(VERTICAL_SCROLL, 16)) + end + end +end + +emu.addEventCallback(Main, emu.eventType.endFrame) \ No newline at end of file diff --git a/docs/lua_scripts/mesen/Show Enemy Positions.lua b/docs/lua_scripts/mesen/Show Enemy Positions.lua new file mode 100644 index 0000000..0ceb039 --- /dev/null +++ b/docs/lua_scripts/mesen/Show Enemy Positions.lua @@ -0,0 +1,106 @@ +-- shows the player - enemy collision boxes +-- does not show the player bullet - enemy collision boxes +-- doesn't currently handle collision code f (rising spiked walls and fire beams) + +function check_player_x_collision(player_index) + local PLAYER_STATE = emu.read(0x90 + player_index, emu.memType.cpu) + if PLAYER_STATE ~= 0x01 then + -- exit if player not in normal state + return nil + end + + -- @check_in_water_crouched + local collision_box = 0x00 + local LEVEL_LOCATION_TYPE = emu.read(0x40 + player_index, emu.memType.cpu) + local PLAYER_WATER_STATE = emu.read(0xb2 + player_index, emu.memType.cpu) + if LEVEL_LOCATION_TYPE & 0xfe == 0x00 then + -- outdoor + local PLAYER_SPRITE_SEQUENCE = emu.read(0xbc + player_index, emu.memType.cpu) + if PLAYER_WATER_STATE ~= 0x00 and PLAYER_SPRITE_SEQUENCE == 0x02 then + -- exit if player crouched in water, no collision can happen with player + return nil + end + end + + if PLAYER_WATER_STATE == 0x00 then + collision_box = collision_box + 1 + end + + -- not checking for crouched while on indoor level, because non-bullets can + -- collide when crouching player on indoor levels + + local PLAYER_JUMP_STATUS = emu.read(0xa0 + player_index, emu.memType.cpu) + local PLAYER_SPRITE_CODE = emu.read(0xd6 + player_index, emu.memType.cpu) + if PLAYER_JUMP_STATUS == 0x00 then + -- player not jumping + collision_box = collision_box + 1 + if PLAYER_SPRITE_CODE ~= 0x17 then + collision_box = collision_box + 1 + end + end + + -- draw player collision point + local SPRITE_Y_POS = emu.read(0x031a + player_index, emu.memType.cpu) + local SPRITE_X_POS = emu.read(0x0334 + player_index, emu.memType.cpu) + emu.drawRectangle(SPRITE_X_POS, SPRITE_Y_POS, 1, 1, 0x0000ff, false) + + for i = 0,0xf do + local ENEMY_ROUTINE = emu.read(0x04b8 + i, emu.memType.cpu) + local ENEMY_ROUTINE = emu.read(0x04b8 + i, emu.memType.cpu) + local ENEMY_STATE_WIDTH = emu.read(0x0598 + i, emu.memType.cpu) + local should_test_collision = ENEMY_STATE_WIDTH & 0x01 == 0x00 + if ENEMY_ROUTINE ~= 0x00 and should_test_collision then + set_enemy_collision_box(player_index, i, collision_box) + end + end +end + +function set_enemy_collision_box(player_index, i, collision_box) + local ENEMY_SCORE_COLLISION = emu.read(0x0588 + i, emu.memType.cpu) & 0x0f + + if ENEMY_SCORE_COLLISION == 0x0f then + -- todo handle (fire beam and rising spiked wall) + return + end + + local collision_box_addr = emu.readWord(0xe4e8 + (collision_box * 2), emu.memType.cpu) -- collision_box_codes_tbl + local offset = ENEMY_SCORE_COLLISION * 4 + local y0 = emu.read(collision_box_addr + offset, emu.memType.cpu) + local x0 = emu.read(collision_box_addr + offset + 1, emu.memType.cpu) + local height = emu.read(collision_box_addr + offset + 2, emu.memType.cpu) + local width = emu.read(collision_box_addr + offset + 3, emu.memType.cpu) + local ENEMY_Y_POS = emu.read(0x0324 + i, emu.memType.cpu) + local ENEMY_X_POS = emu.read(0x033e + i, emu.memType.cpu) + + local topY = ENEMY_Y_POS + if y0 > 0x80 then + y0 = negateInt(y0) + topY = ENEMY_Y_POS - y0 + else + topY = ENEMY_Y_POS + y0 + end + + local topX = ENEMY_X_POS + if x0 > 0x80 then + x0 = negateInt(x0) + topX = ENEMY_X_POS - x0 + else + topX = ENEMY_X_POS + x0 + end + + emu.drawRectangle(topX, topY, width, height, 0x0000ff, false) +end + +function negateInt(num) + if(num > 0x80) then + num = (~num + 1) & 0xff + end + + return num +end + +function Main() + check_player_x_collision(0) +end + +emu.addEventCallback(Main, emu.eventType.endFrame) \ No newline at end of file diff --git a/docs/lua_scripts/mesen/Show HUD Info.lua b/docs/lua_scripts/mesen/Show HUD Info.lua new file mode 100644 index 0000000..be1a107 --- /dev/null +++ b/docs/lua_scripts/mesen/Show HUD Info.lua @@ -0,0 +1,27 @@ +-- shows information about weapon on head-up-display area + +function Main() + local p1score1 = emu.read(0x07e2, emu.memType.cpu) + local p1score2 = emu.read(0x07e3, emu.memType.cpu) + p1score = ((p1score2 << 8) + p1score1) * 100 + + local weapon_hex = emu.read(0x00aa, emu.memType.cpu) + local rapid_fire = "" + if weapon_hex & 0x10 == 0x10 then + rapid_fire = " Rapid " + else + rapid_fire = " " + end + + weapon_hex = weapon_hex & 0x0f + local weapon_name = "" + + if weapon_hex == 0 then weapon_name = "Default Gun" + elseif weapon_hex == 1 then weapon_name = "Machine Gun" + elseif weapon_hex == 2 then weapon_name = "Flame Thrower" + elseif weapon_hex == 3 then weapon_name = "Spray" + elseif weapon_hex == 4 then weapon_name = "Laser" end + emu.drawString(5,40, "P1: " .. p1score .. rapid_fire .. weapon_name) +end + +emu.addEventCallback(Main, emu.eventType.endFrame) \ No newline at end of file diff --git a/docs/sprite_library/README.md b/docs/sprite_library/README.md new file mode 100644 index 0000000..865579b --- /dev/null +++ b/docs/sprite_library/README.md @@ -0,0 +1,45 @@ +This folder contains every sprite that can be loaded in Contra. The sprites +here are not used by the game code, they were extracted from the ROM for ease of +viewing. + +The name of each sprite is the same as the label in bank1.asm in the 2 large +sprite tables sprite_ptr_tbl_0 and sprite_ptr_tbl_1. + +The player lives medals in the heads up display (HUD) are not in the sprite +tables, but the tiles are written directly to the OAMDMA via the CPU buffer. + +# Sprite Table +| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | a | b | c | d | e | f | +|---|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------| +| 0 | | | ![sprite_02](sprite_02.png?raw=true "sprite_02") ![sprite_02_p2](sprite_02_p2.png?raw=true "sprite_02_p2") | ![sprite_03](sprite_03.png?raw=true "sprite_03") ![sprite_03_p2](sprite_03_p2.png?raw=true "sprite_03_p2") | ![sprite_04](sprite_04.png?raw=true "sprite_04") ![sprite_04_p2](sprite_04_p2.png?raw=true "sprite_04_p2") | ![sprite_05](sprite_05.png?raw=true "sprite_05") ![sprite_05_p2](sprite_05_p2.png?raw=true "sprite_05_p2") | ![sprite_06](sprite_06.png?raw=true "sprite_06") ![sprite_06_p2](sprite_06_p2.png?raw=true "sprite_06_p2") | ![sprite_07](sprite_07.png?raw=true "sprite_07") | ![sprite_08](sprite_08.png?raw=true "sprite_08") ![sprite_08_p2](sprite_08_p2.png?raw=true "sprite_08_p2") | ![sprite_09](sprite_09.png?raw=true "sprite_09") ![sprite_09_p2](sprite_09_p2.png?raw=true "sprite_09_p2") | ![sprite_0a](sprite_0a.png?raw=true "sprite_0a") ![sprite_0a_p2](sprite_0a_p2.png?raw=true "sprite_0a_p2") | ![sprite_0b](sprite_0b.png?raw=true "sprite_0b") ![sprite_0b_p2](sprite_0b_p2.png?raw=true "sprite_0b_p2") | ![sprite_0c](sprite_0c.png?raw=true "sprite_0c") ![sprite_0c_p2](sprite_0c_p2.png?raw=true "sprite_0c_p2") | ![sprite_0d](sprite_0d.png?raw=true "sprite_0d") ![sprite_0d_p2](sprite_0d_p2.png?raw=true "sprite_0d_p2") | ![sprite_0e](sprite_0e.png?raw=true "sprite_0e") ![sprite_0e_p2](sprite_0e_p2.png?raw=true "sprite_0e_p2") | ![sprite_0f](sprite_0f.png?raw=true "sprite_0f") ![sprite_0f_p2](sprite_0f_p2.png?raw=true "sprite_0f_p2") | +| 1 | ![sprite_10](sprite_10.png?raw=true "sprite_10") ![sprite_10_p2](sprite_10_p2.png?raw=true "sprite_10_p2") | ![sprite_11](sprite_11.png?raw=true "sprite_11") ![sprite_11_p2](sprite_11_p2.png?raw=true "sprite_11_p2") | ![sprite_12](sprite_12.png?raw=true "sprite_12") ![sprite_12_p2](sprite_12_p2.png?raw=true "sprite_12_p2") | ![sprite_13](sprite_13.png?raw=true "sprite_13") ![sprite_13_p2](sprite_13_p2.png?raw=true "sprite_13_p2") | ![sprite_14](sprite_14.png?raw=true "sprite_14") ![sprite_14_p2](sprite_14_p2.png?raw=true "sprite_14_p2") | ![sprite_15](sprite_15.png?raw=true "sprite_15") ![sprite_15_p2](sprite_15_p2.png?raw=true "sprite_15_p2") | ![sprite_16](sprite_16.png?raw=true "sprite_16") ![sprite_16_p2](sprite_16_p2.png?raw=true "sprite_16_p2") | ![sprite_17](sprite_17.png?raw=true "sprite_17") ![sprite_17_p2](sprite_17_p2.png?raw=true "sprite_17_p2") | ![sprite_18](sprite_18.png?raw=true "sprite_18") | ![sprite_19](sprite_19.png?raw=true "sprite_19") | ![sprite_1a](sprite_1a.png?raw=true "sprite_1a") | ![sprite_1b](sprite_1b.png?raw=true "sprite_1b") | ![sprite_1c](sprite_1c.png?raw=true "sprite_1c") | ![sprite_1d](sprite_1d.png?raw=true "sprite_1d") | ![sprite_1e](sprite_1e.png?raw=true "sprite_1e") | ![sprite_1f](sprite_1f.png?raw=true "sprite_1f") | +| 2 | ![sprite_20](sprite_20.png?raw=true "sprite_20") | ![sprite_21](sprite_21.png?raw=true "sprite_21") | ![sprite_22](sprite_22.png?raw=true "sprite_22") | ![sprite_23](sprite_23.png?raw=true "sprite_23") | ![sprite_24](sprite_24.png?raw=true "sprite_24") | ![sprite_25](sprite_25.png?raw=true "sprite_25") | ![sprite_26](sprite_26.png?raw=true "sprite_26") | ![sprite_27](sprite_27.png?raw=true "sprite_27") | ![sprite_28](sprite_28.png?raw=true "sprite_28") | ![sprite_29](sprite_29.png?raw=true "sprite_29") | ![sprite_2a](sprite_2a.png?raw=true "sprite_2a") | ![sprite_2b](sprite_2b.png?raw=true "sprite_2b") | ![sprite_2c](sprite_2c.png?raw=true "sprite_2c") | ![sprite_2d](sprite_2d.png?raw=true "sprite_2d") | | ![sprite_2f](sprite_2f.png?raw=true "sprite_2f") | +| 3 | ![sprite_30](sprite_30.png?raw=true "sprite_30") | ![sprite_31](sprite_31.png?raw=true "sprite_31") | ![sprite_32](sprite_32.png?raw=true "sprite_32") | ![sprite_33](sprite_33.png?raw=true "sprite_33") | ![sprite_34](sprite_34.png?raw=true "sprite_34") | ![sprite_35](sprite_35.png?raw=true "sprite_35") | ![sprite_36](sprite_36.png?raw=true "sprite_36") | ![sprite_37](sprite_37.png?raw=true "sprite_37") | ![sprite_38](sprite_38.png?raw=true "sprite_38") | ![sprite_39](sprite_39.png?raw=true "sprite_39") | ![sprite_3a](sprite_3a.png?raw=true "sprite_3a") | ![sprite_3b](sprite_3b.png?raw=true "sprite_3b") | ![sprite_3c](sprite_3c.png?raw=true "sprite_3c") | ![sprite_3d](sprite_3d.png?raw=true "sprite_3d") | ![sprite_3e](sprite_3e.png?raw=true "sprite_3e") | ![sprite_3f](sprite_3f.png?raw=true "sprite_3f") | +| 4 | ![sprite_40](sprite_40.png?raw=true "sprite_40") | ![sprite_41](sprite_41.png?raw=true "sprite_41") | ![sprite_42](sprite_42.png?raw=true "sprite_42") | ![sprite_43](sprite_43.png?raw=true "sprite_43") | ![sprite_44](sprite_44.png?raw=true "sprite_44") | ![sprite_45](sprite_45.png?raw=true "sprite_45") | ![sprite_46](sprite_46.png?raw=true "sprite_46") | ![sprite_47](sprite_47.png?raw=true "sprite_47") | ![sprite_48](sprite_48.png?raw=true "sprite_48") | ![sprite_49](sprite_49.png?raw=true "sprite_49") | ![sprite_4a](sprite_4a.png?raw=true "sprite_4a") | ![sprite_4b](sprite_4b.png?raw=true "sprite_4b") | ![sprite_4c](sprite_4c.png?raw=true "sprite_4c") | ![sprite_4d](sprite_4d.png?raw=true "sprite_4d") | ![sprite_4e](sprite_4e.gif?raw=true "sprite_4e") | | +| 5 | ![sprite_50](sprite_50.png?raw=true "sprite_50") ![sprite_50_p2](sprite_50_p2.png?raw=true "sprite_50_p2") | ![sprite_51](sprite_51.png?raw=true "sprite_51") ![sprite_51_p2](sprite_51_p2.png?raw=true "sprite_51_p2") | ![sprite_52](sprite_52.png?raw=true "sprite_52") ![sprite_52_p2](sprite_52_p2.png?raw=true "sprite_52_p2") | ![sprite_53](sprite_53.png?raw=true "sprite_53") ![sprite_53_p2](sprite_53_p2.png?raw=true "sprite_53_p2") | ![sprite_54](sprite_54.png?raw=true "sprite_54") ![sprite_54_p2](sprite_54_p2.png?raw=true "sprite_54_p2") | ![sprite_55](sprite_55.png?raw=true "sprite_55") ![sprite_55_p2](sprite_55_p2.png?raw=true "sprite_55_p2") | ![sprite_56](sprite_56.png?raw=true "sprite_56") ![sprite_56_p2](sprite_56_p2.png?raw=true "sprite_56_p2") | ![sprite_57](sprite_57.png?raw=true "sprite_57") ![sprite_57_p2](sprite_57_p2.png?raw=true "sprite_57_p2") | ![sprite_58](sprite_58.png?raw=true "sprite_58") ![sprite_58_p2](sprite_58_p2.png?raw=true "sprite_58_p2") | | | | | ![sprite_5d](sprite_5d.png?raw=true "sprite_5d") | ![sprite_5e](sprite_5e.png?raw=true "sprite_5e") | ![sprite_5f](sprite_5f.png?raw=true "sprite_5f") | +| 6 | ![sprite_60](sprite_60.png?raw=true "sprite_60") | ![sprite_61](sprite_61.png?raw=true "sprite_61") | ![sprite_62](sprite_62.png?raw=true "sprite_62") | ![sprite_63](sprite_63.png?raw=true "sprite_63") | ![sprite_64](sprite_64.png?raw=true "sprite_64") | | | | ![sprite_68](sprite_68.png?raw=true "sprite_68") | ![sprite_69](sprite_69.png?raw=true "sprite_69") | ![sprite_6a](sprite_6a.png?raw=true "sprite_6a") | ![sprite_6b](sprite_6b.png?raw=true "sprite_6b") | ![sprite_6c](sprite_6c.png?raw=true "sprite_6c") | ![sprite_6d](sprite_6d.png?raw=true "sprite_6d") | ![sprite_6e](sprite_6e.png?raw=true "sprite_6e") | ![sprite_6f](sprite_6f.png?raw=true "sprite_6f") | +| 7 | ![sprite_70](sprite_70.png?raw=true "sprite_70") | ![sprite_71](sprite_71.png?raw=true "sprite_71") | ![sprite_72](sprite_72.png?raw=true "sprite_72") | ![sprite_73](sprite_73.png?raw=true "sprite_73") | ![sprite_74](sprite_74.png?raw=true "sprite_74") | ![sprite_75](sprite_75.png?raw=true "sprite_75") | ![sprite_76](sprite_76.png?raw=true "sprite_76") | ![sprite_77](sprite_77.png?raw=true "sprite_77") | ![sprite_78](sprite_78.png?raw=true "sprite_78") | ![sprite_79](sprite_79.png?raw=true "sprite_79") | ![sprite_7a](sprite_7a.png?raw=true "sprite_7a") | ![sprite_7b](sprite_7b.png?raw=true "sprite_7b") | ![sprite_7c](sprite_7c.png?raw=true "sprite_7c") | ![sprite_7d](sprite_7d.png?raw=true "sprite_7d") | ![sprite_7e](sprite_7e.png?raw=true "sprite_7e") | | +| 8 | | | ![sprite_82](sprite_82.png?raw=true "sprite_82") | ![sprite_83](sprite_83.png?raw=true "sprite_83") | ![sprite_84](sprite_84.png?raw=true "sprite_84") | ![sprite_85](sprite_85.png?raw=true "sprite_85") | ![sprite_86](sprite_86.png?raw=true "sprite_86") | ![sprite_87](sprite_87.png?raw=true "sprite_87") | ![sprite_88](sprite_88.png?raw=true "sprite_88") | ![sprite_89](sprite_89.png?raw=true "sprite_89") | ![sprite_8a](sprite_8a.png?raw=true "sprite_8a") | ![sprite_8b](sprite_8b.png?raw=true "sprite_8b") | ![sprite_8c](sprite_8c.png?raw=true "sprite_8c") | ![sprite_8d](sprite_8d.png?raw=true "sprite_8d") | ![sprite_8e](sprite_8e.png?raw=true "sprite_8e") | ![sprite_8f](sprite_8f.png?raw=true "sprite_8f") | +| 9 | ![sprite_90](sprite_90.png?raw=true "sprite_90") | ![sprite_91](sprite_91.png?raw=true "sprite_91") ![sprite_91_p2](sprite_91_p2.png?raw=true "sprite_91_p2") | ![sprite_92](sprite_92.png?raw=true "sprite_92") | ![sprite_93](sprite_93.png?raw=true "sprite_93") | ![sprite_94](sprite_94.png?raw=true "sprite_94") | ![sprite_95](sprite_95.png?raw=true "sprite_95") | ![sprite_96](sprite_96.png?raw=true "sprite_96") | ![sprite_97](sprite_97.png?raw=true "sprite_97") | ![sprite_98](sprite_98.png?raw=true "sprite_98") | ![sprite_99](sprite_99.png?raw=true "sprite_99") | ![sprite_9a](sprite_9a.png?raw=true "sprite_9a") | ![sprite_9b](sprite_9b.png?raw=true "sprite_9b") | ![sprite_9c](sprite_9c.png?raw=true "sprite_9c") | ![sprite_9d](sprite_9d.png?raw=true "sprite_9d") | ![sprite_9e](sprite_9e.png?raw=true "sprite_9e") | ![sprite_9f](sprite_9f.png?raw=true "sprite_9f") | +| a | ![sprite_a0](sprite_a0.png?raw=true "sprite_a0") | ![sprite_a1](sprite_a1.png?raw=true "sprite_a1") | ![sprite_a2](sprite_a2.png?raw=true "sprite_a2") | ![sprite_a3](sprite_a3.png?raw=true "sprite_a3") | ![sprite_a4](sprite_a4.png?raw=true "sprite_a4") | ![sprite_a5](sprite_a5.png?raw=true "sprite_a5") | ![sprite_a6](sprite_a6.png?raw=true "sprite_a6") | ![sprite_a7](sprite_a7.png?raw=true "sprite_a7") | ![sprite_a8](sprite_a8.png?raw=true "sprite_a8") | ![sprite_a9](sprite_a9.png?raw=true "sprite_a9") | ![sprite_aa](sprite_aa.png?raw=true "sprite_aa") | ![sprite_ab](sprite_ab.png?raw=true "sprite_ab") | ![sprite_ac](sprite_ac.png?raw=true "sprite_ac") | ![sprite_ad](sprite_ad.png?raw=true "sprite_ad") | ![sprite_ae](sprite_ae.png?raw=true "sprite_ae") | ![sprite_af](sprite_af.png?raw=true "sprite_af") | +| b | ![sprite_b0](sprite_b0.png?raw=true "sprite_b0") | ![sprite_b1](sprite_b1.png?raw=true "sprite_b1") | ![sprite_b2](sprite_b2.png?raw=true "sprite_b2") | ![sprite_b3](sprite_b3.png?raw=true "sprite_b3") | ![sprite_b4](sprite_b4.png?raw=true "sprite_b4") | ![sprite_b5](sprite_b5.png?raw=true "sprite_b5") | ![sprite_b6](sprite_b6.png?raw=true "sprite_b6") | ![sprite_b7](sprite_b7.png?raw=true "sprite_b7") | ![sprite_b8](sprite_b8.png?raw=true "sprite_b8") | ![sprite_b9](sprite_b9.png?raw=true "sprite_b9") | ![sprite_ba](sprite_ba.png?raw=true "sprite_ba") | ![sprite_bb](sprite_bb.png?raw=true "sprite_bb") | ![sprite_bc](sprite_bc.png?raw=true "sprite_bc") | ![sprite_bd](sprite_bd.png?raw=true "sprite_bd") | ![sprite_be](sprite_be.png?raw=true "sprite_be") | ![sprite_bf](sprite_bf.png?raw=true "sprite_bf") | +| c | ![sprite_c0](sprite_c0.png?raw=true "sprite_c0") | ![sprite_c1](sprite_c1.png?raw=true "sprite_c1") | ![sprite_c2](sprite_c2.png?raw=true "sprite_c2") | ![sprite_c3](sprite_c3.png?raw=true "sprite_c3") | ![sprite_c4](sprite_c4.png?raw=true "sprite_c4") | ![sprite_c5](sprite_c5.png?raw=true "sprite_c5") | ![sprite_c6](sprite_c6.png?raw=true "sprite_c6") | ![sprite_c7](sprite_c7.png?raw=true "sprite_c7") | ![sprite_c8](sprite_c8.png?raw=true "sprite_c8") | ![sprite_c9](sprite_c9.png?raw=true "sprite_c9") | ![sprite_ca](sprite_ca.png?raw=true "sprite_ca") | ![sprite_cb](sprite_cb.png?raw=true "sprite_cb") | ![sprite_cc](sprite_cc.png?raw=true "sprite_cc") | ![sprite_cd](sprite_cd.png?raw=true "sprite_cd") | ![sprite_ce](sprite_ce.png?raw=true "sprite_ce") | ![sprite_cf](sprite_cf.png?raw=true "sprite_cf") | + +# Hud Sprites + +* Player 1 + * ![player_1_lives_medal](player_1_lives_medal.png?raw=true "player_1_lives_medal") + * ![player_1_game_over](player_1_game_over.png?raw=true "player_1_game_over") +* Player 2 + * ![player_2_lives_medal](player_2_lives_medal.png?raw=true "player_2_lives_medal") + * ![player_2_game_over](player_2_game_over.png?raw=true "player_2_game_over") + +# Sprites Not Used In Game + +* ![sprite_78](sprite_78.png?raw=true "sprite_78") (`sprite_78`) exists in the + game code, but isn't used. It is identical to `sprite_74` and that is what is + used insead. +* `sprite_59`, `sprite_5a`, `sprite_5b`, `sprite_5c`, `sprite_65`, `sprite_66`, + and `sprite_67` are all empty and not used in the game. +* `sprite_80` and `sprite_81` are defined in the game, but never used so I can't + tell which pattern tiles they are supposed to utilize. \ No newline at end of file diff --git a/docs/sprite_library/player_1_game_over.png b/docs/sprite_library/player_1_game_over.png new file mode 100644 index 0000000..085ed95 Binary files /dev/null and b/docs/sprite_library/player_1_game_over.png differ diff --git a/docs/sprite_library/player_1_lives_medal.png b/docs/sprite_library/player_1_lives_medal.png new file mode 100644 index 0000000..314dcd3 Binary files /dev/null and b/docs/sprite_library/player_1_lives_medal.png differ diff --git a/docs/sprite_library/player_2_game_over.png b/docs/sprite_library/player_2_game_over.png new file mode 100644 index 0000000..070ab00 Binary files /dev/null and b/docs/sprite_library/player_2_game_over.png differ diff --git a/docs/sprite_library/player_2_lives_medal.png b/docs/sprite_library/player_2_lives_medal.png new file mode 100644 index 0000000..a947c6f Binary files /dev/null and b/docs/sprite_library/player_2_lives_medal.png differ diff --git a/docs/sprite_library/sprite_02.png b/docs/sprite_library/sprite_02.png new file mode 100644 index 0000000..597e6a0 Binary files /dev/null and b/docs/sprite_library/sprite_02.png differ diff --git a/docs/sprite_library/sprite_02_p2.png b/docs/sprite_library/sprite_02_p2.png new file mode 100644 index 0000000..af29c7e Binary files /dev/null and b/docs/sprite_library/sprite_02_p2.png differ diff --git a/docs/sprite_library/sprite_03.png b/docs/sprite_library/sprite_03.png new file mode 100644 index 0000000..b07d135 Binary files /dev/null and b/docs/sprite_library/sprite_03.png differ diff --git a/docs/sprite_library/sprite_03_p2.png b/docs/sprite_library/sprite_03_p2.png new file mode 100644 index 0000000..e0738cd Binary files /dev/null and b/docs/sprite_library/sprite_03_p2.png differ diff --git a/docs/sprite_library/sprite_04.png b/docs/sprite_library/sprite_04.png new file mode 100644 index 0000000..2d7f29c Binary files /dev/null and b/docs/sprite_library/sprite_04.png differ diff --git a/docs/sprite_library/sprite_04_p2.png b/docs/sprite_library/sprite_04_p2.png new file mode 100644 index 0000000..59fddab Binary files /dev/null and b/docs/sprite_library/sprite_04_p2.png differ diff --git a/docs/sprite_library/sprite_05.png b/docs/sprite_library/sprite_05.png new file mode 100644 index 0000000..54c252d Binary files /dev/null and b/docs/sprite_library/sprite_05.png differ diff --git a/docs/sprite_library/sprite_05_p2.png b/docs/sprite_library/sprite_05_p2.png new file mode 100644 index 0000000..4176f58 Binary files /dev/null and b/docs/sprite_library/sprite_05_p2.png differ diff --git a/docs/sprite_library/sprite_06.png b/docs/sprite_library/sprite_06.png new file mode 100644 index 0000000..03d3185 Binary files /dev/null and b/docs/sprite_library/sprite_06.png differ diff --git a/docs/sprite_library/sprite_06_p2.png b/docs/sprite_library/sprite_06_p2.png new file mode 100644 index 0000000..1813401 Binary files /dev/null and b/docs/sprite_library/sprite_06_p2.png differ diff --git a/docs/sprite_library/sprite_07.png b/docs/sprite_library/sprite_07.png new file mode 100644 index 0000000..eaecc19 Binary files /dev/null and b/docs/sprite_library/sprite_07.png differ diff --git a/docs/sprite_library/sprite_08.png b/docs/sprite_library/sprite_08.png new file mode 100644 index 0000000..c87adfd Binary files /dev/null and b/docs/sprite_library/sprite_08.png differ diff --git a/docs/sprite_library/sprite_08_p2.png b/docs/sprite_library/sprite_08_p2.png new file mode 100644 index 0000000..63dd93f Binary files /dev/null and b/docs/sprite_library/sprite_08_p2.png differ diff --git a/docs/sprite_library/sprite_09.png b/docs/sprite_library/sprite_09.png new file mode 100644 index 0000000..5d0f85a Binary files /dev/null and b/docs/sprite_library/sprite_09.png differ diff --git a/docs/sprite_library/sprite_09_p2.png b/docs/sprite_library/sprite_09_p2.png new file mode 100644 index 0000000..630ce12 Binary files /dev/null and b/docs/sprite_library/sprite_09_p2.png differ diff --git a/docs/sprite_library/sprite_0a.png b/docs/sprite_library/sprite_0a.png new file mode 100644 index 0000000..eaed07c Binary files /dev/null and b/docs/sprite_library/sprite_0a.png differ diff --git a/docs/sprite_library/sprite_0a_p2.png b/docs/sprite_library/sprite_0a_p2.png new file mode 100644 index 0000000..f48f000 Binary files /dev/null and b/docs/sprite_library/sprite_0a_p2.png differ diff --git a/docs/sprite_library/sprite_0b.png b/docs/sprite_library/sprite_0b.png new file mode 100644 index 0000000..2754587 Binary files /dev/null and b/docs/sprite_library/sprite_0b.png differ diff --git a/docs/sprite_library/sprite_0b_p2.png b/docs/sprite_library/sprite_0b_p2.png new file mode 100644 index 0000000..7c12658 Binary files /dev/null and b/docs/sprite_library/sprite_0b_p2.png differ diff --git a/docs/sprite_library/sprite_0c.png b/docs/sprite_library/sprite_0c.png new file mode 100644 index 0000000..5f11fd3 Binary files /dev/null and b/docs/sprite_library/sprite_0c.png differ diff --git a/docs/sprite_library/sprite_0c_p2.png b/docs/sprite_library/sprite_0c_p2.png new file mode 100644 index 0000000..e41c6d7 Binary files /dev/null and b/docs/sprite_library/sprite_0c_p2.png differ diff --git a/docs/sprite_library/sprite_0d.png b/docs/sprite_library/sprite_0d.png new file mode 100644 index 0000000..10e3008 Binary files /dev/null and b/docs/sprite_library/sprite_0d.png differ diff --git a/docs/sprite_library/sprite_0d_p2.png b/docs/sprite_library/sprite_0d_p2.png new file mode 100644 index 0000000..260cd71 Binary files /dev/null and b/docs/sprite_library/sprite_0d_p2.png differ diff --git a/docs/sprite_library/sprite_0e.png b/docs/sprite_library/sprite_0e.png new file mode 100644 index 0000000..f696ac0 Binary files /dev/null and b/docs/sprite_library/sprite_0e.png differ diff --git a/docs/sprite_library/sprite_0e_p2.png b/docs/sprite_library/sprite_0e_p2.png new file mode 100644 index 0000000..be45fc6 Binary files /dev/null and b/docs/sprite_library/sprite_0e_p2.png differ diff --git a/docs/sprite_library/sprite_0f.png b/docs/sprite_library/sprite_0f.png new file mode 100644 index 0000000..4b8800d Binary files /dev/null and b/docs/sprite_library/sprite_0f.png differ diff --git a/docs/sprite_library/sprite_0f_p2.png b/docs/sprite_library/sprite_0f_p2.png new file mode 100644 index 0000000..48054bf Binary files /dev/null and b/docs/sprite_library/sprite_0f_p2.png differ diff --git a/docs/sprite_library/sprite_10.png b/docs/sprite_library/sprite_10.png new file mode 100644 index 0000000..08c1381 Binary files /dev/null and b/docs/sprite_library/sprite_10.png differ diff --git a/docs/sprite_library/sprite_10_p2.png b/docs/sprite_library/sprite_10_p2.png new file mode 100644 index 0000000..427c4c6 Binary files /dev/null and b/docs/sprite_library/sprite_10_p2.png differ diff --git a/docs/sprite_library/sprite_11.png b/docs/sprite_library/sprite_11.png new file mode 100644 index 0000000..63a7f5c Binary files /dev/null and b/docs/sprite_library/sprite_11.png differ diff --git a/docs/sprite_library/sprite_11_p2.png b/docs/sprite_library/sprite_11_p2.png new file mode 100644 index 0000000..0324d47 Binary files /dev/null and b/docs/sprite_library/sprite_11_p2.png differ diff --git a/docs/sprite_library/sprite_12.png b/docs/sprite_library/sprite_12.png new file mode 100644 index 0000000..29f748f Binary files /dev/null and b/docs/sprite_library/sprite_12.png differ diff --git a/docs/sprite_library/sprite_12_p2.png b/docs/sprite_library/sprite_12_p2.png new file mode 100644 index 0000000..c926483 Binary files /dev/null and b/docs/sprite_library/sprite_12_p2.png differ diff --git a/docs/sprite_library/sprite_13.png b/docs/sprite_library/sprite_13.png new file mode 100644 index 0000000..983256a Binary files /dev/null and b/docs/sprite_library/sprite_13.png differ diff --git a/docs/sprite_library/sprite_13_p2.png b/docs/sprite_library/sprite_13_p2.png new file mode 100644 index 0000000..be31df8 Binary files /dev/null and b/docs/sprite_library/sprite_13_p2.png differ diff --git a/docs/sprite_library/sprite_14.png b/docs/sprite_library/sprite_14.png new file mode 100644 index 0000000..3cc52fe Binary files /dev/null and b/docs/sprite_library/sprite_14.png differ diff --git a/docs/sprite_library/sprite_14_p2.png b/docs/sprite_library/sprite_14_p2.png new file mode 100644 index 0000000..a0cccc0 Binary files /dev/null and b/docs/sprite_library/sprite_14_p2.png differ diff --git a/docs/sprite_library/sprite_15.png b/docs/sprite_library/sprite_15.png new file mode 100644 index 0000000..c25f35b Binary files /dev/null and b/docs/sprite_library/sprite_15.png differ diff --git a/docs/sprite_library/sprite_15_p2.png b/docs/sprite_library/sprite_15_p2.png new file mode 100644 index 0000000..6f8b6ae Binary files /dev/null and b/docs/sprite_library/sprite_15_p2.png differ diff --git a/docs/sprite_library/sprite_16.png b/docs/sprite_library/sprite_16.png new file mode 100644 index 0000000..1dd558f Binary files /dev/null and b/docs/sprite_library/sprite_16.png differ diff --git a/docs/sprite_library/sprite_16_p2.png b/docs/sprite_library/sprite_16_p2.png new file mode 100644 index 0000000..78f1be1 Binary files /dev/null and b/docs/sprite_library/sprite_16_p2.png differ diff --git a/docs/sprite_library/sprite_17.png b/docs/sprite_library/sprite_17.png new file mode 100644 index 0000000..47016ee Binary files /dev/null and b/docs/sprite_library/sprite_17.png differ diff --git a/docs/sprite_library/sprite_17_p2.png b/docs/sprite_library/sprite_17_p2.png new file mode 100644 index 0000000..503ad01 Binary files /dev/null and b/docs/sprite_library/sprite_17_p2.png differ diff --git a/docs/sprite_library/sprite_18.png b/docs/sprite_library/sprite_18.png new file mode 100644 index 0000000..53677e5 Binary files /dev/null and b/docs/sprite_library/sprite_18.png differ diff --git a/docs/sprite_library/sprite_19.png b/docs/sprite_library/sprite_19.png new file mode 100644 index 0000000..ceef92f Binary files /dev/null and b/docs/sprite_library/sprite_19.png differ diff --git a/docs/sprite_library/sprite_1a.png b/docs/sprite_library/sprite_1a.png new file mode 100644 index 0000000..00ff024 Binary files /dev/null and b/docs/sprite_library/sprite_1a.png differ diff --git a/docs/sprite_library/sprite_1b.png b/docs/sprite_library/sprite_1b.png new file mode 100644 index 0000000..b9a23f6 Binary files /dev/null and b/docs/sprite_library/sprite_1b.png differ diff --git a/docs/sprite_library/sprite_1c.png b/docs/sprite_library/sprite_1c.png new file mode 100644 index 0000000..8b992e4 Binary files /dev/null and b/docs/sprite_library/sprite_1c.png differ diff --git a/docs/sprite_library/sprite_1d.png b/docs/sprite_library/sprite_1d.png new file mode 100644 index 0000000..381b227 Binary files /dev/null and b/docs/sprite_library/sprite_1d.png differ diff --git a/docs/sprite_library/sprite_1e.png b/docs/sprite_library/sprite_1e.png new file mode 100644 index 0000000..0050f18 Binary files /dev/null and b/docs/sprite_library/sprite_1e.png differ diff --git a/docs/sprite_library/sprite_1f.png b/docs/sprite_library/sprite_1f.png new file mode 100644 index 0000000..fbf7dd1 Binary files /dev/null and b/docs/sprite_library/sprite_1f.png differ diff --git a/docs/sprite_library/sprite_20.png b/docs/sprite_library/sprite_20.png new file mode 100644 index 0000000..62a421f Binary files /dev/null and b/docs/sprite_library/sprite_20.png differ diff --git a/docs/sprite_library/sprite_21.png b/docs/sprite_library/sprite_21.png new file mode 100644 index 0000000..a5f0d3c Binary files /dev/null and b/docs/sprite_library/sprite_21.png differ diff --git a/docs/sprite_library/sprite_22.png b/docs/sprite_library/sprite_22.png new file mode 100644 index 0000000..9597991 Binary files /dev/null and b/docs/sprite_library/sprite_22.png differ diff --git a/docs/sprite_library/sprite_23.png b/docs/sprite_library/sprite_23.png new file mode 100644 index 0000000..37bfe9d Binary files /dev/null and b/docs/sprite_library/sprite_23.png differ diff --git a/docs/sprite_library/sprite_24.png b/docs/sprite_library/sprite_24.png new file mode 100644 index 0000000..98a6573 Binary files /dev/null and b/docs/sprite_library/sprite_24.png differ diff --git a/docs/sprite_library/sprite_25.png b/docs/sprite_library/sprite_25.png new file mode 100644 index 0000000..f26bb1f Binary files /dev/null and b/docs/sprite_library/sprite_25.png differ diff --git a/docs/sprite_library/sprite_26.png b/docs/sprite_library/sprite_26.png new file mode 100644 index 0000000..15b7b3a Binary files /dev/null and b/docs/sprite_library/sprite_26.png differ diff --git a/docs/sprite_library/sprite_27.png b/docs/sprite_library/sprite_27.png new file mode 100644 index 0000000..645746a Binary files /dev/null and b/docs/sprite_library/sprite_27.png differ diff --git a/docs/sprite_library/sprite_28.png b/docs/sprite_library/sprite_28.png new file mode 100644 index 0000000..7964415 Binary files /dev/null and b/docs/sprite_library/sprite_28.png differ diff --git a/docs/sprite_library/sprite_29.png b/docs/sprite_library/sprite_29.png new file mode 100644 index 0000000..ce2626f Binary files /dev/null and b/docs/sprite_library/sprite_29.png differ diff --git a/docs/sprite_library/sprite_2a.png b/docs/sprite_library/sprite_2a.png new file mode 100644 index 0000000..e6fdb5e Binary files /dev/null and b/docs/sprite_library/sprite_2a.png differ diff --git a/docs/sprite_library/sprite_2b.png b/docs/sprite_library/sprite_2b.png new file mode 100644 index 0000000..ed5c543 Binary files /dev/null and b/docs/sprite_library/sprite_2b.png differ diff --git a/docs/sprite_library/sprite_2c.png b/docs/sprite_library/sprite_2c.png new file mode 100644 index 0000000..29f5f54 Binary files /dev/null and b/docs/sprite_library/sprite_2c.png differ diff --git a/docs/sprite_library/sprite_2d.png b/docs/sprite_library/sprite_2d.png new file mode 100644 index 0000000..f53437a Binary files /dev/null and b/docs/sprite_library/sprite_2d.png differ diff --git a/docs/sprite_library/sprite_2f.png b/docs/sprite_library/sprite_2f.png new file mode 100644 index 0000000..9e904b1 Binary files /dev/null and b/docs/sprite_library/sprite_2f.png differ diff --git a/docs/sprite_library/sprite_30.png b/docs/sprite_library/sprite_30.png new file mode 100644 index 0000000..d4e3483 Binary files /dev/null and b/docs/sprite_library/sprite_30.png differ diff --git a/docs/sprite_library/sprite_31.png b/docs/sprite_library/sprite_31.png new file mode 100644 index 0000000..0be7349 Binary files /dev/null and b/docs/sprite_library/sprite_31.png differ diff --git a/docs/sprite_library/sprite_32.png b/docs/sprite_library/sprite_32.png new file mode 100644 index 0000000..c0ea56b Binary files /dev/null and b/docs/sprite_library/sprite_32.png differ diff --git a/docs/sprite_library/sprite_33.png b/docs/sprite_library/sprite_33.png new file mode 100644 index 0000000..3d4c2a2 Binary files /dev/null and b/docs/sprite_library/sprite_33.png differ diff --git a/docs/sprite_library/sprite_34.png b/docs/sprite_library/sprite_34.png new file mode 100644 index 0000000..8052b96 Binary files /dev/null and b/docs/sprite_library/sprite_34.png differ diff --git a/docs/sprite_library/sprite_35.png b/docs/sprite_library/sprite_35.png new file mode 100644 index 0000000..92cb264 Binary files /dev/null and b/docs/sprite_library/sprite_35.png differ diff --git a/docs/sprite_library/sprite_36.png b/docs/sprite_library/sprite_36.png new file mode 100644 index 0000000..92d3497 Binary files /dev/null and b/docs/sprite_library/sprite_36.png differ diff --git a/docs/sprite_library/sprite_37.png b/docs/sprite_library/sprite_37.png new file mode 100644 index 0000000..0a4d996 Binary files /dev/null and b/docs/sprite_library/sprite_37.png differ diff --git a/docs/sprite_library/sprite_38.png b/docs/sprite_library/sprite_38.png new file mode 100644 index 0000000..9909b36 Binary files /dev/null and b/docs/sprite_library/sprite_38.png differ diff --git a/docs/sprite_library/sprite_39.png b/docs/sprite_library/sprite_39.png new file mode 100644 index 0000000..06b4609 Binary files /dev/null and b/docs/sprite_library/sprite_39.png differ diff --git a/docs/sprite_library/sprite_3a.png b/docs/sprite_library/sprite_3a.png new file mode 100644 index 0000000..a99af7d Binary files /dev/null and b/docs/sprite_library/sprite_3a.png differ diff --git a/docs/sprite_library/sprite_3b.png b/docs/sprite_library/sprite_3b.png new file mode 100644 index 0000000..7c12653 Binary files /dev/null and b/docs/sprite_library/sprite_3b.png differ diff --git a/docs/sprite_library/sprite_3c.png b/docs/sprite_library/sprite_3c.png new file mode 100644 index 0000000..8aebbcf Binary files /dev/null and b/docs/sprite_library/sprite_3c.png differ diff --git a/docs/sprite_library/sprite_3d.png b/docs/sprite_library/sprite_3d.png new file mode 100644 index 0000000..85af712 Binary files /dev/null and b/docs/sprite_library/sprite_3d.png differ diff --git a/docs/sprite_library/sprite_3e.png b/docs/sprite_library/sprite_3e.png new file mode 100644 index 0000000..2c15638 Binary files /dev/null and b/docs/sprite_library/sprite_3e.png differ diff --git a/docs/sprite_library/sprite_3f.png b/docs/sprite_library/sprite_3f.png new file mode 100644 index 0000000..f86ec29 Binary files /dev/null and b/docs/sprite_library/sprite_3f.png differ diff --git a/docs/sprite_library/sprite_40.png b/docs/sprite_library/sprite_40.png new file mode 100644 index 0000000..c49d535 Binary files /dev/null and b/docs/sprite_library/sprite_40.png differ diff --git a/docs/sprite_library/sprite_41.png b/docs/sprite_library/sprite_41.png new file mode 100644 index 0000000..0bec2ad Binary files /dev/null and b/docs/sprite_library/sprite_41.png differ diff --git a/docs/sprite_library/sprite_42.png b/docs/sprite_library/sprite_42.png new file mode 100644 index 0000000..450e65b Binary files /dev/null and b/docs/sprite_library/sprite_42.png differ diff --git a/docs/sprite_library/sprite_43.png b/docs/sprite_library/sprite_43.png new file mode 100644 index 0000000..d3fc2d0 Binary files /dev/null and b/docs/sprite_library/sprite_43.png differ diff --git a/docs/sprite_library/sprite_44.png b/docs/sprite_library/sprite_44.png new file mode 100644 index 0000000..2a00f18 Binary files /dev/null and b/docs/sprite_library/sprite_44.png differ diff --git a/docs/sprite_library/sprite_45.png b/docs/sprite_library/sprite_45.png new file mode 100644 index 0000000..9aad7f7 Binary files /dev/null and b/docs/sprite_library/sprite_45.png differ diff --git a/docs/sprite_library/sprite_46.png b/docs/sprite_library/sprite_46.png new file mode 100644 index 0000000..aca9872 Binary files /dev/null and b/docs/sprite_library/sprite_46.png differ diff --git a/docs/sprite_library/sprite_47.png b/docs/sprite_library/sprite_47.png new file mode 100644 index 0000000..08dded5 Binary files /dev/null and b/docs/sprite_library/sprite_47.png differ diff --git a/docs/sprite_library/sprite_48.png b/docs/sprite_library/sprite_48.png new file mode 100644 index 0000000..6851807 Binary files /dev/null and b/docs/sprite_library/sprite_48.png differ diff --git a/docs/sprite_library/sprite_49.png b/docs/sprite_library/sprite_49.png new file mode 100644 index 0000000..74afa8d Binary files /dev/null and b/docs/sprite_library/sprite_49.png differ diff --git a/docs/sprite_library/sprite_4a.png b/docs/sprite_library/sprite_4a.png new file mode 100644 index 0000000..174741a Binary files /dev/null and b/docs/sprite_library/sprite_4a.png differ diff --git a/docs/sprite_library/sprite_4b.png b/docs/sprite_library/sprite_4b.png new file mode 100644 index 0000000..256c7bd Binary files /dev/null and b/docs/sprite_library/sprite_4b.png differ diff --git a/docs/sprite_library/sprite_4c.png b/docs/sprite_library/sprite_4c.png new file mode 100644 index 0000000..7c60ae8 Binary files /dev/null and b/docs/sprite_library/sprite_4c.png differ diff --git a/docs/sprite_library/sprite_4d.png b/docs/sprite_library/sprite_4d.png new file mode 100644 index 0000000..1a703e5 Binary files /dev/null and b/docs/sprite_library/sprite_4d.png differ diff --git a/docs/sprite_library/sprite_4e 1.png b/docs/sprite_library/sprite_4e 1.png new file mode 100644 index 0000000..90ab864 Binary files /dev/null and b/docs/sprite_library/sprite_4e 1.png differ diff --git a/docs/sprite_library/sprite_4e 2.png b/docs/sprite_library/sprite_4e 2.png new file mode 100644 index 0000000..7b3d46e Binary files /dev/null and b/docs/sprite_library/sprite_4e 2.png differ diff --git a/docs/sprite_library/sprite_4e 3.png b/docs/sprite_library/sprite_4e 3.png new file mode 100644 index 0000000..5a7abb7 Binary files /dev/null and b/docs/sprite_library/sprite_4e 3.png differ diff --git a/docs/sprite_library/sprite_4e 4.png b/docs/sprite_library/sprite_4e 4.png new file mode 100644 index 0000000..42d1f0e Binary files /dev/null and b/docs/sprite_library/sprite_4e 4.png differ diff --git a/docs/sprite_library/sprite_4e.gif b/docs/sprite_library/sprite_4e.gif new file mode 100644 index 0000000..3c76ab1 Binary files /dev/null and b/docs/sprite_library/sprite_4e.gif differ diff --git a/docs/sprite_library/sprite_50.png b/docs/sprite_library/sprite_50.png new file mode 100644 index 0000000..17d2299 Binary files /dev/null and b/docs/sprite_library/sprite_50.png differ diff --git a/docs/sprite_library/sprite_50_p2.png b/docs/sprite_library/sprite_50_p2.png new file mode 100644 index 0000000..5818a04 Binary files /dev/null and b/docs/sprite_library/sprite_50_p2.png differ diff --git a/docs/sprite_library/sprite_51.png b/docs/sprite_library/sprite_51.png new file mode 100644 index 0000000..7256bd2 Binary files /dev/null and b/docs/sprite_library/sprite_51.png differ diff --git a/docs/sprite_library/sprite_51_p2.png b/docs/sprite_library/sprite_51_p2.png new file mode 100644 index 0000000..8ad4a2c Binary files /dev/null and b/docs/sprite_library/sprite_51_p2.png differ diff --git a/docs/sprite_library/sprite_52.png b/docs/sprite_library/sprite_52.png new file mode 100644 index 0000000..e8043cd Binary files /dev/null and b/docs/sprite_library/sprite_52.png differ diff --git a/docs/sprite_library/sprite_52_p2.png b/docs/sprite_library/sprite_52_p2.png new file mode 100644 index 0000000..4d81865 Binary files /dev/null and b/docs/sprite_library/sprite_52_p2.png differ diff --git a/docs/sprite_library/sprite_53.png b/docs/sprite_library/sprite_53.png new file mode 100644 index 0000000..db2b3e5 Binary files /dev/null and b/docs/sprite_library/sprite_53.png differ diff --git a/docs/sprite_library/sprite_53_p2.png b/docs/sprite_library/sprite_53_p2.png new file mode 100644 index 0000000..9b8c0c0 Binary files /dev/null and b/docs/sprite_library/sprite_53_p2.png differ diff --git a/docs/sprite_library/sprite_54.png b/docs/sprite_library/sprite_54.png new file mode 100644 index 0000000..ca8211a Binary files /dev/null and b/docs/sprite_library/sprite_54.png differ diff --git a/docs/sprite_library/sprite_54_p2.png b/docs/sprite_library/sprite_54_p2.png new file mode 100644 index 0000000..cec0f7e Binary files /dev/null and b/docs/sprite_library/sprite_54_p2.png differ diff --git a/docs/sprite_library/sprite_55.png b/docs/sprite_library/sprite_55.png new file mode 100644 index 0000000..2387b47 Binary files /dev/null and b/docs/sprite_library/sprite_55.png differ diff --git a/docs/sprite_library/sprite_55_p2.png b/docs/sprite_library/sprite_55_p2.png new file mode 100644 index 0000000..0931024 Binary files /dev/null and b/docs/sprite_library/sprite_55_p2.png differ diff --git a/docs/sprite_library/sprite_56.png b/docs/sprite_library/sprite_56.png new file mode 100644 index 0000000..b15e53c Binary files /dev/null and b/docs/sprite_library/sprite_56.png differ diff --git a/docs/sprite_library/sprite_56_p2.png b/docs/sprite_library/sprite_56_p2.png new file mode 100644 index 0000000..a38b6ff Binary files /dev/null and b/docs/sprite_library/sprite_56_p2.png differ diff --git a/docs/sprite_library/sprite_57.png b/docs/sprite_library/sprite_57.png new file mode 100644 index 0000000..6d894b6 Binary files /dev/null and b/docs/sprite_library/sprite_57.png differ diff --git a/docs/sprite_library/sprite_57_p2.png b/docs/sprite_library/sprite_57_p2.png new file mode 100644 index 0000000..6b6af99 Binary files /dev/null and b/docs/sprite_library/sprite_57_p2.png differ diff --git a/docs/sprite_library/sprite_58.png b/docs/sprite_library/sprite_58.png new file mode 100644 index 0000000..9e25f95 Binary files /dev/null and b/docs/sprite_library/sprite_58.png differ diff --git a/docs/sprite_library/sprite_58_p2.png b/docs/sprite_library/sprite_58_p2.png new file mode 100644 index 0000000..be13001 Binary files /dev/null and b/docs/sprite_library/sprite_58_p2.png differ diff --git a/docs/sprite_library/sprite_5d.png b/docs/sprite_library/sprite_5d.png new file mode 100644 index 0000000..f0a1e64 Binary files /dev/null and b/docs/sprite_library/sprite_5d.png differ diff --git a/docs/sprite_library/sprite_5e.png b/docs/sprite_library/sprite_5e.png new file mode 100644 index 0000000..cc20ad4 Binary files /dev/null and b/docs/sprite_library/sprite_5e.png differ diff --git a/docs/sprite_library/sprite_5f.png b/docs/sprite_library/sprite_5f.png new file mode 100644 index 0000000..184f03e Binary files /dev/null and b/docs/sprite_library/sprite_5f.png differ diff --git a/docs/sprite_library/sprite_60.png b/docs/sprite_library/sprite_60.png new file mode 100644 index 0000000..0a4b31f Binary files /dev/null and b/docs/sprite_library/sprite_60.png differ diff --git a/docs/sprite_library/sprite_61.png b/docs/sprite_library/sprite_61.png new file mode 100644 index 0000000..09dc50d Binary files /dev/null and b/docs/sprite_library/sprite_61.png differ diff --git a/docs/sprite_library/sprite_62.png b/docs/sprite_library/sprite_62.png new file mode 100644 index 0000000..e365248 Binary files /dev/null and b/docs/sprite_library/sprite_62.png differ diff --git a/docs/sprite_library/sprite_63.png b/docs/sprite_library/sprite_63.png new file mode 100644 index 0000000..b1e83ce Binary files /dev/null and b/docs/sprite_library/sprite_63.png differ diff --git a/docs/sprite_library/sprite_64.png b/docs/sprite_library/sprite_64.png new file mode 100644 index 0000000..f309cde Binary files /dev/null and b/docs/sprite_library/sprite_64.png differ diff --git a/docs/sprite_library/sprite_68.png b/docs/sprite_library/sprite_68.png new file mode 100644 index 0000000..04b8db9 Binary files /dev/null and b/docs/sprite_library/sprite_68.png differ diff --git a/docs/sprite_library/sprite_69.png b/docs/sprite_library/sprite_69.png new file mode 100644 index 0000000..174530e Binary files /dev/null and b/docs/sprite_library/sprite_69.png differ diff --git a/docs/sprite_library/sprite_6a.png b/docs/sprite_library/sprite_6a.png new file mode 100644 index 0000000..98328ef Binary files /dev/null and b/docs/sprite_library/sprite_6a.png differ diff --git a/docs/sprite_library/sprite_6b.png b/docs/sprite_library/sprite_6b.png new file mode 100644 index 0000000..85097a6 Binary files /dev/null and b/docs/sprite_library/sprite_6b.png differ diff --git a/docs/sprite_library/sprite_6c.png b/docs/sprite_library/sprite_6c.png new file mode 100644 index 0000000..7715cbe Binary files /dev/null and b/docs/sprite_library/sprite_6c.png differ diff --git a/docs/sprite_library/sprite_6d.png b/docs/sprite_library/sprite_6d.png new file mode 100644 index 0000000..4cb29c8 Binary files /dev/null and b/docs/sprite_library/sprite_6d.png differ diff --git a/docs/sprite_library/sprite_6e.png b/docs/sprite_library/sprite_6e.png new file mode 100644 index 0000000..eafa95a Binary files /dev/null and b/docs/sprite_library/sprite_6e.png differ diff --git a/docs/sprite_library/sprite_6f.png b/docs/sprite_library/sprite_6f.png new file mode 100644 index 0000000..1fda18b Binary files /dev/null and b/docs/sprite_library/sprite_6f.png differ diff --git a/docs/sprite_library/sprite_70.png b/docs/sprite_library/sprite_70.png new file mode 100644 index 0000000..eafa95a Binary files /dev/null and b/docs/sprite_library/sprite_70.png differ diff --git a/docs/sprite_library/sprite_71.png b/docs/sprite_library/sprite_71.png new file mode 100644 index 0000000..90fb20c Binary files /dev/null and b/docs/sprite_library/sprite_71.png differ diff --git a/docs/sprite_library/sprite_72.png b/docs/sprite_library/sprite_72.png new file mode 100644 index 0000000..1fda18b Binary files /dev/null and b/docs/sprite_library/sprite_72.png differ diff --git a/docs/sprite_library/sprite_73.png b/docs/sprite_library/sprite_73.png new file mode 100644 index 0000000..c35186f Binary files /dev/null and b/docs/sprite_library/sprite_73.png differ diff --git a/docs/sprite_library/sprite_74.png b/docs/sprite_library/sprite_74.png new file mode 100644 index 0000000..244cad5 Binary files /dev/null and b/docs/sprite_library/sprite_74.png differ diff --git a/docs/sprite_library/sprite_75.png b/docs/sprite_library/sprite_75.png new file mode 100644 index 0000000..08d6ccd Binary files /dev/null and b/docs/sprite_library/sprite_75.png differ diff --git a/docs/sprite_library/sprite_76.png b/docs/sprite_library/sprite_76.png new file mode 100644 index 0000000..fada0b3 Binary files /dev/null and b/docs/sprite_library/sprite_76.png differ diff --git a/docs/sprite_library/sprite_77.png b/docs/sprite_library/sprite_77.png new file mode 100644 index 0000000..9e024b6 Binary files /dev/null and b/docs/sprite_library/sprite_77.png differ diff --git a/docs/sprite_library/sprite_78.png b/docs/sprite_library/sprite_78.png new file mode 100644 index 0000000..46aedf8 Binary files /dev/null and b/docs/sprite_library/sprite_78.png differ diff --git a/docs/sprite_library/sprite_79.png b/docs/sprite_library/sprite_79.png new file mode 100644 index 0000000..fa04098 Binary files /dev/null and b/docs/sprite_library/sprite_79.png differ diff --git a/docs/sprite_library/sprite_7a.png b/docs/sprite_library/sprite_7a.png new file mode 100644 index 0000000..96a19e3 Binary files /dev/null and b/docs/sprite_library/sprite_7a.png differ diff --git a/docs/sprite_library/sprite_7b.png b/docs/sprite_library/sprite_7b.png new file mode 100644 index 0000000..336d9aa Binary files /dev/null and b/docs/sprite_library/sprite_7b.png differ diff --git a/docs/sprite_library/sprite_7c.png b/docs/sprite_library/sprite_7c.png new file mode 100644 index 0000000..b8605e0 Binary files /dev/null and b/docs/sprite_library/sprite_7c.png differ diff --git a/docs/sprite_library/sprite_7d.png b/docs/sprite_library/sprite_7d.png new file mode 100644 index 0000000..4980840 Binary files /dev/null and b/docs/sprite_library/sprite_7d.png differ diff --git a/docs/sprite_library/sprite_7e.png b/docs/sprite_library/sprite_7e.png new file mode 100644 index 0000000..0c8b9f9 Binary files /dev/null and b/docs/sprite_library/sprite_7e.png differ diff --git a/docs/sprite_library/sprite_82.png b/docs/sprite_library/sprite_82.png new file mode 100644 index 0000000..edb8bbd Binary files /dev/null and b/docs/sprite_library/sprite_82.png differ diff --git a/docs/sprite_library/sprite_83.png b/docs/sprite_library/sprite_83.png new file mode 100644 index 0000000..f5c9d41 Binary files /dev/null and b/docs/sprite_library/sprite_83.png differ diff --git a/docs/sprite_library/sprite_84.png b/docs/sprite_library/sprite_84.png new file mode 100644 index 0000000..edb8bbd Binary files /dev/null and b/docs/sprite_library/sprite_84.png differ diff --git a/docs/sprite_library/sprite_85.png b/docs/sprite_library/sprite_85.png new file mode 100644 index 0000000..fc6c9f7 Binary files /dev/null and b/docs/sprite_library/sprite_85.png differ diff --git a/docs/sprite_library/sprite_86.png b/docs/sprite_library/sprite_86.png new file mode 100644 index 0000000..7a288ab Binary files /dev/null and b/docs/sprite_library/sprite_86.png differ diff --git a/docs/sprite_library/sprite_87.png b/docs/sprite_library/sprite_87.png new file mode 100644 index 0000000..0767297 Binary files /dev/null and b/docs/sprite_library/sprite_87.png differ diff --git a/docs/sprite_library/sprite_88.png b/docs/sprite_library/sprite_88.png new file mode 100644 index 0000000..ce0e22a Binary files /dev/null and b/docs/sprite_library/sprite_88.png differ diff --git a/docs/sprite_library/sprite_89.png b/docs/sprite_library/sprite_89.png new file mode 100644 index 0000000..9e2c61e Binary files /dev/null and b/docs/sprite_library/sprite_89.png differ diff --git a/docs/sprite_library/sprite_8a.png b/docs/sprite_library/sprite_8a.png new file mode 100644 index 0000000..1997ee3 Binary files /dev/null and b/docs/sprite_library/sprite_8a.png differ diff --git a/docs/sprite_library/sprite_8b.png b/docs/sprite_library/sprite_8b.png new file mode 100644 index 0000000..f322844 Binary files /dev/null and b/docs/sprite_library/sprite_8b.png differ diff --git a/docs/sprite_library/sprite_8c.png b/docs/sprite_library/sprite_8c.png new file mode 100644 index 0000000..d9799fd Binary files /dev/null and b/docs/sprite_library/sprite_8c.png differ diff --git a/docs/sprite_library/sprite_8d.png b/docs/sprite_library/sprite_8d.png new file mode 100644 index 0000000..14d40f0 Binary files /dev/null and b/docs/sprite_library/sprite_8d.png differ diff --git a/docs/sprite_library/sprite_8e.png b/docs/sprite_library/sprite_8e.png new file mode 100644 index 0000000..02e70e1 Binary files /dev/null and b/docs/sprite_library/sprite_8e.png differ diff --git a/docs/sprite_library/sprite_8f.png b/docs/sprite_library/sprite_8f.png new file mode 100644 index 0000000..011d16b Binary files /dev/null and b/docs/sprite_library/sprite_8f.png differ diff --git a/docs/sprite_library/sprite_90.png b/docs/sprite_library/sprite_90.png new file mode 100644 index 0000000..157ed36 Binary files /dev/null and b/docs/sprite_library/sprite_90.png differ diff --git a/docs/sprite_library/sprite_91.png b/docs/sprite_library/sprite_91.png new file mode 100644 index 0000000..447b7ee Binary files /dev/null and b/docs/sprite_library/sprite_91.png differ diff --git a/docs/sprite_library/sprite_91_p2.png b/docs/sprite_library/sprite_91_p2.png new file mode 100644 index 0000000..b115ad0 Binary files /dev/null and b/docs/sprite_library/sprite_91_p2.png differ diff --git a/docs/sprite_library/sprite_92.png b/docs/sprite_library/sprite_92.png new file mode 100644 index 0000000..f5c9d41 Binary files /dev/null and b/docs/sprite_library/sprite_92.png differ diff --git a/docs/sprite_library/sprite_93.png b/docs/sprite_library/sprite_93.png new file mode 100644 index 0000000..1506b82 Binary files /dev/null and b/docs/sprite_library/sprite_93.png differ diff --git a/docs/sprite_library/sprite_94.png b/docs/sprite_library/sprite_94.png new file mode 100644 index 0000000..5a14c39 Binary files /dev/null and b/docs/sprite_library/sprite_94.png differ diff --git a/docs/sprite_library/sprite_95.png b/docs/sprite_library/sprite_95.png new file mode 100644 index 0000000..38de2db Binary files /dev/null and b/docs/sprite_library/sprite_95.png differ diff --git a/docs/sprite_library/sprite_96.png b/docs/sprite_library/sprite_96.png new file mode 100644 index 0000000..50b4140 Binary files /dev/null and b/docs/sprite_library/sprite_96.png differ diff --git a/docs/sprite_library/sprite_97.png b/docs/sprite_library/sprite_97.png new file mode 100644 index 0000000..54195cd Binary files /dev/null and b/docs/sprite_library/sprite_97.png differ diff --git a/docs/sprite_library/sprite_98.png b/docs/sprite_library/sprite_98.png new file mode 100644 index 0000000..be6f20f Binary files /dev/null and b/docs/sprite_library/sprite_98.png differ diff --git a/docs/sprite_library/sprite_99.png b/docs/sprite_library/sprite_99.png new file mode 100644 index 0000000..abdf03b Binary files /dev/null and b/docs/sprite_library/sprite_99.png differ diff --git a/docs/sprite_library/sprite_9a.png b/docs/sprite_library/sprite_9a.png new file mode 100644 index 0000000..7f207d1 Binary files /dev/null and b/docs/sprite_library/sprite_9a.png differ diff --git a/docs/sprite_library/sprite_9b.png b/docs/sprite_library/sprite_9b.png new file mode 100644 index 0000000..58e9502 Binary files /dev/null and b/docs/sprite_library/sprite_9b.png differ diff --git a/docs/sprite_library/sprite_9c.png b/docs/sprite_library/sprite_9c.png new file mode 100644 index 0000000..41eeb5b Binary files /dev/null and b/docs/sprite_library/sprite_9c.png differ diff --git a/docs/sprite_library/sprite_9d.png b/docs/sprite_library/sprite_9d.png new file mode 100644 index 0000000..583b048 Binary files /dev/null and b/docs/sprite_library/sprite_9d.png differ diff --git a/docs/sprite_library/sprite_9e.png b/docs/sprite_library/sprite_9e.png new file mode 100644 index 0000000..8647f74 Binary files /dev/null and b/docs/sprite_library/sprite_9e.png differ diff --git a/docs/sprite_library/sprite_9f.png b/docs/sprite_library/sprite_9f.png new file mode 100644 index 0000000..21aa8e1 Binary files /dev/null and b/docs/sprite_library/sprite_9f.png differ diff --git a/docs/sprite_library/sprite_a0.png b/docs/sprite_library/sprite_a0.png new file mode 100644 index 0000000..598bae7 Binary files /dev/null and b/docs/sprite_library/sprite_a0.png differ diff --git a/docs/sprite_library/sprite_a1.png b/docs/sprite_library/sprite_a1.png new file mode 100644 index 0000000..6e3ca42 Binary files /dev/null and b/docs/sprite_library/sprite_a1.png differ diff --git a/docs/sprite_library/sprite_a2.png b/docs/sprite_library/sprite_a2.png new file mode 100644 index 0000000..c3a3c47 Binary files /dev/null and b/docs/sprite_library/sprite_a2.png differ diff --git a/docs/sprite_library/sprite_a3.png b/docs/sprite_library/sprite_a3.png new file mode 100644 index 0000000..0785da8 Binary files /dev/null and b/docs/sprite_library/sprite_a3.png differ diff --git a/docs/sprite_library/sprite_a4.png b/docs/sprite_library/sprite_a4.png new file mode 100644 index 0000000..3752edc Binary files /dev/null and b/docs/sprite_library/sprite_a4.png differ diff --git a/docs/sprite_library/sprite_a5.png b/docs/sprite_library/sprite_a5.png new file mode 100644 index 0000000..31eb337 Binary files /dev/null and b/docs/sprite_library/sprite_a5.png differ diff --git a/docs/sprite_library/sprite_a6.png b/docs/sprite_library/sprite_a6.png new file mode 100644 index 0000000..c274740 Binary files /dev/null and b/docs/sprite_library/sprite_a6.png differ diff --git a/docs/sprite_library/sprite_a7.png b/docs/sprite_library/sprite_a7.png new file mode 100644 index 0000000..7af1b25 Binary files /dev/null and b/docs/sprite_library/sprite_a7.png differ diff --git a/docs/sprite_library/sprite_a8.png b/docs/sprite_library/sprite_a8.png new file mode 100644 index 0000000..15a23b1 Binary files /dev/null and b/docs/sprite_library/sprite_a8.png differ diff --git a/docs/sprite_library/sprite_a9.png b/docs/sprite_library/sprite_a9.png new file mode 100644 index 0000000..2c2ab4b Binary files /dev/null and b/docs/sprite_library/sprite_a9.png differ diff --git a/docs/sprite_library/sprite_aa.png b/docs/sprite_library/sprite_aa.png new file mode 100644 index 0000000..87e283e Binary files /dev/null and b/docs/sprite_library/sprite_aa.png differ diff --git a/docs/sprite_library/sprite_ab.png b/docs/sprite_library/sprite_ab.png new file mode 100644 index 0000000..260afec Binary files /dev/null and b/docs/sprite_library/sprite_ab.png differ diff --git a/docs/sprite_library/sprite_ac.png b/docs/sprite_library/sprite_ac.png new file mode 100644 index 0000000..00d87f8 Binary files /dev/null and b/docs/sprite_library/sprite_ac.png differ diff --git a/docs/sprite_library/sprite_ad.png b/docs/sprite_library/sprite_ad.png new file mode 100644 index 0000000..1a7bd08 Binary files /dev/null and b/docs/sprite_library/sprite_ad.png differ diff --git a/docs/sprite_library/sprite_ae.png b/docs/sprite_library/sprite_ae.png new file mode 100644 index 0000000..9a3d2d2 Binary files /dev/null and b/docs/sprite_library/sprite_ae.png differ diff --git a/docs/sprite_library/sprite_af.png b/docs/sprite_library/sprite_af.png new file mode 100644 index 0000000..3040a44 Binary files /dev/null and b/docs/sprite_library/sprite_af.png differ diff --git a/docs/sprite_library/sprite_b0.png b/docs/sprite_library/sprite_b0.png new file mode 100644 index 0000000..d3b23ec Binary files /dev/null and b/docs/sprite_library/sprite_b0.png differ diff --git a/docs/sprite_library/sprite_b1.png b/docs/sprite_library/sprite_b1.png new file mode 100644 index 0000000..6b60560 Binary files /dev/null and b/docs/sprite_library/sprite_b1.png differ diff --git a/docs/sprite_library/sprite_b2.png b/docs/sprite_library/sprite_b2.png new file mode 100644 index 0000000..0cc61d8 Binary files /dev/null and b/docs/sprite_library/sprite_b2.png differ diff --git a/docs/sprite_library/sprite_b3.png b/docs/sprite_library/sprite_b3.png new file mode 100644 index 0000000..e44e6d5 Binary files /dev/null and b/docs/sprite_library/sprite_b3.png differ diff --git a/docs/sprite_library/sprite_b4.png b/docs/sprite_library/sprite_b4.png new file mode 100644 index 0000000..7886555 Binary files /dev/null and b/docs/sprite_library/sprite_b4.png differ diff --git a/docs/sprite_library/sprite_b5.png b/docs/sprite_library/sprite_b5.png new file mode 100644 index 0000000..a232a03 Binary files /dev/null and b/docs/sprite_library/sprite_b5.png differ diff --git a/docs/sprite_library/sprite_b6.png b/docs/sprite_library/sprite_b6.png new file mode 100644 index 0000000..7bc1adf Binary files /dev/null and b/docs/sprite_library/sprite_b6.png differ diff --git a/docs/sprite_library/sprite_b7.png b/docs/sprite_library/sprite_b7.png new file mode 100644 index 0000000..69764da Binary files /dev/null and b/docs/sprite_library/sprite_b7.png differ diff --git a/docs/sprite_library/sprite_b8.png b/docs/sprite_library/sprite_b8.png new file mode 100644 index 0000000..69764da Binary files /dev/null and b/docs/sprite_library/sprite_b8.png differ diff --git a/docs/sprite_library/sprite_b9.png b/docs/sprite_library/sprite_b9.png new file mode 100644 index 0000000..1ce3fac Binary files /dev/null and b/docs/sprite_library/sprite_b9.png differ diff --git a/docs/sprite_library/sprite_ba.png b/docs/sprite_library/sprite_ba.png new file mode 100644 index 0000000..04f628e Binary files /dev/null and b/docs/sprite_library/sprite_ba.png differ diff --git a/docs/sprite_library/sprite_bb.png b/docs/sprite_library/sprite_bb.png new file mode 100644 index 0000000..d85dd95 Binary files /dev/null and b/docs/sprite_library/sprite_bb.png differ diff --git a/docs/sprite_library/sprite_bc.png b/docs/sprite_library/sprite_bc.png new file mode 100644 index 0000000..ee7f014 Binary files /dev/null and b/docs/sprite_library/sprite_bc.png differ diff --git a/docs/sprite_library/sprite_bd.png b/docs/sprite_library/sprite_bd.png new file mode 100644 index 0000000..2c29989 Binary files /dev/null and b/docs/sprite_library/sprite_bd.png differ diff --git a/docs/sprite_library/sprite_be.png b/docs/sprite_library/sprite_be.png new file mode 100644 index 0000000..a6e89f4 Binary files /dev/null and b/docs/sprite_library/sprite_be.png differ diff --git a/docs/sprite_library/sprite_bf.png b/docs/sprite_library/sprite_bf.png new file mode 100644 index 0000000..934791e Binary files /dev/null and b/docs/sprite_library/sprite_bf.png differ diff --git a/docs/sprite_library/sprite_c0.png b/docs/sprite_library/sprite_c0.png new file mode 100644 index 0000000..249ac2d Binary files /dev/null and b/docs/sprite_library/sprite_c0.png differ diff --git a/docs/sprite_library/sprite_c1.png b/docs/sprite_library/sprite_c1.png new file mode 100644 index 0000000..3caeff4 Binary files /dev/null and b/docs/sprite_library/sprite_c1.png differ diff --git a/docs/sprite_library/sprite_c2.png b/docs/sprite_library/sprite_c2.png new file mode 100644 index 0000000..82a2ed7 Binary files /dev/null and b/docs/sprite_library/sprite_c2.png differ diff --git a/docs/sprite_library/sprite_c3.png b/docs/sprite_library/sprite_c3.png new file mode 100644 index 0000000..5149bdb Binary files /dev/null and b/docs/sprite_library/sprite_c3.png differ diff --git a/docs/sprite_library/sprite_c4.png b/docs/sprite_library/sprite_c4.png new file mode 100644 index 0000000..03df0c3 Binary files /dev/null and b/docs/sprite_library/sprite_c4.png differ diff --git a/docs/sprite_library/sprite_c5.png b/docs/sprite_library/sprite_c5.png new file mode 100644 index 0000000..8344c10 Binary files /dev/null and b/docs/sprite_library/sprite_c5.png differ diff --git a/docs/sprite_library/sprite_c6.png b/docs/sprite_library/sprite_c6.png new file mode 100644 index 0000000..c0316bc Binary files /dev/null and b/docs/sprite_library/sprite_c6.png differ diff --git a/docs/sprite_library/sprite_c7.png b/docs/sprite_library/sprite_c7.png new file mode 100644 index 0000000..789953e Binary files /dev/null and b/docs/sprite_library/sprite_c7.png differ diff --git a/docs/sprite_library/sprite_c8.png b/docs/sprite_library/sprite_c8.png new file mode 100644 index 0000000..1584bd9 Binary files /dev/null and b/docs/sprite_library/sprite_c8.png differ diff --git a/docs/sprite_library/sprite_c9.png b/docs/sprite_library/sprite_c9.png new file mode 100644 index 0000000..3158a71 Binary files /dev/null and b/docs/sprite_library/sprite_c9.png differ diff --git a/docs/sprite_library/sprite_ca.png b/docs/sprite_library/sprite_ca.png new file mode 100644 index 0000000..750dfa0 Binary files /dev/null and b/docs/sprite_library/sprite_ca.png differ diff --git a/docs/sprite_library/sprite_cb.png b/docs/sprite_library/sprite_cb.png new file mode 100644 index 0000000..44485c0 Binary files /dev/null and b/docs/sprite_library/sprite_cb.png differ diff --git a/docs/sprite_library/sprite_cc.png b/docs/sprite_library/sprite_cc.png new file mode 100644 index 0000000..15e126e Binary files /dev/null and b/docs/sprite_library/sprite_cc.png differ diff --git a/docs/sprite_library/sprite_cd.png b/docs/sprite_library/sprite_cd.png new file mode 100644 index 0000000..08daff0 Binary files /dev/null and b/docs/sprite_library/sprite_cd.png differ diff --git a/docs/sprite_library/sprite_ce.png b/docs/sprite_library/sprite_ce.png new file mode 100644 index 0000000..c465ed8 Binary files /dev/null and b/docs/sprite_library/sprite_ce.png differ diff --git a/docs/sprite_library/sprite_cf.png b/docs/sprite_library/sprite_cf.png new file mode 100644 index 0000000..2903b06 Binary files /dev/null and b/docs/sprite_library/sprite_cf.png differ diff --git a/set_bytes.vbs b/set_bytes.vbs new file mode 100644 index 0000000..85234c3 --- /dev/null +++ b/set_bytes.vbs @@ -0,0 +1,22 @@ +' function used by build.bat for copying bytes from the source Contra US NES rom +' file to the various binary files for use when assembling. + +Set fso=CreateObject("Scripting.FileSystemObject") + +' check if output already exists +If fso.FileExists(WScript.Arguments(2)) Then +Else + Wscript.Echo " Writing file " + WScript.Arguments(2) + "." + + ' read bytes from source Contra US NES rom + Set inFile=fso.OpenTextFile("baserom.nes") + inFile.Skip(WScript.Arguments(0)) + buf=inFile.Read(WScript.Arguments(1)) + inFile.Close + + ' write binary file + + Set outFile = fso.CreateTextFile(WScript.Arguments(2)) + outFile.Write buf + outFile.Close +End If \ No newline at end of file diff --git a/src/assets/README.md b/src/assets/README.md new file mode 100644 index 0000000..c257cf0 --- /dev/null +++ b/src/assets/README.md @@ -0,0 +1 @@ +This folder contains the assets (audio and graphics) required to build the rom. \ No newline at end of file diff --git a/src/assets/audio_data/README.md b/src/assets/audio_data/README.md new file mode 100644 index 0000000..ea2403a --- /dev/null +++ b/src/assets/audio_data/README.md @@ -0,0 +1 @@ +This folder contains the audio assets required to build the rom. \ No newline at end of file diff --git a/src/assets/graphic_data/README.md b/src/assets/graphic_data/README.md new file mode 100644 index 0000000..a1efd35 --- /dev/null +++ b/src/assets/graphic_data/README.md @@ -0,0 +1 @@ +This folder contains the graphics assets required to build the rom. \ No newline at end of file diff --git a/src/bank0.asm b/src/bank0.asm new file mode 100644 index 0000000..eda9d86 --- /dev/null +++ b/src/bank0.asm @@ -0,0 +1,10427 @@ +; Contra US Disassembly - v1.0 +; https://github.com/vermiceli/nes-contra-us +; Bank 0 is used exclusively for enemy routines. Enemy routines are the logic +; controlling enemy behaviors, AI, movements, and attack patterns. Almost every +; enemy is coded in bank 0, but some enemies, usually those who appear in more +; than one level, are coded in bank 7. + +.segment "BANK_0" + +.include "constants.asm" + +; import labels from bank 7 +.import play_sound, init_APU_channels, load_bank_3_update_nametable_supertile +.import load_bank_3_update_nametable_tiles, load_palettes_color_to_cpu +.import get_bg_collision_far, get_cart_bg_collision, wall_core_routine_05 +.import boss_defeated_routine, enemy_routine_init_explosion +.import mortar_shot_routine_03, set_enemy_delay_adv_routine +.import advance_enemy_routine, roller_routine_04, shared_enemy_routine_03 +.import enemy_routine_explosion, enemy_routine_remove_enemy +.import shared_enemy_routine_clear_sprite, set_enemy_routine_to_a +.import update_enemy_pos, update_enemy_x_pos_rem_off_screen +.import set_enemy_y_vel_rem_off_screen, set_outdoor_weapon_item_vel +.import add_scroll_to_enemy_pos, set_enemy_velocity_to_0 +.import set_enemy_y_velocity_to_0, set_enemy_x_velocity_to_0 +.import reverse_enemy_x_direction, set_destroyed_enemy_routine +.import destroy_all_enemies, clear_supertile_bg_collision +.import set_supertile_bg_collision, set_supertile_bg_collisions +.import create_explosion_89, create_two_explosion_89, create_enemy_for_explosion +.import level_boss_defeated, set_delay_remove_enemy +.import disable_bullet_enemy_collision, disable_enemy_collision +.import enable_enemy_player_collision_check, enable_bullet_enemy_collision +.import enable_enemy_collision, add_a_to_enemy_y_pos, add_a_to_enemy_x_pos +.import set_08_09_to_enemy_pos, add_with_enemy_pos, add_10_to_enemy_y_fract_vel +.import add_a_to_enemy_y_fract_vel, generate_enemy_a, generate_enemy_at_pos +.import add_4_to_enemy_y_pos, add_a_with_vert_scroll_to_enemy_y_pos +.import update_nametable_tiles_set_delay, draw_enemy_supertile_a_set_delay +.import draw_enemy_supertile_a, update_2_enemy_supertiles +.import update_enemy_nametable_tiles_no_palette, update_enemy_nametable_tiles +.import check_enemy_collision_solid_bg, init_vars_get_enemy_bg_collision +.import add_y_to_y_pos_get_bg_collision, add_a_y_to_enemy_pos_get_bg_collision +.import set_flying_capsule_y_vel, set_flying_capsule_x_vel +.import red_turret_find_target_player, player_enemy_x_dist +.import find_far_segment_for_x_pos, find_far_segment_for_a +.import set_enemy_falling_arc_pos, set_weapon_item_indoor_velocity +.import find_next_enemy_slot, clear_sprite_clear_enemy_pt_3 +.import clear_enemy_custom_vars, initialize_enemy, aim_and_create_enemy_bullet +.import bullet_generation, create_enemy_bullet_angle_a, set_bullet_velocities +.import aim_var_1_for_quadrant_aim_dir_01, aim_var_1_for_quadrant_aim_dir_00 +.import get_rotate_00, get_rotate_01 +.import get_rotate_dir, dragon_arm_orb_seek_should_move +.import get_quadrant_aim_dir_for_player, remove_enemy + +; export labels for bank 7 +; level 1 enemies +.export bomb_turret_routine_ptr_tbl +.export boss_wall_plated_door_routine_ptr_tbl +.export exploding_bridge_routine_ptr_tbl + +; level 2 and 4 enemies +.export boss_eye_routine_ptr_tbl +.export roller_routine_ptr_tbl +.export grenade_routine_ptr_tbl +.export wall_turret_routine_ptr_tbl +.export wall_core_routine_ptr_tbl +.export indoor_soldier_routine_ptr_tbl +.export jumping_soldier_routine_ptr_tbl +.export grenade_launcher_routine_ptr_tbl +.export four_soldiers_routine_ptr_tbl +.export indoor_soldier_gen_routine_ptr_tbl +.export indoor_roller_gen_routine_ptr_tbl +.export eye_projectile_routine_ptr_tbl +.export boss_gemini_routine_ptr_tbl +.export spinning_bubbles_routine_ptr_tbl +.export blue_soldier_routine_ptr_tbl +.export red_soldier_routine_ptr_tbl +.export red_blue_soldier_gen_routine_ptr_tbl + +; level 3 enemies +.export floating_rock_routine_ptr_tbl +.export moving_flame_routine_ptr_tbl +.export rock_cave_routine_ptr_tbl +.export falling_rock_routine_ptr_tbl +.export boss_mouth_routine_ptr_tbl +.export dragon_arm_orb_routine_ptr_tbl + +; level 5 enemies +.export ice_grenade_generator_routine_ptr_tbl +.export ice_grenade_routine_ptr_tbl +.export tank_routine_ptr_tbl +.export ice_separator_routine_ptr_tbl +.export boss_ufo_routine_ptr_tbl +.export mini_ufo_routine_ptr_tbl +.export boss_ufo_bomb_routine_ptr_tbl + +; level 6 enemies +.export fire_beam_down_routine_ptr_tbl +.export fire_beam_left_routine_ptr_tbl +.export fire_beam_right_routine_ptr_tbl +.export boss_giant_soldier_routine_ptr_tbl +.export boss_giant_projectile_routine_ptr_tbl + +; level 7 enemies +.export claw_routine_ptr_tbl +.export rising_spiked_wall_routine_ptr_tbl +.export spiked_wall_routine_ptr_tbl +.export mine_cart_generator_routine_ptr_tbl +.export moving_cart_routine_ptr_tbl +.export immobile_cart_generator_routine_ptr_tbl +.export boss_door_routine_ptr_tbl +.export boss_mortar_routine_ptr_tbl +.export boss_soldier_generator_routine_ptr_tbl + +; level 8 enemies +.export alien_guardian_routine_ptr_tbl +.export alien_fetus_routine_ptr_tbl +.export alien_mouth_routine_ptr_tbl +.export white_blob_routine_ptr_tbl +.export alien_spider_routine_ptr_tbl +.export alien_spider_spawn_routine_ptr_tbl +.export boss_heart_routine_ptr_tbl + +; enemies that exist on multiple levels +.export enemy_bullet_routine_ptr_tbl +.export rotating_gun_routine_ptr_tbl +.export red_turret_routine_ptr_tbl +.export sniper_routine_ptr_tbl +.export soldier_routine_ptr_tbl +.export weapon_box_routine_ptr_tbl +.export weapon_item_routine_ptr_tbl +.export flying_capsule_routine_ptr_tbl + +; Every PRG ROM bank starts with a single byte specifying which number it is +.byte $00 ; The PRG ROM bank number (0) + +; table of pointers for weapon item routines ($03 * $02 = $06 bytes) +weapon_item_routine_ptr_tbl: + .addr weapon_item_routine_00 ; CPU address $8007 + .addr weapon_item_routine_01 ; CPU address $8068 + .addr weapon_item_routine_02 ; CPU address $8100 + +; weapon item - pointer 1 +; sets collision code, velocity +; weapon items are created after flying capsule, pill box sensors, or red soldiers (in indoor/base levels) are destroyed +weapon_item_routine_00: + lda #$80 ; a = #$80 + sta ENEMY_STATE_WIDTH,x ; mark weapon item so bullets travel through it + lda #$22 ; a = #$22 + sta ENEMY_SCORE_COLLISION,x ; score code #$02, collision type #$02 + lda #$05 ; set sprite palette #$01, bit 2 specifies sprite code ROM data override + sta ENEMY_SPRITE_ATTR,x ; set weapon item sprite palette to palette #$01 + lda LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor + beq @set_velocity_outdoor ; branch for outdoor level + lda ENEMY_Y_POS,x ; indoor level, load y position on screen + sta ENEMY_VAR_1,x ; set ENEMY_VAR_1 to initial Y position + jsr set_weapon_item_indoor_velocity ; sets X and Y velocities for indoor level based on X position + lda #$80 + sta ENEMY_VAR_4,x + lda #$fd + sta ENEMY_VAR_B,x + jmp advance_enemy_routine ; advance to next routine + +@set_velocity_outdoor: + ldy #$00 ; set weapon_item_init_vel_tbl to first set of entries + lda LEVEL_SCROLLING_TYPE ; 0 = horizontal, indoor/base; 1 = vertical + beq @continue ; branch for horizontal scrolling + ldy #$04 ; vertical scrolling, set weapon_item_init_vel_tbl to second set of entries + lda ENEMY_X_POS,x ; load weapon item position on screen + cmp #$80 + bcc @continue ; branch if ENEMY_X_POS < #$80 + ldy #$08 ; weapon item close to right edge, set weapon_item_init_vel_tbl to 3rd set of entries + +@continue: + lda weapon_item_init_vel_tbl,y ; load weapon item initial fractional y velocity + sta ENEMY_Y_VELOCITY_FRACT,x ; set weapon item initial fractional y velocity + lda weapon_item_init_vel_tbl+1,y ; load weapon item initial fast y velocity + sta ENEMY_Y_VELOCITY_FAST,x ; set weapon item initial fast y velocity + lda weapon_item_init_vel_tbl+2,y ; load weapon item initial fractional x velocity + sta ENEMY_X_VELOCITY_FRACT,x ; set weapon item initial fractional x velocity + lda weapon_item_init_vel_tbl+3,y ; load weapon item initial fast x velocity + sta ENEMY_X_VELOCITY_FAST,x ; set weapon item initial fast x velocity + +weapon_item_advance_enemy_routine: + jmp advance_enemy_routine ; advance to next routine + +; table for outdoor weapon item velocities (#$4 * #$4 = #$c bytes) +; #$04 bytes each entry +; * byte 0 - initial y velocity - fractional velocity byte +; * byte 1 - initial y velocity - fast velocity byte +; * byte 2 - initial x velocity - fractional velocity byte +; * byte 3 - initial x velocity - fast velocity byte +weapon_item_init_vel_tbl: + .byte $00,$fd,$80,$00 ; outdoor horizontal level initial velocities (go up 3, go right .5) + .byte $00,$fd,$40,$00 ; vertical level left side of screen (go up 3, go right .25) + .byte $00,$fd,$c0,$ff ; vertical level close to right edge (to up -3, go left .75) + +; weapon item - pointer 2 +; responsible for falling and landing on ground pattern in outdoor levels and +; falling arc pattern on indoor levels until land on ground, then advance enemy routine +weapon_item_routine_01: + jsr set_weapon_item_sprite ; set the correct sprite code based on weapon item type + lda LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor + beq @outdoor_pos_update ; branch for outdoor level + lda ENEMY_VAR_4,x ; indoor level, load ENEMY_VAR_4,x + clc ; clear carry in preparation for addition + adc #$12 ; ENEMY_VAR_4 + #$12 + sta ENEMY_VAR_4,x ; used to help calculate arc trajectory + lda ENEMY_VAR_B,x ; used to help calculate arc trajectory + adc #$00 ; add any overflow from ENEMY_VAR_4 + #$12 + sta ENEMY_VAR_B,x ; used to help calculate arc trajectory + jsr set_enemy_falling_arc_pos ; set X and Y position to follow a falling arc + lda ENEMY_VAR_3,x ; used to help calculate arc trajectory + bmi @exit ; see if weapon item should "land" at the bottom of the indoor/base level + lda #$a4 ; weapon item should stop on ground, hard-coded y position #$a4 for indoor levels + sta ENEMY_Y_POS,x ; set weapon item Y position on screen + bne weapon_item_advance_enemy_routine ; set routine to weapon_item_routine_02 + +@exit: + rts + +; updates weapon item's X and Y position for outdoor levels +@outdoor_pos_update: + jsr set_outdoor_weapon_item_vel ; apply weapon item velocity and remove if off screen (at bottom, left, and right) + jsr @top_of_screen_check ; see if off screen to the top, if so, don't do collision checks + bcc @check_collision_reverse_dir ; branch if no collision + lda #$0a ; collision detected, set a #$0a (the amount to move in the Y direction to land) + jsr add_a_with_vert_scroll_to_enemy_y_pos ; weapon item landed on ground, update weapon item Y position + jsr set_enemy_velocity_to_0 ; set x/y velocities to zero + jmp advance_enemy_routine ; move to weapon_item_routine_02 + +@check_collision_reverse_dir: + lda ENEMY_X_POS,x ; load enemy x position on screen + ldy ENEMY_X_VELOCITY_FAST,x ; load fast x velocity + bmi @check_collision ; check if a collision for weapon item floating left + cmp #$e8 ; weapon item stationary or floating right, compare x position to #$e8 + bcs @reverse_direction ; if close to right side of screen > #$e8, then reverse direction + lda #$0a ; add #$0a to weapon item X position + jsr weapon_item_check_bg_collision ; add a to X position and get bg collision + ; exit if LEVEL_SOLID_BG_COLLISION_CHECK is #$00 + bmi @reverse_direction ; branch if weapon item collided with solid object to reverse direction + bpl @add_10_to_y_fract_vel ; branch if not solid bg collision to add #$10 to fractional y velocity and exit + +; no collision detected or negative X velocity +@check_collision: + cmp #$18 ; see if close close to left edge of screen + bcc @reverse_direction ; branch if close to left edge of screen + lda #$f6 ; subtract #$10 (#$f6) from weapon item X position + jsr weapon_item_check_bg_collision ; going left, see if about to collide with solid bg + bpl @add_10_to_y_fract_vel ; branch if not solid bg collision to add #$10 to fractional y velocity and exit + +@reverse_direction: + jsr reverse_enemy_x_direction ; reverse enemy's x direction + +@add_10_to_y_fract_vel: + jmp add_10_to_enemy_y_fract_vel ; add #$10 to y fractional velocity (.06 faster) + +@top_of_screen_check: + lda ENEMY_Y_POS,x ; load enemy y position on screen + cmp #$20 ; compare ENEMY_Y_POS,x < #$20 + bcc clear_carry_exit ; branch if weapon item is off the top of the screen + +; outdoor weapon item collision check +; if weapon item is falling (not ascending), then check for bg collision +; output +; * carry set when falling and collided with background +; * carry clear when either ascending or no bg collision +check_weapon_item_collision: + lda ENEMY_FRAME,x ; load enemy animation frame number + ora ENEMY_Y_VELOCITY_FAST,x + bmi clear_carry_exit ; branch if flying upward, no bg collision detection + ldy #$08 ; y = #$08 + jsr add_y_to_y_pos_get_bg_collision ; add #$10 to enemy y position and gets bg collision code + ; (see if about to land on something) + beq clear_carry_exit ; exit if no background collision + sec ; landing on something, set carry + rts + +clear_carry_exit: + clc ; clear the carry flag + rts + +; check for background collision for weapon item if LEVEL_SOLID_BG_COLLISION_CHECK is non-zero +; add a to ENEMY_X_POS,x +; input +; * a - weapon item test x position +; output +; * a collision code #$00 (empty), #$01 (floor), #$02 (water), or #$80 (solid) +; * carry set when collision is with floor (#$01) +; carry clear when LEVEL_SOLID_BG_COLLISION_CHECK is #$00 +weapon_item_check_bg_collision: + clc ; clear carry in preparation for addition + adc ENEMY_X_POS,x ; add to enemy x position on screen + sta $08 + lda LEVEL_SOLID_BG_COLLISION_CHECK ; see if level specifies that weapon items should collide with solid bg objects + beq clear_carry_exit ; exit as if no background solid collision enabled for level + lda ENEMY_FRAME,x ; checking for solid bg collision, enemy animation frame number + bne @continue + ldy ENEMY_Y_POS,x ; enemy y position on screen + cpy #$10 + bcs @continue_2 ; branch if ENEMY_Y_POS,x >= #$10 + +@continue: + ldy #$10 ; use position #$10 for weapon item Y position for bg collision check + +@continue_2: + lda $08 ; load enemy X position + jmp get_bg_collision_far ; get background collision code + +; weapon item - pointer 3 +; continually watches to see if still on ground +; if ground disappears or weapon item slides off, move back to weapon_item_routine_01 +weapon_item_routine_02: + jsr set_weapon_item_sprite + lda LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor + beq @outdoor_weapon_item ; branch for outdoor level + lda LEVEL_SCREEN_SCROLL_OFFSET ; indoor level, load scrolling offset within frame in pixels + beq @exit + jmp remove_enemy ; from bank 7 + +@outdoor_weapon_item: + jsr set_outdoor_weapon_item_vel ; apply weapon item velocity and remove if off screen (at bottom, left, and right) + jsr check_weapon_item_collision ; check weapon item bg collision if falling + bcs @exit ; exit if collision detected + lda #$02 ; no bg collision detected, a = #$02 + jmp set_enemy_routine_to_a ; set routine index to weapon_item_routine_01 + +@exit: + rts + +; sets sprite, attributes (palette) and updates for flashing if falcon weapon item +set_weapon_item_sprite: + lda #$00 ; a = #$00 + ldy ENEMY_FRAME,x ; enemy animation frame number + bne @set_a_to_sprite_code_exit ; not first frame of animation, set invisible and exit + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + and #$07 ; keep enemy attributes portion + tay + cmp #$06 ; 06 = falcon item + bne @set_sprite_code + lda FRAME_COUNTER ; falcon item, flash falcon colors based on frame counter + lsr + lsr + lsr ; flash every #$08 frames + and #$03 ; keep bits .... ..xx + ora #$04 ; set bit 3 + sta ENEMY_SPRITE_ATTR,x ; update enemy sprite attribute so it flashes + +@set_sprite_code: + lda weapon_item_sprite_code_tbl,y ; load weapon item sprite code + +@set_a_to_sprite_code_exit: + sta ENEMY_SPRITES,x ; set weapon item sprite code + rts + +; table for item sprite codes (#$7 bytes) +; #$33 - sprite code for r weapon (sprite_33) +; #$34 - sprite code for m weapon (sprite_34) +; #$31 - sprite code for f weapon (sprite_35) +; #$2f - sprite code for s weapon (sprite_2f) +; #$32 - sprite code for l weapon (sprite_32) +; #$30 - sprite code for b weapon (barrier/invincibility) (sprite_30) +; #$4e - sprite code for falcon (sprite_4e) +weapon_item_sprite_code_tbl: + .byte $33,$34,$31,$2f,$32,$30,$4e + +; pointer table for enemy bullet (#$4 * #42 = #48 bytes) +enemy_bullet_routine_ptr_tbl: + .addr enemy_bullet_routine_00 ; CPU address $814f (initialize collision code) + .addr enemy_bullet_routine_01 ; CPU address $8161 (init palette, sprite, and velocity) + .addr enemy_bullet_routine_02 ; CPU address $81e4 (level 1 boss cannonball explosion animation routine) + .addr remove_enemy ; CPU address $e809 from bank 7 + +; enemy bullet - pointer 1 +enemy_bullet_routine_00: + ldy ENEMY_VAR_1,x ; load bullet type + lda bullet_collision_code_tbl,y ; load bullet collision code + sta ENEMY_SCORE_COLLISION,x ; store bullet collision code + jmp advance_enemy_routine ; advance to next routine + +; for enemy bullet collision box (#$6 bytes) +; * #$01 - regular bullets (bullet types #$00, #$03) +; * #$05 - larger cannonball bullets (bullet types #$01, #$02) +; * #$02 - level 3 dragon boss fire ball (dragon arm orb projectile) (bullet type #$04) +bullet_collision_code_tbl: + .byte $01,$05,$05,$01,$02,$00 + +; enemy bullet - pointer 2 +enemy_bullet_routine_01: + ldy ENEMY_VAR_1,x ; load bullet type + bne @init_bullet_vel_pos_sprite ; bullet type is not #$00, no need changing to red for snow field + lda CURRENT_LEVEL ; current level + cmp #$04 ; check if level 5 (snow field) + bne @init_bullet_vel_pos_sprite ; not level 5, don't change bullet type #$00 to #05 + ldy #$05 ; snow field, change bullet type #$00 to #05 (for red bullets) + +@init_bullet_vel_pos_sprite: + lda bullet_sprite_tbl,y ; load bullet sprite + sta ENEMY_SPRITES,x ; store bullet sprite + lda bullet_palette_tbl,y ; load bullet's palette + sta ENEMY_SPRITE_ATTR,x ; set sprite attribute (palette) + jsr update_enemy_pos ; apply velocities and scrolling adjust + ldy ENEMY_VAR_1,x ; re-load bullet type (clears snow level #$05 override) + bne @continue ; skip if not a regular bullet + lda LEVEL_SOLID_BG_COLLISION_CHECK ; see if should test bullet - solid bg collisions + bpl @continue ; skip if level doesn't specify bullets should collide with solid bg objects + jsr check_enemy_collision_solid_bg ; see if bullet is colliding with solid object, or floor on top of solid object + bpl enemy_bullet_routine_01_exit ; exit if solid collision code + jmp remove_enemy ; from bank 7 + +@continue: + dey + beq cannonball_add_gravity_explode ; branch if bullet type #$01 (large cannonball) + dey + dey + beq indoor_bullet_offscreen_check ; branch if bullet type #$03 (indoor regular bullet) + dey + bne enemy_bullet_routine_01_exit ; exit if bullet not bullet type #$04 (dragon arm orb projectile) + lda FRAME_COUNTER ; bullet type #$04, load frame counter for animating fire ball projectile + lsr ; shift right twice + lsr + and #$03 ; bits 0 and 1 cause animation to change every #$04 frames + tay ; transfer bullet_04_palette_mirror_tbl offset to y + lda bullet_04_palette_mirror_tbl,y ; load sprite flipping and palette code for animating dragon arm orb projectile + sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes + +enemy_bullet_routine_01_exit: + rts + +; dragon arm orb projectile (orange fireballs) palette code and mirroring (#$4 bytes) +; used for animating palette and sprite flipping every #$04 frames +bullet_04_palette_mirror_tbl: + .byte $01,$41,$c1,$81 + +; check if bullet should be removed, otherwise exit +indoor_bullet_offscreen_check: + lda ENEMY_Y_POS,x ; load enemy y position on screen + cmp #$b4 ; see if bullet far at bottom of screen + bcs @remove_bullet ; remove if too far down on screen + lda ENEMY_X_POS,x ; load enemy x position on screen + cmp #$20 ; see if bullet far to the left + bcc @remove_bullet ; remove if bullet too far to the left + cmp #$e0 ; see if bullet too far to the right + bcc enemy_bullet_exit ; exit if not too far to the left + +@remove_bullet: + jmp remove_enemy ; from bank 7 + +; large cannonball bullet (bullet type #$01) +; adds gravity to create arc, and explodes at height #$d0 (the ground) +; if explodes, moves to enemy_bullet_routine_02, otherwise exists +cannonball_add_gravity_explode: + lda #$14 ; a = #$14 (gravity coefficient for bombs) + jsr add_a_to_enemy_y_fract_vel ; add a to enemy y fractional velocity + lda ENEMY_Y_POS,x ; load enemy y position on screen + cmp #$d0 ; bombs explode at this height + bcc enemy_bullet_exit ; exit if not yet exploded + lda #$00 ; a = #$00 + sta ENEMY_FRAME,x ; set enemy animation frame number + lda #$01 ; a = #$01 + sta ENEMY_ANIMATION_DELAY,x ; enemy explosion animation frame delay counter + +adv_bullet_routine: + jmp advance_enemy_routine ; advance to next routine + +; table for bullet sprite codes (#$6 bytes) +; sprite_1e - regular bullet +; sprite_21 - large cannonball bullet +; sprite_79 - level 3 dragon boss fire ball +; sprite_07 - level 5 - snow field red bullets +bullet_sprite_tbl: + .byte $1e,$21,$21,$1e,$79,$07 + +; table for bullet palette codes (#$6 bytes) +bullet_palette_tbl: + .byte $01,$02,$02,$01,$01,$02 + +; enemy bullet - pointer 3 +; only used for bullet type #$03 (level 1 boss cannonball) explosion animation +enemy_bullet_routine_02: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne enemy_bullet_exit ; exit if animation delay hasn't elapsed + lda #$08 ; a = #$08 + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + inc ENEMY_FRAME,x ; increment enemy animation frame number + ldy ENEMY_FRAME,x ; load enemy animation frame number + cpy #$03 ; see if on third frame of animation + bcs adv_bullet_routine ; go to enemy_bullet_routine_03 if explosion animation complete + lda cannonball_explosion_sprite_tbl,y ; load cannonball explosion sprite + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + +enemy_bullet_exit: + rts + +; table for bullet type #$02 (cannonball) explosion sprite codes (#$3 bytes) +cannonball_explosion_sprite_tbl: + .byte $37,$36,$37 + +; pointer table for weapon box (#$b * #$2 = #$16 bytes) +weapon_box_routine_ptr_tbl: + .addr weapon_box_routine_00 ; CPU address $821b + .addr weapon_box_routine_01 ; CPU address $8225 + .addr weapon_box_routine_02 ; CPU address $8248 + .addr weapon_box_routine_03 ; CPU address $82b0 + .addr weapon_box_routine_04 ; CPU address $82bd + .addr enemy_routine_init_explosion ; CPU address $e74b + .addr enemy_routine_explosion ; CPU address $e7b0 + .addr enemy_routine_remove_enemy ; CPU address $e806 + .addr enemy_routine_init_explosion ; CPU address $e74b + .addr enemy_routine_explosion ; CPU address $e7b0 + .addr enemy_routine_remove_enemy ; CPU address $e806 + +; weapon box - pointer 1 +; initialize ENEMY_FRAME, set delay and move to weapon_box_routine_01 +weapon_box_routine_00: + lda #$01 ; a = #$01 + sta ENEMY_FRAME,x ; set enemy animation frame number + lda #$20 ; a = #$20 (weapon box initial delay) + +set_enemy_delay_adv_routine_far: + jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a; advance enemy routine + +; Weapon Box - Pointer 2 +; wait for scroll to bring weapon box to specified position, then advance to weapon_box_routine_02 +; if pill box sensor is close to edge of screen (left for horizontal levels, bottom for vertical levels), then move to weapon_box_routine_03 +weapon_box_routine_01: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda #$f0 ; set x position for weapon box activation + ldy #$30 ; set y position for vertical level weapon box activation + jsr set_carry_if_past_trigger_point ; set carry if enemy has crossed #$f0 X position for horizontal levels, and #$30 for vertical levels + bcc weapon_box_exit ; exit if player is too far from pill box sensor to activate it + lda #$18 ; a = #$18 + ldy #$c8 ; y = #$c8 + jsr set_carry_if_past_trigger_point ; set carry if enemy has crossed #$18 X position for horizontal levels, and #$c8 for vertical levels + bcs adv_to_weapon_box_routine_03 ; see if pill box sensor is close to left (or bottom) of screen, if so, move to weapon_box_routine_03 (closes weapon box) + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne weapon_box_exit + lda #$08 ; a = #$08 + bne set_enemy_delay_adv_routine_far ; set ENEMY_ANIMATION_DELAY to #$08 and move to weapon_box_routine_02 + +adv_to_weapon_box_routine_03: + lda #$04 ; a = #$04 + jmp set_enemy_routine_to_a ; set to weapon_box_routine_03 + +; Weapon Box - Pointer 3 +; animates opening and closing of weapon box +weapon_box_routine_02: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda #$18 ; a = #$18 + ldy #$c8 ; y = #$c8 + jsr set_carry_if_past_trigger_point ; set carry if enemy has crossed #$18 X position for horizontal levels, and #$c8 for vertical levels + bcs adv_to_weapon_box_routine_03 ; see if pill box sensor is close to left (or bottom) of screen, if so, move to weapon_box_routine_03 (closes weapon box) + dec ENEMY_ANIMATION_DELAY,x ; decrement animation frame + bne weapon_box_exit ; exit if animation hasn't completed + lda #$08 ; a = #$08 + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + lda ENEMY_VAR_2,x ; load whether the weapon box is open or not + bne @open_weapon_box ; branch if weapon box is open + jsr set_weapon_box_supertile ; weapon box is close,d set the weapon box super tile based on ENEMY_FRAME + bcs weapon_box_exit + lda ENEMY_FRAME,x ; load enemy animation frame number + cmp #$02 ; see if weapon box is open + bcc inc_weapon_box_frame ; branch if weapon box is closed, or closing/opening, increment ENEMY_FRAME to move to next frame's super-tile + dec ENEMY_FRAME,x ; weapon box is closed; animate closing by decrementing ENEMY_FRAME to use the partially closed super-tile + lda #$01 ; a = #$01 + sta ENEMY_HP,x ; set weapon box hp to #$01 (when not closed) + lda #$01 ; a = #$01 + bne @set_animation_delay ; always branch + +@open_weapon_box: + jsr set_weapon_box_supertile ; set the weapon box super tile based on ENEMY_FRAME + bcs weapon_box_exit + lda ENEMY_FRAME,x ; load enemy animation frame number + bne dec_weapon_box_frame ; + inc ENEMY_FRAME,x ; increment enemy animation frame number + lda #$f0 ; a = #$f0 (f0 = no hit) + sta ENEMY_HP,x ; set enemy hp + lda #$00 ; a = #$00 + +@set_animation_delay: + sta ENEMY_VAR_2,x ; store whether the weapon box is open + lda #$40 ; a = #$40 (delay when weapon box fully open) + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + lda #$02 ; a = #$02 + jmp set_enemy_routine_to_a ; set routine to weapon_box_routine_01 + +; next animation frame +inc_weapon_box_frame: + inc ENEMY_FRAME,x ; increment enemy animation frame number + +weapon_box_exit: + rts + +; previous animation frame +dec_weapon_box_frame: + dec ENEMY_FRAME,x ; decrement enemy animation frame number + rts + +; sets the weapon box super tile based on ENEMY_FRAME +set_weapon_box_supertile: + ldy ENEMY_FRAME,x ; enemy animation frame number + lda weapon_box_supertile_tbl,y ; load the correct super-tile index (indexes into level_X_nametable_update_supertile_data) + jmp draw_enemy_supertile_a_set_delay ; draw pattern table tile specified in a at enemy position + +; table for weapon box frame codes (#$3 bytes) +; #$00 closed +; #$01 semi-open +; #$02 fully open +weapon_box_supertile_tbl: + .byte $00,$01,$02 + +; weapon box pointer 4 +; weapon box deactivated, mark closed, only executed once +weapon_box_routine_03: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda #$00 ; #$00 = weapon box closed (indexes into level_X_nametable_update_supertile_data) + jsr draw_enemy_supertile_a ; draw the closed weapon box super-tile + bcs weapon_box_exit_1 + jmp remove_enemy ; remove the enemy since not drawn + +; Weapon Box - Pointer 5 +; initiated when weapon box is destroyed via enemy_destroyed_routine_00 +; set appropriate background super-tile based on level and ENEMY_ATTRIBUTES (bit 4) +weapon_box_routine_04: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda CURRENT_LEVEL ; current level + asl + tay + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + and #$08 ; keep bits .... x... + beq @continue ; if big 4 is not set then use the first byte + iny ; bit 4 was set, use second byte + +@continue: + lda weapon_box_destroyed_supertile,y + jsr draw_enemy_supertile_a ; draw super-tile a at position (ENEMY_X_POS, ENEMY_Y_POS) + bcs weapon_box_exit + +play_explosion_sound: + lda #$19 ; a = #$19 (sound_19) + jsr play_sound ; play enemy destroyed sound + jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position + jsr create_two_explosion_89 ; create explosion #$89 at location ($09, $08) + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + and #$07 ; keep bits .... .xxx + sta ENEMY_ATTRIBUTES,x + jsr clear_sprite_clear_enemy_pt_3 + lda #$01 ; a = #$01 + sta ENEMY_ROUTINE,x ; enemy routine index + lda #$00 ; a = #$00 + sta ENEMY_TYPE,x ; set enemy slot to be weapon item + +weapon_box_exit_1: + rts + +; table for weapon box tile code after destruction (#$8 * #$2 = #$10 bytes) +; each entry is for a level +; the least significant bit of the vertical position is also used to +; specify which tile is shown after being destroyed. +weapon_box_destroyed_supertile: + .byte $16,$16 ; level 1 + .byte $16,$16 ; level 2 + .byte $16,$16 ; level 3 + .byte $16,$16 ; level 4 + .byte $19,$1a ; level 5 + .byte $03,$04 ; level 6 + .byte $09,$09 ; level 7 + .byte $16,$16 ; ending + +; pointer table for weapon zeppelin (#43 * #$2 = #$6 bytes) +flying_capsule_routine_ptr_tbl: + .addr flying_capsule_routine_00 ; CPU address $830b + .addr flying_capsule_routine_01 ; CPU address $835d + .addr flying_capsule_routine_02 ; CPU address $8376 + +; weapon zeppelin - pointer 1 +flying_capsule_routine_00: + lda #$03 ; a = #$03 (weapon zeppelin palette code) + sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes + lda ENEMY_Y_POS,x + sta ENEMY_VAR_1,x + lda ENEMY_X_POS,x ; load enemy x position on screen + sta ENEMY_VAR_2,x + lda LEVEL_SCROLLING_TYPE ; 0 = horizontal, indoor/base; 1 = vertical + bne @set_vertical_level_vel ; branch for vertical level (waterfall) + lda #$20 ; a = #$20 (zeppelin vertical amplitude) + jsr add_a_to_enemy_y_pos ; add #$20 to y position + lda #$10 ; a = #$10 (zeppelin initial x position) + sta ENEMY_X_POS,x ; set initial enemy x position on screen + ldy #$00 ; y = #$00 + beq @set_vel_adv_routine ; always branch + +@set_vertical_level_vel: + lda #$20 ; a = #$20 + jsr add_a_to_enemy_x_pos ; add #$20 to enemy x position on screen + lda #$e0 ; a = #$e0 + sta ENEMY_Y_POS,x ; enemy y position on screen + ldy #$04 ; y = #$04 + +@set_vel_adv_routine: + lda flying_capsule_vel_tbl,y ; load y fractional velocity byte + sta ENEMY_Y_VELOCITY_FRACT,x ; set y fractional velocity byte + lda flying_capsule_vel_tbl+1,y ; load y velocity fast byte + sta ENEMY_Y_VELOCITY_FAST,x ; set y velocity fast byte + lda flying_capsule_vel_tbl+2,y ; load x fractional velocity byte + sta ENEMY_X_VELOCITY_FRACT,x ; set x fractional velocity byte + lda flying_capsule_vel_tbl+3,y ; load x velocity fast byte + sta ENEMY_X_VELOCITY_FAST,x ; set x velocity fast byte + jmp advance_enemy_routine ; go to flying_capsule_routine_01 + +; table for weapon zeppelin velocities (#$4 * #$2 = #$8 bytes) +flying_capsule_vel_tbl: + .byte $00,$00,$80,$01 ; horizontal and indoor/base levels + .byte $80,$fe,$00,$00 ; vertical level (level 3 - waterfall) + +; weapon zeppelin - pointer 2 +flying_capsule_routine_01: + lda #$4d ; a = #$4d + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + lda LEVEL_SCROLLING_TYPE ; 0 = horizontal, indoor/base; 1 = vertical + bne @continue + ldy #$01 ; y = #$01 + jsr set_flying_capsule_y_vel + jmp @update_enemy_pos + +@continue: + ldy #$01 ; y = #$01 + jsr set_flying_capsule_x_vel + +@update_enemy_pos: + jmp update_enemy_pos ; apply velocities and scrolling adjust + +; weapon zeppelin pointer 3 +flying_capsule_routine_02: + jmp play_explosion_sound + +; pointer table for rotating gun (#$a * #$2 = #$14 bytes) +; level 1 or level 3 enemy +rotating_gun_routine_ptr_tbl: + .addr rotating_gun_routine_00 ; CPU address $838d - set aim direction to left + .addr rotating_gun_routine_01 ; CPU address $8397 - wait until player is close to activate + .addr rotating_gun_routine_02 ; CPU address $83ac - show opening animation, enable collision + .addr rotating_gun_routine_03 ; CPU address $83d5 - shut down if off screen, otherwise, wait for animation delay, then aim + .addr rotating_gun_routine_04 ; CPU address $842e - fire desired number of bullets at player, once complete go back to rotating_gun_routine_03 + .addr rotating_gun_routine_05 ; CPU address $8482 - shuts down rotating gun, gun retracts and no longer fires, removes enemy + .addr rotating_gun_routine_06 ; CPU address $848f - enemy destroyed routine (see enemy_destroyed_routine_01) + .addr enemy_routine_init_explosion ; CPU address $e74b from bank 7 + .addr enemy_routine_explosion ; CPU address $e7b0 from bank 7 + .addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7 + +; rotating gun - pointer 1 +; set aim direction to left +rotating_gun_routine_00: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda #$06 ; a = #$06 + sta ENEMY_VAR_1,x ; set aim direction to face left + bne rotating_gun_adv_routine_exit ; advance routine to rotating_gun_routine_01 and exit + +; rotating gun - pointer 2 +; wait until player is close to activate +rotating_gun_routine_01: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda #$f0 ; a = #$f0 (horizontal level trigger point) + ldy #$30 ; y = #$30 (vertical level trigger point) + jsr set_carry_if_past_trigger_point ; set carry if enemy has crossed #$f0 X position for horizontal levels, and #$30 for vertical levels + bcc rotating_gun_exit_00 ; exit if not yet at trigger point on screen, i.e. don't activate + lda #$08 ; should activate rotating gun, set delay to #$08 + ; advance routine to rotating_gun_routine_02 + +rotating_gun_set_delay_adv_routine_exit: + sta ENEMY_ANIMATION_DELAY,x + +rotating_gun_adv_routine_exit: + jmp advance_enemy_routine + +rotating_gun_exit_00: + rts + +; rotating gun - pointer 3 +; show opening animation (#$02 frames), enable collision +rotating_gun_routine_02: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + dec ENEMY_ANIMATION_DELAY,x ; decrement animation delay + bne rotating_gun_exit_00 ; exit if animation delay hasn't elapsed + lda #$08 ; a = #$08 + sta ENEMY_ANIMATION_DELAY,x ; set next animation delay to #$08 + lda ENEMY_FRAME,x ; load current super-tile index to (level_xx_nametable_update_supertile_data) + clc ; clear carry in preparation for addition + adc #$03 ; rotating gun super-tiles start at offset #$03, add #$03 + jsr draw_enemy_supertile_a_set_delay ; draw pattern table tile specified in a at enemy position + bcs rotating_gun_exit_00 ; exit if unable to update super-tile + inc ENEMY_FRAME,x ; increment rotating gun super-tile to draw + lda ENEMY_FRAME,x ; load rotating gun super-tile to draw + cmp #$03 ; see if rotating gun is active and open, i.e. the gun is showing + bcc rotating_gun_exit_00 ; branch if rotating gun not yet open + jsr enable_bullet_enemy_collision ; rotating gun active, allow bullets to collide (and stop) upon colliding with rotating gun + lda #$08 ; a = #$08 + bne rotating_gun_set_delay_adv_routine_exit ; set delay and move to rotating_gun_routine_03 + +; rotating gun - pointer 4 +; shut down if off screen, otherwise, wait for animation delay, then aim +rotating_gun_routine_03: + jsr rotating_gun_should_disable ; determine if almost scrolled off screen + bcc rotating_gun_routine_03_continue ; branch if not past trigger point + +; shuts down rotating gun by moving to rotating_gun_routine_05, +; which sets super-tile to closed and removes enemy +rotating_gun_disable: + lda #$06 ; a = #$06 + jmp set_enemy_routine_to_a ; set enemy routine index to rotating_gun_routine_05 + +; rotating gun isn't disabled, continue animation delay and aiming +rotating_gun_routine_03_continue: + dec ENEMY_ANIMATION_DELAY,x ; decrement animation delay + bne @exit ; exit if animation delay hasn't elapsed + ldy PLAYER_WEAPON_STRENGTH ; load player's weapon strength + lda rotating_gun_rotation_delay_tbl,y ; load rotation animation delay based on weapon strength + sta ENEMY_ANIMATION_DELAY,x ; set new animation delay + jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01) + sty $0a ; store closest player index in $0a + jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position + jsr aim_var_1_for_quadrant_aim_dir_00 ; determine next aim direction [#$00-#$0b] ($0c), adjusts ENEMY_VAR_1 to get closer to that value using quadrant_aim_dir_00 + php ; save the processor flags to the stack + lda ENEMY_VAR_1,x ; load new enemy aim direction [#$00-#$0b] #$00 when facing right incrementing clockwise + sec ; set carry flag in preparation for subtraction + sbc #$06 ; subtract #$06 to get to correct super-tile to draw based on aim dir + bcs @draw_supertile ; draw super-tile immediately if no underflow occurred + adc #$0c ; underflow, add #$0c to wrap around back to correct super-tile + ; e.g. aim dir #$03 should result in #$09 + +@draw_supertile: + clc ; clear carry in preparation for addition + adc #$05 ; add #$05, offset to open rotating gun super-tiles (level_xx_nametable_update_supertile_data) + jsr draw_enemy_supertile_a_set_delay ; draw pattern table tile specified in a at enemy position + bcc @set_vars_adv_routine ; branch if successfully drawn supertile + ; to set ENEMY_VAR_2, animation delay, and advance routine if rotating gun aiming at player + plp ; restore processor flags + +@exit: + rts + +; if rotating gun aiming at player (carry flag set when enemy aiming at player from aim_var_1_for_quadrant_aim_dir_00) +; set vars and advance routine to rotating_gun_routine_04 to fire at player +@set_vars_adv_routine: + plp ; restore processor flags + bcc @exit ; exit if not aiming at player + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + and #$03 ; keep bits .... ..xx + tay + lda rotating_gun_bullets_per_attack_tbl,y ; load bullets per attack + sta ENEMY_VAR_2,x ; store bullets per attack in ENEMY_VAR_2 + lda #$08 ; a = #$08 + jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to #$08 go to rotating_gun_routine_04 + +; table for rotating gun bullets per attack (from attributes) (#$4 bytes) +rotating_gun_bullets_per_attack_tbl: + .byte $01,$02,$03,$03 + +; whether or not to disable/shut down the rotating gun +; rotating gun shuts down when scrolled to the left 10% of the screen (horizontal level) +; rotating gun shuts down when scrolled down to the bottom 20% of the screen (vertical level) +rotating_gun_should_disable: + jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll + lda #$18 ; a = #$18 + ldy #$c8 ; y = #$c8 + jmp set_carry_if_past_trigger_point ; set carry if enemy has crossed #$18 X position for horizontal levels, and #$c8 for vertical levels + +; rotating gun - pointer 5 +; fire desired number of bullets at player, once complete go back to rotating_gun_routine_03 +rotating_gun_routine_04: + jsr rotating_gun_should_disable ; determine if almost scrolled off screen + bcs rotating_gun_disable ; if out of range, shut down by moving to rotating_gun_routine_05 + dec ENEMY_ANIMATION_DELAY,x ; decrement animation delay + bne @exit ; exit if animation delay hasn't elapsed + ldy ENEMY_VAR_1,x ; load current aim direction. [#$00-#$0b] #$00 when facing right incrementing clockwise + lda rotating_gun_bullet_y_offset_tbl,y ; load the bullet y offset from rotating gun based on aim direction + clc ; clear carry in preparation for addition + adc ENEMY_Y_POS,x ; add bullet offset to enemy position + sta $08 ; store bullet generation y position + lda rotating_gun_bullet_x_offset_tbl,y ; load the bullet x offset from rotating gun based on aim direction + clc ; clear carry in preparation for addition + adc ENEMY_X_POS,x ; add to enemy x position on screen + sta $09 ; store bullet generation x position + tya ; set bullet type + ldy #$04 ; y = #$04 rotating gun bullet speed + jsr bullet_generation ; create enemy bullet (ENEMY_TYPE #$02) of type a with speed y at ($09, $08) + lda #$10 ; a = #$10 delay between bullets + sta ENEMY_ANIMATION_DELAY,x ; set delay between bullets + dec ENEMY_VAR_2,x ; created bullet, decrement number of bullets to fire + bne @exit ; exit if still more bullets to fire + ldy PLAYER_WEAPON_STRENGTH ; fired all bullets, load player weapon strength + lda rotating_gun_animation_delay_tbl,y ; load animation delay based on weapon strength + sta ENEMY_ANIMATION_DELAY,x ; store enemy animation delay + lda #$04 ; a = #$04 + jmp set_enemy_routine_to_a ; set enemy routine index to rotating_gun_routine_03 + +@exit: + rts + +; table for rotating gun delays while rotating based on player weapon strength (#$4 bytes) +; the stronger the weapon the shorter the delay +rotating_gun_rotation_delay_tbl: + .byte $30,$28,$20,$18 + +; table for rotating gun delays after attack (#$4 bytes) +rotating_gun_animation_delay_tbl: + .byte $80,$60,$40,$30 + +; table for rotating gun bullet initial y offset positions (#$f bytes) +rotating_gun_bullet_y_offset_tbl: + .byte $00,$07,$0c + +rotating_gun_bullet_x_offset_tbl: + .byte $0d,$0c,$07 + .byte $00,$f9,$f4 + .byte $f3,$f4,$f9 + .byte $00,$07,$0c + +; rotating gun - pointer 6 +; shuts down rotating gun, gun retracts and no longer fires, removes enemy +rotating_gun_routine_05: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda #$03 ; a = #$03 (rotating gun closed super-tile) + jsr draw_enemy_supertile_a ; draw super-tile a (level_xx_nametable_update_supertile_data offset) at position (ENEMY_X_POS, ENEMY_Y_POS) + bcs rotating_gun_exit_01 ; exit if unable to draw super-tile + jmp remove_enemy ; remove rotating gun + +; rotating gun - pointer 7 +; enemy destroyed routine (see enemy_destroyed_routine_01) +rotating_gun_routine_06: + jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll + lda #$16 ; a = #$16 (red turret and rotating gun rock background, see level_xx_nametable_update_supertile_data) + jsr draw_enemy_supertile_a ; draw super-tile a (offset into level nametable update table) at position (ENEMY_X_POS, ENEMY_Y_POS) + bcs rotating_gun_exit_01 ; exit if unable to draw super-tile + jmp advance_enemy_routine ; advance to enemy_routine_init_explosion + +; sets carry once enemy is far enough on the screen +; for horizontal levels, triggers once enemy is to the left of specified x location +; for vertical levels, triggers once enemy is below the specified y location +; also used to see if player past enemy for red turrets +; input +; * a - x position on screen to trigger enemy (for horizontal levels) +; * y - y position on screen where enemy is activated (for vertical levels) +set_carry_if_past_trigger_point: + sta $08 + sty $09 + lda LEVEL_SCROLLING_TYPE ; 0 = horizontal, indoor/base; 1 = vertical + bne @vertical_level ; branch for vertical level + lda ENEMY_X_POS,x ; horizontal or indoor/base level, load enemy x position on screen + cmp $08 ; compare enemy position to X trigger position + bcs @exit_carry_clear ; scroll has not put enemy at trigger position, exit with carry clear + bcc @exit_carry_set ; enemy crossed trigger position, exit with carry set + +@vertical_level: + lda ENEMY_Y_POS,x ; load enemy Y position on screen + cmp $09 ; compare enemy position to Y trigger position + bcc @exit_carry_clear ; scroll has not put enemy at trigger position, exit with carry clear + +@exit_carry_set: + sec ; set the carry flag, enemy close to player + rts + +@exit_carry_clear: + clc ; clear the carry flag, enemy too far away from player + +rotating_gun_exit_01: + rts + +; pointer table for red turret (#$9 * #$2 bytes = #$12 bytes) +red_turret_routine_ptr_tbl: + .addr red_turret_routine_00 ; CPU address $84ca - initialize ENEMY_VAR_1 (aim direction), advance to red_turret_routine_01 + .addr red_turret_routine_01 ; CPU address $84d4 - wait for player to get close, then advance routine + .addr red_turret_routine_02 ; CPU address $84e8 + .addr red_turret_routine_03 ; CPU address $8537 + .addr red_turret_routine_04 ; CPU address $85d1 + .addr red_turret_routine_05 ; CPU address $85e1 + .addr enemy_routine_init_explosion ; CPU address $e74b + .addr enemy_routine_explosion ; CPU address $e7b0 + .addr enemy_routine_remove_enemy ; CPU address $e806 + +; red turret - pointer 1 +; initialize ENEMY_VAR_1 (aim direction), advance to red_turret_routine_01 +; identical code to rotating_gun_routine_00 +red_turret_routine_00: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda #$06 ; a = #$06 + sta ENEMY_VAR_1,x ; set aim direction to face left + bne red_turret_adv_routine ; always branch, advance enemy routine + +; red turret - pointer 2 +; wait for player to get close, then advance routine +red_turret_routine_01: + lda #$f0 ; a = #$f0 (red turret emerge at this x offset) + ldy #$40 ; y = #$40 + jsr set_carry_if_past_trigger_point ; set carry if enemy has crossed #$f0 X position for horizontal levels, and #$40 for vertical levels + bcc red_turret_add_scroll_to_pos ; player not close, do nothing + lda #$01 ; player close, set a = #$01 (delay before emerging) + +red_turret_set_delay_adv_routine: + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + +red_turret_adv_routine: + jsr advance_enemy_routine ; advance to next routine + +red_turret_add_scroll_to_pos: + jmp add_scroll_to_enemy_pos ; add scrolling to enemy position + +; red turret - pointer 3 +red_turret_routine_02: + jsr red_turret_load_supertile ; load the appropriate super-tile if the animation delay has elapsed + bcs red_turret_add_scroll_to_pos ; exit if enemy animation delay hasn't elapsed, no super-tile to update + inc ENEMY_FRAME,x ; increment the enemy animation frame number + lda ENEMY_FRAME,x ; load the enemy animation frame number + cmp #$04 ; compare to the last frame + bcc red_turret_add_scroll_to_pos ; if not the last frame, then add scroll and exit + lda #$02 ; a = #$02 + sta ENEMY_VAR_2,x + lda #$28 ; a = #$28 (delay before first attack) + ldy GAME_COMPLETION_COUNT ; load the number of times the game has been completed + beq @set_attack_delay_enable_collision ; keep #$28 attack delay if game hasn't been beaten + lda #$08 ; lower attack delay to #$08 when game has been beaten at least once + +@set_attack_delay_enable_collision: + sta ENEMY_ATTACK_DELAY,x + jsr enable_bullet_enemy_collision ; allow bullets to collide (and stop) upon colliding with red turret + lda #$10 ; set animation delay to #$10 + bne red_turret_set_delay_adv_routine ; always branch, set animation delay to #$10 and advance routine + +red_turret_load_supertile: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne @set_carry_exit ; set carry and exit if animation delay hasn't elapsed + lda #$04 ; a = #$04 + sta ENEMY_ANIMATION_DELAY,x ; delay between frames when emerging + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + lsr ; move bit 0 to carry flag, this bit specifies which background to load + lda ENEMY_FRAME,x ; load enemy animation frame number + bcc @load_supertile ; if bit 0 was 0, then rocky background, don't adjust super-tile offset + adc #$03 + +@load_supertile: + tay + lda red_turret_supertile_1_tbl,y + jmp draw_enemy_supertile_a_set_delay ; draw pattern table tile specified in a at enemy position + +@set_carry_exit: + sec ; set carry flag + rts + +; table for red turret super-tile codes (#$b bytes) +; see level_1_nametable_update_supertile_data +; see level_3_nametable_update_supertile_data +; #$11 - red turret facing left +; #$12 - red turret facing up left +; #$13 - red turret facing up up left (almost straight up) +; #$14 - red turret 1/2 rising from ground rocky +; #$15 - red turret 1/2 rising from ground metal/waterfall background +; #$16 - just rocky background no red turret +; #$17 - just secondary background no red turret (mostly metal background for level 1, waterfall for level 3) +; #$18 - red turret 3/4 rising from ground black background +red_turret_supertile_1_tbl: + .byte $16,$14 + +; see level_1_nametable_update_supertile_data +; see level_3_nametable_update_supertile_data +red_turret_supertile_2_tbl: + .byte $18,$11,$17,$15,$18,$11,$11,$12,$13 + +; red turret - pointer 4 +red_turret_routine_03: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda #$30 ; a = #$30 (x offset to return to ground) + ldy #$c0 ; y = #$c0 + jsr set_carry_if_past_trigger_point ; set carry if enemy has crossed #$30 X position for horizontal levels (left side of screen) + ; and #$c0 for the vertical level (bottom of screen) + bcc @gen_bullet_if_appropriate ; branch if red turret should still be active (not scrolled to left/bottom of screen yet) + lda #$02 ; red turret on far left (horizontal) or far bottom (vertical), disable + sta ENEMY_FRAME,x ; initial frame code when returning to ground (#$02) + jsr disable_enemy_collision ; prevent player enemy collision check and allow bullets to pass through enemy + lda #$01 ; a = #$01 + jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to #401 advance to red_turret_routine_04 + +@gen_bullet_if_appropriate: + jsr red_turret_find_target_player ; find player to target, set to y + jsr check_red_turret_firing_range ; see if the player is above (or equal) and to the left of the red turret + tya ; transfer closest player to a + bcs @continue + eor #$01 ; flip bits .... ...x + +@continue: + sta $0a ; store player index in $0a + jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position + jsr get_rotate_00 ; get enemy aim direction and rotation direction using quadrant_aim_dir_00 + sta $08 ; set rotate direction in $08 (#$00 clockwise, #$01 counterclockwise, #$80 no rotation) + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne @dec_attack_delay_fire_bullet + lda #$10 ; a = #$10 + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + ldy ENEMY_VAR_1,x + lda $08 + bmi @dec_attack_delay_fire_bullet + bne @continue2 + cpy #$08 + beq @dec_attack_delay_fire_bullet + inc ENEMY_VAR_1,x + bne @set_supertile_fire + +@continue2: + cpy #$06 + beq @dec_attack_delay_fire_bullet + dec ENEMY_VAR_1,x + +@set_supertile_fire: + ldy ENEMY_VAR_1,x + lda red_turret_supertile_2_tbl,y + jsr draw_enemy_supertile_a_set_delay ; draw pattern table tile specified in a at enemy position + +@dec_attack_delay_fire_bullet: + dec ENEMY_ATTACK_DELAY,x ; decrement attack delay + bne red_turret_exit ; exit if attack delay hasn't elapsed + ldy #$10 ; y = #$10 (delay between attacks) + dec ENEMY_VAR_2,x ; delay between consecutive bullets + bpl @generate_bullet ; fire bullet + lda #$02 ; a = #$02 + sta ENEMY_VAR_2,x ; consecutive bullets (#$02 = 3 bullets) + ldy #$50 ; y = #$50 (delay between attacks) + +@generate_bullet: + tya + sta ENEMY_ATTACK_DELAY,x ; set delay between attacks + ldy $0f ; load the closest player in y + jsr check_red_turret_firing_range ; see if the player is above (or equal) and to the left of the red turret + bcc red_turret_exit ; exit if player not in firing range of red turret + ldy ENEMY_VAR_1,x + lda @bullet_offset_tbl_base,y ; load y bullet generation point y offset + clc ; clear carry in preparation for addition + adc ENEMY_Y_POS,x ; add offset to red turret's y position + sta $08 ; store result in $08 for bullet_generation call + lda red_turret_bullet_offset_tbl-3,y ; silly to have offsets like this !(WHY?) + ; load bullet generation point x offset + clc ; clear carry in preparation for addition + adc ENEMY_X_POS,x ; add offset to red turret's x position + sta $09 ; store in $09 for bullet_generation call + tya + +; !(WHY?) weird to have this as a label point when only used for reading offset data in red_turret_bullet_offset_tbl +; #$06 bytes before table data +@bullet_offset_tbl_base: + ldy #$05 ; red turret bullet speed + jmp bullet_generation ; create enemy bullet (ENEMY_TYPE #$02) of type a with speed y at ($09, $08) + +red_turret_exit: + rts + +; table for red turret bullet initial offsets (#$6 bytes) +red_turret_bullet_offset_tbl: + .byte $00,$f8,$f0 ; x offset #$0 , -#$8 , -#$10 + .byte $f2,$f2,$f8 ; y offset -#$e , -#$e , -#$8 + +; red turret - pointer 5 +red_turret_routine_04: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + jsr red_turret_load_supertile + bcs red_turret_exit + dec ENEMY_FRAME,x ; decrement enemy animation frame number + bpl red_turret_exit + jmp remove_enemy ; from bank 7 + +; red turret - pointer 6 +red_turret_routine_05: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + lsr ; move bit 0 to carry + lda #$16 ; a = #$16 (rocky background) + bcc @draw_supertile_adv_routine ; branch if ENEMY_ATTRIBUTES specifies red turret has rocky background (bit 0 - 0) + lda #$17 ; a = #$17 (metal or waterfall background) + +@draw_supertile_adv_routine: + jsr draw_enemy_supertile_a ; update red turret super-tile + bcs red_turret_exit ; exit if unable to update super-tile, will try again later + jmp advance_enemy_routine ; updated super-tile, advance to next routine + +; red turrets only fire left and up left, checks to see if player is in firing range +; i.e. the player is above (or equal) and to the left of the red turret +; output +; * carry - set when red turret below player and to the right +check_red_turret_firing_range: + lda ENEMY_Y_POS,x ; load enemy y position on screen + clc ; clear carry in preparation for addition + adc #$20 ; add #$20 to allow firing if player and turret are at same height + cmp SPRITE_Y_POS,y ; player y position on screen + bcc @exit ; red turret above player, exit with carry clear + lda ENEMY_X_POS,x ; load enemy x position on screen + cmp SPRITE_X_POS,y ; set carry if red turret to right of player + +@exit: + rts + +; pointer table for running man (#$6 * #$2 = #$16 bytes) +soldier_routine_ptr_tbl: + .addr soldier_routine_00 ; CPU address $861e - slightly offset y position, set initial animation delay based on ENEMY_ATTRIBUTES + .addr soldier_routine_01 ; CPU address $8665 - set velocities, enable collision + .addr soldier_routine_02 ; CPU address $86af - soldier animation routine: walk, if jumping try to find find landing + .addr soldier_routine_03 ; CPU address $8803 - try and fire bullet + .addr soldier_routine_04 ; CPU address $88c3 - soldier hit, begin destroying soldier + .addr soldier_routine_05 ; CPU address $8900 - soldier hit, apply negative gravity + .addr enemy_routine_init_explosion ; CPU address $e74b + .addr enemy_routine_explosion ; CPU address $e7b0 + .addr enemy_routine_remove_enemy ; CPU address $e806 + .addr soldier_routine_09 ; CPU address $888c - soldier landing in water routine + .addr soldier_routine_0a ; CPU address $88a1 - continue splash animation and begin removing soldier + +; running man - pointer 1 +; slightly offset y position, set initial animation delay based on ENEMY_ATTRIBUTES +soldier_routine_00: + jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll + jsr add_4_to_enemy_y_pos ; adjust soldier down slightly so walks on ground + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + lsr ; shift right 4 times to load high byte + lsr + lsr + lsr + and #$03 ; keep bits 0,1,2 + tay ; transfer offset to y + lda soldier_initial_anim_delay_tbl,y ; load enemy animation delay + jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a + ; advance enemy routine + +; table for soldier initial animation delay (#$4 bytes) +soldier_initial_anim_delay_tbl: + .byte $01,$10,$20,$30 + +; set soldier x velocity based on ENEMY_VAR_2 (#$00 left, #$01 right) (see soldier_x_vel_tbl) +; stop y velocity +soldier_stop_y_set_x_velocity: + jsr soldier_set_x_velocity ; set soldier x velocity based on ENEMY_VAR_2 (#$00 left, #$01 right) + jmp set_enemy_y_velocity_to_0 ; stop any y velocity + +; set soldier x velocity based on ENEMY_VAR_2 index and level scrolling type +soldier_set_x_velocity: + ldy #$00 ; y = #$00 + lda LEVEL_SCROLLING_TYPE ; 0 = horizontal, indoor/base; 1 = vertical + beq @continue ; horizontal scrolling, continue + ldy #$04 ; vertical level, add #$04 to offset, y = #$04 + +@continue: + sty $08 ; set offset into $08 + lda ENEMY_VAR_2,x ; load soldier x direction (#$00 left, #$01 right) + asl ; multiply by #$02, each entry is #$02 bytes + clc ; clear carry in preparation for addition + adc $08 ; add result to base offset (#$00 or #$04) + tay ; transfer offset to y + lda soldier_x_vel_tbl,y ; load x velocity fractional byte + sta ENEMY_X_VELOCITY_FRACT,x ; set x velocity fractional byte + lda soldier_x_vel_tbl+1,y ; load x velocity fast byte + sta ENEMY_X_VELOCITY_FAST,x ; set x velocity fast byte + +soldier_routine_exit: + rts + +; table for running man (#$8 bytes) +; first pair of bytes per level scroll is moving left, second pair is moving right +; byte 0 - x fractional velocity +; byte 1 - x fast velocity +soldier_x_vel_tbl: + .byte $00,$ff ; (-1.00) (horizontal scrolling) + .byte $40,$01 ; ( 1.25) (horizontal scrolling) + .byte $00,$ff ; (-1.00) (vertical level) + .byte $00,$01 ; ( 1.00) (vertical level) + +; running man - pointer 2 +; set velocities, enable collision +soldier_routine_01: + lda LEVEL_SCROLLING_TYPE ; 0 = horizontal, indoor/base; 1 = vertical + beq @horizontal_level ; branch for horizontal/indoor levels + jsr add_scroll_to_enemy_pos ; vertical level, adjust enemy location based on scroll + jmp @dec_delay_enable_set_vel ; decrement animation delay and exit + +@horizontal_level: + lda FRAME_SCROLL ; how much to scroll the screen (#00 - no scroll) + beq @dec_delay_enable_set_vel + lda ENEMY_ATTRIBUTES,x ; scrolling, load soldier enemy attributes + and #$01 ; keep bit 0 specifying run direction + beq @continue ; branch if running left + and FRAME_COUNTER ; running right, load frame counter + lsr ; push bit 0 to carry + bcs @dec_delay_enable_set_vel ; if odd frame, decrement animation delay + bcc soldier_routine_exit ; even frame, exit without decrementing animation delay + +@continue: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + beq @enable_set_vel + +@dec_delay_enable_set_vel: + dec ENEMY_ANIMATION_DELAY,x + bne soldier_routine_exit + +@enable_set_vel: + ldy #$10 ; y = #$10 + jsr add_y_to_y_pos_get_bg_collision ; add #$10 to enemy y position and gets bg collision code + bne @enable_collision_set_vel ; branch if collision with ground, water, or solid + jmp remove_enemy ; remove enemy if no collision + ; soldier wasn't placed in an appropriate position, e.g. bridge destroyed + +@enable_collision_set_vel: + jsr enable_enemy_collision ; enable bullet-enemy collision and player-enemy collision checks + lda ENEMY_ATTRIBUTES,x ; load soldier enemy attributes + and #$01 ; keep soldier running direction + sta ENEMY_VAR_2,x ; set running direction in ENEMY_VAR_2 + beq @stop_y_set_x_adv_routine ; if running left, branch + lda #$0a ; running right, set x position to #$0a, a = #$0a + sta ENEMY_X_POS,x ; set enemy x position on screen + +@stop_y_set_x_adv_routine: + jsr soldier_stop_y_set_x_velocity ; set soldier x velocity based on direction (ENEMY_VAR_2); set y velocity to #$00 + lda #$10 ; a = #$10 + jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a; advance enemy routine + +; Running Man - Pointer 3 +; soldier animation routine: walk, if jumping try to find find landing +soldier_routine_02: + lda ENEMY_VAR_3,x ; see if soldier is jumping or not + beq @continue ; branch if not jumping + lda #$0a ; soldier jumping, set a = #$0a + sta ENEMY_FRAME,x ; set enemy animation frame number to jumping frame + lda ENEMY_Y_VELOCITY_FAST,x ; load y velocity + bmi @no_landing + ldy #$10 ; y = #$10 + jsr add_y_to_y_pos_get_bg_collision ; add #$10 to enemy y position and gets bg collision code + bmi @floor_solid_landing ; branch if landed on solid object + bcc @land_in_water_or_no_landing ; branch if not a collision with the floor, i.e. empty, or water collision + +; floor or solid collision +@floor_solid_landing: + lda #$00 ; a = #$00 + sta ENEMY_VAR_3,x ; clear flag specifying soldier is jumping + sta ENEMY_FRAME,x ; set enemy animation frame number + jsr add_4_to_enemy_y_pos + jsr soldier_stop_y_set_x_velocity ; set soldier x velocity based on direction (ENEMY_VAR_2); set y velocity to #$00 + jmp soldier_apply_vel_check_solid_collision + +@land_in_water_or_no_landing: + cmp #$02 ; see if water collision code (#$02) + bne @no_landing ; branch if not a water collision, i.e. floor collision or empty collision + lda #$0a ; soldier landed in water, change routine, a = #$0a + jsr set_enemy_routine_to_a ; set enemy routine index to soldier_routine_09 + +@no_landing: + jsr add_10_to_enemy_y_fract_vel ; add #$10 to y fractional velocity (.06 faster) + jmp soldier_apply_vel_check_solid_collision + +; soldier_routine_02 - soldier not jumping +@continue: + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + and #$0c ; keep bits .... xx.. + beq @continue_walk_routine ; soldier doesn't fire, continue walking routine + lda ENEMY_ATTACK_FLAG ; see if enemies should attack + beq @continue_walk_routine ; enemies shouldn't attack, continue walking routine + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne @continue_walk_routine ; delay timer hasn't elapsed, continue walking routine + lda #$80 ; a = #$80 + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + lda #$08 ; a = #$08 + sta ENEMY_ATTACK_DELAY,x ; set enemy attack delay to #$08 + jsr get_soldier_num_bullets ; get random number of bullets to fire (influenced by PLAYER_WEAPON_STRENGTH) + sta ENEMY_VAR_3,x ; set number of bullets to fire + jsr advance_enemy_routine ; advance to soldier_routine_03 + jmp set_soldier_sprite_update_pos ; set soldier sprite and apply velocities to position + +; soldier isn't firing +@continue_walk_routine: + inc ENEMY_VAR_A,x ; increment ENEMY_FRAME update timer + lda ENEMY_VAR_A,x ; load ENEMY_FRAME update timer + and #$07 ; keep bits 0, 1, and 2 + bne @soldier_move ; continue if #$08 frames haven't elapsed + inc ENEMY_FRAME,x ; increment enemy animation frame number + lda ENEMY_FRAME,x ; load enemy animation frame number + cmp #$06 ; compare to last frame of soldier animation sequence + bcc @soldier_move ; continue if not past last frame + lda #$00 ; animated past last frame, go back to #$0th frame + sta ENEMY_FRAME,x ; set enemy animation frame number to first frame + +@soldier_move: + ldy #$10 ; increment y position by #$10 + lda ENEMY_X_VELOCITY_FAST,x ; load x fast velocity + jsr add_a_y_to_enemy_pos_get_bg_collision ; add a to X position and y to Y position; get bg collision code + bmi soldier_apply_vel_check_solid_collision ; branch if enemy collided with solid bg object + bcs soldier_apply_vel_check_solid_collision ; branch if collision with floor (#$01) + lda ENEMY_VAR_4,x ; no collision, or collision with water + cmp #$02 ; compare the number of times the soldier has already turned around + ; if #$02, have soldier jump off ledge + bcs @soldier_fall_off_ledge ; branch if soldier has decided to jump off ledge + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + and #$02 ; see if soldier should turn around at edge + beq @soldier_fall_off_ledge ; branch if soldier should walk off edge + jsr soldier_change_direction ; soldier should turn around, change direction + jmp soldier_apply_vel_check_solid_collision ; apply velocity + +@soldier_fall_off_ledge: + inc ENEMY_VAR_3,x ; set that the soldier is jumping + jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01) + lda SPRITE_Y_POS,y ; load the y position of the closest player + sec ; set carry flag in preparation for subtraction + sbc ENEMY_Y_POS,x ; subtract closest player y position from the enemy y position on screen + ldy #$04 ; set default soldier_vel_index_tbl offset (higher probability of larger y jump) + bcs @cmp_player_dist ; branch if no underflow occurred + eor #$ff ; underflow occurred, flip all bits and add #$01 to get absolute value + adc #$01 + ldy #$00 ; set soldier_vel_index_tbl (higher probability of larger x jump) + +@cmp_player_dist: + cmp #$10 ; compare closest player and enemy y distance + bcs @soldier_set_jump_vel ; branch if enemy is far away from player vertically to keep the configured y + ldy #$00 ; if enemy is close, give higher probability of shorter y jump + +@soldier_set_jump_vel: + sty $08 ; store current soldier_vel_index_tbl offset into $08 + lda RANDOM_NUM ; load random number + and #$03 ; random number between #$00 and #$03 + clc ; clear carry in preparation for addition + adc $08 ; add random number between #$00 and #$03 to current soldier_vel_index_tbl offset (#$00 or #$04) + tay ; transfer offset to y + lda soldier_vel_index_tbl,y ; load appropriate jump velocity configuration + tay ; transfer jump velocity configuration to y + lda soldier_velocity_tbl,y ; load jump y fractional velocity + sta ENEMY_Y_VELOCITY_FRACT,x ; set jump y fractional velocity + lda soldier_velocity_tbl+1,y ; load jump y fast velocity + sta ENEMY_Y_VELOCITY_FAST,x ; se jump y fast velocity + lda soldier_velocity_tbl+2,y ; load jump x fractional velocity + sta ENEMY_X_VELOCITY_FRACT,x ; set jump x fractional velocity + lda soldier_velocity_tbl+3,y ; load jump x fast velocity + sta ENEMY_X_VELOCITY_FAST,x ; set jump x fast velocity + lda ENEMY_VAR_2,x ; load enemy running direction + beq @set_sprite_update_pos ; branch if running left + jsr reverse_enemy_x_direction ; reverse enemy's x velocities if running right + ; soldier_velocity_tbl had values assuming running left + +@set_sprite_update_pos: + jmp set_soldier_sprite_update_pos ; set soldier sprite and apply velocities to position + +soldier_apply_vel_check_solid_collision: + jsr check_enemy_collision_solid_bg ; see if soldier is colliding with solid object + bpl @continue ; continue if solid collision code + lda #$07 ; no solid collision, go to soldier_routine_09, a = #$07 + jmp set_enemy_routine_to_a ; set enemy routine index soldier_routine_09 + +@continue: + lda ENEMY_VAR_4,x + cmp #$02 + bcs set_soldier_sprite_update_pos ; set soldier sprite and apply velocities to position + lda #$f8 ; set amount to adjust x position to -8 + ldy ENEMY_VAR_2,x ; load current enemy direction (#$00 = left, #$01 = right) + beq @check_collision_update_x_pos ; continue to add -8 to x position if current direction is left + lda #$08 ; direction is right, set x amount to add to #$08 + +; update soldier location, if on screen see if running into a solid object, if so, turn soldier around +; input +; * a - amount to add to x position +@check_collision_update_x_pos: + clc ; clear carry in preparation for addition + adc ENEMY_X_POS,x ; add to enemy x position on screen + cmp #$f0 ; see if off screen to the right + bcs set_soldier_sprite_update_pos ; branch if far right to set soldier sprite and apply velocities to position + cmp #$10 ; see if off screen to the left + bcc set_soldier_sprite_update_pos ; branch if far left to set soldier sprite and apply velocities to position + ldy ENEMY_Y_POS,x ; on screen, load enemy y position on screen + jsr get_bg_collision_far ; determine player background collision code at position (a,y) + bpl set_soldier_sprite_update_pos ; branch if solid collision to set soldier sprite and apply velocities to position + jsr soldier_change_direction ; solid collision, turn soldier around + +set_soldier_sprite_update_pos: + jsr set_soldier_sprite ; set soldier sprite code based on ENEMY_FRAME + jmp update_enemy_pos ; apply velocities and scrolling adjust + +soldier_change_direction: + inc ENEMY_VAR_4,x + lda ENEMY_VAR_2,x ; load current enemy x direction + eor #$01 ; swap offset to turn the soldier around (change soldier x direction) + ; #$00 -> #$01, or #$01 -> #$0 + sta ENEMY_VAR_2,x ; update soldier direction + jmp soldier_set_x_velocity ; set soldier x velocity based on ENEMY_VAR_2 index (#$00 left, #$01 right) + +; randomly (based on PLAYER_WEAPON_STRENGTH) determine the number of bullets to fire and store in a +; output +; * a - number of bullets to fire, #$00 or #$02 +get_soldier_num_bullets: + lda PLAYER_WEAPON_STRENGTH ; load player weapon strength + and #$02 ; keep bit 1 (FSL) + asl ; shift left + sta $08 ; store value in $08 + lda RANDOM_NUM ; load random number + and #$03 ; between #$00 and #$03 + adc $08 ; add value between #$00 and #$04 + tay ; transfer to y for offset + lda soldier_num_bullets_tbl,y ; load ENEMY_VAR_3 + rts + +; table for soldier running direction (#$8 bytes) +; PLAYER_WEAPON_STRENGTH increases chances of soldiers firing twice +soldier_num_bullets_tbl: + .byte $01,$01,$02,$01,$02,$01,$02,$02 + +; running man possible jumping codes (#$8 bytes) +; offset into table at soldier_velocity_tbl below +; higher probability of +soldier_vel_index_tbl: + .byte $00,$00,$04,$00,$04,$00,$04,$04 + +; table for running man jumping velocities (#$8 byte) +; grouped into 2 sections +; first #$04 bytes is a larger y jump, but a shorter x jump +; second #$04 bytes is a shorter y jump, but a farther x jump +soldier_velocity_tbl: + .byte $00,$fe ; jumping y velocity + .byte $48,$ff ; jumping x velocity + .byte $00,$ff ; jumping y velocity + .byte $60,$ff ; jumping x velocity + +; running man - pointer 4 - try and fire bullet +soldier_routine_03: + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + and #$0c ; keep bits .... xx.. + cmp #$05 ; see if should fire bullet + ldy #$00 ; set y = #$00 for standing bullet y pos offset index (soldier_bullet_y_offset offset) + lda #$06 ; ENEMY_FRAME #$06 (see soldier_sprite_codes) (sprite_40 - soldier shooting) + bcc @continue ; branch if soldier should shoot while standing + lda #$1b ; ENEMY_ATTRIBUTES bit 3 is set, enemy crouches to shoot, set a = #$1b + sta ENEMY_SCORE_COLLISION,x ; update collision code for crouching soldier (score byte (high byte) is already #$01) + ldy #$02 ; set y = #$02 for crouching bullet y pos offset index (soldier_bullet_y_offset offset) + lda #$07 ; ENEMY_FRAME #$07 (see soldier_sprite_codes) (sprite_26 - soldier crouching shooting) + +@continue: + sta ENEMY_FRAME,x ; set the enemy animation frame (either shooting while standing, or shooting while crouching) + dec ENEMY_ATTACK_DELAY,x ; decrement enemy attack delay + bne set_soldier_sprite_add_scroll_01 ; attack delay hasn't elapsed, update sprite and exit + dec ENEMY_VAR_3,x ; decrement number of bullets to fire + bmi soldier_fired_all_bullets ; branch if fired all bullets to reset and go to soldier_routine_02 + lda #$10 ; another bullet to fire, a = #$10 + sta ENEMY_ATTACK_DELAY,x ; set attack delay to #$10 + lda ENEMY_VAR_2,x ; load soldier running direction (#$00 left, #$01 right) + beq @set_bullet_pos_and_fire ; branch if running left + iny ; running right, increment soldier_bullet_y_offset offset + +; determine where bullet should generate based on enemy pos and fire if on screen +@set_bullet_pos_and_fire: + lda ENEMY_Y_POS,x ; load enemy y position on screen + clc ; clear carry in preparation for addition + adc soldier_bullet_y_offset,y ; add bullet y position offset to enemy position + sta $08 ; set bullet y position + lda soldier_bullet_x_offset,y ; load bullet x position offset + clc + bmi @negative_x_offset ; branch if x position offset of bullet is negative + adc ENEMY_X_POS,x ; bullet x position offset positive, add to enemy x position on screen + bcs set_soldier_sprite_add_scroll_01 ; branch to update sprite and exit if soldier's bullet would be offscreen to the right + bcc @soldier_fire_bullet + +@negative_x_offset: + adc ENEMY_X_POS,x ; add enemy x position on screen + bcc set_soldier_sprite_add_scroll_01 ; branch to update sprite and exit if soldier's bullet would be offscreen + cmp #$08 + bcc set_soldier_sprite_add_scroll_01 ; branch to update sprite and exit if soldier's bullet would be offscreen to the left + +@soldier_fire_bullet: + sta $09 + ldy ENEMY_VAR_2,x ; set y to walking direction (#$00 - left, #$01 - right) + lda soldier_bullet_type_tbl,y ; load bullet type + ldy #$06 ; y = #$06 (regular bullet firing left (angle #$0c) + jsr bullet_generation ; create enemy bullet (ENEMY_TYPE #$02) of type a (and angle) with speed y at ($09, $08) + bne set_soldier_sprite_add_scroll_01 ; branch if unable to create bullet + lda #$06 ; a = #$06 + sta ENEMY_VAR_1,x ; set gun recoil timer + +set_soldier_sprite_add_scroll_01: + jsr set_soldier_sprite ; set soldier sprite code based on ENEMY_FRAME + jmp add_scroll_to_enemy_pos ; adjust enemy location based on scroll + +; soldier has fired all bullets, stand back up if crouching, +; set enemy routine back soldier animation routine (soldier_routine_02) +soldier_fired_all_bullets: + lda #$10 ; a = #$10 + sta ENEMY_SCORE_COLLISION,x ; reset collision box in case soldier was crouched + lda #$00 ; a = #$00 + sta ENEMY_VAR_3,x ; reset number of bullets to fire to #$00 + sta ENEMY_FRAME,x ; reset enemy animation frame (see soldier_sprite_codes) (sprite_3b - soldier running frame #$00) + jsr set_soldier_sprite ; set soldier sprite code based on ENEMY_FRAME + jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll + lda #$03 ; a = #$03 + jmp set_enemy_routine_to_a ; set enemy routine index to soldier_routine_02 + +; table for specifying y offset from soldier position of generated bullets for soldier (#$4 bytes) +; first two bytes are while soldier is standing, second 2 bytes are for crouching and firing +; byte 0 - running left +; byte 1 - running right +soldier_bullet_y_offset: + .byte $f7,$f7 ; -9, -9 - standing and firing + .byte $0a,$0a ; 10, 10 - crouching and firing + +; table for specifying x offset from soldier position of generated bullets for soldier (#$4 bytes) +; first two bytes are while soldier is standing, second 2 bytes are for crouching and firing +; byte 0 - running left +; byte 1 - running right +soldier_bullet_x_offset: + .byte $f0,$10 ; -16, 10 standing and firing + .byte $f0,$10 ; -16, 10 crouching and firing + +; table for the bullet type and angle to generate for a soldier (#$2 bytes) +; byte 0 - soldier walking left (#$0c firing direction) +; byte 1 - soldier walking right +soldier_bullet_type_tbl: + .byte $06,$00 + +; running man - pointer a +; soldier landing in water routine +soldier_routine_09: + lda #$08 ; ENEMY_FRAME #$08 (see soldier_sprite_codes) (sprite_73 - water splash) + sta ENEMY_FRAME,x ; set enemy animation frame number to be a water splash, soldier is landing in water + lda #$10 ; a = #$10 + jsr soldier_set_y_pos_sprite_add_scroll ; add #$10 to solider y position to slightly lower his position in the water + jsr set_soldier_sprite ; set soldier sprite code based on ENEMY_FRAME (water splash) + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda #$08 ; a = #$08 + jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY and advance enemy routine to soldier_routine_0a + +; running man - pointer b +; continue splash animation and begin removing soldier +soldier_routine_0a: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne set_soldier_sprite_add_scroll ; continue animation if should still show splash + lda #$08 ; splash animation complete, begin removing soldier + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter to #$08 + inc ENEMY_FRAME,x ; increment enemy animation frame number to #$09 (see soldier_sprite_codes) (sprite_18 - water splash/puddle) + lda ENEMY_FRAME,x ; load enemy animation frame number + cmp #$0a ; compare ENEMY_FRAME to #$0a (water splash sprite) + bcc @continue ; branch if haven't completed showing sprite_18 + jmp remove_enemy ; animation elapsed and sprite_18 has been shown, remove soldier (from bank 7) + +@continue: + lda #$08 ; add #$08 to enemy y position, a = #$08 + +soldier_set_y_pos_sprite_add_scroll: + jsr add_a_to_enemy_y_pos ; add a to y position on screen + +set_soldier_sprite_add_scroll: + jsr set_soldier_sprite ; set soldier sprite code based on ENEMY_FRAME + jmp add_scroll_to_enemy_pos ; add scrolling to enemy position + +; running man - pointer 5 +; soldier hit, begin destroying soldier +soldier_routine_04: + lda #$0b ; a = #$0b + sta ENEMY_FRAME,x ; set enemy animation frame number + jsr set_soldier_sprite ; set soldier sprite code based on ENEMY_FRAME + +; set velocities for soldier being hit to +; -4.5 y velocity +; 6.0 x velocity +init_soldier_hit_vel: + jsr disable_enemy_collision ; prevent player enemy collision check and allow bullets to pass through enemy + lda #$80 ; a = #$80 + sta ENEMY_Y_VELOCITY_FRACT,x ; initial y velocity when enemy fly up (low) + lda #$fc ; a = #$fc + sta ENEMY_Y_VELOCITY_FAST,x ; initial y velocity when enemy fly up (high) + lda #$60 ; a = #$60 + sta ENEMY_X_VELOCITY_FRACT,x ; initial x velocity when enemy fly up (low) + lda #$00 ; a = #$00 + sta ENEMY_X_VELOCITY_FAST,x ; initial x velocity when enemy fly up (high) + lda ENEMY_X_POS,x ; load enemy x position on screen + cmp #$10 ; compare enemy x position to left edge + bcc @stop_x_vel_set_delay_adv_routine ; stop x velocity if on left edge + cmp #$f0 ; compare to right edge + bcc @set_dir_delay_adv_routine ; branch if not near right edge, otherwise, stop x velocity + +@stop_x_vel_set_delay_adv_routine: + jsr set_enemy_x_velocity_to_0 ; set x velocity to zero + +@set_dir_delay_adv_routine: + lda ENEMY_VAR_2,x ; load soldier running direction + beq @set_delay_adv_routine ; branch if running left + jsr reverse_enemy_x_direction ; reverse enemy's x direction to face left + +@set_delay_adv_routine: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda #$10 ; a = #$10 + jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a; advance enemy routine + +; running man - pointer 6 +; soldier hit, apply negative gravity +soldier_routine_05: + jsr set_soldier_sprite ; set soldier sprite code based on ENEMY_FRAME + +; applies gravity to destroyed soldier that is floating up +; removes enemy if off screen, or if animation timer has elapsed +apply_gravity_to_destroyed_soldier: + lda #$30 ; a = #$30 (gravity to slow enemy down as they are flying up) + jsr add_a_to_enemy_y_fract_vel ; add #$30 to enemy y fractional velocity + lda ENEMY_Y_POS,x ; load enemy y position on screen + cmp #$08 ; compare to top of screen + bcc @adv_enemy_routine ; branch if soldier moved off top of screen, move to next routine + jsr update_enemy_pos ; apply velocities and scrolling adjust + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne soldier_routine_05_exit ; exit if animation timer hasn't elapsed + +@adv_enemy_routine: + jmp advance_enemy_routine ; advance to next routine + +; set soldier sprite code based on ENEMY_FRAME +set_soldier_sprite: + ldy ENEMY_FRAME,x ; enemy animation frame number + lda soldier_sprite_codes,y ; load appropriate sprite code + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + lda #$40 ; a = #$40 (running man facing right, flip sprite horizontally) + ldy ENEMY_VAR_2,x ; load running direction + beq @set_sprite_attr ; branch if running left + lda #$00 ; a = #$00 (running man facing right, don't flip sprite) + +@set_sprite_attr: + ldy ENEMY_VAR_1,x ; load gun recoil timer + beq @set_sprite_attr_exit ; exit if not firing weapon + dec ENEMY_VAR_1,x ; decrement gun recoil timer + ora #$08 ; set bits .... x... (gun recoil flag) + +@set_sprite_attr_exit: + sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes + +soldier_routine_05_exit: + rts + +; table for running man sprite codes (#$c bytes) +; sprite_18, sprite_26, sprite_27, sprite_28 +; sprite_3b, sprite_3c, sprite_3d, sprite_3e +; sprite_3f, sprite_40, sprite_73 +soldier_sprite_codes: + .byte $3b,$3c,$3d,$3f,$3c,$3e,$40,$26,$73,$18,$28,$27 + +; pointer table for rifle man (#$9 * #$2 = #$12 bytes) +sniper_routine_ptr_tbl: + .addr sniper_routine_00 ; CPU address $8958 - load variables (ENEMY_ANIMATION_DELAY, ENEMY_FRAME), adjust y pos for crouching sniper + .addr sniper_routine_01 ; CPU address $8982 - cycle crouch animation (if crouching sniper), enable collision (when standing only for crouching snipers) + .addr sniper_routine_02 ; CPU address $89d2 - attack + .addr sniper_routine_03 ; CPU address $8ab3 + .addr sniper_routine_04 ; CPU address $8af1 + .addr sniper_routine_05 ; CPU address $8afc + .addr enemy_routine_init_explosion ; CPU address $e74b from bank 7 + .addr enemy_routine_explosion ; CPU address $e7b0 from bank 7 + .addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7 + +; rifle man - pointer 1 +; load variables (ENEMY_ANIMATION_DELAY, ENEMY_FRAME), adjust y pos for crouching sniper +sniper_routine_00: + ldy ENEMY_ATTRIBUTES,x ; load sniper type (#$00 = standing, #$01 = hiding, #$02 = boss hiding) + lda sniper_animation_delay_tbl,y ; load animation delay based on sniper type + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + lda sniper_frame_tbl,y ; load ENEMY_FRAME (sniper_sprite_xx offset) + sta ENEMY_FRAME,x ; set enemy animation frame number + jsr add_4_to_enemy_y_pos ; adjust sniper slightly down + lda ENEMY_ATTRIBUTES,x ; load sniper type (#$00 = standing, #$01 = hiding, #$02 = boss hiding) + cmp #$01 ; see if sniper type #$01 (crouching sniper) + bne @adv_enemy_routine ; advance routine if standing sniper, or boss screen sniper + lda #$05 ; crouching sniper adjust y position by #$05 + jsr add_a_to_enemy_y_pos ; lower enemy y position on screen + +@adv_enemy_routine: + jmp advance_enemy_routine + +; table for rifle man initial animation delay (#$3 bytes) +; each byte is for each sniper type #$00, #$01, or #$04 +sniper_animation_delay_tbl: + .byte $01,$30,$80 + +; table for rifle man sniper_routine_03 animation delay (#$3 bytes) +; each byte is for each sniper type #$00, #$01, or #$04 +sniper_animation_delay_2_tbl: + .byte $01,$60,$80 + +; table for rifle man initial ENEMY_FRAME (#$3 bytes) +; each byte is for each sniper type #$00, #$01, or #$04 +; offsets into sniper_sprite_xx table +; byte 0 - sprite_43 or sprite_2c (sniper aiming horizontally) +; byte 1 and byte 2 - sprite_44 (sniper crouched behind bush) +sniper_frame_tbl: + .byte $03,$00,$00 + +; rifle man - pointer 2 +; cycle crouch animation (if crouching sniper), enable collision (when standing only for crouching snipers) +sniper_routine_01: + jsr sniper_set_sprite ; set sprite and attributes based on sniper type and firing angle + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne sniper_routine_exit ; exit if animation delay hasn't elapsed + ldy ENEMY_ATTRIBUTES,x ; load sniper type (#$00 = standing, #$01 = hiding, #$02 = boss hiding) + beq @enable_collision_adv_routine ; exit if sniper type #$00 (standing sniper), the following logic is only for crouching snipers + lda #$08 ; crouching or boss screen sniper, set a = #$08 + sta ENEMY_ANIMATION_DELAY,x ; set delay between frames when un-hiding to #$08 + inc ENEMY_FRAME,x ; increment enemy animation frame number (sniper_sprite_xx offset) + lda ENEMY_FRAME,x ; load current enemy animation frame number (sniper_sprite_xx offset) + cmp #$03 ; see if last sprite of animation + bcc sniper_routine_exit ; exit if animation cycle not yet complete + cpy #$01 ; see if ENEMY_FRAME is #$01 (sprite_45 - rifle man behind bush (frame 2)) + bne @continue ; branch if not finished crouch animation + dec ENEMY_FRAME,x ; decrement enemy animation frame number + bne @enable_collision_adv_routine + +@continue: + lda #$f2 ; a = #$f2 (-14) + jsr add_a_to_enemy_y_pos ; add #$f2 (-14) to enemy y position on screen + lda #$01 ; a = #$01 + jsr add_a_to_enemy_x_pos ; add #$01 to enemy x position on screen + +@enable_collision_adv_routine: + jsr enable_enemy_collision ; enable bullet-enemy collision and player-enemy collision checks + lda #$30 ; a = #$30 (related to score and collision test) + sta ENEMY_SCORE_COLLISION,x ; set score code to #$03, collision code to #$00 + lda sniper_attack_delay_tbl,y ; load attack delay + sta ENEMY_ATTACK_DELAY,x ; set delay between attacks based on sniper type + lda sniper_bullet_attack_count_tbl,y ; load number of bullets for attack round + sta ENEMY_VAR_4,x ; enemy bullet counter + jmp advance_enemy_routine ; advance to sniper_routine_02 + +sniper_routine_exit: + rts + +; table for rifle man (#$3 bytes) +; #$40 - delay before resuming attack - standing rifle man +; #$04 - delay before shooting after un-hiding (hiding rifle man) +; #$10 - delay before shooting after un-hiding (boss screen rifle man) +sniper_attack_delay_tbl: + .byte $40,$04,$10 + +; table for rifle man, number of bullets per attack (#$3 bytes) +; #$03 - number of bullets to shoot per attack - standing rifle man +; #$01 - number of bullets to shoot per attack - hiding rifle man +; #$03 - number of bullets to shoot per attack - boss screen rifle man +sniper_bullet_attack_count_tbl: + .byte $03, $01,$03 + +; rifle man - pointer 3 +sniper_routine_02: + jsr sniper_set_sprite ; set sprite and attributes based on sniper type and firing angle + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + dec ENEMY_ATTACK_DELAY,x ; decrement delay between attacks + bne sniper_routine_exit ; exit if attack delay hasn't elapsed + dec ENEMY_VAR_4,x ; decrement enemy bullet counter + bpl @continue_fire_bullet ; if bullets left to fire, branch + jmp @standing_set_attack_count_exit ; fired all bullets, jump + +@continue_fire_bullet: + lda #$18 ; a = #$18 + sta ENEMY_ATTACK_DELAY,x ; set attack delay between bullets + jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01) + sty $0a ; store closest player index in $0a + lda SPRITE_X_POS,y ; load the x position of the closest player + cmp ENEMY_X_POS,x ; enemy x position on screen + lda #$00 ; a = #$00 + bcc @check_bullet_angle ; branch if closest player is left of the enemy + lda #$01 ; closest player to right of enemy, set a = #$01 + +@check_bullet_angle: + sta ENEMY_VAR_2,x ; #$00 if player to left, #$01 if player to right of enemy + lda ENEMY_ATTRIBUTES,x ; load sniper type (#$00 = standing, #$01 = hiding, #$02 = boss hiding) + lsr + bcc @adjust_bullet_angle_y_offset + lda #$00 ; a = #$00 + ldy ENEMY_VAR_2,x ; bullet angle + bne @adjust_bullet_angle_with_a + lda #$0c ; a = #$0c + +@adjust_bullet_angle_with_a: + sta $0c ; set bullet type (xxx. ....) and angle index (...x xxxx) + jmp @adjust_bullet_angle + +@adjust_bullet_angle_y_offset: + ldy #$00 ; y = #$00 + lda ENEMY_ATTRIBUTES,x ; load sniper type (#$00 = standing, #$01 = hiding, #$02 = boss hiding) + cmp #$02 ; compare to boss screen sniper + bne @prep_create_bullet_vars ; branch if not boss screen sniper + ldy #$f0 ; set vertical offset from enemy position for crouching sniper (param for add_with_enemy_pos) + +@prep_create_bullet_vars: + lda #$00 ; set horizontal offset from enemy position (param for add_with_enemy_pos) + jsr add_with_enemy_pos ; stores absolute screen x position in $09, and y position in $08 + jsr get_rotate_01 ; get enemy aim direction and rotation direction using quadrant_aim_dir_01 + +@adjust_bullet_angle: + lda $0c ; load bullet aim direction + clc ; clear carry in preparation for addition + adc #$06 ; add one quadrant to the calculated direction + cmp #$18 ; compare to maximum direction code (3 o'clock) + bcc @continue + sbc #$18 ; wrapped around, subtract max value + ; i.e. a = ($0c + #$06) % #$18 + +@continue: + cmp #$0c ; compare the midway aim direction (9 o'clock) + bcc @continue2 ; branch if player is in quadrant I + sta $08 + lda #$18 ; a = #$18 + sec ; set carry flag in preparation for subtraction + sbc $08 + +@continue2: + ldy #$00 ; y = #$00 + cmp #$05 + bcc @continue_create_bullet + iny + cmp #$08 + bcc @continue_create_bullet + iny + +@continue_create_bullet: + lda ENEMY_ATTRIBUTES,x ; load sniper type (#$00 = standing, #$01 = crouching, #$02 = boss screen crouching) + cmp #$01 ; compare to crouching sniper + beq @get_pos_create_bullet ; branch if crouching sniper + lda sniper_standing_sprite_tbl,y ; standing sniper, or boss screen sniper, load sprite + sta ENEMY_FRAME,x ; set enemy animation frame number + +@get_pos_create_bullet: + lda ENEMY_Y_POS,x ; load sniper y position + clc ; clear carry in preparation for addition + adc sniper_bullet_y_offset,y ; add bullet y offset + sta $08 ; store bullet creation y location + lda ENEMY_VAR_2,x ; load sniper firing angle + lsr + lda sniper_bullet_x_offset,y + bcc @create_bullet + eor #$ff ; negative offset, flip all bits and add #$01 + adc #$00 + +@create_bullet: + clc ; clear carry in preparation for addition + adc ENEMY_X_POS,x ; add to enemy x position on screen + sta $09 ; store bullet creation x location + ldy ENEMY_ATTRIBUTES,x ; load sniper type (#$00 = standing, #$01 = hiding, #$02 = boss hiding) + lda sniper_bullet_speed,y ; load bullet speed based on sniper type + tay ; transfer bullet speed to y + lda $0c ; load bullet type (xxx. ....) and angle index (...x xxxx) + ; bullet type is going to be #$00 + jsr create_enemy_bullet_angle_a ; create a bullet with speed y, bullet type a, angle a at position ($09, $08) + bne @exit + lda #$06 ; a = #$06 + sta ENEMY_VAR_3,x ; sniper firing, set to #$03 + +@exit: + rts + +@standing_set_attack_count_exit: + ldy ENEMY_ATTRIBUTES,x ; load sniper type (#$00 = standing, #$01 = hiding, #$02 = boss hiding) + bne @set_frame_adv_routine ; branch if sniper that crouches + lda sniper_bullet_attack_count_tbl,y ; standing sniper, load bullet attack count + sta ENEMY_VAR_4,x ; set enemy bullet counter + lda #$80 ; a = #$80 + sta ENEMY_ATTACK_DELAY,x ; set delay between attacks (standing) + rts + +@set_frame_adv_routine: + lda ENEMY_ATTRIBUTES,x ; load sniper type (#$00 = standing, #$01 = hiding, #$02 = boss hiding) + lsr ; shift bit 0 to carry + lda #$02 ; a = #$02 + bcs @set_enemy_frame_adv_routine ; branch if sniper type #$01 (crouching) to set frame #$02 (sprite_46) + lda #$03 ; boss screen sniper, sprite_2c by setting ENEMY_FRAME to #$03 + +@set_enemy_frame_adv_routine: + sta ENEMY_FRAME,x ; set enemy animation frame number + lda #$80 ; a = #$80 (delay before re-hiding) + jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a; advance enemy routine + +; tile codes while shooting - standing rifle man +; $04 = shooting up +; $03 = shooting straight +; $05 = shooting down +sniper_standing_sprite_tbl: + .byte $04,$03,$05 + +; initial y offset of bullets - standing rifle man +sniper_bullet_y_offset: + .byte $ee,$f5,$06 + +; initial x offset of bullets - standing rifle man +sniper_bullet_x_offset: + .byte $f3,$f1,$f1 + +; bullet speed code +; $03 = bullet speed code for standing rifle man +; $05 = bullet speed code for hiding rifle man +; $03 = bullet speed code for boss screen sniper +sniper_bullet_speed: + .byte $03,$05,$03 + +; rifle man - pointer 4 +sniper_routine_03: + dec ENEMY_ANIMATION_DELAY,x + bne @set_sprite_add_scroll_exit + jsr disable_enemy_collision ; prevent player enemy collision check and allow bullets to pass through enemy + lda #$08 ; a = #$08 + sta ENEMY_ANIMATION_DELAY,x ; delay between frames when hiding + dec ENEMY_FRAME,x ; decrement enemy animation frame number + bne @continue + ldy ENEMY_ATTRIBUTES,x ; load sniper type (#$00 = standing, #$01 = hiding, #$02 = boss hiding) + lda sniper_animation_delay_2_tbl,y + sta ENEMY_ANIMATION_DELAY,x + lda #$02 ; a = #$02 + jsr set_enemy_routine_to_a ; set enemy routine index to a + +@continue: + lda ENEMY_ATTRIBUTES,x ; load sniper type (#$00 = standing, #$01 = hiding, #$02 = boss hiding) + cmp #$02 + bne @set_sprite_add_scroll_exit + lda ENEMY_FRAME,x ; load enemy animation frame number + cmp #$02 + bne @set_sprite_add_scroll_exit + lda #$0e ; a = #$0e + jsr add_a_to_enemy_y_pos ; add a to enemy y position on screen + lda #$ff ; a = #$ff + jsr add_a_to_enemy_x_pos ; add #$ff to enemy x position on screen + +@set_sprite_add_scroll_exit: + jsr sniper_set_sprite ; set sprite and attributes based on sniper type and firing angle + jmp add_scroll_to_enemy_pos ; adjust enemy location based on scroll + +; rifle man - pointer 5 +sniper_routine_04: + lda #$06 ; a = #$06 + sta ENEMY_FRAME,x ; set enemy animation frame number + jsr sniper_set_sprite ; set sprite and attributes based on sniper type and firing angle + jmp init_soldier_hit_vel ; set the velocities for sniper to start floating after hit + +; rifle man - pointer 6 +sniper_routine_05: + jsr sniper_set_sprite ; set sprite and attributes based on sniper type and firing angle + jmp apply_gravity_to_destroyed_soldier ; apply gravity to destroyed sniper that is floating up + ; removes enemy if off screen, or if animation timer has elapsed + +; set sniper sprite and sprite attributes based on sniper type and firing angle +sniper_set_sprite: + ldy #$00 ; default to use sniper_sprite_00 + lda ENEMY_ATTRIBUTES,x ; load sniper type (#$00 = standing, #$01 = hiding, #$02 = boss hiding) + cmp #$02 ; see if sniper type #$02 (boss screen sniper) + bcc @continue ; branch if not boss screen sniper + ldy #$02 ; boss screen sniper, use sniper_sprite_01 + +@continue: + lda sniper_sprite_ptr_tbl,y ; load low byte + sta $08 ; set low byte + lda sniper_sprite_ptr_tbl+1,y ; load high byte + sta $09 ; set high byte + ldy ENEMY_FRAME,x ; load current ENEMY_FRAME index + lda ($08),y ; load specific sprite code from sniper_sprite_xx + sta ENEMY_SPRITES,x ; set enemy sprite code to CPU buffer + lda ENEMY_VAR_2,x ; load sniper firing angle + lsr ; shift right + lda #$40 ; a = #$40 + bcc @continue2 ; branch if bullet bit 0 was clear + lda #$00 ; a = #$00 + +@continue2: + ldy ENEMY_VAR_3,x + beq @set_sprite_attr_exit + dec ENEMY_VAR_3,x + ora #$08 ; set bits .... x... (gun recoil flag) + +@set_sprite_attr_exit: + sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes + rts + +; pointer table for rifle man sprite codes (#$2 * #$2 = #$4 bytes) +sniper_sprite_ptr_tbl: + .addr sniper_sprite_00 ; CPU address $8b3b - regular/hiding rifle man (sniper type #$00 and type #$01) + .addr sniper_sprite_01 ; CPU address $8b42 - boss screen sniper (sniper type #$04) + +; regular/hiding rifle man sprite codes (#$7 bytes) +; sprite_29, sprite_41 sprite_42, sprite_43, sprite_44, sprite_45, sprite_46 +sniper_sprite_00: + .byte $44,$45,$46,$43,$42,$41,$29 + +; boss screen sniper sprite codes (#$7 bytes) +; sprite_29, sprite_2c, sprite_2d, sprite_42, sprite_44, sprite_45, sprite_46 +sniper_sprite_01: + .byte $44,$45,$46,$2c,$42,$2d,$29 + +; pointer table for level 1 bomb turret (#$6 * #$2 = c bytes) +; the two turrets on the jungle level boss wall +bomb_turret_routine_ptr_tbl: + .addr boss_bomb_turret_routine_00 ; CPU address $8b55 + .addr boss_bomb_turret_routine_01 ; CPU address $8b5c + .addr boss_bomb_turret_routine_02 ; CPU address $8bbf + .addr enemy_routine_init_explosion ; CPU address $e74b from bank 7 + .addr enemy_routine_explosion ; CPU address $e7b0 from bank 7 + .addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7 + +; bomb turret - pointer 1 +; set attack delay and move to routine_01 +boss_bomb_turret_routine_00: + lda #$20 ; a = #$20 + sta ENEMY_ATTACK_DELAY,x ; set attack delay to 20 frames + bne boss_bomb_turret_advance_routine ; set to go to next routine boss_bomb_turret_routine_01 + +; bomb turret - pointer 2 +; firing animation and bullet generation +; ENEMY_VAR_1 is the enemy super-tile index to draw (level_1_nametable_update_supertile_data) +boss_bomb_turret_routine_01: + jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll + dec ENEMY_ATTACK_DELAY,x ; decrement delay between attacks + bne level_1_boss_exit ; exit if delay timer hasn't elapsed + ; either #$08 frames between firing animation, or #$28 frames between bullets + jsr draw_boss_bomb_turret ; draws the bomb turret based on the current recoil (ENEMY_VAR_1) + bcs level_1_boss_exit + lda #$28 ; set bomb firing delay to #28 frames + ldy ENEMY_VAR_1,x ; load current super-tile index (alternates between #$00 and #$02) + beq @continue ; branch if #$00 (not firing) + lda #$08 ; firing a bomb, set delay to #$08 (number of frames between recoil animation) + +@continue: + sta ENEMY_ATTACK_DELAY,x ; store delay between bombs (either #$28 or #$08) + tya ; move ENEMY_VAR_1 to a + eor #$02 ; toggle between #$00 and #$02 (which super-tile to draw) + sta ENEMY_VAR_1,x ; store updated super-tile index back in ENEMY_VAR_1 + beq level_1_boss_exit ; if not firing a bomb, exit + lda #$f8 ; set bomb horizontal offset from enemy position (param for add_with_enemy_pos) + ldy #$00 ; set bomb vertical offset from enemy position (param for add_with_enemy_pos) + jsr add_with_enemy_pos ; stores absolute screen x position in $09, and y position in $08 + lda RANDOM_NUM ; load random number + and #$03 ; random number between 0 and 3 + tay + lda boss_bomb_turret_bomb_velocity_tbl,y ; select the random initial velocity + tay + lda #$17 ; a = #$17 - bullet type (#$00) and angle (#$17) + jmp bullet_generation ; create enemy bullet (ENEMY_TYPE #$02) of type a with speed y at ($09, $08) + +; table for bombs initial x velocities (#$4 bytes) +boss_bomb_turret_bomb_velocity_tbl: + .byte $01,$03,$05,$07 + +draw_boss_bomb_turret: + ldy ENEMY_VAR_1,x ; load current super-tile index for enemy + +; uses value y to load correct super-tile +draw_boss_bomb_turret_y: + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + lsr + bcc @continue ; branch if ENEMY_ATTRIBUTES bit 0 is 0 (wall background) + iny ; increment boss_bomb_turret_supertile_tbl offset by 1 to get jungle bg super-tile + lda #$f8 ; move jungle bg bomb turret enemy to the left 8 pixels + ; the jungle turret super-tile has some background, which isn't part of the enemy position + ; so subtract #$08, draw super-tile at position, then add #$08 back + jsr add_a_to_enemy_x_pos ; subtract 8 from the enemy position (drawn super-tile is 8 to the left of enemy position) + +@continue: + lda boss_bomb_turret_supertile_tbl,y ; load the correct super-tile to draw for the bomb turret (3-frame) + jsr draw_enemy_supertile_a_set_delay ; draw pattern table tile specified in a at enemy position + php ; save status flags on stack + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + beq @exit ; exit if wall background + iny + lda #$08 ; move jungle bg bomb turret enemy to the right 8 pixels + jsr add_a_to_enemy_x_pos ; add #$08 back to enemy x position on screen + +@exit: + plp ; restore pushed status flags from above + +level_1_boss_exit: + rts + +; table for bomb turrets super-tile codes (#$6 bytes) +; offsets into level_1_nametable_update_supertile_data +; $29, $2a, $2b - wall bg bomb turret super-tiles +; $26, $27, $28 - jungle bg bomb turret super-tiles +boss_bomb_turret_supertile_tbl: + .byte $29,$26,$2a,$27,$2b,$28 + +; bomb turret - pointer 3 +; updates super-tile to show boss bomb turret destroy and move to next routine +; this routine is started when enemy is destroyed (enemy_destroyed_routine_ptr_tbl) +boss_bomb_turret_routine_02: + ldy #$04 ; load the super-tile for the turret being destroyed + jsr draw_boss_bomb_turret_y ; draw super-tile from boss_bomb_turret_supertile_tbl ($2b or $28 depending on bg) + bcs level_1_boss_exit ; exit + +boss_bomb_turret_advance_routine: + jmp advance_enemy_routine ; advance to next routine + +; pointer table for door plate with siren (#$7 * #$2 = #$e bytes) +; level 1 boss defense wall boss target +boss_wall_plated_door_routine_ptr_tbl: + .addr boss_wall_plated_door_routine_00 ; CPU address $8bd7 + .addr add_scroll_to_enemy_pos ; CPU address $e8a7 from bank 7 - add scrolling to enemy position + .addr boss_defeated_routine ; CPU address $e740 from bank 7 + .addr enemy_routine_explosion ; CPU address $e7b0 from bank 7 + .addr shared_enemy_routine_clear_sprite ; CPU address $e814 from bank 7 - set tile sprite code to #$00 and advance routine + .addr boss_wall_plated_door_routine_05 ; CPU address $8bdf + .addr boss_wall_plated_door_routine_06 ; CPU address $8be9 + +; plays a siren sound and advance enemy routine +boss_wall_plated_door_routine_00: + lda #$1b ; a = #$1b (sound_1b) + jsr play_sound ; play level 1 jungle boss siren sound + jmp advance_enemy_routine ; advance to next routine + +; door plate - pointer 6 +boss_wall_plated_door_routine_05: + lda #$00 ; a = #$00 + sta ENEMY_VAR_1,x + lda #$08 ; a = #$08 + jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a; advance enemy routine + +; door plate - pointer 7 +boss_wall_plated_door_routine_06: + dec ENEMY_ANIMATION_DELAY,x + bne @create_tunnel_explosion ; create tunnel explosion to go to next level if delay has elapsed + lda #$08 ; a = #$08 + sta ENEMY_ANIMATION_DELAY,x + ldy ENEMY_VAR_1,x ; load current tunnel explosion index + lda wall_plated_door_supertile_tbl,y ; load the correct supertile for the tunnel + jsr draw_enemy_supertile_a_set_delay ; draw tunnel supertile specified in a at enemy position + bcs level_1_boss_exit ; exit if unable to draw supertile + ldy ENEMY_VAR_1,x ; load current tunnel explosion index + lda wall_plated_door_collision_code_tbl,y ; load correct collision code for tunnel super-tile component + jsr set_supertile_bg_collision ; update bg collision codes to collision code a for a single super-tile at PPU address $12 (low) $13 (high) + jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position + inc ENEMY_VAR_1,x ; increment current tunnel index + jmp create_enemy_for_explosion ; create explosion + +; creates tunnel to go to next level if delay has elapsed +@create_tunnel_explosion: + lda ENEMY_ANIMATION_DELAY,x + cmp #$01 ; see if explosion delay on wall plated door is completed + bne level_1_boss_exit ; exit if delay hasn't elapsed + lda ENEMY_VAR_1,x ; current tunnel creation index + asl ; double entry since each entry is two bytes + tay ; transfer offset to y + lda wall_plated_door_explosion_offset_tbl,y ; load current tunnel index + cmp #$ff ; see if end of tunnel explosions + beq @set_delay_remove_enemy ; set delay to #$30 and remove enemy if all explosions have been shown + jsr add_a_to_enemy_y_pos ; add offset to plated door location y position + lda wall_plated_door_explosion_offset_tbl+1,y + jmp add_a_to_enemy_x_pos ; add a to enemy x position on screen + +@set_delay_remove_enemy: + lda #$30 ; a = #$30 + jmp set_delay_remove_enemy + +; table for destroyed plated door tunnel explosions offsets (#$11 bytes) +; byte 0 - y offset +; byte 1 - x offset +wall_plated_door_explosion_offset_tbl: + .byte $f0,$f0 ; -10, -10 + .byte $20,$00 ; 20, 00 + .byte $e0,$20 ; -20, 20 + .byte $20,$00 ; 20, 00 + .byte $e0,$20 ; -20, 20 + .byte $20,$00 ; 20, 00 + .byte $e0,$20 ; -20, 20 + .byte $20,$00 ; 20, 00 + .byte $ff + +; table for boss wall plated door tunnel super-tiles (#$8 bytes) +wall_plated_door_supertile_tbl: + .byte $1e,$22,$1f,$23,$20,$24,$21,$25 + +; table for wall plated door tunnel collision codes (#$8 bytes) +wall_plated_door_collision_code_tbl: + .byte $00,$00,$00,$04,$00,$04,$00,$04 + +; pointer table for exploding bridge (#$5 * #$2 = #$a bytes) +exploding_bridge_routine_ptr_tbl: + .addr exploding_bridge_routine_00 ; CPU address $8c5c + .addr exploding_bridge_routine_01 ; CPU address $8c73 + .addr enemy_routine_init_explosion ; CPU address $e74b from bank 7 + .addr enemy_routine_explosion ; CPU address $e7b0 from bank 7 + .addr exploding_bridge_routine_04 ; CPU address $8cf0 + +; waits until player is close to bridge, then advance to next routine +exploding_bridge_routine_00: + jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll + jsr player_enemy_x_dist ; a = closest x distance to bridge from players, y = closest player (#$00 or #$01) + cmp #$18 ; see if the player is within #$18 x distance from the bridge + bcs exploding_bridge_exit ; player(s) aren't close enough, simply exit + lda #$01 ; a = #$01 + +; sets delay to a, clears ENEMY_VAR_2 and advances to next routine +exploding_bridge_advance_routine: + sta ENEMY_ANIMATION_DELAY,x ; set delay to #$01 frame + lda #$00 ; a = #$00 + sta ENEMY_VAR_2,x ; clear cloud explosion animation number + +advance_enemy_routine_far: + jmp advance_enemy_routine + +exploding_bridge_routine_01: + jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll + dec ENEMY_ANIMATION_DELAY,x ; decrement animation delay between explosions + bne exploding_bridge_exit ; current animation not complete, exit + lda ENEMY_VAR_1,x ; load currently exploding bridge section + asl ; each bridge section has 2 super-tiles to animate through + sta $08 ; store into $08 + lda ENEMY_VAR_2,x ; load the sprite cloud explosion number + cmp #$02 ; see if last small explosion before generic explosion animation + bcs bridge_explosion_clouds ; only 2 super-tile animations per bridge section, skip updating nametable if already done + clc ; clear carry in preparation for addition + adc $08 ; (2 * ENEMY_VAR_1,x) + ENEMY_VAR_2 + ; ENEMY_VAR_2 is only ever #$00 or #$01 here due to cmp above + tay ; set as offset, y can be #$00 to #$07, which overflows into exploding_bridge_cloud_y_offset by 1 entry + lda exploding_bridge_destroyed_supertile_tbl,y ; load super-tile code + beq bridge_explosion_clouds ; if loaded #$00, then no nametable update, draw explosion cloud sprites + sta $10 ; store super-tile code in $10 to update nametable + lda ENEMY_Y_POS,x ; load the bridge X position + clc ; clear carry in preparation for addition + adc #$f4 ; bridge Y position minus #$c + tay ; store value in Y, this is the super-tile draw y position + lda ENEMY_VAR_2,x ; load the sprite cloud explosion number + lsr ; move bit 0 into carry + lda ENEMY_X_POS,x ; load the bridge X position + bcs @draw_exploding_bridge_supertile ; if ENEMY_VAR_2 is #$00, updating current bridge section + adc #$e0 ; updating previous bridge section, subtract #$20 (one super-tile width) + +; executed twice per bridge section (except last bridge section, only executes once) +; updates nametable super-tile for current bridge section on first frame of explosion animation +; on next call, updates previous bridge section to second animation super-tile +@draw_exploding_bridge_supertile: + clc ; clear carry in preparation for addition + adc #$f4 ; subtract #$c from x position + jsr load_bank_3_update_nametable_supertile ; draw super-tile $10 at position (a,y) + ldx ENEMY_CURRENT_SLOT + bcc clear_supertile_bg_collision_draw_clouds + lda #$01 ; a = #$01 + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + +exploding_bridge_exit: + rts + +; exploding bridge - clear the background collision code so that a player falls through +; continue through to bridge_explosion_clouds logic +clear_supertile_bg_collision_draw_clouds: + jsr clear_supertile_bg_collision ; set background collision code to #$00 (empty) for a single super-tile at PPU address $12 (low) $13 (high) + +; show cloud explosion and play sound +bridge_explosion_clouds: + inc ENEMY_VAR_2,x ; increment explosion cloud number + lda ENEMY_VAR_2,x ; a = explosion cloud number + cmp #$04 ; number of explosion clouds per segment + bcs advance_enemy_routine_far ; >= 4 explosions have happened, move to next routine + lda #$24 ; a = #$24 (sound_24) + jsr play_sound ; play explosion sound + lda #$04 ; a = #$04 (delay between explosions and clouds) + sta ENEMY_ANIMATION_DELAY,x ; set a #$4 frame delay between explosions + ldy ENEMY_VAR_2,x ; a = explosion cloud number + lda exploding_bridge_cloud_x_offset,y ; load explosion x offset + sta $08 ; store x offset in $08 + lda exploding_bridge_cloud_y_offset,y ; load explosion y offset (#$01 to #$03) + tay ; set vertical offset from enemy position (param for add_with_enemy_pos) + lda $08 ; set horizontal offset from enemy position (param for add_with_enemy_pos) + jsr add_with_enemy_pos ; stores absolute screen x position in $09, and y position in $08 + jmp create_enemy_for_explosion ; create new enemy for explosion animation (enemy_routine_init_explosion) + +; table for tile codes after destruction (#$7 bytes), see level_1_nametable_update_supertile_data +; for first animation of left-most section of bridge, #$00 indicates no super-tile is changed +; #$19 - blank super-tile, used for destroyed bridges +; #$1a - exploding bridge partially destroyed both ends still exist +; #$1b - exploding bridge partially destroyed left only +; #$1c - exploding bridge partially destroyed right only +; #$1d - exploding bridge partially destroyed right only (more destroyed) +exploding_bridge_destroyed_supertile_tbl: + .byte $00 + .byte $1a,$1b ; first bridge section + .byte $1c,$19 ; second bridge section + .byte $1c,$19 ; third bridge section + +; table for clouds y offsets (#$4 bytes) +; #$1d is actually used when referencing exploding_bridge_destroyed_supertile_tbl +; #$00 = 0 +; #$f0 = -16 +exploding_bridge_cloud_y_offset: + .byte $1d,$00,$f0,$00 + +; table for clouds x offsets (#$5 bytes) +; #$f0 = -16 +; #$00 = 0 +; #$10 = 20 +exploding_bridge_cloud_x_offset: + .byte $10,$f0,$00,$10,$00 + +; initializes next section of bridge for exploding +; then sets ENEMY_ROUTINE #$02 (exploding_bridge_routine_01) to initiate next explosion +; if all bridge sections have exploded, then remove enemy +exploding_bridge_routine_04: + jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll + inc ENEMY_VAR_1,x ; increment currently exploding bridge section (#$00 - #$03) + lda ENEMY_VAR_1,x ; load currently exploding bridge section + cmp #$04 ; number of explosions + bcs @remove_enemy ; remove bridge if all sections have exploded + lda ENEMY_X_POS,x ; load enemy x position on screen + adc #$20 ; adjust position to next bridge section (move over to the next super-tile) + bcs @remove_enemy ; unnecessary, already checked if last section earlier, this branch shouldn't ever happen + sta ENEMY_X_POS,x ; set enemy x position on screen + lda #$01 ; a = #$01 + sta ENEMY_SPRITES,x ; remove last cloud explosion sprite from previous bridge section (sprite_01 is invisible sprite) + lda #$01 ; a = #$01 + jsr exploding_bridge_advance_routine ; set delay to #$01, clear ENEMY_VAR_2 (advance to next routine is overwritten below) + lda #$02 ; initialize a to desired enemy routine to initiate next explosion + jmp set_enemy_routine_to_a ; set enemy routine to exploding_bridge_routine_01 to begin next bridge element explosion + +@remove_enemy: + jmp remove_enemy ; remove enemy + +; pointer table for green guys generator (#$3 * #$2 = #$6 bytes) +; generates soldier enemies: running, jumping, grenade launcher, and group of 4 +indoor_soldier_gen_routine_ptr_tbl: + .addr indoor_soldier_gen_routine_00 ; CPU address $8d1f + .addr indoor_soldier_gen_routine_01 ; CPU address $8d28 + .addr remove_enemy ; CPU address $e809 from bank 7 + +indoor_soldier_gen_routine_00: + lda #$40 ; a = #$40 + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter to #$40 + jmp advance_enemy_routine + +indoor_soldier_gen_exit: + rts + +; generates indoor soldier enemies: running, jumping, grenade launcher, and group of 4 +; delay between enemy generation is specified in byte 3 of lvl_x_enemy_gen_screen_xx +indoor_soldier_gen_routine_01: + lda FRAME_COUNTER ; load frame counter + lsr + bcc indoor_soldier_gen_exit ; if FRAME_COUNTER is even, return + lda GRENADE_LAUNCHER_FLAG ; see if a grenade launcher enemy (ENEMY_TYPE #$15) is on the screen + bne indoor_soldier_gen_exit ; if GRENADE_LAUNCHER_FLAG exists on screen, exit + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne indoor_soldier_gen_exit ; exit if the animation delay hasn't elapsed + lda ENEMY_ATTRIBUTES,x ; load the soldier attributes (byte 2 of level_x_enemy_screen_xx for enemy) + asl ; disregard bit 7 and double bit 0, which is used as offset indoor_enemy_gen_tbl + tay ; transfer + lda indoor_enemy_gen_tbl,y ; pointer to table entry, low byte + sta $0a ; load lvl_x_enemy_gen_tbl low byte + lda indoor_enemy_gen_tbl+1,y ; pointer to table entry, high byte + sta $0b ; load lvl_x_enemy_gen_tbl high byte + lda LEVEL_SCREEN_NUMBER ; load current screen number within the level + asl ; double screen number since each entry is a 2 byte address + tay ; transfer offset lvl_x_enemy_gen_tbl offset to y + lda ($0a),y ; load the low byte of the lvl_x_enemy_gen_tbl address + sta $08 ; store in $08 + iny ; increment indoor_enemy_gen_tbl read offset + lda ($0a),y ; load the high byte of the lvl_x_enemy_gen_tbl address + sta $09 ; store in $09 + ldy ENEMY_VAR_1,x ; load current screen's enemy offset + lda ($08),y ; read the first byte + and #$3f ; keep bits 0 to 5 (ENEMY_ATTRIBUTES) + sta $0a ; store ENEMY_ATTRIBUTES + lda ($08),y ; re-read the first byte + rol ; look at bits 6 and 7 to see enemy type + rol ; (0 = indoor soldier, 1 = jumping soldier, 2 = group of four, 3 = grenade launcher) + rol ; rotate until top 2 bits are bits 0 and bit 1 + and #$03 ; keep bits .... ..xx (enemy type) + sta $0b ; set current enemy type (#$00-#$03) + ; #$00 - running guy, #$01 - jumping guy, #$02 - group of 4, #$03 - grenade launcher + iny ; increment lvl_x_enemy_gen_screen_xx read offset + lda ($08),y ; read next byte (delay byte) + iny ; increment lvl_x_enemy_gen_screen_xx read offset + asl ; shift bit 7 to the carry flag + bcc @create_enemy ; if bit 7 = 0, don't increment INDOOR_ENEMY_ATTACK_COUNT + ldy #$00 ; y = #$00 + pha ; push a on to the stack + inc INDOOR_ENEMY_ATTACK_COUNT ; increment the total number of enemy attack rounds + lda INDOOR_ENEMY_ATTACK_COUNT ; load the total number of enemy attack rounds for the screen + cmp #$07 ; guys stop after 7 cycles + pla ; pop a from stack + bcc @create_enemy ; continue to create an enemy if not all #$07 rounds of attack have occurred + jmp remove_enemy ; remove enemy generator if all 7 rounds of attack have happened + +@create_enemy: + lsr ; shift byte 2 back (with the former bit 7 now 0) + ; this is the animation delay for the enemy to generate + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + tya ; transfer lvl_x_enemy_gen_screen_xx read offset to a + sta ENEMY_VAR_1,x ; store updated lvl_x_enemy_gen_screen_xx read offset in ENEMY_VAR_1 + ldy $0b ; load enemy type (#$00 - #$03) + beq @create_running_guy ; if enemy type is #$00 create indoor soldier + dey ; decrement enemy type + beq @create_jumping_guy ; if enemy type was #$01 create jumping soldier + dey ; decrement enemy type + beq @create_group_of_4 ; if enemy type was #$02 create group of 4 soldiers + lda #$17 ; a = #$17 grenade launcher + bne @create_enemy_a ; enemy type was #$03, create grenade launcher + +@create_running_guy: + lda #$15 ; a = #$15 running guy + bne @create_enemy_a + +@create_jumping_guy: + lda #$16 ; a = #$16 jumping guy + +; creates indoor soldier, jumping soldier, and seeking guy (grenade launcher) based on ENEMY_TYPE value in a +@create_enemy_a: + sta $08 ; store ENEMY_TYPE in $08 + jsr find_next_enemy_slot ; find next available enemy slot, put result in x register + bne indoor_soldier_gen_routine_exit ; exit if not empty enemy slots + lda $08 ; load ENEMY_TYPE to create + sta ENEMY_TYPE,x ; save in ENEMY_TYPE + jsr initialize_enemy ; initialize enemy variables + lda $0a ; load enemy attributes from lvl_x_enemy_gen_screen_xx + sta ENEMY_ATTRIBUTES,x ; store in ENEMY_ATTRIBUTES + jmp indoor_soldier_gen_routine_exit ; done creating enemy, exit + +; group of 4 running guys +@create_group_of_4: + lda #$03 ; a = #$03 + sta $0c ; loop counter, create #$04 green soldiers + +; create 4 green guys +@green_guy_creation_loop: + jsr find_next_enemy_slot ; find next available enemy slot, put result in x register + bne indoor_soldier_gen_routine_exit ; exit if unable find an empty slog + lda #$18 ; a = #$18 (group of 4 running guys) + sta ENEMY_TYPE,x ; set enemy to type 18 + jsr initialize_enemy ; initialize enemy variables + lda $0a ; load enemy attributes from lvl_x_enemy_gen_screen_xx + sta ENEMY_ATTRIBUTES,x ; store in ENEMY_ATTRIBUTES + lda $0c ; load remaining number of green soldiers (enemy type #$18) to create + sta ENEMY_VAR_1,x ; store in ENEMY_VAR_1, this is used to label each individual soldier [#$00-#$03] + dec $0c ; decrement remaining number of green soldiers (enemy type #$18) to create + bpl @green_guy_creation_loop ; loop if haven't yet created #$04 soldiers + +indoor_soldier_gen_routine_exit: + ldx ENEMY_CURRENT_SLOT + rts + +; pointer table for level 2/4 enemy cycles pointers (#$2 * #$2 = #$4 bytes) +indoor_enemy_gen_tbl: + .addr lvl_2_enemy_gen_tbl ; CPU address $8dd3 + .addr lvl_4_enemy_gen_tbl ; CPU address $8e09 + +; pointer table for level 2 enemy cycles (#$5 * #$2 = #$e) +lvl_2_enemy_gen_tbl: + .addr lvl_2_enemy_gen_screen_00 ; CPU address $8ddd + .addr lvl_2_enemy_gen_screen_01 ; CPU address $8de3 + .addr lvl_2_enemy_gen_screen_02 ; CPU address $8def + .addr lvl_2_enemy_gen_screen_03 ; CPU address $8df3 + .addr lvl_2_enemy_gen_screen_04 ; CPU address $8df9 + +; byte 0 +; - xx.. .... type (0 = indoor soldier, 1 = jumping soldier, 2 = group of four, 3 = grenade launcher) +; - ..xx xxxx enemy attributes, different per enemy type +; byte 1 +; * bit 7 = 0, don't increment INDOOR_ENEMY_ATTACK_COUNT +; * bits 0-6 = delay +lvl_2_enemy_gen_screen_00: + .byte $42,$30 ; jumping soldier, regular bullet, from right + .byte $01,$01 ; indoor soldier, regular bullet, from left + .byte $00,$c0 ; indoor soldier, regular bullet, from right + +lvl_2_enemy_gen_screen_01: + .byte $46,$30 ; jumping soldier + .byte $81,$50 ; group of 4 + .byte $01,$10 ; indoor soldier + .byte $00,$30 ; indoor soldier + .byte $00,$10 ; indoor soldier + .byte $01,$e0 ; indoor soldier + +lvl_2_enemy_gen_screen_02: + .byte $00,$30 ; indoor soldier + .byte $c5,$a0 ; grenade launcher + +lvl_2_enemy_gen_screen_03: + .byte $46,$20 ; jumping soldier + .byte $81,$60 ; group of 4 + .byte $c3,$e1 ; grenade launcher + +lvl_2_enemy_gen_screen_04: + .byte $40,$30 ; jumping soldier + .byte $81,$60 ; group of 4 + .byte $00,$10 ; indoor soldier + .byte $03,$30 ; indoor soldier + .byte $02,$10 ; indoor soldier + .byte $01,$40 ; indoor soldier + .byte $47,$10 ; jumping soldier + .byte $4a,$e0 ; jumping soldier + +; pointer table for level 4 enemy cycles (#$8 * #$2 = #$10 bytes) +lvl_4_enemy_gen_tbl: + .addr lvl_4_enemy_gen_screen_00 ; CPU address $8e19 + .addr lvl_4_enemy_gen_screen_01 ; CPU address $8e25 + .addr lvl_4_enemy_gen_screen_02 ; CPU address $8e33 + .addr lvl_4_enemy_gen_screen_03 ; CPU address $8e3b + .addr lvl_4_enemy_gen_screen_04 ; CPU address $8e43 + .addr lvl_4_enemy_gen_screen_05 ; CPU address $8e49 + .addr lvl_4_enemy_gen_screen_06 ; CPU address $8e51 + .addr lvl_4_enemy_gen_screen_07 ; CPU address $8e5d + +; (#$c bytes) +lvl_4_enemy_gen_screen_00: + .byte $04,$30 ; indoor soldier + .byte $05,$60 ; indoor soldier + .byte $41,$60 ; jumping soldier + .byte $02,$30 ; indoor soldier + .byte $03,$60 ; indoor soldier + .byte $80,$e0 ; group of 4 + +; (#$e bytes) +lvl_4_enemy_gen_screen_01: + .byte $4a,$50 ; jumping soldier + .byte $c3,$20 ; grenade launcher + .byte $c2,$20 ; grenade launcher + .byte $04,$20 ; indoor soldier + .byte $05,$50 ; indoor soldier + .byte $47,$50 ; jumping soldier + .byte $c2,$b0 ; grenade launcher + +; (#$8 bytes) +lvl_4_enemy_gen_screen_02: + .byte $05,$40 ; indoor soldier + .byte $80,$60 ; group of 4 + .byte $53,$60 ; jumping soldier + .byte $80,$e0 ; group of 4 + +; (#$8 bytes) +lvl_4_enemy_gen_screen_03: + .byte $57,$60 ; jumping soldier + .byte $40,$60 ; jumping soldier + .byte $41,$60 ; jumping soldier + .byte $40,$e0 ; jumping soldier + +; (#$6 bytes) +lvl_4_enemy_gen_screen_04: + .byte $05,$30 ; indoor soldier + .byte $04,$60 ; indoor soldier + .byte $42,$e0 ; jumping soldier + +; (#$8 bytes) +lvl_4_enemy_gen_screen_05: + .byte $4e,$40 ; jumping soldier + .byte $81,$60 ; group of 4 + .byte $41,$60 ; jumping soldier + .byte $40,$e0 ; jumping soldier + +; (#$c bytes) +lvl_4_enemy_gen_screen_06: + .byte $04,$20 ; indoor soldier + .byte $03,$40 ; indoor soldier + .byte $4b,$60 ; jumping soldier + .byte $07,$20 ; indoor soldier + .byte $02,$40 ; indoor soldier + .byte $4b,$e0 ; jumping soldier + +; (#$a bytes) +lvl_4_enemy_gen_screen_07: + .byte $02,$30 ; indoor soldier + .byte $47,$40 ; jumping soldier + .byte $80,$60 ; group of 4 + .byte $03,$20 ; indoor soldier + .byte $04,$d0 ; indoor soldier + +; pointer table for base i boss eye (#$7 * #$2 = #$e bytes) +boss_eye_routine_ptr_tbl: + .addr boss_eye_routine_00 ; CPU address $8e75 + .addr boss_eye_routine_01 ; CPU address $8e7d + .addr boss_eye_routine_02 ; CPU address $8ea4 + .addr boss_eye_routine_03 ; CPU address $8f08 + .addr boss_defeated_routine ; CPU address $e740 + .addr enemy_routine_explosion ; CPU address $e7b0 + .addr boss_eye_routine_06 ; CPU address $8f2d + +; base I boss eye - pointer 1 +boss_eye_routine_00: + lda #$40 ; a = #$40 (delay before indoor boss appears) + +; set the animation delay to a and advanced the ENEMY_ROUTINE +; input +; * a - the ENEMY_ANIMATION_DELAY +; this label is identical to two other labels +; * bank 7 - set_anim_delay_adv_enemy_routine +; * bank 0 - (this bank) set_anim_delay_adv_enemy_routine_01 +set_anim_delay_adv_enemy_routine_00: + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + jmp advance_enemy_routine ; advance to next routine + +boss_eye_routine_01: + lda WALL_PLATING_DESTROYED_COUNT ; number of boss platings destroyed + cmp #$04 ; number of boss platings to destroy (level 1) + bcc boss_eye_exit ; exit if not all boss platings have been destroyed + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne boss_eye_exit ; exist if ENEMY_ANIMATION_DELAY hasn't elapsed + lda #$40 ; ready to create boss, a = #$40 + sta ENEMY_X_VELOCITY_FRACT,x ; boss x velocity (low byte) + lda #$01 ; a = #$01 + sta ENEMY_X_VELOCITY_FAST,x ; boss x velocity (high byte) + lda #$10 ; a = #$10 + sta ENEMY_VAR_1,x ; boss hp + jsr enable_enemy_collision ; enable bullet-enemy collision and player-enemy collision checks + lda #$20 ; a = #$20 (delay before first attack) + sta ENEMY_ATTACK_DELAY,x ; set delay between attacks + lda #$c0 ; a = #$c0 + bne set_anim_delay_adv_enemy_routine_00 ; set ENEMY_ANIMATION_DELAY to #$c0 and advance enemy routine + +boss_eye_exit: + rts + +boss_eye_routine_02: + ldy #$00 ; y = #$00 + lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter + beq @continue ; animation delay has elapsed, continue + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + lda FRAME_COUNTER ; load frame counter + lsr + lsr + lsr + bcc @continue + ldy #$04 ; y = #$04 + +@continue: + sty $08 + inc ENEMY_FRAME,x ; increment enemy animation frame number + lda ENEMY_FRAME,x ; load enemy animation frame number + lsr + lsr + lsr + and #$03 ; keep bits .... ..xx + clc ; clear carry in preparation for addition + adc $08 + tay + lda boss_eye_sprite_code_tbl,y + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + jsr update_enemy_pos ; apply velocities and scrolling adjust + lda ENEMY_X_POS,x ; load enemy x position on screen + ldy ENEMY_X_VELOCITY_FAST,x + bmi @check_pos_create_projectile ; see if boss + cmp #$b0 ; compare to left-most 70% of horizontal screen + bcc @create_projectile_if_should ; in firing position, create eye projectile if attack delay has elapsed + bcs @reverse_dir_fire_projectile ; reverse player direction and create eye projectile if attack delay has elapsed + +@check_pos_create_projectile: + cmp #$50 ; minimum x position + bcs @create_projectile_if_should ; branch if boss eye is on the right 2/3rds of the screen + +@reverse_dir_fire_projectile: + jsr reverse_enemy_x_direction ; reverse enemy's x direction + +@create_projectile_if_should: + lda ENEMY_ATTACK_FLAG ; see if enemies should attack + beq boss_eye_exit ; exit if enemies shouldn't attack + dec ENEMY_ATTACK_DELAY,x ; decrement attack delay + bne boss_eye_exit ; exit if ENEMY_ATTACK_DELAY hasn't elapsed + ldy PLAYER_WEAPON_STRENGTH ; weapon strength code + lda boss_eye_attack_delay_tbl,y ; load attack delay based on player's weapon strength + sta ENEMY_ATTACK_DELAY,x ; set delay between attacks + lda #$1b ; a = #$1b (boss eye fire ring projectile) + jmp generate_enemy_a ; generate eye projectile enemy + +; tables for indoor/base level 1 boss eye +; boss eye attack delay (#$04 bytes) +; based off player's weapon strength +boss_eye_attack_delay_tbl: + .byte $70,$50,$40,$28 + +; table for boss eye sprite codes (#$8 bytes) +; sprite_5d, sprite_5e, sprite_5f +; sprite_60, sprite_61, sprite_62 +boss_eye_sprite_code_tbl: + .byte $5d,$5e,$5f,$5e,$60,$61,$62,$61 + +boss_eye_routine_03: + dec ENEMY_VAR_1,x + beq boss_eye_adv_routine + lda ENEMY_VAR_1,x + cmp #$01 + bne @continue + lda #$52 ; a = #$52 + sta ENEMY_SCORE_COLLISION,x + +@continue: + lda #$16 ; a = #$16 (sound_16) + jsr play_sound ; play bullet - metal ting sound + lda #$01 ; a = #$01 + sta ENEMY_HP,x ; set enemy hp + lda #$20 ; a = #$20 + sta ENEMY_ANIMATION_DELAY,x ; time for enemy flashing red when hit + lda #$03 ; a = #$03 + jmp set_enemy_routine_to_a ; set enemy routine index to a + +boss_eye_routine_06: + jsr shared_enemy_routine_clear_sprite ; set tile sprite code to #$00 and advance routine + lda #$60 ; a = #$60 + jmp set_delay_remove_enemy + +; pointer table for base i boss eye sphere projectile (#$5 * #$2 = #$a bytes) +eye_projectile_routine_ptr_tbl: + .addr eye_projectile_routine_00 ; CPU address $8f3f + .addr eye_projectile_routine_01 ; CPU address $8f58 + .addr enemy_routine_init_explosion ; CPU address $e74b from bank 7 + .addr enemy_routine_explosion ; CPU address $e7b0 from bank 7 + .addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7 + +eye_projectile_routine_00: + jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01) + sty $0a ; store closest player in $0a + jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position + lda #$06 ; a = #$06 (projectile speed) + sta $06 + lda #$01 ; a = #$01 (quadrant_aim_dir_01) + sta $0f ; quadrant_aim_dir_lookup_tbl offset + jsr get_quadrant_aim_dir_for_player ; set a to the aim direction within a quadrant + ; based on source position ($09, $08) targeting player index $0a + jsr set_bullet_velocities ; set the projectile X and Y velocities (both high and low) based on register a (#$01) + +boss_eye_adv_routine: + jmp advance_enemy_routine + +eye_projectile_routine_01: + lda #$63 ; a = #$63 + ldy ENEMY_Y_POS,x ; enemy y position on screen + cpy #$48 ; height for projectile to become big & hittable + bcc @continue + jsr enable_enemy_collision ; enable bullet-enemy collision and player-enemy collision checks + lda #$64 ; a = #$64 + +@continue: + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + lda FRAME_COUNTER ; load frame counter + lsr + lsr + and #$03 ; keep bits .... ..xx + tay + lda ENEMY_SPRITE_ATTR,x ; load enemy sprite attributes + and #$3f ; keep bits ..xx xxxx + ora eye_projectile_sprite_attr_tbl,y + sta ENEMY_SPRITE_ATTR,x + jmp update_enemy_pos ; apply velocities and scrolling adjust + +; table for sphere projectile mirroring codes (#$4 bytes) +eye_projectile_sprite_attr_tbl: + .byte $00,$40,$c0,$80 + +; pointer table for rollers (#$5 * #$2 = #$a bytes) +roller_routine_ptr_tbl: + .addr roller_routine_00 ; CPU address $8f8c - initialize y position to #$72 advance routine + .addr roller_routine_01 ; CPU address $8f94 - set appropriate sprite, apply velocity, enable player collision when close, remove when rolled past + .addr enemy_routine_init_explosion ; CPU address $e74b from bank 7 + .addr roller_routine_04 ; CPU address $e7a4 from bank 7 + .addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7 + +; initialize y position to #$72 advance routine +roller_routine_00: + lda #$72 ; a = #$72 (initial y position) + sta ENEMY_Y_POS,x ; enemy y position on screen + jmp advance_enemy_routine + +; set appropriate sprite, apply velocity, enable player collision when close, remove when rolled past +roller_routine_01: + ldy #$03 ; y = #$03 + lda ENEMY_Y_POS,x ; load enemy y position on screen + +@sprite_y_check: + cmp roller_sprite_y_cutoff_tbl-1,y ; see if y position is below cutoff + bcs @found_size ; branch if found size of sprite to use + dey ; roller isn't below y cutoff for current y, got to next higher cutoff + bne @sprite_y_check ; loop to next y offset if y isn't 0 + ; otherwise use smallest sprite (sprite_99) + +@found_size: + tya ; transfer sprite size index to a + clc ; clear carry in preparation for addition + adc #$99 ; add #$99 to get actual sprite code (sprite_99 up to sprite_9c) + sta ENEMY_SPRITES,x ; write roller sprite code to CPU buffer + cpy #$02 ; see how close the roller is to the player + bcc @continue ; branch if roller is relatively far from the player + lda #$2e ; a = #$2e + sta ENEMY_SCORE_COLLISION,x ; set score and collision code to #$2e + +@continue: + jsr update_enemy_pos ; apply velocities and scrolling adjust + lda ENEMY_Y_POS,x ; load enemy y position on screen + cmp #$ac ; see if roller should have collision enabled (close to the player) + bcc @exit ; exit if roller shouldn't yet be enabled for player-roller collision + cmp #$bc ; see if roller should be removed + bcs @remove_enemy ; roller rolled past player, remove roller + jmp enable_enemy_collision ; roller position is between #$ac and #$bc, enable roller-player collision + +@exit: + rts + +@remove_enemy: + jmp remove_enemy ; remove enemy + +; the y positions where the roller sprite changes to a larger size +; #$7c - sprite_9a +; #$8c - sprite_9b +; #$9c - sprite_9c +roller_sprite_y_cutoff_tbl: + .byte $7c,$8c,$9c + +; pointer table for grenades (indoor) (#$6 * #$2 = #$c bytes) +; thrown by indoor soldiers (15) and grenade launchers (17) on indoor levels +grenade_routine_ptr_tbl: + .addr grenade_routine_00 ; CPU address $8fd5 - init ENEMY_VAR_1, ENEMY_VAR_4, and ENEMY_ATTACK_DELAY, advance routine + .addr grenade_routine_01 ; CPU address $8fe8 - sets sprite code, sprite attribute, apply vector to get falling arc, advance routine if appropriate + .addr grenade_routine_02 ; CPU address $907c + .addr enemy_routine_init_explosion ; CPU address $e74b from bank 7 + .addr enemy_routine_explosion ; CPU address $e7b0 from bank 7 + .addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7 + +; init ENEMY_VAR_1, ENEMY_VAR_4, and ENEMY_ATTACK_DELAY, advance routine +grenade_routine_00: + lda ENEMY_Y_POS,x ; load grenade y position + sta ENEMY_VAR_1,x ; store grenade y position in ENEMY_VAR_1 + lda #$00 ; a = #$00 + sta ENEMY_VAR_4,x + lda #$fd ; a = #$fd + sta ENEMY_ATTACK_DELAY,x ; set attack delay to #$fd (253) + +grenade_adv_routine: + jmp advance_enemy_routine + +; sets sprite code, sprite attribute, apply vector to get falling arc, advance routine if appropriate +grenade_routine_01: + ldy #$02 ; y = #$02 (start at farthest point from player) + lda ENEMY_VAR_1,x ; load grenade y position + +; find appropriate y register index based on y position +@determine_sprite_code_loop: + cmp grenade_sprite_tbl_y_cutoff_tbl-1,y ; compare y position to cutoff point + bcs @sprite_code_tbl_found ; branch if found appropriate y value + dey ; y position was less than value, decrement y + bne @determine_sprite_code_loop ; try next higher cutoff point if y isn't #$00 + +@sprite_code_tbl_found: + lda FRAME_COUNTER ; load current frame number + and #$07 ; keep bits 0-2 + bne @check_frame_number ; don't move to next frame if the frame number isn't divisible by #$08 + inc ENEMY_FRAME,x ; increment enemy animation frame number every #$08 frames + +@check_frame_number: + lda ENEMY_FRAME,x ; load enemy animation frame number + cmp grenade_sprite_codes_len_tbl,y ; see if ENEMY_FRAME hasn't gone past last frame + bcc @set_sprite_create_arc ; branch if showing a frame in animation and don't need to loop back + lda #$00 ; loop back to first frame, a = #$00 + +; sets sprite code, sprite attribute, apply vector to get falling arc, advance routine if appropriate +@set_sprite_create_arc: + sta ENEMY_FRAME,x ; set enemy animation frame number (offset into grenade_sprite_codes_xx) + lda grenade_sprite_codes_len_tbl,y ; load the number of sprites in the grenade_sprite_codes_xx table + sta $0a ; store value in $09 + tya ; transfer the sprite code table index to a + asl ; double sprite code table index since each entry has #$92 bytes + tay ; transfer sprite code table index back to y + lda grenade_sprite_codes_ptr_tbl,y ; load low byte of grenade_sprite_codes_xx + sta $08 ; store in $08 + lda grenade_sprite_codes_ptr_tbl+1,y ; load high byte of grenade_sprite_codes_xx + sta $09 ; store in $09 + ldy ENEMY_FRAME,x ; load enemy animation frame number + lda ($08),y ; load sprite code from grenade_sprite_codes_xx + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + tya ; transfer sprite code table index back to y + clc ; clear carry in preparation for addition + adc $0a ; add offset to get to corresponding sprite attribute + ; each grenade_sprite_codes_xx contains both sprite codes and sprite attribute values + ; $0a is the number of (sprite code, sprite attribute) pairs + tay ; transfer offset back to y + lda ($08),y ; load grenade sprite attribute + sta ENEMY_SPRITE_ATTR,x ; store grenade sprite attribute + lda ENEMY_VAR_4,x + clc ; clear carry in preparation for addition + adc #$0c ; add #$0c into ENEMY_VAR_4 + sta ENEMY_VAR_4,x + lda ENEMY_ATTACK_DELAY,x + adc #$00 + sta ENEMY_ATTACK_DELAY,x + jsr set_enemy_falling_arc_pos ; set X and Y position to follow a falling arc + lda ENEMY_VAR_3,x + bpl grenade_adv_routine + rts + +; table for delays before changing sprite (#$2 bytes) +grenade_sprite_tbl_y_cutoff_tbl: + .byte $80,$90 + +; table for sprite attribute offset (related to z position and palette) (#$3 bytes) +grenade_sprite_codes_len_tbl: + .byte $04,$08,$08 + +; pointer table for grenade sprites and palettes (#$3 * #$2 = #$6 bytes) +grenade_sprite_codes_ptr_tbl: + .addr grenade_sprite_codes_00 ; CPU address $9054 + .addr grenade_sprite_codes_01 ; CPU address $905c + .addr grenade_sprite_codes_02 ; CPU address $906c + +; table for grenade sprites and sprite attributes (closest to player) (#$8 bytes) +; sprite_a8, sprite_a9, sprite_a6 +; sprite attributes +; * $c0 - flip horizontally and vertically +grenade_sprite_codes_00: + .byte $a8,$a9,$a6,$a9 ; sprite codes + .byte $00,$00,$00,$c0 ; sprite attributes + +; table for grenade sprites and sprite attributes (close to player) (#$10 bytes) +; sprite_a4, sprite_a5, sprite_a6, sprite_a6, sprite_a7 +; sprite attributes +; * $c0 - flip horizontally and vertically +grenade_sprite_codes_01: + .byte $a4,$a5,$a6,$a5,$a4,$a7,$a6,$a7 ; sprite codes + .byte $00,$00,$00,$c0,$c0,$00,$00,$c0 ; sprite attributes + +; table for grenade sprites and sprite attributes (farthest from player) (#$10 bytes) +; sprite_a0, sprite_a1, sprite_a2, sprite_a3 +; sprite attributes +; * $c0 - flip horizontally and vertically +grenade_sprite_codes_02: + .byte $a0,$a1,$a2,$a1,$a0,$a3,$a2,$a3 ; sprite codes + .byte $00,$00,$00,$c0,$c0,$00,$00,$c0 ; sprite attributes + +; play sound, set +grenade_routine_02: + lda #$24 ; a = #$24 (sound_24) + jsr play_sound ; play explosion sound + lda #$ac ; a = #$ac + sta ENEMY_Y_POS,x ; set y position to #$ac (bottom of screen where explosion occurs) + jsr mortar_shot_routine_03 ; update collision to allow player-enemy collision + jmp advance_enemy_routine ; go to enemy_routine_init_explosion + +; pointer table for wall turret (#$8 * #$2 = #$10 bytes) +wall_turret_routine_ptr_tbl: + .addr wall_turret_routine_00 ; CPU address $909c - set initial delay and advance routine + .addr wall_turret_routine_01 ; CPU address $90a8 - draw 'wall turret / core - closed' super-tile, wait for delay, advance routine + .addr wall_turret_routine_02 ; CPU address $90c1 - opening animation and enabling collision + .addr wall_turret_routine_03 ; CPU address $90ee - aim and fire turret + .addr wall_turret_routine_04 ; CPU address $9108 - enemy destroyed routine - draw 'core - destroyed' nametable tiles, advance routine + .addr wall_core_routine_05 ; CPU address $e737 from bank 7 + .addr enemy_routine_explosion ; CPU address $e7b0 from bank 7 + .addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7 + +; set initial delay and advance routine +wall_turret_routine_00: + ldy ENEMY_ATTRIBUTES,x ; load enemy attributes + lda wall_turret_initial_delay_tbl,y + bne set_turret_delay_adv_routine + +; table for wall turret deployment delays (#$4 bytes) +wall_turret_initial_delay_tbl: + .byte $50,$80,$b0,$f0 + +; draw 'wall turret / core - closed' super-tile, wait for delay, advance routine +wall_turret_routine_01: + lda ENEMY_VAR_1,x ; load byte specifying + bne @wait_for_delay_adv_routine ; branch if already set 'wall turret / core - closed' super-tile + lda #$84 ; level_2_4_tile_animation offset (#$04) (wall turret / core - closed) + jsr update_enemy_nametable_tiles ; draw the nametable tiles from level_2_4_tile_animation (a) at the enemy position + bcs @wait_for_delay_adv_routine ; branch if unable to draw super-tile. Don't set super-tile drawn byte (ENEMY_VAR_1) + inc ENEMY_VAR_1,x ; set byte specifying that the 'wall turret / core - closed' super-tile was drawn + +@wait_for_delay_adv_routine: + dec ENEMY_ANIMATION_DELAY,x ; decrement animation delay as set by ENEMY_ATTRIBUTES (wall_turret_initial_delay_tbl) + bne wall_turret_exit ; exit if delay hasn't elapsed + lda #$01 ; animation delay elapsed, set new animation delay, and advance routine to wall_turret_routine_02 + +set_turret_delay_adv_routine: + jmp set_anim_delay_adv_enemy_routine_00 ; set ENEMY_ANIMATION_DELAY to a and advance enemy routine + +; opening animation and enabling collision +wall_turret_routine_02: + dec ENEMY_ANIMATION_DELAY,x ; decrement animation delay + bne wall_turret_exit ; exit if delay hasn't elapsed + lda #$08 ; a = #$08 (delay between deployment frames) + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + ldy ENEMY_FRAME,x ; load enemy animation frame number + lda wall_turret_tile_animation_tbl,y ; load level_2_4_tile_animation offset + jsr update_nametable_tiles_set_delay ; draw the nametable tiles from level_2_4_tile_animation (a) at the enemy position + ; set animation delay to #$01 if drawing was successful + bcs wall_turret_exit ; exit if unable to draw nametable tiles + inc ENEMY_FRAME,x ; increment enemy animation frame number + lda ENEMY_FRAME,x ; load enemy animation frame number + cmp #$03 ; see if last frame of opening animation frames + bcc wall_turret_exit ; exit if not finished opening + jsr enable_bullet_enemy_collision ; allow bullets to collide (and stop) upon colliding with wall turret + lda #$80 ; a = #$80 (initial delay before attacking) + sta ENEMY_ATTACK_DELAY,x ; set attack delay + +wall_turret_adv_routine: + jmp advance_enemy_routine ; advance to next routine + +; table for wall turret opening tile super-tile codes (#$3 bytes) +; offsets into level_2_4_tile_animation +; #$85 - wall turret / core - opening frame 1 +; #$88 - wall turret - opening frame 2 +; #$89 - wall turret - open +wall_turret_tile_animation_tbl: + .byte $85,$88,$89 + +; aim and fire turret +wall_turret_routine_03: + dec ENEMY_ATTACK_DELAY,x ; decrement attack delay + bne wall_turret_exit ; exit if attack delay hasn't elapsed + lda #$50 ; ready to attack, set next round attack delay to #$50 + sta ENEMY_ATTACK_DELAY,x ; set next round of attack delay to #$50 + jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01) + sty $0a ; store closest player in $0a + jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position + lda #$60 ; a = #$60 + ldy #$04 ; bullet speed code + jmp aim_and_create_enemy_bullet ; get firing dir based on enemy ($08, $09) and player pos ($0b, $0a) + ; and creates bullet (type a) with speed y if appropriate + +wall_turret_exit: + rts + +; draw 'core - destroyed' nametable tiles, advance routine +wall_turret_routine_04: + lda #$83 ; a = #$83 (core - destroyed) + jsr update_enemy_nametable_tiles ; draw the nametable tiles from level_2_4_tile_animation (a) at the enemy position + bcc wall_turret_adv_routine ; advance routine to wall_core_routine_05 + rts ; exit if unable to draw 'core - destroyed' super-tile + +; pointer table for base core (#$a * #$2 = #$14 bytes) +wall_core_routine_ptr_tbl: + .addr wall_core_routine_00 ; CPU address $9124 (init variables) + .addr wall_core_routine_01 ; CPU address $9167 (set core plating) + .addr wall_core_routine_02 ; CPU address $91a0 (wall core opening) + .addr wall_core_routine_03 ; CPU address $91cf (fire at player if conditions met) + .addr wall_core_routine_04 ; CPU address $91fb (update nametable for destroyed plating/destroyed core, reset HP) + .addr wall_core_routine_05 ; CPU address $e737 (initialize explosion) + .addr enemy_routine_explosion ; CPU address $e7b0 + .addr wall_core_routine_07 ; CPU address $923b (core destroyed, see if last core, if so destroy all enemies) + .addr wall_core_routine_08 ; CPU address $9251 (boss appears after or during this routine) + .addr wall_core_routine_09 ; CPU address $92ae (wait for explosion delay, mark screen cleared, remove enemy) + +; initializes variables like HP, collision box code, destruction animation sequence +wall_core_routine_00: + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + lsr ; shift right + lsr ; shift right, removing opening delay + and #$03 ; keep bits .... ..xx (core size, and whether or not it is plated) + tay ; transfer value to y + lsr ; shift bit 2 of ENEMY_ATTRIBUTES into carry + lda #$25 ; a = #$25 + bcc @continue ; branch if not plated + lda #$04 ; plated, set bullet collision sound code to #$04 + sta ENEMY_VAR_A,x ; core plating bullet collision sound code (see bullet_hit_sound_tbl) + lda #$22 ; score code #$02, collision box code #$02 + +@continue: + sta ENEMY_SCORE_COLLISION,x ; set score code and collision box code + lda wall_core_hp_tbl,y ; load ENEMY_HP based on core type (size and plating) + sta ENEMY_HP,x ; set enemy hp + lda wall_core_init_dmg_tile_anim_tbl,y ; load correct initial tile update animation offset (wall_core_tile_anim_tbl) + ; based on core type (size and plating) for destruction animation sequence + sta ENEMY_VAR_2,x ; set current wall_core_tile_anim_tbl offset + ldy #$00 ; default #$20 core opening delay + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + and #$04 ; keep bit 2 (whether core is plated) + bne @set_opening_delay_adv_routine ; branch if core is plated, use default #$20 opening delay + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + and #$03 ; keep bits 0 and 1 (core opening delay) + tay ; transfer opening delay offset to y + +@set_opening_delay_adv_routine: + lda core_opening_delay,y + bne wall_core_adv_routine ; always branch + ; set animation delay and advance to wall_core_routine_01 + +; table for core opening delays (#$4 bytes) +; the larger the number, the longer the delay (#$f0 is largest delay) +core_opening_delay: + .byte $20,$80,$b0,$f0 + +; table for indoor/base wall core hp (#$4 bytes) +; #$08 - ENEMY_HP for normal-sized core not plated +; #$05 - ENEMY_HP for normal-sized plated core +; #$10 - ENEMY_HP for big core, not plated +; #$05 - ENEMY_HP for big core, plated (not used) +wall_core_hp_tbl: + .byte $08,$05,$10,$05 + +; table for initial cracked core tiles when being attacked (#$4 bytes) +; as core is destroyed nametable tiles are updated in descending sequence +; from wall_core_tile_anim_tbl +; #$00 - normal-sized core not plated - #$83 core - destroyed +; #$03 - normal-sized plated core - #$81 core plating - cracked +; #$00 - big core, not plated - #$83 core - destroyed +; #$03 - big core, plated (not used) - #$81 core plating - cracked +wall_core_init_dmg_tile_anim_tbl: + .byte $00,$03,$00,$03 + +; set core plating +wall_core_routine_01: + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + and #$08 ; keep bit 3 (whether or not is a large core) + bne @wait_for_delay_adv_routine ; branch if big core + lda ENEMY_VAR_1,x ; see if nametable has been updated + bne @wait_for_delay_adv_routine + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + lsr + lsr + lsr + lda #$84 ; a = #$84 (level_2_4_tile_animation offset) wall turret / core - closed + bcc @update_nametable ; branch if not plated core + lda #$80 ; plated core, set a = #$80 (level_2_4_tile_animation offset) core plating + +@update_nametable: + jsr update_enemy_nametable_tiles ; draw the nametable tiles from level_2_4_tile_animation (a) at the enemy position + bcs @wait_for_delay_adv_routine ; branch if unable to update nametable tiles + inc ENEMY_VAR_1,x ; mark nametable as updated + +@wait_for_delay_adv_routine: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne wall_core_exit_00 + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + and #$04 ; keep bit 2 (whether or not is a plated core) + beq @set_delay_adv_routine ; branch if not a plated core, no need to enable collision + jsr wall_core_enable_collision_adv_routine ; enable collision and move to wall_core_routine_02 + +@set_delay_adv_routine: + lda #$01 ; a = #$01, attack and animation delay + sta ENEMY_ATTACK_DELAY,x + lda #$01 ; a = #$01 (unneeded) + +wall_core_adv_routine: + jmp set_anim_delay_adv_enemy_routine_00 ; set ENEMY_ANIMATION_DELAY to a and advance enemy routine + +wall_core_routine_02: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne wall_core_exit_00 + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + and #$08 ; keep bit 3 (whether or not is a large core) + bne wall_core_enable_collision_adv_routine ; enable collision and move to wall_core_routine_03, large core doesn't have opening delay + lda #$08 ; a = #$08 (delay between frames when core opens) + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + ldy ENEMY_FRAME,x ; enemy animation frame number + lda wall_core_nametable_update_tbl,y + jsr update_nametable_tiles_set_delay ; draw the nametable tiles from level_2_4_tile_animation (a) at the enemy position + ; set animation delay to #$01 if drawing was successful + bcs wall_core_exit_00 ; exit if unable to update nametable tiles + inc ENEMY_FRAME,x ; increment enemy animation frame number + lda ENEMY_FRAME,x ; load enemy animation frame number + cmp #$03 ; see if last frame (fully open) + bcc wall_core_exit_00 ; exit if not fully open yet + +wall_core_enable_collision_adv_routine: + jsr enable_bullet_enemy_collision ; allow bullets to collide (and stop) upon colliding with wall core + jmp advance_enemy_routine + +; table for core opening update nametable tiles (offsets into level_2_4_tile_animation) (#43 bytes) +; #$85 wall turret / core - opening frame 1 +; #$86 core - opening frame 2 +; #$87 core - open +wall_core_nametable_update_tbl: + .byte $85,$86,$87 + +; fire at player if conditions met +wall_core_routine_03: + lda INDOOR_ENEMY_ATTACK_COUNT ; load the total number of enemy attack rounds for the screen + cmp #$07 ; number of cycles for core to start shooting + bcc wall_core_exit_00 ; don't attack, not enough rounds of soldier attacks have happened + lda ENEMY_VAR_2,x ; load current nametable index + bne wall_core_exit_00 ; exit if wall core is still plated + lda ENEMY_Y_POS,x ; load enemy y position on screen + cmp #$70 ; check if core is not too low + bcs wall_core_exit_00 ; exit if core is too low (player needs to crouch to attack) + dec ENEMY_ATTACK_DELAY,x ; decrement delay between attacks + bne wall_core_exit_00 ; exit if attack delay hasn't elapsed + lda #$28 ; a = #$28 (delay between bullets) + sta ENEMY_ATTACK_DELAY,x ; reset attack delay + jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01) + sty $0a ; store closest player in $0a + jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position + lda #$60 ; a = #$60 + ldy #$05 ; y = #$05 (wall turret bullet speed code) + jmp aim_and_create_enemy_bullet ; get firing dir based on enemy ($08, $09) and player pos ($0b, $0a) + ; and creates bullet (type a) with speed y if appropriate + +wall_core_exit_00: + rts + +; update nametable for destroyed plating/destroyed core, reset HP +wall_core_routine_04: + ldy ENEMY_VAR_2,x ; load current state of nametable tiles + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + and #$08 ; keep bit 3 (whether or not is a large core) + beq @continue ; branch if a normal-sized core + tya ; large core, transfer ENEMY_VAR_2 to a + clc ; clear carry in preparation for addition + adc #$04 ; add #$04 to set correct large core nametable tiles + tay ; transfer a back to y to use as animation offset + +@continue: + lda wall_core_tile_anim_tbl,y ; load appropriate level_2_4_tile_animation offset to update tiles + jsr update_enemy_nametable_tiles ; draw the nametable tiles from level_2_4_tile_animation (a) at the enemy position + bcs wall_core_exit_00 ; exit if unable to update nametable tiles + ldy #$05 ; y = #$05 (hp for 2nd and 3rd levels of plating) + dec ENEMY_VAR_2,x ; decrement to next level of destroyed plating + bmi @adv_routine ; go to wall_core_routine_05 if plating has been destroyed + bne @set_hp_go_routine_03 ; plating not yet destroyed, set HP to #$05 and set enemy routine to wall_core_routine_03 + lda #$00 ; plating destroyed, a = #$00 + sta ENEMY_VAR_A,x ; normal collision sound since bullet no longer colliding with plating (see bullet_hit_sound_tbl) + lda #$25 ; a = #$25 (updating collision box code) + sta ENEMY_SCORE_COLLISION,x ; score code #$02, collision box code #$05 + ldy #$08 ; y = #$08 (hp for core after plating destroyed) + +@set_hp_go_routine_03: + tya ; transfer HP to a + sta ENEMY_HP,x ; set enemy hp + lda #$04 ; a = #$04 + jmp set_enemy_routine_to_a ; set enemy routine index to wall_core_routine_03 + +@adv_routine: + jmp advance_enemy_routine + +; nametable update offsets into level_2_4_tile_animation for destroyed core (#$8 bytes) +; #$81 core plating - cracked +; #$82 core plating - more cracks +; #$83 core - destroyed +; #$87 core - open +; #$8a big core +wall_core_tile_anim_tbl: + .byte $83,$87,$82,$81,$83,$8a,$82,$81 + +; core destroyed, see if last core, if so destroy all enemies +wall_core_routine_07: + dec WALL_CORE_REMAINING ; decrement remaining cores/bosses to destroy required to allow advance to next screen + bne wall_core_remove_enemy ; branch to remove enemy if other wall cores still need to be destroyed + lda #$00 ; all wall cores have been destroyed, set a = #$00 (sprite_00) + sta ENEMY_SPRITES,x ; hide enemy (invisible sprite) + jsr destroy_all_enemies ; destroy any other enemies on screen + lda #$03 ; a = #$03 + sta ENEMY_VAR_3,x ; + lda #$04 ; a = #$04 (animation delay) + +wall_core_set_delay_adv_routine: + jmp set_anim_delay_adv_enemy_routine_00 ; set ENEMY_ANIMATION_DELAY to a and advance enemy routine + +; removes back wall +wall_core_routine_08: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne wall_core_wait_play_sound + ldy ENEMY_VAR_3,x ; load current destroyed back wall super-tile index to load + lda wall_core_y_pos_tbl,y ; load destroyed back wall quadrant y position + sta ENEMY_Y_POS,x ; enemy y position on screen + lda wall_core_x_pos_tbl,y ; load destroyed back wall quadrant x position + sta ENEMY_X_POS,x ; set enemy x position on screen + lda wall_core_update_supertile_tbl,y ; load level update super-tile to draw (level_xx_nametable_update_supertile_data) + jsr draw_enemy_supertile_a ; draw super-tile a at position (ENEMY_X_POS, ENEMY_Y_POS) + bcs @set_delay_exit ; exit if unable to update super-tile + ldy ENEMY_VAR_3,x ; load current destroyed back wall super-tile index + tya ; transfer to y + lsr + lsr + lda #$fc ; a = #$fc + bcc @continue ; branch if not updating bottom super-tile + lda #$f4 ; a = #$f4 + +@continue: + clc ; clear carry in preparation for addition + adc wall_core_y_pos_tbl,y ; subtract from wall position to set explosion animation y position + sta $08 ; store y position of explosion in $08 + lda wall_core_x_pos_tbl,y ; subtract from wall position to set explosion animation x position + sta $09 ; store x position of explosion in $09 + jsr create_explosion_89 ; create explosion type #$89 at ($09, $08) + dec ENEMY_VAR_3,x ; move to next destroyed wall quadrant + bmi wall_core_set_delay_10_adv_routine ; advance routine if finished updated destroyed back wall + +@set_delay_exit: + lda #$01 ; a = #$01 + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + +wall_core_exit_01: + rts + +wall_core_set_delay_10_adv_routine: + lda #$10 ; a = #$10 + bne wall_core_set_delay_adv_routine + +wall_core_wait_play_sound: + lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter + cmp #$01 + bne wall_core_exit_01 + lda #$25 ; a = #$25 (small boom sound) + jmp play_sound + +; tables related to back wall cleared explosions and replacing super-tiles +; indexes into level_2_nametable_update_supertile_data/level_4_nametable_update_supertile_data + ; #$00 - top left back wall destroyed + ; #$01 - top right back wall destroyed + ; #$02 - bottom left back wall destroyed + ; #$03 - bottom right back wall destroyed +wall_core_update_supertile_tbl: + .byte $02,$03,$01,$00 + +; table for destroyed back wall super-tile y position (#$4 bytes) +wall_core_y_pos_tbl: + .byte $78,$78,$58,$58 + +; table for destroyed back wall super-tile x position (#$4 bytes) +wall_core_x_pos_tbl: + .byte $70,$90,$90,$70 + +; wait for explosion delay, mark screen cleared, remove enemy +wall_core_routine_09: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne wall_core_exit_01 ; exit if explosion animations aren't completed + lda #$01 ; a = #$01 (mark screen cleared) + sta INDOOR_SCREEN_CLEARED ; indoor screen cleared flag (0 = not cleared; 1 = cleared) + +wall_core_remove_enemy: + jmp remove_enemy ; remove enemy + +indoor_soldier_routine_ptr_tbl: + .addr indoor_soldier_routine_00 ; CPU address $92c8 + .addr indoor_soldier_routine_01 ; CPU address $92d5 + .addr shared_enemy_routine_00 ; CPU address $9346 - soldier has been hit by player bullet + .addr shared_enemy_routine_01 ; CPU address $9360 - perform enemy hit by bullet animation, then advance routine + .addr enemy_routine_init_explosion ; CPU address $e74b from bank 7 + .addr shared_enemy_routine_03 ; CPU address $e7aa from bank 7 - show explosion_type_02 + .addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7 + +; initializes indoor soldier +; * sets position, velocity and attack delay +indoor_soldier_routine_00: + ldy #$00 ; y = #$00 + jsr init_indoor_enemy_pos_and_vel ; initialize indoor soldier soldier velocity and position + lda #$08 ; a = #$08 (delay before attacking, running guy) + sta ENEMY_ATTACK_DELAY,x ; set delay between attacks + jmp advance_enemy_routine + +; wait for attack delay to elapse, then fire weapon based on ENEMY_ATTRIBUTES +indoor_soldier_routine_01: + jsr init_sprite_from_frame ; determine enemy sprite based on ENEMY_FRAME, flip sprite horizontally if running left + jsr apply_enemy_velocity_set_bg_priority ; apply enemy velocity to position (remove if off screen), set bg priority + dec ENEMY_ATTACK_DELAY,x ; decrement delay between attacks + bne @exit ; delay between attacks not yet elapsed, just exit + lda #$10 ; ready to attack, first reset attack delay to #$10 + sta ENEMY_ATTACK_DELAY,x ; set delay between attacks + lda ENEMY_X_POS,x ; load enemy x position on screen + cmp #$68 ; location of disappearance, from left + bcc @exit ; enemy too far to the left to attack, exit + cmp #$98 ; location of disappearance, from right + bcs @exit ; enemy too far to the right to attack, exit + lda ENEMY_ATTRIBUTES,x ; load the enemy weapon type and enemy direction + lsr ; shift enemy direction bit to carry (not used to determine enemy weapon type) + and #$03 ; keep bits .... ..xx that specify weapon type + tay ; transfer weapon type to y + bne @continue ; not a regular bullet weapon type (#$00), see if grenade or a roller + jmp create_indoor_bullet ; fire a regular indoor bullet + +@continue: + dey ; decrement enemy weapon type + bne @create_roller ; not a grenade, so create a roller (ENEMY_TYPE #$11) + inc ENEMY_VAR_1,x ; increment total grenades fired + lda ENEMY_VAR_1,x ; load total grenades fired + lsr ; shift bit 0 to the carry flag + bcc @exit ; exit every other time, effectively doubling ENEMY_ATTACK_DELAY + jmp enemy_launch_grenade ; create a grenade (ENEMY_TYPE #$12) + +; creates a roller x pixels down from indoor soldier to roll towards player +@create_roller: + ldy #$08 ; set vertical offset from enemy position (param for add_with_enemy_pos) + lda #$00 ; set horizontal offset from enemy position (param for add_with_enemy_pos) + jsr add_with_enemy_pos ; stores absolute screen x position in $09, and y position in $08 + jmp create_roller ; create the roller + +@exit: + rts + +; sets the enemy sprite and flips it if necessary depending on enemy direction +init_sprite_from_frame: + lda FRAME_COUNTER ; load frame counter + and #$03 ; keep bits .... ..xx + bne @set_sprite_and_attr ; set the enemy sprite code, and sprite attribute + inc ENEMY_FRAME,x ; increment enemy animation frame number + lda ENEMY_FRAME,x ; load enemy animation frame number + cmp #$03 ; indoor enemies have 3 frames to cycle through + bcc @set_frame_sprite_and_attr ; set the enemy sprite based on frame, and flip sprite if necessary + lda #$00 ; a = #$00 + +@set_frame_sprite_and_attr: + sta ENEMY_FRAME,x ; update enemy animation frame number + +@set_sprite_and_attr: + lda ENEMY_FRAME,x ; load enemy animation frame number + clc ; clear carry in preparation for addition + adc #$93 ; determine sprite based on enemy frame + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + lda ENEMY_SPRITE_ATTR,x ; load enemy sprite attributes + ldy ENEMY_X_VELOCITY_FAST,x ; see if enemy is running left + bmi @horizontal_flip_sprite ; branch if enemy is traveling left, flip sprite horizontally + and #$bf ; keep bits x.xx xxxx (no horizontal flipping) + bpl @set_sprite_attr + +@horizontal_flip_sprite: + ora #$40 ; set bits .x.. .... (flip sprite horizontally) + +@set_sprite_attr: + sta ENEMY_SPRITE_ATTR,x ; set updated sprite attribute + +; also exit for +shared_enemy_routine_01_exit: + rts + +; used by the indoor soldiers: #$15 - Indoor Soldier, #$16 - Jumping Soldier, #$17 - Grenade Launcher, #$18 - Group of Four Soldiers +; soldier has been hit by player bullet +shared_enemy_routine_00: + jsr disable_enemy_collision ; prevent player enemy collision check and allow bullets to pass through enemy + lda #$96 ; a = #$96 (sprite_96) indoor soldier hit by bullet + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + lda #$80 ; a = #$80 + sta ENEMY_Y_VELOCITY_FRACT,x ; set negative velocity for initial hit reaction (slow .5) + lda #$fd ; a = #$fd + sta ENEMY_Y_VELOCITY_FAST,x ; set negative velocity for initial hit reaction (fast -3) + jsr set_enemy_x_velocity_to_0 ; set x velocity to zero + lda #$10 ; a = #$10 + jmp set_anim_delay_adv_enemy_routine_00 ; set ENEMY_ANIMATION_DELAY to #$10 and advance enemy routine + +; perform enemy hit by bullet animation +shared_enemy_routine_01: + jsr update_enemy_pos ; apply velocities and scrolling adjust + lda #$38 ; a = #$38 (gravity when flying up, indoor) + jsr add_a_to_enemy_y_fract_vel ; add a to enemy y fractional velocity + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne shared_enemy_routine_01_exit + jmp advance_enemy_routine + +; pointer table for jumping guy (#$8 * #$2 = #$10 bytes) +jumping_soldier_routine_ptr_tbl: + .addr jumping_soldier_routine_00 ; CPU address $9380 - see if red soldier, if so mark flag, advance routine + .addr jumping_soldier_routine_01 ; CPU address $93a5 - set sprite, and perform jump animation + .addr shared_enemy_routine_00 ; CPU address $9346 - soldier has been hit by player bullet + .addr shared_enemy_routine_01 ; CPU address $9360 - perform enemy hit by bullet animation, then advance routine + .addr jumping_soldier_routine_04 ; CPU address $9437 - soldier destroyed, if red soldier play explosion + .addr enemy_routine_init_explosion ; CPU address $e74b from bank 7 + .addr shared_enemy_routine_03 ; CPU address $e7aa from bank 7 - show explosion_type_02 + .addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7 + +; see if red soldier, if so mark flag, advance routine +jumping_soldier_routine_00: + lda ENEMY_ATTRIBUTES,x ; load the ENEMY_ATTRIBUTES to get bit 1 + lsr ; shift bit 0 into carry to disregard + lsr ; shift bit 1 into carry. This specifies if jumping soldier is red (drops a weapon item) + bcc @init_enemy_adv_routine ; branch if jumping soldier is not red + lda INDOOR_RED_SOLDIER_CREATED ; jumping soldier is red, see if one has already been created + bne @clear_red_soldier_continue ; jumping red soldier has been created, don't create another + lda INDOOR_ENEMY_ATTACK_COUNT ; load the total number of enemy attack rounds for the screen + beq @clear_red_soldier_continue ; don't have red jumping soldier on the first round of attacks + lda #$01 ; a = #$01 + sta INDOOR_RED_SOLDIER_CREATED ; create a red jumping soldier, mark as created to prevent further creation + bne @init_enemy_adv_routine ; always jump, create red jumping soldier + +@clear_red_soldier_continue: + lda ENEMY_ATTRIBUTES,x ; load the original ENEMY_ATTRIBUTES + and #$fd ; keep bits xxxx xx.x (drop red soldier status) + sta ENEMY_ATTRIBUTES,x ; update ENEMY_ATTRIBUTES to no longer create a red jumping soldier + +@init_enemy_adv_routine: + ldy #$02 ; y = #$02 (jumping soldier) + jsr init_indoor_enemy_pos_and_vel ; initialize indoor jumping soldier enemy velocity and position + jmp advance_enemy_routine ; advance routine to jumping_soldier_routine_01 + +; set sprite, and perform jump animation +jumping_soldier_routine_01: + lda #$97 ; a = #$97 (sprite_97 jumping man in air) + ldy ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter + beq @set_sprite ; branch to continue if animation delay is #$00 + lda #$93 ; a = #$93 (sprite_93 jumping man running) + cpy #$04 ; compare animation delay to #$04 + bcc @set_sprite ; continue if animation delay is less than #$04 + lda #$98 ; animation delay is > #404, set a = #$98 (sprite_98 jumping man running) + +@set_sprite: + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + lsr + lsr + lda #$00 ; a = #$00 (default palette) + bcc @set_sprite_attr + lda #$05 ; sprite palette #$01 (red jumping man) + ; #$00-#$03 (green normal), #$04 (blue), #$05 (red) + ; #$06 (fire), #$07 (green with green hair) + +@set_sprite_attr: + sta $08 + lda ENEMY_SPRITE_ATTR,x ; load enemy sprite attributes + ldy ENEMY_X_VELOCITY_FAST,x ; load enemy x velocity (direction) + bmi @flip_spite ; branch if jumping left + and #$bf ; jumping towards the right, strip bit 6 (don't horizontally flip sprite) + bpl @continue ; always branch + +@flip_spite: + ora #$40 ; set bit 6 (flip sprite horizontally) + +@continue: + and #$f8 ; strip sprite palette bits + ora $08 ; set sprite palette bits as calculated above (#$05 if jumping soldier is red, #$00 otherwise) + sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes + lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter + beq @apply_y_vel + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + and #$02 ; keep bits .... ..x. + bne @exit + lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter + cmp #$08 + bne @exit + jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01) + sty $0a ; store closest player in $0a + jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position + lda #$60 ; a = #$60 + ldy #$04 ; y = #$04 (jumping guy bullet speed code) + jmp aim_and_create_enemy_bullet ; get firing dir based on enemy ($08, $09) and player pos ($0b, $0a) + ; and creates bullet (type a) with speed y if appropriate + +@apply_y_vel: + jsr apply_enemy_velocity_set_bg_priority ; apply enemy velocity to position (remove if off screen), set bg priority + ldy ENEMY_VAR_1,x ; load current jumping_soldier_y_vel_tbl velocity + lda jumping_soldier_y_vel_tbl,y ; load actual amount to change y velocity + clc ; clear carry in preparation for addition + adc ENEMY_Y_POS,x ; add distance to current jumping soldier y position + sta ENEMY_Y_POS,x ; set new enemy y position on screen + inc ENEMY_VAR_1,x ; increment jumping_soldier_y_vel_tbl for next frame + lda ENEMY_VAR_1,x ; load new jumping_soldier_y_vel_tbl + cmp #$14 ; check to see if finished jump + bcc @exit ; exit if haven't yet finished jump + lda #$00 ; finished jumping, set a = #$00 + sta ENEMY_VAR_1,x ; reset jumping_soldier_y_vel_tbl index to start next jump + lda #$10 ; a = #$10 + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + +@exit: + rts + +; table for jumping guy y velocities when jumping (#$14 bytes) +jumping_soldier_y_vel_tbl: + .byte $fd,$fd,$fe,$fe,$fe,$ff,$ff,$ff,$00,$00,$00,$00,$01,$01,$01,$02 + .byte $02,$02,$03,$03 + +jumping_soldier_routine_04: + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + and #$02 ; keep bits .... ..x. + beq @adv_routine ; not a red jumping soldier, just advance routine + lda ENEMY_X_POS,x ; red jumping soldier, enemy x position on screen + cmp #$64 ; check if on left side + bcc @adv_routine ; if too far to the left (behind wall), just advance routine + cmp #$9c ; check if on right side + bcs @adv_routine ; if too far to the right (behind wall), just advance routine + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + bmi @adv_routine ; if bit 7 is set, just advance routine (not sure when this happens) !(WHY?) + lsr ENEMY_ATTRIBUTES,x + lsr ENEMY_ATTRIBUTES,x ; zero out the ENEMY_ATTRIBUTES (not sure why this is needed) !(WHY?) + jmp play_explosion_sound ; play explosion + +@adv_routine: + jmp advance_enemy_routine + +; pointer table for seeking guy (#$7 * #$2 = #$e bytes) +grenade_launcher_routine_ptr_tbl: + .addr grenade_launcher_routine_00 ; CPU address $9468 + .addr grenade_launcher_routine_01 ; CPU address $9479 + .addr shared_enemy_routine_00 ; CPU address $9346 - soldier has been hit by player bullet + .addr shared_enemy_routine_01 ; CPU address $9360 - perform enemy hit by bullet animation, then advance routine + .addr enemy_routine_init_explosion ; CPU address $e74b from bank 7 + .addr shared_enemy_routine_03 ; CPU address $e7aa from bank 7 + .addr grenade_launcher_routine_06 ; CPU address $9529 + +grenade_launcher_routine_00: + lda #$01 ; a = #$01 + sta GRENADE_LAUNCHER_FLAG ; flag that there is a grenade launcher on screen to prevent other enemies from being generated + jsr set_enemy_var_2_to_closest_x_player ; set closest player (#$00 or #$01) in ENEMY_VAR_2, a, and y + ldy #$06 ; y = #$06 (offset specifying grenade launcher configuration) + jsr init_indoor_enemy_pos_and_vel ; initialize indoor grenade launcher enemy velocity and position + lda #$20 ; a = #$20 + jmp set_anim_delay_adv_enemy_routine_00 ; set ENEMY_ANIMATION_DELAY to #$20 and advance enemy routine + +grenade_launcher_routine_01: + lda ENEMY_VAR_3,x + beq grenade_launcher_apply_vel_aim ; apply velocities, if animation timer elapsed, aim and set number of grenades to fire + lda #$96 ; a = #$96 (sprite_96) - grenade launcher + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + dec ENEMY_ANIMATION_DELAY,x ; decrement animation delay + bne @launch_grenade_if_appropriate ; fire if animation timer has elapsed + lda #$08 ; a = #$08 + sta ENEMY_ANIMATION_DELAY,x ; set delay between bullets + lda #$00 ; a = #$00 + sta ENEMY_VAR_3,x ; + lda ENEMY_X_POS,x ; load enemy x position on screen + jsr find_far_segment_for_a ; find the appropriate velocity code, given the X position + sta $0a ; store enemy segment code in $0a + jsr set_enemy_var_2_to_closest_x_player ; set closest player (#$00 or #$01) in ENEMY_VAR_2, a, and y + jsr find_close_segment ; get segment number for where player is on screen (#$06 = farthest left, #$00 = farthest right) + cmp $0a ; compare player and enemy segment + lda #$00 ; a = #$00 + bcc @set_direction ; branch if player is to the right of the enemy + lda #$80 ; player to left of enemy + +@set_direction: + eor ENEMY_X_VELOCITY_FAST,x ; exclusive or #$80 and the current fast velocity + bpl @exit ; branch if enemy needs to change direction to seek player + jsr reverse_enemy_x_direction ; reverse x direction + +@exit: + rts + +@launch_grenade_if_appropriate: + lda ENEMY_VAR_1,x ; see if grenade launcher is in same horizontal segment as player and ready to fire + beq @launch_grenade_exit ; exit if not ready to fire + dec ENEMY_ATTACK_DELAY,x ; decrement delay between attacks + bne @launch_grenade_exit ; exit if attack delay hasn't elapsed + lda #$14 ; a = #$14 (delay between grenades, indoor) + sta ENEMY_ATTACK_DELAY,x ; need to attack, but first reinitialize attack delay + dec ENEMY_VAR_1,x ; mark that the grenade has launched + jmp enemy_launch_grenade ; create a grenade enemy (ENEMY_TYPE #$12) + +@launch_grenade_exit: + rts + +; apply velocities, if animation timer elapsed, aim and set number of grenades to fire +grenade_launcher_apply_vel_aim: + jsr init_sprite_from_frame ; determine enemy sprite based on ENEMY_FRAME, flip sprite horizontally if running left + lda ENEMY_X_POS,x ; load enemy x position on screen + ldy ENEMY_X_VELOCITY_FAST,x ; load enemy fast velocity + bmi @grenade_launcher_running_left ; branch if enemy is running left + cmp #$a0 ; see if enemy position is off the screen to the right + bcs @cmp_player_enemy_segment ; if enemy too far to the right + bcc @apply_vel_and_aim ; always branch, enemy is running right and not off screen + +@grenade_launcher_running_left: + cmp #$60 + bcc @cmp_player_enemy_segment ; if enemy too far to the left + +@apply_vel_and_aim: + jsr apply_enemy_velocity_set_bg_priority ; apply enemy velocity to position (remove if off screen), set bg priority + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne grenade_launcher_exit ; exit if animation delay hasn't elapsed + +; animation delay elapsed +@cmp_player_enemy_segment: + lda ENEMY_X_POS,x ; load enemy x position on screen + jsr find_far_segment_for_a ; find the appropriate velocity code, given the X position + sta $0a ; set enemy segment in $0a + ldy ENEMY_VAR_2,x ; player offset to find the segment of + jsr find_close_segment ; get segment number for where player is on screen (#$06 = farthest left, #$00 = farthest right) + ldy #$18 ; y = #$18 (delay for pause between seeks) + cmp $0a ; see if player and enemy are in same segment + php ; push status flags on to the stack (namely the zero flag) + bne @set_num_grenades_exit ; branch if player and enemy are in different horizontal segments + ldy #$38 ; y = #$38 (delay for resume seek after attack) + +@set_num_grenades_exit: + tya + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + inc ENEMY_VAR_3,x + lda #$04 ; a = #$04 (delay before attacking) + sta ENEMY_ATTACK_DELAY,x ; set delay between attacks + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + lsr ; strip off direction bit + and #$03 ; keep bits .... ..xx (number of grenades) + plp ; restore status flags + beq @set_enemy_var_1 ; player and grenade launcher in same segment, mark as available to fire + lda #$00 ; a = #$00 (don't fire grenades) + +; set whether or not the grenade launcher is in the same horizontal segment as the player +; meaning the grenade launcher is ready to fire +@set_enemy_var_1: + sta ENEMY_VAR_1,x ; set number of grenades to fire + +grenade_launcher_exit: + rts + +; determine the closest player to the enemy horizontally, then store result in ENEMY_VAR_2, a, and y +; output +; * ENEMY_VAR_2, y, and a - closest player index (#$00 or #$01) +set_enemy_var_2_to_closest_x_player: + jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01) + lda PLAYER_STATE,y ; load the player state of the closest player + cmp #$01 ; see if in normal player state + beq @set_closest_player + tya ; store closest player in a + eor #$01 ; swap to other player (flip bit 0) + tay ; move new closest player back to y + +@set_closest_player: + tya ; move closest player to a + sta ENEMY_VAR_2,x ; store closest player in ENEMY_VAR_2 + rts + +grenade_launcher_routine_06: + jsr enemy_routine_remove_enemy + lda #$00 ; a = #$00 + sta GRENADE_LAUNCHER_FLAG ; clear flag that there is a grenade launcher on screen + ; allows others enemies to be generated again + rts + +; pointer table for group of 4 (#$8 * #$2 = #$10 bytes) +four_soldiers_routine_ptr_tbl: + .addr four_soldiers_routine_00 ; CPU address $9541 - initialize soldier + .addr four_soldiers_routine_01 ; CPU address $954c - walk until timer elapsed begin firing move to four_soldiers_routine_02 + .addr four_soldiers_routine_02 ; CPU address $9582 - waits for delay, get into firing position, set new delay, go back to four_soldiers_routine_01 + .addr shared_enemy_routine_00 ; CPU address $9346 - soldier has been hit by player bullet + .addr shared_enemy_routine_01 ; CPU address $9360 - perform enemy hit by bullet animation, then advance routine + .addr enemy_routine_init_explosion ; CPU address $e74b from bank 7 + .addr shared_enemy_routine_03 ; CPU address $e7aa from bank 7 - show explosion_type_02 + .addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7 + +; initialize soldier +four_soldiers_routine_00: + ldy #$04 ; y = #$04 + jsr init_indoor_enemy_pos_and_vel ; initialize indoor group of 4 soldiers enemy velocity and position + jsr four_soldiers_set_firing_delay ; set appropriate animation delay + jmp advance_enemy_routine ; advance routine to four_soldiers_routine_01 + +; wait for firing delay, then begin running +four_soldiers_routine_01: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne @fire_if_appropriate ; branch if delay hasn't elapsed to possibly fire + lda ENEMY_VAR_2,x ; animation delay is #$00, load number of fires in current round + cmp #$01 ; see if fired already + bne @set_delay_adv_routine ; branch haven't fired to continue + lda ENEMY_VAR_1,x ; already fired once, load soldier number within the group of four [#$00-#$03] + cmp #$02 ; split soldiers so some go left, some go right + bcc @set_delay_adv_routine ; continue in same direction for first 2 soldiers + jsr reverse_enemy_x_direction ; reverse enemy's x direction for other 2 soldiers + +@set_delay_adv_routine: + jsr four_soldiers_get_delay_offset + lda four_soldiers_delay_running_tbl,y ; load initial animation delay + jmp set_anim_delay_adv_enemy_routine_00 ; set ENEMY_ANIMATION_DELAY and advance to four_soldiers_routine_02 + +; fire if animation delay is #$04 fire, otherwise, just exit +@fire_if_appropriate: + lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter + cmp #$04 ; compare animation delay to #$04 + bne four_soldiers_exit ; exit if animation delay isn't #$04 + jmp create_indoor_bullet ; animation delay is #$04, fire a regular indoor bullet + +four_soldiers_exit: + rts + +; table for group of 4 running distances (#$c bytes) +; initial delays before stop +; each column is for the specific soldier within the group of 4 +four_soldiers_delay_running_tbl: + .byte $3f,$39,$33,$2d + .byte $18,$10,$10,$18 ; delays for second attack, each side + .byte $ff,$ff,$ff,$ff + +; waits for animation delay to elapse, get into firing position, set new delay, go back to four_soldiers_routine_01 +four_soldiers_routine_02: + jsr init_sprite_from_frame ; determine enemy sprite based on ENEMY_FRAME, flip sprite horizontally if running left + jsr apply_enemy_velocity_set_bg_priority ; apply enemy velocity to position (remove if off screen), set bg priority + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne four_soldiers_exit ; exit if animation delay hasn't elapsed + lda #$96 ; animation delay elapsed, set a = #$96 (sprite_96) indoor soldier firing position + sta ENEMY_SPRITES,x ; set enemy sprite to indoor soldier firing position + inc ENEMY_VAR_2,x ; increment the number of times the soldier has fired + jsr four_soldiers_set_firing_delay ; set animation delay + lda #$02 ; a = #$02 + jmp set_enemy_routine_to_a ; set enemy routine index to four_soldiers_routine_01 + +four_soldiers_set_firing_delay: + jsr four_soldiers_get_delay_offset ; determine next delay for soldier and set it to y + lda four_soldiers_firing_delay_tbl,y ; load appropriate delay + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + rts + +; gets the appropriate delay based on how many times the soldier has fired and +; the soldier index within the group of four +; output +; * y - the four_soldiers_delay_running_tbl and four_soldiers_firing_delay_tbl offset for the enemy +four_soldiers_get_delay_offset: + lda ENEMY_VAR_2,x ; load number of times the soldier has fired + asl + asl ; double twice since each entry is #$04 bytes + sta $08 ; store result in $08 + lda ENEMY_VAR_1,x ; load soldier number within the group of four [#$00-#$03] + clc ; clear carry in preparation for addition + adc $08 ; add the soldier index to the row offset to get exact delay offset + tay ; transfer offset to y + rts + +; table for group of 4 soldiers standing still to fire delay (#$c bytes) +; each row is are delays for a round of attacks +; each column is for the specific soldier within the group of 4 +; used in four_soldiers_routine_00 and four_soldiers_routine_02 +four_soldiers_firing_delay_tbl: + .byte $01,$07,$0d,$13 ; delay before running into screen + .byte $18,$18,$18,$18 ; delay for firing first shot + .byte $10,$18,$18,$10 ; delay for firing second shot + +; pointer table for rollers generator (#$3 * #$2 = #$6 bytes) +indoor_roller_gen_routine_ptr_tbl: + .addr indoor_roller_gen_routine_00 ; CPU address $95c8 + .addr indoor_roller_gen_routine_01 ; CPU address $95cd + .addr remove_enemy ; CPU address $e809 from bank 7 + +indoor_roller_gen_routine_00: + lda #$60 ; a = #$60 (delay before first set of rollers) + jmp set_anim_delay_adv_enemy_routine_00 ; set ENEMY_ANIMATION_DELAY to #$60 and advance enemy routine + +indoor_roller_gen_routine_01: + lda INDOOR_ENEMY_ATTACK_COUNT ; load the total number of enemy attack rounds for the screen + cmp #$07 ; total number of cycles for rollers to stop + bcc @continue ; haven't reached total 'rounds' of attack, create roller if timer elapsed + jmp remove_enemy ; remove enemy + +@continue: + lda FRAME_COUNTER ; load frame counter + lsr + bcc @exit ; ENEMY_ANIMATION_DELAY is decremented every odd frame + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne @exit ; exit if ENEMY_ANIMATION_DELAY hasn't elapsed + lda ENEMY_ATTRIBUTES,x ; generate roller, load roller generator attributes + and #$07 ; keep bits .... .xxx + asl ; double since each entry is #$02 byte memory address + tay ; transfer offset to y + lda roller_gen_init_tbl,y ; rollers pattern - low byte + sta $10 ; store address low byte in $10 + lda roller_gen_init_tbl+1,y ; rollers pattern - high byte + sta $11 ; store address high byte in $11 + +@create_roller: + ldy ENEMY_VAR_1,x ; load roller number to generate + +@loop: + lda ($10),y ; load the first byte + cmp #$ff + bne @create_roller_for_a + ldy #$00 ; y = #$00 + beq @loop + +@create_roller_for_a: + sta $0b ; store first byte in $0b + and #$0f ; keep bits .... xxxx + sta $0a ; store ENEMY_ATTRIBUTES for roller + iny + lda ($10),y + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + iny + tya + sta ENEMY_VAR_1,x + lda $0b + lsr + lsr + lsr + lsr + tay + lda roller_initial_x_pos_tbl,y ; load the roller's initial x position + sta $09 ; store roller x position + lda #$70 ; a = #$70 (rollers starting y position) + sta $08 ; store roller y position + tya ; move the horizontal segment number into a + jsr create_roller_with_segment_a ; set roller x velocity based on X position + lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter + beq @create_roller + +@exit: + rts + +; rollers starting x positions (#$7 bytes) +roller_initial_x_pos_tbl: + .byte $98,$90,$88,$80,$78,$70,$68 + +; pointer table for rollers pattern (#$2 * #$2 = #$4 bytes) +roller_gen_init_tbl: + .addr roller_gen_init_00 ; CPU address $9634 + .addr roller_gen_init_01 ; CPU address $966d + +; table for rollers type a (#$39 bytes) +; byte 0 : position and attributes for roller 0 +; byte 1 : delay before roller 0 +; byte 2 : position and attributes for roller 1 +; byte 3 : delay before roller 1 +; +; ... 7 times (d bytes) ... +; +; byte d : delay before next set of rollers +; +; ff : end of sets, go from the start +roller_gen_init_00: + .byte $00,$00 + .byte $10,$00 + .byte $20,$00 + .byte $30,$00 + .byte $40,$00 + .byte $50,$00 + .byte $60,$f0 + .byte $01,$00 + .byte $11,$00 + .byte $21,$00 + .byte $31,$00 + .byte $41,$00 + .byte $51,$00 + .byte $61,$f0 + .byte $30,$10 + .byte $20,$00 + .byte $40,$10 + .byte $10,$00 + .byte $50,$10 + .byte $00,$00 + .byte $60,$f0 + .byte $00,$00 + .byte $60,$10 + .byte $10,$00 + .byte $50,$10 + .byte $20,$00 + .byte $40,$10 + .byte $30,$f0 + .byte $ff + +; table for rollers type b (#$f bytes) +roller_gen_init_01: + .byte $00,$00 + .byte $20,$00 + .byte $40,$00 + .byte $60,$f0 + .byte $10,$00 + .byte $30,$00 + .byte $50,$f0 + .byte $ff + +; gets a number from #$06 to #$00 indicating how far the closest player to the enemy is from the left of the screen +; starting from #$06 for farthest left, down to #$00 for farthest right +; input +; * y - player offset to compare +; output +; * a - #$00 when player to the right of cutoff, offset when to the left +; very similar to find_far_segment_for_a in bank 7 +; usually used together to compare player and enemy x positions on indoor levels +find_close_segment: + lda SPRITE_X_POS,y ; load the player's x position + ldy #$06 ; y = #$06 + +@loop: + cmp indoor_close_segment_tbl,y + bcc @exit + dey + bmi @use_segment_0 + bcs @loop + +@use_segment_0: + lda #$00 ; a = #$00 + rts + +@exit: + tya + rts + +; table for close segment x offsets (right to left) (#$7 bytes) +indoor_close_segment_tbl: + .byte $ff,$bc,$a4,$8c,$74,$5c,$44 + +; initializes indoor enemy velocity and position +; input +; * a - is indoor enemy type +; * #$00 - indoor soldier (ENEMY_TYPE #$15) +; * #$02 - jumping soldier (ENEMY_TYPE #$16) +; * #$04 - group of 4 soldiers (ENEMY_TYPE #$18) +; * #$06 - grenade launcher (ENEMY_TYPE #$17) +init_indoor_enemy_pos_and_vel: + lda indoor_soldier_x_velocity_tbl,y ; load the enemy's fractional velocity byte + sta ENEMY_X_VELOCITY_FRACT,x ; store the enemy's fractional velocity byte + lda indoor_soldier_x_velocity_tbl+1,y ; load the enemy's velocity high byte + sta ENEMY_X_VELOCITY_FAST,x ; store the enemy's velocity high byte + lda ENEMY_ATTRIBUTES,x ; load the ENEMY_ATTRIBUTES + lsr ; shift bit 0 to the carry + lda #$a8 ; a = #$a8 (initial x position, from right) + bcc @set_enemy_pos ; if bit 0 is 0 then enemy comes from right screen, branch + jsr reverse_enemy_x_direction ; enemy comes from left side, reverse enemy's x direction + lda #$58 ; a = #$58 (initial x position, from left) + +@set_enemy_pos: + sta ENEMY_X_POS,x ; set enemy x position on screen + lda #$6d ; load y position, hard-coded for indoor levels + sta ENEMY_Y_POS,x ; set enemy y position on screen + rts + +; table for guys x velocities (#$8 bytes) +; byte 0 - ENEMY_X_VELOCITY_FRACT +; byte 1 - ENEMY_X_VELOCITY_FAST +indoor_soldier_x_velocity_tbl: + .byte $20,$ff ; indoor soldier (-.875) + .byte $40,$ff ; jumping soldier (-.75) + .byte $40,$ff ; group of 4 (-.75) + .byte $40,$ff ; grenade launcher (-.75) + +; apply enemy velocity to position +; use updated position to determine if enemy off screen and removes enemy if so +; use updated position to determine sprite attribute background priority +; (enemy drawn in front or behind background) +apply_enemy_velocity_set_bg_priority: + lda ENEMY_X_VEL_ACCUM,x + clc ; clear carry in preparation for addition + adc ENEMY_X_VELOCITY_FRACT,x + sta ENEMY_X_VEL_ACCUM,x + lda ENEMY_X_POS,x ; load enemy x position on screen + adc ENEMY_X_VELOCITY_FAST,x + sta ENEMY_X_POS,x ; set enemy x position on screen + ldy ENEMY_X_VELOCITY_FAST,x + bmi @continue + cmp #$b0 ; indoor enemy right limit (disappearance) + bcs @remove_enemy ; remove enemy if too far to the right + bcc @set_enemy_bg_priority + +@continue: + cmp #$50 ; indoor enemy left limit (disappearance) + bcs @set_enemy_bg_priority + +@remove_enemy: + jmp remove_enemy ; remove enemy + +@set_enemy_bg_priority: + ldy ENEMY_SPRITE_ATTR,x ; enemy sprite attributes + cmp #$a0 + bcs @draw_enemy_behind_bg + cmp #$60 + bcs @draw_enemy_in_front_of_bg + +@draw_enemy_behind_bg: + tya + ora #$20 ; set bits ..x. .... (drawn behind bg) + bne @set_enemy_sprite_attr + +@draw_enemy_in_front_of_bg: + tya + and #$df ; keep bits xx.x xxxx (drawn in front of bg) + +@set_enemy_sprite_attr: + sta ENEMY_SPRITE_ATTR,x + rts + +; dead code, never called !(UNUSED) +bank_0_unused_label_00: + jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's Y and X position respectively + +; creates an indoor roller enemy (ENEMY_TYPE #$11) +create_roller: + jsr find_far_segment_for_x_pos ; get horizontal segment based on X position ($09) + +; creates an indoor roller enemy (ENEMY_TYPE #$11) +; input +; * a - roller horizontal segment number (offset into roller_vel_code_tbl) +; * $09 - x position +; * $08 - y position +create_roller_with_segment_a: + sta $0f ; store horizontal segment code in $0f + lda ENEMY_ATTACK_FLAG ; see if enemies should attack + beq @exit ; exit if enemies shouldn't attack + jsr find_next_enemy_slot ; find next available enemy slot, put result in x register + bne @exit ; exit if not enemy slot available to create the roller + lda #$11 ; a = #$11 (enemy type code for rollers) + jsr init_enemy_set_type_and_pos ; initialize enemy, set enemy type to a, and set X ($09) and Y ($08) position + lda $0a ; load ENEMY_ATTRIBUTES + sta ENEMY_ATTRIBUTES,x + lda $0f ; load velocity code + asl + tay + lda roller_vel_code_tbl,y + sta ENEMY_X_VELOCITY_FRACT,x + lda roller_vel_code_tbl+1,y + sta ENEMY_X_VELOCITY_FAST,x + lda #$80 ; a = #$80 (y velocity for rollers, low byte) (.5) + sta ENEMY_Y_VELOCITY_FRACT,x + lda #$00 ; a = #$00 (y velocity for rollers, high byte) + sta ENEMY_Y_VELOCITY_FAST,x + +@exit: + ldx ENEMY_CURRENT_SLOT + rts + +; table for rollers x velocities (#$e bytes) +roller_vel_code_tbl: + .byte $55,$00 + .byte $38,$00 + .byte $1c,$00 + .byte $00,$00 + .byte $e4,$ff + .byte $c8,$ff + .byte $ab,$ff + +; grenade launcher and indoor soldier +enemy_launch_grenade: + jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position + jsr find_far_segment_for_x_pos ; get horizontal segment based on X position ($09) + sta $0f ; store velocity code in $0f + lda ENEMY_ATTACK_FLAG ; see if enemies should attack + beq @exit ; exit if enemies shouldn't attack + jsr find_next_enemy_slot ; find next available enemy slot, put result in x register + bne @exit + lda #$12 ; a = #$12 (code for grenade) + jsr init_enemy_set_type_and_pos ; initialize enemy, set enemy type to a, and set X ($09) and Y ($08) position + lda $0f ; load velocity code + asl + tay + lda grenade_vel_code_tbl,y + sta ENEMY_X_VELOCITY_FRACT,x + lda grenade_vel_code_tbl+1,y + sta ENEMY_X_VELOCITY_FAST,x + lda #$80 ; a = #$80 + sta ENEMY_Y_VELOCITY_FRACT,x + lda #$00 ; a = #$00 + sta ENEMY_Y_VELOCITY_FAST,x + +@exit: + ldx ENEMY_CURRENT_SLOT + rts + +; table for grenade X velocities (#$e bytes) +grenade_vel_code_tbl: + .byte $55,$00,$38,$00,$1c,$00,$00,$00,$e4,$ff,$c8,$ff,$ab,$ff + +; fire a regular indoor bullet +create_indoor_bullet: + jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position + lda $09 ; load enemy x position + cmp #$a0 ; compare to right side of screen + bcs @exit ; if x position is >= #$a0 (right edge of indoor screen), just exit + cmp #$60 ; compare to left side of screen + bcc @exit ; if x position is < #$60 (left edge of indoor screen), just exit + lda ENEMY_ATTACK_FLAG ; see if enemies should attack + beq @exit ; exit if enemies shouldn't attack + jsr find_far_segment_for_x_pos ; get horizontal segment based on X position ($09) + sta $0f ; store segment number in $0f + jsr find_next_enemy_slot ; find next available enemy slot, put result in x register + bne @exit ; exit if no enemy slot available + lda #$01 ; a = #$01 (bullet enemy type) + jsr init_enemy_set_type_and_pos ; initialize bullet enemy, set enemy type to a, and set X ($09) and Y ($08) position + lda #$03 ; a = #$03 (indoor regular bullet type) + sta ENEMY_VAR_1,x ; set ENEMY_VAR_1 to #$03 + lda $0f ; load horizontal segment + asl ; each entry in table is #$02 bytes (fractional and fast), so double offset + tay ; transfer offset to y + lda indoor_bullet_velocity_tbl,y ; load the x fractional velocity + sta ENEMY_X_VELOCITY_FRACT,x ; store in ENEMY_X_VELOCITY_FRACT + lda indoor_bullet_velocity_tbl+1,y ; load the x fast velocity + sta ENEMY_X_VELOCITY_FAST,x ; store in ENEMY_X_VELOCITY_FAST + lda #$40 ; a = #$40 (y velocity of bullet) + sta ENEMY_Y_VELOCITY_FRACT,x + lda #$01 ; a = #$01 (y velocity of bullet) + sta ENEMY_Y_VELOCITY_FAST,x + +@exit: + ldx ENEMY_CURRENT_SLOT + rts + +; table for indoor bullet velocity (#$e bytes) +indoor_bullet_velocity_tbl: + .byte $d4,$00 ; (.83) + .byte $8d,$00 ; (.55) + .byte $46,$00 ; (.27) + .byte $00,$00 ; (0) + .byte $ba,$ff ; (-.27) + .byte $73,$ff ; (-.55) + .byte $2c,$ff ; (-.83) + +; initialize enemy, set enemy type to a, and set X ($09) and Y ($08) position +init_enemy_set_type_and_pos: + sta ENEMY_TYPE,x ; set enemy type + jsr initialize_enemy + lda $08 + sta ENEMY_Y_POS,x ; enemy y position on screen + lda $09 + sta ENEMY_X_POS,x ; set enemy x position on screen + rts + +; Pointer table for Rock Platform Code (#$2 * #$2 = #$4 bytes) +floating_rock_routine_ptr_tbl: + .addr floating_rock_routine_00 ; CPU address $97e9 + .addr floating_rock_routine_01 ; CPU address $981c + +; also used for moving flame enemy +; loads initial velocity, direction, and boundaries for floating rock and moving flames on vertical level +floating_rock_routine_00: + lda ENEMY_ATTRIBUTES,x ; load attributes to determine direction and boundaries + asl + tay + lda rock_moving_flame_init_vel_tbl,y ; load x fractional velocity value + sta ENEMY_X_VELOCITY_FRACT,x ; store x fractional velocity value + lda rock_moving_flame_init_vel_tbl+1,y ; load x number of units to move per frame + sta ENEMY_X_VELOCITY_FAST,x ; store x number of units to move per frame + lda rock_moving_flame_boundaries_tbl,y ; load left X boundary + sta ENEMY_VAR_2,x ; store left boundary in ENEMY_VAR_2 + lda rock_moving_flame_boundaries_tbl+1,y + sta ENEMY_VAR_1,x ; store right boundary in ENEMY_VAR_1 + jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll + jmp advance_enemy_routine + +; table for rock platform and moving flame velocities (#$8 bytes) +; byte 0 - x fractional velocity value +; byte 1 - x velocity fast value +rock_moving_flame_init_vel_tbl: + .byte $80,$ff ; slow (00) rock platform x velocity (move left every other frame) + .byte $c0,$00 ; fast (01) rock platform x velocity (move right 3 out of every 4 frames) + .byte $80,$ff ; moving flame going left x velocity (move left every other frame) + .byte $80,$00 ; moving flame going right x velocity (move right every other frame) + +; table for rock platform and moving flame boundaries (#$8 bytes) +; byte 0 - left X boundary +; byte 1 - right X boundary +rock_moving_flame_boundaries_tbl: + .byte $50,$b0 ; slow (00) rock platform boundaries + .byte $70,$c0 ; fast (01) rock platform boundaries + .byte $48,$b8 ; flame going left boundaries + .byte $48,$b8 ; flame going right boundaries + +; update enemy position based on velocity and direction +; see if enemy encountered left or right barrier if so, tell enemy to turn around +floating_rock_routine_01: + lda #$48 ; a = #$48 (sprite_48) floating rock + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + +; also used by moving flame +update_pos_turn_around_if_needed: + jsr update_enemy_pos ; apply velocities and scrolling adjust + lda ENEMY_X_POS,x ; load enemy x position on screen + ldy ENEMY_X_VELOCITY_FAST,x ; see if moving left (#$ff) or right (#$00) + bmi @compare_left_barrier ; branch if enemy is moving left + cmp ENEMY_VAR_1,x ; enemy moving right; compare X position to right barrier + bcc @exit ; enemy didn't hit barrier, exit + bcs @turn_around ; enemy hig barrier, tell them to turn around + +@compare_left_barrier: + cmp ENEMY_VAR_2,x ; compare enemy X position to left barrier position + bcs @exit ; enemy didn't hit barrier, exit + +@turn_around: + jmp reverse_enemy_x_direction ; reverse x direction + +@exit: + rts + +; pointer table for moving flame (#$2 * #$2 = #$4 bytes) +moving_flame_routine_ptr_tbl: + .addr floating_rock_routine_00 ; CPU address $97e9 + .addr moving_flame_routine_01 ; CPU address $9840 + +; update enemy position based on velocity and direction +; see if enemy encountered left or right barrier if so, tell enemy to turn around +; also, set flashing palette +moving_flame_routine_01: + lda #$49 ; a = #$49 (sprite_49) bridge fire + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + lda FRAME_COUNTER ; load frame counter to enable flashing every #$08 frames + lsr + lsr + lsr + lsr + lda #$00 ; default sprite attribute (palette) + bcc @continue + lda #$40 ; secondary sprite attribute (palette) + +@continue: + sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes + jmp update_pos_turn_around_if_needed ; update enemy position and turn around if encountered barrier + +; pointer table for falling rock generator (#$3 * #$2 = #$6 bytes) +rock_cave_routine_ptr_tbl: + .addr rock_cave_routine_00 ; CPU address $985d + .addr rock_cave_routine_01 ; CPU address $9863 + .addr rock_cave_routine_02 ; CPU address $986b + +rock_cave_routine_00: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + jmp advance_enemy_routine ; advance to next routine + +rock_cave_routine_01: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda #$08 ; a = #$08 (delay before first falling rock) + jmp set_anim_delay_adv_routine ; set ENEMY_ANIMATION_DELAY then advance enemy routine to rock_cave_routine_02 + +rock_cave_routine_02: + jsr update_enemy_pos ; apply velocities and scrolling adjust + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne rock_exit + lda #$e0 ; a = #$e0 (delay before next falling rock) + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + lda #$13 ; a = #$13 (enemy type for falling rock) + jmp generate_enemy_a ; generate #$13 enemy (falling rock) + +; pointer table for falling rock (#$6 * #$2 = #$c bytes) +falling_rock_routine_ptr_tbl: + .addr falling_rock_routine_00 ; CPU address $9889 - initialize sprite, set initial delay, advance routine + .addr falling_rock_routine_01 ; CPU address $988e - wobble left and right until animation delay elapsed, then advance routine + .addr falling_rock_routine_02 ; CPU address $98ce - actual falling of the rock, and bounce against the ground + .addr enemy_routine_init_explosion ; CPU address $e74b + .addr enemy_routine_explosion ; CPU address $e7b0 + .addr enemy_routine_remove_enemy ; CPU address $e806 + +falling_rock_routine_00: + lda #$40 ; a = #$40 (delay before rock starts falling) + jmp set_anim_delay_adv_routine ; set ENEMY_ANIMATION_DELAY then advance enemy routine to falling_rock_routine_01 + +; wobble left and right until animation delay elapsed, then advance routine +falling_rock_routine_01: + jsr falling_rock_set_sprite ; set boulder sprite (sprite_4a) + jsr update_enemy_pos ; apply velocities and scrolling adjust + lda FRAME_COUNTER ; load frame counter + and #$03 ; keep bits .... ..xx + bne @continue ; branch if not 4th frame since no rocking/swaying direction change + lda FRAME_COUNTER ; frame counter divisible by #$04, reload frame counter + lsr + lsr + lsr ; push bit 2 into carry, every #$04 frames rock sways in a direction + bcc @dec_x_pos ; branch if rocking left + inc ENEMY_X_POS,x ; rocking right, increment enemy x position + bcs @continue ; skip decrement if rocking right + +; rocking left +@dec_x_pos: + dec ENEMY_X_POS,x ; enemy x position + +; enable collision, set animation delay, wait, advance routine +@continue: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne rock_exit ; exit if animation delay hasn't elapsed + jsr enable_enemy_collision ; delay elapsed, enable bullet-enemy collision and player-enemy collision checks + lda #$01 ; a = #$01 + jmp set_anim_delay_adv_routine ; set ENEMY_ANIMATION_DELAY then advance enemy routine to falling_rock_routine_02 + +; sets the sprite attribute for the falling rock so it tumbles +falling_rock_set_sprite_and_attr: + lda FRAME_COUNTER ; load frame counter + lsr + lsr ; moves to next flip every #$04 frames + and #$03 ; keep bits .... ..xx + tay ; transfer to y to be falling_rock_sprite_attr_tbl offset + lda ENEMY_SPRITE_ATTR,x ; load enemy sprite attributes + and #$3f ; strip horizontal and vertical flip bits + ora falling_rock_sprite_attr_tbl,y ; load whether the rock needs to be reflected + sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes + +falling_rock_set_sprite: + lda #$4a ; a = #$4a (sprite_4a - boulder) + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + +rock_exit: + rts + +; actual falling of the rock, and bounce against ground +falling_rock_routine_02: + jsr falling_rock_set_sprite_and_attr ; set the sprite attribute so the rock tumbles + lda ENEMY_Y_POS,x ; load enemy y position on screen + cmp ENEMY_VAR_1,x ; compare y position to the ground collision y position + bcc @apply_gravity_update_pos ; branch if rock is still above where it collided with the ground + ldy #$08 ; rock below the ground it collided with, check for next collision + jsr add_y_to_y_pos_get_bg_collision ; add #$08 to enemy y position and gets bg collision code + bcc @apply_gravity_update_pos ; branch if no floor collision + lda #$05 ; collision with floor, a = #$05 (sound_05) + jsr play_sound ; play sound of rock hitting ground + lda #$40 ; a = #$40 + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + lda ENEMY_Y_POS,x ; load enemy y position on screen + clc ; clear carry in preparation for addition + adc #$10 ; add #$10 to get y position of ground + bcc @continue ; branch if no overflow occurred (not offscreen) + lda #$ff ; off screen, set y position to #$ff + +; set y velocity to -1.25 +@continue: + sta ENEMY_VAR_1,x ; set ENEMY_VAR_1 to ground y position + lda #$c0 ; a = #$c0 (.75) (y velocity for rock bouncing, low) + sta ENEMY_Y_VELOCITY_FRACT,x + lda #$fe ; a = #$fe (-2) (y velocity for rock bouncing, high) + sta ENEMY_Y_VELOCITY_FAST,x ; combined the total velocity is -1.25 + +@apply_gravity_update_pos: + jsr add_10_to_enemy_y_fract_vel ; add #$10 to y fractional velocity (.06 faster) (applying gravity) + lda ENEMY_VAR_1,x ; load rock ground collision y position + clc ; clear carry in preparation for addition + adc FRAME_SCROLL ; how much to scroll the screen (#00 - no scroll) + bcc @update_pos ; branch if no carry to set ENEMY_VAR_1 and update position + lda #$ff ; a = #$ff + +@update_pos: + sta ENEMY_VAR_1,x ; adjust by scroll position + jmp update_enemy_pos ; apply velocities and scrolling adjust + +; table for falling rock mirroring codes (#$4 bytes) +; $00 - no flip +; $40 - flip horizontally +; $c0 - flip horizontally and vertically +; $80 - flip vertically +falling_rock_sprite_attr_tbl: + .byte $00,$40,$c0,$80 + +; pointer table for level 3 boss mouth (dragon) (#$9 * #$2 = #$12 bytes) +boss_mouth_routine_ptr_tbl: + .addr boss_mouth_routine_00 ; CPU address $992a + .addr boss_mouth_routine_01 ; CPU address $9941 + .addr boss_mouth_routine_02 ; CPU address $9954 + .addr boss_mouth_routine_03 ; CPU address $99a2 + .addr boss_mouth_routine_04 ; CPU address $99ef + .addr boss_defeated_routine ; CPU address $e740 from bank 7 + .addr enemy_routine_explosion ; CPU address $e7b0 from bank 7 + .addr shared_enemy_routine_clear_sprite ; CPU address $e814 from bank 7 - set tile sprite code to #$00 and advance routine + .addr boss_mouth_routine_08 ; CPU address $9a14 + +; set HP, frame, animation/attack delay, advance routine +boss_mouth_routine_00: + lda #$20 ; a = #$20 + sta ENEMY_VAR_1,x ; level 3 boss mouth hp + lda #$02 ; a = #$02 + sta ENEMY_VAR_3,x ; set flag so when dragon is destroyed, the animation is + ; delayed by one frame. See `boss_mouth_routine_08` + lda #$01 ; a = #$01 + sta ENEMY_FRAME,x ; set enemy animation frame number + lda #$ff ; a = #$ff (positive delay before first attack) + +; used by boss mouth, rock cave, and falling rock +set_anim_delay_adv_routine: + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + jmp advance_enemy_routine + +; wait for boss auto scroll to complete, wait for animation delay to elapse, advance routine +boss_mouth_routine_01: + jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll + lda BOSS_AUTO_SCROLL_COMPLETE ; see if boss reveal auto-scroll has completed + beq boss_mouth_exit ; exit if boss reveal auto-scroll hasn't completed + lda BG_PALETTE_ADJ_TIMER + bne boss_mouth_exit + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne boss_mouth_exit ; exit if animation delay hasn't elapsed + jmp advance_enemy_routine ; advance to boss_mouth_routine_02 + +; animate opening of mouth +boss_mouth_routine_02: + jsr boss_mouth_draw_supertiles_set_delay + bcs boss_mouth_exit ; exit if didn't need to draw updates super-tiles + lda ENEMY_FRAME,x ; updated super-tiles, load enemy animation frame number + cmp #$02 + bcs boss_mouth_enable_collision ; boss mouth is open, enable collision, set attack delay, advance routine + inc ENEMY_FRAME,x ; increment enemy animation frame number + +boss_mouth_exit: + rts + +boss_mouth_enable_collision: + jsr enable_bullet_enemy_collision ; allow bullets to collide (and stop) upon colliding with boss mouth + lda ENEMY_VAR_1,x + sta ENEMY_HP,x ; set enemy hp + lda #$06 ; a = #$06 + sta ENEMY_ATTACK_DELAY,x ; delay between mouth open and attack + lda #$70 ; a = #$70 (time during which mouth is open) + bne set_anim_delay_adv_routine ; set ENEMY_ANIMATION_DELAY then advance enemy routine to boss_mouth_routine_03 + +boss_mouth_draw_supertiles_set_delay: + jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne @set_carry_exit ; exit if animation delay timer hasn't elapsed + lda ENEMY_FRAME,x ; load enemy animation frame number + asl ; double since each entry is #$02 bytes + tay ; transfer offset to y + lda boss_mouth_nametable_update_tbl,y ; load first nametable update super-tile index + sta $10 ; store in $10 for update_2_enemy_supertiles + lda boss_mouth_nametable_update_tbl+1,y ; load second nametable update super-tile index + ldy #$01 ; y = #$01, skip setting collision + jsr update_2_enemy_supertiles ; draw nametable update super-tile $10, then a at enemy position + lda #$06 ; a = #$06 + bcc @set_anim_exit ; (delay between frames when animating mouth open/close) + lda #$01 ; a = #$01 + +@set_anim_exit: + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + rts + +@set_carry_exit: + sec ; set carry flag + rts + +; table for nametable update super-tile indexes (#$6 bytes) +; offsets into level_3_nametable_update_supertile_data +; related to closed/closing mouth nametable tiles +; #$20 (#$a0) - boss mouth closed (top half) +; #$21 (#$a1) - boss mouth closed (bottom half) +; #$22 (#$a2) - boss mouth partially open (top half) +; #$23 (#$a3) - boss mouth partially open (bottom half) +; #$24 (#$a4) - boss mouth fully open (top half) +; #$25 (#$a4) - boss mouth fully open (bottom half) +boss_mouth_nametable_update_tbl: + .byte $a0,$a1 ; closed + .byte $a2,$a3 ; partially open + .byte $a4,$a5 ; fully open + +boss_mouth_routine_03: + jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll + lda ENEMY_ATTACK_DELAY,x ; load delay between attacks + beq @adv_routine + dec ENEMY_ATTACK_DELAY,x ; decrement delay between attacks + bne @adv_routine + lda #$02 ; a = #$02 + sta $16 ; number of projectiles + +@projectile_loop: + ldy #$08 ; set vertical offset from enemy position (param for add_with_enemy_pos) + lda #$00 ; set horizontal offset from enemy position (param for add_with_enemy_pos) + jsr add_with_enemy_pos ; stores absolute screen x position in $09, and y position in $08 + ldy #$06 ; y = #$06 (bullet speed) + lda BOSS_SCREEN_ENEMIES_DESTROYED ; load number of destroyed dragon arm orbs + cmp #$02 ; see if both arms have been destroyed + bcc @continue ; branch if at least one dragon arm orb still exists + ldy #$07 ; both dragon arm orbs destroyed, increase bullet speed to #$07 (bullet speed) + +@continue: + sty $0f + ldy $16 + lda mouth_projectile_type_angle,y ; load mouth projectile type (xxx. ....) and angle index (...x xxxx) + ldy $0f ; load mouth projectile speed + jsr create_enemy_bullet_angle_a ; create a bullet with speed y, bullet type a, angle a at position ($09, $08) + dec $16 + bpl @projectile_loop + +@adv_routine: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne boss_mouth_exit + jsr disable_bullet_enemy_collision ; allow bullets to travel through boss mouth + lda ENEMY_HP,x ; load enemy hp + sta ENEMY_VAR_1,x ; store enemy hp here while it's closed + lda #$f1 ; a = #$f1 (f1 = hittable, no damage) + sta ENEMY_HP,x ; set enemy hp + lda #$06 ; a = #$06 + jmp set_anim_delay_adv_routine ; set ENEMY_ANIMATION_DELAY then advance enemy routine to boss_mouth_routine_04 + +; table for mouth projectiles types and angles (#$3 bytes) +; from 3h, clockwise +; $00-$17: white bullets +; $20-$37: bomb drop (like level 1 boss bomb turret) +; $40-$57: red bullets (like base triple cannon) +; $60-$77: white bullets with limited range +; $80-$97: orange fireballs (default) +mouth_projectile_type_angle: + .byte $88,$86,$84 + +boss_mouth_routine_04: + jsr boss_mouth_draw_supertiles_set_delay + bcs @exit + lda ENEMY_FRAME,x ; load enemy animation frame number + beq @set_mouth_delay + dec ENEMY_FRAME,x ; decrement enemy animation frame number + +@exit: + rts + +@set_mouth_delay: + lda BOSS_SCREEN_ENEMIES_DESTROYED ; load number of destroyed dragon arm orbs + cmp #$02 ; see if both arms have been destroyed + bcc @continue ; branch if at least one dragon arm orb still exists + lda #$02 ; both dragon arms destroyed, ensure a = #$02, should already be #$02 + +@continue: + tay ; transfer delay offset to y + lda boss_mouth_anim_delay_tbl,y + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + lda #$03 ; a = #$03 + jmp set_enemy_routine_to_a ; set enemy routine index to boss_mouth_routine_02 + ; to begin wait to open mouth + +; table for delays between mouth attacks, depends on number of dragon arms (#$3 bytes) +; #$c0 delay with 2 arms +; #$70 delay with 1 arm +; #$20 delay without arms +boss_mouth_anim_delay_tbl: + .byte $c0,$70,$20 + +; draw explosions +boss_mouth_routine_08: + dec ENEMY_VAR_3,x ; decrement variable to not start explosion until next iteration + bne @exit ; exit if ENEMY_VAR_3 is not #$00 + lda #$01 ; a = #$01 + sta ENEMY_VAR_3,x + jsr @update_nametable_create_explosions + bcs @exit ; exit if unable to create explosion + inc ENEMY_VAR_2,x ; increment current explosion counter + lda ENEMY_VAR_2,x + cmp #$0e ; see if have generated all explosions + bcs @set_delay_remove ; remove enemy all explosions have been generated + +@exit: + rts + +@set_delay_remove: + lda #$60 ; a = #$60 + jmp set_delay_remove_enemy + +@update_nametable_create_explosions: + ldy ENEMY_VAR_2,x + lda boss_mouth_y_pos_tbl,y + sta ENEMY_Y_POS,x ; enemy y position on screen + lda boss_mouth_x_pos_tbl,y + sta ENEMY_X_POS,x ; set enemy x position on screen + lda boss_mouth_destroyed_nametable_update_tbl,y + jsr draw_enemy_supertile_a ; draw super-tile a (offset into level nametable update table) at position (ENEMY_X_POS, ENEMY_Y_POS) + bcs @draw_failed_exit ; exit when unable to update super-tile + ldy ENEMY_VAR_2,x ; load current explosion counter + lda boss_mouth_y_pos_tbl,y ; load explosion y position + sta $08 ; store explosion y position in $80 + lda boss_mouth_x_pos_tbl,y ; load explosion x position + sta $09 ; store explosion x position in $09 + jsr create_two_explosion_89 ; create explosion #$89 at location ($09, $08) + clc ; indicate successfully created explosions + rts + +@draw_failed_exit: + lda #$01 + sta ENEMY_VAR_3,x ; unnecessary since already #$01 + rts + +; tables for explosions and replacing nametable super-tiles (after level 3 boss) +; y positions (#$e bytes) +boss_mouth_y_pos_tbl: + .byte $20,$20,$20,$20,$40,$40,$60,$60,$80,$80,$a0,$a0,$c0,$c0 + +; x positions (#$e bytes) +boss_mouth_x_pos_tbl: + .byte $50,$b0,$70,$90,$70,$90,$70,$90,$70,$90,$70,$90,$70,$90 + +; nametable update super-tile indexes (#$e bytes) +; offset into level_3_nametable_update_supertile_data or level_3_nametable_update_palette_data +boss_mouth_destroyed_nametable_update_tbl: + .byte $19,$19,$19,$19,$1a,$1b,$29,$2a,$1c,$1d,$1e,$1f,$26,$27 + +; pointer table for dragon arm orb (#$8 * #$2 = #$10 bytes) +dragon_arm_orb_routine_ptr_tbl: + .addr dragon_arm_orb_routine_00 ; CPU address $9a9c - variable initialization + .addr dragon_arm_orb_routine_01 ; CPU address $9ac5 + .addr dragon_arm_orb_routine_02 ; CPU address $9b63 - dragon arms extending outward animation + .addr dragon_arm_orb_routine_03 ; CPU address $9c03 - dragon arms attack patterns, only executes code for shoulder orbs + .addr dragon_arm_orb_routine_04 ; CPU address $9edd + .addr enemy_routine_init_explosion ; CPU address $e74b from bank 7 + .addr enemy_routine_explosion ; CPU address $e7b0 from bank 7 + .addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7 + +; variable initialization +dragon_arm_orb_routine_00: + lda ENEMY_ATTRIBUTES,x ; load ENEMY_ATTRIBUTES + lsr ; shift bit 0 to the carry flag (dragon arm side) + lda #$38 ; a = #$38 (position index) + ldy #$08 ; used for enemy x adjustment + bcc @continue ; continue if right arm + lda #$28 ; left arm ball, a = #$28 (position index) + ldy #$f8 ; used for enemy x adjustment (-8) + +@continue: + sta ENEMY_VAR_1,x ; set position index (see dragon_arm_orb_pos_tbl) + sta ENEMY_VAR_A,x ; set position index (see dragon_arm_orb_pos_tbl) + tya ; transfer x adjustment to a + jsr add_a_to_enemy_x_pos ; adjust enemy x position on screen + lda #$ff ; a = #$ff + sta ENEMY_VAR_4,x ; set previous dragon arm orb index to #$ff (shoulder) + lda #$04 ; a = #$04 (number of child dragon arm orbs to spawn) + sta ENEMY_FRAME,x ; set number of child dragon arm orbs to spawn + txa + sta ENEMY_VAR_2,x + jmp advance_enemy_routine ; advance routine to dragon_arm_orb_routine_01 + +dragon_arm_orb_routine_01: + jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll + lda BOSS_AUTO_SCROLL_COMPLETE ; see if boss reveal auto-scroll has completed + beq @exit ; exit if scrolling isn't complete + lda BG_PALETTE_ADJ_TIMER ; boss reveal auto-scroll complete, load BG_PALETTE_ADJ_TIMER + bne @exit + lda ENEMY_VAR_4,x ; load ENEMY_VAR_4 to see if parent dragon arm orb + bmi @create_child_dragon_arm_orb ; if parent dragon arm orb, spawn and initialize another dragon arm orb + ; once spawned all child dragon arm orbs, advance all their routines + +@exit: + rts + +; only called from left and right parent dragon arm orbs to generate the entire arm +@create_child_dragon_arm_orb: + jsr find_next_enemy_slot ; find next available enemy slot, put result in x register + bne @set_slot_exit ; exit if no enemy slot available + lda #$15 ; enemy slot found, a = #$15 (dragon arm orb) + sta ENEMY_TYPE,x ; set enemy type to dragon arm orb + jsr initialize_enemy ; initialize dragon arm orb + jsr @init_child_dragon_arm_orb ; initialize dragon arm orb specific variables + stx $10 ; store spawn dragon arm orb enemy slot index in $10 + ldx ENEMY_CURRENT_SLOT ; restore parent dragon arm orb slot index + dec ENEMY_FRAME,x ; decrement number of child dragon arm orbs to spawn + bne @set_slot_exit ; exit if enemy frame isn't #$00 + ldy $10 ; spawned all dragon arm orbs for side + ; continue to initialize red dragon arm orb and advance all arms enemy routines + ; load last spawned dragon arm orb enemy slot index (red dragon arm orb) + lda #$ff ; a = #$ff + sta ENEMY_VAR_3,y ; set spawned dragon arm orb ENEMY_VAR_3 to #$ff to signify it's the 'hand' (red dragon arm orb) + lda #$10 ; a = #$10 + sta ENEMY_HP,y ; set hand orb enemy hp (red dragon arm orb) + lda #$0c ; a = #$0c + sta ENEMY_STATE_WIDTH,y ; set hand orb collision box type, and explosion type (red dragon arm orb) + lda #$01 ; a = #$01 + sta ENEMY_VAR_2,y ; set hand orb ENEMY_VAR_2 (red dragon arm orb) + lda #$20 ; a = #$20 (delay before arms appear) + sta ENEMY_ANIMATION_DELAY,y ; set spawned enemy dragon arm orb animation frame delay counter + tya ; transfer spawned enemy dragon arm orb slot index to a + sta ENEMY_X_VELOCITY_FRACT,x ; set fractional velocity based on enemy slot index + +@loop: + jsr advance_enemy_routine ; advance dragon arm orb enemy routine to dragon_arm_orb_routine_02 + lda ENEMY_VAR_3,x ; load next enemy dragon arm orb slot to advance the routine of + tax ; transfer enemy slot index of linked dragon arm orb to x + bpl @loop ; if not last orb (red orb) continue to advance the next orb's routine + ldx ENEMY_CURRENT_SLOT ; load parent orb slot index + lda #$00 ; a = #$00 + sta ENEMY_VAR_2,x + sta ENEMY_FRAME,x ; set enemy animation frame number to #$00 + +@set_slot_exit: + ldx ENEMY_CURRENT_SLOT ; restore parent dragon arm orb slot index + rts + +; input +; * x - enemy slot +@init_child_dragon_arm_orb: + lda #$02 ; a = #$02 (dragon_arm_orb_routine_01) + sta ENEMY_ROUTINE,x ; set enemy slot to dragon_arm_orb_routine_01 + lda #$8c ; a = #$8c + sta ENEMY_STATE_WIDTH,x ; set bit 7, 3, 2. explosion type, enable sound on collision, allow bullets to fly through + lda #$52 ; a = #$52 (last nibble determines size) + sta ENEMY_SCORE_COLLISION,x ; score code 5 (2,000 points), collision code 2 + lda #$f1 ; a = #$f1 + sta ENEMY_HP,x ; set enemy hp to #$f1 (241) + lda #$00 ; a = #$00 + sta ENEMY_VAR_1,x + ldy ENEMY_CURRENT_SLOT ; load parent orb + lda ENEMY_ATTRIBUTES,y ; load parent dragon arm orb attributes + sta ENEMY_ATTRIBUTES,x ; set dragon arm orb part attribute so it's the same side + lda ENEMY_Y_POS,y ; load enemy y position on screen + sta ENEMY_Y_POS,x ; copy parent dragon arm orb y position + lda ENEMY_X_POS,y ; load enemy x position on screen + sta ENEMY_X_POS,x ; copy parent dragon arm orb enemy x position on screen + lda ENEMY_VAR_2,y + sta ENEMY_VAR_4,x ; set parent dragon arm orb ENEMY_VAR_2 into ENEMY_VAR_4 + sta $08 + txa + sta ENEMY_VAR_2,y + ldy $08 + sta ENEMY_VAR_3,y ; set child dragon arm orb + rts + +; dragon arms extending outward animation +dragon_arm_orb_routine_02: + jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll + lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter + beq @set_pos_and_delay + lda FRAME_COUNTER ; load frame counter + lsr + bcc @exit + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + +@exit: + rts + +@set_pos_and_delay: + lda ENEMY_VAR_2,x + beq @exit2 + jsr dragon_arm_orb_set_sprite ; set dragon arm orb sprite, either sprite_7a (gray) or sprite_7b (red) + jsr @set_pos_add_accum + lda ENEMY_VAR_2,x + bmi @exit2 + inc ENEMY_VAR_2,x + lda ENEMY_VAR_2,x + cmp #$10 + bcc @exit2 + lda #$ff ; a = #$ff + sta ENEMY_VAR_2,x + ldy ENEMY_VAR_4,x + lda #$01 ; a = #$01 + sta ENEMY_VAR_2,y + lda #$00 ; a = #$00 + sta ENEMY_ANIMATION_DELAY,y + lda ENEMY_VAR_4,y + bpl @set_enemy_slot_exit + tya + tax + +@adv_routine_exit: + jsr advance_enemy_routine + lda #$00 ; a = #$00 + sta ENEMY_VAR_2,x + lda ENEMY_VAR_3,x + tax + bpl @adv_routine_exit + ldx ENEMY_CURRENT_SLOT + lda #$00 ; a = #$00 + sta ENEMY_FRAME,x ; set enemy animation frame number + +@set_enemy_slot_exit: + ldx ENEMY_CURRENT_SLOT + +@exit2: + rts + +@set_pos_add_accum: + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + and #$01 ; keep bit 0 (left or right arm) + asl + asl ; quadruple since each entry is #$04 bytes + tay ; transfer to animation table offset + lda dragon_arm_open_anim_tbl,y ; load y velocity accumulator adjustment + clc ; clear carry in preparation for addition + adc ENEMY_Y_VEL_ACCUM,x + sta ENEMY_Y_VEL_ACCUM,x + lda dragon_arm_open_anim_tbl+1,y + adc ENEMY_Y_POS,x + sta ENEMY_Y_POS,x ; enemy y position on screen + lda dragon_arm_open_anim_tbl+2,y + clc ; clear carry in preparation for addition + adc ENEMY_X_VEL_ACCUM,x + sta ENEMY_X_VEL_ACCUM,x + lda dragon_arm_open_anim_tbl+3,y + adc ENEMY_X_POS,x ; add to enemy x position on screen + sta ENEMY_X_POS,x ; set enemy x position on screen + rts + +; table for dragon arm extending out animation values (#$8 bytes) +; byte 0 - y velocity accumulator adjustment +; byte 1 - y position adjustment +; byte 2 - x velocity accumulator adjustment +; byte 3 - x position adjustment +dragon_arm_open_anim_tbl: + .byte $4b,$ff,$b5,$00 ; right arm - go up one pixel + .byte $4b,$ff,$4b,$ff ; left arm - go up one pixel, go left one pixel + +; set dragon arm orb sprite, either sprite_7a (gray) or sprite_7b (red) +dragon_arm_orb_set_sprite: + lda #$7a ; a = #$7a (sprite_7a) dragon arm interior orb (gray) + ldy ENEMY_VAR_3,x ; load the child dragon arm orb to see if its a hand (red orb) + bpl @set_sprite_exit ; branch if not #$ff (red orb) to keep gray sprite + lda #$7b ; a = #$7b (sprite_7b) dragon arm hand orb (red) + +@set_sprite_exit: + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + rts + +; dragon arms attack patterns, only executes code for shoulder orbs +dragon_arm_orb_routine_03: + jsr dragon_arm_orb_set_sprite ; set dragon arm orb sprite, either sprite_7a (gray) or sprite_7b (red) + lda ENEMY_VAR_4,x ; load to see which orb this is + bmi @continue ; continue if shoulder orb, otherwise do nothing + rts + +@continue: + jsr dragon_arm_orb_attack_pat ; run appropriate logic based on attack pattern (ENEMY_FRAME) + lda ENEMY_FRAME,x ; load enemy animation frame number + cmp #$04 ; compare to #$04 (arm seeking player, reaching down) + beq @set_positions_exit ; branch if attack pattern is arm seeking player, reaching down + jsr dragon_arm_animate ; executed for all except ENEMY_FRAME #$04 (arm seeking player) + +@set_positions_exit: + jmp dragon_arm_orb_set_positions + +; dead code, never called !(UNUSED) +bank_0_unused_label_01: + bmi dragon_arm_open_anim_tbl ; .byte $30,$d0 + +dragon_arm_orb_attack_pat: + ldy ENEMY_FRAME,x ; load attack pattern, e.g. #$00 - wave up and down, #$01 - spin towards center, etc. + bne dragon_arm_orb_pat_1_2_3_or_4 ; branch if not the wave arms up and down pattern + jsr dragon_arm_orb_fire_projectile ; ENEMY_FRAME #$00 wave arm up and down + ; fire projectile if shoulder dragon arm orb's ENEMY_VAR_A timer has elapsed + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + and #$01 ; keep bit 0 (which side of dragon the arm is) + tay ; transfer to offset register + lda ENEMY_Y_VELOCITY_FRACT,x ; 1 = dragon arm wave up, 0 = wave down + ; not actually used to move shoulder dragon arm orb + bne @hand_below_shoulder ; branch if hands are below the shoulder + lda ENEMY_VAR_1,x ; hands above shoulder, load position index + cmp wave_direction_up_change_tbl,y ; compare to value that determines when to change direction + beq @set_delay_swap_dir ; see if need to change direction + lda dragon_arm_orb_pattern_timer_tbl,y ; load timer for given side of the dragon + sta ENEMY_VAR_2,x + rts + +; dragon arm orb 'wave arms up and down' attack pattern (ENEMY_FRAME = #$00) +@hand_below_shoulder: + lda ENEMY_VAR_1,x ; load height of red arm ?? + cmp wave_direction_down_change_tbl,y ; see if arm should change direction + beq @wave_in_other_direction ; branch if ENEMY_VAR_1 has value to cause change in directions + lda dragon_arm_orb_pattern_timer_tbl+1,y ; load timer for given side of the dragon + sta ENEMY_VAR_2,x + rts + +@wave_in_other_direction: + inc ENEMY_ATTACK_DELAY,x ; + lda ENEMY_ATTACK_DELAY,x ; load delay between attacks + cmp #$03 + bne @set_delay_swap_dir + inc ENEMY_FRAME,x ; move to next attack pattern (#$01 - spin toward center) + +@set_delay_swap_dir: + lda #$03 ; a = #$03 + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + lda ENEMY_Y_VELOCITY_FRACT,x + eor #$01 ; flip bits .... ...x + sta ENEMY_Y_VELOCITY_FRACT,x + rts + +; table for ENEMY_VAR_1 trigger point to change waving direction (#$2 bytes) +; byte 0 - right screen of dragon arm value +; byte 1 - left screen of dragon arm value +wave_direction_up_change_tbl: + .byte $14,$0c + +; table for ENEMY_VAR_1 trigger point to change waving direction (#$2 bytes) +; byte 0 - right screen of dragon arm value +; byte 1 - left screen of dragon arm value +wave_direction_down_change_tbl: + .byte $2c,$34 + +; used for ENEMY_FRAME #00 and #$02 to set ENEMY_VAR_2 (rotation timer) +dragon_arm_orb_pattern_timer_tbl: + .byte $40,$c0,$40 + +dragon_arm_orb_pat_1_2_3_or_4: + dey ; decrement from loaded ENEMY_FRAME + bne dragon_arm_orb_pat_2_3_or_4 ; branch if ENEMY_FRAME is not #$01 (spin toward center) + jsr dragon_arm_orb_fire_projectile ; ENEMY_FRAME is #$01 (spin toward center) + ; fire projectile if shoulder dragon arm orb's ENEMY_VAR_A timer has elapsed + lda ENEMY_Y_VELOCITY_FAST,x ; load merged value of all orb ENEMY_VAR_2 timers + bne @exit ; exit without advancing ENEMY_FRAME if all rotation timers are not yet elapsed + inc ENEMY_FRAME,x ; increment enemy animation frame number (attack pattern) + +@exit: + rts + +dragon_arm_orb_pat_2_3_or_4: + dey ; decrement from loaded ENEMY_FRAME + bne dragon_arm_orb_pat_3_or_4 ; branch if ENEMY_FRAME is not #$02 (spin away from center) + jsr dragon_arm_orb_fire_projectile ; ENEMY_FRAME is #$02 (spin away from center) + ; fire projectile if shoulder dragon arm orb's ENEMY_VAR_A timer has elapsed + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + and #$01 ; keep bit 0 (which side of dragon the arm is) + sta $08 ; store side into $08 (0 = right side of screen, 1 = left side of screen) + lda ENEMY_VAR_3,x ; load next arm orb (farther from body) enemy slot index + tax ; transfer to x + +@frame_02_arm_orb_loop: + ldy $08 ; load the arm side (0 = right side of screen, 1 = left side of screen) + lda ENEMY_VAR_1,x + cmp dragon_arm_frame_02_tbl,y + beq @next_arm_orb + lda dragon_arm_orb_pattern_timer_tbl,y + sta ENEMY_VAR_2,x + ldx ENEMY_CURRENT_SLOT ; restore dragon arm shoulder orb enemy slot index + rts + +@next_arm_orb: + lda ENEMY_VAR_3,x ; load next dragon arm orb (farther from body) enemy slot index + bmi @frame_02_exit ; branch if next dragon arm orb is the red hand + tax ; move next dragon arm orb enemy slot index to x + bpl @frame_02_arm_orb_loop + +@frame_02_exit: + ldx ENEMY_CURRENT_SLOT + lda RANDOM_NUM ; load random number + adc FRAME_COUNTER + and #$03 ; keep bits .... ..xx + tay + lda dragon_arm_delay_tbl,y + bne dragon_arm_orb_03_set_delay_exit ; always branch + +; table for ENEMY_VAR_1 trigger points for when ENEMY_FRAME is #$02 (spin away from center) (#$2 bytes) +; byte 0 - right side of screen +; byte 1 - left side of screen +dragon_arm_frame_02_tbl: + .byte $08,$38 + +; table for ? (#$4 bytes) +dragon_arm_delay_tbl: + .byte $40,$60,$30,$70 + +; see if ENEMY_FRAME is #$03 or #$04 and execute appropriate logic +; then decrement delay that controls moving to next attack pattern (ENEMY_FRAME) +dragon_arm_orb_pat_3_or_4: + dey ; decrement from loaded ENEMY_FRAME + bne dragon_arm_orb_seek_player ; branch if ENEMY_FRAME is not #$03 (hook shape) + ; i.e. ENEMY_FRAME = #$04 (arm seeking player, reaching down) + jsr dragon_arm_orb_fire_projectile ; ENEMY_FRAME is #$03 (hook shape) + ; fire projectile if shoulder dragon arm orb's ENEMY_VAR_A timer has elapsed + dec ENEMY_ATTACK_DELAY,x ; decrement delay to control moving to next ENEMY_FRAME attack pattern + bne dragon_arm_orb_03_exit ; exit if attack delay hasn't elapsed + lda #$c0 ; prepare to move to next attack pattern + ; set a = #$c0 (delay between switching attack patterns) + +dragon_arm_orb_03_set_delay_exit: + sta ENEMY_ATTACK_DELAY,x ; set delay between switching attack patterns to a + inc ENEMY_FRAME,x ; increment enemy animation frame number (attack pattern) + +dragon_arm_orb_03_exit: + rts + +; ENEMY_FRAME #$04 (arm seeking player, reaching down) +dragon_arm_orb_seek_player: + jsr dragon_arm_seek_player_logic + dec ENEMY_ATTACK_DELAY,x ; decrement enemy attack delay (timer before moving to next attack pattern) + bne @exit ; exit if timer hasn't elapsed + lda #$00 ; attack pattern delay elapsed, a = #$00 + sta ENEMY_Y_VELOCITY_FRACT,x ; reset initial orb position point + sta ENEMY_ATTACK_DELAY,x ; clear delay between attacks + sta ENEMY_FRAME,x ; set enemy attack pattern to #$00 (wave arm up and down) + +@exit: + rts + +; fire projectile if shoulder dragon arm orb's ENEMY_VAR_A timer has elapsed +dragon_arm_orb_fire_projectile: + dec ENEMY_VAR_A,x + bne @exit + lda #$90 ; a = #$90 + sta ENEMY_VAR_A,x + lda ENEMY_X_VELOCITY_FRACT,x + tax + jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01) + sty $0a ; store closest player in $0a + jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position + lda #$80 ; a = #$80 + ldy #$05 ; bullet speed code + jsr aim_and_create_enemy_bullet ; get firing dir based on enemy ($08, $09) and player pos ($0b, $0a) + ; and creates bullet (type a) with speed y if appropriate + ldx ENEMY_CURRENT_SLOT + +@exit: + rts + +; ENEMY_FRAME #$04 (arm seeking player, reaching down) set appropriate ENEMY_VAR_1 +; update orb(s) ENEMY_VAR_1 (position offset index into dragon_arm_orb_pos_tbl) +dragon_arm_seek_player_logic: + lda ENEMY_X_VELOCITY_FRACT,x ; load enemy slot index of hand orb + ; ENEMY_X_VELOCITY_FRACT is set to hand enemy slot index on shoulder orm + tax ; transfer hand orb enemy slot index to x + jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01) + ; find closest player to the hand orb + sty $10 ; store closest player to hand orb in $10 + +; loop through enemy orbs (by following ENEMY_VAR_4, i.e. previous orbs) looking for when dragon_arm_orb_seek_should_move returns positive +; starting at hand, going up to shoulder +@enemy_orb_loop: + txa ; transfer current dragon arm orb enemy slot index to a + tay ; transfer current dragon arm orb enemy slot index to y + stx $11 ; store current dragon arm orb enemy slot index in $11 + ldx ENEMY_VAR_4,y ; see if previous orb (closer to body) is shoulder dragon arm orb + bmi @exit ; exit if previous orb is shoulder dragon arm orb + lda $10 ; load player index of player closest to enemy + sta $0a ; store closest player index in $0a for use in get_quadrant_aim_dir_for_player + jsr dragon_arm_orb_seek_should_move ; determine whether dragon arm orb should move, and if so, in which direction + bmi @enemy_orb_loop ; continue to previous orb (closer to body) if orb doesn't need to move + ldx $11 ; orb should move, load successful dragon arm orb enemy slot index + tay ; transfer successful orb enemy slot index to y + bne @dec_position ; branch if dragon_arm_orb_seek_should_move returned #$01, i.e. decrement ENEMY_VAR_1 + +; increment ENEMY_VAR_1, very similar to @dec_position +@inc_position: + lda ENEMY_VAR_4,x ; see if specified orb is shoulder dragon arm orb + bmi @check_child_orb_inc ; continue if shoulder dragon arm orb + ldy ENEMY_VAR_1,x ; not shoulder dragon arm orb, load ENEMY_VAR_1 + cpy #$08 + bne @check_child_orb_inc + tax + jmp @inc_position + +@check_child_orb_inc: + cpx $11 ; compare orb to move enemy slot index with enemy slot index of orb with ENEMY_VAR_1 set to #$08 + ; (or shoulder orb if not found) + bne @inc_var_1_exit ; branch if or is not the same + lda ENEMY_VAR_3,x ; load next dragon arm enemy slot index (farther away from body) + bmi @inc_11_var_1_exit ; increment enemy slot index $11's ENEMY_VAR_1 and exit if dragon arm orb is the hand + tax ; transfer next dragon arm enemy slot index (farther away from body) to x + dec ENEMY_VAR_1,x ; decrement position index (see dragon_arm_orb_pos_tbl) + lda ENEMY_VAR_1,x ; load position index (see dragon_arm_orb_pos_tbl) + and #$3f ; keep bits ..xx xxxx + sta ENEMY_VAR_1,x ; update position index + +@inc_11_var_1_exit: + ldx $11 ; load enemy slot index of orb with ENEMY_VAR_1 set to #$08 (or shoulder) to $11 + +@inc_var_1_exit: + inc ENEMY_VAR_1,x ; increment position index (see dragon_arm_orb_pos_tbl) + jmp @sanitize_pos_index_exit + +; finds the orb with the ENEMY_VAR_1 value of #$38 and decrements it if found +; decrement ENEMY_VAR_1, very similar to @inc_position +@dec_position: + lda ENEMY_VAR_4,x ; load previous dragon arm orb (closer to body) + bmi @check_child_orb_dec ; branch if previous orb is the shoulder + ldy ENEMY_VAR_1,x ; load successfully orb's position index + cpy #$38 ; see if it's #$38 + bne @check_child_orb_dec ; branch if not #$38 + tax ; found orb where ENEMY_VAR_1 is #$38 + ; transfer previous dragon arm orb (closer to body) to x + jmp @dec_position ; see if previous orb has an position index of #$38 + +; found orb with ENEMY_VAR_1 of #$38 or ended up on shoulder orb +@check_child_orb_dec: + cpx $11 ; compare found orb enemy slot index to successful dragon_arm_orb_seek_should_move orb enemy slot index + bne @dec_var_1_exit ; branch if they are not the same to decrement ENEMY_VAR_1 and exit + lda ENEMY_VAR_3,x ; load the next dragon arm orb (farther from body) + bmi @dec_11_var_1_exit ; branch if next orb is the hand + tax ; next orb is not hand, transfer that orb's enemy slot index to x + inc ENEMY_VAR_1,x ; increment that next orb's ENEMY_VAR_1 (position index) + lda ENEMY_VAR_1,x ; load ENEMY_VAR_1 to 'sanitize' it, i.e. make sure its bounds are safe + and #$3f ; keep bits ..xx xxxx + sta ENEMY_VAR_1,x ; store sanitized position index + +@dec_11_var_1_exit: + ldx $11 + +@dec_var_1_exit: + dec ENEMY_VAR_1,x + +@sanitize_pos_index_exit: + lda ENEMY_VAR_1,x + and #$3f ; keep bits ..xx xxxx + sta ENEMY_VAR_1,x + +@exit: + ldx ENEMY_CURRENT_SLOT + rts + +; executed for all but ENEMY_FRAME #$04 (arm seeking player) +; input +; * x - current dragon arm orb enemy slot index +; * $08 - +; * negative flag - +dragon_arm_animate: + lda #$00 ; a = #$00 + sta $08 + sta $0e + +; merges every arm orb's ENEMY_VAR_2 to set new y velocity control value +@arm_orb_loop: + stx $10 ; backup shoulder orb's enemy slot index to $10 + jsr @check_delay_run_timer + ldx $10 ; restore shoulder orb's enemy slot index to $10 + lda ENEMY_VAR_2,x ; load rotation direction timer + ora $0e ; merge with previous arm orb rotation timers + sta $0e ; update shoulder orb's arm orb rotation timer + lda ENEMY_VAR_3,x ; load next orb farther out from current orb + tax ; transfer to x register + bpl @arm_orb_loop ; loop if next orb is not the hand + ldx ENEMY_CURRENT_SLOT ; next orb is the hand, load shoulder orb's enemy slot + lda $0e ; when all orbs' ENEMY_VAR_2 are #$00 and ENEMY_FRAME = #$01, then ENEMY_FRAME #$01 is complete + sta ENEMY_Y_VELOCITY_FAST,x ; when ENEMY_Y_VELOCITY_FAST is #$00 and ENEMY_FRAME = #$01, will specify to move to ENEMY_FRAME = #$02 + ; only used for ENEMY_FRAME = #$01 + rts + +@check_delay_run_timer: + lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter + beq @timer_elapsed ; continue once animation timer has elapsed + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + lda #$00 ; timer elapsed, a = #$00 + beq @timer_logic ; always branch + +@timer_elapsed: + lda ENEMY_VAR_2,x ; load dragon arm orb rotation timer (can be negative) + beq @timer_logic ; branch if dragon arm is frozen (not moving) + bmi @negative_rotation_adjustment ; branch if dragon arm is rotating counterclockwise + dec ENEMY_VAR_2,x ; rotating clockwise, decrement dragon arm rotation timer + lda #$01 ; a = #$01 + bne @timer_logic + +@negative_rotation_adjustment: + inc ENEMY_VAR_2,x + lda #$ff ; a = #$ff + +@timer_logic: + sta $0c ; store dragon arm rotation timer adjustment (increase, decrease, stay same) + ldy #$00 ; y = #$00 + sty $0d + clc ; clear carry in preparation for addition + adc $08 + sta $0b + beq @exit + bmi @inc_timer_loop + +; very similar to @inc_timer_loop +@enemy_var_2_loop: + lda ENEMY_VAR_4,x ; load previous arm orb enemy slot (closer to body) + bmi @inc_var_1 ; branch if previous arm orb is the shoulder + ldy ENEMY_VAR_1,x ; load position index + cpy #$08 ; see if it is #$08 + bne @inc_var_1 ; branch if position index is not #$08 + ldy $0c ; load dragon arm rotation timer adjustment (increase, decrease, stay same) + beq @dec_var_2 ; decrement rotation timer if current rotation is frozen + bmi @dec_var_2 ; decrement rotation timer if current rotation is counterclockwise + lda #$00 ; rotation timer is positive, clear rotation timer + sta ENEMY_VAR_2,x ; reset duration timer + beq @continue ; always branch + +@dec_var_2: + lda #$01 + sta ENEMY_ANIMATION_DELAY,x + dec ENEMY_VAR_2,x + jmp @continue + +@inc_var_1: + dec $0d + inc ENEMY_VAR_1,x + lda ENEMY_VAR_1,x + and #$3f ; keep bits ..xx xxxx + sta ENEMY_VAR_1,x + +@continue: + dec $0b + bne @enemy_var_2_loop + beq @exit ; always branch + +; very similar to enemy_var_2_loop +@inc_timer_loop: + lda ENEMY_VAR_4,x ; load previous arm orb enemy slot (closer to body) + bmi @dec_var_1 ; branch if previous arm orb is the shoulder + ldy ENEMY_VAR_1,x ; load position index + cpy #$38 ; see if it is #$38 + bne @dec_var_1 ; branch if position index is not #$38 + ldy $0c ; load dragon arm rotation timer adjustment (increase, decrease, stay same) + beq @inc_var_2 ; increment rotation timer if current rotation is frozen + bpl @inc_var_2 ; increment rotation timer if current rotation is clockwise + lda #$00 ; rotation timer is negative, clear rotation timer + sta ENEMY_VAR_2,x ; reset duration timer + beq @inc_0b_continue ; always branch + +@inc_var_2: + lda #$01 ; a = #$01 + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + inc ENEMY_VAR_2,x + jmp @inc_0b_continue + +@dec_var_1: + inc $0d + dec ENEMY_VAR_1,x + lda ENEMY_VAR_1,x + and #$3f ; keep bits ..xx xxxx + sta ENEMY_VAR_1,x + +@inc_0b_continue: + inc $0b + bne @inc_timer_loop + +@exit: + lda $08 + clc ; clear carry in preparation for addition + adc $0d + sta $08 + rts + +; adjust dragon arm orb positions based on ENEMY_VAR_1 for all orbs in an arm +dragon_arm_orb_set_positions: + ldy ENEMY_CURRENT_SLOT ; load current dragon arm orb enemy slot (shoulder) + lda ENEMY_VAR_1,y ; load position index + sta ENEMY_X_VELOCITY_FAST,y ; set to position index + +; update the next arm orb's (farther away from body) x and y position based on +; the previous arm orb (closer to the body) for all orbs in the arm +@orb_pos_update_loop: + lda ENEMY_VAR_3,y ; load the next dragon arm orb enemy slot + bmi @exit ; exit if next orb is the hand (last orb in the arm) (#$ff) + tax ; transfer next orb enemy slot to x + ; starting here x refers to 'current' orb and + ; y refers to 'previous' orb (closer to body) + lda ENEMY_Y_POS,y ; load previous orb's y position on screen + sta $08 ; store previous orb y position in $08 + lda ENEMY_X_POS,y ; load previous orb's x position on screen + sta $09 ; store previous orb x position in $09 + lda ENEMY_X_VELOCITY_FAST,y ; load previous orb's position index (ENEMY_VAR_1) + clc ; clear carry in preparation for addition + adc ENEMY_VAR_1,x ; add previous orb's position index to current orb's position index + and #$3f ; strip bit 6 and 7 (sanitize) + sta ENEMY_X_VELOCITY_FAST,x ; set current orb's new ENEMY_VAR_1 (position index) + tay ; transfer position index to offset register + lda dragon_arm_orb_pos_tbl,y ; load offset from current y position for next orb + clc ; clear carry in preparation for addition + adc $08 ; add to current arm orb's y position + sta ENEMY_Y_POS,x ; update next orb's y position on screen + lda dragon_arm_orb_pos_tbl+16,y ; load offset from current x position for next orb + clc ; clear carry in preparation for addition + adc $09 ; add current arm orb's x position + sta ENEMY_X_POS,x ; update next orb's x position on screen + txa ; transfer next orb enemy slot to a + tay ; transfer next orb enemy slot to y + jmp @orb_pos_update_loop ; loop to next orb in the arm + +@exit: + ldx ENEMY_CURRENT_SLOT + rts + +; table for dragon arm orb position offsets from previous orb's pos based on ENEMY_VAR_1 +; possible offsets from previous orb (in decimal) +; note that [row 2] = -1 * [row 0] and [row 3] = -1 * [row 2] +; (15,0) (15,1) (15,3) (15,4) (14,6) (14,7) (13,8) (12,10) (11,11) (10,12) (8,13) (7,14) (6,14) (4,15) (3,15) (1,15) +; (0,15) (-1,15) (-3,15) (-4,15) (-6,14) (-7,14) (-8,13) (-10,12) (-11,11) (-12,10) (-13,8) (-14,7) (-14,6) (-15,4) (-15,3) (-15,1) +; (-15,0) (-15,-1) (-15,-3) (-15,-4) (-14,-6) (-14,-7) (-13,-8) (-12,-10) (-11,-11) (-10,-12) (-8,-13) (-7,-14) (-6,-14) (-4,-15) (-3,-15) (-1,-15) +; (0,-15) (1,-15) (3,-15) (4,-15) (6,-14) (7,-14) (8,-13) (10,-12) (11,-11) (12,-10) (13,-8) (14,-7) (14,-6) (15,-4) (15,-3) (15,-1) +dragon_arm_orb_pos_tbl: + .byte $00,$01,$03,$04,$06,$07,$08,$0a,$0b,$0c,$0d,$0e,$0e,$0f,$0f,$0f + .byte $0f,$0f,$0f,$0f,$0e,$0e,$0d,$0c,$0b,$0a,$08,$07,$06,$04,$03,$01 + .byte $00,$ff,$fd,$fc,$fa,$f9,$f8,$f6,$f5,$f4,$f3,$f2,$f2,$f1,$f1,$f1 + .byte $f1,$f1,$f1,$f1,$f2,$f2,$f3,$f4,$f5,$f6,$f8,$f9,$fa,$fc,$fd,$ff + .byte $00,$01,$03,$04,$06,$07,$08,$0a,$0b,$0c,$0d,$0e,$0e,$0f,$0f,$0f + +dragon_arm_orb_routine_04: + lda ENEMY_VAR_3,x + bpl @adv_routine + inc BOSS_SCREEN_ENEMIES_DESTROYED ; increase number of dragon arms destroyed + +@destroy_arm_part_loop: + lda ENEMY_VAR_4,x + bmi @set_slot_adv_routine + tax + jsr set_destroyed_enemy_routine ; update enemy's routine to the destroyed routine + jmp @destroy_arm_part_loop + +@set_slot_adv_routine: + ldx ENEMY_CURRENT_SLOT + +@adv_routine: + jmp advance_enemy_routine + +; pointer table for boss gemini (#$7 * #$2 = #$e bytes) +boss_gemini_routine_ptr_tbl: + .addr boss_gemini_routine_00 ; CPU address $9f03 - initialize velocities and attack delay to #$80 once created + .addr boss_gemini_routine_01 ; CPU address $9f25 - wait for the #$03 wall platings to be destroyed, then wait for attack delay, enable collision + .addr boss_gemini_routine_02 ; CPU address $9f3d - main routine, animate and fire based on timer + .addr boss_gemini_routine_03 ; CPU address $9fff - boss gemini 'destroyed' routine. destroy if ENEMY_VAR_4 is 1, otherwise, reset ENEMY_HP, play ting! sound, and go back to boss_gemini_routine_02 + .addr boss_gemini_routine_04 ; CPU address $a038 - create explosion, if both boss gemini destroyed, jump to boss_defeated_routine + .addr enemy_routine_explosion ; CPU address $e7b0 from bank 7 + .addr boss_gemini_routine_06 ; CPU address $a042 - remove enemy, if last gemini destroyed set level end delay + +; initialize velocities and attack delay to #$80 once created +boss_gemini_routine_00: + lda #$0a ; a = #$0a (boss gemini hp) + sta ENEMY_VAR_4,x ; set initial HP to #$0a, this routine doesn't ENEMY_HP in the standard way + lda ENEMY_X_POS,x ; load enemy x position on screen (set from level_4_enemy_screen_08) + sta ENEMY_VAR_1,x ; set static x position, used for offset calculations + lda #$80 ; a = #$80 (.5) + sta ENEMY_X_VELOCITY_FRACT,x ; set amount to move per frame to #$80 (.5) + lda #$00 ; a = #$00 + sta ENEMY_X_VELOCITY_FAST,x ; set initial x fast velocity to 0 + lda #$80 ; a = #$80 + sta ENEMY_ATTACK_DELAY,x ; set delay between attacks + lda #$40 ; a = #$40 (delay before appearing) + +; set the animation delay to a and advanced the ENEMY_ROUTINE +; input +; * a - the ENEMY_ANIMATION_DELAY +; this label is identical to two other labels +; * bank 7 - set_anim_delay_adv_enemy_routine +; * bank 0 - (this bank) set_anim_delay_adv_enemy_routine_00 +set_anim_delay_adv_enemy_routine_01: + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + jmp advance_enemy_routine ; advance to next routine + +; wait for the #$03 wall platings to be destroyed, then wait for attack delay, enable collision +boss_gemini_routine_01: + lda WALL_PLATING_DESTROYED_COUNT ; number of boss platings destroyed + cmp #$03 ; number of boss platings to destroy (level 4) + bcc @exit ; don't start up the boss gemini until all 3 platings have been destroyed + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne @exit ; exit if animation delay hasn't elapsed + lda #$a0 ; a = #$a0 + sta ENEMY_ATTACK_DELAY,x ; set delay between attacks + jsr enable_bullet_enemy_collision ; allow bullets to collide (and stop) upon colliding with boss gemini + lda #$20 ; a = #$20 + bne set_anim_delay_adv_enemy_routine_01 ; set ENEMY_ANIMATION_DELAY to #$20 and advance enemy routine + +@exit: + rts + +; main routine, animate and fire based on timer +boss_gemini_routine_02: + lda FRAME_COUNTER ; load frame counter + and #$07 ; keep bits .... .xxx + bne @set_sprite_mod_flag ; branch if not #$08th frame + inc ENEMY_FRAME,x ; #$08 frames have elapsed, increment enemy animation frame number (see boss_gemini_sprite_tbl) + lda ENEMY_FRAME,x ; load enemy animation frame number (see boss_gemini_sprite_tbl) + cmp #$03 ; see if past last frame + bcc @set_frame ; not past last frame, continue + lda #$00 ; past the last frame, go back to first frame (sprite_68) + +@set_frame: + sta ENEMY_FRAME,x ; set enemy animation frame number + +; determine sprite to used based on whether flashing after being hit and low hp flag +; if both low hp flag (ENEMY_VAR_3) is set and timer is odd, then flip so sprite is correct +@set_sprite_mod_flag: + lda ENEMY_VAR_3,x ; load boss gemini low hp flag + ldy ENEMY_VAR_2,x ; load timer that starts after being hit (#$10 -> #$00) + beq @set_sprite_offset_continue ; branch if the hit timer is #$00 + dec ENEMY_VAR_2,x ; hit timer is not zero, decrement + lda ENEMY_VAR_2,x ; re-load updated timer that starts after being hit + lsr ; shift bit 0 to carry + lda ENEMY_VAR_3,x ; re-load boss gemini low hp flag + bcc @set_sprite_offset_continue ; branch if bit 0 of hit timer is 0 + eor #$01 ; hit timer bit 0 was 1, flip bit 0 of low hp flag + +@set_sprite_offset_continue: + lsr ; shift low hp flag/flashing due to being hit flag to the carry flag + lda #$00 ; a = #$00 + bcc @continue ; branch if should show the green brain and not the red brain + ; (low hp or flashing after being hit) + lda #$03 ; a = #$03 + +; create projectile if attack delay has elapsed +@continue: + clc ; clear carry in preparation for addition + adc ENEMY_FRAME,x ; add #$00 or #$03 to enemy animation frame number + tay ; transfer to offset register + lda boss_gemini_sprite_tbl,y ; load correct sprite based on ENEMY_FRAME + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + lda ENEMY_ATTACK_FLAG ; see if enemies should attack + beq @wait_delay_update_pos ; branch if attack flag disabled to see if update position animation delay has elapsed + dec ENEMY_ATTACK_DELAY,x ; decrement delay between attacks + bne @wait_delay_update_pos ; branch if attack flag disabled to see if update position animation delay has elapsed + lda PLAYER_WEAPON_STRENGTH ; load player's weapon strength + asl + asl + asl ; multiply weapon strength by 8 + sta $08 ; store multiplied weapon strength into $08 + lda RANDOM_NUM ; load random number + adc FRAME_COUNTER ; add frame counter to random number + sta RANDOM_NUM ; re-randomize random number + lsr ; shift random number right (not sure of need) !(WHY?) + and #$03 ; keep bits 0 and 1 + tay ; transfer random number between 0 and 3 to offset register + lda boss_gemini_attack_delay_tbl,y ; load random attack delay + sec ; set carry flag in preparation for subtraction + sbc $08 ; subtract multiplied weapon strength from attack delay. the strong the weapon, the shorter the attack delay + sta ENEMY_ATTACK_DELAY,x ; set delay between attacks + lda #$1d ; a = #$1d (#$1d = spinning bubbles) + jsr generate_enemy_a ; generate #$1d enemy (spinning bubbles) + +@wait_delay_update_pos: + lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter + beq @calc_offset_set_pos ; branch to calculate new position if animation delay has elapsed + dec ENEMY_ANIMATION_DELAY,x ; animation delay hasn't elapsed, decrement enemy animation frame delay counter + bne @set_x_pos ; if animation delay still hasn't elapsed, set position based on FRAME_COUNTER and ENEMY_VAR_1 + jsr disable_enemy_collision ; animation delay has elapsed and boss gemini are about to separate + ; prevent player enemy collision check and allow bullets to pass through enemy + +@calc_offset_set_pos: + lda ENEMY_Y_VELOCITY_FRACT,x ; alternates between #$00 or #$80 + clc ; clear carry in preparation for addition + adc ENEMY_X_VELOCITY_FRACT,x ; load x fractional velocity. Always #$80 (.5) + sta ENEMY_Y_VELOCITY_FRACT,x ; store result back into x position offset + ; this overflows every #$02 frames, causing ENEMY_Y_VELOCITY_FAST to increment + ; every other frame + lda ENEMY_Y_VELOCITY_FAST,x ; load x position offset (#$00 or #$80) + adc ENEMY_X_VELOCITY_FAST,x ; add x direction (#$00 = away from center, #$ff = towards center) + ; this includes any overflow from previous addition + sta ENEMY_Y_VELOCITY_FAST,x ; set x position offset + ldy ENEMY_X_VELOCITY_FAST,x ; load x direction (#$00 = away from center, #$ff = towards center) + bmi @check_combined_set_x ; branch if boss gemini helmets are going to the center (combining), or have combined + cmp #$30 ; see if x position offset is at maximum (#$30) + bcc @set_x_pos ; branch if x position offset is less than max (#$30) + lda #$20 ; x position offset is max, reset to #$20 + bne @set_delay_reverse_dir ; always branch to reverse direction + +@check_combined_set_x: + tay ; transfer x position offset to y + bpl @set_x_pos ; branch if boss gemini haven't yet combined + jsr set_enemy_y_velocity_to_0 ; boss gemini have combined to become solid, pause motion + jsr enable_bullet_enemy_collision ; allow bullets to collide (and stop) upon colliding with boss gemini + lda #$30 ; a = #$30 (delay when gemini is fused) + +@set_delay_reverse_dir: + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + jsr reverse_enemy_x_direction ; reverse x direction + +; sets x position based on ENEMY_VAR_1 and FRAME_COUNTER +; even frames use addition, odd frames use subtraction +@set_x_pos: + lda FRAME_COUNTER ; load frame counter + lsr ; shift bit 0 to carry flag + lda ENEMY_VAR_1,x ; load boss gemini initial x position + bcs @phase_left ; odd frame, branch to subtract offset from initial x position + adc ENEMY_Y_VELOCITY_FAST,x ; even frame, add offset from initial x position + jmp @set_x_pos_exit ; set new x position and exit + +@phase_left: + sbc ENEMY_Y_VELOCITY_FAST,x + +@set_x_pos_exit: + sta ENEMY_X_POS,x ; set enemy x position on screen + rts + +; table for gemini boss sprite codes (#$6 bytes) +; ENEMY_FRAME is #$00, #$01, or #$02, but if ENEMY_VAR_3 is #$01, then #$03 is added so +; ENEMY_VAR_3 = #$00 -> sprite_68, sprite_69, sprite_6a +; ENEMY_VAR_3 = #$01 -> sprite_68, sprite_6b, sprite_6c (hit by bullet, or almost dead, red brain) +; sprite_68, sprite_69, sprite_6b, sprite_6c +boss_gemini_sprite_tbl: + .byte $68,$69,$6a ; ENEMY_VAR_3 is #$00 + .byte $68,$6b,$6c ; ENEMY_VAR_3 is #$01 + +; table for possible delays (#$4 bytes) +boss_gemini_attack_delay_tbl: + .byte $8a,$a9,$63,$d7 + +; boss gemini 'destroyed' routine, however, doesn't remove the enemy unless +; ENEMY_VAR_4 is 1, otherwise, reset ENEMY_HP, play ting! sound, and go back to boss_gemini_routine_02 +boss_gemini_routine_03: + lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter + beq @continue + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + +@continue: + dec ENEMY_VAR_4,x ; decrement boss gemini's HP + beq @adv_routine ; advance if HP is #$00 + lda ENEMY_VAR_4,x ; load boss gemini HP + cmp #$07 ; compare to #$07 + bcs @play_sound_set_routine_02 ; branch to skip low health flag setting and HP = 1 test + cmp #$01 ; see if HP is #$01 + bne @set_low_hp_flag ; branch if HP is not #$01 + lda #$52 ; HP is #$01, set score code to #$05 (2,000 points) and collision box to #$02 + sta ENEMY_SCORE_COLLISION,x ; update score and collision code + +@set_low_hp_flag: + lda #$01 ; a = #$01 + sta ENEMY_VAR_3,x ; set low hp flag, used to use different sprites so brain is red and not green + +@play_sound_set_routine_02: + lda #$01 ; a = #$01 + sta ENEMY_HP,x ; reset enemy hp to #$01, always #$01 until ENEMY_VAR_4 is #$00 when killed + lda #$10 ; a = #$10 + sta ENEMY_VAR_2,x ; initialize timer after being hit + lda #$16 ; a = #$16 (sound_16) + jsr play_sound ; play metal enemy hit ting sound + lda #$03 ; a = #$03 + jmp set_enemy_routine_to_a ; set enemy routine index to boss_gemini_routine_02 + +@adv_routine: + jmp advance_enemy_routine ; advance to next routine + +; create explosion, if both boss gemini destroyed, jump to boss_defeated_routine +boss_gemini_routine_04: + dec WALL_CORE_REMAINING ; load remaining boss gemini to destroy + bne @adv_routine ; at least one boss gemini exist, create normal explosion + jmp boss_defeated_routine ; normal explosion + final boom (with echo) + +@adv_routine: + jmp enemy_routine_init_explosion ; normal explosion + +; remove enemy, if last gemini destroyed set level end delay +boss_gemini_routine_06: + lda WALL_CORE_REMAINING ; load remaining boss gemini to destroy (at this point either #$00 or #$01) + beq @both_gemini_destroyed ; branch if both gemini are destroyed + jmp remove_enemy ; one more boss gemini exists, just remove this enemy + +@both_gemini_destroyed: + jsr shared_enemy_routine_clear_sprite ; set sprite code to 0 and advance to next routine + ; no routine after this, will be overwritten to #$00 in set_delay_remove_enemy + lda #$60 ; a = #$60 (delay before auto-move) + jmp set_delay_remove_enemy ; set delay to #$60 and remove the enemy + +; pointer table for spinning bubbles projectile (#$5 * #$2 = #$a bytes) +spinning_bubbles_routine_ptr_tbl: + .addr spinning_bubbles_routine_00 ; CPU address $a05b + .addr spinning_bubbles_routine_01 ; CPU address $a094 + .addr enemy_routine_init_explosion ; CPU address $e74b from bank 7 + .addr enemy_routine_explosion ; CPU address $e7b0 from bank 7 + .addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7 + +spinning_bubbles_routine_00: + jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01) + sty $0a ; store closest player in $0a + tya ; transfer closest player to a + sta ENEMY_VAR_2,x ; store closest player to enemy in ENEMY_VAR_2 + jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position + lda FRAME_COUNTER ; load frame counter + and #$03 ; keep bits .... ..xx + sta ENEMY_ATTRIBUTES,x ; store random number between 0 and 3 in the enemy attributes + tay ; transfer random number to y + lda spinning_bubbles_speed_tbl,y ; load bullet velocity routine table value (bullet_velocity_adjust_xx) + sta $06 ; store bullet direction velocity routine value (bullet_velocity_adjust_xx) in $06 + lda #$01 ; a = #$01 (quadrant_aim_dir_01) + sta $0f ; set quadrant_aim_dir_lookup_tbl offset to #$01 + jsr get_quadrant_aim_dir_for_player ; set a to the aim direction within a quadrant + ; based on source position ($09, $08) targeting player index $0a + pha ; push quadrant aim dir to the stack + jsr set_bullet_velocities ; set the projectile X and Y velocities (both high and low) based on register a (#$01) + pla ; pop quadrant aim dir from stack + jsr get_rotate_dir ; determine which direction to rotate + ; based on a (quadrant aim dir) and quadrant ($07) + lda $0c ; load new enemy aim direction + sta ENEMY_VAR_1,x ; set new enemy aim direction + lda #$20 ; a = #$20 + sta ENEMY_ATTACK_DELAY,x ; set delay between aim readjustments + jmp advance_enemy_routine ; advance to spinning_bubbles_routine_01 + +; table for possible initial speed codes (#$4 bytes) +; bullet_velocity_adjust_01 (.75x), bullet_velocity_adjust_03 (1.25x) +; bullet_velocity_adjust_04 (1.5x), bullet_velocity_adjust_05 (1.62x) +spinning_bubbles_speed_tbl: + .byte $01,$03,$04,$05 + +spinning_bubbles_routine_01: + ldy ENEMY_ATTRIBUTES,x ; load enemy attributes initialized in spinning_bubbles_routine_00 + ; it is random number between 0 and 3 inclusively + inc ENEMY_ANIMATION_DELAY,x ; increment animation delay + lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter + cmp spinning_bullet_spin_tbl,y ; compare animation delay to random value from table + ; used to determine if bubble should animation, i.e. spin + bcc @continue ; don't animate bubble if delay was below threshold in spinning_bullet_spin_tbl + lda #$00 ; a = #$00 + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + inc ENEMY_FRAME,x ; increment enemy animation frame number + lda ENEMY_FRAME,x ; load enemy animation frame number + cmp #$06 ; see if past last frame + bcc @set_frame_continue ; continue to set ENEMY_FRAME if not past the last frame + lda #$00 ; past the last animation frame, reset to #$00 + +@set_frame_continue: + sta ENEMY_FRAME,x ; set enemy animation frame number + +@continue: + lda ENEMY_FRAME,x ; load enemy animation frame number + clc ; clear carry in preparation for addition + adc #$6d ; determine sprite code based on ENEMY_FRAME + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + jsr update_enemy_pos ; apply velocities and scrolling adjust + lda ENEMY_VAR_3,x ; load the number of times the bubbles have checked for aiming readjustment + cmp #$14 ; compare that to #$14 + bcs @exit ; don't readjust more than 13 times + dec ENEMY_ATTACK_DELAY,x ; decrement delay between attacks + bne @exit ; exit if direction adjustment delay hasn't elapsed + lda #$08 ; attack delay has elapsed, set a = #$08 (delay before direction adjust) + sta ENEMY_ATTACK_DELAY,x ; set new direction adjustment delay + inc ENEMY_VAR_3,x ; increment number of direction adjustments + jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position + lda ENEMY_VAR_2,x ; load the player closest to the spinning bubble + sta $0a ; set player index for call aim_var_1_for_quadrant_aim_dir_01 + jsr aim_var_1_for_quadrant_aim_dir_01 ; determine next aim direction [#$00-#$0b] ($0c), adjusts ENEMY_VAR_1 to get closer to that value using quadrant_aim_dir_01 + bcs @exit ; exit if already aiming at player + lda ENEMY_ATTRIBUTES,x ; need to readjust aiming direction, load enemy attributes + ora #$03 ; set bits .... ..xx + sta ENEMY_ATTRIBUTES,x ; set enemy attributes to #$03 + ; this will cause the bubble to spin very frequently due to check against spinning_bullet_spin_tbl + lda ENEMY_VAR_1,x ; load enemy aim direction + asl ; double since each entry is #$02 bytes + tay ; transfer to offset register + lda spinning_bullet_vel_tbl,y ; load y fractional velocity + sta ENEMY_Y_VELOCITY_FRACT,x ; set y fractional velocity + lda spinning_bullet_vel_tbl+1,y ; load y fast velocity + sta ENEMY_Y_VELOCITY_FAST,x ; set y fast velocity + lda spinning_bullet_vel_tbl+12,y ; load x fractional velocity + sta ENEMY_X_VELOCITY_FRACT,x ; set y fractional velocity + lda spinning_bullet_vel_tbl+13,y ; load x fast velocity + sta ENEMY_X_VELOCITY_FAST,x ; set x fast velocity + +@exit: + rts + +; table for spinning bubble x/y velocities (#$3c bytes) +spinning_bullet_vel_tbl: + .byte $00,$00 ; 0 + .byte $63,$00 ; .39 + .byte $c0,$00 ; .75 + .byte $0f,$01 ; 1.06 + .byte $4b,$01 ; 1.29 + .byte $72,$01 ; 1.44 + .byte $7e,$01 ; 1.49 + .byte $72,$01 ; 1.44 + .byte $4b,$01 ; 1.29 + .byte $0f,$01 ; 1.06 + .byte $c0,$00 ; .75 + .byte $63,$00 ; .39 + .byte $00,$00 ; 0 + .byte $9d,$ff ; -.39 + .byte $40,$ff ; -.75 + .byte $f1,$fe ; -1.06 + .byte $b5,$fe ; -1.29 + .byte $8e,$fe ; -1.44 + .byte $82,$fe ; -1.49 + .byte $8e,$fe ; -1.44 + .byte $b5,$fe ; -1.29 + .byte $f1,$fe ; -1.06 + .byte $40,$ff ; -.75 + .byte $9d,$ff ; -.39 + .byte $00,$00 ; 0 + .byte $63,$00 ; .39 + .byte $c0,$00 ; .75 + .byte $0f,$01 ; 1.06 + .byte $4b,$01 ; 1.29 + .byte $72,$01 ; 1.44 + +; table for possible spinning speeds (#$4 bytes) +spinning_bullet_spin_tbl: + .byte $08,$06,$04,$02 + +; pointer table for blue soldier (#$7 * #$2 = #$e bytes) +blue_soldier_routine_ptr_tbl: + .addr red_blue_soldier_routine_00 ; CPU address $a157 - initialize position and x velocity + .addr blue_soldier_routine_01 ; CPU address $a18a - run across screen, once past trigger point, see if close to player, if so advance routine to jump down + .addr blue_soldier_routine_02 ; CPU address $a1f7 - go through jump animation routine, then initialize jump velocities and advance routine + .addr blue_soldier_routine_03 ; CPU address $a245 - animate jumping down frames based on time since jump, apply velocity + .addr enemy_routine_init_explosion ; CPU address $e74b from bank 7 + .addr enemy_routine_explosion ; CPU address $e7b0 from bank 7 + .addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7 + +; initialization for both red and blue soldiers +red_blue_soldier_routine_00: + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + asl ; double since each entry is #$02 bytes + tay ; transfer offset to y + lda red_blue_soldier_init_pos_tbl,y ; load initial y position + sta ENEMY_Y_POS,x ; set enemy y position on screen + lda red_blue_soldier_init_pos_tbl+1,y ; load initial x position + sta ENEMY_X_POS,x ; set enemy x position on screen + lda ENEMY_ATTRIBUTES,x ; reload enemy attributes + and #$01 ; keep bit 0 + asl ; double since each entry is #$02 bytes + tay ; transfer offset to y + lda red_blue_soldier_init_vel_tbl,y ; load initial fractional x velocity + sta ENEMY_X_VELOCITY_FRACT,x ; set fractional x velocity + lda red_blue_soldier_init_vel_tbl+1,y ; load initial fast x velocity + sta ENEMY_X_VELOCITY_FAST,x ; set fast x velocity + jmp advance_enemy_routine ; advance to next routine + +; table for blue guys stop positions (#$8 bytes) +; byte 0 - y position +; byte 1 - x position +red_blue_soldier_init_pos_tbl: + .byte $9c,$f0 ; lower right - (#$f0, #$9c) - uses negative x velocity + .byte $9c,$10 ; lower left - (#$10, #$9c) - uses positive x velocity + .byte $61,$f0 ; upper right - (#$f0, #$61) - uses negative x velocity + .byte $61,$10 ; upper left - (#$10, #$61) - uses positive x velocity + +; table for red/blue guys running velocities (#$4 bytes) +; byte 0 - fractional x velocity +; byte 1 - fast x velocity +red_blue_soldier_init_vel_tbl: + .byte $00,$ff ; x velocity from left + .byte $00,$01 ; x velocity from right + +; run across screen, once past trigger point, see if close to player, if so advance routine to jump down +blue_soldier_routine_01: + jsr red_blue_soldier_set_run_frame ; set appropriate ENEMY_FRAME for running animation + lda ENEMY_FRAME,x ; load enemy animation frame number + clc ; clear carry in preparation for addition + adc #$85 ; ENEMY_FRAME is offset from sprite_85, add #$85 to get sprite + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + lsr ; shift bit 0 to carry (left or right soldier) + lda #$47 ; right soldier, horizontal flip and override with sprite palette #$02 + bcc @continue ; branch if right soldier + lda #$07 ; left soldier, no flip and override with sprite palette #$02 + +@continue: + sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes (palette and whether to flip horizontally) + jsr red_blue_soldier_set_bg_priority ; set sprite bg priority for when blue soldiers are behind pillar + jsr update_enemy_pos ; apply velocities and scrolling adjust + lda ENEMY_X_POS,x ; load enemy x position on screen + cmp #$d8 ; compare to enable attack x position from right + bcs blue_soldier_routine_01_exit ; exit if (right) soldier should keep running toward enable attack trigger point + cmp #$28 ; compare to enable attack x position from left + bcc blue_soldier_routine_01_exit ; exit if (left) soldier should keep running toward enable attack trigger point + jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01) + cmp #$10 ; see if blue soldier within #$10 horizontal pixels of closest player + bcs blue_soldier_routine_01_exit ; exit if farther than #$10 from enemy + lda #$00 ; close to player, set delay and move to next routine to jump attack + sta ENEMY_FRAME,x ; set ENEMY_FRAME to #$00 (sprite_85) + ; will be interpreted as sprite_88 in blue_soldier_routine_01 + lda #$01 ; a = #$01 + jmp set_anim_delay_adv_enemy_routine_01 ; set ENEMY_ANIMATION_DELAY to #$01 and advance enemy routine + +red_blue_soldier_set_run_frame: + lda FRAME_COUNTER ; load frame counter + and #$03 ; keep bits .... ..xx + bne blue_soldier_routine_01_exit ; exit if not 4th frame + inc ENEMY_FRAME,x ; 4th frame, increment enemy animation frame number + lda ENEMY_FRAME,x ; load enemy animation frame number + cmp #$03 ; compare to the last blue soldier running sprite + bcc blue_soldier_routine_01_exit ; exit if not past last running sprite + lda #$00 ; reset back to first blue soldier running sprite + sta ENEMY_FRAME,x ; set enemy animation frame number + +blue_soldier_routine_01_exit: + rts + +; sets the sprite bg priority for when red and blue soldiers are behind pillar +red_blue_soldier_set_bg_priority: + ldy #$00 ; y = #$00 + lda ENEMY_X_POS,x ; load enemy x position on screen + cmp #$dc ; compare x position to #$dc (86% of screen) + bcs @continue ; branch if to the right of #$dc (not behind pillar) + cmp #$24 ; compare x position to #$24 (14% of screen) + bcs @set_sprite_attr ; branch if to the right of #$24 (not behind pillar) + +; left of #$24 or right of #$dc, e.g. behind pillar +@continue: + ldy #$20 ; y = #$20 + +@set_sprite_attr: + sty $08 ; set $08 to have the bg priority and sprite palette flags + lda ENEMY_SPRITE_ATTR,x ; load enemy sprite attributes + and #$df ; strip bit 5 (bg priority) + ora $08 ; specify bg priority and palette flags from $08 + sta ENEMY_SPRITE_ATTR,x ; set sprite attributes + +blue_soldier_routine_02_exit: + rts + +; go through jump animation routine, then initialize jump velocities and advance routine + blue_soldier_routine_02: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne blue_soldier_routine_02_exit ; exit if animation delay hasn't elapsed + lda #$08 ; a = #$08 + sta ENEMY_ANIMATION_DELAY,x ; set next animation frame enemy delay counter + lda ENEMY_FRAME,x ; load enemy animation frame number (offset from sprite_88) + clc ; clear carry in preparation for addition + adc #$88 ; add blue soldier attack starting sprite location (sprite_88) + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + inc ENEMY_FRAME,x ; increment enemy animation frame number + lda ENEMY_FRAME,x ; load enemy animation frame number + cmp #$03 ; see if past last attacking sprite of attack animation + bcc blue_soldier_routine_02_exit ; exit if not on last attack frame + lda ENEMY_SPRITE_ATTR,x ; showing last attack sprite, start jump down to actually attack + ; load enemy sprite attributes + and #$df ; strip bits 5 (bg priority) + sta ENEMY_SPRITE_ATTR,x ; update sprite attribute so blue soldier when attacking is always in foreground + jsr enable_enemy_collision ; enable bullet-enemy collision and player-enemy collision checks + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + and #$01 ; keep bit 0 (left or right soldier) + asl ; double since each entry is #$02 bytes + tay ; transfer to offset register + lda blue_soldier_jmp_x_vel_tbl,y ; load fractional x velocity + sta ENEMY_X_VELOCITY_FRACT,x ; set enemy fractional x velocity + lda blue_soldier_jmp_x_vel_tbl+1,y ; load fast x velocity + sta ENEMY_X_VELOCITY_FAST,x ; set enemy fast x velocity + lda #$00 + sta ENEMY_Y_VELOCITY_FRACT,x ; set y fractional velocity to #$00 + lda #$ff ; -1 + sta ENEMY_Y_VELOCITY_FAST,x ; set y fast velocity to #$ff (-1) + lda #$10 ; a = #$10 + jmp set_anim_delay_adv_enemy_routine_01 ; set ENEMY_ANIMATION_DELAY to #$10 and advance enemy routine + +; table for blue guy x velocity while jumping (attacking) (#$4 bytes) +blue_soldier_jmp_x_vel_tbl: + .byte $c0,$ff ; coming from left + .byte $40,$00 ; coming from right + +; animate jumping down frames based on time since jump, apply velocity +blue_soldier_routine_03: + lda #$8b ; a = #$8b (sprite_8b) + ldy ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter + beq @continue ; branch if delay is #$00 to use sprite_8b + dec ENEMY_ANIMATION_DELAY,x ; delay hasn't elapsed. decrement enemy animation frame delay counter + lda #$8a ; a = #$8a (sprite_8a) + +@continue: + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + jsr add_10_to_enemy_y_fract_vel ; add #$10 to y fractional velocity (.06 faster) + jmp update_enemy_pos ; apply velocities and scrolling adjust + +; pointer table for red shooting guys (#$6 * #$2 = #$c bytes) +red_soldier_routine_ptr_tbl: + .addr red_blue_soldier_routine_00 ; CPU address $a157 - initialize position and x velocity + .addr red_soldier_routine_01 ; CPU address $a266 - run across screen, once past trigger point, see if close to player, if so advance routine to fire at player + .addr red_soldier_routine_02 ; CPU address $a2bb - fire ENEMY_VAR_1 times and then go back to red_soldier_routine_01 to continue running off screen + .addr enemy_routine_init_explosion ; CPU address $e74b from bank 7 + .addr enemy_routine_explosion ; CPU address $e7b0 from bank 7 + .addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7 + +; run across screen, once past trigger point, see if close to player, if so advance routine to fire at player +; if already fired from red_soldier_routine_02, just continue running off screen +red_soldier_routine_01: + jsr red_blue_soldier_set_run_frame ; set appropriate ENEMY_FRAME for running animation + lda ENEMY_FRAME,x ; load enemy animation frame number + clc ; clear carry in preparation for addition + adc #$8c ; ENEMY_FRAME is offset from sprite_8c, add #$8c to get sprite + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + lsr ; shift bit 0 to carry (left or right soldier) + lda #$46 ; right soldier, horizontal flip and override with sprite palette #$01 + bcc @continue ; branch if right soldier + lda #$06 ; left soldier, no flip and override with sprite palette #$01 + +@continue: + sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes (palette and whether to flip horizontally) + jsr red_blue_soldier_set_bg_priority ; set sprite bg priority for when red soldiers are behind pillar + jsr update_enemy_pos ; apply velocities and scrolling adjust + lda ENEMY_VAR_2,x ; load soldier fired flag + bne red_soldier_routine_exit ; exit when red soldier has already fired at the player + ; this allows the red soldier to continue running off screen + lda ENEMY_X_POS,x ; load enemy x position on screen + cmp #$d8 ; compare to enable attack x position from right + bcs red_soldier_routine_exit ; exit if (right) soldier should keep running toward enable attack trigger point + cmp #$28 ; compare to enable attack x position from left + bcc red_soldier_routine_exit ; exit if (left) soldier should keep running toward enable attack trigger point + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + lsr + lsr ; shift bit 1 into the carry flag + lda #$10 ; a = #$10 (carry clear attack distance) + bcc @attack_if_close ; branch if carry flag clear + lda #$30 ; a = #$30 (carry flag set attack distance) + +@attack_if_close: + sta $0f ; set minimum attack distance + jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01) + cmp $0f ; compare closest player x to $0f + bcs red_soldier_routine_exit ; exit if player too far away from enemy to attack + lda #$8f ; a = #$8f (sprite_8f) red soldier facing player + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + lda #$03 ; a = #$03 + sta ENEMY_VAR_1,x ; set to fire #$03 bullets + lda #$10 ; a = #$10 (delay before first attack) + sta ENEMY_ATTACK_DELAY,x ; set delay between attacks + jmp advance_enemy_routine ; advance to red_soldier_routine_02 + +; fire ENEMY_VAR_1 times and then go back to red_soldier_routine_01 +red_soldier_routine_02: + dec ENEMY_ATTACK_DELAY,x ; decrement delay between attacks + beq red_soldier_fire_weapon ; fire weapon if attack delay has elapsed + lda ENEMY_ATTACK_DELAY,x ; load delay between attacks + cmp #$2c ; see if attack delay is #$2c + bne red_soldier_routine_exit ; exit if attack delay isn't #$2c + lda ENEMY_SPRITE_ATTR,x ; attack delay is #$2c, load enemy sprite attributes + and #$f7 ; strip recoil effect flag + sta ENEMY_SPRITE_ATTR,x ; update enemy sprite attribute + +red_soldier_routine_exit: + rts + +red_soldier_fire_weapon: + lda #$90 ; a = #$90 (sprite_90) red soldier facing player with weapon + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + dec ENEMY_VAR_1,x ; decrement number of bullets to fire + bmi @set_routine_01 ; go back to red_soldier_routine_01 if all bullets have been fired + lda #$30 ; a = #$30 (delay when shooting) + sta ENEMY_ATTACK_DELAY,x ; set delay between attacks + lda ENEMY_SPRITE_ATTR,x ; load enemy sprite attributes + ora #$08 ; set bit 3 (recoil effect) + sta ENEMY_SPRITE_ATTR,x ; update sprite attribute + jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01) + sty $0a ; store closest player in $0a + jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position + lda #$00 ; a = #$00 + ldy #$04 ; bullet speed code + jmp aim_and_create_enemy_bullet ; get firing dir based on enemy ($08, $09) and player pos ($0b, $0a) + ; and creates bullet (type a) with speed y if appropriate + +@set_routine_01: + inc ENEMY_VAR_2,x ; set flag indicating soldier has already fired at player + lda #$02 ; a = #$02 + jmp set_enemy_routine_to_a ; set enemy routine index to red_soldier_routine_01 + +; pointer table for red/blue guys generator (#$3 * #$2 = #$6 bytes) +red_blue_soldier_gen_routine_ptr_tbl: + .addr red_blue_soldier_gen_routine_00 ; CPU address $a304 + .addr red_blue_soldier_gen_routine_01 ; CPU address $a309 + .addr remove_enemy ; CPU address $e809 from bank 7 + +red_blue_soldier_gen_routine_00: + lda #$80 ; a = #$80 + jmp set_anim_delay_adv_enemy_routine_01 ; set generation delay to #$80 and advance enemy routine + +red_blue_soldier_gen_routine_01: + lda WALL_PLATING_DESTROYED_COUNT ; number of boss platings destroyed + cmp #$03 ; compare to number of wall platings on level 4 boss screen + bcc @continue ; continue to generate red and blue soldiers if all plates haven't been destroyed + jmp remove_enemy ; all plates have been destroyed, remove enemy to stop generating red and blue soldiers + +@continue: + lda FRAME_COUNTER ; load frame counter + lsr ; shift bit 0 to the carry flag + bcs red_blue_soldier_gen_routine_01_exit ; exit if odd frame + dec ENEMY_ANIMATION_DELAY,x ; even frame, decrement enemy animation frame delay counter + bne red_blue_soldier_gen_routine_01_exit ; exit if generation delay hasn't elapsed + +; reads from red_blue_solider_data_tbl and based on byte, creates red or blue soldier, or waits +@read_soldier_byte: + ldy ENEMY_VAR_1,x ; load the soldier generation read offset + +@read_soldier_data: + inc ENEMY_VAR_1,x ; increment the soldier generation read offset + lda red_blue_solider_data_tbl,y ; read the data byte for generating a red or blue soldier + cmp #$ff ; see if the last byte was read (end of data byte) + bne @eval_data_byte ; continue if didn't read the end of data byte + ldy #$00 ; finished reading data, reset read offset and start over + tya ; transfer y to a + sta ENEMY_VAR_1,x ; reset the soldier generation read offset to repeated the pattern + beq @read_soldier_data ; always branch to start from the beginning + +@eval_data_byte: + lda red_blue_solider_data_tbl,y ; reload the data byte + bmi @set_delay_exit ; if the byte is negative, don't create soldier, instead delay for amount specified + and #$03 ; byte is positive, creating red or blue soldier, keep bits .... ..xx + sta $08 ; set red or blue soldier to generate's ENEMY_ATTRIBUTES + lda red_blue_solider_data_tbl,y ; reload the data byte + lsr + lsr ; bit 3 specifies which soldier to generate + sta $09 ; set whether to create a red or blue soldier (0 = red soldier, 1 = blue soldier) + jsr @find_slot_init_red_blue_soldier ; find enemy slot and create soldier + jmp @read_soldier_byte ; move to the next byte + +@set_delay_exit: + asl + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + rts + +@find_slot_init_red_blue_soldier: + jsr find_next_enemy_slot ; find next available enemy slot, put result in x register + bne @exit ; exit if no enemy slot available + lda $09 ; load whether to create a red or blue soldier (0 = red soldier, 1 = blue soldier) + lsr ; shift bit 0 to the carry flag + lda #$1f ; a = #$1f (enemy type for red soldier) + bcc @init_red_blue_soldier ; branch if + lda #$1e ; a = #$1e (enemy type for blue soldier) + +@init_red_blue_soldier: + sta ENEMY_TYPE,x ; set current enemy type to either red or blue soldier + jsr initialize_enemy ; initialize enemy variables + lda $08 ; load blue or red soldier's initial position and velocity + ; see red_blue_soldier_init_pos_tbl and red_blue_soldier_init_vel_tbl + sta ENEMY_ATTRIBUTES,x + +@exit: + ldx ENEMY_CURRENT_SLOT ; restore x to red blue soldier generator enemy slot index + +red_blue_soldier_gen_routine_01_exit: + rts + +; table for red or blue soldier soldier generation (#$1c bytes) +; if byte is negative +; * a soldier isn't generated and the byte value shifted left is the delay +; if positive +; * bits 0, 1, and 2 - ENEMY_ATTRIBUTES for red or blue soldier to generate +; * bit 3 - 0 = red solider, 1 = blue soldier +red_blue_solider_data_tbl: + .byte $00,$01,$02,$03,$d0 ; red soldier (x4), #$a0 delay + .byte $06,$07,$a0 ; blue soldier (x2), #$40 delay + .byte $04,$05,$c0 ; blue soldier (x2), #$80 delay + .byte $00,$01,$b0 ; red soldier (x2), #$60 delay + .byte $02,$03,$d0 ; red soldier (x4), #$a0 delay + .byte $04,$05,$06,$07,$d0 ; blue soldier (x4), #$a0 delay + .byte $00,$01,$02,$03,$fe ; red soldier (x4), #$fc delay + .byte $ff + +; pointer table for grenade generator (#$3 * #$2 = #$6 bytes) +ice_grenade_generator_routine_ptr_tbl: + .addr ice_grenade_generator_routine_00 ; CPU address $a38a + .addr ice_grenade_generator_routine_01 ; CPU address $a399 + .addr remove_enemy ; CPU address $e809 from bank 7 + +ice_grenade_generator_routine_00: + jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll + lda ENEMY_X_POS,x ; load enemy x position on screen + cmp #$c8 + bcs ice_grenade_exit ; exit if grenade generator not yet at trigger point (78% of screen) + lda #$01 ; a = #$01 (ENEMY_ANIMATION_DELAY) + jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to #$01; advance enemy routine + +ice_grenade_generator_routine_01: + jsr add_scroll_to_enemy_pos ; adjust enemy location based on scroll + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne ice_grenade_exit ; exit if delay hasn't elapsed + lda #$80 ; a = #$80 (delay between grenades) + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + lda #$11 ; a = #$11 (ice grenade) + jmp generate_enemy_a ; generate #$11 enemy (ice grenade) + +; pointer table for grenade (#$5 * #$2 = #$a bytes) +ice_grenade_routine_ptr_tbl: + .addr ice_grenade_routine_00 ; CPU address $a3b5 - play sound, initialize velocity + .addr ice_grenade_routine_01 ; CPU address $a3d7 - animate, apply gravity, check for collision + .addr mortar_shot_routine_03 ; CPU address $e752 from bank 7 - play explosion sound, update collision, hide sprite + .addr enemy_routine_explosion ; CPU address $e7b0 from bank 7 + .addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7 + +; play sound, initialize velocity +ice_grenade_routine_00: + lda #$1a ; a = #$1a ("piiuuu" sound) (sound_1a) + jsr play_sound ; play ice grenade whistling noise sound + lda #$20 ; a = #$20 (bg priority flag) + sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes + lda #$80 ; a = #$80 (.5) + sta ENEMY_X_VELOCITY_FRACT,x ; set ice grenade fractional x velocity + lda #$00 ; a = #$00 + sta ENEMY_X_VELOCITY_FAST,x ; set ice grenade fast x velocity + lda #$00 ; a = #$00 (unnecessary) + sta ENEMY_Y_VELOCITY_FRACT,x ; set ice grenade fractional y velocity + lda #$fe ; a = #$fe (-2) + sta ENEMY_Y_VELOCITY_FAST,x ; set ice grenade fast y velocity + jmp advance_enemy_routine ; advance to ice_grenade_routine_01 + +ice_grenade_exit: + rts + +; animate, apply gravity, check for collision +ice_grenade_routine_01: + lda FRAME_COUNTER ; load frame counter + and #$07 ; keep bits ... .xxx + bne @continue ; continue without changing sprite if #$08 frames haven't elapsed + inc ENEMY_FRAME,x ; #$08 frames have elapsed, increment enemy animation frame number + +@continue: + lda ENEMY_FRAME,x ; load enemy animation frame number + and #$03 ; keep bit 0 and 1 (allows frame number to continue incrementing) + tay ; transfer ice_grenade_sprite_tbl offset to y + lda ice_grenade_sprite_tbl,y ; load appropriate sprite code + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + jsr update_enemy_pos ; apply velocities and scrolling adjust + lda #$0a ; a = #$0a (gravity) + jsr add_a_to_enemy_y_fract_vel ; add a to enemy y fractional velocity + bmi ice_grenade_exit ; exit if no overflow + lda #$00 ; a = #$00 + sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes + ldy #$04 ; y = #$04 (distance from ground for explosion) + jsr add_y_to_y_pos_get_bg_collision ; add #$04 to enemy y position and gets bg collision code + beq ice_grenade_exit ; exit if no collision + lda #$24 ; collision, set a = #$24 (sound_24) + jsr play_sound ; play explosion sound + jmp advance_enemy_routine ; advance to mortar_shot_routine_03 + +; table for grenade sprite codes (#$4 bytes) +; sprite_74, sprite_75, sprite_76, sprite_77 +ice_grenade_sprite_tbl: + .byte $74,$75,$76,$77 + +; pointer table for tank (#$6 * #$2 = #$c bytes) +; tank is actually in nametable and stationary (not a sprite) +; auto scroll makes it look like the tank is approaching the player (ice separators are actually sprites) +tank_routine_ptr_tbl: + .addr tank_routine_00 ; CPU address $a41a - initialize tank position and palettes + .addr tank_routine_01 ; CPU address $a448 - animate tank driving to player until gets to within #$a0 pixels of player + .addr tank_routine_02 ; CPU address $a4ee - tank stopped, fire at player + .addr tank_routine_03 ; CPU address $a5b5 - tank starts moving again after stopping in previous routine + .addr tank_routine_04 ; CPU address $a5e3 - disable collision, prep variables for next routine to remove tank, play + .addr tank_routine_05 ; CPU address $a5f8 - tank destroyed routine + +; initialize tank position and palettes +tank_routine_00: + lda #$30 ; a = #$30 + sta ENEMY_X_POS,x ; set enemy x position on screen + lda #$01 ; a = #$01 + sta ENEMY_X_VEL_ACCUM,x ; specify that the tank is off the screen to the right + lda #$90 ; a = #$90 + sta ENEMY_Y_POS,x ; enemy y position on screen + lda #$0c ; a = #$0c + sta ENEMY_VAR_1,x ; set turret aim direction (straight to the left) + lsr ; a = #$06 + sta ENEMY_ATTACK_DELAY,x ; set delay between attacks (#$06) + lda #$3f ; a = #$3f + sta PAUSE_PALETTE_CYCLE ; disable palette color cycling + ; tank is nametable tiles not a sprite, don't want its colors to change + sta TANK_ICE_JOINT_SCROLL_FLAG ; set the ice joint enemy move left while player walks right + sta LEVEL_PALETTE_INDEX+2 ; overwrite level palette to #$3f (offset into game_palettes) + ; (COLOR_WHITE_20, COLOR_DARK_GRAY_00, COLOR_MED_ORANGE_17) + lda #$41 ; a = #$41 (COLOR_DARK_ORANGE_07, COLOR_DARK_GRAY_00, COLOR_MED_ORANGE_17) + sta LEVEL_PALETTE_INDEX+3 ; overwrite level background palette to #$41 (offset into game_palettes) + lda #$10 ; number of palettes to load + jsr load_palettes_color_to_cpu ; load #$10 palette colors into PALETTE_CPU_BUFFER based on LEVEL_PALETTE_INDEX + ldx ENEMY_CURRENT_SLOT ; load the current enemy slot number + jmp advance_enemy_routine ; move to tank_routine_01 + +; animate tank driving to player until gets to within #$a0 pixels of player +tank_routine_01: + lda FRAME_COUNTER ; load frame counter + and #$01 ; auto scroll every other frame + sta TANK_AUTO_SCROLL ; enable automatic screen scroll to simulate tank scrolling left + jsr tank_update_pos ; update the tank's position, enabling bullet collision when appropriate + lda ENEMY_X_VEL_ACCUM,x ; load tank visibility (#$00 = visible, #$01 = off screen to right, #$ff = off screen to left) + bne tank_move_logic ; branch if tank is not fully visible + lda ENEMY_X_POS,x ; tank is now on screen, load enemy x position on screen + cmp #$a0 ; compare to tank stopping point + bcc tank_stop ; stop the tank if it at the stopping point + +tank_move_logic: + lda ENEMY_HP,x ; load enemy hp + beq @draw_tire ; branch if hp is 0 + dec ENEMY_ATTACK_DELAY,x ; enemy hp is not 0, decrement delay between attacks + bne @draw_tire ; branch if attack delay hasn't elapsed + lda #$1e ; a = #$1e (sound_1e) + jsr play_sound ; play tank attack sound + lda #$06 ; a = #$06 (tank sound repeat frequency) + sta ENEMY_ATTACK_DELAY,x ; set delay between attacks + +@draw_tire: + lda FRAME_COUNTER ; load frame counter + and #$03 ; keep bits .... ..xx (change tire animation every #$04 frames) + tay ; transfer to tank_wheel_supertile_tbl offset + lda tank_wheel_supertile_tbl,y ; load the correct tank tires based on frame + sta $10 ; tank tire super-tile + lda FRAME_COUNTER ; load frame counter + and #$01 ; keep bits .... ...x + bne @draw_back_tire ; draw different tire every other frame + lda ENEMY_X_POS,x ; load enemy x position on screen + sec ; set carry flag in preparation for subtraction + sbc #$0c ; subtract #$0c from enemy position to get front tire position + sta $09 ; set x position in $09 + lda ENEMY_X_VEL_ACCUM,x ; load tank visibility (#$00 = visible, #$01 = off screen to right, #$ff = off screen to left) + sbc #$00 ; subtract 1 + bne @exit ; exit if front tire is already off screen + beq @draw_specific_tire ; draw tire if tank front tire is visible on screen + +@draw_back_tire: + lda ENEMY_X_POS,x ; load enemy x position on screen + clc ; clear carry in preparation for addition + adc #$14 ; add #$14 to get back tire position + sta $09 ; set x position in $09 + lda ENEMY_X_VEL_ACCUM,x ; load tank visibility (#$00 = visible, #$01 = off screen to right, #$ff = off screen to left) + adc #$00 ; add any overflow when determining back tire x position (#$ff = right edge of screen) + bne @exit ; exit if back tire is off screen to the right + +@draw_specific_tire: + ldy ENEMY_Y_POS,x ; tire y position on screen + lda $09 ; load tire x position + jsr load_bank_3_update_nametable_supertile ; draw super-tile $10 at position (a,y) + ldx ENEMY_CURRENT_SLOT ; restore tank slot index + +@exit: + rts + +tank_stop: + lda #$00 ; a = #$00 + sta TANK_AUTO_SCROLL ; disabled automatic screen scroll + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + and #$01 ; keep bit 0 (attack delay offset) + tay ; transfer to offset register + lda tank_attack_delay_tbl,y ; load specific tank attack delay + sta ENEMY_VAR_4,x ; delay for entire attack sequence + lda #$47 ; a = #$47 (hp for tank) + sta ENEMY_HP,x ; set enemy hp + lda #$08 ; a = #$08 (initial delay before first attack) + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + +tank_adv_routine: + jmp advance_enemy_routine + +; updates the tanks position and when on screen, enables player bullet collision +; note that the tank is still 'invincible' until it stops +; before the tank is visible, its actual position is behind the player. +; it isn't until the actual tank position is off screen to the left before +; the bullet collision is enabled +tank_update_pos: + lda FRAME_SCROLL ; how much to scroll the screen (#00 - no scroll) + clc ; clear carry in preparation for addition + adc TANK_AUTO_SCROLL ; add amount to auto scroll + sta $00 ; store new frame scroll in $00 + lda ENEMY_X_POS,x ; load enemy x position on screen + sec ; set carry flag in preparation for subtraction + sbc $00 ; subtract the frame scroll + sta ENEMY_X_POS,x ; update enemy x position on screen + bcs tank_routine_exit ; exit if no underflow when determining new x position + dec ENEMY_X_VEL_ACCUM,x ; tank visibility change state + ; either going from #$01 to #$00 (appearing on screen from right) + ; or going from #$00 to #$ff (offscreen to the left) + ; time to 'enable' to allow it to be attacked + lda ENEMY_HP,x ; load enemy hp + beq tank_routine_exit ; exit if tank hp is 0 + lda ENEMY_STATE_WIDTH,x ; load whether bullets affect and interact with tank + eor #$81 ; flip bits x... ...x + sta ENEMY_STATE_WIDTH,x ; enable bullet - tank collisions + +tank_routine_exit: + rts + +; table for time during which tank is immobile (#$2 bytes) +; offset determined by ENEMY_ATTRIBUTES bit 0 +tank_attack_delay_tbl: + .byte $00,$f8 + +; tank stopped, fire at player +tank_routine_02: + jsr tank_set_palette_update_pos ; update position, set palette, remove if appropriate + bcc tank_routine_exit + lda FRAME_COUNTER ; load frame counter + and #$01 ; keep bits .... ...x + bne @check_aim_fire + dec ENEMY_VAR_4,x ; decrement tank stop timer + beq tank_adv_routine ; if timer has elapsed, advance routine + +; attack time counter is decreased by 1 on even frames (every other frame) +; tank isn't ready to start moving again, should fire at player +@check_aim_fire: + lda ENEMY_STATE_WIDTH,x ; load whether bullets should travel through tank + bmi tank_routine_exit ; exit bullets should travel through tank (bit 7 is set) + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne tank_routine_exit ; exit if animation delay hasn't elapsed + lda ENEMY_VAR_3,x ; animation delay has elapsed, load remaining bullets to fire + bne @create_bullet ; branch if there are still bullets to fire + lda ENEMY_VAR_1,x ; fired all bullets in current round of attack, need to re-aim + sta $17 ; set aim direction in $17 (#$0c = straight left, #$0b = aiming down left, #$0a = down down left) + jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01) + sty $0a ; store closest player in $0a + ldy #$f4 ; set vertical offset from enemy position (param for add_with_enemy_pos) + lda #$00 ; set horizontal offset from enemy position (param for add_with_enemy_pos) + jsr add_with_enemy_pos ; stores absolute screen x position in $09, and y position in $08 + jsr aim_var_1_for_quadrant_aim_dir_01 ; determine next aim direction [#$00-#$0b] ($0c), adjusts ENEMY_VAR_1 to get closer to that value using quadrant_aim_dir_01 + lda ENEMY_VAR_1,x ; load calculated aim direction + cmp #$0a ; compare to the tank's lowest aiming direction + bcc @keep_aim_direction ; branch if calculated aim direction is not possible for tank to use previous aim direction + cmp #$0d ; compare calculated aim direction to one past the maximum aim direction (straight left) + bcc @set_bullets_draw_turret ; branch if aiming less than up-left + +; calculated aim direction from aim_var_1_for_quadrant_aim_dir_01 is not possible for tank +; instead, re-use previous round of attack's aim direction +@keep_aim_direction: + lda $17 ; load last round of attack's aim direction + sta ENEMY_VAR_1,x ; store back in ENEMY_VAR_1 + sta $0c ; store result in $0c as well + +@set_bullets_draw_turret: + cmp $0c ; see if drawing the same turret as what's already drawn + ; used to allow turret to slowly aim towards player, and only fire once aimed + bne @draw_tank_turret_supertile ; branch to draw new turret super-tile if different + lda #$03 ; a = #$03 (number of consecutive bullets) + sta ENEMY_VAR_3,x ; set number of bullets to fire in next round of attack + +@draw_tank_turret_supertile: + lda ENEMY_VAR_1,x ; load tank turret aim direction + sec ; set carry flag in preparation for subtraction + sbc #$0a ; subtract smallest aim direction (turret can only aim from #$0a to #$0c inclusively) + tay ; transfer offset to y + lda tank_turret_supertile_code_tbl,y ; load appropriate super-tile + sta $10 ; set super-tile to draw + lda ENEMY_Y_POS,x ; load enemy y position on screen + sbc #$1c ; subtract #$1c from from enemy's y position (y location of super-tile to draw) + tay ; transfer result to y for load_bank_3_update_nametable_supertile + lda ENEMY_X_POS,x ; load enemy x position on screen + sbc #$2c ; subtract #$2c from from enemy's x position (x location of super-tile to draw) + sta $00 ; set result in $00, will be used later in load_bank_3_update_nametable_supertile + lda ENEMY_X_VEL_ACCUM,x ; load tank visibility (#$00 = visible, #$01 = off screen to right, #$ff = off screen to left) + sbc #$00 ; subtract #$00 or #$01 if carry clear !(WHY?) + ; I couldn't get this to happen + bne tank_exit ; exit if result isn't #$00 + lda $00 ; load x position to draw tank super-tile + jsr load_bank_3_update_nametable_supertile ; draw turret super-tile $10 at position (a,y) + lda #$01 ; a = #$01 + bcs @set_delay_exit ; exit if unable to draw the super-tile for the turret + ldx ENEMY_CURRENT_SLOT ; restore x to current enemy slot + lda #$30 ; a = #$30 (delay before attack) + bne @set_delay_exit ; set animation delay and exit + +@create_bullet: + lda ENEMY_VAR_1,x ; load turret aim direction [#$0a-#$0c] + sec ; set carry flag in preparation for subtraction + sbc #$0a ; subtract minimum aim direction to get relative index, e.g. aim direction #$0b becomes #$01 + sta $00 ; store offset in $00 + asl + adc $00 ; multiply offset by #$03 since each entry in tank_bullet_pos_vel_tbl is #$03 bytes + tay ; transfer to offset register + lda ENEMY_X_POS,x ; load enemy x position on screen + sbc tank_bullet_pos_vel_tbl,y ; subtract relative x offset + sta $09 ; set bullet initial x position + lda ENEMY_X_VEL_ACCUM,x ; load tank visibility (#$00 = visible, #$01 = off screen to right, #$ff = off screen to left) + sbc #$00 ; subtract #$00 if turret on screen, #$01 if turret offscreen + bcc tank_exit ; exit if turret is off screen to the left + lda ENEMY_Y_POS,x ; load enemy y position on screen + sbc tank_bullet_pos_vel_tbl+1,y ; subtract relative y offset + sta $08 ; set bullet initial y position + lda tank_bullet_pos_vel_tbl+2,y ; load bullet type (xxx. ....) and angle index (...x xxxx) + ldy ENEMY_ATTRIBUTES,x ; (tank bullets speed) + jsr create_enemy_bullet_angle_a ; create a bullet with speed y, bullet type a, angle a at position ($09, $08) + dec ENEMY_VAR_3,x ; decrement number of bullets to fire for the round + lda #$20 ; a = #$20 (delay between bullets) + +@set_delay_exit: + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + +tank_exit: + rts + +; table for tank cannon tile codes (#$3 bytes) +; see level_5_nametable_update_supertile_data +tank_turret_supertile_code_tbl: + .byte $13,$12,$0f + +; table for tank bullets (#$9 bytes) +; byte 0: x offset of bullet (to the left) +; byte 1: y offset of bullet (to the left) +; byte 2: bullet type and angle +tank_bullet_pos_vel_tbl: + .byte $24,$03,$09 ; position 0 (#$0a) + .byte $29,$09,$0a ; position 1 (#$0b) + .byte $2e,$14,$0c ; position 2 (#$0c) + +; update position, set palette, remove if appropriate +tank_set_palette_update_pos: + jsr tank_set_palette ; set the tank's palette based on its hp + jsr tank_update_pos ; update the tank's position + jmp tank_check_removal ; check if tank is destroyed and at position to be removed + +; tank starts moving again after stopping in previous routine +tank_routine_03: + lda FRAME_COUNTER ; load frame counter + and #$01 ; keep bits .... ...x + sta TANK_AUTO_SCROLL ; enable automatic screen scroll + jsr tank_set_palette_update_pos ; update position, set palette, remove if appropriate + bcc tank_routine_03_exit + jmp tank_move_logic + +; output +; * carry flag - clear when tank removed, set when not removed +tank_check_removal: + lda ENEMY_X_VEL_ACCUM,x ; load whether or not the tank is visible on screen + sec ; set carry flag + bpl tank_routine_03_exit ; exit if tank is off screen + lda ENEMY_X_POS,x ; load enemy x position on screen + cmp #$d0 ; see if (invisible) destroyed tank has wrapped around and can be removed + bcs tank_routine_03_exit ; exit if tank hasn't gotten to removal point + jsr remove_enemy ; tank at removal point, remove enemy (from bank 7) + lda #$00 ; a = #$00 + sta TANK_AUTO_SCROLL ; disable automatic screen scroll + sta PAUSE_PALETTE_CYCLE ; re-enable palette color cycling + sta TANK_ICE_JOINT_SCROLL_FLAG ; stop the pipe joints from autoscrolling left + ldx ENEMY_CURRENT_SLOT ; restore x to enemy offset + clc + +tank_routine_03_exit: + rts + +; table for tank update super-tiles (#$4 bytes) +tank_wheel_supertile_tbl: + .byte $10,$11,$14,$15 + +; disable collision, prep variables for next routine to remove tank, play +tank_routine_04: + jsr tank_update_pos ; update the tank's position + jsr disable_enemy_collision ; prevent player enemy collision check and allow bullets to pass through enemy + lda #$05 ; a = #$05 + sta ENEMY_VAR_1,x ; set number of explosions and super-tiles to go through when destroying tank + lda #$55 ; a = #$55 (sound_55) + jsr play_sound ; play tank destroyed sound + lda #$00 ; a = #$00 + jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a; advance enemy routine + +; tank destroyed routine +tank_routine_05: + lda FRAME_COUNTER ; load frame counter + and #$01 ; keep bits .... ...x + sta TANK_AUTO_SCROLL ; randomly enable/disable automatic screen scroll + jsr tank_update_pos ; update the tank's position + jsr tank_check_removal ; check if tank is destroyed and at position to be removed + bcc @exit ; branch if tank was removed + lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter + bne @dec_delay_exit ; branch if animation delay hasn't elapsed + lda ENEMY_VAR_1,x ; load current explosion animation to do + bmi @exit ; exit if have finished explosion animations + asl + asl + adc ENEMY_VAR_1,x ; each entry is #$05 bytes, so double twice and add to itself + tay ; transfer to offset register + lda ENEMY_X_POS,x ; load enemy x position on screen + adc tank_destroy_tbl+1,y ; add relative x offset from enemy position + sta $00 ; store in $00 + lda ENEMY_X_VEL_ACCUM,x ; load tank visibility (#$00 = visible, #$01 = off screen to right, #$ff = off screen to left) + adc tank_destroy_tbl,y + bne @dec_var_1_exit ; decrement explosion offset and exit + lda ENEMY_Y_POS,x ; load enemy y position on screen + adc tank_destroy_tbl+2,y ; add relative y offset from enemy position + sty $07 ; store explosion destruction offset (ENEMY_VAR_1,x * #$05) in $07 + tay ; transfer y position to draw super-tile to y + lda #$9b ;all black supertile (level_5_nametable_update_supertile_data - #$1b) + sta $10 ; set black super-tile to draw in $10 + lda $00 ; load x position to draw super-tile in a + jsr load_bank_3_update_nametable_supertile ; draw super-tile $10 at position (a,y) + bcs @exit ; exit if unable to draw super-tile so it can be attempted the next frame + ldx ENEMY_CURRENT_SLOT ; restore x to the current enemy slot + ldy $07 ; load explosion destruction offset (ENEMY_VAR_1,x * #$05) in $07 + lda ENEMY_X_POS,x ; load enemy x position on screen + adc tank_destroy_tbl+3,y ; add relative x offset for explosion + sta $09 ; store x position for explosion in $09 + lda ENEMY_Y_POS,x ; load enemy y position on screen + clc ; clear carry in preparation for addition + adc tank_destroy_tbl+4,y ; add relative y offset for explosion + sta $08 ; store y position for explosion in $08 + dec ENEMY_VAR_1,x ; decrement destruction sequence offset + lda #$04 ; a = #$04 + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + jmp create_two_explosion_89 ; create explosion #$89 at location ($09, $08) + +@dec_var_1_exit: + dec ENEMY_VAR_1,x ; don't animate explosion, just decrement offset and exit + +@exit: + rts + +@dec_delay_exit: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + rts + +; table for data to use when a tank is destroyed (#$1e bytes) +; * byte 0 - used to help only animate part of the tank that is visible +; * byte 1 - x offset from tank position for blank super-tile to draw +; * byte 2 - y offset from tank position for blank super-tile to draw +; * byte 3 - x offset from tank position for explosion +; * byte 4 - y offset from tank position for explosion +tank_destroy_tbl: + .byte $00,$16,$04,$1c,$0e ; ( 22, 4) + .byte $00,$16,$e4,$1c,$f2 ; ( 22, -28) + .byte $ff,$f6,$04,$00,$0e ; (-10, 4) + .byte $ff,$f6,$e4,$00,$f2 ; (-10, -28) + .byte $ff,$d6,$04,$e4,$0e ; (-42, 4) + .byte $ff,$d6,$e4,$e4,$f2 ; (-42, -28) + +; set tank palette based on HP +tank_set_palette: + lda ENEMY_HP,x ; load enemy hp + lsr + lsr + lsr + lsr + tay ; transfer to offset register, every 16 hp the palette changes + lda tank_palette_tbl,y ; load correct palette for tank + sta LEVEL_PALETTE_INDEX+2 ; set tank's palette + lda #$10 ; a = #$10 + jsr load_palettes_color_to_cpu ; load #$10 palette colors into PALETTE_CPU_BUFFER based on LEVEL_PALETTE_INDEX + ldx ENEMY_CURRENT_SLOT ; restore x to enemy current slot (load_palettes_color_to_cpu overwrites x) + rts + +; table for tank palette changes based on hp (#$5 bytes) +tank_palette_tbl: + .byte $61,$60,$5f,$3f,$3f + +; pointer table for alien carrier - level 5 boss (#$18 bytes) +boss_ufo_routine_ptr_tbl: + .addr boss_ufo_routine_00 ; CPU address $a6b2 - set y position and initialize palette fade in effect timer + .addr boss_ufo_routine_01 ; CPU address $a6c3 - determine x position randomly, add #$20 to y (if overflow set y to #$30) + .addr boss_ufo_routine_02 ; CPU address $a6e6 - draw super-tiles for the boss ufo at correct location + .addr boss_ufo_routine_03 ; CPU address $a723 - animate opening top, enable collision + .addr boss_ufo_routine_04 ; CPU address $a761 - generate mini ufos and bombs + .addr boss_ufo_routine_05 ; CPU address $a7fd - animate closing of top, disable collision + .addr boss_ufo_routine_06 ; CPU address $a817 - make ufo immediately invisible, and set BG_PALETTE_ADJ_TIMER to being fade in effect + .addr boss_ufo_routine_07 ; CPU address $a82c - update nametable to black to get rid of old boss ufo drawn in nametable + .addr boss_ufo_routine_08 ; CPU address $a834 - wait for animation delay and then go to boss_ufo_routine_01 + .addr boss_ufo_routine_09 ; CPU address $a83f - boss ufo destroyed routine, play sound disable collision, remove all enemies + .addr boss_ufo_routine_0a ; CPU address $a857 - animate boss ufo explosions + .addr boss_ufo_routine_0b ; CPU address $a8b5 - animate door explosion and opening + +; set y position and initialize palette fade in effect timer +boss_ufo_routine_00: + lda #$10 ; a = #$10 + sta ENEMY_Y_POS,x ; enemy y position on screen + lda #$10 ; a = #$10 + sta BG_PALETTE_ADJ_TIMER ; create initial fade in effect for boss ufo + ; gives #$08 frames to draw boss ufo super-tiles before fade in effect will start + jsr load_palettes_color_to_cpu ; load #$10 palette colors into PALETTE_CPU_BUFFER based on LEVEL_PALETTE_INDEX + ldx ENEMY_CURRENT_SLOT ; restore x to current enemy slot + jmp advance_enemy_routine ; advance to boss_ufo_routine_01 + +; determine x position randomly, add #$20 to y (if overflow set y to #$30) +boss_ufo_routine_01: + lda RANDOM_NUM ; load random number + and #$03 ; keep bits .... ..xx + tay ; transfer to offset register + lda boss_ufo_x_pos_tbl,y ; load random boss ufo initial random x position + sta ENEMY_X_POS,x ; set enemy x position on screen + lda ENEMY_Y_POS,x ; load enemy y position on screen + clc ; clear carry in preparation for addition + adc #$20 + cmp #$71 ; compare to max y position + bcc @continue ; continue if not greater than max y position + lda #$30 ; past max y position, set a = #$30 + +@continue: + sta ENEMY_Y_POS,x ; set enemy y position on screen + lda #$03 ; ENEMY_ANIMATION_DELAY is used as an index to know which super-tile to draw + ; and not really an animation delay + +boss_ufo_set_delay_adv_routine: + jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a and advance enemy routine + +; table for boss possible x positions (#$4 bytes) +boss_ufo_x_pos_tbl: + .byte $40,$60,$80,$80 + +; draw super-tiles for the boss ufo at correct location +boss_ufo_routine_02: + ldy ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter (index of which super-tile to draw) + lda boss_ufo_supertile_update_ptr_tbl,y ; load appropriate supertile based on delay [#$00-#$03] + ; see level_5_nametable_update_supertile_data + +; draw boss ufo supertiles as specified location index +; decrements ENEMY_ANIMATION_DELAY and see if result is positive, +; * if positive, exit. Otherwise, advance the routine +; input +; * a - index into level_5_nametable_update_supertile_data +; * y - location to draw super-tile (indexes into boss_ufo_supertile_pos_tbl) +boss_ufo_draw_supertile_a: + sta $10 ; store nametable super-tile update index in $10 + tya ; transfer delay index to a + asl ; double since each entry in boss_ufo_supertile_pos_tbl is #$02 bytes + tay ; transfer to offset register + lda boss_ufo_supertile_pos_tbl,y ; load the relative x position of the super-tile to draw + adc ENEMY_X_POS,x ; add enemy x position to relative offset + sta $00 ; store result in $00 + lda boss_ufo_supertile_pos_tbl+1,y ; load the relative y position of the super-tile to draw + clc ; clear carry in preparation for addition + adc ENEMY_Y_POS,x ; add enemy y position on screen to relative offset + tay ; transfer result to y for load_bank_3_update_nametable_supertile call + lda $00 ; load x position of super-tile to draw for load_bank_3_update_nametable_supertile + jsr load_bank_3_update_nametable_supertile ; draw super-tile $10 at position (a,y) + ldx ENEMY_CURRENT_SLOT ; restore the enemy current slot to x + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bpl boss_ufo_exit_00 ; exit if still have more super-tiles to draw + lda #$02 ; drawn all super-tiles, a = #$02 + sta ENEMY_VAR_1,x ; + lda #$60 ; a = #$60 (delay before starting attacks) + bne boss_ufo_set_delay_adv_routine ; always branch to advance routine + +boss_ufo_exit_00: + rts + +; table for boss ufo super-tile data (see level_5_nametable_update_supertile_data) (#$4 bytes) +; #$0d - blue top fully open (left) +; #$0e - blue top fully open (right) +; #$07 - bottom thruster full throttle (left) +; #$08 - bottom thruster full throttle (right) +boss_ufo_supertile_update_ptr_tbl: + .byte $0d,$0e,$07,$08 + +; table for relative x/y positions of boss ufo tile parts (#$8 bytes) +boss_ufo_supertile_pos_tbl: + .byte $e4,$e4 ; top-left (-28, -28) + .byte $04,$e4 ; top-right ( 04, -28) + .byte $e4,$04 ; bottom-left (-28, 04) + .byte $04,$04 ; bottom-right ( 04, 04) + +; animate opening top, enable collision +boss_ufo_routine_03: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + beq @timer_elapsed ; branch if timer has elapsed + jmp boss_ufo_draw_thrusters ; draw thruster super-tiles at appropriate thrust + +@timer_elapsed: + jsr enable_enemy_collision ; enable bullet-enemy collision and player-enemy collision checks + jsr boss_ufo_draw_blue_top ; draw blue top if the delay timer has elapsed + dec ENEMY_VAR_1,x ; decrement which frame of the blue top to draw + bpl boss_ufo_exit_00 ; exit if still more to animate + lda #$00 ; a = #$00 + jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a; advance enemy routine + +boss_ufo_draw_blue_top: + lda #$0a ; a = #$0a (delay when opening the side bays) + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + lda ENEMY_VAR_1,x ; load blue top super-tile index to draw + asl ; double since each entry is #$02 bytes + tay ; transfer to offset register + lda boss_ufo_supertile_update_ptr_tbl_2+1,y ; load left side blue top super-tile + sta $07 ; store super-tile to draw in $07 + lda boss_ufo_supertile_update_ptr_tbl_2,y ; load right side blue top super-tile + ldy #$00 ; set boss_ufo_supertile_pos_tbl index to #$00 (top-left) + jsr boss_ufo_draw_supertile_a ; draw boss ufo super-tile a at location index y + lda $07 ; load left side blue top super-tile nametable update + ldy #$01 ; set boss_ufo_supertile_pos_tbl index to #$01 (top-right) + jmp boss_ufo_draw_supertile_a ; draw boss ufo super-tile a at location index y + +; table for side bays opening/closing sequence super-tiles (#$8 bytes) +; see (see level_5_nametable_update_supertile_data) +boss_ufo_supertile_update_ptr_tbl_2: + .byte $0b,$05 ; top closing frame 1 (left), top closing frame 1 (right) + .byte $0a,$04 ; top closing frame 2 (left), top closing frame 2 (right) + .byte $09,$03 ; top closing frame 3 (left), top closing frame 3 (right) + .byte $0d,$0e ; blue top fully open (left), blue top fully open (right) + +; generate mini ufos and bombs +boss_ufo_routine_04: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + beq boss_ufo_set_routine_05 ; advance to boss_ufo_set_routine_05 if animation delay has elapsed + lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter + and #$0f ; keep bits .... xxxx + bne boss_ufo_draw_thrusters + lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter + lsr + lsr + and #$0c ; keep bits .... xx.. + tay + lda boss_ufo_enemy_gen_tbl,y ; load enemy type to generate + sta $0a ; store enemy type to generate + lda boss_ufo_enemy_gen_tbl+1,y ; load x position of enemy to generate + sty $17 ; store x position of enemy to generate in $17 + ldy #$f4 ; y = #$f4 (relative height of bombs and ufos) + jsr generate_enemy_at_pos ; generate enemy type $0a at relative position a,y + bne boss_ufo_exit_01 ; exit if unable to create enemy + ldx $17 ; load x position of generated enemy + lda boss_ufo_enemy_gen_tbl+2,x ; load x fast velocity for generated enemy + sta ENEMY_X_VELOCITY_FAST,y ; set x fast velocity for generated enemy + lda #$10 ; a = #$10 (delay between ufo appear and move) + sta ENEMY_ANIMATION_DELAY,y ; set delay for generated enemy + lda #$02 ; a = #$02 + sta ENEMY_SCORE_COLLISION,y ; set enemy score collision code + txa ; transfer x position of generated enemy to a + and #$04 ; keep bit 2 + bne @continue ; exit if x positions bit 2 is set (mini-ufo and not drop bomb) + lda #$80 ; a = #$80 + sta ENEMY_X_VELOCITY_FRACT,y ; set mini-ufo fractional velocity to .5 + +@continue: + lda boss_ufo_enemy_gen_tbl+3,x ; load enemy sprite code + sta ENEMY_SPRITES,y ; write enemy sprite code to CPU buffer + ldx ENEMY_CURRENT_SLOT ; restore x to boss ufo enemy slot index + +boss_ufo_exit_01: + rts + +boss_ufo_set_routine_05: + lda #$01 ; a = #$01 + sta ENEMY_VAR_1,x ; prep ENEMY_VAR_1 for drawing super-tiles in boss_ufo_routine_05 + lda #$08 ; a = #$08 + bne boss_ufo_adv_routine_00 ; advance routine to boss_ufo_routine_05 + +boss_ufo_draw_thrusters: + lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter + and #$07 ; keep bits .... .xxx + cmp #$03 ; compare to #$03 + bne boss_ufo_exit_01 ; exit if not #$03 + lda ENEMY_ANIMATION_DELAY,x ; reload enemy animation frame delay counter + sta $06 ; backup animation delay in $06 + ldy #$00 ; y = #$00 (thrusters full throttle) + and #$08 ; keep bit 3 of animation delay + bne @load_rocket_thrusters_nametable ; throttle on thrusters changes every #$08 frames + ldy #$02 ; thrusters half throttle + +@load_rocket_thrusters_nametable: + lda #$02 ; a = #$02 + sta ENEMY_ANIMATION_DELAY,x ; set animation delay so that boss_ufo_draw_supertile_a doesn't advance routine + lda boss_ufo_supertile_update_ptr_tbl_3+1,y ; load right thruster super-tile index + sta $07 ; store right thruster super-tile in $07 + lda boss_ufo_supertile_update_ptr_tbl_3,y ; load left thruster super-tile index + ldy #$02 ; set the position index to draw to #$02 (bottom left) + jsr boss_ufo_draw_supertile_a ; draw boss ufo super-tile a at location index y + lda $07 ; load right thruster super-tile index + ldy #$03 ; set the position index to draw to #$03 (bottom right) + jsr boss_ufo_draw_supertile_a ; draw boss ufo super-tile a at location index y + lda $06 ; restore animation delay to correct value + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + rts + +; table for boss ufo enemy generation data (#$14 bytes) +; #$15 = enemy type +; #$14 = relative initial x position +; #$01 = initial x velocity (high byte) +; #$7c = enemy sprite code +boss_ufo_enemy_gen_tbl: + .byte $15,$14,$01,$7c ; mini ufo (#$15) from right side (sprite_7c) + .byte $16,$00,$00,$22 ; dropped bomb 1 (sprite_22) + .byte $15,$ec,$fe,$7c ; mini ufo (#$15) from left side (sprite_7c) + .byte $16,$00,$00,$22 ; dropped bomb 2 (sprite_22) + +; #$07 = boss ufo - bottom thruster full throttle (left) +; #$08 = boss ufo - bottom thruster full throttle (right) +; #$0c = boss ufo - bottom thruster half throttle (left) +; #$06 = boss ufo - bottom thruster half throttle (right) +; see (see level_5_nametable_update_supertile_data) +boss_ufo_supertile_update_ptr_tbl_3: + .byte $07,$08,$0c,$06 ; rocket thrusters tile codes + +; animate closing of top, become invisible after drawing +boss_ufo_routine_05: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne boss_ufo_draw_thrusters ; draw thrusters super-tiles at appropriate thrust + jsr boss_ufo_draw_blue_top ; draw boss ufo top super-tiles + inc ENEMY_VAR_1,x ; move to + lda ENEMY_VAR_1,x + cmp #$04 + bne boss_ufo_exit_02 + jsr disable_enemy_collision ; prevent player enemy collision check and allow bullets to pass through enemy + lda #$20 ; a = #$20 (delay between bays closing and fade) + +boss_ufo_adv_routine_00: + jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a; advance enemy routine + +; make ufo immediately invisible, and set BG_PALETTE_ADJ_TIMER to being fade in effect +boss_ufo_routine_06: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne boss_ufo_draw_thrusters ; continue to animate thrusters until animation delay has elapsed + lda #$18 ; delay has elapsed, a = #$18 + sta BG_PALETTE_ADJ_TIMER ; create 'fade in' effect and make ufo immediately invisible + ; every frame BG_PALETTE_ADJ_TIMER will be decremented, while outside the range of #$09 to #$01, black is drawn + ; once in range [#$01-#$09], the ufo will be faded in, but it'll be at a new location + lda #$10 ; a = #$10 (loading #$10 nametable palettes) + jsr load_palettes_color_to_cpu ; load #$10 palette colors into PALETTE_CPU_BUFFER based on LEVEL_PALETTE_INDEX + ldx ENEMY_CURRENT_SLOT ; restore x to boss ufo enemy slot index + lda #$03 ; a = #$03 + jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a and advance enemy routine + +; clear nametable where boss ufo was so that when BG_PALETTE_ADJ_TIMER is back in range [#$01-#$09], there is only one boss ufo +; and it's in a new location +boss_ufo_routine_07: + ldy ENEMY_ANIMATION_DELAY,x ; load super-tile index location [#$03-#$00] + lda #$9b ; a = #$9b (#$1b - all black) + jmp boss_ufo_draw_supertile_a ; draw boss ufo super-tile a at location index y + +; wait for animation delay and then go to boss_ufo_routine_01 +boss_ufo_routine_08: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne boss_ufo_exit_02 ; exit if animation delay hasn't elapsed + lda #$02 ; a = #$02 + jmp set_enemy_routine_to_a ; set enemy routine index to boss_ufo_routine_01 + +boss_ufo_exit_02: + rts + +; boss ufo destroyed routine, play sound disable collision, remove all enemies +boss_ufo_routine_09: + jsr init_APU_channels + lda #$55 ; a = #$55 (sound_55) + jsr play_sound ; play sound + jsr disable_enemy_collision ; prevent player enemy collision check and allow bullets to pass through enemy + lda #$04 ; a = #$04 (final explosions count) + sta ENEMY_VAR_1,x ; initialize explosion location index + jsr destroy_all_enemies ; destroy all mini-ufos and dropped bombs + lda #$10 ; a = #$10 (delay before final explosions) + +boss_ufo_adv_routine_01: + jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a; advance enemy routine + +; animate boss ufo explosions +boss_ufo_routine_0a: + lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter + bne boss_ufo_dec_delay_exit ; exit if animation delay hasn't elapsed + lda ENEMY_VAR_1,x ; load explosion location index + bmi boss_ufo_adv_to_0b ; branch if all explosions animated to advance to boss_ufo_routine_0b + asl ; double current explosion location index + tay ; transfer to offset register + lda ENEMY_X_POS,x ; load enemy x position on screen + sta $16 ; store enemy x position in $16 + adc boss_ufo_explosion_rel_pos_tbl,y ; add relative x offset for explosion + sta ENEMY_X_POS,x ; set enemy x position on screen + lda ENEMY_Y_POS,x ; load enemy y position on screen + sta $17 ; store enemy y position in $17 + clc ; clear carry in preparation for addition + adc boss_ufo_explosion_rel_pos_tbl+1,y ; add relative y offset for explosion + sta ENEMY_Y_POS,x ; store enemy y position on screen + cpy #$10 ; see if position index is >= #$04, not sure why this is here, never happens !(WHY?) + bcs boss_ufo_create_explosion ; prevent updating the super-tile with black if location index is greater #$04 + lda #$9b ; a = #$9b (#$1b - all black) + jsr draw_enemy_supertile_a ; draw super-tile a (offset into level nametable update table) at position (ENEMY_X_POS, ENEMY_Y_POS) + bcs boss_ufo_move_explosion ; branch if unable to draw explosion, not sure why this is coded like this !(WHY?) + +boss_ufo_create_explosion: + jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position + dec ENEMY_VAR_1,x ; decrement explosion location index + lda #$08 ; a = #$08 (delay between final explosions) + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + jmp create_two_explosion_89 ; create explosion #$89 at location ($09, $08) + +boss_ufo_dec_delay_exit: + dec ENEMY_ANIMATION_DELAY,x + +boss_ufo_exit_03: + rts + +; moves the explosion by moving base enemy position +; backup for when unable to draw explosion due to CPU_GRAPHICS_BUFFER being full +; couldn't get to execute and not sure why this is implemented !(WHY?) +boss_ufo_move_explosion: + lda $16 + sta ENEMY_X_POS,x ; set enemy x position on screen + lda $17 + sta ENEMY_Y_POS,x + rts + +boss_ufo_adv_to_0b: + lda #$02 ; a = #$02 + sta ENEMY_VAR_1,x ; init door explosion/opening index + lda #$04 ; a = #$04 + bne boss_ufo_adv_routine_01 ; advance routine to boss_ufo_routine_0b + +; table for boss ufo final explosions relative offsets (#$a bytes) +; byte 0 - x relative offset +; byte 1 - y relative offset +boss_ufo_explosion_rel_pos_tbl: + .byte $e0,$00 ; (-32, 0) + .byte $00,$20 ; ( 0, 32) + .byte $20,$00 ; ( 20, 0) + .byte $f0,$f0 ; (-16, -16) + .byte $00,$00 ; ( 0 , 0) + +; animate door explosion and opening +boss_ufo_routine_0b: + lda ENEMY_ANIMATION_DELAY,x ; load animation delay + bne boss_ufo_dec_delay_exit ; exit if animation delay hasn't elapsed + lda ENEMY_VAR_1,x ; load door explosion index + bmi @remove_boss ; branch to remove boss if all door explosions have happened + asl + adc ENEMY_VAR_1,x ; multiply by 3 + tay ; transfer to offset register + lda boss_ufo_door_explosion_tbl,y ; load x position of door explosion + sta ENEMY_X_POS,x ; set enemy x position on screen of door explosion + lda boss_ufo_door_explosion_tbl+1,y ; load y position of door explosion + sta ENEMY_Y_POS,x ; set enemy y position on screen of door explosion + lda boss_ufo_door_explosion_tbl+2,y ; load super-tile to draw (see level_5_nametable_update_supertile_data) + jsr draw_enemy_supertile_a ; draw super-tile a (offset into level nametable update table) at position (ENEMY_X_POS, ENEMY_Y_POS) + bcs boss_ufo_exit_03 ; exit if unable to draw super-tile + jmp boss_ufo_create_explosion ; create explosion at enemy position, i.e. explosion position + +@remove_boss: + jsr level_boss_defeated ; play sound a (#$ff) !(BUG?) and set auto-move delay to ff, and set boss defeated flag + ; this does not occur in Japanese version of the game, because in that version + ; level_boss_defeated doesn't call play_sound + lda #$30 ; a = #$30 + jmp set_delay_remove_enemy + +; table for boss ufo generation (#$9 bytes) +; byte 0 - x position for exit explosions +; byte 1 - y position for exit explosions +; byte 2 - super-tile to draw (see level_5_nametable_update_supertile_data) +boss_ufo_door_explosion_tbl: + .byte $c0,$80,$96 ; (192, 128) (#$16 - boss screen open door top) + .byte $c0,$a0,$97 ; (192, 160) (#$17 - boss screen open door) + .byte $d0,$c0,$98 ; (208, 192) (#$18 - boss screen open door bottom) + +; pointer table for flying saucer (#$7 * #$2 = #$e bytes) +mini_ufo_routine_ptr_tbl: + .addr mini_ufo_routine_00 ; CPU address $a8fa + .addr mini_ufo_routine_01 ; CPU address $a905 + .addr mini_ufo_routine_02 ; CPU address $a922 + .addr mini_ufo_routine_03 ; CPU address $a94c + .addr enemy_routine_init_explosion ; CPU address $e74b from bank 7 + .addr enemy_routine_explosion ; CPU address $e7b0 from bank 7 + .addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7 + +; flying saucer - pointer 1 +mini_ufo_routine_00: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + beq mini_ufo_advance_routine ; go to mini_ufo_routine_01 if animation delay has elapsed + jmp set_mini_ufo_sprite ; go through sprites every #$04 frames to create rotation animation + +mini_ufo_advance_routine: + jmp advance_enemy_routine ; advance to next routine + +; flying saucer - pointer 2 +mini_ufo_routine_01: + jsr dec_mini_ufo_anim_delay_set_sprite ; decrement ENEMY_ANIMATION_DELAY and update sprite (if needed) + jsr update_enemy_x_pos_rem_off_screen ; update X velocity; remove enemy if X position < #$08 (off screen to left) + lda ENEMY_X_POS,x ; load enemy x position on screen + cmp #$20 ; left moving mini ufo point where starts vertical descent + bcc @begin_descent ; if mini ufo is too far to the left, reverse direction + cmp #$e0 ; right moving mini ufo point where starts vertical descent + bcc mini_ufo_exit ; exit if not ready to descend + +; flying saucer y velocity going down (1.5) (high byte and low byte) +@begin_descent: + lda #$80 ; a = #$80 (.5) + sta ENEMY_Y_VELOCITY_FRACT,x ; set fractional velocity to 1/2 + lda #$01 ; a = #$01 (1) + sta ENEMY_Y_VELOCITY_FAST,x ; set fast velocity to 1 + bne mini_ufo_advance_routine ; move to mini_ufo_routine_02 + +; flying saucer - pointer 3 +mini_ufo_routine_02: + jsr dec_mini_ufo_anim_delay_set_sprite ; decrement ENEMY_ANIMATION_DELAY and update sprite (if needed) + jsr set_enemy_y_vel_rem_off_screen ; add velocity to enemy Y position; remove enemy if Y position >= #$e8 (off screen to bottom) + lda ENEMY_Y_POS,x ; load enemy y position on screen + cmp #$a8 ; flying saucer bottom limit + bcc mini_ufo_exit ; exit if not at lowest point of path + lda #$a9 ; a = #$a9 (y adjust when at bottom limit) + sta ENEMY_Y_POS,x ; enemy y position on screen + ldy #$01 ; set fast velocity x portion to 1 (go right) + lda ENEMY_X_POS,x ; load enemy x position on screen + bpl @set_vel_adv_routine ; branch if left mini ufo + ldy #$fe ; set right mini ufo fast x velocity to -1 (go left) + ; x velocity is -1.5 when considering fast and fractional velocities + +@set_vel_adv_routine: + tya ; transfer x velocity fast byte to a + sta ENEMY_X_VELOCITY_FAST,x ; set x velocity fast byte (1 for right, -1 for left) + lda #$80 ; a = #$80 + sta ENEMY_X_VELOCITY_FRACT,x ; set x fractional velocity byte to .5 (1/2) + jsr set_enemy_y_velocity_to_0 ; set y velocity to zero (stop descent) + beq mini_ufo_advance_routine ; go to mini_ufo_routine_03 + +mini_ufo_exit: + rts + +; flying saucer - pointer 4 +mini_ufo_routine_03: + jsr dec_mini_ufo_anim_delay_set_sprite ; decrement ENEMY_ANIMATION_DELAY and update sprite (if needed) + +set_mini_ufo_drop_bomb_pos: + jmp update_enemy_pos ; apply velocities and scrolling adjust + +dec_mini_ufo_anim_delay_set_sprite: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + +set_mini_ufo_sprite: + lda ENEMY_ANIMATION_DELAY,x ; load animation delay + and #$03 ; keep bits .... ..xx + bne @exit ; exit if not the #$04th frame + inc ENEMY_SPRITES,x ; every #$04 frames, change sprite + lda ENEMY_SPRITES,x ; load new sprite code + cmp #$7f ; last sprite of mini ufo sprites + bcc @exit ; exit if sprite is a mini ufo sprite + sbc #$03 ; went to non mini ufo sprite, subtract #$03 to set sprite_7c + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + +@exit: + rts + +; pointer table for drop bomb (#$4 * #$2 = #$8 bytes) +boss_ufo_bomb_routine_ptr_tbl: + .addr boss_ufo_bomb_routine_00 ; CPU address $a974 + .addr enemy_routine_init_explosion ; CPU address $e74b from bank 7 + .addr enemy_routine_explosion ; CPU address $e7b0 from bank 7 + .addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7 + +; drop bomb - pointer 1 +boss_ufo_bomb_routine_00: + lda #$28 ; a = #$28 (gravity value for drop bomb) + jsr add_a_to_enemy_y_fract_vel ; add a to enemy y fractional velocity, added every frame + lda ENEMY_Y_POS,x ; load enemy y position on screen + cmp #$b0 ; drop bomb explosion height + bcc set_mini_ufo_drop_bomb_pos ; apply velocities and scrolling adjust + jmp advance_enemy_routine ; advance to next routine enemy_routine_init_explosion + +; pointer table for pipe joint (2 bytes) +ice_separator_routine_ptr_tbl: + .addr ice_separator_routine_00 ; CPU address $a985 + +; pipe joint - pointer 1 +ice_separator_routine_00: + lda #$c4 ; a = #$c4 (sprite_c4) + sta ENEMY_SPRITES,x ; write sprite to sprite buffer + lda TANK_ICE_JOINT_SCROLL_FLAG + beq @add_scroll + lda FRAME_SCROLL ; how much to scroll the screen (#00 - no scroll) + beq @exit ; exit if scroll is #$00 + dec ENEMY_X_POS,x ; subtract sprite x position from scroll to keep it still on screen + +@exit: + rts + +@add_scroll: + jmp add_scroll_to_enemy_pos ; add scrolling to enemy position + +; pointer table for energy fire down (#$4 * #$2 = #$8 bytes) +fire_beam_down_routine_ptr_tbl: + .addr fire_beam_down_routine_00 ; CPU address $a9a1 + .addr fire_beam_down_routine_01 ; CPU address $a9c8 + .addr fire_beam_down_routine_02 ; CPU address $aa0f + .addr fire_beam_down_routine_03 ; CPU address $aa2b + +; fire beam down - pointer 1 +fire_beam_down_routine_00: + lda #$04 ; a = #$04 + sta ENEMY_FRAME,x ; set enemy animation frame number + lda #$80 ; a = #$80 + +fire_beam_add_pos_set_delay: + ora ENEMY_ATTRIBUTES,x ; merge flip bits with original enemy attributes + sta ENEMY_ATTRIBUTES,x ; update enemy attributes (to support flipping horizontally and/or vertically) + lda #$08 ; a = #$08 + jsr add_a_to_enemy_y_pos ; add a to enemy y position on screen + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + lsr + lsr + and #$03 ; keep bits .... ..xx + tay + lda fire_beam_anim_delay_tbl,y ; load animation delay + sta ENEMY_VAR_A,x ; storey in ENEMY_VAR_A + +fire_beam_set_delay_adv_routine: + jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a; advance enemy routine + +; table for fire beams delay between bursts (#$4 bytes) +fire_beam_anim_delay_tbl: + .byte $00,$20,$40,$60 + +; fire beam down - pointer 2 +fire_beam_down_routine_01: + jsr animate_small_flame ; animate small flame when fire beam isn't firing + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter + bne fire_beam_dec_delay_exit ; decrement animation delay and exit if delay hasn't elapsed + jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01) + cmp #$20 ; see if player is within #$20 pixels of fire beam + bcs fire_beam_down_exit ; branch if closest player is farther than #$20 + +; load fire beam length, play sound, clear variables for use +begin_fire_beam_attack: + lda ENEMY_X_POS,x ; load enemy x position on screen + cmp #$30 ; position at which fire beam stops firing (as player scrolls right) + bcc fire_beam_down_exit ; don't fire if too far to the left + lda #$09 ; a = #$09 (sound_09) + jsr play_sound ; play fire beam burning sound + jsr enable_enemy_player_collision_check ; enable player collision check with flame + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + and #$03 ; keep bits 0 and 1 (fire beam length offset) + tay ; move offset to y + lda fire_beam_length_tbl,y ; load fire beam length + sta ENEMY_VAR_2,x ; store fire beam length + ; for transfer to either ENEMY_VAR_3 or ENEMY_VAR_4 + ; based on specific fire beam enemy type + lda #$01 ; a = #$01 (sprite_01 - blank) + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + lda #$00 ; a = #$00 + sta ENEMY_VAR_1,x + sta ENEMY_VAR_3,x + sta ENEMY_VAR_4,x + beq fire_beam_set_delay_adv_routine + +fire_beam_dec_delay_exit: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + rts + +; table for fire beams possible lengths (#$4 bytes) +fire_beam_length_tbl: + .byte $05,$09,$0d,$0f + +; fire beam down - pointer 3 +fire_beam_down_routine_02: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + ldy #$00 ; y = #$00 + jsr draw_fire_beam_if_anim_elapsed + bcs fire_beam_down_exit + dec ENEMY_VAR_2,x ; decrement fire beam length + beq set_fire_beam_delay_10_adv_routine + lda ENEMY_VAR_4,x ; load current fire beam length + adc #$08 ; add #$08 to length of fire beam + sta ENEMY_VAR_4,x ; update fire beam length + +fire_beam_down_exit: + rts + +set_fire_beam_delay_10_adv_routine: + lda #$10 ; a = #$10 (delay for fire beam to stay at max) + bne fire_beam_set_delay_adv_routine + +; fire beam down - pointer 4 +fire_beam_down_routine_03: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + ldy #$02 ; y = #$02 + jsr draw_fire_beam_if_anim_elapsed + bcs fire_beam_down_exit + lda ENEMY_VAR_4,x + sbc #$07 + sta ENEMY_VAR_4,x + bpl fire_beam_down_exit + +fire_beam_disable_collision_routine_01: + lda ENEMY_VAR_A,x ; load animation delay + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + jsr disable_enemy_collision ; prevent player enemy collision check and allow bullets to pass through enemy + lda #$02 ; a = #$02 + jmp set_enemy_routine_to_a ; set enemy routine fire_beam_xx_routine_01 + +; pointer table for fire beam left (#$4 * #$2 = #$8 bytes) +fire_beam_left_routine_ptr_tbl: + .addr fire_beam_left_routine_00 ; CPU address $aa55 + .addr fire_beam_left_routine_01 ; CPU address $aa5a + .addr fire_beam_left_routine_02 ; CPU address $aa6c + .addr fire_beam_left_routine_03 ; CPU address $aa84 + +fire_beam_left_routine_00: + lda #$40 ; a = #$40 + jmp fire_beam_add_pos_set_delay + +fire_beam_left_routine_01: + jsr animate_small_flame ; animate small flame when fire beam isn't firing + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda FRAME_COUNTER ; load frame counter + and #$7f ; keep bits .xxx xxxx + cmp ENEMY_VAR_A,x + bne fire_beam_left_exit + jmp begin_fire_beam_attack ; load fire beam length, play sound, clear variables for use + +fire_beam_left_routine_02: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + ldy #$04 ; y = #$04 + jsr draw_fire_beam_if_anim_elapsed + bcs fire_beam_left_exit + dec ENEMY_VAR_2,x ; fire beam length + beq set_fire_beam_delay_10_adv_routine + lda ENEMY_VAR_3,x + sbc #$07 + sta ENEMY_VAR_3,x + +fire_beam_left_exit: + rts + +fire_beam_left_routine_03: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + ldy #$06 ; y = #$06 + jsr draw_fire_beam_if_anim_elapsed + bcs fire_beam_left_exit + lda ENEMY_VAR_3,x + adc #$08 + sta ENEMY_VAR_3,x + bmi fire_beam_left_exit + beq fire_beam_left_exit + bpl fire_beam_disable_collision_routine_01 + +; pointer table for fire beam right (#$4 * #$2 = #$8 bytes) +fire_beam_right_routine_ptr_tbl: + .addr fire_beam_right_routine_00 ; CPU address $aaa4 + .addr fire_beam_right_routine_01 ; CPU address $aaae + .addr fire_beam_right_routine_02 ; CPU address $aac3 + .addr fire_beam_right_routine_03 ; CPU address $aade + +fire_beam_right_routine_00: + lda #$40 ; a = #$40 + sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes (flip sprite horizontally) + lda #$00 ; a = #$00 + jmp fire_beam_add_pos_set_delay + +fire_beam_right_routine_01: + jsr animate_small_flame ; animate small flame when fire beam isn't firing + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne fire_beam_right_exit_00 + lda RANDOM_NUM ; load random number + and #$3f ; keep bits ..xx xxxx + sta ENEMY_VAR_A,x ; set delay between bursts + jmp begin_fire_beam_attack ; load fire beam length, play sound, clear variables for use + +fire_beam_right_routine_02: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + ldy #$08 ; y = #$08 + jsr draw_fire_beam_if_anim_elapsed + bcs fire_beam_right_exit_00 + dec ENEMY_VAR_2,x ; fire beam length + beq fire_beam_delay_10_adv_routine + lda ENEMY_VAR_3,x + adc #$08 + sta ENEMY_VAR_3,x + +fire_beam_right_exit_00: + rts + +fire_beam_delay_10_adv_routine: + jmp set_fire_beam_delay_10_adv_routine + +fire_beam_right_routine_03: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + ldy #$0a ; y = #$0a + jsr draw_fire_beam_if_anim_elapsed + bcs fire_beam_right_exit_00 + lda ENEMY_VAR_3,x + sbc #$07 + sta ENEMY_VAR_3,x + bpl fire_beam_right_exit_00 + jmp fire_beam_disable_collision_routine_01 + +; input +; * x - current enemy offset +; * y - fire_beam_tile_tbl offset minus 1 +draw_fire_beam_if_anim_elapsed: + lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter + bne set_fire_beam_anim_delay_exit + lda ENEMY_VAR_3,x ; either ENEMY_VAR_3 or ENEMY_VAR_4 contain fire beam length + ora ENEMY_VAR_4,x ; merge horizontal and vertical fire beam length + beq @draw_fire_beam_section ; + iny ; fire beam length isn't #$00, increment fire_beam_tile_tbl offset + +@draw_fire_beam_section: + lda fire_beam_tile_tbl,y ; offset into level_6_tile_animation + sta $10 ; load fire beam section update tiles offset + lda ENEMY_VAR_4,x ; load vertical fire beam length + clc ; clear carry in preparation for addition + adc ENEMY_Y_POS,x ; add fire eam length to enemy y position + tay ; transfer result to y + dey ; subtract #$01 from result + lda ENEMY_X_POS,x ; load enemy x position on screen + sec ; set carry flag in preparation for subtraction + sbc #$07 ; subtract #$07 from fire beam x position + sta $00 ; store result in $00 + lda ENEMY_VAR_3,x ; load horizontal fire beam length + clc + bmi fire_beam_add_x_length_exit ; add horizontal length, but don't draw if off screen + adc $00 + bcc draw_fire_beam_tiles ; draw fire beam section at (a, y) + clc + +fire_beam_exit: + rts + +fire_beam_add_x_length_exit: + adc $00 + bcc fire_beam_exit + +; draws the fire beam tiles $10 at (a, y) +; input +; * $10 - level_6_tile_animation offset (tiles to draw) +; * a - x position to draw tile +; * y - y position to draw tile +draw_fire_beam_tiles: + jsr load_bank_3_update_nametable_tiles ; draw tile code $10 to nametable at (a, y) + bcs @exit + ldx ENEMY_CURRENT_SLOT ; restore current enemy slot + lda ENEMY_X_POS,x ; load enemy x position on screen + cmp #$30 ; see if fire beam is closer to the left edge + lda #$00 ; clear animation delay + bcc @set_vars_exit ; set animation delay and update ENEMY_VAR_1 to fire beam length + clc + lda #$01 ; a = #$01 (delay between steps of burst) + +@set_vars_exit: + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + lda ENEMY_VAR_3,x + ora ENEMY_VAR_4,x + sta ENEMY_VAR_1,x + +@exit: + rts + +set_fire_beam_anim_delay_exit: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + sec ; set carry flag + rts + +; table for fire beams tile codes (#$c bytes) +; offset into level_6_tile_animation +fire_beam_tile_tbl: + .byte $87,$88,$80,$89 ; beam down + .byte $84,$85,$80,$86 ; beam left + .byte $81,$82,$80,$83 ; beam right + +; animates small flame for when fire beam isn't firing +animate_small_flame: + dec ENEMY_ATTACK_DELAY,x ; decrement attack delay + lda ENEMY_ATTACK_DELAY,x ; load attack delay + and #$07 ; keep bits .... .xxx + bne fire_beam_exit ; exit if not 8th frame + lda ENEMY_ATTACK_DELAY,x ; every 8th frame, change to next small flame animation + lsr + lsr + lsr + and #$03 ; keep bits .... ..xx + ora ENEMY_FRAME,x ; enemy animation frame number + tay + lda fire_beam_not_firing_sprite_tbl,y ; load sprite for animating flame before firing + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + rts + +; table for sprites for animating small flame before firing fire beam (#$8 bytes) +fire_beam_not_firing_sprite_tbl: + .byte $01,$bf,$c0,$bf + .byte $01,$c1,$c2,$c1 + +; pointer table for boss robot (#$a * #$2 = #$14 bytes) +boss_giant_soldier_routine_ptr_tbl: + .addr boss_giant_soldier_routine_00 ; CPU address $ab93 + .addr boss_giant_soldier_routine_01 ; CPU address $abb3 + .addr boss_giant_soldier_routine_02 ; CPU address $abbf + .addr boss_giant_soldier_routine_03 ; CPU address $ac7b + .addr boss_giant_soldier_routine_04 ; CPU address $acd4 + .addr boss_giant_soldier_routine_05 ; CPU address $ad16 + .addr boss_giant_soldier_routine_06 ; CPU address $ad49 + .addr boss_giant_soldier_routine_07 ; CPU address $ad61 + .addr boss_giant_soldier_routine_08 ; CPU address $ad9b + .addr boss_giant_soldier_routine_09 ; CPU address $adad + +; boss robot - pointer 0 +boss_giant_soldier_routine_00: + lda PLAYER_WEAPON_STRENGTH + asl + asl + asl + adc #$40 + sta ENEMY_HP,x ; set enemy hp (40 + wsc * 8) + lda RANDOM_NUM ; load random number + sta ENEMY_VAR_1,x ; store random number in ENEMY_VAR_1 + lda #$b8 ; a = #$b8 (sprite_b8) giant boss soldier standing + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + lda #$9b ; a = #$9b (initial boss y position) + sta ENEMY_Y_POS,x ; enemy y position on screen + lda #$31 ; a = #$31 (initial boss delay waiting) + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + bne giant_soldier_adv_enemy_routine + +; boss robot - pointer 1 +boss_giant_soldier_routine_01: + jsr update_enemy_pos ; apply velocities and scrolling adjust + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + beq giant_soldier_adv_enemy_routine + rts + +giant_soldier_adv_enemy_routine: + jmp advance_enemy_routine ; advance to next routine + +; boss robot - pointer 2 +boss_giant_soldier_routine_02: + jsr update_enemy_pos ; apply velocities and scrolling adjust + lda ENEMY_VAR_1,x ; load initialized random number + and #$03 ; keep bits .... ..xx + bne begin_giant_soldier_attack ; 3/4 chance of branching + jsr giant_soldier_face_random_player ; 1/4 chance of happening, walk towards player + lda #$f9 ; a = #$f9 + sta ENEMY_Y_VELOCITY_FAST,x ; set initial y fast velocity when jumping + lda #$80 ; a = #$80 + sta ENEMY_Y_VELOCITY_FRACT,x ; set initial y fractional velocity when jumping + lda RANDOM_NUM ; load random number + adc FRAME_COUNTER ; add random number to frame counter + and #$03 ; keep bits .... ..xx + asl ; strip #$00 to #$04 to just either #$00 or #$02 + tay ; transfer #$00 or #$02 to y + lda boss_giant_soldier_x_vel_tbl,y ; load x velocity fast byte + sta ENEMY_X_VELOCITY_FAST,x ; store in x velocity fast byte + lda boss_giant_soldier_x_vel_tbl+1,y ; load x fractional velocity byte + sta ENEMY_X_VELOCITY_FRACT,x ; store in x fractional velocity byte + lda #$00 ; a = #$00 + sta ENEMY_VAR_4,x ; initialize number of thrown saucers to #$00 + lda #$ba ; a = #$ba (sprite_ba) sprite code while jumping + sta ENEMY_SPRITES,x ; set sprite code to sprite_ba + lda #$05 ; a = #$05 (advance to boss_giant_soldier_routine_04) + +giant_soldier_set_enemy_routine_a: + jmp set_enemy_routine_to_a ; set enemy routine index to a + +begin_giant_soldier_attack: + cmp #$01 ; probability of attacking (1/4) + bne @walk_to_player ; don't attack + jsr giant_soldier_face_random_player ; attack + inc ENEMY_VAR_4,x ; increment number of thrown saucers + lda ENEMY_VAR_4,x ; load number of thrown saucers + cmp #$04 ; max number of consecutive thrown saucers + bcs giant_soldier_stay_still ; stay still if thrown #$04 consecutive saucers + lda #$20 ; a = #$20 (delay before throwing position) + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + bne giant_soldier_adv_enemy_routine ; go to boss_giant_soldier_routine_03 + +@walk_to_player: + jsr giant_soldier_face_random_player + lda RANDOM_NUM ; load random number + and #$01 ; keep bits .... ...x + asl + tay + lda boss_giant_soldier_walk_x_vel_tbl,y ; load walking x velocity fast byte + sta ENEMY_X_VELOCITY_FAST,x ; store walking x velocity fast byte + lda boss_giant_soldier_walk_x_vel_tbl+1,y ; load walking x fractional velocity byte + sta ENEMY_X_VELOCITY_FRACT,x ; store walking x fractional velocity byte + lda #$0c ; a = #$0c + sta ENEMY_VAR_2,x ; delay for first step of walking + lda #$06 ; a = #$06 + bne giant_soldier_set_enemy_routine_a ; go to boss_giant_soldier_routine_05 + +giant_soldier_stay_still: + lda #$b8 ; a = #$b8 (sprite_b8) giant boss soldier standing + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + jsr set_enemy_velocity_to_0 ; set x/y velocities to zero + lda RANDOM_NUM ; load random number + sta ENEMY_VAR_1,x ; store random number in ENEMY_VAR_1 + adc FRAME_COUNTER ; add frame counter to random number + and #$80 ; keep bits x... .... + ora ENEMY_ANIMATION_DELAY,x ; (extend delay before next attack by 0 or 80) + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + lda #$03 ; a = #$03 + bne giant_soldier_set_enemy_routine_a ; always branch to boss_giant_soldier_routine_02 + +; gets boss to face random non-game over player +giant_soldier_face_random_player: + ldy #$00 ; y = #$00 (player 1) + lda P2_GAME_OVER_STATUS ; load player 2 game over state (1 = game over) + bne @set_sprite_attr ; flip sprite horizontally if appropriate, then exit + lda RANDOM_NUM ; player 2 not in game over, load random number + and #$01 ; keep bit 0 (select random player) + tay ; transfer player index to y + lda P1_GAME_OVER_STATUS ; game over state of player 1 + beq @set_sprite_attr ; flip sprite horizontally if appropriate, then exit + ldy #$01 ; y = #$01, player 1 in game over, use player 2 + +@set_sprite_attr: + lda SPRITE_X_POS,y ; load randomly selected non-dead player's current x position + sta $08 ; store in $08 + lda ENEMY_X_POS,x ; load enemy current x position + cmp $08 ; compare enemy x position to player x position + lda #$00 ; a = #$00 (assume boss facing left) + bcs @set_sprite_attr_exit ; branch if enemy to right of player (boss face left) + lda #$40 ; player to right of enemy, flip sprite horizontally to face right + +@set_sprite_attr_exit: + sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes + rts + +; table for possible x velocities when jumping (#$8 bytes) +; seems to happen rarely, only at the beginning +; when he is not on his left or right limit (x position) +boss_giant_soldier_x_vel_tbl: + .byte $00,$80 + .byte $00,$00 + .byte $00,$00 + .byte $ff,$80 + +; table for level 6 boss walking speed (#$4 bytes) +boss_giant_soldier_walk_x_vel_tbl: + .byte $01,$18 + .byte $fe,$e8 + +; boss robot - pointer 3 +; creates spiked projectiles +boss_giant_soldier_routine_03: + jsr update_enemy_pos ; apply velocities and scrolling adjust + lda ENEMY_ATTACK_FLAG ; see if enemies should attack + beq boss_giant_stay_still ; exit if enemies shouldn't attack + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + beq create_spiked_projectile ; create the spiked projectile + lda ENEMY_ANIMATION_DELAY,x ; attack delay not elapsed, load enemy animation frame delay counter + cmp #$0f ; (determines delay for throw stance) + bcs set_giant_soldier_palette ; change palette according to hp (every #$04 frames) + lda #$c3 ; a = #$c3 (sprite_c3) sprite code before throw + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + +; change palette according to hp +set_giant_soldier_palette: + lda FRAME_COUNTER ; load frame counter + and #$07 ; keep bits .... .xxx + cmp #$03 ; only update palette every #$04 frames + bne @exit + lda ENEMY_HP,x ; every #$04 frames, check palette, load chance enemy hp + cmp #$20 ; normal palette if hp >= #$20 + bcs @exit + ldy #$51 ; palette #$01 (palette for medium damage) + cmp #$10 ; medium damage if hp >= #$10 and < #$20 + bcs @set_palette ; branch if hp > #$10, use palette #$01 + ldy #$52 ; hp < #$10, palette #$02 (palette for critical damage) + +; critical damage if hp < 10 +@set_palette: + tya ; transfer palette (and sprite byte override) to a + sta LEVEL_PALETTE_INDEX+7 ; set sprite's 3rd palette + lda #$20 ; a = #$20 + jsr load_palettes_color_to_cpu ; load #$20 palette colors into PALETTE_CPU_BUFFER based on LEVEL_PALETTE_INDEX + +@exit: + ldx ENEMY_CURRENT_SLOT + rts + +create_spiked_projectile: + lda #$14 ; a = #$14 (14 = spiked disk projectile) + sta $0a ; set enemy type for projectile + lda #$f0 ; a = #$f0 (initial relative x position) + ldy #$e8 ; y = #$e8 (initial relative y position) + jsr generate_enemy_at_pos ; generate enemy type $0a at relative position a,y + bne boss_giant_stay_still ; exit if unable to create spiked disk projectile + lda ENEMY_SPRITE_ATTR,x ; load boss giant's ENEMY_SPRITE_ATTR + and #$40 ; load boss giant's horizontal flip bit + beq boss_giant_stay_still ; branch if boss giant facing left + lda ENEMY_X_POS,y ; load spiked saucer x position on screen + adc #$30 ; add 30 to x position if facing right + sta ENEMY_X_POS,y ; set spiked saucer x position on screen + +boss_giant_stay_still: + jmp giant_soldier_stay_still + +; boss robot - pointer 4 +boss_giant_soldier_routine_04: + jsr set_giant_soldier_palette ; change palette according to hp (every #$04 frames) + jsr @apply_gravity ; apply gravity and x boundaries check + jsr update_enemy_pos ; apply velocities and scrolling adjust + lda ENEMY_Y_POS,x ; load enemy y position on screen + cmp #$9b ; ground limit when landing after jump + bcs @boss_landing ; branch if landed on ground + rts + +; boss robot landing +@boss_landing: + lda #$15 ; a = #$15 (sound_15) + jsr play_sound ; play boss robot landing sound + jsr set_enemy_velocity_to_0 ; set x/y velocities to zero + lda #$9b ; a = #$9b + sta ENEMY_Y_POS,x ; set enemy y position on screen to ground + lda #$b8 ; a = #$b8 (sprite_b8) (same as sprite_b7) + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + bne boss_giant_stay_still + +; apply gravity and x boundaries check +@apply_gravity: + lda #$38 ; a = #$38 (gravity when jumping) + jsr add_a_to_enemy_y_fract_vel ; add a to enemy y fractional velocity (#$38) + lda ENEMY_X_POS,x ; load enemy x position on screen + cmp #$21 ; #$20 = left boundary + bcc @set_enemy_to_left_edge ; giant soldier is at left edge, hard code stop at boundary + cmp #$c0 ; #$c0 = right boundary + bcc @exit ; enemy hasn't reached left nor right boundary, exit + lda #$c0 ; reached right boundary hard code stop at boundary + bne @set_to_x_boundary ; always branch to set giant soldier to right boundary + +@set_enemy_to_left_edge: + lda #$20 ; a = #$20 + +@set_to_x_boundary: + sta ENEMY_X_POS,x ; set enemy x position on screen + jsr set_enemy_x_velocity_to_0 ; set x velocity to zero + +@exit: + rts + +; boss robot - pointer 5 +boss_giant_soldier_routine_05: + jsr set_giant_soldier_palette ; change palette according to hp (every #$04 frames) + jsr update_enemy_pos ; apply velocities and scrolling adjust + lda ENEMY_X_POS,x ; load enemy x position on screen + cmp #$20 ; left limit when walking + bcc @stay_still + cmp #$c0 ; right limit when walking + bcs @stay_still + dec ENEMY_VAR_2,x ; decrement delay between steps + beq @continue ; branch if delay has elapsed + rts + +@stay_still: + jmp giant_soldier_stay_still + +@continue: + lda #$00 ; a = #$00 + sta ENEMY_VAR_4,x ; clear number of consecutive thrown saucers + lda #$0c ; a = #$0c (delay between steps) + sta ENEMY_VAR_2,x ; initialize delay between steps + inc ENEMY_VAR_3,x + lda ENEMY_VAR_3,x + and #$01 ; keep bits .... ...x + clc ; clear carry in preparation for addition + adc #$b8 ; load sprite_b8 (sprite_b7) or sprite_b9 + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + rts + +boss_giant_soldier_routine_06: + jsr init_APU_channels + lda #$55 ; a = #$55 (sound_55) + jsr level_boss_defeated ; play sound and initiate auto-move + jsr clear_enemy_custom_vars ; set ENEMY_VAR_1, ENEMY_VAR_2, ENEMY_VAR_3, ENEMY_VAR_4 to zero + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + sta $08 ; set relative y offset to #$00 + sta $09 ; set relative x offset to #$00 + jsr create_giant_boss_explosion ; create explosion at center of enemy + ; $09 - relative x offset, $08 - relative y offset + jmp advance_enemy_routine + +; create explosion animations +boss_giant_soldier_routine_07: + lda #$08 ; a = #$08 + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + lda ENEMY_VAR_1,x ; load explosion number + cmp #$04 ; number of explosions when boss is destroyed + bcc @create_explosion ; when not yet created all explosions branch + lda #$30 ; all explosions have been created, set delay and move to next routine + jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to #$30 + ; move to boss_giant_soldier_routine_08 + +@create_explosion: + inc ENEMY_VAR_1,x ; increment explosion number + asl ; double for offset + tay ; transfer boss_giant_explosion_loc_tbl offset to y + lda boss_giant_explosion_loc_tbl,y ; load relative y offset to enemy for explosion + sta $08 ; store relative y offset to enemy for explosion + lda boss_giant_explosion_loc_tbl+1,y ; load relative x offset to enemy for explosion + sta $09 ; store relative x offset to enemy for explosion + +create_giant_boss_explosion: + lda ENEMY_Y_POS,x ; load enemy y position on screen + adc $08 ; add relative y offset for explosion y location + tay ; transfer result to y + lda ENEMY_X_POS,x ; load enemy x position on screen + adc $09 ; add relative x offset for explosion x location + sty $08 ; store absolute y location of explosion in $08 + sta $09 ; store absolute x location of explosion in $09 + jmp create_two_explosion_89 ; create explosion #$89 at location ($09, $08) + +; table for explosions relative offsets (#$4 * #$2 = #$8 bytes) +; byte 0 - y offset +; byte 1 - x offset +boss_giant_explosion_loc_tbl: + .byte $f0,$f0 ; -16, -16 + .byte $10,$10 ; 16, 16 + .byte $f0,$10 ; -16, 16 + .byte $10,$f0 ; 10, -10 + +boss_giant_soldier_routine_08: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + beq @continue ; useless branching !(HUH) + +@continue: + jsr clear_enemy_custom_vars ; set ENEMY_VAR_1, ENEMY_VAR_2, ENEMY_VAR_3, ENEMY_VAR_4 to zero + lda #$08 ; a = #$08 (number of steps for door opening) + sta ENEMY_VAR_3,x + lda #$0a ; a = #$0a (delay for 1st step of door opening) + jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a; advance enemy routine + +; waits for animation timer to elapse, then opens next part of door, repeats until door opened +boss_giant_soldier_routine_09: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + beq @open_door_section ; enemy destroyed delay elapsed, open door + rts + +; updates the door nametable tiles for most of the opening animation when ENEMY_VAR_1 is #$01 +; this is the majority of the door opening animation +@mode_1_open_door: + lda #$8c ; a = #$8c (blank spot and top of door tile code) + sta $10 ; set tile index offset for load_bank_3_update_nametable_tiles + lda ENEMY_VAR_2,x ; load the y position of the bottom of the end of level door (for animating) + tay ; set y position + lda #$d0 ; a = #$d0 (x position to draw tiles) + jsr load_bank_3_update_nametable_tiles ; draw the open door tile code $10 at position (#$d0, y) + ldx ENEMY_CURRENT_SLOT ; restore x's value to the current enemy (boss giant) + dec ENEMY_VAR_3,x ; decrement door opening animation timer + bne @set_door_next_open_pos ; if animation timer hasn't elapsed don't update ENEMY_VAR_1 to #$02 + inc ENEMY_VAR_1,x ; animation timer has elapsed, increment ENEMY_VAR_1 to #$02 (top of door animation frame) + +@set_door_next_open_pos: + lda ENEMY_VAR_2,x ; load current y position of the bottom of the end of level door + sec ; set carry flag in preparation for subtraction + sbc #$08 ; subtract #$08 from y position + sta ENEMY_VAR_2,x ; update y position + rts + +; boss_giant_soldier_routine_09, delay elapsed, open door +; ENEMY_VAR_1 #$00 and #$02 uses boss_giant_door_open_ptr_tbl, #$01 uses @mode_1_open_door +@open_door_section: + lda #$0a ; a = #$0a (delay for each step of door opening) + sta ENEMY_ANIMATION_DELAY,x ; frame delay counter for door opening animation + lda ENEMY_VAR_1,x ; load door opening animation frame type + cmp #$01 ; see if use the most common nametable update tile + beq @mode_1_open_door ; ENEMY_VAR_1 uses #$8c for animating the door opening (most common) + asl ; ENEMY_VAR_1 is either #$00 or #$02, double since each entry is #$02 bytes + tay ; transfer to offset register + lda boss_giant_door_open_ptr_tbl,y ; load low byte of address + sta $04 ; set low byte of address in $04 + lda boss_giant_door_open_ptr_tbl+1,y ; load high byte of address + sta $05 ; store high byte of address in $05 + ldy #$00 ; initialize y = #$00 + lda ($04),y ; load initial y position of door animation + clc ; clear carry in preparation for addition + adc #$10 ; add #$10 to the position (move down) + sta $06 ; update initial y position of door opening update + lda #$d0 ; a = #$d0 + sta $07 ; set x position of door + stx ENEMY_CURRENT_SLOT + +@update_door_tiles: + lda $07 ; load x position of door + iny ; increment boss_giant_door_open_xx read offset + lda ($04),y ; load tile to draw (offset into tile_animation table) + cmp #$ff ; see if end of data byte + beq @finished_drawing_door + sta $10 ; set nametable tile to draw + tya ; transfer to offset register + pha ; push a to stack + lda $07 ; load x position + ldy $06 ; load y position + jsr load_bank_3_update_nametable_tiles ; draw tile code $10 to nametable at (a, y) + pla ; restore a register (nametable tile to draw) + tay ; transfer a to offset register + lda $06 ; load y position of door animation + clc ; clear carry in preparation for addition + adc #$10 ; add #$10 to y position (move down) + sta $06 ; update $06 with new y position + jmp @update_door_tiles ; loop to next part of nametable to draw + +@finished_drawing_door: + ldx ENEMY_CURRENT_SLOT + lda $06 ; load current door opening position + sec ; set carry flag in preparation for subtraction + sbc #$20 ; + sta ENEMY_VAR_2,x ; update door opening y location + inc ENEMY_VAR_1,x ; increment opening animation frame type + lda ENEMY_VAR_1,x ; load opening animation frame type + cmp #$03 ; compare to #$03 + beq @remove_enemy ; if finished all three frame types (#$00, #$01, #$02) + rts + +@remove_enemy: + lda #$01 ; a = #$01 + jmp set_delay_remove_enemy + +; pointer table for boss giant soldier door opening (#$3 * #$2 = #$6 bytes) +; related to ENEMY_VAR_1 +; boss_giant_door_open_00 is for the beginning of the animation (lifting from floor) +; boss_giant_door_open_01 is for the end of the animation (lifting into top) +boss_giant_door_open_ptr_tbl: + .addr boss_giant_door_open_00 ; CPU address $ae3b + .addr boss_giant_door_open_00 ; CPU address $ae3b (unused, filler) + ; filler because ENEMY_VAR_1 = 1 uses #$8c (see @mode_1_open_door) + .addr boss_giant_door_open_01 ; CPU address $ae3f + +; table for boss giant door being opening tiles (#$4 bytes) +; byte 0 is initial y position. the rest of the bytes reference level_6_tile_animation +boss_giant_door_open_00: + .byte $90,$8b,$8a,$ff + +; table for boss giant end door opening (#$3 bytes) +; byte 0 is initial y position. the rest of the bytes reference level_6_tile_animation +boss_giant_door_open_01: + .byte $58,$8d,$ff + +; pointer table for spiked disk projectile (#$3 * #$2 = #$6 bytes) +boss_giant_projectile_routine_ptr_tbl: + .addr boss_giant_projectile_routine_00 ; CPU address $ae48 - set sprite code, and velocities + .addr boss_giant_projectile_routine_01 ; CPU address $ae8a - update position, if animation delay has elapsed, update sprite so the disk rotates + .addr remove_enemy ; CPU address $e809 from bank 7 + +; set sprite code, and velocities +boss_giant_projectile_routine_00: + lda #$06 ; a = #$06 (delay between frames when airborne) + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + lda #$bb ; a = #$bb (sprite_bb) + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + ; assume boss giant facing left, set spiked velocity + ; if not, will be changed later (+14 lines down) + lda #$fd ; a = #$fd (-3) + sta ENEMY_X_VELOCITY_FAST,x ; set spiked disk x fast velocity + lda #$00 ; a = #$00 + sta ENEMY_X_VELOCITY_FRACT,x ; set spiked disk x fractional velocity + ldx #$0f ; set enemy slot offset to #$0f + +@find_boss_giant: + lda ENEMY_TYPE,x ; load current enemy type + cmp #$13 ; see if current slot is boss giant + beq @continue ; branch if boss giant found + dex ; search next enemy slot + bne @find_boss_giant ; boss giant not found, loop + +@continue: + lda ENEMY_SPRITE_ATTR,x ; load enemy sprite attributes + and #$40 ; keep bit 6 (horizontal flip flag) + beq @set_y_vel_adv_routine ; branch if boss giant facing left to set y vel and advance routine + ldx ENEMY_CURRENT_SLOT ; boss giant facing right, load spiked disk slot + lda #$03 ; a = #$03 + sta ENEMY_X_VELOCITY_FAST,x ; set spiked disk x fast velocity going right + lda #$00 ; a = #$00 + sta ENEMY_X_VELOCITY_FRACT,x ; set spiked disk x fractional velocity + +; y velocity for spiked disk +@set_y_vel_adv_routine: + ldx ENEMY_CURRENT_SLOT ; restore spiked disk enemy slot + lda #$02 ; a = #$02 + sta ENEMY_Y_VELOCITY_FAST,x ; set spiked disk fast y velocity to #$02 + lda #$00 ; a = #$00 + sta ENEMY_Y_VELOCITY_FRACT,x ; set spiked disk fractional y velocity to #$00 + +boss_giant_projectile_adv_routine: + jmp advance_enemy_routine + +; update position, if animation delay has elapsed, update sprite so the disk rotates +; remove enemy if off screen +boss_giant_projectile_routine_01: + lda ENEMY_X_POS,x ; load enemy x position on screen + cmp #$e0 ; see if off screen to the right + bcs boss_giant_projectile_adv_routine ; advance routine to remove_enemy + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + beq @set_sprite_update_pos_exit ; update sprite if animation delay has elapsed + lda ENEMY_Y_POS,x ; animation delay hasn't elapse,d load enemy y position on screen + cmp #$af ; ground limit for spiked disk + bcs @stop_y_velocity ; branch if landed on ground to stop disk from falling down below ground + bcc @update_pos_exit ; update pos and exit if not yet landed on ground + +; stops y velocity, keeping x velocity +@stop_y_velocity: + jsr set_enemy_y_velocity_to_0 ; set y velocity to zero + +@update_pos_exit: + jmp update_enemy_pos ; apply velocities and scrolling adjust + +@set_sprite_update_pos_exit: + lda #$06 ; a = #$06 + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + inc ENEMY_VAR_1,x ; increment sprite code control flag + lda ENEMY_VAR_1,x ; load sprite code control flag + and #$01 ; keep bit 0 + clc ; clear carry in preparation for addition + adc #$bb ; add to #$bb to determine wither sprite_bb or sprite_bc will be drawn + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + jmp update_enemy_pos ; apply velocities and scrolling adjust + +; pointer table for mechanical claw (#$4 * #$2 = #$8 bytes) +claw_routine_ptr_tbl: + .addr claw_routine_00 ; CPU address $aec3 - set ENEMY_FRAME to frame counter trigger, strip ENEMY_ATTRIBUTES to just claw length, set delay, advance routine + .addr claw_routine_01 ; CPU address $aee5 - wait for descent, advance routine + .addr claw_routine_02 ; CPU address $af30 - animate claw descent + .addr claw_routine_03 ; CPU address $af4d - animate claw ascent + +; set ENEMY_FRAME to frame counter trigger, strip ENEMY_ATTRIBUTES to just claw length, set delay, advance routine +claw_routine_00: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + lsr + lsr ; get rid of claw length + and #$03 ; keep bits 0 and 1 (descend delay) + tay ; transfer to offset register + lda claw_frame_trigger_tbl,y ; load frame counter number at which to trigger descent + sta ENEMY_FRAME,x ; store claw delay in ENEMY_FRAME + lda ENEMY_ATTRIBUTES,x ; reload enemy attributes + and #$03 ; keep bits 0 and 1 (claw length) + sta ENEMY_ATTRIBUTES,x ; update ENEMY_ATTRIBUTES to just store claw length + lda #$20 ; a = #$20 (initial delay before going descending) + +claw_set_delay_adv_routine: + jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a; advance enemy routine + +; table of frame counter triggers before going down (#$4 bytes) +; whenever (FRAME_COUNTER & #$7f) matches this value, the claw will descend +; used so that all claws of the same delay go down at the same time +; enemy attribute bits .... xx.. +claw_frame_trigger_tbl: + .byte $00,$20,$40,$60 + +; wait for descent, advance routine +claw_routine_01: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + cmp #$03 ; see if claw length 3 (seeking claw - only attack when player near) + beq @check_delay_seek_player ; branch if seeking claw + lda FRAME_COUNTER ; not seeking claw, load frame counter to see if should descend + and #$7f ; strip bit 7 + cmp ENEMY_FRAME,x ; compare to enemy animation frame number (frame counter trigger point) + bne claw_routine_01_exit ; branch if frame counter doesn't match and claw shouldn't descend + +@descend_claw: + lda ENEMY_X_POS,x ; load enemy x position on screen + cmp #$2c ; compare to the left 17% of screen + bcc claw_routine_01_exit ; don't descend if exit if claw is far to the left + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + tay ; transfer maximum claw length to offset register + asl ; double since each entry in claw_update_nametable_ptr_tbl is #$02 bytes + sta ENEMY_VAR_4,x ; set claw_update_nametable_ptr_tbl offset + lda claw_length_tbl,y ; load claw length + sta ENEMY_VAR_2,x ; set claw length + lda #$00 ; a = #$00 + sta ENEMY_VAR_3,x + beq claw_set_delay_adv_routine ; always branch + +; claw length 3 - seeking claw +@check_delay_seek_player: + lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter + bne claw_dec_delay_exit ; decrement delay and exit if timer hasn't elapsed + lda FRAME_COUNTER ; load frame counter + cmp #$c0 ; seeking claws don't attack 25% of the time, i.e. between #$c0 and #$ff inclusively + bcs claw_routine_01_exit ; exit if claw shouldn't attack due to timing + jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01) + cmp #$10 ; see if player within #$10 horizontal pixels of claw + bcc @descend_claw ; descend claw if closest player is < #$10 distance away + rts ; exit if player too far from claw for it to attack + +claw_dec_delay_exit: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + sec ; set carry flag (indicates claw wasn't animated) + rts + +; table for possible claw lengths (#$4 bytes) +; length code 3 makes the claw activate only when the player is near +claw_length_tbl: + .byte $04,$03,$08,$03 + +; animate claw descent +claw_routine_02: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + jsr animate_claw ; update the nametable tiles + bcs claw_routine_01_exit ; exit if unable to update the nametable tiles + dec ENEMY_VAR_2,x ; decrement remaining claw length for ascending + beq claw_inc_tile_adv_routine ; advance routine if claw fully extended + lda #$08 ; a = #$08 + jsr add_a_to_enemy_y_pos ; add #$08 to enemy y position on screen + inc ENEMY_VAR_3,x ; increment current length of the claw + +claw_routine_01_exit: + rts + +claw_inc_tile_adv_routine: + inc ENEMY_VAR_4,x ; increment claw_update_nametable_ptr_tbl offset + lda #$08 ; a = #$08 + bne claw_set_delay_adv_routine ; advance to claw_routine_03 + +; animate claw ascent +claw_routine_03: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + jsr animate_claw ; animate claw ascent + bcs claw_routine_01_exit ; exit if unable to update nametable tiles + dec ENEMY_VAR_3,x ; decrement current length of the claw + bmi @wait_for_descent ; go back to claw_routine_01 to wait for next descent if claw fully retracted + lda #$f8 ; a = #$f8 (-8) + jmp add_a_to_enemy_y_pos ; add a to enemy y position on screen + +@wait_for_descent: + lda ENEMY_FRAME,x ; load enemy animation frame number + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + lda #$02 ; a = #$02 + jmp set_enemy_routine_to_a ; set enemy routine index to claw_routine_01 to wait for next descent + +; updates nametable tiles to animate ascending and descending the claw +; output +; * carry flag - clear when nametable updated, set when not +animate_claw: + lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter + bne claw_dec_delay_exit ; exit if animation delay hasn't elapsed + ; with carry flag set indicating no update + lda ENEMY_VAR_4,x ; load current offset into claw_update_nametable_ptr_tbl + asl ; double since each entry is #$02 bytes + tay ; transfer to offset register + lda claw_update_nametable_ptr_tbl,y ; load low byte of address + sta $10 ; store low byte of address in $10 + lda claw_update_nametable_ptr_tbl+1,y ; load high byte of address + sta $11 ; store high byte of address in $11 + ldy ENEMY_VAR_3,x ; load current extension of the claw + lda ($10),y ; load tile code to draw + sta $10 ; store tile code to draw for load_bank_3_update_nametable_tiles + lda ENEMY_X_POS,x ; load enemy x position on screen + ldy ENEMY_Y_POS,x ; enemy y position on screen + jsr load_bank_3_update_nametable_tiles ; draw tile code $10 to nametable at (a, y) + bcs @exit ; exit if unable to update nametable tiles + ldx ENEMY_CURRENT_SLOT ; temporary storage for x register + lda ENEMY_X_POS,x ; load enemy x position on screen + cmp #$2c ; compare to the left 17% of screen + lda #$00 ; a = #$00 (no delay when claw wasn't updated) + bcc @set_anim_delay_exit ; if enemy is too far to the left, exit + clc ; clear carry to indicate success + lda #$02 ; a = #$02 (speed of claw) + +@set_anim_delay_exit: + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + +@exit: + rts + +; pointer table for claw super-tile animation indexes (8 * 2 = 10 bytes) +; see level_7_tile_animation +claw_update_nametable_ptr_tbl: + .addr claw_tile_code_00 ; CPU address $afb2 - ascent + .addr claw_tile_code_01 ; CPU address $afb6 - descent + .addr claw_tile_code_00 ; CPU address $afb2 - ascent + .addr claw_tile_code_01 ; CPU address $afb6 - descent + .addr claw_tile_code_02 ; CPU address $afba - ascent + .addr claw_tile_code_03 ; CPU address $afc2 - descent + .addr claw_tile_code_00 ; CPU address $afb2 - ascent + .addr claw_tile_code_01 ; CPU address $afb6 - descent + +; tables for claw tile codes +; see level_7_tile_animation +; this table is different from Trax source +claw_tile_code_00: + .byte $86,$86,$86,$86 + +claw_tile_code_01: + .byte $80,$80,$82,$84 + +claw_tile_code_02: + .byte $86,$86,$86,$86 + +; unused space +; same as claw_tile_code_00 +claw_tile_code_unused_00: + .byte $86,$86,$86,$86 + +claw_tile_code_03: + .byte $80,$80,$80,$80 + +; unused space +; same as claw_tile_code_01 +claw_tile_code_unused_01: + .byte $80,$80,$82,$84 + +; pointer table for raising spiked wall (enemy type #$11) (#$6 * #$2 = #$c bytes) +rising_spiked_wall_routine_ptr_tbl: + .addr rising_spiked_wall_routine_00 ; CPU address $afd6 - init variables + .addr rising_spiked_wall_routine_01 ; CPU address $b00c - wait for player to get close, and advance routine + .addr rising_spiked_wall_routine_02 ; CPU address $b025 - animate rising wall and configure collision box size + .addr rising_spiked_wall_routine_03 ; CPU address $b200 - ensure scroll up to date + .addr rising_spiked_wall_routine_04 ; CPU address $b087 - destroyed routine, set spiked_wall_destroyed_update_tbl offset, play sound + .addr rising_spiked_wall_routine_05 ; CPU address $b09c - animate wall destruction by updating super-tiles + +; init variables +rising_spiked_wall_routine_00: + lda ENEMY_ATTRIBUTES,x ; load the rising spiked wall's attribute + and #$0c ; keep bits 2 and 3, for use in distance trigger + lsr ; only shift once since each entry is #$02 bytes + tay ; transfer offset to y + lda rising_spike_wall_trigger_dist_tbl,y ; load trigger distance + sta ENEMY_VAR_3,x ; store trigger distance + lda rising_spike_wall_trigger_dist_tbl+1,y ; load rising delay timer + sta ENEMY_VAR_4,x ; set rising delay timer + lda ENEMY_ATTRIBUTES,x ; load attributes again + and #$03 ; keep bits 0 and 1 + tay ; transfer index to y + lda rising_spike_wall_delay_tbl,y ; load ENEMY_ATTACK_DELAY + sta ENEMY_ATTACK_DELAY,x ; store delay + +; used for both rising spiked wall and spiked wall to set collision box index to 16 +; this is to specify that the collision box grows upwards in heigh with a fixed width +spiked_wall_set_collision_box: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda #$c0 ; a = #$c0 + sta ENEMY_ATTRIBUTES,x ; set negative collision code f values + ; and use offset #$10 (16) into collision_code_f_adj_tbl (expand collision box upwards) + +advance_spiked_wall_enemy_routine: + jmp advance_enemy_routine + +; table for distance of emergence and subsequent delays (#$c bytes) +rising_spike_wall_trigger_dist_tbl: + .byte $30,$00 ; first wall (no delay) + .byte $50,$0f ; distance and delay before second wall + .byte $70,$1e ; distance and delay before third wall + .byte $40,$00 + +; table for possible delays between emerging steps (#$4 bytes) +; determines emergence speed +rising_spike_wall_delay_tbl: + .byte $0c,$08,$04,$02 + +; wait for player to get close, and advance routine +rising_spiked_wall_routine_01: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01) + cmp ENEMY_VAR_3,x ; compare the distance of the closest player to the trigger distance before the rising timer will start + bcs rising_spiked_wall_exit ; branch if closest player is farther than ENEMY_VAR_3,x + jsr enable_enemy_collision ; enable bullet-enemy collision and player-enemy collision checks + lda #$06 ; a = #$06 + sta ENEMY_VAR_2,x ; set initial offset + lda ENEMY_VAR_4,x ; load the delay timer before the wall starts rising + jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a and advance enemy routine + +; animate rising wall +rising_spiked_wall_routine_02: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter + bne dec_delay_enable_set_vel_exit ; if the animation delay hasn't elapsed, decrement and exit + lda ENEMY_VAR_2,x ; animation delay has elapsed, load rising_spiked_wall_data_tbl read offset + asl + adc ENEMY_VAR_2,x ; multiply by 3 + tay ; transfer to offset register + lda rising_spiked_wall_data_tbl+1,y ; load nametable super-tile update index + sta $10 ; set nametable super-tile update index + lda rising_spiked_wall_data_tbl+2,y ; load collision box placeholder replacement amount (see collision_code_f_adj_tbl) + sta ENEMY_VAR_1,x ; store collision box placeholder replacement amount (see collision_code_f_adj_tbl) + ; since bit 6 of ENEMY_ATTRIBUTES is set, the final value is actually (-1 * ENEMY_VAR_1,x) + #$08 + ; this value (or its negation) are used to replace placeholder values in collision_code_f_adj_tbl + ; to support a dynamic collision box size that can grow upwards with a fixed width + lda rising_spiked_wall_data_tbl,y ; load y position offset + adc ENEMY_Y_POS,x ; add to enemy y position on screen + tay ; transfer result to offset register for load_bank_3_update_nametable_supertile + lda ENEMY_X_POS,x ; load enemy x position on screen + sbc #$0d ; subtract 13 from x position + jsr load_bank_3_update_nametable_supertile ; draw super-tile $10 at position (a,y) + bcs rising_spiked_wall_exit ; exit if unable to draw the rising wall + ldx ENEMY_CURRENT_SLOT ; restore x to rising wall enemy slot index + lda ENEMY_VAR_2,x ; load rising_spiked_wall_data_tbl read offset + cmp #$04 ; compare to the last #$04 super-tiles to draw + bcs @continue ; branch if drawing the first #$03 super-tiles + lda #$00 ; x <= 3, left side of super-tile bg collision (#$00 = empty collision codes) + ldy #$0f ; right side of super-tile bg collision (#$0f = solid collision codes) + jsr set_supertile_bg_collisions ; update bg collision codes for a single super-tile at PPU address $12 (low) $13 (high) + +@continue: + lda ENEMY_ATTACK_DELAY,x ; load animation delay + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + dec ENEMY_VAR_2,x ; move to next rising_spiked_wall_data_tbl read offset + bpl rising_spiked_wall_exit ; exit if still more super-tiles to draw + bmi advance_spiked_wall_enemy_routine ; finished animation, move to rising_spiked_wall_routine_03 + +dec_delay_enable_set_vel_exit: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + +rising_spiked_wall_exit: + rts + +; tile codes for raising spiked wall (#$15 bytes) +; byte 0 - enemy y position offset +; byte 1 - super-tile code (see level_7_nametable_update_supertile_data) +; byte 2 - collision box initial configuration (see collision_code_f_base_tbl) +rising_spiked_wall_data_tbl: + .byte $c0,$91,$d0 ; #$11 - rising wall (frame 5) (four rows of spikes visible) (collision box heigh = #$38) + .byte $d0,$91,$e0 ; #$11 - rising wall (frame 5) (four rows of spikes visible) (collision box heigh = #$20) + .byte $e0,$90,$f0 ; #$10 - rising wall (frame 4) (three rows of spikes visible) (collision box heigh = #$18) + .byte $e0,$8f,$f8 ; #$0f - rising wall (frame 3) (two rows of spikes visible) (collision box heigh = #$10) + .byte $f0,$8e,$00 ; #$0e - rising wall (frame 2) (first row of spikes visible) (collision box heigh = #$08) + .byte $f0,$8d,$09 ; #$0d - rising wall (frame 1) (slightly out of ground) (collision box heigh = #$ff) + .byte $f0,$8c,$09 ; #$0c - rising wall (frame 0) (barely out of ground) (collision box heigh = #$ff) + +; destroyed routine, set spiked_wall_destroyed_update_tbl offset, play sound +rising_spiked_wall_routine_04: + lda #$00 ; a = #$00 + sta ENEMY_VAR_4,x ; set spiked_wall_destroyed_update_tbl offset to #$00 + lda #$03 ; a = #$03 + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + +; spiked wall destroyed routine - play sound advance routine +spiked_wall_routine_02: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda #$24 ; a = #$24 (sound_24) + jsr play_sound ; play explosion sound + jmp advance_enemy_routine ; advance to next routine + +; animate wall destruction by updating super-tiles +rising_spiked_wall_routine_05: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda ENEMY_VAR_4,x ; load spiked_wall_destroyed_update_tbl read offset + asl + adc ENEMY_VAR_4,x ; multiple by 3 + tay ; transfer to offset register + lda spiked_wall_destroyed_update_tbl+1,y ; load super-tile nametable update index (see level_7_nametable_update_supertile_data) + sta $10 ; store in $10 for load_bank_3_update_nametable_supertile call + lda spiked_wall_destroyed_update_tbl,y ; load relative y position + adc ENEMY_Y_POS,x ; add to enemy y position on screen + sty $f0 ; store read offset in $f0 + tay ; transfer result y position to y + lda ENEMY_X_POS,x ; load enemy x position on screen + sbc #$0d ; subtract #$0d from x position + bcc remove_spiked_wall ; if underflow, remove spiked wall (far left of screen) + jsr load_bank_3_update_nametable_supertile ; draw super-tile $10 at position (a,y) + bcs rising_spiked_wall_exit ; exit if unable to draw destroyed spiked wall super-tile + ldx ENEMY_CURRENT_SLOT ; updated super-tile in graphics buffer + ; restore rising spiked wall enemy slot index + lda ENEMY_VAR_4,x ; load the current entry in spiked_wall_destroyed_update_tbl + and #$03 ; keep bits .... ..xx + beq @continue ; branch if currently drawing first super-tile + jsr clear_supertile_bg_collision ; set background collision code to #$00 (empty) for a single super-tile at PPU address $12 (low) $13 (high) + +@continue: + ldy $f0 ; restore spiked_wall_destroyed_update_tbl read offset + lda spiked_wall_destroyed_update_tbl+2,y ; load relative x position + tay ; set vertical offset from enemy position (param for add_with_enemy_pos) + lda #$fc ; set horizontal offset from enemy position (param for add_with_enemy_pos) + jsr add_with_enemy_pos ; stores absolute screen x position in $09, and y position in $08 + jsr create_two_explosion_89 ; create explosion + inc ENEMY_VAR_4,x ; move to next super-tile to draw (higher up on wall) + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne rising_spiked_wall_exit ; exit if animation delay hasn't elapsed + +remove_spiked_wall: + jmp remove_enemy ; remove enemy + +; spiked walls block indexes after destruction and +; fixed walls of 3 tiles high (#$15 bytes) +; byte 0 - relative y position +; byte 1 - super-tile nametable update index (see level_7_nametable_update_supertile_data) +; byte 2 - relative x position (+#$0d) +spiked_wall_destroyed_update_tbl: + .byte $00,$84,$08 ; #$04 - spiked wall super-tile destroyed floor + .byte $e0,$8b,$f0 ; #$0b - fence + .byte $c0,$8a,$d0 ; #$0a - blank super-tile + .byte $a0,$86,$b0 ; #$06 - tall spiked wall destroyed top (parting hanging from ceiling) + .byte $00,$84,$08 ; #$04 - spiked wall super-tile destroyed floor + .byte $e0,$8b,$f0 ; #$0b - fence + .byte $c0,$86,$d0 ; #$06 - tall spiked wall destroyed top (parting hanging from ceiling) + +; pointer table for spiked wall (12) (#$4 * #$2 = #$8 bytes) +spiked_wall_routine_ptr_tbl: + .addr spiked_wall_routine_00 ; CPU address $b103 - initialize collision box and wall destroyed variables + .addr rising_spiked_wall_routine_03 ; CPU address $b200 - ensure scroll up to date + .addr spiked_wall_routine_02 ; CPU address $b091 - spiked wall destroyed routine - play sound advance routine + .addr rising_spiked_wall_routine_05 ; CPU address $b09c - animate wall destruction by updating super-tiles + +; initialize collision box and wall destroyed variables, advance routine +spiked_wall_routine_00: + lda #$b8 ; a = #$b8 + sta ENEMY_VAR_1,x ; dynamic collision box height = #$50 ((-1 * #$b8) + #$08)) + ldy ENEMY_ATTRIBUTES,x ; load enemy attributes + lda spiked_wall_destroyed_data_tbl,y ; load spiked_wall_destroyed_update_tbl offset + sta ENEMY_VAR_4,x ; set spiked_wall_destroyed_update_tbl offset + lda spiked_wall_destroyed_data_tbl+1,y ; load wall-destroyed enemy animation delay timer + sta ENEMY_ANIMATION_DELAY,x ; set wall-destroyed enemy animation delay timer + jmp spiked_wall_set_collision_box ; set collision code f dynamic mode to #$10 (grow upwards) + ; and advance routine + +; table for spiked wall routine (#$4 bytes) +; byte 0 - initial spiked_wall_destroyed_update_tbl offset +; byte 1 - wall destroyed enemy animation delay timer (timer before wall is removed after explosion) +spiked_wall_destroyed_data_tbl: + .byte $04,$03 ; half-screen wall on top half of screen + .byte $00,$04 ; full screen wall + +; pointer table for cart generator (13) (#$2 * #$2 = #$4 bytes) +mine_cart_generator_routine_ptr_tbl: + .addr mine_cart_generator_routine_00 ; CPU address $b122 + .addr mine_cart_generator_routine_01 ; CPU address $b12c + +mine_cart_generator_routine_00: + lda #$80 ; a = #$80 + sta ENEMY_FRAME,x ; set generated cart slot number to signify no cart generated + lda #$01 ; a = #$01 + jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a; advance enemy routine + +mine_cart_generator_routine_01: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda ENEMY_FRAME,x ; load generated cart's slot number, #$80 means no cart generated + bpl @check_generated_cart_status ; if generated cart exists, see how it is doing. did it get destroyed yet + dec ENEMY_ANIMATION_DELAY,x ; no cart generated, decrement enemy animation frame delay counter + bne cart_routine_exit ; exit if delay timer hasn't elapsed + inc ENEMY_ANIMATION_DELAY,x ; enemy animation frame delay counter + jsr find_next_enemy_slot ; find next available enemy slot, put result in x register + bne @exit ; exit when no enemy slot available + lda #$14 ; a = #$14 (14 = moving cart) + sta ENEMY_TYPE,x ; set current enemy type to moving cart + jsr initialize_enemy ; generate enemy + lda #$f8 ; a = #$f8 + sta ENEMY_X_POS,x ; set enemy x position on screen to #$f8 + lda #$ff ; a = #$ff + sta ENEMY_X_VELOCITY_FAST,x ; set to move 1 unit left every frame + lda #$02 ; a = #$02 + sta ENEMY_VAR_4,x ; set cart direction to left + lda #$80 ; a = #$80 + sta ENEMY_ATTRIBUTES,x ; specify the cart should blow up upon collision with background + jsr init_cart_vel_and_y_pos ; set cart initial velocity and y position + txa + ldx ENEMY_CURRENT_SLOT + sta ENEMY_FRAME,x ; set enemy animation frame number + +@exit: + ldx ENEMY_CURRENT_SLOT + rts + +; see status of generated cart, if no longer active, set to generate new cart after delay +@check_generated_cart_status: + ldy ENEMY_FRAME,x ; load generated cart's slot number + lda ENEMY_ROUTINE,y ; load current routine index for generated mining cart + bne cart_routine_exit ; generated cart has not been destroyed, exit + lda #$80 ; generated cart has been destroyed, start logic to generate a new cart + sta ENEMY_FRAME,x ; set generated cart slot number to signify no cart generated + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + +cart_routine_exit: + rts + +; pointer table for moving cart (14) (#$6 * #$2 = #$c bytes) +moving_cart_routine_ptr_tbl: + .addr moving_cart_routine_00 ; CPU address $b186 + .addr moving_cart_routine_00 ; CPU address $b186 + .addr moving_cart_routine_00 ; CPU address $b186 + .addr enemy_routine_init_explosion ; CPU address $e74b from bank 7 + .addr enemy_routine_explosion ; CPU address $e7b0 from bank 7 + .addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7 + +moving_cart_routine_00: + lda FRAME_COUNTER ; load frame counter + lsr + lsr + and #$01 ; every 8 frames alternate sprite code to show wheel moving animation + clc ; clear carry in preparation for addition + adc #$2a ; either sprite code #$2a or #$2b depending if big 2 of FRAME_COUNTER is 1 or not + sta ENEMY_SPRITES,x ; save mine cart sprite to enemy sprite buffer + jsr update_enemy_pos ; apply velocities and scrolling adjust + ldy ENEMY_VAR_4,x ; cart direction (0 = right, 2 = left) + lda ENEMY_X_POS,x ; load enemy x position on screen + clc ; clear carry in preparation for addition + adc cart_collision_config_tbl+1,y ; load x offset for bg collision check depending on cart direction + sta $00 ; store in variable used in get_cart_bg_collision as x position + lda #$00 ; a = #$00 + adc cart_collision_config_tbl,y ; add XOR value used in bg collision depending on cart direction + ; note carry can be set from previous addition + sta $10 ; XOR for use in bg collision + lda $00 ; sprite x position + ldy ENEMY_Y_POS,x ; enemy y position on screen + jsr get_cart_bg_collision ; get enemy background collision + bne cart_bg_collision ; branch if cart has collided with anything (floor (#$01), water (#$02), or a solid object (#$80)) + lda #$00 ; a = #$00 + ldy #$09 ; y = #$09 + jsr add_a_y_to_enemy_pos_get_bg_collision ; add a (#$00) to X position and y (#$09) to Y position; get bg collision code + bne cart_routine_exit ; exit if no collision + lda #$20 ; a = #$20 (gravity for cart) + jmp add_a_to_enemy_y_fract_vel ; add a to enemy y fractional velocity + +; cart has collided with something, reverse direction +cart_bg_collision: + lda ENEMY_ATTRIBUTES,x ; generated moving carts explode on impact, but immobile carts will reverse direction + bmi set_cart_explosion ; whether or not the cart should turn around or explode upon collision + lda ENEMY_VAR_4,x ; load cart direction variable + eor #$02 ; swap cart direction by flipping bit 1 (0 = right, 2 = left) + sta ENEMY_VAR_4,x ; set new cart direction + jmp reverse_enemy_x_direction ; reverse x direction + +; move to enemy_routine_init_explosion routine +set_cart_explosion: + lda #$04 ; a = #$04 + jmp set_enemy_routine_to_a ; set enemy routine index to a + +; byte 0 is the XOR value used to help level_screen_mem_offset_tbl_01 lookup ($10) +; byte 1 is the X offset from cart X position for use in collision detection +cart_collision_config_tbl: + .byte $00,$0f + .byte $ff,$f1 + +; pointer table for immobile cart (15), can start rolling when player lands on it (#$6 * #$2 = #$c bytes) +immobile_cart_generator_routine_ptr_tbl: + .addr immobile_cart_generator_routine_00 ; CPU address $b1e5 + .addr immobile_cart_generator_routine_01 ; CPU address $b1fb + .addr moving_cart_routine_00 ; CPU address $b186 + .addr enemy_routine_init_explosion ; CPU address $e74b from bank 7 + .addr enemy_routine_explosion ; CPU address $e7b0 from bank 7 + .addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7 + +; initialize x velocity, sprite, and y pos on screen +immobile_cart_generator_routine_00: + lda #$c0 ; a = #$c0 + jsr init_cart_vel_and_y_pos ; set x velocity of cart when stepped on to #$c0 + +cart_advance_enemy_routine: + jmp advance_enemy_routine ; advance to next routine + +init_cart_vel_and_y_pos: + sta ENEMY_X_VELOCITY_FRACT,x ; set x fractional velocity to a + lda #$2a ; a = #$2a (mining cart sprite code) + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + lda #$c8 ; a = #$c8 + sta ENEMY_Y_POS,x ; enemy y position on screen + rts + +immobile_cart_generator_routine_01: + lda ENEMY_FRAME,x ; load enemy animation frame number (set to #01 when @land_on_enemy executes) + bne cart_advance_enemy_routine ; player has landed on cart, start moving cart (moving_cart_routine_00) + +; ensure scroll up to date +rising_spiked_wall_routine_03: + jmp add_scroll_to_enemy_pos ; add scrolling to enemy position + +; pointer table for jungle armored door (level 7) (16) (#$7 * #$2 = #$e bytes) +boss_door_routine_ptr_tbl: + .addr boss_door_routine_00 ; CPU address $b211 + .addr boss_door_routine_01 ; CPU address $b219 + .addr boss_door_routine_02 ; CPU address $b228 + .addr boss_defeated_routine ; CPU address $e740 from bank 7 + .addr enemy_routine_explosion ; CPU address $e7b0 from bank 7 + .addr boss_door_routine_05 ; CPU address $b240 - set tile sprite code to #$00, advance routine, add #$20 to y position + .addr boss_door_routine_06 ; CPU address $b248 + +; armored door - pointer 0 +boss_door_routine_00: + lda #$1b ; a = #$1b (sound_1b) + jsr play_sound ; play level 1 jungle boss siren sound + jmp advance_enemy_routine ; advance to next routine + +; armored door - pointer 1 +boss_door_routine_01: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda ENEMY_HP,x ; load enemy hp + cmp #$05 ; stop spawning enemies when hp < 5 + bcs @exit + lda #$02 ; a = #$02 + sta BOSS_SCREEN_ENEMIES_DESTROYED ; set number of mortar launchers to be destroyed before soldiers stop being generated + +@exit: + rts + +; armored door - pointer 2 +boss_door_routine_02: + lda ENEMY_VAR_1,x + bne @continue + lda #$08 ; a = #$08 + jsr add_a_to_enemy_x_pos ; add #$08 to enemy x position on screen + inc ENEMY_VAR_1,x + +@continue: + lda #$05 ; a = #$05 + jsr draw_enemy_supertile_a ; draw super-tile a at position (ENEMY_X_POS, ENEMY_Y_POS) + bcc boss_door_adv_enemy_routine + +boss_door_exit: + rts + +boss_door_adv_enemy_routine: + jmp advance_enemy_routine ; advance to next routine + +; set tile sprite code to #$00, advance routine, add #$20 to y position +boss_door_routine_05: + jsr shared_enemy_routine_clear_sprite ; set tile sprite code to #$00 and advance routine + +boss_door_add_20_to_y_pos: + lda #$20 ; a = #$20 + jmp add_a_to_enemy_y_pos ; add a to enemy y position on screen + +boss_door_routine_06: + ldy ENEMY_VAR_2,x + lda boss_door_update_supertile_tbl,y ; load super tile to draw (offset into level_xx_nametable_update_supertile_data) + jsr draw_enemy_supertile_a ; draw super-tile a at position (ENEMY_X_POS, ENEMY_Y_POS) + bcs boss_door_exit + lda #$05 ; left side of super-tile bg collision (#$05 = ground collision codes) + ldy #$05 ; right side of super-tile bg collision (#$05 = ground collision codes) + jsr set_supertile_bg_collisions ; update bg collision codes for a single super-tile at PPU address $12 (low) $13 (high) + jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position + jsr create_two_explosion_89 ; create explosion #$89 at location ($09, $08) + jsr boss_door_add_20_to_y_pos + inc ENEMY_VAR_2,x + lda ENEMY_VAR_2,x + cmp #$02 + bcc boss_door_exit + lda #$80 ; a = #$80 (delay before auto-move) + jmp set_delay_remove_enemy + +; super-tile codes for destroyed door (#$2 bytes) +boss_door_update_supertile_tbl: + .byte $08,$04 + +; pointer table for hangar boss mortar launcher (17) (#$8 * #$2 = #$10 bytes) +boss_mortar_routine_ptr_tbl: + .addr boss_mortar_routine_00 ; CPU address $b284 - offset y position, set default aim direction, set initial delay, advance routine + .addr boss_mortar_routine_01 ; CPU address $b29a - wait for auto scroll, wait for animation delay, update nametable tiles, set delay, increment frame, advance routine if firing + .addr boss_mortar_routine_02 ; CPU address $b2c3 - fire once animation delay has elapsed + .addr boss_mortar_routine_03 ; CPU address $b2ef - animate closing of mortar launcher, set routine to boss_mortar_routine_01 + .addr boss_mortar_routine_04 ; CPU address $b30f - draw destroyed nametable tiles, advance routine + .addr enemy_routine_init_explosion ; CPU address $e74b from bank 7 + .addr enemy_routine_explosion ; CPU address $e7b0 from bank 7 + .addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7 + +; offset y position, set default aim direction, set initial delay, advance routine +boss_mortar_routine_00: + lda #$04 ; a = #$04 to offset y position a little bit + jsr add_a_to_enemy_y_pos ; add #$04 to enemy y position on screen + lda #$04 ; a = #$04 + sta ENEMY_VAR_1,x ; default mortar shot direction (see mortar_shot_velocity_tbl) + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + lsr ; shift initial attack delay flag to carry + lda #$60 ; a = #$60 (initial delay) + bcc @set_delay_adv_routine ; continue with #$60 delay if ENEMY_ATTRIBUTES bit 0 is 0 + lda #$10 ; ENEMY_ATTRIBUTES bit 0 is 1, use #$10 delay + +@set_delay_adv_routine: + bne mortar_set_delay_adv_routine + +; wait for auto scroll, wait for animation delay, update nametable tiles, set delay, increment frame, advance routine if firing +boss_mortar_routine_01: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda BOSS_AUTO_SCROLL_COMPLETE ; see if boss reveal auto-scroll has completed + beq @exit ; exit if boss auto scroll hasn't completed + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne @exit ; exit if animation delay hasn't elapsed + jsr boss_mortar_update_tiles ; update nametable tiles based on enemy frame + bcs @exit ; exit if unable to update the nametable tiles + lda ENEMY_FRAME,x ; load enemy animation frame number + cmp #$02 ; see if completed opening animation and should fire + bcs @enable_adv_routine ; if completed opening, branch to enable collision detection and advance to boss_mortar_routine_02 + inc ENEMY_FRAME,x ; increment enemy animation frame number + +@exit: + rts + +; enable collision detection and advance to boss_mortar_routine_02 +@enable_adv_routine: + jsr enable_enemy_collision ; enable bullet-enemy collision and player-enemy collision checks + lda #$10 ; a = #$10 (delay between opening and attack) + sta ENEMY_ATTACK_DELAY,x ; set delay between attacks + lda #$60 ; a = #$60 (total delay for open state) + +mortar_set_delay_adv_routine: + jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a; advance enemy routine + +; fire mortar after delay, advance routine when animation delay elapsed +boss_mortar_routine_02: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + dec ENEMY_ANIMATION_DELAY,x ; open launcher timer + beq @disable_collision_adv_routine ; if timer elapsed, disable collision and go to boss_mortar_routine_03 to begin closing + dec ENEMY_ATTACK_DELAY,x ; animation delay hasn't elapsed, decrement delay between attacks + bne @exit ; exit if attack delay hasn't elapsed + lda #$0b ; a = #$0b (0b = mortar shot) + jsr generate_enemy_a ; generate #$0b enemy (mortar shot) + bne @exit ; exit if unable to generate mortar shot + lda ENEMY_VAR_1,x ; load launcher's current aim direction [#$01-#$04] + sta ENEMY_VAR_1,y ; set mortar shot initial velocities index (aim direction) + dec ENEMY_VAR_1,x ; decrement launcher's current aim direction + bne @exit ; exit if still a valid velocity index (aim dir) + lda #$04 ; wrapped around, reset aim direction to #$04 + sta ENEMY_VAR_1,x ; set next firing round's aim direction + +@exit: + rts + +@disable_collision_adv_routine: + jsr disable_enemy_collision ; prevent player enemy collision check and allow bullets to pass through enemy + lda #$01 ; a = #$01 + bne mortar_set_delay_adv_routine ; set delay and set routine to boss_mortar_routine_02 + +; animate closing of mortar launcher, set routine to boss_mortar_routine_01 +boss_mortar_routine_03: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne mortar_exit ; exit if animation delay hasn't elapsed + jsr boss_mortar_update_tiles ; update mortar nametable tiles based on ENEMY_FRAME + bcs mortar_exit ; exit if unable to update the nametable tiles + lda ENEMY_FRAME,x ; load enemy animation frame number + beq boss_mortar_set_routine_01 ; set #$a0 delay and set enemy routine to boss_mortar_routine_01 + dec ENEMY_FRAME,x ; decrement enemy animation frame number + +mortar_exit: + rts + +boss_mortar_set_routine_01: + lda #$a0 ; a = #$a0 (delay between mortar shots) + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + lda #$02 ; a = #$02 + jmp set_enemy_routine_to_a ; set enemy routine index to boss_mortar_routine_01 + +; draw destroyed nametable tiles, advance routine +boss_mortar_routine_04: + lda #$03 ; a = #$03 (8b mortar launcher - destroyed) + jsr boss_mortar_update_tiles_a ; set nametable tiles to show a destroyed mortar launcher + bcs mortar_exit ; exit if unable to update the nametable tiles + inc BOSS_SCREEN_ENEMIES_DESTROYED ; increment number of destroyed mortar launchers + ; once both are destroyed soldiers stop being generated + jmp advance_enemy_routine ; advance to enemy_routine_init_explosion + +; update nametable tiles based on ENEMY_FRAME +boss_mortar_update_tiles: + lda ENEMY_FRAME,x ; load enemy animation frame number + +; update nametable tiles based on a register +boss_mortar_update_tiles_a: + clc ; clear carry in preparation for addition + adc #$08 ; convert to real offset into level_7_tile_animation + jsr update_enemy_nametable_tiles_no_palette ; draw the nametable tiles from level_7_tile_animation (a) at the enemy position + lda #$01 ; a = #$01 + bcs @set_delay_exit ; exit if unable to update nametable tiles + lda #$08 ; a = #$08 (delay between door opening frames) + +@set_delay_exit: + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + rts + +; pointer table for hangar boss screen enemy generator door (enemy type = #$18) (#$5 * #$2 = #$a bytes) +boss_soldier_generator_routine_ptr_tbl: + .addr boss_soldier_generator_routine_00 ; CPU address $b338 - set delay to #$a0, advance routine + .addr boss_soldier_generator_routine_01 ; CPU address $b33c - animate door and, if appropriate, prep to generate soldiers before advancing the routine + .addr boss_soldier_generator_routine_02 ; CPU address $b38f - generate soldiers + .addr boss_soldier_generator_routine_03 ; CPU address $b3d0 - close door, set delay for next time to open, set routine to boss_soldier_generator_routine_01 + .addr boss_soldier_generator_routine_04 ; CPU address $b3f5 - enemy destroyed routine, remove enemy + +; set delay to #$a0, advance routine +boss_soldier_generator_routine_00: + lda #$a0 ; a = #$a0 + bne boss_soldier_generator_adv_routine ; initial delay before first attack + +; animate door and, if appropriate, prep to generate soldiers before advancing the routine +boss_soldier_generator_routine_01: + lda ENEMY_VAR_3,x ; load number of waves of soldiers generated + cmp #$1e ; see if 30 waves of soldiers have been generated + bcs @stop_soldier_gen ; branch to stop generating soldiers if 30 waves have occurred + lda BOSS_SCREEN_ENEMIES_DESTROYED ; load how many mortar launchers have been destroyed + cmp #$02 ; see if both mortar launchers have been destroyed + bcc @draw_door_check_if_gen_soldiers ; branch if at least one mortar launcher not destroyed + ; updates door tiles, and see if should generate soldiers + +; sets an animation delay so that boss_soldier_draw_door exits early and no soldier is generated +; soldiers aren't generated when there have been #$1e waves, or both mortar launchers have been destroyed +@stop_soldier_gen: + lda #$f0 ; a = #$f0 + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + +@draw_door_check_if_gen_soldiers: + jsr boss_soldier_draw_door ; draw the appropriate super-tiles for the armored door based on ENEMY_FRAME + bcs @exit ; branch if unable update the super-tiles + lda ENEMY_FRAME,x ; load enemy animation frame number + cmp #$02 ; see if last frame of animation, i.e. the door is open + bcs @init_gen_soldiers ; if open, determine from which side and how many soldiers to generate, advance routine + inc ENEMY_FRAME,x ; still opening door, increment enemy animation frame number + +@exit: + rts + +@init_gen_soldiers: + inc ENEMY_VAR_3,x ; increment number of waves of soldiers generated + ldy #$00 ; y = #$00 + lda SPRITE_X_POS ; load player 1 x position + cmp #$a0 ; see if close to the door on the right + bcs @gen_from_left ; branch if close to the door on the right + lda SPRITE_X_POS+1 ; load if player 2 x position + cmp #$a0 ; see if close to the door on the right + bcc @prep_soldiers_to_gen_adv_routine ; branch if player 2 is not close to the door on the right + +@gen_from_left: + iny ; increment facing direction so it's 1 (face right, attack from left) + +@prep_soldiers_to_gen_adv_routine: + tya ; transfer soldier attack direction (0 = left, 1 = right) to a + sta ENEMY_VAR_2,x ; set soldier facing direction (0 = left, 1 = right) + lda RANDOM_NUM ; load random number + and #$03 ; keep bits 0 and 1 + tay ; transfer to offset register + lda boss_soldier_num_soldiers_tbl,y ; load random number of soldiers to generate + sta ENEMY_VAR_1,x ; set the number of soldiers to generate + lda #$10 ; a = #$10 (delay between door open and attack) + sta ENEMY_ATTACK_DELAY,x ; set delay between attacks + lda #$80 ; a = #$80 (delay for door staying open) + +boss_soldier_generator_adv_routine: + jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a and set routine to boss_soldier_generator_routine_02 + +; table for number of enemies generated (#$4 bytes) +boss_soldier_num_soldiers_tbl: + .byte $03,$04,$02,$04 + +; generate soldiers +boss_soldier_generator_routine_02: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + beq @set_delay_adv_routine ; if door open timer has elapsed, advance routine + dec ENEMY_ATTACK_DELAY,x ; decrement delay between attacks + bne @exit + lda #$05 ; a = #$05 (05 = running man) + sta $0a ; set type of generated enemy + ldy #$00 ; y = #$00 + lda #$f8 ; a = #$f8 + jsr generate_enemy_at_pos ; generate enemy type $0a at relative position a,y + lda ENEMY_VAR_2,x ; load soldier facing direction + bne @set_soldier_attr_delay_exit ; exit if soldiers will spawn from the left (facing right) + lda ENEMY_VAR_3,x ; soldiers to come from right, load the number of waves of attack + cmp #$14 ; see if there have been 20 waves of attack + bcs @init_random_soldier_exit ; branch if there have been 20 rounds of attacks to randomize soldier spawn direction + lda BOSS_SCREEN_ENEMIES_DESTROYED ; load how many mortar launchers have been destroyed + beq @set_soldier_attr_delay_exit ; branch if no mortar launchers have been destroyed + +; soldiers will be generated from a random direction if +; there have been #$14 (20) waves of attack or if one of the two mortar launchers have been destroyed +@init_random_soldier_exit: + lda RANDOM_NUM ; load random number + lsr ; shift bit 0 to carry (!(WHY?) not sure why needed, still 50% odds) + and #$01 ; randomly decide the soldier's running direction + +@set_soldier_attr_delay_exit: + sta ENEMY_ATTRIBUTES,y ; set soldier enemy attributes (facing direction) + ; specifies which side of the screen the soldier spawns from + lda #$10 ; a = #$10 (delay between generated enemies) + dec ENEMY_VAR_1,x ; decrement number of soldiers to generate + bne @set_attack_delay_exit ; exit with #$10 attack delay if more soldiers to generate + lda #$ff ; no more soldiers to generate, exit with #$ff attack delay + +@set_attack_delay_exit: + sta ENEMY_ATTACK_DELAY,x ; set delay between attacks + +@exit: + rts + +@set_delay_adv_routine: + lda #$01 ; a = #$01 + bne boss_soldier_generator_adv_routine ; set routine to boss_soldier_generator_routine_03 + +; close door, set delay for next time to open, set routine to boss_soldier_generator_routine_01 +boss_soldier_generator_routine_03: + jsr boss_soldier_draw_door ; draw the appropriate super-tiles for the armored door based on ENEMY_FRAME + bcs @exit ; exit if unable to update the door tiles + lda ENEMY_FRAME,x ; load enemy animation frame number + beq @door_closed ; branch if door fully closed + dec ENEMY_FRAME,x ; decrement enemy animation frame number to continue closing door + +@exit: + rts + +@door_closed: + lda RANDOM_NUM ; load random number + lsr + lsr + lsr ; !(WHY?) not sure why needed if number is random + and #$03 ; keep bits .... ..xx + tay ; transfer random number to offset register + lda boos_soldier_door_open_delay_tbl,y ; load delay for opening door back up + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + lda #$02 ; a = #$02 + jmp set_enemy_routine_to_a ; set enemy routine index to boss_soldier_generator_routine_01 + +; possible delays between door openings (#$4 bytes) +boos_soldier_door_open_delay_tbl: + .byte $f0,$80,$a0,$c0 + +; enemy destroyed routine, remove enemy +boss_soldier_generator_routine_04: + jmp remove_enemy ; remove enemy + +; draw the appropriate super-tiles for the armored door based on ENEMY_FRAME +; output +; * carry flag - clear when successful, set when CPU_GRAPHICS_BUFFER is full +boss_soldier_draw_door: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne @set_carry_exit ; exit if the animation delay hasn't elapsed + lda ENEMY_FRAME,x ; load enemy animation frame number + asl ; double since each entry is #$02 bytes + tay ; transfer to offset register + lda boss_soldier_nametable_update_tbl,y ; load first nametable update super-tile index + sta $10 ; store in $10 for update_2_enemy_supertiles + lda boss_soldier_nametable_update_tbl+1,y ; load second nametable update super-tile index + ldy #$00 ; y = #$00 + jsr update_2_enemy_supertiles ; draw nametable update super-tile $10, then a at enemy position + lda #$08 ; a = #$08 + bcc @set_delay_exit + lda #$01 ; a = #$01 + +@set_delay_exit: + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + rts + +@set_carry_exit: + sec ; set carry flag + rts + +; table for door nametable update super-tile index (#$6 bytes) +; index into level_7_nametable_update_supertile_data +boss_soldier_nametable_update_tbl: + .byte $03,$12 ; closed armored door + .byte $13,$14 ; partially open armored door + .byte $07,$15 ; opened armored door + +; pointer table for alien guardian (#$d * #$2 = #$1a bytes) +alien_guardian_routine_ptr_tbl: + .addr alien_guardian_routine_00 ; CPU address $b44b - set enemy variables for super-tiles, hp and delays, advance routine + .addr alien_guardian_routine_01 ; CPU address $b47c - repeatedly open and close mouth + .addr alien_guardian_routine_02 ; CPU address $b545 - generates alien fetuses (enemy type #$11) + .addr alien_guardian_routine_03 ; CPU address $b69b - play alien guardian destroyed sound, create initial explosion + .addr alien_guardian_routine_04 ; CPU address $b6b2 - create series of explosions, each with a #$05 frame delay before next explosion + .addr alien_guardian_routine_05 ; CPU address $b572 - blank super-tiles for lower jaw + .addr alien_guardian_routine_06 ; CPU address $b5d8 - blank top jaw first call, second call will blank body portion + .addr alien_guardian_routine_07 ; CPU address $b601 - draws the destroyed alien guardian body super-tiles + .addr alien_guardian_routine_08 ; CPU address $b623 - blank more of the alien guardian body + .addr alien_guardian_routine_09 ; CPU address $b643 - destroys wall in front of alien guardian + .addr alien_guardian_routine_0a ; CPU address $b676 - remove lowest part of wall's collision and set floor for where wall was + .addr alien_guardian_routine_0b ; CPU address $bd02 - destroy all enemies + .addr remove_enemy ; CPU address $e809 from bank 7 + +; determine the game completion count multiplied by #$10 +; output +; * $07 - GAME_COMPLETION_COUNT * #$10 +set_game_completion_10x: + lda GAME_COMPLETION_COUNT ; load the number of times the game has been completed + jsr mv_low_nibble_to_high ; move low nibble of GAME_COMPLETION_COUNT to high nibble + sta $07 ; set result in $07 + rts + +; shift low nibble to high nibble, e.g. %01101100 -> %11000000 +mv_low_nibble_to_high: + asl + asl + asl + asl + rts + +; set enemy variables for super-tiles, hp, and delays, advance routine +alien_guardian_routine_00: + jsr set_guardian_and_heart_enemy_hp ; calculate and set alien guardian's ENEMY_HP + lda #$20 ; a = #$20 + sta ENEMY_VAR_4,x ; delay between mouth movements + lda #$90 ; specify animated super-tiles for alien guardian + ; #$90 -> super-tiles offset #$10, #$11 and #$12 (mouth closed) (see level_8_nametable_update_supertile_data) + sta ENEMY_VAR_1,x ; set super-tile code for drawing a closed mouth + lda #$40 ; set timer to #$40 to complete showing alien guardian with auto scroll + sta AUTO_SCROLL_TIMER_01 ; auto scroll counter + lda #$03 ; a = #$03 + jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to #$03; advance enemy routine + +; called to calculate alien guardian and heart's ENEMY_HP +; (weapon strength code * #$10) + #$37 + (completion count * #$10) +; if the result is >= #$a0, ENEMY_HP is set to #$a0 +set_guardian_and_heart_enemy_hp: + jsr set_game_completion_10x ; $07 = #$10 * GAME_COMPLETION_COUNT + lda PLAYER_WEAPON_STRENGTH ; load player's weapon strength + jsr mv_low_nibble_to_high ; move low nibble into high nibble, setting low nibble to all 0 + clc ; clear carry in preparation for addition + adc #$37 ; add #$37 to a + bcs @set_max_hp + adc $07 + bcs @set_max_hp + cmp #$a0 + bcc @set_enemy_hp + +@set_max_hp: + lda #$a0 ; a = #$a0 (max hp) + +@set_enemy_hp: + sta ENEMY_HP,x ; set enemy hp + rts + +; repeatedly open and close mouth +alien_guardian_routine_01: + lda ENEMY_X_POS,x ; load enemy x position on screen, decremented as frame scrolls right + cmp #$50 ; see if enemy is in the right 70% of the screen + bcs @continue ; branch if alien guardian is in the right 70% of the screen + ; !(WHY?) not sure of use of this check, player cannot cause alien guardian to scroll + ; this far due to wall in the way. Possibly wall wasn't originally there + jmp add_scroll_to_enemy_pos ; add scrolling to enemy position (doesn't occur in normal game play) + +@continue: + lda ENEMY_VAR_2,x ; load whether or not the drawing routine was successful for top jaw (0 = success, 1 = failure) + beq @update_nametable_adv_routine ; branch if successfully drew top jaw + jsr draw_alien_guardian_top_jaw ; update nametable to draw top 2 super-tiles specified by ENEMY_VAR_1 + +@update_nametable_adv_routine: + lda ENEMY_VAR_3,x ; load whether or not the drawing routine was successful for bottom jaw (0 = success, 1 = failure) + beq @draw_mouth_adv_routine ; branch if successfully drew bottom jaw + jsr draw_alien_guardian_lower_jaw ; update nametable to animate the bottom super-tile of the mouth + +; draw top and bottom of mouth +@draw_mouth_adv_routine: + dec ENEMY_VAR_4,x ; decrement delay alien guardian between mouth animation + bne @draw_lower_jaw_adv_routine ; branch if delay hasn't elapsed + lda #$20 ; a = #$20 + sta ENEMY_VAR_4,x ; delay between mouth movements + lda ENEMY_VAR_1,x ; load the top-right super-tile code + cmp #$90 ; see if it is super-tile code #$10 (alien guardian jaw mouth closed) + bne @draw_alien_guardian_closed_mouth ; if not, then set it to closed and draw mouth closed + lda #$92 ; a = #$92 + sta ENEMY_VAR_1,x ; super-tile code for open mouth + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + jmp @draw_alien_guardian_mouth + +@draw_alien_guardian_closed_mouth: + lda #$90 ; a = #$90 + sta ENEMY_VAR_1,x ; #$10 -> super-tile code for closed mouth + +; input +; * ENEMY_VAR_1 - #$90 for an open mouth, #$92 for a closed mouth +@draw_alien_guardian_mouth: + jsr draw_alien_guardian_top_jaw ; update nametable to draw top 2 super-tiles specified by ENEMY_VAR_1 + jsr draw_alien_guardian_lower_jaw ; update nametable to animate the bottom super-tile of the mouth + +@draw_lower_jaw_adv_routine: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter + bne @exit + jmp draw_lower_jaw_open_adv_routine ; update nametable to animate the bottom super-tile of the mouth + ; advance enemy routine to alien_guardian_routine_02 + +@exit: + rts + +; draw super-tile $08 at position ($0a - #$0e, $09 - #$10) +; input +; * $08 - super-tile code to draw +; * $09 - y position of the super-tile to draw (#$10 is subtracted from this point) +; * $0a - x position of the super-tile to draw (#$0e is subtracted rom this point) +; output +; * $0b - clear when successful, set when CPU_GRAPHICS_BUFFER is full +draw_alien_guardian_supertile: + stx ENEMY_CURRENT_SLOT ; temporarily save value of x + lda $08 ; load super-tile number to draw + sta $10 ; store in $10 for load_bank_3_update_nametable_supertile call + lda $09 ; load tile relative y position + sec ; set carry flag in preparation for subtraction + sbc #$10 ; subtract #$10 from y position + tay ; transfer to y position input for load_bank_3_update_nametable_supertile + lda $0a ; load x position into nametable + sec ; set carry flag in preparation for subtraction + sbc #$0e ; subtract #$0e + jsr load_bank_3_update_nametable_supertile ; draw super-tile $10 at position (a,y) + ldx ENEMY_CURRENT_SLOT ; restore value of x + lda #$00 ; a = #$00 + rol ; move carry result from load_bank_3_update_nametable_supertile + sta $0b ; set whether call to update nametable was successful (0 success, 1 failure) + rts + +; draws specified ENEMY_VAR_1 super-tile and its subsequent value for the top two super-tiles +; of the alien guardian mouth +draw_alien_guardian_top_jaw: + lda ENEMY_VAR_1,x ; load super-tile code for alien guardian + sta $08 ; store top-right super-tile code in $08 + clc ; clear carry in preparation for addition + adc #$01 + sta $0c ; set the top-left jaw super-tile to draw + lda #$10 ; a = #$10 + jsr set_nametable_x_pos_for_alien_guardian ; add #$10 to enemy x position and stores result in $0a + jsr draw_alien_boss_supertiles ; draw top 2 super-tiles of alien guardian jaw ($08, $0c) + lda $0b ; load whether or not the top jaw was drawn (0 = success, 1 = failure) + sta ENEMY_VAR_2,x ; set whether or not the supertile was updated for the top jaw (0 = success, 1 = failure) + rts + +; draws either a blank super-tile (mouth closed), or the lower jaw of alien guardian (mouth open) +; based on ENEMY_VAR_1 +; this is the bottom right of the 3 super-tiles that are animated for the alien guardian +draw_alien_guardian_lower_jaw: + lda ENEMY_VAR_1,x ; load the super-tile code for the top right, use to determine bottom super-tile code + cmp #$92 ; super-tile #$14 (alien guardian lower jaw mouth open) + beq @continue + lda #$81 ; super-tile #$03 (blank super-tile used to clear before drawing new super-tile) + +@continue: + clc ; clear carry in preparation for addition + adc #$02 ; add #$02 to super-tile index (level_8_nametable_update_supertile_data) + sta $08 ; set super-tile to draw + ldy #$20 ; y = #$20 + lda #$11 ; a = #$11 + jsr set_nametable_pos_for_alien_guardian ; add #$11 to enemy x position and #$20 to enemy y position. store result x in $0a, y in $09 + jsr draw_alien_guardian_supertile ; draw super-tile $08 at position ($0a - #$0e, $09 - #$10) + lda $0b ; set whether or not the supertile was updated for the bottom jaw (0 = success, 1 = failure) + sta ENEMY_VAR_3,x ; store in ENEMY_VAR_3 + rts + +; updates 2 super-tiles (#$20 tiles apart) for both alien guardian and alien heart +; draws the top 2 super-tiles of the 3 super-tiles that are animated for the alien guardian +; all super-tiles are indexes into level_8_nametable_update_supertile_data +; top left super-tiles +; * #$11 - alien guardian top teeth and lower left jaw mouth closed +; * #$13 - alien guardian top teeth mouth open +; top right super-tiles +; * #$10 - alien guardian jaw mouth closed +; * #$12 - alien guardian jaw mouth open +; input +; * $09 - relative y position of super-tiles to draw +; * $0a - relative x position of the top-right super tile to draw +; * $08 - top right super-tile to draw +; * $0c - top left super-tile to draw +; output +; * $0b - clear when successful, set when CPU_GRAPHICS_BUFFER is full +draw_alien_boss_supertiles: + lda $09 ; load relative y position of tile + sta $05 ; store in $05 as backup + lda $0a ; load relative x position of tile + sta $06 ; store in $06 as backup + lda $0c ; load top right super tile code + sta $04 ; store in $04 + jsr draw_alien_guardian_supertile ; draw top right super-tile $08 at position ($0a - #$0e, $09 - #$10) + lda $04 ; restore value of $0c (top right super-tile code) + sta $08 ; set as super-tile code to draw + lda $05 ; load saved relative y position of super-tile to draw + sta $09 ; set as relative y position of super-tile to draw + lda $06 ; load relative x position of top-left super tile to draw + sec ; set carry flag in preparation for subtraction + sbc #$20 ; subtract #$20 to get relative x position of top left super-tile + sta $0a ; store in relative x position of super-tile to draw + jmp draw_alien_guardian_supertile ; draw top left super-tile $08 at position ($0a - #$0e, $09 - #$10) + +alien_guardian_exit_00: + rts + +; draws the bottom right super-tile of the alien guardian +; sets delay to #$20 and advance enemy routine +draw_lower_jaw_open_adv_routine: + jsr draw_alien_guardian_lower_jaw ; update nametable to animate the bottom super-tile of the mouth + lda #$20 ; a = #$20 (delay between mouth open and attack) + jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a. advance enemy routine to #$03 + +; generates alien fetuses +alien_guardian_routine_02: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda ENEMY_ATTACK_FLAG ; see if enemies should attack + beq @set_delay_routine_01 ; branch if attack flag is disabled to set delay to #$03 and go to alien_guardian_routine_01 + dec ENEMY_ANIMATION_DELAY,x ; enemy attack flag set to off, decrement enemy animation frame delay counter + lda ENEMY_ANIMATION_DELAY,x + cmp #$02 ; see if ENEMY_ANIMATION_DELAY is almost elapsed + bcc @create_fetus_exit ; timer is close to elapsing, create fetus and exit (creates #$02 fetuses) + bne alien_guardian_exit_00 ; timer is not about to elapse, exit + lda PLAYER_WEAPON_STRENGTH ; timer is 2 cycles from elapsing + cmp #$03 + bcc alien_guardian_exit_00 + +@create_fetus_exit: + lda #$11 ; a = #$11 (11 = alien fetus) + jsr generate_enemy_a ; generate #$11 enemy (alien fetus) + lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter + bne alien_guardian_exit_00 + +@set_delay_routine_01: + lda #$03 ; a = #$03 (mouth movements before next attack) + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + lda #$02 ; a = #$02 + jmp set_enemy_routine_to_a ; set enemy routine to alien_guardian_routine_01 + +; blank super-tiles for lower jaw +; called by alien_guardian_routine_06 to blank out body portion above top jaw +alien_guardian_routine_05: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda #$00 ; a = #$00 (blank super-tile entry in alien_boss_supertile_tbl) + jsr update_alien_boss_supertiles ; updates 2 nametable super-tiles for alien guardian animation + lda $0b ; load whether successfully able to update the super-tiles + bne alien_guardian_exit_00 ; exit if unable to update the super-tiles + lda #$01 ; updated super-tiles, load a = #$01 + sta ENEMY_VAR_2,x ; prep variable for alien_guardian_routine_05 to draw part of alien guardian + jmp advance_enemy_routine ; advance to alien_guardian_routine_06 + +; updates 2 nametable tiles for alien heart and alien guardian animation +; input +; * a - entry in alien_boss_supertile_tbl +; output +; * $0b - clear when successful, set when CPU_GRAPHICS_BUFFER is full +update_alien_boss_supertiles: + asl + asl ; quadruple since each entry is #$04 bytes + tay ; transfer to offset register + lda alien_boss_supertile_tbl,y ; load pattern table tile code + sta $08 ; store left supertile code + lda alien_boss_supertile_tbl+1,y ; load pattern table tile code + sta $0c ; store right supertile code + lda alien_boss_supertile_tbl+2,y ; load relative y position + clc ; clear carry in preparation for addition + adc ENEMY_Y_POS,x ; add to enemy y position on screen + sta $09 ; store y position + lda alien_boss_supertile_tbl+3,y ; load relative x position + clc ; clear carry in preparation for addition + adc ENEMY_X_POS,x ; add to enemy x position on screen + sta $0a ; store x position + jmp draw_alien_boss_supertiles ; draw 2 super-tiles ($08, $0c) of alien guardian jaw or alien heart + +; tile codes for alien guardian and heart (#$c * #$4 = #$30) +; byte 0: super-tile code 1 - right +; byte 1: super-tile code 2 - left +; byte 2: relative y position +; byte 3: relative x position +alien_boss_supertile_tbl: + .byte $83,$83,$00,$10 ; #$03, #$03 - 2 blank super-tiles + .byte $95,$96,$c0,$30 ; #$15, #$16 - alien guardian body destroyed + .byte $97,$83,$e0,$50 ; #$17, #$03 - alien guardian body destroyed and blank super-tile + .byte $84,$85,$f0,$10 ; heart - frame 0 - top-right and top-left + .byte $86,$87,$10,$10 ; heart - frame 0 - bottom-right and bottom-left + .byte $88,$89,$f0,$10 ; heart - frame 1 - top-right and top-left + .byte $8a,$8b,$10,$10 ; heart - frame 1 - bottom-right and bottom-left + .byte $8c,$8d,$f0,$10 ; heart - destroyed - top-right and top-left + .byte $8e,$8f,$10,$10 ; heart - destroyed - bottom-right and bottom-left + .byte $83,$83,$20,$f0 ; #$03, #$03 - 2 blank super-tiles (used for wall in front of alien guardian) + .byte $83,$83,$40,$f0 ; #$03, #$03 - 2 blank super-tiles + .byte $29,$29,$60,$f0 ; #$29, #$29 - empty ground + +; blank top jaw first call, second call will blank body portion +alien_guardian_routine_06: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda ENEMY_VAR_2,x ; load whether or not the top jaw has been successfully blanked + beq @blank_body ; branch to blank out the body portion if already blanked out top of jaw + lda #$83 ; a = #$83 (blank super-tile from level_8_nametable_update_supertile_data) + sta $08 ; set super-tile to draw (blank super-tile) + ldy #$20 ; y = #$20 + lda #$10 ; a = #$10 + jsr set_nametable_pos_for_alien_guardian ; add #$10 to enemy x position and #$20 to enemy y position. store result x in $0a, y in $09 + jsr draw_alien_guardian_supertile ; draw super-tile $08 at position ($0a - #$0e, $09 - #$10) + lda $0b ; load whether or not the top jaw was drawn (0 = success, 1 = failure) + sta ENEMY_VAR_2,x ; store value in ENEMY_VAR_2,x so that next loop @blank_body can run if drawn successfully + ; or another attempt at drawing can happen + rts + +@blank_body: + lda #$e0 ; a = #$e0 + jsr add_a_to_enemy_y_pos ; add a to enemy y position on screen + jsr alien_guardian_routine_05 ; blank out body portion + lda #$20 ; a = #$20 + jmp add_a_to_enemy_y_pos ; add a to enemy y position on screen for alien_guardian_routine_07 + +; draws the destroyed alien guardian body super-tiles +alien_guardian_routine_07: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda ENEMY_VAR_2,x ; load whether or not the body portion of alien guardian was blanked + beq alien_guardian_destroy_body ; draw the portion of the body that is destroyed (not blanked) + lda #$01 ; a = #$01 (entry in alien_boss_supertile_tbl) - top portion of destroyed body + jsr update_alien_boss_supertiles ; updates 2 nametable super-tiles for alien guardian animation + lda $0b ; load whether or not the top destroyed body was drawn (0 = success, 1 = failure) + sta ENEMY_VAR_2,x ; store value in ENEMY_VAR_2,x so that next loop alien_guardian_routine_07 can run if drawn successfully + ; or another attempt at drawing can happen + +alien_guardian_exit_02: + rts + +alien_guardian_destroy_body: + lda #$02 ; a = #$02 (entry in alien_boss_supertile_tbl) + jsr update_alien_boss_supertiles ; updates 2 nametable super-tiles for alien guardian animation + +; advance routine if successfully drew alien guardian super-tiles +alien_guardian_adv_routine_if_drawn: + lda $0b ; load whether or not the super-tile was drawn (0 = success, 1 = failure) + bne alien_guardian_exit_02 ; exit if unable to draw super-tiles so that next frame can retry + +alien_guardian_set_draw_adv_routine: + inc ENEMY_VAR_2,x ; set flag indicating not drawn for next routine to draw + jmp advance_enemy_routine + +; blank more of the alien guardian body +alien_guardian_routine_08: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda #$83 ; a = #$83 (blank super-tile) + sta $08 ; set super-tile to draw + lda #$30 ; a = #$30 (amount to add to enemy x position) + jsr set_nametable_x_pos_for_alien_guardian ; add #$30 to enemy x position and stores result in $0a + jsr draw_alien_guardian_supertile ; draw super-tile $08 at position ($0a - #$0e, $09 - #$10) + ldy #$c0 ; y = c0 + lda #$f0 ; a = #$f0 + jsr set_nametable_pos_for_alien_guardian ; subtract #$10 from enemy x position and add #$c0 to enemy y position. store result x in $0a, y in $09 + lda #$83 ; a = #$83 (blank super-tile) + sta $08 ; set super-tile to draw + jsr draw_alien_guardian_supertile ; draw super-tile $08 at position ($0a - #$0e, $09 - #$10) + jmp alien_guardian_adv_routine_if_drawn ; advance routine if updated super-tiles, otherwise just exit to retry next frame + +; destroys wall in front of alien guardian +alien_guardian_routine_09: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda ENEMY_VAR_2,x ; load whether or not successfully updated super-tiles for current routine + beq @delete_bottom_wall_and_bg_collision + lda #$09 ; a = #$09 (#$02 blank super-tiles) - entry in alien_boss_supertile_tbl + +@delete_wall_and_bg_collision: + jsr update_alien_boss_supertiles ; updates 2 nametable super-tiles for alien guardian animation + pha ; backup a on the stack + jsr alien_guardian_clear_wall_bg_collision + pla ; restore a from the stack + sta ENEMY_VAR_2,x ; store whether or not was able to update super-tiles + rts + +@delete_bottom_wall_and_bg_collision: + lda #$0a ; a = #$0a (#$02 blank super-tiles) - entry in alien_boss_supertile_tbl + jsr @delete_wall_and_bg_collision ; draw blank super-tiles over wall, and clear bg collision + beq alien_guardian_set_draw_adv_routine ; advance routine if updated super-tiles, otherwise just exit to retry next frame + rts + +alien_guardian_clear_wall_bg_collision: + lda $12 ; backup $12 on stack + pha ; store $12 on stack for backing up + jsr clear_supertile_bg_collision ; set background collision code for wall to #$00 (empty) for super-tile at PPU address $12 (low) $13 (high) + pla ; pop $12 off of stack + clc ; clear carry in preparation for addition + adc #$04 ; add #$04 to PPU address low byte + sta $12 ; set PPU address low byte + lda $13 ; load PPU address high byte + adc #$00 ; add any carry from $12 + sta $13 ; set PPU address high byte + jmp clear_supertile_bg_collision ; set background collision code to #$00 (empty) for single super-tile at PPU address $12 (low) $13 (high) + +; remove lowest part of wall's collision and set floor for where wall was +alien_guardian_routine_0a: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda ENEMY_VAR_2,x ; load whether or not successfully updated super-tiles for current routine + beq alien_guardian_adv_routine ; advance routine if successfully drawn + lda #$0b ; a = #$0b (empty ground) (alien_boss_supertile_tbl) + ; regular ground where wall was in the way + jsr update_alien_boss_supertiles ; updates 2 nametable super-tiles for alien guardian animation + pha ; backup a on the stack + lda $12 ; load background collision code location + sec ; set carry flag in preparation for subtraction + sbc #$20 + sta $12 + lda $13 + sbc #$00 + sta $13 + jsr alien_guardian_clear_wall_bg_collision ; clear lowest part of wall's collision code + pla ; restore a on the stack + beq alien_guardian_adv_routine ; advance routine to alien_guardian_routine_0b + +alien_guardian_exit_01: + rts + +alien_guardian_adv_routine: + jmp advance_enemy_routine + +; play alien guardian destroyed sound, create initial explosion +alien_guardian_routine_03: + lda #$55 ; a = #$55 (sound_55) + jsr play_sound ; play alien guardian destroyed sound + +create_boss_heart_explosion: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + jsr clear_enemy_custom_vars ; set ENEMY_VAR_1, ENEMY_VAR_2, ENEMY_VAR_3, ENEMY_VAR_4 to zero + sta $08 ; set vertical offset from enemy position to #$00 + sta $09 ; set horizontal offset from enemy position to #$00 + jsr create_explosion_at_x_y + lda #$05 ; a = #$05 + jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to #$05 and advance enemy routine + +; alien guardian - pointer 5 +; heart - pointer 5 +; create series of explosions over screen, each with a #$05 frame delay before next explosion +alien_guardian_routine_04: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne alien_guardian_exit_01 ; exit if initial explosion from alien_guardian_routine_03 hasn't completed + lda #$05 ; a = #$05 + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + inc ENEMY_VAR_1,x ; increment explosion number (!(BUG?) #$00 isn't used) + lda ENEMY_VAR_1,x ; load explosion number + cmp #$0c ; see if drawn all explosions + beq alien_guardian_adv_routine ; advance routine if drawn all explosions + jmp @continue ; why jmp? doesn't seem necessary !(HUH) + +@continue: + asl ; double since each entry in alien_guardian_explosion_offset_tbl is #$02 bytes + tay ; transfer to offset register + lda alien_guardian_explosion_offset_tbl,y ; load x position of explosion + sta $08 ; set x position of explosion + lda alien_guardian_explosion_offset_tbl+1,y ; load y position of explosion + sta $09 ; set y position of explosion + +; input +; * y - vertical offset +; * x - horizontal offset +create_explosion_at_x_y: + ldy $08 ; set vertical offset from enemy position (param for add_with_enemy_pos) + lda $09 ; set horizontal offset from enemy position (param for add_with_enemy_pos) + jsr add_with_enemy_pos ; stores a + enemy x position in $09, and y + enemy y position in $08 + jmp create_two_explosion_89 ; create explosion #$89 at location ($09, $08) + +; pointer table for alien fetus (11) (#$5 * #$2 = #$a bytes) +alien_fetus_routine_ptr_tbl: + .addr alien_fetus_routine_00 ; CPU address $b6ec + .addr alien_fetus_routine_01 ; CPU address $b736 + .addr enemy_routine_init_explosion ; CPU address $e74b from bank 7 + .addr enemy_routine_explosion ; CPU address $e7b0 from bank 7 + .addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7 + +; alien fetus - pointer 0 +alien_fetus_routine_00: + jsr alien_fetus_get_aim_timer ; set ENEMY_VAR_3,x to the delay before re-aiming towards player's current location + asl ENEMY_VAR_3,x ; double the value + lda GAME_COMPLETION_COUNT ; load the number of times the game has been completed + clc ; clear carry in preparation for addition + adc #$02 ; add #$02 to game completion count + sta ENEMY_HP,x ; set enemy hp = completion count + #$02 + lda #$ac ; a = #$ac (sprite_ac) + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + lda #$06 ; a = #$06 + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + lda P2_GAME_OVER_STATUS ; player 2 game over state (1 = game over) + bne @calc_velocity ; branch if player 2 is in game over state + lda RANDOM_NUM ; player 2 is game over or not playing, load random number + adc FRAME_COUNTER ; add frame counter to random number + ; !(WHY?) not sure why adding to random number. it is already random + ; perhaps random number is used for something else this frame and + ; developers didn't want the same number + and #$1f ; keep bits ...x xxxx + clc ; clear carry in preparation for addition + adc #$0e ; add #$0e to random number + sta ENEMY_VAR_4,x ; set to random number [#$0e-#$2d] + lda P1_GAME_OVER_STATUS ; player 1 game over state (1 = game over) + beq @calc_velocity ; branch if player 1 not in game over state + lda #$01 ; player 1 in game over state, set ENEMY_VAR_4,x to #$01 + sta ENEMY_VAR_4,x + +@calc_velocity: + lda RANDOM_NUM ; load random number + and #$03 ; random number between #$00 and #$03 + bne @check_attr ; branch if not #$00 (3/4 probability) + lda #$03 ; random number was #$00, set to #$03 + +; 50% chance of #$03, 25% of #$01, 25% of #$02 +@check_attr: + asl ; double random number between #$00 and #$03 + ldy ENEMY_ATTRIBUTES,x ; load the enemy attributes + beq @set_velocity + lda #$06 ; a = #$06 + +@set_velocity: + sta ENEMY_VAR_1,x ; store enemy aim direction + inc ENEMY_ROUTINE,x ; advance to alien_fetus_routine_01 + jmp alien_fetus_set_velocity ; set velocity based on ENEMY_VAR_1,x + +; alien fetus - pointer 1 +alien_fetus_routine_01: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne @check_velocity + lda #$06 ; animation delay has elapsed, a = #$06 + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + ldy #$ff ; y = #$ff (-1) + lda ENEMY_VAR_1,x ; load enemy direction, #$00 = facing right incrementing clockwise, max #$0b + clc ; clear carry in preparation for addition + adc #$01 ; add one to the aim direction + cmp #$0c ; compare to #$0c (max facing direction) + bne @get_sprite_offset ; branch if not facing down + lda #$00 ; reset to facing right + +@get_sprite_offset: + sec ; set carry flag in preparation for subtraction + sbc #$03 ; subtract #$03 from facing direction + iny ; increment sprite code offset + bcs @get_sprite_offset ; loop until value of a is negative, this determines which sprite to show + tya ; transfer sprite code offset to a [#$00-#$01] + asl ; double value [#$00-#$02] + sta $08 ; set sprite code offset + lda ENEMY_VAR_2,x ; load whether or not the mouth is open (0 = closed, 1 = open) + eor #$01 ; flip bit 0 (close mouth if open, open if closed) + sta ENEMY_VAR_2,x ; set new value of whether or not the mouth is open (0 = closed, 1 = open) + lda ENEMY_SPRITE_ATTR,x ; enemy animation frame delay counter + and #$3f ; strip sprite flip flags + sta ENEMY_SPRITE_ATTR,x ; enemy animation frame delay counter + lda $08 ; load sprite code + cmp #$04 ; see if past the last sprite (sprite_af) + bcc @set_sprite ; branch if not past the last sprite + lda ENEMY_SPRITE_ATTR,x ; load sprite attributes + ora #$c0 ; set bits xx.. .... (flip sprite horizontally and vertically) + sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes + lda $08 ; load sprite code offset + sec ; set carry flag in preparation for subtraction + sbc #$04 ; subtract #$04 from offset to get back to #$00 (sprite_ac) + +@set_sprite: + clc ; clear carry in preparation for addition + adc #$ac ; add #$ac to to base sprite offset [#$00-#$02] (sprite_ac) + adc ENEMY_VAR_2,x ; add 0 or 1 depending on whether mouth is open + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + +@check_velocity: + dec ENEMY_VAR_3,x ; decrement velocity adjustment timer + bne @maintain_current_velocity ; maintain current velocity if timer not elapsed + jsr @aim_towards_player ; timer elapsed, re-aim towards player + +@maintain_current_velocity: + jmp update_enemy_pos ; apply velocities and scrolling adjust + +@aim_towards_player: + lda ENEMY_VAR_4,x + and #$3e ; keep bits ..xx xxx. + beq alien_fetus_exit + jsr alien_fetus_get_aim_timer + jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position + lda ENEMY_VAR_4,x ; load target player index control, bit 0 specifies which player to target + and #$01 ; keep bit 0 + sta $0a ; store player index in $0a + jsr aim_var_1_for_quadrant_aim_dir_00 ; determine next aim direction [#$00-#$0b] ($0c) + ; adjusts ENEMY_VAR_1 to get closer to that value using quadrant_aim_dir_00 + +alien_fetus_set_velocity: + lda ENEMY_VAR_1,x ; load calculated enemy aim direction + asl + asl ; quadruple since each entry in set_white_blob_alien_fetus_vel is #$04 bytes + tay ; transfer to offset register + jsr set_white_blob_alien_fetus_vel ; set the alien fetus velocity based on a + lda ENEMY_VAR_4,x ; load target player index control + clc ; clear carry so that #$03 is subtracted and not #$02 + sbc #$02 ; subtract #$03 from target player index control (carry is clear) + sta ENEMY_VAR_4,x ; set new value for target player index control + +alien_fetus_exit: + rts + +; input +; * y - the offset into white_blob_alien_fetus_vel_tbl +set_white_blob_alien_fetus_vel: + lda white_blob_alien_fetus_vel_tbl,y ; load y fast velocity + sta ENEMY_Y_VELOCITY_FAST,x ; set y fast velocity + lda white_blob_alien_fetus_vel_tbl+1,y ; load y fractional velocity + sta ENEMY_Y_VELOCITY_FRACT,x ; set y fractional velocity + lda white_blob_alien_fetus_vel_tbl+12,y ; load x fast velocity + sta ENEMY_X_VELOCITY_FAST,x ; set x fast velocity + lda white_blob_alien_fetus_vel_tbl+13,y ; load x fractional velocity + sta ENEMY_X_VELOCITY_FRACT,x ; set x fractional velocity + rts + +; determine how frequently to re-aim towards player +alien_fetus_get_aim_timer: + ldy ALIEN_FETUS_AIM_TIMER_INDEX ; load current + inc ALIEN_FETUS_AIM_TIMER_INDEX ; increment read offset for next round + lda alien_fetus_aim_timer_tbl,y ; load amount of time between re-aiming towards player + cmp #$ff ; see if read last byte + bne @set_aim_timer_exit ; set ENEMY_VAR_3,x and exit if not end of data stream + lda #$00 ; read past last byte, reset offset to get first byte + sta ALIEN_FETUS_AIM_TIMER_INDEX ; reset offset back to #$00 + jmp alien_fetus_get_aim_timer ; jump to read the first byte (#$16) from alien_fetus_aim_timer_tbl + +@set_aim_timer_exit: + sta ENEMY_VAR_3,x + rts + +; table for delay amount between re-aiming alien fetus toward player (#$e bytes) +alien_fetus_aim_timer_tbl: + .byte $16,$0f,$08,$13,$3a,$06,$21 + .byte $3a,$1d,$14,$12,$28,$48,$ff + +; pointer table for alien mouth (12) (#$6 * #$2 = #$c bytes) +alien_mouth_routine_ptr_tbl: + .addr alien_mouth_routine_00 ; CPU address $b802 - set mouth hp and draw open mouth super-tile + .addr alien_mouth_routine_01 ; CPU address $b81f - opens and closes while generating white blobs + .addr alien_mouth_routine_02 ; CPU address $b85e - draw destroyed mouth super-tile + .addr enemy_routine_init_explosion ; CPU address $e74b from bank 7 + .addr enemy_routine_explosion ; CPU address $e7b0 from bank 7 + .addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7 + +; set mouth hp and draw open mouth super-tile +; alien mouth hp = (#$02 * GAME_COMPLETION_COUNT) + (PLAYER_WEAPON_STRENGTH + #$03) +alien_mouth_routine_00: + lda PLAYER_WEAPON_STRENGTH ; load player's weapon strength + adc #$03 ; add #$03 to player weapon strength + sta $08 ; store PLAYER_WEAPON_STRENGTH + #$03 in $08 + lda GAME_COMPLETION_COUNT ; load the number of times the game has been completed + asl ; double the number of times the game has been completed + adc $08 ; (#$02 * GAME_COMPLETION_COUNT) + (PLAYER_WEAPON_STRENGTH + #$03) + sta ENEMY_HP,x ; set alien mouth's hp to this computed result + lda #$20 ; a = #$20 (delay after opening mouth for white blob to generate) + sta ENEMY_VAR_3,x ; set initial animation delay + lda #$01 ; a = #$01 (level_8_nametable_update_supertile_data - alien mouth (wadder) open) + jsr draw_enemy_supertile_a ; draw super-tile a at position (ENEMY_X_POS, ENEMY_Y_POS) + lda #$0a ; a = #$0a (delay before first white blob is generated) + jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a and advance enemy routine + +; opens and closes while generating white blobs +alien_mouth_routine_01: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda ENEMY_X_POS,x ; load enemy x position on screen + cmp #$20 ; stop moving/attacking past this x position + bcs @continue ; branch if the alien mouth isn't about to be scrolled off screen + rts ; exit if alien mouth is about to be scrolled off screen + +@continue: + lda ENEMY_ATTACK_FLAG ; see if enemies should attack + beq @check_supertile ; branch if enemies shouldn't attack + dec ENEMY_ANIMATION_DELAY,x ; decrement white blob spawning delay + bne @check_supertile ; branch if animation delay hasn't elapsed + lda #$13 ; enemy type #$13 = white sentient blob + jsr generate_enemy_a ; generate #$13 enemy (white sentient blob) + lda RANDOM_NUM ; load random number + and #$1f ; load random number between #$00 and #$1f + adc #$c0 ; add #$c0 to random number + sta ENEMY_ANIMATION_DELAY,x ; set delay before next white sentient blob will be spawned + +@check_supertile: + dec ENEMY_VAR_3,x ; decrement nametable update timer + bne alien_mouth_exit ; exit if the super-tile shouldn't be changed + lda ENEMY_VAR_4,x ; load which super-tile to draw (#$00 = alien mouth closed, #$01 = alien mouth open) + and #$01 ; keep bit 0 + clc ; clear carry in preparation for addition + adc #$00 ; !(HUH) carry is explicitly clear, this line of code doesn't do anything + jsr draw_enemy_supertile_a ; draw super-tile a at position (ENEMY_X_POS, ENEMY_Y_POS) + lda #$01 ; a = #$01 + bcs @set_anim_delay_exit ; branch if unable to draw super-tile to retry next frame + inc ENEMY_VAR_4,x ; successfully drew super-tile, set next super-tile + ; (only bit 0 matters and that alternates between #$00 and #$01) + lda #$20 ; a = #$20 (delay between mouth open/closed) + +@set_anim_delay_exit: + sta ENEMY_VAR_3,x ; set nametable update timer to #$20 + +alien_mouth_exit: + rts + +; draw destroyed mouth super-tile +alien_mouth_routine_02: + lda #$02 ; level_8_nametable_update_supertile_data - #$02 - alien mouth (wadder) destroyed + jsr draw_enemy_supertile_a ; draw super-tile a at position (ENEMY_X_POS, ENEMY_Y_POS) + bcs alien_mouth_exit ; exit if unable to drwa destoryed mouht to try again next frame + jmp advance_enemy_routine ; advance to next routine + +; pointer table for white sentient blob (13) (#$6 * #$2 = #$c bytes) +white_blob_routine_ptr_tbl: + .addr white_blob_routine_00 ; CPU address $b874 - find player to target + .addr white_blob_routine_01 ; CPU address $b8b3 - float down until the blob 'gains sentience' and begins to target/hone in on the player + .addr white_blob_routine_02 ; CPU address $b940 - targets player and rushes towards them at 3x speed, retargeting every so often + .addr enemy_routine_init_explosion ; CPU address $e74b from bank 7 + .addr enemy_routine_explosion ; CPU address $e7b0 from bank 7 + .addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7 + +; find player to target +white_blob_routine_00: + lda RANDOM_NUM ; load random number + and #$1f ; keep bits ...x xxxx + adc #$50 ; add #$50 + sta ENEMY_VAR_2,x ; delay before sentience starts, e.g. before the white blob pauses + ; then targets player quickly [#$50-#$6f] + lda #$c0 ; a = #$c0 + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + lda P2_GAME_OVER_STATUS ; player 2 game over state (1 = game over) + bne @continue ; branch if player 2 is game over + lda RANDOM_NUM ; load random number + adc FRAME_COUNTER ; add frame counter to random number + and #$01 ; keep bit 0 + sta ENEMY_VAR_3,x ; store which player should be targeted + lda P1_GAME_OVER_STATUS ; player 1 game over state (1 = game over) + beq @continue ; branch if player 1 is not in game over + lda #$01 ; player 1 game over, have blob target player 2 + sta ENEMY_VAR_3,x ; set to target player 2 + +@continue: + lda #$b0 ; a = #$b0 (sprite_b0 - poisonous insect gel) + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + lda ENEMY_VAR_3,x ; load which player to target + sta $0a ; store player to target index in $0a + jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position + jsr get_rotate_01 ; get enemy aim direction and rotation direction using quadrant_aim_dir_01 + lda $0c ; load new aim direction + sta ENEMY_VAR_1,x ; store the aim direction in ENEMY_VAR_1 + inc ENEMY_ROUTINE,x ; enemy routine index to white_blob_routine_01 + jmp white_blob_init_velocity ; initialize the x and y velocities + +; float down until the blob 'gains sentience' and begins to target/hone in on the player +white_blob_routine_01: + lda #$b0 ; load base sprite offset + ; white blob's use sprites sprite_b0, sprite_b1, sprite_b2 + jsr white_blob_spider_set_sprite ; check if ENEMY_ANIMATION_DELAY elapsed, and if so update the sprite + jsr update_enemy_pos ; apply velocities and scrolling adjust + lda ENEMY_VAR_4,x ; see if ENEMY_VAR_2,x (freeze delay) has elapsed, once ENEMY_VAR_2,x elapses ENEMY_VAR_4,x is set + bne white_blob_freeze ; branch if freeze delay has elapsed to freeze and advance to white_blob_routine_02 + jsr blob_spider_ld_delay_timer ; set a to the timer portion of ENEMY_ANIMATION_DELAY (high nibble), e.g. #$74 -> #$07 + ; loops from #$08 to #$01 + cmp #$08 ; see if sprite has just changed and timer has reset + bne @dec_delay ; skip adjusting the velocity if sprite just changed + jsr @adjust_velocity ; ajust the velocity based on the aiming direction + +@dec_delay: + dec ENEMY_VAR_2,x ; decrement delay before advancing to white_blob_routine_02 + beq white_blob_set_freeze_length ; branch if timer has elapsed to determine freeze delay before attacking + rts + +@adjust_velocity: + ldy ENEMY_VAR_1,x ; load the aim direction + lda ENEMY_Y_VELOCITY_FRACT,x ; load the fractional y velocity + clc ; clear carry in preparation for addition + adc white_blob_y_vel_adj_tbl,y ; add velocity adjument amount per frame based on aim direction + sta ENEMY_Y_VELOCITY_FRACT,x ; set new y fractional velocity + lda ENEMY_X_VELOCITY_FRACT,x ; load fractional x velocity + clc ; clear carry in preparation for addition + adc white_blob_x_vel_adj_tbl,y ; add x velocity adjument amount per frame based on aim direction + sta ENEMY_X_VELOCITY_FRACT,x ; set new x fractional velocity + rts + +white_blob_init_velocity: + lda ENEMY_VAR_1,x ; load enemy aim direction + asl ; double value since each entry in white_blob_alien_fetus_vel_tbl is #$02 bytes + tay ; transfer to offset register + jmp set_white_blob_alien_fetus_vel ; set the x and y velocities + +; set a to the timer portion of ENEMY_ANIMATION_DELAY (high nibble), e.g. #$74 -> #$07 +blob_spider_ld_delay_timer: + lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter + lsr + lsr + lsr + lsr ; transfer high nibble to low nibble + rts + +; table for amount to add to y fractional velocity each frame based on aim rotation direction [#$00-#$16] (#$6 bytes) +; spills over into next table +white_blob_y_vel_adj_tbl: + .byte $00,$fd,$fa,$f8,$f6,$f5 + +; table for amount to add to x fractional velocity each frame based on aim rotation direction [#$00-#$16] (#$6 bytes) +; spills over into next table +white_blob_x_vel_adj_tbl: + .byte $f4,$f5,$f6,$f8,$fa,$fd + .byte $00,$03,$06,$08,$0a,$0b + .byte $0c,$0b,$0a,$08,$06,$03 + .byte $00,$fd,$fa,$f8,$f6,$f5 + +; randomly chooses a freeze duration of #$02 or #$20 frames +white_blob_set_freeze_length: + lda RANDOM_NUM ; load random number + and #$20 ; keep bit 5 + clc ; clear carry in preparation for addition + adc #$02 ; add #$02 + sta ENEMY_VAR_4,x ; store either #$02 or #$22 as the 'freeze' duration before attacking + +white_blob_exit: + rts + +; freezes white blob and quickly target player within 4 directions and advance to white_blob_routine_02 +; sets ENEMY_VAR_2,x to #$08 to freeze white blob for additional #$08 frames +white_blob_freeze: + jsr set_enemy_velocity_to_0 ; set x/y velocities to zero + dec ENEMY_VAR_4,x ; decrement freeze timer + bne white_blob_exit ; exit if freeze timer hasn't elapsed + lda #$08 ; freeze timer elapsed, target player with a #$08 frame delay + sta ENEMY_VAR_4,x ; set value for use in white_blob_routine_02 + sta ENEMY_VAR_2,x ; set amount of additional frames to freeze blob for before attacking + inc ENEMY_ROUTINE,x ; advance to white_blob_routine_02 + jsr white_blob_aim_to_player ; hone towards player by calling #$04 times + jsr white_blob_aim_to_player + jsr white_blob_aim_to_player + jmp white_blob_aim_to_player + +; targets player and rushes towards them at 3x speed, retargeting every so often +white_blob_routine_02: + lda #$b0 ; set base sprite code offset for alien spiders (sprite_b0, sprite_b1, sprite_b2) + jsr white_blob_spider_set_sprite ; check if ENEMY_ANIMATION_DELAY elapsed, and if so update the sprite + lda ENEMY_VAR_4,x ; load initial value of the velocity/direction update pause timer + beq @update_enemy_pos ; !(WHY?) not sure the real reason for this check, ENEMY_VAR_4, starts at #$08 and increments by #$02 + dec ENEMY_VAR_2,x ; decrement velocity/direction update pause timer + beq @update_velocity ; branch if velocity/direction update pause timer elapsed to adjust velocity + jmp @update_enemy_pos ; additional freeze timer hasn't elapsed + +; targets player and updates velocity to be 3x the standard velocities +@update_velocity: + inc ENEMY_VAR_4,x ; add #$01 to ENEMY_VAR_4 + inc ENEMY_VAR_4,x ; add #$01 to ENEMY_VAR_4 + lda ENEMY_VAR_4,x ; re-load ENEMY_VAR_4,x, this is the new velocity/direction update pause timer + sta ENEMY_VAR_2,x ; set velocity/direction update pause timer value for new velocity that is about to be calculated + jsr white_blob_aim_to_player ; aim towards player one increment towards correct direction + lda ENEMY_VAR_1,x ; load aim direction [#$00-#$16] + asl ; double since each entry is #$02 bytes + tay ; transfer to offset register + lda white_blob_alien_fetus_vel_tbl+3,y ; load fractional y velocity to multiply by #$03 + sta $08 ; set fractional y velocity before multiplication + lda white_blob_alien_fetus_vel_tbl+2,y ; load fast y velocity to multiply by #$03 + jsr mult_velocity_by_3 ; multiply velocity by #$03 (a = fast velocity, $08 = fractional velocity) + sta ENEMY_Y_VELOCITY_FAST,x ; set y fast velocity (sped up 3x from table value) + lda $08 ; load new fractional y velocity + sta ENEMY_Y_VELOCITY_FRACT,x ; set y fractional velocity (sped up 3x from table value) + lda white_blob_alien_fetus_vel_tbl+9,y ; load fractional x velocity to multiply by #$03 + sta $08 ; set fractional x velocity before multiplication + lda white_blob_alien_fetus_vel_tbl+8,y ; load fast x velocity to multiply by #$03 + jsr mult_velocity_by_3 ; multiply velocity by #$03 (a = fast velocity, $08 = fractional velocity) + sta ENEMY_X_VELOCITY_FAST,x ; set x fast velocity (sped up 3x from table value) + lda $08 ; load new fractional x velocity + sta ENEMY_X_VELOCITY_FRACT,x ; set x fractional velocity (sped up 3x from table value) + +@update_enemy_pos: + jmp update_enemy_pos ; apply velocities and scrolling adjust + +; multiply loaded velocity by 3x +; e.g. fast vel: #$ff, fractional vel: #$23 (-.863) becomes fast vel: #$fd, fractional vel: #$69 (-2.589) +; input +; * a - fast velocity component (either #$00 or #$ff) +; * $08 - fractional velocity component +; output +; * a - sped up fast velocity value +; * $08 - sped up fractional velocity value +mult_velocity_by_3: + sta $09 ; initialize $09 to fast velocity component + sta $0a ; initialize $0a to fast velocity component + lda $08 ; load fractional velocity value + asl ; double fractional component + rol $09 ; move any carry from addition to $09 while also doubling $09 + clc ; clear carry in preparation for addition + adc $08 ; add shifted-left $08 to its original value, i.e. 2 * $08 + $08 -> #$03 * $08 + sta $08 ; store new value in $08 + lda $09 ; load doubled fast velocity component and any overflow from the fractional velocity doubling + adc $0a ; add to original fast velocity (along with any additional overflow when getting the tripled fractional velocity) + rts + +; aim towards player one increment towards correct direction +white_blob_aim_to_player: + jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position + lda ENEMY_VAR_3,x ; load which player to attack + sta $0a ; store value in $0a + jmp aim_var_1_for_quadrant_aim_dir_01 ; determine next aim direction [#$00-#$0b] ($0c), adjusts ENEMY_VAR_1 to get closer to that value using quadrant_aim_dir_01 + +; alien spider and white blob +; check if ENEMY_ANIMATION_DELAY has elapsed, and if so update the sprite +; ENEMY_ANIMATION_DELAY timer is just high nibble portion, low nibble is for knowing which sprite to show next +; input +; * a - base sprite offset to be added to that is specific to enemy type +white_blob_spider_set_sprite: + sta $08 ; set enemy type's base sprite offset + lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter + ; preparing to move high nibble (timer portion) to the low nibble, e.g. #$73 -> #$07 + and #$0f ; keep low nibble + tay ; transfer sprite index to offset register + jsr blob_spider_ld_delay_timer ; set a to the timer portion of ENEMY_ANIMATION_DELAY (high nibble), e.g. #$74 -> #$07 + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter to be just the high nibble moved to low nibble + dec ENEMY_ANIMATION_DELAY,x ; decrement timer portion of ENEMY_ANIMATION_DELAY + bne @set_delay_and_sprite_index ; branch to skip changing sprite, if the animation delay hasn't elapsed + +@get_sprite_offset: + lda white_blob_spider_sprite_tbl,y ; load offset from $08 to determine sprite code + cmp #$ff ; see if reached end of table data + bne @set_sprite_and_delay ; branch if haven't reached end of table data, otherwise point to first entry in table + ldy #$00 ; reset sprite offset index to first entry + jmp @get_sprite_offset ; jump to load the first table entry's sprite offset value + +@set_sprite_and_delay: + iny ; increment sprite index for next animation + clc ; clear carry in preparation for addition + adc $08 ; add enemy type-specific sprite base offset + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + lda #$08 ; reset enemy animation frame delay counter to #$08 + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter (#$08 frames until sprite changes) + +; moves low nibble of ENEMY_ANIMATION_DELAY into high nibble (animation timer) +; then adds y to result, setting the sprite index for the next frame +@set_delay_and_sprite_index: + lda ENEMY_ANIMATION_DELAY,x ; load the timer portion of ENEMY_ANIMATION_DELAY + jsr mv_low_nibble_to_high ; move low nibble (timer nibble) into high nibble, setting low nibble to all 0 + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + tya ; transfer the next white_blob_spider_sprite_tbl index to a + clc ; clear carry in preparation for addition + adc ENEMY_ANIMATION_DELAY,x ; set the low nibble to the sprite index + sta ENEMY_ANIMATION_DELAY,x ; set full ENEMY_ANIMATION_DELAY with timer (high nibble) and index (low nibble) + rts + +; alien spider and white blob sprite code offsets (#$5 bytes) +; white blob - sprite_b0, sprite_b1, sprite_b2, sprite_b1 +white_blob_spider_sprite_tbl: + .byte $00,$01,$02,$01,$ff + +; white blob velocities based on aim rotation direction (#$c bytes) +white_blob_alien_fetus_vel_tbl: + .byte $00,$00 + .byte $00,$42 ; aim rotation dir - #$00 - facing right + .byte $00,$7f ; aim rotation dir - #$01 + .byte $00,$b2 + .byte $00,$dd + .byte $00,$f7 + .byte $00,$ff + .byte $00,$f7 + .byte $00,$dd + .byte $00,$b2 + .byte $00,$7f + .byte $00,$42 + .byte $00,$00 + .byte $ff,$be + .byte $ff,$81 + .byte $ff,$4e + .byte $ff,$23 + .byte $ff,$09 + .byte $ff,$01 + .byte $ff,$09 + .byte $ff,$23 + .byte $ff,$4e + .byte $ff,$81 + .byte $ff,$be + .byte $00,$00 + .byte $00,$42 + .byte $00,$7f + .byte $00,$b2 + .byte $00,$dd + .byte $00,$f7 + +; pointer table for alien spider (14) (#$8 * #$2 = #$10 bytes) +alien_spider_routine_ptr_tbl: + .addr alien_spider_routine_00 ; CPU address $ba3b - set spider hp, velocity, set whether will jump, advance routine to alien_spider_routine_03 + .addr alien_spider_routine_01 ; CPU address $bb44 - set score and collision code, set sprite to egg, set x velocity to -.3125 and y velocity to +-4, advance to alien_spider_routine_02 + .addr alien_spider_routine_02 ; CPU address $bb68 - alien spider while it's still an egg, float to ceiling or fall to ground, once close spawn from egg + .addr alien_spider_routine_03 ; CPU address $ba8c - alien spider is on the ground/ceiling, or just landing on the ground/ceiling + .addr alien_spider_routine_04 ; CPU address $bb2a - spider is jumping, check for groud/ceiling collision and if collided, go back to alien_spider_routine_03 + .addr enemy_routine_init_explosion ; CPU address $e74b from bank 7 + .addr enemy_routine_explosion ; CPU address $e7b0 from bank 7 + .addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7 + +; alien spider - pointer 1 +; spider is on the ground from spawn as generated by level enemies, not by heart boss +alien_spider_routine_00: + jsr set_alien_spider_hp_sprite_attr ; calculate alien spider hp and other variables + +; set x velocity to -2.5, set y velocity to 0, choose player to target +; determine whether spider will jump, and set routine to alien_spider_routine_03 +alien_spider_set_ground_vel_and_routine: + jsr clear_enemy_custom_vars ; set ENEMY_VAR_1, ENEMY_VAR_2, ENEMY_VAR_3, ENEMY_VAR_4 to zero + lda #$b3 ; a = #$b3 (sprite_b3) + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer (sprite_b3) + lda #$fe ; a = #$fe + sta ENEMY_X_VELOCITY_FAST,x ; set fast x velocity of alien spider to -2 + lda #$80 ; a = #$80 + sta ENEMY_X_VELOCITY_FRACT,x ; set enemy fractional velocity to .5 + jsr set_enemy_y_velocity_to_0 ; set y velocity to zero + lda P2_GAME_OVER_STATUS ; player 2 game over state (1 = game over or player 2 not playing) + bne @set_jump_flag_routine_03 ; branch if player 2 game over or not playing + lda RANDOM_NUM ; player 2 playing, load random number + adc FRAME_COUNTER ; add frame counter to random number + and #$01 ; keep bit 0 + sta ENEMY_VAR_1,x ; store player to attack (0 = player 1, 1 = player 2) + +; determine whether or not the alien spider will jump randomly and then set routine to alien_spider_routine_03 +@set_jump_flag_routine_03: + lda RANDOM_NUM ; load random number + lsr ; shift bit 0 to carry + adc FRAME_COUNTER ; add frame counter + and #$02 ; keep bit 1 + sta ENEMY_VAR_2,x ; store whether spider will jump (#$00 = will not jump, #$02 = will jump) + lda #$04 ; a = #$04 + jmp set_enemy_routine_to_a ; set routine to alien_spider_routine_03 + +; alien spider hp = weapon strength + completion count + 1 +set_alien_spider_hp_sprite_attr: + lda PLAYER_WEAPON_STRENGTH ; load player weapon strength + adc GAME_COMPLETION_COUNT ; add with the number of times the game has been completed + adc #$01 ; add #$01 + sta ENEMY_HP,x ; set enemy hp (weapon strength + completion count + 1) + lda #$60 ; a = #$60 + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + lda ENEMY_Y_POS,x ; load enemy y position on screen + cmp #$80 ; see if alien spider in the bottom half of the screen + lda #$00 ; a = #$00 (no horizontal flip, no vertical flip) + bcs @set_attr_exit ; branch if in the bottom half of the screen + lda #$80 ; a = #$80 (vertical flip, no horizontal flip) + +@set_attr_exit: + sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes + rts + +; alien spider is on the ground/ceiling, or just landing on the ground/ceiling +alien_spider_routine_03: + lda #$b3 ; set base sprite code offset for alien spiders (sprite_b3, sprite_b4, sprite_b5) + jsr white_blob_spider_set_sprite ; check if ENEMY_ANIMATION_DELAY elapsed, and if so update the sprite + lda ENEMY_VAR_3,x ; load spider y fast velocity + bne @walk_on_ground_ceiling ; branch if spider has a y velocity, indicating it has jumped from ceiling and landed on ground + ; or jumped from ground and landed on ceiling + lda ENEMY_VAR_2,x ; load whether or not the spider should jump + beq @walk_on_ground_ceiling ; branch if spider isn't jumping + lda ENEMY_VAR_1,x ; spider should jump, load targeted player (selected at random) + tay ; transfer targeted player index to y + lda SPRITE_Y_POS,y ; load targeted player y position on screen + cmp #$20 ; see if towards the top 12.5% of the screen + bcc @walk_on_ground_ceiling ; don't jump if player is so high + lda ENEMY_X_POS,x ; load enemy x position on screen + sec ; set carry flag in preparation for subtraction + sbc SPRITE_X_POS,y ; subtract targeted player x position from enemy x position + cmp #$30 ; distance for initiating jump + bcs @walk_on_ground_ceiling ; branch if player is too far to not jump yet + lda ENEMY_Y_POS,x ; spider should jump, load enemy y position on screen + cmp SPRITE_Y_POS,y ; player y position on screen + bcs @jump_from_ground ; branch if targeted player is above or at same level as spider + ; to jump if player is within #$20 pixels + lda #$02 ; alien spider y velocity while descending to ground from ceiling + sta ENEMY_Y_VELOCITY_FAST,x ; set y fast velocity to 2 + lda #$40 ; a = #$40 + sta ENEMY_Y_VELOCITY_FRACT,x ; set y fractional velocity to .25 + ; new y velocity is 2.25 + lda PLAYER_WEAPON_STRENGTH ; load player weapon strength + jsr mv_low_nibble_to_high ; move low nibble into high nibble, setting low nibble to all 0 + adc ENEMY_Y_VELOCITY_FRACT,x ; add weapon strength * 10 to y velocity + sta ENEMY_Y_VELOCITY_FRACT,x ; set new y fractional velocity, more powerful the weapon, the faster the y velocity + lda ENEMY_Y_VELOCITY_FAST,x ; load spider y fast velocity + adc #$00 ; add any overflow from fractional y velocity + sta ENEMY_Y_VELOCITY_FAST,x ; set new spider y fast velocity + jmp @set_vars_for_jump ; set sprite, velocity, has jumped flag, and set routine to alien_spider_routine_04 + +@jump_from_ground: + lda ENEMY_X_POS,x ; load enemy x position on screen + sec ; set carry flag in preparation for subtraction + sbc SPRITE_X_POS,y ; subtract sprite y's x position from enemy x position + cmp #$20 ; distance for initiating take off when jumping from ground + ; (compare to #$30 when descending from ceiling) + bcs @walk_on_ground_ceiling ; branch to continue alien spider crawling on ground if not close enough + lda #$ff ; a = #$ff + sta ENEMY_Y_VELOCITY_FAST,x ; set alien spider y velocity to -1 + lda #$00 ; a = #$00 + sta ENEMY_Y_VELOCITY_FRACT,x ; set alien spider y fractional velocity to 0 + +; set sprite, velocity, has jumped flag, and set routine to alien_spider_routine_04 +@set_vars_for_jump: + lda #$b3 ; a = #$b3 + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + lda ENEMY_SPRITE_ATTR,x ; load enemy sprite attributes + and #$3f ; keep bits ..xx xxxx (no flip) + sta ENEMY_SPRITE_ATTR,x ; set enemy sprite attributes + lda ENEMY_Y_VELOCITY_FAST,x + bmi @set_jump_adv_routine + lda #$ff ; a = #$ff + sta ENEMY_X_VELOCITY_FAST,x + lda #$80 ; a = #$80 + sta ENEMY_X_VELOCITY_FRACT,x + +@set_jump_adv_routine: + inc ENEMY_VAR_3,x ; set spider to jump + inc ENEMY_ROUTINE,x ; move to enemy routine index to alien_spider_routine_04 + jmp update_enemy_pos ; apply velocities and scrolling adjust + +; spider is landing on ground or ceiling, set y position and stop y velocity +@walk_on_ground_ceiling: + lda #$b8 ; a = #$b8 (spider on ground y position) + cmp ENEMY_Y_POS,x ; compare #$b8 with enemy y position on screen + bcc @continue ; branch to continue if higher than #$b8 on screen (smaller y) + lda #$38 ; spider higher than #$b8, set a = #$38 (spider on ceiling y position) + cmp ENEMY_Y_POS,x ; compare #$38 to enemy y position on screen + bcc @update_pos_stop_y ; branch if spider's y position is greather than #$38 (below ceiling) + +; spider's y position is either +; 1. higher than #$b8 (above the ground) +; 2. higher than #$38 (below ceiling) +@continue: + sta ENEMY_Y_POS,x ; enemy y position on screen + +@update_pos_stop_y: + jsr update_enemy_pos ; apply velocities and scrolling adjust + jmp set_enemy_y_velocity_to_0 ; set y velocity to zero + +; spider is jumping, check for groud/ceiling collision and if collided, go back to alien_spider_routine_03 +alien_spider_routine_04: + jsr init_vars_get_enemy_bg_collision ; initialize required memory and call get_enemy_bg_collision to determine bg collision + bcc @update_pos ; branch if no collision with the ground + jsr set_enemy_y_velocity_to_0 ; collided with ground or ceiling, set y velocity to zero + lda #$fe ; a = #$fe, alien spider x velocity after landing (-2) + sta ENEMY_X_VELOCITY_FAST,x ; set enemy fast velocity + lda #$80 ; a = #$80 (.5) + sta ENEMY_X_VELOCITY_FRACT,x ; set enemy fractional velocity + lda #$04 ; a = #$04 + sta ENEMY_ROUTINE,x ; set routine back to alien_spider_routine_03 + +@update_pos: + jmp update_enemy_pos ; apply velocities and scrolling adjust + +; alien spider - pointer 2 +; set score and collision code, set sprite to egg, set x velocity to -.3125 and y velocity to +-4, advance to alien_spider_routine_02 +alien_spider_routine_01: + lda #$33 ; a = #$33 + sta ENEMY_SCORE_COLLISION,x ; set score code to 3 (500 points), collision code to 3 + jsr set_alien_spider_hp_sprite_attr ; alien spider x/y velocities when out of spider spawn + lda #$b6 ; a = #$b6 (sprite_b6 - alien egg) + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + lda #$ff ; a = #$ff + sta ENEMY_X_VELOCITY_FAST,x ; set fast x velocity to -1 + lda #$b0 ; a = #$b0 + sta ENEMY_X_VELOCITY_FRACT,x ; set fractional velocity to + ; x velocity result in decimal is -0.3125 + lda #$fc ; a = #$fc + sta ENEMY_VAR_3,x ; enemy y fast velocity (-4) + lda #$00 ; a = #$00 + sta ENEMY_VAR_4,x ; enemy y fractional velocity + jmp advance_enemy_routine ; advance to alien_spider_routine_02 + +; alien spider while it's still an egg, float to ceiling or fall to ground, once close spawn from egg +alien_spider_routine_02: + lda ENEMY_VAR_4,x ; load y fractional velocity + clc ; clear carry in preparation for addition + adc #$28 ; add gravity when out of spider spawn + sta ENEMY_VAR_4,x ; add #$28 to y fractional velocity + lda ENEMY_VAR_3,x ; load y fast velocity + adc #$00 ; add any overflow from y velocity accumulator + sta ENEMY_VAR_3,x ; update y fast velocity + lda ENEMY_Y_POS,x ; load enemy y position on screen + cmp #$80 ; compare to midway down the screen + bcc @float_to_ceiling ; branch if above the top of the screen to get 'sucked' up to the ceiling + lda ENEMY_VAR_3,x ; below the middle if the screen, gravity pulls spider down, load y fast velocity + sta ENEMY_Y_VELOCITY_FAST,x ; set y fast velocity + lda ENEMY_VAR_4,x ; load updated fractional velocity + sta ENEMY_Y_VELOCITY_FRACT,x ; set y fractional velocity + jmp @check_if_spawn ; jump to apply velocity and see if should 'spawn' from egg + +@float_to_ceiling: + lda ENEMY_VAR_3,x ; load y fast velocity + eor #$ff ; flip all bits + sta ENEMY_Y_VELOCITY_FAST,x ; set y fast velocity + lda #$00 ; a = #$00 + sec ; set carry flag in preparation for subtraction + sbc ENEMY_VAR_4,x ; subtract y fractional velocity (inverted gravity applied) + sta ENEMY_Y_VELOCITY_FRACT,x ; set new y fractional velocity + +@check_if_spawn: + jsr update_enemy_pos ; apply velocities and scrolling adjust + lda ENEMY_Y_POS,x ; load enemy y position on screen + cmp #$c1 ; see if close to the ceiling + bcs @spawn_spider_from_egg ; spawn from egg if close to ceiling + cmp #$30 ; see if close to ground + bcc @spawn_spider_from_egg ; spawn from egg if close to ground + rts ; exit if not near ceiling nor ground + +@spawn_spider_from_egg: + lda #$b3 ; a = #$b3 (sprite_b3) boss alien bugger insect/spider (frame 1) + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + jmp alien_spider_set_ground_vel_and_routine ; set x velocity to -2.5, set y velocity to 0, choose player to target + ; determine whether spider will jump, and set routine to alien_spider_routine_03 + +; pointer table for spider spawn (15) (#$6 * #$2 = #$c bytes) +alien_spider_spawn_routine_ptr_tbl: + .addr alien_spider_spawn_routine_00 ; CPU address $bbc3 - set spider spawn hp and nametable update supertile index + .addr alien_spider_spawn_routine_01 ; CPU address $bbf0 - cycle animating the opening of the flower and the generation of alien spiders + .addr alien_spider_spawn_routine_02 ; CPU address $bc6d - destroyed routine, draw destroyed super-tile and advance routine + .addr enemy_routine_init_explosion ; CPU address $e74b from bank 7 + .addr enemy_routine_explosion ; CPU address $e7b0 from bank 7 + .addr enemy_routine_remove_enemy ; CPU address $e806 from bank 7 + +; set spider spawn hp and nametable update supertile index +; spider spawn hp = (completion count * 2) + *weapon strength * 2) + #$18 +alien_spider_spawn_routine_00: + lda GAME_COMPLETION_COUNT ; load the number of times the game has been completed + asl ; double the number of times the game has been completed + sta $08 ; store result in $08 + lda PLAYER_WEAPON_STRENGTH ; load player's weapon strength + asl ; double player's weapon strength + adc $08 ; add to doubled game completion count + adc #$18 ; add a base hp of #$18 + sta ENEMY_HP,x ; set enemy hp (#$18 + (2 * GAME_COMPLETION_COUNT) + (2 * PLAYER_WEAPON_STRENGTH)) + lda RANDOM_NUM ; load random number + adc FRAME_COUNTER ; add frame counter + and #$3f ; keep bits ..xx xxxx + adc #$a0 ; add #$a0 + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation delay before spider spawn opens and starts spawning enemies + ; random between 0 and #$3f + #$a0 + ldy #$a5 ; #$25 - alien spider spawn on ground closed (frame 1) (see level_8_nametable_update_supertile_data) + lda ENEMY_Y_POS,x ; load enemy y position on screen + cmp #$80 ; compare to the middle of the screen + bcs @continue ; branch if towards the ground + ldy #$a1 ; #$21 - alien spider spawn on ceiling closed (frame 1) (see level_8_nametable_update_supertile_data) + +@continue: + tya ; transfer to offset register + sta ENEMY_VAR_1,x ; store nametable tile update supertile index (see level_8_nametable_update_supertile_data) + inc ENEMY_ROUTINE,x ; enemy routine index (0 = empty enemy slot) + rts + +; cycle animating the opening of the flower and the generation of alien spiders +alien_spider_spawn_routine_01: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda ENEMY_VAR_4,x ; load delay between generating spiders + bne @check_frame_gen_spider ; branch if spider generation delay not elapsed to advance the animation frame if animation delay elapsed + ; and possibly generate spider + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + lda ENEMY_ANIMATION_DELAY,x ; load frame animation delay + beq @gen_spider ; branch if delay has elasped, can now generate a spider + cmp #$30 ; compare delay to #$30 (animation happens at #$30 and #$10) + beq @adv_frame ; move to next super-tile in the animation (closed -> partially open -> open) + cmp #$10 ; compare delay to #$10 (animation happens at #$30 and #$10) + beq @adv_frame ; move to next super-tile in the animation (closed -> partially open -> open) + rts + +@gen_spider: + lda ENEMY_ATTACK_FLAG ; see if enemies should attack (1 = yes, 0 = no) + beq @continue ; branch if shouldn't attack + lda #$14 ; a = #$14 (14 = alien spider) + jsr generate_enemy_a ; generate #$14 enemy (alien spider) + bne @continue ; branch if unable to generate alien spider + lda #$02 ; generated alien spider, a = #$02 + sta ENEMY_ROUTINE,y ; set newly created alien spider's enemy routine index to alien_spider_routine_01 + ; this skips alien_spider_routine_00, which sets initial ground/ceiling velocities + +; determine delay between generating spiders based on weapon code +@continue: + lda P1_CURRENT_WEAPON ; load player 1 current weapon code (and rapid fire flag) + ora P2_CURRENT_WEAPON ; merge with player 2 current weapon code (and rapid fire flag) + and #$07 ; keep bits .... .xxx + sta $08 ; store merged weapon code in $08 + lda #$0a ; a = #$0a + sec ; set carry flag in preparation for subtraction + sbc $08 ; #$0a - merged weapon code + sta ENEMY_VAR_4,x ; store calculated delay between alien spider generation + lda FRAME_COUNTER ; load frame counter + adc RANDOM_NUM ; randomizer + and #$03 ; keep bits .... ..xx + adc ENEMY_VAR_4,x ; add random number between #$00 and #$3 inclusively to delay + sta ENEMY_VAR_4,x ; update delay between alien spider generation + lda #$14 ; a = #$14 (related to delay between spawns) + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter before starting cycle over + +@exit: + rts + +@adv_frame: + inc ENEMY_VAR_1,x ; increment nametable super-tile index (see level_8_nametable_update_supertile_data) + inc ENEMY_VAR_3,x ; increment animation frame number (easier to keep track of which super-tile is being drawn) + lda ENEMY_VAR_3,x ; load animation frame number + cmp #$03 ; see if past the last frame (alien spider spawn fully open) + bcc @draw_supertile ; branch if super-tile to draw isn't past last animation frame + dec ENEMY_VAR_1,x ; go back to frame 1 by subtracting 2 from ENEMY_VAR_1 (actual index) and ENEMY_VAR_3 (frame number) + dec ENEMY_VAR_1,x ; go back to first super-tile (alien spider spawn closed (frame 1)) + dec ENEMY_VAR_3,x + dec ENEMY_VAR_3,x + +@draw_supertile: + lda ENEMY_VAR_1,x ; load super-tile to draw + jmp draw_enemy_supertile_a ; draw super-tile a at position (ENEMY_X_POS, ENEMY_Y_POS) + +; advance the animation frame if delay elapsed and generate spider if generation delay elapsed +@check_frame_gen_spider: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne @exit ; exit if delay hasn't elapsed + lda #$14 ; delay elapsed, a = #$14 + sta ENEMY_ANIMATION_DELAY,x ; reset enemy animation frame delay counter + jsr @adv_frame ; move to the next super-tile + dec ENEMY_VAR_4,x ; decrement delay between generating spiders + beq @gen_spider ; generate a spider if delay has elapsed + rts + +; destroyed routine, draw destroyed super-tile and advance routine +alien_spider_spawn_routine_02: + ldy #$a8 ; #$28 - destroyed alien spider spawn on ground (see level_8_nametable_update_supertile_data) + lda ENEMY_Y_POS,x ; load enemy y position on screen + cmp #$80 ; see if spawn is above or below the middle of the screen + bcs @draw_supertile_adv_routine ; branch if below + ldy #$a4 ; #$24 - destroyed alien spider spawn on ceiling (see level_8_nametable_update_supertile_data) + +@draw_supertile_adv_routine: + tya ; transfer to offset register + jsr draw_enemy_supertile_a ; draw super-tile a at position (ENEMY_X_POS, ENEMY_Y_POS) + bcc @adv_enemy_routine ; advance routine if able to update super-tile + rts ; exit to try again next loop + +@adv_enemy_routine: + jmp advance_enemy_routine ; advance to next routine + +; pointer table for final boss heart (#$8 * #$2 = #$10 bytes) +boss_heart_routine_ptr_tbl: + .addr boss_heart_routine_00 ; CPU address $bc92 - set heart hp, animation delay, and advance routine + .addr boss_heart_routine_01 ; CPU address $bc9d + .addr boss_heart_routine_02 ; CPU address $bcb4 + .addr boss_heart_routine_03 ; CPU address $bd4c + .addr alien_guardian_routine_04 ; CPU address $b6b2 - create series of explosions, each with a #$05 frame delay before next explosion + .addr boss_heart_routine_05 ; CPU address $bceb + .addr boss_heart_routine_06 ; CPU address $bcf8 + .addr alien_guardian_routine_0b ; CPU address $bd02 + +; set heart hp, animation delay, and advance routine +boss_heart_routine_00: + jsr set_guardian_and_heart_enemy_hp ; calculate heart hp + lda #$0a ; a = #$0a (delay before first heartbeat) + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + ; (wait for AUTO_SCROLL_TIMER_00 set in set_boss_auto_scroll) + jmp advance_enemy_routine ; advance to next routine + +; heart - pointer 2 +; wait for boss auto scroll, advance routine +boss_heart_routine_01: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + beq @animation_delay_elapsed + rts ; exit if animation time has not elapsed + +@animation_delay_elapsed: + inc ENEMY_VAR_2,x + lda ENEMY_HP,x ; load enemy hp + lsr + bne @set_delay_adv_routine + lda #$01 ; set animation delay counter to #$01 + +@set_delay_adv_routine: + jmp set_enemy_delay_adv_routine ; set ENEMY_ANIMATION_DELAY counter to a + ; advance enemy routine + +; heart - pointer 3 +boss_heart_routine_02: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda ENEMY_VAR_2,x + beq @continue + inc ENEMY_VAR_4,x + lda ENEMY_VAR_4,x + and #$01 ; keep bits .... ...x + asl + clc ; clear carry in preparation for addition + adc #$03 + jsr update_alien_boss_supertiles ; updates 2 nametable tiles for boss heart animation + lda $0b + sta ENEMY_VAR_2,x + rts + +@continue: + lda ENEMY_VAR_4,x + and #$01 ; keep bits .... ...x + asl + clc ; clear carry in preparation for addition + adc #$04 ; determine correct entry in alien_boss_supertile_tbl + jsr update_alien_boss_supertiles ; updates 2 nametable tiles for boss heart animation + lda $0b + sta ENEMY_VAR_2,x + beq @set_boss_heart_routine_01 + rts + +@set_boss_heart_routine_01: + lda #$02 ; a = #$02 (boss_heart_routine_01) + sta ENEMY_ROUTINE,x ; enemy routine index + rts + +; heart - pointer 6 +boss_heart_routine_05: + lda #$07 ; a = #$07 + jsr update_alien_boss_supertiles ; updates 2 nametable tiles for boss heart animation (destroyed) + lda $0b + beq boss_heart_destroyed_adv_routine + rts + +boss_heart_destroyed_adv_routine: + jmp advance_enemy_routine ; advance to next routine + +; heart - pointer 7 +boss_heart_routine_06: + lda #$08 ; a = #$08 + jsr update_alien_boss_supertiles ; updates 2 nametable tiles for boss heart animation (destroyed) + lda $0b + beq boss_heart_destroyed_adv_routine + rts + +; alien guardian - pointer c +; heart - pointer 8 +; destroy all enemies +alien_guardian_routine_0b: + ldx #$0f ; x = #$0f + +; looks for specific enemies and runs routine appropriate destroy routine +@destroy_enemy_loop: + lda ENEMY_TYPE,x ; load current enemy type + cmp #$14 ; ENEMY_TYPE #$14 (alien spider) + beq @spider_destroy ; branch if alien spider + cmp #$15 ; ENEMY_TYPE #$15 (spider spawn) + beq @spawn_mouth_fetus_destroy ; branch if spider spawn + cmp #$12 ; ENEMY_TYPE #$12 (alien mouth) + beq @spawn_mouth_fetus_destroy ; branch if alien mouth + cmp #$13 ; ENEMY_TYPE #$13 (white blob) + beq @white_blob_destroy ; branch if white blob + cmp #$11 ; ENEMY_TYPE #$11 (alien fetus) + beq @spawn_mouth_fetus_destroy ; alien fetus + +@move_next_enemy: + dex ; move down to next enemy slot + bne @destroy_enemy_loop ; move to next enemy to see if should handle + ldx ENEMY_CURRENT_SLOT ; restore enemy current slot back to x + lda #$a0 ; a = #$a0 + jmp set_delay_remove_enemy ; set delay to a and remove enemy + +; alien_spider_spawn_routine_02 +; alien_mouth_routine_02 +; enemy_routine_init_explosion +@spawn_mouth_fetus_destroy: + lda #$03 ; a = #$03 + bne @set_routine_move_next_enemy ; set routine to start destroying enemy + +; enemy_routine_init_explosion +@spider_destroy: + lda #$06 ; a = #$06 + +@set_routine_move_next_enemy: + sta ENEMY_ROUTINE,x ; set enemy slot (0 = empty) + bne @move_next_enemy + +; enemy_routine_init_explosion +@white_blob_destroy: + lda #$04 ; a = #$04 + bne @set_routine_move_next_enemy + +; table for explosions relative positions for heart (#$c * #$2 = #$18 bytes) +alien_guardian_explosion_offset_tbl: + .byte $10,$10 ; #$10 , #$10 - unused !(UNUSED) (see alien_guardian_routine_04) + .byte $f0,$10 ; -#$10 , #$10 + .byte $f0,$f0 ; -#$10 , -#$10 + .byte $10,$f0 ; #$10 , -#$10 + .byte $20,$20 ; #$20 , #$20 + .byte $e0,$20 ; -#$20 , #$20 + .byte $e0,$e0 ; -#$20 , -#$20 + .byte $20,$e0 ; #$20 , -#$20 + .byte $40,$40 ; #$40 , #$40 + .byte $c0,$40 ; -#$40 , #$40 + .byte $c0,$c0 ; -#$40 , -#$40 + .byte $50,$00 ; #$50 , #$00 + +; heart - pointer 4 +boss_heart_routine_03: + jsr init_APU_channels + lda #$57 ; a = #$57 (sound_57) - boss destroyed + jsr level_boss_defeated ; play boss destroyed sound and initiate auto-move + jmp create_boss_heart_explosion ; start animation of heart explosion + +; adds a to ENEMY_X_POS and stores result in $0a +; $0a and $09 are used in draw_alien_guardian_supertile +set_nametable_x_pos_for_alien_guardian: + ldy #$00 ; y = #$00 + beq set_nametable_pos_for_alien_guardian ; always jump + lda #$00 ; dead code, never called !(UNUSED) + +; adds a to ENEMY_X_POS and stores result in $0a +; adds y to ENEMY_Y_POS and stores result in $09 +; $0a and $09 are used in draw_alien_guardian_supertile +set_nametable_pos_for_alien_guardian: + clc ; clear carry in preparation for addition + adc ENEMY_X_POS,x ; add to enemy x position on screen + sta $0a + tya + clc ; clear carry in preparation for addition + adc ENEMY_Y_POS,x ; add to enemy y position on screen + sta $09 + rts + +; unused space (#$295 bytes) +; these $ff bytes can technically be deleted here because the contra.cfg +; specifies that any free bytes in a ROM bank will be filled with $ff +; and each ROM bank is 16 KiB +bank_0_unused_space: + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff \ No newline at end of file diff --git a/src/bank1.asm b/src/bank1.asm new file mode 100644 index 0000000..e10db70 --- /dev/null +++ b/src/bank1.asm @@ -0,0 +1,6206 @@ +; Contra US Disassembly - v1.0 +; https://github.com/vermiceli/nes-contra-us +; Bank 1 is responsible for audio and sprites. The audio code takes up about +; 3/4 of the bank. The remaining 1/4 of the bank is for sprite data and code to +; draw sprites. + +.segment "BANK_1" + +.include "constants.asm" + +; import labels from bank 7 +.import play_sound + +; export labels used in other banks +.export draw_sprites +.export init_pulse_and_noise_channels +.export init_sound_code_vars +.export handle_sound_slots + +; Every PRG ROM bank starts with a single byte specifying which number it is +.byte $01 ; The PRG ROM bank number (1) + +; pointer table for music of level 1 (#$8 * #$2 = #$10 bytes) +; related to pulse channel config registers +pulse_volume_ptr_tbl: + .addr lvl_1_pulse_volume_00 ; CPU address $91dd + .addr lvl_1_pulse_volume_01 ; CPU address $91e5 + .addr lvl_1_pulse_volume_02 ; CPU address $91ea + .addr lvl_1_pulse_volume_03 ; CPU address $91f7 + .addr lvl_1_pulse_volume_04 ; CPU address $9204 + .addr lvl_1_pulse_volume_05 ; CPU address $9216 + .addr lvl_1_pulse_volume_06 ; CPU address $9229 + .addr lvl_1_pulse_volume_07 ; CPU address $923c + +; music of level 2 (#$8 * #$2 = #$10 bytes) + .addr lvl_2_pulse_volume_00 ; CPU address $a3db + .addr lvl_2_pulse_volume_01 ; CPU address $a3e8 + .addr lvl_2_pulse_volume_02 ; CPU address $a3ee + .addr lvl_2_pulse_volume_03 ; CPU address $a3f5 + .addr lvl_2_pulse_volume_04 ; CPU address $a402 + .addr lvl_2_pulse_volume_05 ; CPU address $a40f + .addr lvl_2_pulse_volume_06 ; CPU address $a706 + .addr lvl_2_pulse_volume_07 ; CPU address $aa5f + +; music of level 3 (#$8 * #$2 = #$10 bytes) + .addr lvl_3_pulse_volume_00 ; CPU address $aa6c + .addr lvl_3_pulse_volume_01 ; CPU address $aa73 + .addr lvl_3_pulse_volume_02 ; CPU address $ab04 + .addr lvl_3_pulse_volume_03 ; CPU address $ab11 + .addr lvl_3_pulse_volume_04 ; CPU address $ab1e + .addr lvl_3_pulse_volume_05 ; CPU address $ab29 + .addr lvl_3_pulse_volume_06 ; CPU address $aa78 + .addr lvl_3_pulse_volume_07 ; CPU address $98ca + +; music of level 4 (#$8 * #$2 = #$10 bytes) + .addr lvl_4_pulse_volume_00 ; CPU address $98d5 + .addr lvl_4_pulse_volume_01 ; CPU address $98ea + .addr lvl_4_pulse_volume_02 ; CPU address $98f1 + .addr lvl_4_pulse_volume_03 ; CPU address $98fd + .addr lvl_4_pulse_volume_04 ; CPU address $9904 + .addr lvl_4_pulse_volume_05 ; CPU address $9909 + .addr lvl_4_pulse_volume_06 ; CPU address $9915 + .addr lvl_4_pulse_volume_07 ; CPU address $991c + +; music of level 5 (#$8 * #$2 = #$10 bytes) + .addr lvl_5_pulse_volume_00 ; CPU address $9928 + .addr lvl_5_pulse_volume_01 ; CPU address $9e71 + .addr lvl_5_pulse_volume_02 ; CPU address $9e7d + .addr lvl_5_pulse_volume_03 ; CPU address $9c43 + .addr lvl_5_pulse_volume_04 ; CPU address $9c50 + .addr lvl_5_pulse_volume_05 ; CPU address $9c59 + .addr lvl_5_pulse_volume_06 ; CPU address $9c65 + .addr lvl_5_pulse_volume_07 ; CPU address $a064 + +; music of level 6 (#$8 * #$2 = #$10 bytes) + .addr lvl_6_pulse_volume_00 ; CPU address $a072 + .addr lvl_6_pulse_volume_01 ; CPU address $a080 + .addr lvl_6_pulse_volume_02 ; CPU address $a088 + .addr lvl_6_pulse_volume_03 ; CPU address $9e84 + .addr lvl_6_pulse_volume_04 ; CPU address $9e90 + .addr lvl_6_pulse_volume_05 ; CPU address $9e9c + .addr lvl_6_pulse_volume_06 ; CPU address $a713 + .addr lvl_6_pulse_volume_07 ; CPU address $a71b + +; music of level 7 (#$6 * #$2 = #$c bytes) + .addr lvl_7_pulse_volume_00 ; CPU address $a728 + .addr lvl_7_pulse_volume_01 ; CPU address $abd2 + .addr lvl_7_pulse_volume_02 ; CPU address $abda + .addr lvl_7_pulse_volume_03 ; CPU address $abea + .addr lvl_7_pulse_volume_04 ; CPU address $abeb + .addr lvl_7_pulse_volume_05 ; CPU address $aa85 + +; silences pulse wave channel +; after pause or level end, or anytime need to silence pulse wave channel +; input +; * x - sound register offset +; * y - APU channel register offset +mute_pulse_channel: + lda #$30 ; a = #$30 + sta $4000,x ; set volume to 0 and duty cycle to 25% + jsr wait ; execute #$0a nop instructions + bne set_pulse_timer_and_length ; always branch to set pulse timer and length + +; muse/unmutes pulse wave channel based on pause state +; input +; * x - sound register offset +mute_unmute_pulse_channel: + lda SOUND_FLAGS,x ; load the current sound slot's sound flags + and #$41 ; keep bits .x.. ...x + ora PAUSE_STATE_01 ; merge with current game pause state (0 = unpaused, 1 = paused) + bne mute_pulse_channel ; branch if paused + +; input +; * x - sound channel register config offset +; * y - sound channel register offset +unmute_pulse_channel: + lda PULSE_VOLUME,x ; load current volume + ora SOUND_CFG_HIGH,x + sta $4000,y + jsr wait ; execute #$0a nop instructions + +set_pulse_timer_and_length: + lda SOUND_PULSE_PERIOD,x ; load in memory pulse period value + sta APU_PULSE_PERIOD,y ; set APU pulse period value + jsr wait ; execute #$0a nop instructions + lda SOUND_PULSE_LENGTH,x ; load in memory pulse length value + sta APU_PULSE_LENGTH,y ; set the APU pulse channel length + jmp wait ; execute #$0a nop instructions + +; see if pausing/unpausing +; if unpausing, turn off level music, otherwise play music +sound_check_pause: + lda PAUSE_STATE ; #$01 for paused, #$00 for not paused + cmp PAUSE_STATE_01 ; compare to last frame pause state + beq sound_check_exit ; pause already handled, exit + sta PAUSE_STATE_01 ; set current pause value (0 = unpaused, 1 = paused) + cmp #$00 ; see if unpausing + beq @unpausing ; if unpausing, continue to play level music + jmp reset_channels ; pausing, reset triangle, noise, and pulse channels to stop level music + +@unpausing: + lda SOUND_CODE+4 ; load sound code for sound slot 4 + beq @toggle_pulse_channel ; branch if not playing game pausing jingle + lda SOUND_FLAGS+4 ; still playing game pausing jingle, load sound slot #$04 (pulse 1 channel) read index + ; this is the channel that the pause jingle uses + bpl @unmute_pulse_2 ; branch if still playing the game pausing jingle + lda $012d + sta APU_PULSE_SWEEP + +; unpausing in middle of pause jingle +@unmute_pulse_2: + ldx #$04 ; x = #$04 (pulse 2 channel config register) + ldy #$00 ; y = #$00 (pulse 1 channel register offset) + jsr unmute_pulse_channel ; could have been optimized to a jmp call with no rts + rts + +@toggle_pulse_channel: + lda SOUND_CODE ; load sound slot 0's sound code (pulse 1 channel) + beq sound_check_exit ; exit if not playing anything + ldx #$00 ; x = #$00 + ldy #$00 ; y = #$00 + jsr mute_unmute_pulse_channel ; mutes/unmutes pulse wave channel based on pause state + +sound_check_exit: + rts + +; sound and music entry point, check each sound slot and if populated, execute +; that slot's sound command +handle_sound_slots: + jsr sound_check_pause ; if unpausing, turn off level music, otherwise play music + ldx #$00 ; initialize sound slot loop + ldy #$00 ; initialize sound channel register offset to #$00 (pulse 1 channel) + +; input +; * x - sound slot index +; * y - channel register offset, e.g. #$00 (pulse 1 channel), #$04 (pulse 2 channel), #$08 (triangle channel), #$0c (noise/dmc channel) +@sound_slot_loop: + stx SOUND_CURRENT_SLOT ; set the current sound slot to the current loop index + sty SOUND_CHNL_REG_OFFSET ; set sound channel config register offset (#$00, #$04, $08, or #$0c) + lda SOUND_CODE,x ; load sound code for sound slot + beq @prep_next_loop ; prep to move to next sound slot, or exit if looped through all slots + tay ; sound slot has a sound code, transfer sound code to offset register y + jsr handle_sound_code ; read and interpret sound code in slot x + +@prep_next_loop: + inx ; increment sound slot index + cpx #$06 ; compare to after last sound slot + beq sound_music_entry_exit ; exit if looped through all sound slots + cpx #$05 ; see if last sound slot + bne @load_sound_channel_offset ; branch if not the last sound slot + lda #$0c ; sound slot #$05, set sound register offset to #$0c (noise channel) + jmp @loop_next ; loop to last sound slot with the sound channel register to use being the noise channel + +; all sound slots except #$05 +; slot #$05 is always the noise channel +@load_sound_channel_offset: + txa ; transfer sound slot index to a + asl ; double sound slot index + asl ; double sound slot index again + and #$0f ; keep low nibble, this is the new sound channel register offset + ; (e.g. #$00 = pulse 1 channel, #$04 = pulse 2 channel) + +@loop_next: + tay ; set SOUND_CHNL_REG_OFFSET (e.g. #$00 = pulse 1 channel, #$04 = pulse 2 channel) + jmp @sound_slot_loop + +sound_music_entry_exit: + rts + +; read and interpret loaded sound code in slot x +; output +; * x - sound slot with the sound code to handle +handle_sound_code: + lda PAUSE_STATE_01 ; load current game pause state (0 = unpaused, 1 = paused) + beq @check_sound_command ; branch if game not paused + lda SOUND_CODE,x ; game paused, load sound code for current slot + cmp #$54 ; see if sound code is the pause jingle (sound_54) + bne sound_music_entry_exit ; exit if not the game pausing jingle sound + +@check_sound_command: + jsr load_sound_code_addr ; gets the sound_xx pointer for sound slot from SOUND_CMD_LOW_ADDR and stores in ($e0) + ; also sets y to point to beginning, i.e. #$00 + lda SOUND_FLAGS,x ; load the current sound slot's sound flags + ; (0 = sound_xx command byte >= #$30, 1 = sound_xx command byte 0 < #$30) + dec SOUND_CMD_LENGTH,x ; decrement the number of video frames that the current sound code should execute for + ; before continuing to the next sound command + bne @pulse_vol_and_vibrato ; branch if sound command hasn't finished executing + jmp read_sound_command_00 ; finished with any previous sound command (or first command), read next/first sound command + +@pulse_vol_and_vibrato: + and #$41 ; keep bit 0 and 6 of the sound slot's sound flags (mute flags) + bne handle_sound_code_exit_00 ; exit if bit 0 or bit 6 is set (mute flags set) + cpx #$02 ; compare sound slot to #$02 (triangle channel) + beq handle_sound_code_exit_00 ; exit if sound slot #$02 (triangle channel) + cpx #$03 ; compare sound slot to #$03 (noise/dmc channel) + beq handle_sound_code_exit_00 ; exit if sound slot #$03 (noise/dmc channel) + cpx #$02 ; compare sound slot to #$02 (triangle channel) + bcs @check_pulse_volume ; branch if sound slot #$04 (pulse 1 channel), or #$05 (noise channel) + ; not sure why developer didn't compare to #$04 here since they've already compared to #$02 and #$03 !(HUH) + lda VIBRATO_CTRL,x ; sound slot is #$00 (pulse 1 channel) or #$01 (pulse 2 channel) + ; load VIBRATO_CTRL,x (#$80 = no vibrato) + bmi @check_pulse_volume ; skip incrementing SOUND_VOL_TIMER,x if VIBRATO_CTRL,x is negative + inc SOUND_VOL_TIMER,x ; VIBRATO_CTRL,x is [#$00-#$03], increment vibrato counter (increments up to VIBRATO_DELAY) + lda VIBRATO_DELAY,x ; load vibrato delay amount + cmp SOUND_VOL_TIMER,x ; see if vibrato timer has counted up to vibrato delay, if so, start checking vibrato + bcs @check_pulse_volume ; branch if SOUND_VOL_TIMER,x < VIBRATO_DELAY,x (vibrato hasn't started) + jsr pulse_sustain_note ; sustain the current pitch with optional vibrato (note: vibrato portion not used in Contra) + ; SOUND_VOL_TIMER >= VIBRATO_DELAY + +; see if volume is set or should be lowered +@check_pulse_volume: + lda SOUND_FLAGS,x ; load the current sound slot's sound flags + and #$20 ; see if volume decrescendo is complete by checking bit 5 + bne @check_volume_source ; branch if bit 5 set, indicating to no longer lower the volume (pause decrescendo) + ; bit 5 not set, check where volume should come from, and set volume + lda SOUND_VOL_ENV,x ; see if pulse volume range amount bit 7 is set (specifying to decrescendo) + bmi lower_pulse_volume ; branch when bit 7 set to lower the pulse volume by using and decrementing PULSE_VOLUME,x + +; SOUND_VOL_ENV is greater than or equal to #$00 or decrescendo is complete +; pulse volume control override +@check_volume_source: + lda SOUND_FLAGS,x ; re-load the current sound slot's sound flags + tay ; transfer sound flags to the y register + and #$04 ; keep bit 2 + bne handle_possible_decrescendo ; branch if bit 2 of the sound flags are set, indicating to use existing PULSE_VOLUME and not pulse_volume_ptr_tbl + ; this method handles when decrescendo should pause and continue + +; shapes volume envelope for note +; set new PULSE_VOLUME,x byte from lvl_x_pulse_volume_xx based on LVL_PULSE_VOL_INDEX,x and set pulse 1 and 2 configuration (volume) +; based on newly loaded PULSE_VOLUME,x, UNKNOWN_SOUND_01, and SOUND_CFG_HIGH,x +; called for simple sound commands as well +; input +; * x - sound channel offset +lvl_config_pulse: + lda SOUND_VOL_ENV,x ; pulse_volume_ptr_tbl offset, i.e. the current level music segment volume envelop to use (lvl_x_pulse_volume_xx) + asl ; double since each entry is #$02 bytes + tay ; transfer to offset register + lda pulse_volume_ptr_tbl,y ; read the low byte of the lvl_x_pulse_volume_xx address + sta $e4 ; set the low byte of the lvl_x_pulse_volume_xx address + lda pulse_volume_ptr_tbl+1,y ; read the high byte of the lvl_x_pulse_volume_xx address + sta $e5 ; set the high byte of the lvl_x_pulse_volume_xx address + +lvl_pulse_volume_byte: + lda LVL_PULSE_VOL_INDEX,x ; load read offset into lvl_x_pulse_volume_xx + tay ; transfer to offset register + lda ($e4),y ; read byte from lvl_x_pulse_volume_xx + cmp #$fe ; see if reached end of data + bcs lvl_pulse_volume_ctrl_code ; branch if lvl_x_pulse_volume_xx byte is greater than or equal to #$fe + ; to handle control code + ; #$fe means to set new offset based on next byte (not used in this game) + ; #$ff means to set bit 2 of SOUND_FLAGS,x then exit + inc LVL_PULSE_VOL_INDEX,x ; increment lvl_x_pulse_volume_xx read offset + and #$1f ; keep bits ...x xxxx of the lvl_x_pulse_volume_xx byte + +; sets PULSE_VOLUME,x value to a, then updates APU_PULSE_CONFIG to one of +; * (a - UNKNOWN_SOUND_01) | SOUND_CFG_HIGH,x +; * #$00 | SOUND_CFG_HIGH,x +; * a | SOUND_CFG_HIGH,x +; input +; * a - the PULSE_VOLUME,x value +; * x - current sound slot +set_pulse_config_a: + sta PULSE_VOLUME,x ; set current lvl_x_pulse_volume_xx byte + +; sets APU_PULSE_CONFIG value to one of the following +; only used when setting pulse channel 1 or 2 config register +; * (PULSE_VOLUME,x - UNKNOWN_SOUND_01) | SOUND_CFG_HIGH,x +; * #$00 | SOUND_CFG_HIGH,x +; * PULSE_VOLUME,x | SOUND_CFG_HIGH,x +; input +; * a - PULSE_VOLUME,x +; * x - current sound slot +set_pulse_config: + cmp #$02 + bcc @continue ; branch if volume is less than #$02 + sec ; volume greater than #$02, set carry flag in preparation for subtraction + sbc UNKNOWN_SOUND_01 + bpl @continue ; branch if subtraction is a positive answer + lda #$00 ; a = #$00 (negative volume, just use #$00) + +@continue: + ora SOUND_CFG_HIGH,x ; merge with high nibble of pulse config value + jsr ldx_pulse_triangle_reg ; set x to apu channel register [0, 1, 4, 5, 8, #$c] + bcs @exit + sta APU_PULSE_CONFIG,x ; set either pulse channel 1 or 2 config + ; a is either (PULSE_VOLUME,x - UNKNOWN_SOUND_01) | SOUND_CFG_HIGH,x + ; or #$00 | SOUND_CFG_HIGH,x + ; or PULSE_VOLUME,x | SOUND_CFG_HIGH,x + +@exit: + ldx SOUND_CURRENT_SLOT ; load current sound slot + +handle_sound_code_exit_00: + rts + +; handle lvl_x_pulse_volume_xx byte control code +; #$fe means to set new offset based on next byte (unused in this game) +; #$ff means to set bit 2 of SOUND_FLAGS,x then exit +; input +; * empty flag, set when lvl_x_pulse_volume_xx byte is #$fe +lvl_pulse_volume_ctrl_code: + bne disable_lvl_pulse_ctrl_exit ; branch if #$ff, i.e. not #$fe to enable automatic decrescendo logic + ; !(UNUSED) dead code, not ever executed in gameplay + ; no lvl_x_pulse_volume_xx byte is #$fe + iny ; byte is #$fe, increment offset into lvl_x_pulse_volume_xx + lda ($e4),y ; load next byte from lvl_x_pulse_volume_xx + sta LVL_PULSE_VOL_INDEX,x ; set new offset + jsr check_decrescendo_end_pause ; see if the decrescendo should resume and if so update SOUND_FLAGS,x + jmp lvl_pulse_volume_byte ; go back to read the next lvl_x_pulse_volume_xx byte + +; set bit 2 of SOUND_FLAGS, which indicates to check handle_possible_decrescendo +; setting disables use of pulse_volume_ptr_tbl and instead use automatic decrescendo +disable_lvl_pulse_ctrl_exit: + lda SOUND_FLAGS,x ; load the current sound slot's sound flags + ora #$04 ; set bit 2 + sta SOUND_FLAGS,x + rts + +; bit 2 of SOUND_FLAGS,x indicating to grab volume from memory, handling when decrescendo should pause and continue +; input +; * y - SOUND_FLAGS,x +handle_possible_decrescendo: + tya ; transfer SOUND_FLAGS,x to a + and #$02 ; keep bit 1 + bne resume_decrescendo ; branch if bit 1 set + ; this means both bit 1 and 2 are set, indicating DECRESCENDO_END_PAUSE has triggered + ; branching will resume the decrescendo + +; pulse channel only +; see if the number of remaining frames in the sound command is less than the decrescendo pause end +; if so, decrescendo should resume, specify this by setting bits 1 and 2 in the SOUND_FLAGS,x +check_decrescendo_end_pause: + lda SOUND_CMD_LENGTH,x ; load the remaining length of the sound command + cmp DECRESCENDO_END_PAUSE,x ; compare remaining length of the sound command to when decrescendo should resume + bcs @exit ; exit if shouldn't resume decrescendo + lda SOUND_FLAGS,x ; resume decrescendo, load the current sound slot's sound flags + ora #$06 ; set bits 1 and 2 + sta SOUND_FLAGS,x ; save sound flags now specify that decrescendo should resume + +@exit: + rts + +; lowers the pulse volume by using and decrementing PULSE_VOLUME,x +; compare to resume_decrescendo +lower_pulse_volume: + dec PULSE_VOL_DURATION,x ; decrement pulse volume decrescendo duration (how many frames to lower the volume) + bmi @pause_decrescendo ; if volume decrescendo length elapsed, brach to pause decrescendo + dec PULSE_VOLUME,x ; decrement current volume + bmi handle_sound_code_exit_01 ; branch if volume is negative + lda PULSE_VOLUME,x ; re-load volume + jmp set_pulse_config ; set pulse channel 1 or 2 config register based on PULSE_VOLUME,x, UNKNOWN_SOUND_01, and SOUND_CFG_HIGH,x + +; set bit 5 and bit 2 +@pause_decrescendo: + lda SOUND_FLAGS,x ; load the current sound slot's sound flags + ora #$20 ; set bit 5 (stop decrescendo) + sta SOUND_FLAGS,x ; store updated sound flags + jmp disable_lvl_pulse_ctrl_exit + +; set the pulse volume by using and decrementing PULSE_VOLUME,x +; called after DECRESCENDO_END_PAUSE has triggered and the decrescendo starts again +resume_decrescendo: + dec PULSE_VOLUME,x ; decrement volume + beq handle_sound_code_exit_01 ; branch if volume is #$00 + lda PULSE_VOLUME,x ; re-load volume + jmp set_pulse_config ; set pulse channel 1 or 2 config register based on PULSE_VOLUME,x, UNKNOWN_SOUND_01, and SOUND_CFG_HIGH,x + +handle_sound_code_exit_01: + inc PULSE_VOLUME,x ; reset the previous volume level + rts + +; one of two modes (along with read_high_sound_cmd) of parsing sound commands. +; This mode is used for pulse channels only +; read sound byte and if not [#$fd-#$ff] interpret_sound_byte otherwise sound_cmd_routine_03 +; SOUND_FLAGS,x is odd, indicating initial sound code byte was < #$30 +read_low_sound_cmd: + lda ($e0),y ; read sound_xx byte at current offset + cmp #$fd ; compare to #$fd + bcc @interpret_sound_byte ; branch if not #$fd, #$fe, nor #$ff + and #$0f ; sound_xx byte is either #$fd, #$fe, or #$ff, keep low nibble + jmp sound_cmd_routine_03 + +@interpret_sound_byte: + jmp interpret_sound_byte ; interprets sound_xx byte + +; sustains the current pitch (PULSE_NOTE,x) with optional vibrato (not used in Contra) +; input +; * y - sound code read offset (always #$00 from handle_sound_code -> load_sound_code_addr) !(HUH) +; * x - sound slot, either #$00 (pulse 1 channel) or #$01 (pulse 2 channel) +; jungle and hangar level music +pulse_sustain_note: + tya ; transfer sound code read offset (#$00) to a + pha ; backup y to the stack + ldy SOUND_CHNL_REG_OFFSET ; load sound channel config register offset (#$00, #$04, $08, or #$0c) + sec ; set carry flag in preparation for subtraction + sbc VIBRATO_DELAY,x ; negate vibrato duration (or #$100 - vibrato duration) + ; y - VIBRATO_DELAY,x --> #$00 - VIBRATO_DELAY,x + sta $e4 ; store result in $e4 + lda VIBRATO_AMOUNT,x ; load vibrato amount to apply + and #$f0 ; keep high nibble + lsr + lsr + lsr + lsr ; move to low nibble + cmp $e4 ; compare vibrato amount to negated VIBRATO_DELAY,x + bne @check_vibrato_ctrl ; branch if (negated $071e,x) is not equal to ($0180,x >> 4) to skip vibrato + lda VIBRATO_DELAY,x ; the following 8 lines of code are never executed in contra meaning no vibrato is used in contra !(UNUSED) + ; also not used in Japanese version which has one extra level track 'DEMO' + ; load period vibrato adjustment amount + sta SOUND_VOL_TIMER,x ; set vibrato counter; increments up to VIBRATO_DELAY before stopping + inc VIBRATO_CTRL,x ; increment vibrato control mode [#$00-#$03] + lda VIBRATO_CTRL,x ; load vibrato control mode [#$00-#$03] + cmp #$04 ; see if vibrato has gone more than #$03 + bcc @check_vibrato_ctrl ; branch if vibrato control mode has not yet gone past max value (#$03) + lda #$00 ; a = #$00 + sta VIBRATO_CTRL,x ; reset vibrato control mode back to #$00 + +@check_vibrato_ctrl: + lda VIBRATO_CTRL,x ; load vibrato control mode [#$00-#$03] + and #$01 ; keep bit 0 + bne @apply_vibrato ; branch if vibrato amount is odd (#$01 or #$03) + ; to add vibrato to note by pitching down (#$01) or up (#$03) + lda PULSE_NOTE,x ; vibrato amount is even, load existing pulse period value + +; set pulse channel period to a +; pop a from stack +; input +; * y - SOUND_CHNL_REG_OFFSET +@set_pulse_period: + sta APU_PULSE_PERIOD,y + jsr wait ; execute #$0a nop instructions + pla ; restore y value from stack (by loading into a) + tay ; restore y value from before call + rts + +; VIBRATO_CTRL,x is #$01 or #$03 (odd), add vibrato to note by pitching up or down +@apply_vibrato: + lda VIBRATO_AMOUNT,x ; load vibrato amount adjust + and #$0f ; keep low nibble + sta $e4 + lda VIBRATO_CTRL,x ; reload vibrato amount + and #$02 ; keep bit 1 + bne @pitch_up ; branch if vibrato amount is #$03 + lda $e4 ; vibrato amount is #$01, pitching down + ; vibrato is #$01, add $e4 (low byte of VIBRATO_AMOUNT,x) to PULSE_NOTE,x (APU pulse period value) + clc ; clear carry in preparation for addition + adc PULSE_NOTE,x ; add $e4 to APU pulse period value + bcc @set_pulse_period ; set pulse channel period to a + lda #$ff ; overflow occurred, set maximum period (a = #$ff) + jmp @set_pulse_period ; set pulse channel period to a + +; VIBRATO_CTRL,x is #$03, subtract $e4 (low byte of VIBRATO_AMOUNT,x) from PULSE_NOTE,x (APU pulse period value) +@pitch_up: + lda PULSE_NOTE,x ; load note that is sustained or has the vibrato applied to, in Contra only ever sustained + sec ; set carry flag in preparation for subtraction + sbc $e4 ; subtract low byte of VIBRATO_AMOUNT,x from PULSE_NOTE,x + jmp @set_pulse_period ; set pulse channel period to a + +; called when SOUND_FLAGS,x is not zero +; input +; * a - SOUND_FLAGS,x +; * y - sound_xx byte read offset +read_sound_command_00: + lsr ; shift bit 0 of SOUND_FLAGS,x to carry + ; (0 = sound_xx command byte >= #$30, 1 = sound_xx command byte 0 < #$30) + bcs read_low_sound_cmd ; branch if byte 0 of sound_xx was less than #$30 + ; reads sound byte and if not [#$fd-#$ff] interpret_sound_byte otherwise sound_cmd_routine_03 + +; sound code starts with byte that is greater than or equal to #$30 +; reads the sound byte and handles it +; input +; * ($e0) - pointer to sound byte +; * y - sound byte read offset +; output +; * x - SOUND_CURRENT_SLOT [0-3] +read_high_sound_cmd: + lda SOUND_CURRENT_SLOT ; loud sound slot index + cmp #$03 ; compare to sound slot #$03 (noise/dmc channel) + beq parse_percussion_cmd ; branch if sound slot #$03 (noise/dmc channel) + lda ($e0),y ; not noise channel, load sound byte + and #$f0 ; keep high nibble + cmp #$c0 ; compare to #$c0 + bcs @regular_sound_cmd ; branch if high nibble is greater than #$c0 to process the sound command + jmp simple_sound_cmd ; simple sound command, just a note and length multiplier + +; sound byte < #$c0 +@regular_sound_cmd: + and #$30 ; keep bits ..xx .... of high nibble + lsr + lsr + lsr + tax + lda sound_cmd_ptr_tbl,x ; load low byte of address pointer + sta $e4 ; set low byte of address pointer + lda sound_cmd_ptr_tbl+1,x ; load high byte of address pointer + sta $e5 ; set high byte of address pointer + ldx SOUND_CURRENT_SLOT ; load current sound slot + lda ($e0),y ; load sound code byte + and #$0f ; keep low nibble + jmp ($e4) ; jump to sound_cmd_routine_xx + +; only for sound slot #$03 (noise and dmc channel) +parse_percussion_cmd: + lda ($e0),y ; load sound code byte + and #$f0 ; keep high nibble + cmp #$f0 ; see if #$f + bne @continue ; branch if high nibble isn't #$f + lda ($e0),y ; high nibble is #$f + and #$0f ; keep low nibble + jmp sound_cmd_routine_03 ; high nibble #$f, go to sound_cmd_routine_03 with low nibble + ; either #$e or #$f + ; moves to next (child or parent) sound command + ; or finished with entire sound command and re-initialize channel + +; high nibble isn't #$f +@continue: + cmp #$d0 + beq @control_nibble_d ; branch if sound command high nibble is #$d to determine SOUND_LENGTH_MULTIPLIER + ; and then loop to actually play percussion sound + jmp calc_cmd_len_play_percussion ; high nibble isn't #$f nor #$d + ; play percussive sound (dpcm sample) + +; high nibble is #$d (delay command) +; load low nibble and set it as SOUND_LENGTH_MULTIPLIER before looping to actually +; play the percussion sound sample +@control_nibble_d: + lda ($e0),y ; read slot #$03 sound command byte + and #$0f ; keep low nibble + sta SOUND_LENGTH_MULTIPLIER,x ; set sound length multiplier + iny ; increment sound_xx read offset + jmp parse_percussion_cmd ; recursively loop to read next byte of percussion command + +; uses low nibble of sound_xx byte to determine sound code to play +; plays dmc sample for percussive track +; also will play sound_02 (bass drum/tom drum) for sound slots 0 and 1 (short percussive tick on noise channel) +play_percussive_sound: + lda ($e0),y ; load sound_xx byte + lsr + lsr + lsr + lsr ; move high nibble to low nibble + cmp #$0c ; see if high nibble was #$0c + beq @exit ; exit if high nibble was #$0c + tax ; transfer percussion_tbl offset to x + sta PERCUSSION_INDEX_BACKUP ; backup percussion_tbl offset + lda percussion_tbl,x ; load sound code based on high nibble from sound_xx + jsr play_sound ; play percussion sound (sound_02, sound_25, sound_5a, sound_5b, sound_5c) + lda PERCUSSION_INDEX_BACKUP ; restore percussion_tbl offset + cmp #$03 ; see if shifted sound nibble offset was less than #$03 + bcc @exit ; exit if index less than #$03 + lda #$02 ; shifted sound nibble is greater than or equal to #$03, play sound_02 (bass drum/tom drum) as well + ; note that offset 5 (sound_25) is also slot #$05 and will cause sound_02 to not play + jsr play_sound ; play sound_02 (short percussive tick) + +@exit: + ldx SOUND_CURRENT_SLOT ; load current sound slot + rts + +; contains sound codes to play the intro theme (#$08 bytes) +; related to music tracks instruments +; * sound_02 - percussive tick (bass drum/tom drum) +; * sound_25 - game intro tune song noise explosion +; * sound_5a - dmc sample (high hat) +; * sound_5b - dmc sample (snare) +; * sound_5c - dmc sample (high hat) +percussion_tbl: + .byte $02,$5a,$5b,$5a,$5b,$25,$5c,$5d + +; low sound command - interprets sound_xx byte +; input +; * a - sound_xx byte +; * y - sound code read offset +interpret_sound_byte: + and #$f0 ; keep high nibble + cmp #$20 ; see if high nibble is #$20, this is a control byte + bne @high_nibble_not_2 ; branch if high nibble isn't #$20 + lda ($e0),y ; high nibble is #$20, this is a control byte, reload sound byte + ; #$2 - sets the number of video frames to wait before reading the next sound + ; command (`SOUND_LENGTH_MULTIPLIER`) as well as the high nibble of the APU + ; configuration register for the sound channel (`SOUND_CFG_HIGH`). + and #$0f ; keep low nibble to see if using low nibble for SOUND_LENGTH_MULTIPLIER or the entire next byte + bne @continue ; branch if low nibble isn't #$f to use low nibble for SOUND_LENGTH_MULTIPLIER + iny ; low nibble is #$f, i.e. sound_xx byte is #$2f, use next byte as full byte multiplier + lda ($e0),y ; increment sound_xx byte read offset + +@continue: + sta SOUND_LENGTH_MULTIPLIER,x ; store low nibble (or next full byte) in SOUND_LENGTH_MULTIPLIER,x + iny ; increment sound_xx byte read offset + lda ($e0),y ; load new sound byte + sta SOUND_CFG_HIGH,x ; store pulse config high nibble value + iny ; increment sound_xx byte read offset + beq @high_nibble_not_2 ; !(WHY?) I don't think this will branch as y wouldn't go up to #$ff + jmp read_low_sound_cmd ; read sound byte and if not [#$fd-#$ff] interpret_sound_byte otherwise sound_cmd_routine_03 + +; high nibble wasn't 2, see if #$1 (flatten note or set sweep), or not #$1 (@high_nibble_not_1) +; - #$10 will set sweep, #$1x will flatten note +@high_nibble_not_2: + cmp #$10 ; see if sound code high nibble is #$10 + bne @high_nibble_not_1 ; branch if the high nibble isn't #$1 + ; branching sets command length, apu channel config, and note value + lda ($e0),y ; high nibble is #$1, reload full byte, if #$10 set optional sweep and continue + ; otherwise flatten note (not used in Contra) + iny ; increment sound_xx read offset + cmp #$10 ; compare full byte to #$10 + bne @flatten_note ; branch if full byte of sound_xx isn't #$10 to slightly flatten the note + lda ($e0),y ; sound byte is #$10, load next sound_xx byte for setting optional sweep and continue + bne @set_sweep ; branch if there is a sweep value to load it + lda SOUND_FLAGS,x ; no sweep, strip bit 7 (has sweep flag) + ; load the current sound slot's sound flags + and #$7f ; strip bit 7 (has sweep flag) + sta SOUND_FLAGS,x ; set new sound flags for sound slot (disable sweep) + lda #$7f ; a = #$7f (pulse 1 channel disable sweep) + bne @pulse_1_set_sweep_continue ; always branch to continue setting APU registers + +; sound byte wasn't #$00, set sweep and pulse 1 decrescendo duration PULSE_VOL_DURATION if sound slot #$04 +@set_sweep: + lda SOUND_FLAGS,x ; load the current sound slot's sound flags + ora #$80 ; set bit 7 + sta SOUND_FLAGS,x ; set bit 7 (sweep flag) + lda ($e0),y ; load APU_PULSE_SWEEP value + +; sets the pulse 1 channel PULSE_VOL_DURATION if current sound slot is #$04 (pulse 1 channel) +@pulse_1_set_sweep_continue: + cpx #$04 ; see if sound slot 4 (pulse 1 channel) + bne @set_sweep_continue ; skip setting duration if not sound slot 4 + sta PULSE_VOL_DURATION+3 ; set slot 3's (noise/dcm channel) volume duration + +@set_sweep_continue: + jsr ldx_pulse_triangle_reg ; set x to apu channel register [0, 1, 4, 5, 8, #$c] + bcs @next_high_control_sound_byte + sta APU_PULSE_SWEEP,x ; enable or disable sweep + +@next_high_control_sound_byte: + ldx SOUND_CURRENT_SLOT ; load current sound slot + iny + jmp read_low_sound_cmd ; read sound byte and if not [#$fd-#$ff] interpret_sound_byte otherwise sound_cmd_routine_03 + +@flatten_note: + lda SOUND_FLAGS,x ; load the current sound slot's sound flags + ora #$10 ; set bit 4 (slightly flatten note flat) + sta SOUND_FLAGS,x ; set new sound slot flag values + jmp read_low_sound_cmd ; read sound byte and if not [#$fd-#$ff] interpret_sound_byte otherwise sound_cmd_routine_03 + +; low sound command high nibble of sound code is not #$10 +; now will set set command length, apu channel config, note value, and then exit +; to allow sound to play for expected duration +@high_nibble_not_1: + lda SOUND_LENGTH_MULTIPLIER,x ; load sound command length + sta SOUND_CMD_LENGTH,x ; set loop counter + lda ($e0),y ; load volume + cmp #$f8 + bne @set_in_mem_cfg + iny ; sound byte is #$f8, increment sound byte read offset + lda ($e0),y ; load next sound byte + +@set_in_mem_cfg: + lsr + lsr + lsr + lsr ; move high nibble to low nibble + sta SOUND_CFG_LOW,x ; set new PULSE_VOLUME value to high nibble of sound_xx byte + lda SOUND_CFG_HIGH,x ; load configuration high byte + and #$10 ; keep bit 4 (C - constant volume flag) + beq @dont_set_volume ; branch to restore SOUND_CFG_HIGH,x to not include volume when setting configuration + lda SOUND_CFG_LOW,x ; reload high nibble of sound_xx byte + ora SOUND_CFG_HIGH,x ; merge previous sound byte with high nibble of current sound byte + +; set config register ($4000, $4004, or $400c) and period & length register +@set_cfg_period_length: + jsr ldx_pulse_triangle_reg ; set x to apu channel register [0, 1, 4, 5, 8, #$c] + bcs @load_set_period_length + sta $4000,x ; set pulse, triangle, or noise channel configuration + +@load_set_period_length: + ldx SOUND_CURRENT_SLOT ; load current sound slot + lda ($e0),y ; read current sound_xx byte + and #$0f ; keep low nibble + sta $ef ; store high byte (3 bits) of note period ($4003/$4007) + iny ; increment sound_xx read offset + lda ($e0),y ; load next sound_xx byte + sta $ee ; store low byte of note period ($4002/$4006) + jmp set_note ; set note value + +; C is set on APU configuration, indicating constant volume, restore full high byte of configuration +@dont_set_volume: + lda SOUND_CFG_HIGH,x ; load high nibble for when storing in pulse config register + jmp @set_cfg_period_length + +cfg_triangle_channel: + lda SOUND_TRIANGLE_CFG ; load triangle config + sta APU_TRIANGLE_CONFIG ; set triangle config + jmp clear_mute_set_note + +prep_pulse_set_note: + lda SOUND_CFG_LOW,x ; load new PULSE_VOLUME + jsr set_pulse_config_a ; sets PULSE_VOLUME,x value to a, then updates APU_PULSE_CONFIG + lda SOUND_VOL_ENV,x ; load the pulse volume range amount (low nibble) (how many frames to lower the volume) + and #$0f ; keep low nibble + sta PULSE_VOL_DURATION,x ; set the pulse volume decrescendo duration (how many frames to lower the volume) + jmp clear_mute_set_note ; set note (APU_PULSE_LENGTH) based on sound byte + +; play a single note with a specified length change from previous note +; called from read_high_sound_cmd, sound byte high nibble less than #$0c +; sound_xx byte +; * high nibble - indirect offset into note_period_tbl +; * low nibble - multiplier to use with previous note's base length +; for example, if the previous note was #$09 and #$04 and the low nibble is #$04, then note length will be the same +simple_sound_cmd: + jsr calc_cmd_len_play_percussion ; calculates SOUND_CMD_LENGTH and DECRESCENDO_END_PAUSE + ; doesn't play any percussion since simple_sound_cmd doesn't execute for sound slot #$03 + cpx #$02 ; compare to sound slot #$02 (triangle channel) + beq cfg_triangle_channel ; branch if slot #$02 (triangle channel) + lda SOUND_VOL_ENV,x ; pulse_volume_ptr_tbl offset, i.e. the current level music segment to play (lvl_x_pulse_volume_xx) + bmi prep_pulse_set_note + lda #$00 ; a = #$00 + sta LVL_PULSE_VOL_INDEX,x + sty $e6 ; backup sound_xx read offset + jsr lvl_config_pulse ; read lvl_x_pulse_volume_xx based on LVL_PULSE_VOL_INDEX,x and set pulse 1 and 2 configuration + ldy $e6 ; restore sound_xx read offset + +clear_mute_set_note: + lda SOUND_FLAGS,x ; load the current sound slot's sound flags + and #$99 ; keep bits x..x x..x, clearing volume and mute flags + sta SOUND_FLAGS,x ; update sound flags + lda ($e0),y ; load sound_xx byte + and #$f0 ; keep high nibble + lsr + lsr + lsr ; shift high nibble right 3 bits + tax ; transfer to offset register + tya ; transfer sound_xx read offset to a for backing up + pha ; back up sound_xx read offset to stack + ldy SOUND_CURRENT_SLOT ; load current sound slot + lda SOUND_PITCH_ADJ,y ; load sound period value for slot + beq @continue ; if loaded value is #$00 branch + txa ; load the high nibble (now in low nibble) from the sound_xx byte + clc ; clear carry in preparation for addition + adc SOUND_PITCH_ADJ,y ; add adjustment to offset current note_period_tbl offset + tax ; set the note_period_tbl read offset + +@continue: + pla ; restore sound_xx read offset + tay ; restore sound_xx read offset to y + lda note_period_tbl,x ; load low period byte music note + sta $ee ; store low byte of note period ($4002/$4006) + lda note_period_tbl+1,x ; load high period byte music note + sta $ef ; store high byte (3 bits) of note period ($4003/$4007) + ldx SOUND_CURRENT_SLOT ; load current sound slot (!(HUH) this value is overwritten two lines down) + lda SOUND_PERIOD_ROTATE,x ; load pitch shifting amount + tax ; transfer amount to x + +@loop: + cpx #$04 ; only shift if the value is not #$04 + beq @exit_loop ; exit if SOUND_PERIOD_ROTATE,x is #$04 + lsr $ef ; shift right high byte (3 bits) of note period ($4003/$4007) + ror $ee ; rotate low byte of note period ($4002/$4006) + inx ; add one to the sound slot + bne @loop ; branch until reach #$00 + +@exit_loop: + ldx SOUND_CURRENT_SLOT ; load current sound slot + cpx #$02 ; compare to sound slot #$02 (triangle channel) + bcs set_note ; branch if slot #$02 (triangle), #$03 (noise), #$04 (pulse 1), or #$05 (noise) + lda VIBRATO_CTRL,x ; sound slot is #$00 (pulse 1) or #$01 (pulse 2), load vibrato control #$80 = no vibrato + bmi set_note ; branch to set period and length directly if no vibrato + lda $ee ; load low byte of note period ($4002/$4006) + sta PULSE_NOTE,x ; store low byte of note period in memory for later when checking for vibrato + lda SOUND_FLAGS,x ; load the current sound slot's sound flags + and #$10 ; keep bit 4 (slightly flatten note flag) + beq @load_slot_set_note ; branch if note should not be flattened slightly + inc PULSE_NOTE,x ; increase period, which lowers the frequency of the note + +; load sound slot and set the period and length registers to specify the note to play +@load_slot_set_note: + ldx SOUND_CURRENT_SLOT ; load current sound slot + +; sets the period and length registers to specify the note to play +; input +; * $ee - low byte of note period ($4002/$4006) +; * $ef - high byte (3 bits) of note period ($4003/$4007) +set_note: + lda SOUND_FLAGS,x ; load the current sound slot's sound flags + and #$10 ; keep bit 4 (flatten note bit) + beq @continue_00 ; branch if bit it clear, indicating to not flatten the specified note + inc $ee ; increment low byte of note period ($4002/$4006), slightly flattens a note + bne @continue_00 ; branch if no overflow occurred + inc $ef ; increment high byte (3 bits) of note period ($4003/$4007) + +@continue_00: + lda $ef ; load high byte (3 bits) of note period ($4003/$4007) + cpx #$02 ; compare to sound slot #$02 (triangle channel) + beq @set_length ; branch if sound slot #$02 (triangle channel) to set APU_PULSE_LENGTH + cpx #$05 ; compare to sound slot #$05 (noise channel) + beq @continue_01 ; branch if sound slot #$05 (noise channel) to set APU_PULSE_LENGTH + ; !(HUH) could have branched to @set_length directly, a already has $ef + cmp SOUND_PULSE_LENGTH,x ; compare to the current pulse/noise length + bne @set_length ; set new length if not already set + lda SOUND_FLAGS,x ; load the current sound slot's sound flags + bmi @continue_01 ; branch if sound flags negative to to set APU_PULSE_LENGTH + lda SOUND_CFG_HIGH,x ; load high nibble for when storing in pulse config register + and #$10 ; keep bits ...x .... + bne @set_period ; skip setting APU_PULSE_LENGTH if SOUND_CFG_HIGH,x bit 4 set + +@continue_01: + lda $ef + +; set length and high 3 bits of timer +@set_length: + sta SOUND_PULSE_LENGTH,x ; set in memory copy of current pulse length + ora #$08 ; set bit 0 of high timer to be 1 + jsr ldx_pulse_triangle_reg ; set x to apu channel register [0, 1, 4, 5, 8, #$c] + bcs @set_period + sta APU_PULSE_LENGTH,x ; set duration and high 3 bits of the pulse, or triangle channel + +; set low period +@set_period: + lda $ee ; load low byte of note period ($4002/$4006) + ldx SOUND_CURRENT_SLOT ; load current sound slot + cpx #$02 ; compare to sound slot #$02 (triangle channel) + bcs @set_apu_period ; branch if slot #$02 (triangle), #$03 (noise), #$04 (pulse 1), or #$05 (noise) + sta SOUND_PULSE_PERIOD,x ; set in memory pulse period value + +@set_apu_period: + jsr ldx_pulse_triangle_reg ; set x to apu channel register [0, 1, 4, 5, 8, #$c] + bcs @restore_x_adv_sound_ptr ; if carry set, do not update APU register + ; continue to restore x to the sound slot index, update SOUND_CMD_LOW_ADDR value, and exit + sta APU_PULSE_PERIOD,x ; update APU pulse period + +@restore_x_adv_sound_ptr: + ldx SOUND_CURRENT_SLOT ; load current sound slot + +; simple_sound_cmd - finished parsing 'sound command' and next frame will pick up at new location +; which is just after parsed sound command. +; updates the sound_xx read offset, i.e. SOUND_CMD_LOW_ADDR to point to current read location + 1 +; game logic uses load_sound_code_addr to know where to read +; input +; * y - current sound_xx read offset +; * $e0 - SOUND_CMD_LOW_ADDR (address low byte) +; * $e1 - SOUND_CMD_HIGH_ADDR (address high byte) +adv_sound_cmd_addr: + iny ; increment sound_xx sound command read offset + tya ; transfer sound_xx sound command read offset to a + clc ; clear carry in preparation for addition + adc $e0 ; add to current read offset address low byte + sta SOUND_CMD_LOW_ADDR,x ; set new starting position to read sound_xx data (low byte) + lda #$00 ; a = #$00 + adc $e1 ; add any carry from previous addition to current read offset address high byte + sta SOUND_CMD_HIGH_ADDR,x ; set new starting position to read sound_xx data (high byte) + rts + +; e.g. sound_1b - level 1 jungle boss siren +restore_parent_sound_cmd_addr: + lda NEW_SOUND_CODE_LOW_ADDR,x ; load new sound code address low byte + sta $e0 ; set new sound code address low byte + lda NEW_SOUND_CODE_HIGH_ADDR,x ; load new sound code address high byte + sta $e1 ; set new sound code address high byte + lda SOUND_FLAGS,x ; load the current sound slot's sound flags + and #$f7 ; strip bit 7 (sweep flag) + sta SOUND_FLAGS,x ; set the sound slot's sound flags + ldy #$00 + jmp read_sound_command_01 + +; sound_cmd_routine_03 - low nibble #$0f +; either initialize sound channel, or finished with 'sound_xx_part' go back to 'parent' sound command and parse it +low_nibble_f: + lda SOUND_FLAGS,x ; load the current sound slot's sound flags + and #$08 + bne restore_parent_sound_cmd_addr ; branch if bit 3 is set + lda SOUND_CODE,x ; bit 3 not set, load sound code + sta $e6 ; backup sound code + lda #$00 + +; input +; * a - sound code (#$00 is always passed in) +; * x - sound slot offset +exe_channel_init_ptr_tbl_routine: + sta SOUND_CODE,x ; clear sound code + txa ; transfer sound slot offset to a for doubling + asl ; double sound slot offset since each entry in channel_init_ptr_tbl is #$02 bytes + tax ; transfer back to x + lda channel_init_ptr_tbl,x ; load low byte of address + sta $e4 ; store low byte in $e4 + lda channel_init_ptr_tbl+1,x ; load high byte of address + sta $e5 ; store high byte in $e5 + ldx SOUND_CURRENT_SLOT ; restore current sound slot + jmp ($e4) + +; moves to next (child or parent) sound command, or finished with entire sound command and re-initialize channel +; - could be entering a shared sound command used for playing shared sound data across different sound_xx commands (#$fd) +; - could be entering a repeat subcommand used to repeat sound parts (#$fe) +; - could be exiting a shared sound command and need to return to parent sound command (#$ff) +; - could be finished reading entire sound (#$ff) +; * a - low nibble of sound byte value +sound_cmd_routine_03: + cmp #$0e ; compare low nibble of sound byte to #$e + beq @repeat_cmd ; branch if sound command is #$fe to allow repeating a shared sound part ($e0),y+1 times + bcs low_nibble_f + jsr move_sound_code_read_addr ; sound command is #$fd, move to sound command address to shared sound part beginning + iny ; increment sound code read offset from address byte + tya ; transfer sound_xx read offset to a + clc ; clear carry before updating ($e0)'s address + adc $e0 ; skip the 2 address bytes that specified SOUND_CMD_LOW_ADDR + sta NEW_SOUND_CODE_LOW_ADDR,x ; set sound command return location low byte once shared sound command part specified in move_sound_code_read_addr executes + lda #$00 + tay + adc $e1 ; add any carry from low byte address to high byte + sta NEW_SOUND_CODE_HIGH_ADDR,x ; set sound command return location high byte once shared sound command part specified in move_sound_code_read_addr executes + lda SOUND_FLAGS,x ; load the current sound slot's sound flags + ora #$08 ; set bit 3 + sta SOUND_FLAGS,x ; save sound slot's flags + jmp @load_sound_code_addr ; gets the sound_xx pointer for sound slot from SOUND_CMD_LOW_ADDR and stores in ($e0) + ; then begins reading that sound command + +; #$fe command, i.e. a repeat command. Allows repeating a section of music a specified number of times +; .byte $fe,$03 ; repeat #$3 times +; .addr sound_3e +@repeat_cmd: + inc SOUND_REPEAT_COUNT,x ; increment sound part repeat counter + lda SOUND_REPEAT_COUNT,x ; load current sound part repeat counter + iny ; increment sound code read byte offset + cmp ($e0),y ; compare SOUND_REPEAT_COUNT,x to sound code byte + beq skip_3_read_sound_command_01 ; looped ($e0),y times, don't loop any more, move to next sound command + bmi @move_sound_code_read_addr ; branch if SOUND_REPEAT_COUNT,x < number of times to repeat shared sound part + dec SOUND_REPEAT_COUNT,x ; not sure if ever executed, shouldn't go past ($e0),y, but if so, decrement and repeat !(WHY?) + +@move_sound_code_read_addr: + jsr move_sound_code_read_addr ; update the sound code read address based on the next two bytes of the sound code + +@load_sound_code_addr: + jsr load_sound_code_addr ; gets the sound_xx pointer for sound slot from SOUND_CMD_LOW_ADDR and stores in ($e0) + ; also sets y to point to beginning, i.e. #$00 + +read_sound_command_01: + lda SOUND_FLAGS,x ; load the current sound slot's sound flags + lsr ; shift bit 0 to carry (0 = sound_xx byte >= #$30, 1 = sound_xx byte 0 < #$30) + bcs @read_low_sound_cmd ; branch if byte 0 of sound_xx was less than #$30 + ; read sound byte and if not [#$fd-#$ff] interpret_sound_byte otherwise sound_cmd_routine_03 + jmp read_high_sound_cmd ; read sound_xx,y byte and handle it + +@read_low_sound_cmd: + jmp read_low_sound_cmd ; read sound byte and if not [#$fd-#$ff] interpret_sound_byte otherwise sound_cmd_routine_03 + +; skips the shared sound repeat loop counter (1 byte), and the specified address (2 bytes), then reads next sound command (read_sound_command_01) +; also resets shared sound part repeat counter +; input +; * x - sound slot index +; * y - sound byte read offset +skip_3_read_sound_command_01: + lda #$00 ; clear a register + sta SOUND_REPEAT_COUNT,x ; reset shared sound part repeat counter + iny + iny + iny ; skip #$03 bytes of sound_xx code + tya ; transfer sound_xx read offset to a + clc ; clear carry in preparation for addition + adc $e0 ; add to sound byte read location's low byte + sta $e0 ; set new sound code read location's low byte + lda #$00 + tay ; clear y register + adc $e1 ; add any carry from previous addition into high byte of sound byte read location + sta $e1 ; set any carry for new read location pointer address + jmp read_sound_command_01 ; logically same as read_sound_command_00 + ; (except SOUND_FLAGS are loaded within read_sound_command_00) + +; set sound channel configuration (mute), advance sound command address +; * a - amount to multiply SOUND_CMD_LENGTH by +sound_cmd_routine_00: + jsr calc_cmd_delay ; multiply SOUND_CMD_LENGTH by a + lda #$00 ; sound config low nibble = #$00 (mute sound channel) + cpx #$02 ; see if sound slot #$02 (triangle channel) + beq @continue + lda SOUND_CFG_HIGH,x ; pulse/noise channels use SOUND_CFG_HIGH,x + +@continue: + jsr ldx_pulse_triangle_reg ; set x to apu channel register [0, 1, 4, 5, 8, #$c] + bcs @adv_read_addr + sta $4000,x ; set pulse 1, pulse 2, or triangle configuration + +@adv_read_addr: + ldx SOUND_CURRENT_SLOT ; load current sound slot + lda SOUND_FLAGS,x ; load the current sound slot's sound flags + ora #$40 ; set bit 6 (mute flag) + sta SOUND_FLAGS,x + jmp adv_sound_cmd_addr ; set the sound_xx command read offset to current read location + 1 + +; set in memory configuration for channel, set multiplier, and sometimes read_high_sound_cmd +; * a - low nibble of sound byte value +sound_cmd_routine_01: + sta SOUND_LENGTH_MULTIPLIER,x ; store value used to calculate SOUND_CMD_LENGTH + iny + lda ($e0),y ; load sound code byte + cpx #$02 ; compare to sound slot #$02 (triangle channel) + beq set_sound_triangle_config ; branch if triangle channel to set triangle config in memory and read_high_sound_cmd + and #$0f ; not triangle sound slot, get low nibble + sec + sbc UNKNOWN_SOUND_01 + bpl @set_cfg_read_sound_byte ; if result is positive set full in memory apu config and read_high_sound_cmd + lda #$00 ; used to set SOUND_CODE,x to #$00 + jmp exe_channel_init_ptr_tbl_routine ; initialize sound channel + +@set_cfg_read_sound_byte: + sta SOUND_CFG_LOW,x ; set result of sound_xx low nibble - UNKNOWN_SOUND_01 + lda ($e0),y ; load sound code byte + and #$f0 ; keep high nibble + sta SOUND_CFG_HIGH,x ; store high nibble of pulse config value (see set_pulse_config) + iny ; increment sound code read offset + lda ($e0),y ; load sound volume envelop control byte + ; when bit 7 set, automatic decrescendo, otherwise, will follow pattern from pulse_volume_ptr_tbl + sta SOUND_VOL_ENV,x ; set indicator whether or not to lower the volume (see @check_pulse_volume) + iny ; increment sound code read offset + lda ($e0),y ; load sound code byte + and #$0f + sta UNKNOWN_SOUND_00,x ; see calc_cmd_len_play_percussion, amount to multiply to SOUND_CMD_LENGTH,x when calculating DECRESCENDO_END_PAUSE,x + iny + jmp read_high_sound_cmd ; reads sound_xx,y byte and handles it + +; sets in-memory value for triangle configuration SOUND_TRIANGLE_CFG +; called only from sound_cmd_routine_01 for triangle sound slot +; input +; * a - sound byte value that was the byte after the sound_cmd_routine_01 control byte +set_sound_triangle_config: + sta SOUND_TRIANGLE_CFG ; set in memory value for triangle config + iny + jmp read_high_sound_cmd ; reads sound_xx,y byte and handles it + +; set/adjust pitch, and read_high_sound_cmd +; input +; * a - low nibble of control byte +; * y - sound byte read offset +sound_cmd_routine_02: + cmp #$05 ; compare low nibble of sound code to #$05 + ; when less than #$05 it is referring to a sound slot + bcs @high_val ; branch if more than #$05 + sta SOUND_PERIOD_ROTATE,x ; when not #$04, the number of times to shift the high byte of note_period_tbl into the low byte + iny ; increment read offset + jmp read_high_sound_cmd ; reads sound_xx,y byte and handles it + +@high_val: + cmp #$08 + beq @flip_flatten_note_adv + cmp #$0b + beq @set_vibrato_vars_adv + cmp #$0c + beq @set_pitch_adj_adv + iny ; unknown low byte, move to next sound byte + jmp read_high_sound_cmd ; reads sound_xx,y byte and handles it + +; low nibble of control byte is #$08, flip bit specifying whether to slightly flatten note +@flip_flatten_note_adv: + lda SOUND_FLAGS,x ; load the current sound slot's sound flags + eor #$10 ; flip bit 4 (whether or not to slightly flatten note) + sta SOUND_FLAGS,x + iny ; increment sound_xx read offset + jmp read_high_sound_cmd ; reads sound_xx,y byte and handles it + +; sound_cmd_routine_02 -> low nibble #$b +; set vibrato variables, and move to next sound byte +; jungle and hangar music +@set_vibrato_vars_adv: + iny ; increment sound_xx read offset + lda ($e0),y ; load sound_xx byte + sta VIBRATO_DELAY,x ; set delay until SOUND_VOL_TIMER has counted up to this value before checking vibrato + cmp #$ff ; see if sound_xx byte is #$ff + beq @end_of_sound_code ; branch if sound_xx byte is #$ff + iny ; increment sound_xx read offset + lda ($e0),y ; load sound_xx byte + sta VIBRATO_AMOUNT,x ; set the amount of vibrato to apply + lda #$00 ; a = #$00 + sta VIBRATO_CTRL,x ; set vibrato mode to #$00 + iny ; increment sound_xx read offset + jmp read_high_sound_cmd ; loop to read next sound_xx,y byte and handle it + +; sound_xx byte is #$ff, read entire sound_xx code +@end_of_sound_code: + lda #$80 ; disable vibrato check + sta VIBRATO_CTRL,x ; set to not use vibrato (0 = yes, #$80 = no) + iny + jmp read_high_sound_cmd ; read sound_xx,y byte and handle it + +; sound_cmd_routine_02 -> low nibble #$c +; sets the pitch adjustment, and move to next sound byte +@set_pitch_adj_adv: + iny ; increment sound_xx read offset + lda ($e0),y ; load sound_xx byte + asl a ; double value since each entry in note_period_tbl is #$02 bytes + sta SOUND_PITCH_ADJ,x ; set sound period adjustment (adjusts note frequency/pitch) + iny ; increment sound_xx read offset + jmp read_high_sound_cmd ; reads sound_xx,y byte and handles it + +; called from calc_cmd_len_play_percussion when sound slot is #$03 (noise/dmc channel) +; advance sound_xx read offset, and plays dpcm sample based on low nibble +adv_sound_play_percussive: + jsr adv_sound_cmd_addr ; advance the sound_xx read offset (SOUND_CMD_LOW_ADDR) to current read location + 1 + dey ; decrement sound_xx read offset (it was incremented by adv_sound_cmd_addr) + jmp play_percussive_sound ; play appropriate intro sound code based on next sound_xx high nibble + +; load sound_xx byte and calculate new SOUND_CMD_LENGTH and DECRESCENDO_END_PAUSE +; for simple_sound_cmd, high nibble is less than #$c +; for read_high_sound_cmd sound slot #$03, also play a percussion sound sample (adv_sound_play_percussive) +; input +; * x - sound slot index [0-3] +calc_cmd_len_play_percussion: + lda ($e0),y ; load sound byte + and #$0f ; keep low nibble + +; input +; * a - amount (+1) multiplied to SOUND_LENGTH_MULTIPLIER,x to get total delay +; ex: a = #$03, SOUND_LENGTH_MULTIPLIER,x = #$09 => #$24 == #$04 * #$09 +calc_cmd_delay: + sta $e4 ; set amount of times to add $0154 to itself + beq @skip_loop ; don't loop if multiplier is #$00 + lda SOUND_LENGTH_MULTIPLIER,x + +@calc_delay_loop: + clc ; clear carry in preparation for addition + adc SOUND_LENGTH_MULTIPLIER,x ; add $0154 to itself + dec $e4 ; decrement loop counter + bne @calc_delay_loop ; loop if not finished adding to itself + beq @loop_complete ; break loop if added $0154 $e4 times + +@skip_loop: + lda SOUND_LENGTH_MULTIPLIER,x ; load new loop counter, i.e. SOUND_LENGTH_MULTIPLIER,x * $e4 + +@loop_complete: + sta SOUND_CMD_LENGTH,x ; set new $0154 value in $0100 + cpx #$02 ; compare to sound slot #$02 (triangle channel) + bcs @continue ; branch if slot #$02 (triangle), #$03 (noise), #$04 (pulse 1), or #$05 (noise) + lda VIBRATO_CTRL,x ; load whether or not to use vibrato (0 = yes, #$80 = no) + bmi @continue ; skip resetting SOUND_VOL_TIMER,x if VIBRATO_CTRL,x has bit 7 set + lda #$00 + sta SOUND_VOL_TIMER,x ; clear SOUND_VOL_TIMER,x value + +@continue: + cpx #$02 ; compare to sound slot #$02 (triangle channel) + beq @exit ; exit if sound slot #$02 (triangle channel) + cpx #$03 ; compare to sound slot #$03 (noise/dmc channel) + beq adv_sound_play_percussive ; play dmc sample if sound slot #$03 (noise/dmc channel) + lda UNKNOWN_SOUND_00,x ; load sound byte low nibble + jsr @calc_decrescendo_pause_end ; calculate the high #$02 nibbles of the #$03 nibble result of a * SOUND_CMD_LENGTH,x + sta DECRESCENDO_END_PAUSE,x ; set result in DECRESCENDO_END_PAUSE,x + +@exit: + rts + +; calculate the high #$02 nibbles of the #$03 nibble multiplication result of a * SOUND_CMD_LENGTH,x +; if the result is less than #$02 nibbles, then #$00 will be returned +; ex: a = #$0a, SOUND_CMD_LENGTH,x = #$24 -> #$0a * #$24 -> #$168 -> #$16 +; ex: a = #$06, SOUND_CMD_LENGTH,x = #$24 -> #$06 * #$24 -> #$0d8 -> #$d8 +; result: #$16 +; input +; * a - sound byte low nibble +; * SOUND_CMD_LENGTH,x +; output +; * a - the high #$02 nibbles of the #$03 nibble multiplication result of a * SOUND_CMD_LENGTH,x +@calc_decrescendo_pause_end: + and #$0f ; keep low nibble of UNKNOWN_SOUND_00,x + ; (should already be stripped to low nibble) + sta $e4 ; set low nibble in $e4 + lda #$00 + sta $e6 + sta $e7 + +@loop: + dec $e4 ; decrement multiplier + bmi @set_e7_exit ; return high nibble of a + clc + lda SOUND_CMD_LENGTH,x + adc $e6 ; add SOUND_CMD_LENGTH,x to total + sta $e6 ; store result back in $e6 + bcc @loop ; branch to loop if no overflow occurred + inc $e7 ; carry occurred keep track in $e7 + bne @loop ; always loop + +; shift high nibble from a into low nibble of $e7 +; any previous overflow is now in the high nibble of $e7 +@set_e7_exit: + asl + rol $e7 + asl + rol $e7 + asl + rol $e7 + asl + rol $e7 + lda $e7 + rts + +; gets the sound_xx pointer for sound slot from SOUND_CMD_LOW_ADDR and stores in ($e0) +; also sets y to point to beginning, i.e. #$00 +load_sound_code_addr: + ldy #$00 ; reset sound code read offset + lda SOUND_CMD_LOW_ADDR,x ; load sound_xx address low byte + sta $e0 ; set sound_xx address low byte + lda SOUND_CMD_HIGH_ADDR,x ; load sound_xx address high byte + sta $e1 ; set sound_xx address high byte + rts + +; updates the sound code read address (SOUND_CMD_LOW_ADDR) based on the next two bytes of the sound code +move_sound_code_read_addr: + iny ; increment sound code read offset + lda ($e0),y ; load new sound code location low byte read offset + sta SOUND_CMD_LOW_ADDR,x ; set new sound code location low byte read offset + iny ; increment sound code read offset + lda ($e0),y ; load new sound code location high byte read offset + sta SOUND_CMD_HIGH_ADDR,x ; set new sound code location high byte read offset + rts + +; get the APU configuration register offset (SOUND_CHNL_REG_OFFSET) for sound slot, e.g. #$01 --> #$04 +; if 2 pulse channel 1 sounds are playing, this will set the carry to indicate no way to play sound +; input +; * x - sound slot +; output +; * x - sound channel configuration register offset +; * carry flag - clear to update the apu register, set to not update apu register +; when set, there is already a sound playing on that channel that has priority +ldx_pulse_triangle_reg: + pha ; backup a on the stack + cpx #$01 ; compare to sound slot #$02 (pulse channel 2) + bcc @move_next_apu_reg ; branch if pulse channel 0 + +; requested register is not for sound slot 0 +; exit with x set to SOUND_CHNL_REG_OFFSET and the carry clear +@clc_exit: + clc ; clear carry to signal to update sound register + ldx SOUND_CHNL_REG_OFFSET ; load sound channel config register offset (#$00, #$04, $08, or #$0c) + pla ; restore a from stack + rts + +; sound slot is #$00 (pulse channel 1), prefer sound slot #$04 (pulse 1) (higher priority) +@move_next_apu_reg: + inx + inx + inx + inx + lda SOUND_CODE,x ; load sound slot 4's sound code to see if a sound is playing for that slot + beq @clc_exit ; exit if no sound is playing in slot #$4, use that slot instead of #$0 + ; carry will clear and x will be set to SOUND_CHNL_REG_OFFSET + ldx SOUND_CURRENT_SLOT ; otherwise, load current sound slot, set carry, and exit + sec ; set carry flag to signal to not update sound register + pla + +sound_exit_00: + rts + +; mutes the current sound channel, and for sound slot 1, see if playing boss heart destroyed sound +; if so, play end of level song +; input +; * x - current sound slot +mute_channel: + lda #$30 ; a = #$30 (mute pulse channel register) + jsr ldx_pulse_triangle_reg ; set x to apu channel register [0, 1, 4, 5, 8, #$c] + bcs @continue + sta $4000,x ; update pulse channel config (mute pulse channel 1 or 2 register) + +@continue: + ldx SOUND_CURRENT_SLOT ; load current sound slot + cpx #$01 ; compare to sound slot #$01 (pulse 2 channel) + bne sound_exit_00 ; exit if not sound slot 01 + lda $e6 ; sound slot #$01 (pulse 2 channel) + cmp #$57 ; see if boss heart destroyed - big blast with echo + bne sound_exit_00 ; exit if not sound_57 + lda LEVEL_END_PLAYERS_ALIVE ; see if any players are alive still at after defeating boss heart + beq sound_exit_00 ; exit if no players are alive + lda #$46 ; a = #$46 (sound_46) + jsr play_sound ; play end of level song + ; could have been optimized to a jmp and no rts !(OBS) + rts + +init_triangle_channel: + lda #$0b ; %0000 1011 + sta APU_STATUS ; disable triangle channel (while also enabling noise, and pulse channels) + lda #$00 ; a = #$00 + sta APU_TRIANGLE_CONFIG + lda #$0f ; a = #$0f + sta APU_STATUS ; re-enable triangle channel (enable noise, triangle, and pulse channels) + rts + +init_pulse_channel: + ldx SOUND_CHNL_REG_OFFSET ; load pulse waive channel register offset, i.e. #$04 for second pulse channel #$00 for first + lda #$30 ; a = #$30 + sta APU_PULSE_CONFIG,x ; mute the pulse channel 0 register + jsr wait ; execute #$0a nop instructions + lda #$7f ; bit 7 set to 0 all other bits 1 + sta APU_PULSE_SWEEP,x ; disable pulse 1 channel sweep + txa + lsr + lsr + tax + lda SOUND_CODE,x + beq sound_code_00 ; branch if sound code is #$00 + ldy SOUND_CHNL_REG_OFFSET ; load sound channel config register offset (#$00, #$04, $08, or #$0c) + jsr mute_unmute_pulse_channel ; mutes/unmutes pulse wave channel based on pause state + ldx SOUND_CURRENT_SLOT ; load current sound slot + rts + +; mutes the noise channel +mute_noise_channel: + lda #$30 ; a = #$30 + sta APU_NOISE_CONFIG ; initialize noise config with no volume + lda $e6 + cmp #$4a ; compare to the end credits music + bne sound_exit_00 ; + lda #$4e ; end credits finished, load a = #$4e (sound_4e) + jsr play_sound ; play after credits music (presented by konami) + ; could have been optimized to jmp with no rts + rts + +sound_code_00: + ldx SOUND_CURRENT_SLOT ; load current sound slot + lda $e6 + jmp sound_exit_00 + +; pointer table for ? (#$7 * #$2 = e bytes) +channel_init_ptr_tbl: + .addr mute_channel ; CPU address $8651 + .addr mute_channel ; CPU address $8651 + .addr init_triangle_channel ; CPU address $8673 + .addr mute_noise_channel ; CPU address $86a6 + .addr init_pulse_channel ; CPU address $8683 + .addr mute_noise_channel ; CPU address $86a6 + .addr mute_channel ; CPU address $8651 (not sure if possible, only #$06 sound slots) !(WHY?) + +; pointer table for ? (#$4 * #$2 = #$8 bytes) +sound_cmd_ptr_tbl: + .addr sound_cmd_routine_00 ; CPU address $8500 - set sound channel configuration, advance sound command address + .addr sound_cmd_routine_01 ; CPU address $8522 - set in memory configuration for channel, set multiplier, and sometimes read_high_sound_cmd + .addr sound_cmd_routine_02 ; CPU address $855c - set/adjust pitch, and read_high_sound_cmd + .addr sound_cmd_routine_03 ; CPU address $84a2 - move to next (child or parent) sound command, or finished with entire sound command and re-initialize channel + +; table for note period to use when writing notes to the APU (#$30 bytes) +; the frequency of the pulse channels is a division of the CPU Clock (1.789773MHz NTSC, 1.662607MHz PAL) +; the output frequency (f) of the generator can be determined by the 11-bit period value (f_pulse) written to $4002–$4003/$4006–$4007 +; note that triangle channel is one octave lower +; frequency = cpu_speed / (#$0f * (f_pulse + 1)) +; ex: 1789773 / (#$0f * (#$06ae + 1)) => 65.38 Hz +note_period_tbl: + .byte $ae,$06 ; $06AE - 1,710 - 65.38 Hz (c 2/deep c) + .byte $4e,$06 ; $064E - 1,614 - 69.26 Hz (c#/d flat 2) + .byte $f4,$05 ; $05F4 - 1,524 - 73.35 Hz (d 2) + .byte $9e,$05 ; $059E - 1,438 - 77.74 Hz (d#/e flat 2) + .byte $4e,$05 ; $054E - 1,358 - 82.31 Hz (e 2) + .byte $01,$05 ; $0501 - 1,281 - 87.25 Hz (f 2) + .byte $b9,$04 ; $04B9 - 1,209 - 92.45 Hz (f#/g flat 2) + .byte $76,$04 ; $0476 - 1,142 - 97.87 Hz (g 2) + .byte $36,$04 ; $0436 - 1,078 - 103.67 Hz (g#/a flat 2) + .byte $f9,$03 ; $03F9 - 1,017 - 109.88 Hz (a 2) + .byte $c0,$03 ; $03C0 - 960 - 116.40 Hz (a#/b flat 2) + .byte $8a,$03 ; $038A - 906 - 123.33 Hz (b 2) + .byte $57,$03 ; $0357 - 855 - 130.68 Hz (c 3) + .byte $27,$03 ; $0327 - 807 - 138.44 Hz (c#/d flat 3) + .byte $fa,$02 ; $02FA - 762 - 146.61 Hz (d 3) + .byte $cf,$02 ; $02CF - 719 - 155.36 Hz (d#/e flat 3) + .byte $a7,$02 ; $02A7 - 679 - 164.50 Hz (e 3) + .byte $81,$02 ; $0281 - 641 - 174.24 Hz (f 3) + .byte $5d,$02 ; $025D - 605 - 184.59 Hz (f#/g flat 3) + .byte $3b,$02 ; $023B - 571 - 195.56 Hz (g 3) + .byte $1b,$02 ; $021B - 539 - 207.15 Hz (g#/a flat 3) + .byte $fd,$01 ; $01FD - 509 - 219.33 Hz (a 3) + .byte $e0,$01 ; $01E0 - 480 - 232.56 Hz (b 3) + .byte $c5,$01 ; $01C5 - 453 - 246.39 Hz (c 4/middle c) + +; 10 nop instructions +wait: + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + rts + +; pointer for master lookup table (#$2 bytes) +sound_master_ptr_tbl: + .addr sound_table_00 ; CPU address $88e8 + +; set pulse channel duty cycle, volume, and sweep data, mute noise channel +init_pulse_and_noise_channels: + txa ; temporarily save current value of x to be restored after function call + pha ; push a to stack + tya ; temporarily save current value of y to be restored after function call + pha ; push a to stack + lda #$00 + tax + +; write #$00-#$05 to $0106 +@loop: + sta SOUND_CODE,x + inx + cpx #$06 + bcc @loop + sta $0140 + sta $013e + sta UNKNOWN_SOUND_01 ; adjusts pulse channel control (see set_pulse_config) + jsr init_triangle_channel ; re-initialize triangle channel + lda #$30 ; both duty cycle bits set to 1 (%0011 0000) + sta APU_PULSE_CONFIG ; set the duty cycle of pulse 1 channel to 75% high + ; duty affects the audio's tone + jsr wait ; execute #$0a nop instructions + sta APU_PULSE2_CONFIG ; set the duty cycle of pulse 2 channel to 75% high + jsr wait ; execute #$0a nop instructions + sta APU_NOISE_CONFIG ; mute noise channel + jsr wait ; execute #$0a nop instructions + ldx #$7f ; load disable pulse channel sweep value + stx APU_PULSE_SWEEP ; disable pulse 1 channel sweep + jsr wait ; execute #$0a nop instructions + stx APU_PULSE2_SWEEP ; disable pulse 2 channel sweep + pla ; pop saved value y back from stack + tay ; restore y value from before init_pulse_and_noise_channels + pla ; pop saved value x back from stack + tax ; restore x value from before init_pulse_and_noise_channels + rts + +; reset triangle, pulse, and noise channels +reset_channels: + jsr init_triangle_channel ; re-initialize triangle channel + lda #$30 ; a = #$30 + sta APU_PULSE_CONFIG ; set constant volume and halt any envelope loops + jsr wait ; execute #$0a nop instructions + sta APU_PULSE2_CONFIG ; set constant volume and halt any envelope loops + jsr wait ; execute #$0a nop instructions + sta APU_NOISE_CONFIG ; set constant volume and halt any envelope loops + jsr wait ; execute #$0a nop instructions + ldx #$7f ; x = #$7f + stx APU_PULSE_SWEEP ; disable pulse 1 channel sweep + jsr wait ; execute #$0a nop instructions + stx APU_PULSE2_SWEEP ; disable pulse 2 channel sweep + rts + +; dead code, never called !(UNUSED) +bank_1_unused_label_00: + txa + pha + tya + pha + ldx #$00 ; x = #$00 + stx $e6 + lda $010a + beq @pop_and_exit + stx $010a ; set sound channel config register offset (#$00, #$04, $08, or #$0c) + stx SOUND_CHNL_REG_OFFSET ; set sound channel config register offset (#$00, #$04, $08, or #$0c) + jsr init_pulse_channel + +@pop_and_exit: + pla + tay + pla + tax + rts + +; dead code, never called !(UNUSED) +bank_1_unused_label_01: + sta $013e + sta $0141 + lda #$00 ; a = #$00 + sta UNKNOWN_SOUND_01 ; reset pulse channel adjustment (see set_pulse_config) + rts + +; plays the specified sound +; input +; * a - the sound code to play +init_sound_code_vars: + sta INIT_SOUND_CODE ; store the sound code to play + cmp #$01 + bne @check_dmc_init ; branch if sound code isn't #$01 + jmp init_pulse_and_noise_channels ; sound code #$01, init pulse and noise channels + ; sets pulse channel duty cycle, volume, and sweep data, mute noise channel + +@check_dmc_init: + cmp #$5a ; compare sound code to #$5a + bcc @check_boss_heart_destroyed_sound ; branch if sound code is less than #$5a + jmp play_dpcm_sample ; sound code larger than or equal to #$5a, sound code is a DPCM sample + ; jump to configure DMC (delta modulation channel) and play sample + +@check_boss_heart_destroyed_sound: + cmp #$57 ; compare to #$57 (sound_57) (boss heart destroyed - big blast with echo) + bne @check_end_level_sound ; branch if not boss heart destroyed sound + jsr init_pulse_and_noise_channels ; sound code #$57 - boss heart destroyed, initialize pulse and noise channels + ; sets pulse channel duty cycle, volume, and sweep data, mute noise channel + jmp @play_sound ; play sound boss heart destroyed - big blast with echo + +@check_end_level_sound: + cmp #$46 ; compare to #$46 (sound_46) (end of level song) + bne @check_dmc_play_sound ; branch to play the sound if not end of level song + lda SOUND_CODE+1 ; sound code is end of level sound, load sound code of next sound channel + cmp #$57 ; wait for SOUND_CODE of next sound channel to clear + bne @play_end_of_level_tune ; play end of level tune if not already playing it + rts ; SOUND_CODE+1 is #$57, exit + +; play end of level tune if not already playing it +@play_end_of_level_tune: + cmp #$46 ; see if currently playing end of level song + bne @check_dmc_play_sound ; continue to play end of level tune if not already playing it + rts ; exit if already playing the end of level tune + +; plays a sound from the sound table, but first checks for specific sounds to +; see if dmc counter needs to be cleared +; input +; * a - sound code to play +@check_dmc_play_sound: + cmp #$2a ; see if starting to play jungle/hangar song + beq @init_dmc_sample_value ; branch to clear dmc counter before playing sound if jungle/hangar + cmp #$2e ; see if starting to play waterfall waterfall + bne @play_sound ; branch to play sound without clearing dmc counter if not waterfall + +; jungle, hangar, and waterfall +@init_dmc_sample_value: + lda #$00 ; a = #$00 + sta APU_DMC_COUNTER ; reset DMC starting vale for sample + +@play_sound: + txa ; backup x to a + pha ; push a to stack (backup) + tya ; backup y to a + pha ; push a to stack (backup) + lda sound_master_ptr_tbl ; load low byte of sound_table_00 (only one entry in sound_master_ptr_tbl) + sta SOUND_TABLE_PTR ; store low byte of sound_table_00 + lda sound_master_ptr_tbl+1 ; load high byte of sound_table_00 (only one entry in sound_master_ptr_tbl) + sta SOUND_TABLE_PTR+1 ; store high byte of sound_table_00 + lda #$03 ; read counter index (each entry is #$03 bytes) + sta $ea ; set loop counter to 3 so that the entire entry is read + +; each entry in sound_master_ptr_tbl is 3 bytes, find offset based on INIT_SOUND_CODE by looping +@loop: + lda INIT_SOUND_CODE ; load the sound code to play + clc ; clear carry in preparation for addition + adc SOUND_TABLE_PTR ; add INIT_SOUND_CODE to the low byte of the of the sound_table_00 (keeping track of if a carry happens) + sta SOUND_TABLE_PTR ; store offset back to SOUND_TABLE_PTR + lda #$00 + adc SOUND_TABLE_PTR+1 ; add 1 to the high byte if a carry happened when adding INIT_SOUND_CODE to low byte + sta SOUND_TABLE_PTR+1 ; set new value if overflow + dec $ea ; decrement read calculation counter (3 rounds to get actual offset into sound_table_00) + ; this is essentially multiplying INIT_SOUND_CODE by 3 and adding that result to SOUND_TABLE_PTR + bne @loop ; continue if haven't calculated final read index + ldy #$00 ; y = #$00 + sty $eb ; set number of bytes read to #$00 + lda (SOUND_TABLE_PTR),y ; read first byte of sound_code entry in the sound_table_00 + lsr + lsr + lsr + and #$03 ; keep bits ...x x... of original byte 0 (control byte) of sound_code entry + ; this is the total number of sound slots to fill for the specified sound code + sta $ea ; set how many times to loop load_sound_code_entry + +; read the sound code entry and load appropriate variables in memory as part of init_sound_code_vars +load_sound_code_entry: + lda $eb ; load number of entries away from initial entry to read (can be #$00) + asl ; double the value + clc ; clear carry in preparation for addition + adc $eb ; add entry to itself + ; since each entry is #$03 bytes, multiplied by #$03 to get new offset + tay ; transfer sound_table_00 read offset to y + lda (SOUND_TABLE_PTR),y ; read first byte from sound_table_00 entry triple (control byte) + and #$07 ; keep bits .... .xxx (sound slot offset) + tax ; transfer offset to x + lda INIT_SOUND_CODE ; load the sound code to play + cmp SOUND_CODE,x ; see if another sound code (sound effect) is playing for the current slot + bcc play_sound_code_exit ; exit if a existing sound gets priority + lda #$00 ; a = #$00 + sta SOUND_CODE,x ; clear sound code slot value + iny ; increment sound_table_00 read offset + lda (SOUND_TABLE_PTR),y ; read low byte of sound_xx address from sound_table_00 triple + sta SOUND_CMD_LOW_ADDR,x ; set low byte of sound_xx address + sta $e8 ; set low byte of sound_xx address + iny ; increment read offset + lda (SOUND_TABLE_PTR),y ; load high byte of sound_xx address + sta SOUND_CMD_HIGH_ADDR,x ; set high byte of sound_xx address + sta $e9 ; set high byte of sound_xx address + lda #$f8 ; a = #$f8 + sta SOUND_PULSE_LENGTH,x ; set pulse length to #$f8 (note length 30) + lda #$01 ; a = #$01 + sta SOUND_CMD_LENGTH,x ; initialize SOUND_CMD_LENGTH to #$01 so that when sound command is read for the first time + ; the data is parsed as a new sound and not a sound that's already playing (@check_sound_command) + cpx #$03 ; see if sound slot is #$03 or more + bcs @continue ; branch if sound slot is #$03 or greater + cpx #$02 ; see if sound slot is #$02 + beq @clear_pitch_adj ; branch if sound slot #$02 + lda #$80 ; sound slot #$00 or #$01 (pulse channel 1 or 2) set a = #$80 + sta VIBRATO_CTRL,x ; set to not use vibrato (0 = yes, #$80 = no) + +@clear_pitch_adj: + lda #$00 ; a = #$00 + sta SOUND_PITCH_ADJ,x ; clear sound period adjustment value + +@continue: + lda #$00 ; a = #$00 + sta SOUND_REPEAT_COUNT,x ; initialize shared sound part repeat counter to #$00 + tay ; set sound_xx read offset to #$00 + lda ($e8),y ; read sound_xx byte 0 + iny ; increment sound_xx read offset + cmp #$30 + bcc @config_channel + dey ; control code greater than or equal to #$30 + ; y will be #$00 for setting SOUND_FLAGS,x + ; indicating to use read_high_sound_cmd + sty $013e + sty UNKNOWN_SOUND_01 + +@config_channel: + tya ; transfer sound_xx read offset to a + sta SOUND_FLAGS,x ; set to either #$00 or #$01 depending whether byte 0 is greater than #$30 of sound_xx + ; 0 when sound byte is greater than or equal to #$30, 1 is when sound byte is less than #$30 + ; this specifies whether the sound commands in the code will be processed as a 'high code', or a 'low code' + lda INIT_SOUND_CODE ; load sound code to play + sta SOUND_CODE,x ; store sound code to play in sound slot x + txa ; transfer sound slot index to a + cmp #$01 ; compare to #$01 (pulse 2 channel) + bcc move_to_pulse_2 ; branch if pulse 1 channel + cpx #$05 ; see if sound slot #$05 (noise channel) + bne convert_slot_to_offset ; branch if not sound slot #$05 (noise channel) + lda #$0c ; sound slot #$05 (noise channel), set sound channel register offset to #$0c (noise/dmc channel) + jmp cfg_channel + +; converts the channel slot index to the channel register offset +; * #$00: #$00 (pulse 1 channel) +; * #$01: #$04 (pulse 2 channel) +; * #$02: #$08 (triangle channel) +; * #$03: #$0c (noise/dmc channel) +; * #$04: #$00 (pulse 1 channel) +; * #$05: do not call this method for sound slot #$05 +; input +; * a - sound slot index +; output +; * a - sound channel register offset (#$00, #$04, #$08, or #$0c) +; note: does not work for sound slot #$05, must check if slot #$05 before calling this method +convert_slot_to_offset: + asl + asl + and #$0f ; keep low nibble + +; configure pulse or noise channel +cfg_channel: + tax ; transfer sound channel register offset to x + lda #$00 ; a = #$00 + cpx #$08 ; see if sound channel register offset is triangle channel + beq @set_cfg_exit ; exit if triangle channel + lda #$30 ; set pulse channel or noise channel to play note indefinitely and that v bits will control volume + ; until another value is written to pulse to $4000/$4004 + +@set_cfg_exit: + sta $4000,x ; either set pulse channel or noise channel to #$30 or set triangle channel to #$00 + jsr wait ; execute #$0a nop instructions + lda #$7f ; a = #$7f (disable sweep) + sta APU_PULSE_SWEEP,x ; disable sweep + +play_sound_code_exit: + inc $eb ; move to next entry in sound_table_00 + dec $ea ; load number of additional slots required for sound code + bpl @load_entry + pla + tay + pla + tax + rts + +@load_entry: + jmp load_sound_code_entry + +; update +; input +; * a - sound slot index +; * x - sound slot +; output +; * a - sound slot index for pulse 2 channel (#$04) +move_to_pulse_2: + ora #$04 ; set bits .... .x.. + tay + lda SOUND_CODE,y + bne play_sound_code_exit + txa ; transfer sound slot to a + jmp convert_slot_to_offset ; determine sound channel register offset for slot + +; configure DMC (delta modulation channel) and play DPCM sample from bank 7 +; !(BUG?) special sound code #$ff (used by end of snow field level after boss defeated) +; sounds like door sliding opening +; * dpcm_sample_data_tbl offset is #$94 +; * sampling rate #$03 (5593.04 Hz), no loop +; * counter length #$77 +; * offset #$97 --> $c000 + (#$97 * #$40) --> $e5c0 --> (collision_box_codes_03) +; * sample length #$19 +play_dpcm_sample: + sec ; set carry flag in preparation for subtraction + sbc #$5a ; sound codes larger than #$5a are commands to initialize the DMC + ; subtract #$5a to get actual initialization values (offset to dpcm_sample_data_tbl) + sta INIT_SOUND_CODE ; set dpcm_sample_data_tbl offset + tya ; back up y register, transfer to a + pha ; backup a by pushing to stack + lda INIT_SOUND_CODE ; load dpcm_sample_data_tbl offset + asl + asl ; quadruple since each entry is #$04 bytes + tay ; transfer to offset register + lda #$0f ; a = #$0f + sta APU_STATUS ; enable noise, triangle, and the 2 pulse channels + lda dpcm_sample_data_tbl,y ; load DMC configuration (max sampling rate, no looping) + sta APU_DMC ; set DMC configuration (max sampling rate, no looping) + lda dpcm_sample_data_tbl+1,y ; load DMC counter + sta APU_DMC_COUNTER ; set DMC counter + lda dpcm_sample_data_tbl+2,y ; load address of DPCM sample data (this value * #$40) + $c000 is final address + sta APU_DMC_SAMPLE_ADDR ; set address of DPCM sample data + lda dpcm_sample_data_tbl+3,y ; load length of sample + sta APU_DMC_SAMPLE_LEN ; set length of sample + lda #$1f ; a = #$1f + sta APU_STATUS ; enable DMC, noise, triangle, and the 2 pulse channels + pla ; restore a (backup of y) + tay ; restore y + rts + +; table for APU configuration (#$d bytes) +; byte 0 - APU_DMC - sampling rate (how many CPU cycles happen between playback samples), no looping +; - always $0f, highest sampling rate. NTSC 33,143.9 Hz, PAL 33,252.1 Hz +; byte 1 - APU_DMC_COUNTER +; byte 2 - APU_DMC_SAMPLE_ADDR +; byte 3 - APU_DMC_SAMPLE_LEN +dpcm_sample_data_tbl: + .byte $0f,$2f,$f0,$05 ; #$5a - sample address $fc00 (dpcm_sample_00) (#$51 bytes) + .byte $0f,$75,$f3,$25 ; #$5b - sample address $fcc0 (dpcm_sample_01) (#$251 bytes) + .byte $0f,$00,$f0,$05 ; #$5c - sample address $fc00 (dpcm_sample_00) (end of level) + .byte $0f + +; CPU address $88e8 +; main look-up table for sounds and music #$5E * #$3 = #$11a bytes) +; groups of #$03 bytes +; read by @play_sound +; byte 0 - ...y yxxx -> +; * x - sound slot offset +; * y - how many sound additional sound codes to play +; note that subsequent sound codes don't read/use the y bits of their byte 0 +; byte 1 and 2 - address to sound data +; see level_vert_scroll_and_song (bank 7) +sound_table_00: + .byte $00,$f3,$25 ; these bytes are for dpcm_sample_data_tbl, lowest sound code is #$01 + ; sample address $fcc0 (dpcm_sample_01) + + ; unused, empty/silence (noise/dmc channel) + ; #$00 additional sound code entries, sound slot #$03 + .byte $03 + .addr sound_01 ; CPU address $8a24 + + ; percussive tick (bass drum/tom drum), used by other sound codes (noise channel) + ; #$00 additional sound code entries, sound slot #$05 + .byte $05 + .addr sound_02 ; CPU address $8a02 + + ; player landing on ground or water (pulse 1 channel) + ; #$01 additional sound code entry, sound slot #$04 + .byte $0c + .addr sound_03 ; CPU address $8a25 + + ; FOOT - player landing on ground or water (noise channel) + ; #$00 additional sound code entries, sound slot #$05 + .byte $05 + .addr sound_04 ; CPU address $8a36 + + ; ROCK - waterfall rock landing on ground (pulse 1 channel) + ; #$00 additional sound code entries, sound slot #$04 + .byte $04 + .addr sound_05 ; CPU address $8a41 + + ; TYPE 1 - unused, keyboard typing in Japanese version of game (pulse 1 channel) + ; #$01 additional sound code entry, sound slot #$04 + .byte $0c + .addr sound_06 ; CPU address $8a78 + + ; TYPE 1 - unused, keyboard typing in Japanese version of game (noise channel) + ; #$00 additional sound code entries, sound slot #$05 + .byte $05 + .addr sound_07 ; CPU address $8a82 + + ; unused, rumbling (noise channel) + ; #$00 additional sound code entries, sound slot #$05 + .byte $05 + .addr sound_08 ; CPU address $8a90 + + ; FIRE - energy zone fire beam (noise channel) + ; #$00 additional sound code entries, sound slot #$05 + .byte $05 + .addr sound_09 ; CPU address $8c81 + + ; SHOTGUN1 - regular bullet firing (pulse 1 channel) + ; #$01 additional sound code entry, sound slot #$04 + .byte $0c + .addr sound_0a ; CPU address $8ab3 + + ; SHOTGUN1 - regular bullet firing (noise channel) + ; #$00 additional sound code entries, sound slot #$05 + .byte $05 + .addr sound_0b ; CPU address $8adc + + ; SHOTGUN2 - M weapon firing, turret man (pulse 1 channel) + ; #$01 additional sound code entry, sound slot #$04 + .byte $0c + .addr sound_0c ; CPU address $8b05 + + ; SHOTGUN2 - M weapon firing, turret man (noise channel) + ; #$00 additional sound code entries, sound slot #$05 + .byte $05 + .addr sound_0d ; CPU address $8b32 + + ; LASER - L weapon firing (pulse 1 channel) + ; #$01 additional sound code entry, sound slot #$04 + .byte $0c + .addr sound_0e ; CPU address $8b59 + + ; LASER - L weapon firing (noise channel) + ; #$00 additional sound code entries, sound slot #$05 + .byte $05 + .addr sound_0f ; CPU address $8b90 + + ; PL FIRE - f bullet firing (pulse 1 channel) + ; #$01 additional sound code entry, sound slot #$04 + .byte $0c + .addr sound_10 ; CPU address $8bf3 + + ; PL FIRE - f bullet firing (noise channel) + ; #$00 additional sound code entries, sound slot #$05 + .byte $05 + .addr sound_11 ; CPU address $8c07 + + ; SPREAD - s bullet firing (pulse 1 channel) + ; #$01 additional sound code entry, sound slot #$04 + .byte $0c + .addr sound_12 ; CPU address $8c19 + + ; SPREAD - s bullet firing (noise channel) + ; #$00 additional sound code entries, sound slot #$05 + .byte $05 + .addr sound_13 ; CPU address $8c27 + + ; HIBIWARE - bullet shielded wall plating ting (pulse 1 channel) + ; #$00 additional sound code entries, sound slot #$04 + .byte $04 + .addr sound_14 ; CPU address $8cba + + ; CHAKUCHI - energy zone boss landing (pulse 1 channel) + ; #$00 additional sound code entries, sound slot #$04 + .byte $04 + .addr sound_15 ; CPU address $8d49 + + ; DAMEGE 1 - bullet to metal collision ting (pulse 1 channel) + ; #$01 additional sound code entry, sound slot #$04 + .byte $0c + .addr sound_16 ; CPU address $8cd4 + + ; DAMEGE 1 - bullet to metal collision ting (noise channel) + ; #$00 additional sound code entries, sound slot #$05 + .byte $05 + .addr sound_17 ; CPU address $8cf7 + + ; DAMEGE 2 - alien heart boss hit (pulse 1 channel) + ; #$00 additional sound code entries, sound slot #$04 + .byte $04 + .addr sound_18 ; CPU address $8d1c + + ; TEKI OUT - enemy destroyed (pulse 1 channel) + ; #$00 additional sound code entries, sound slot #$04 + .byte $04 + .addr sound_19 ; CPU address $8d33 + + ; HIRAI 1 - ice grenade whistling noise (pulse 1 channel) + ; #$00 additional sound code entries, sound slot #$04 + .byte $04 + .addr sound_1a ; CPU address $8ddd + + ; SENSOR - level 1 jungle boss siren (pulse 1 channel) + ; #$00 additional sound code entries, sound slot #$04 + .byte $04 + .addr sound_1b ; CPU address $8d76 + + ; KANDEN - electrocution sound (pulse 1 channel) + ; #$01 additional sound code entry, sound slot #$04 + .byte $0c + .addr sound_1c ; CPU address $8ea5 + + ; KANDEN - electrocution sound (noise channel) + ; #$00 additional sound code entries, sound slot #$05 + .byte $05 + .addr sound_1d ; CPU address $8ec6 + + ; CAR - tank advancing (pulse 1 channel) + ; #$00 additional sound code entries, sound slot #$04 + .byte $04 + .addr sound_1e ; CPU address $8e2f + + ; POWER UP - pick up weapon item (pulse 1 channel) + ; #$00 additional sound code entries, sound slot #$04 + .byte $04 + .addr sound_1f ; CPU address $8e47 + + ; 1UP - extra life (pulse 1 channel) + ; #$00 additional sound code entries, sound slot #$04 + .byte $04 + .addr sound_20 ; CPU address $8e5e + + ; HERI - helicopter rotors (pulse 1 channel) + ; #$02 additional sound code entries, sound slot #$04 + .byte $14 + .addr sound_21 ; CPU address $8ee3 + + ; HERI - helicopter rotors (pulse 2 channel) + ; #$00 additional sound code entries, sound slot #$01 + .byte $01 + .addr sound_21 ; CPU address $8ee3 + + ; HERI - helicopter rotors (noise channel) + ; #$00 additional sound code entries, sound slot #$05 + .byte $05 + .addr sound_23 ; CPU address $8f83 + + ; BAKUHA 1 - explosion (noise channel) + ; #$00 additional sound code entries, sound slot #$05 + .byte $05 + .addr sound_24 ; CPU address $8fc2 + + ; BAKUHA 2 - game intro explosion, indoor wall explosion, and island explosion (noise channel) + ; #$00 additional sound code entries, sound slot #$05 + .byte $05 + .addr sound_25 ; CPU address $9001 + + ; TITLE - game intro tune (pulse 1 channel) + ; #$03 additional sound code entries, sound slot #$00 + .byte $18 + .addr sound_26 ; CPU address $9195 + + ; TITLE - game intro tune (pulse 2 channel) + ; #$00 additional sound code entries, sound slot #$01 + .byte $01 + .addr sound_27 ; CPU address $91ab + + ; TITLE - game intro tune (triangle channel) + ; #$00 additional sound code entries, sound slot #$02 + .byte $02 + .addr sound_28 ; CPU address $91c3 + + ; TITLE - game intro tune (noise/dmc channel) + ; #$00 additional sound code entries, sound slot #$03 + .byte $03 + .addr sound_29 ; CPU address $91d3 + + ; BGM 1 - level 1 jungle and level 7 hangar music (pulse 1 channel) + ; #$03 additional sound code entries, sound slot #$00 + .byte $18 + .addr sound_2a ; CPU address $9428 + + ; BGM 1 - level 1 jungle and level 7 hangar music (pulse 2 channel) + ; #$00 additional sound code entries, sound slot #$01 + .byte $01 + .addr sound_2b ; CPU address $924e + + ; BGM 1 - level 1 jungle and level 7 hangar music (triangle channel) + ; #$00 additional sound code entries, sound slot #$02 + .byte $02 + .addr sound_2c ; CPU address $95c7 + + ; BGM 1 - level 1 jungle and level 7 hangar music (noise/dmc channel) + ; #$00 additional sound code entries, sound slot #$03 + .byte $03 + .addr sound_2d ; CPU address $9775 + + ; BGM 2 - level 3 waterfall music (pulse 1 channel) + ; #$03 additional sound code entries, sound slot #$00 + .byte $18 + .addr sound_2e ; CPU address $9985 + + ; BGM 2 - level 3 waterfall music (pulse 2 channel) + ; #$00 additional sound code entries, sound slot #$01 + .byte $01 + .addr sound_2f ; CPU address $9a71 + + ; BGM 2 - level 3 waterfall music (triangle channel) + ; #$00 additional sound code entries, sound slot #$02 + .byte $02 + .addr sound_30 ; CPU address $9b67 + + ; BGM 2 - level 3 waterfall music (noise/dmc channel) + ; #$00 additional sound code entries, sound slot #$03 + .byte $03 + .addr sound_31 ; CPU address $9bce + + ; BGM 3 - level 5 snow field music (pulse 1 channel) + ; #$03 additional sound code entries, sound slot #$00 + .byte $18 + .addr sound_32 ; CPU address $9ca4 + + ; BGM 3 - level 5 snow field music (pulse 2 channel) + ; #$00 additional sound code entries, sound slot #$01 + .byte $01 + .addr sound_33 ; CPU address $9d32 + + ; BGM 3 - level 5 snow field music (triangle channel) + ; #$00 additional sound code entries, sound slot #$02 + .byte $02 + .addr sound_34 ; CPU address $9d9a + + ; BGM 3 - level 5 snow field music (noise/dmc channel) + ; #$00 additional sound code entries, sound slot #$03 + .byte $03 + .addr sound_35 ; CPU address $9e1e + + ; BGM 4 - level 6 energy zone (pulse 1 channel) + ; #$03 additional sound code entries, sound slot #$00 + .byte $18 + .addr sound_36 ; CPU address $9ea8 + + ; BGM 4 - level 6 energy zone (pulse 2 channel) + ; #$00 additional sound code entries, sound slot #$01 + .byte $01 + .addr sound_37 ; CPU address $9f46 + + ; BGM 4 - level 6 energy zone (triangle channel) + ; #$00 additional sound code entries, sound slot #$02 + .byte $02 + .addr sound_38 ; CPU address $9fb8 + + ; BGM 4 - level 6 energy zone (noise/dmc channel) + ; #$00 additional sound code entries, sound slot #$03 + .byte $03 + .addr sound_39 ; CPU address $a003 + + ; BGM 5 - level 8 alien's lair music (pulse 1 channel) + ; #$03 additional sound code entries, sound slot #$00 + .byte $18 + .addr sound_3a ; CPU address $a092 + + ; BGM 5 - level 8 alien's lair music (pulse 2 channel) + ; #$00 additional sound code entries, sound slot #$01 + .byte $01 + .addr sound_3b ; CPU address $a1a7 + + ; BGM 5 - level 8 alien's lair music (triangle channel) + ; #$00 additional sound code entries, sound slot #$02 + .byte $02 + .addr sound_3c ; CPU address $a295 + + ; BGM 5 - level 8 alien's lair music (noise/dmc channel) + ; #$00 additional sound code entries, sound slot #$03 + .byte $03 + .addr sound_3d ; CPU address $a32f + + ; 3D BGM - indoor/base level music (pulse 1 channel) + ; #$03 additional sound code entries, sound slot #$00 + .byte $18 + .addr sound_3e ; CPU address $a468 + + ; 3D BGM - indoor/base level music (pulse 2 channel) + ; #$00 additional sound code entries, sound slot #$01 + .byte $01 + .addr sound_3f ; CPU address $a570 + + ; 3D BGM - indoor/base level music (triangle channel) + ; #$00 additional sound code entries, sound slot #$02 + .byte $02 + .addr sound_40 ; CPU address $a5eb + + ; 3D BGM - indoor/base level music (noise/dmc channel) + ; #$00 additional sound code entries, sound slot #$03 + .byte $03 + .addr sound_41 ; CPU address $a67a + + ; BOSS - indoor/base boss screen music (pulse 1 channel) + ; #$03 additional sound code entries, sound slot #$00 + .byte $18 + .addr sound_42 ; CPU address $a793 + + ; BOSS - indoor/base boss screen music (pulse 2 channel) + ; #$00 additional sound code entries, sound slot #$01 + .byte $01 + .addr sound_43 ; CPU address $a878 + + ; BOSS - indoor/base boss screen music (triangle channel) + ; #$00 additional sound code entries, sound slot #$02 + .byte $02 + .addr sound_44 ; CPU address $a8fb + + ; BOSS - indoor/base boss screen music (noise/dmc channel) + ; #$00 additional sound code entries, sound slot #$03 + .byte $03 + .addr sound_45 ; CPU address $aa0e + + ; PCLR - end of level tune (pulse 1 channel) + ; #$03 additional sound code entries, sound slot #$00 + .byte $18 + .addr sound_46 ; CPU address $aa92 + + ; PCLR - end of level tune (pulse 2 channel) + ; #$00 additional sound code entries, sound slot #$01 + .byte $01 + .addr sound_47 ; CPU address $aab3 + + ; PCLR - end of level tune (triangle channel) + ; #$00 additional sound code entries, sound slot #$02 + .byte $02 + .addr sound_48 ; CPU address $aad4 + + ; PCLR - end of level tune (noise/dmc channel) + ; #$00 additional sound code entries, sound slot #$03 + .byte $03 + .addr sound_49 ; CPU address $aaef + + ; ENDING - end credits (pulse 1 channel) + ; #$03 additional sound code entries, sound slot #$00 + .byte $18 + .addr sound_4a ; CPU address $ac9f + + ; ENDING - end credits (pulse 2 channel) + ; #$00 additional sound code entries, sound slot #$01 + .byte $01 + .addr sound_4b ; CPU address $ad1a + + ; ENDING - end credits (triangle channel) + ; #$00 additional sound code entries, sound slot #$02 + .byte $02 + .addr sound_4c ; CPU address $ae05 + + ; ENDING - end credits (noise/dmc channel) + ; #$00 additional sound code entries, sound slot #$03 + .byte $03 + .addr sound_4d ; CPU address $ae87 + + ; OVER - game over/after end credits, presented by konami (pulse 1 channel) + ; #$03 additional sound code entries, sound slot #$00 + .byte $18 + .addr sound_4e ; CPU address $ab34 + + ; OVER - game over/after end credits, presented by konami (pulse 2 channel) + ; #$00 additional sound code entries, sound slot #$01 + .byte $01 + .addr sound_4f ; CPU address $ab5c + + ; OVER - game over/after end credits, presented by konami (triangle channel) + ; #$00 additional sound code entries, sound slot #$02 + .byte $02 + .addr sound_50 ; CPU address $ab86 + + ; OVER - game over/after end credits, presented by konami (noise/dmc channel) + ; #$00 additional sound code entries, sound slot #$03 + .byte $03 + .addr sound_51 ; CPU address $abb2 + + ; PL OUT - player death (pulse 1 channel) + ; #$01 additional sound code entry, sound slot #$04 + .byte $0c + .addr sound_52 ; CPU address $9117 + + ; PL OUT - player death (noise channel) + ; #$00 additional sound code entries, sound slot #$05 + .byte $05 + .addr sound_53 ; CPU address $916a + + ; game pausing jingle (pulse 1 channel) + ; #$00 additional sound code entries, sound slot #$04 + .byte $04 + .addr sound_54 ; CPU address $8a0a + + ; BOSS BK - tank, boss ufo, boss giant, alien guardian destroyed (pulse 1 channel) + ; #$01 additional sound code entry, sound slot #$04 + .byte $0c + .addr sound_55 ; CPU address $903c + + ; BOSS BK - tank, boss ufo, boss giant, alien guardian destroyed (noise channel) + ; #$00 additional sound code entries, sound slot #$05 + .byte $05 + .addr sound_56 ; CPU address $9055 + + ; BOSS OUT - boss destroyed (pulse 1 channel) + ; #$02 additional sound code entries, sound slot #$04 + .byte $14 + .addr sound_57 ; CPU address $9094 + + ; BOSS OUT - boss destroyed (pulse 2 channel) + ; #$00 additional sound code entries, sound slot #$01 + .byte $01 + .addr sound_58 ; CPU address $9098 + + ; BOSS OUT - boss destroyed (noise channel) + ; #$00 additional sound code entries, sound slot #$05 + .byte $05 + .addr sound_59 ; CPU address $90dc + + ; #$00 additional sound code entries, sound slot #$03 (noise/dmc channel) + .byte $03 + .addr sound_01 ; CPU address $8a24 - 5a + + ; #$00 additional sound code entries, sound slot #$03 (noise/dmc channel) + .byte $03 + .addr sound_01 ; CPU address $8a24 - 5b + + ; #$00 additional sound code entries, sound slot #$03 (noise/dmc channel) + .byte $03 + .addr sound_01 ; CPU address $8a24 - 5c + + ; #$00 additional sound code entries, sound slot #$03 (noise/dmc channel) + .byte $03 + .addr sound_01 ; CPU address $8a24 - 5d + +; percussive tick (bass drum/tom drum), used by other sound codes (noise channel) +; CPU address $8a02 +sound_02: + .incbin "assets/audio_data/sound_02.bin" + +; game pausing jingle (pulse 1 channel) +sound_54: + .incbin "assets/audio_data/sound_54.bin" + +; CPU address $8a24 +; unused, empty/silence +sound_01: + .byte $ff + +; player landing on ground or water (pulse 1 channel) +; CPU address $8a25 +sound_03: + .incbin "assets/audio_data/sound_03.bin" + +; FOOT - player landing on ground or water (noise channel) +; CPU address $8a36 +sound_04: + .incbin "assets/audio_data/sound_04.bin" + +; ROCK - waterfall rock landing on ground (pulse 1 channel) +; CPU address $8a41 +sound_05: + .incbin "assets/audio_data/sound_05.bin" + +; TYPE 1 - unused, keyboard typing in Japanese version of game (pulse 1 channel) +; CPU address $8a78 +sound_06: + .incbin "assets/audio_data/sound_06.bin" + +; TYPE 1 - unused, keyboard typing in Japanese version of game (noise channel) +; CPU address $8a82 +sound_07: + .incbin "assets/audio_data/sound_07.bin" + +; unused, rumbling (noise/dmc channel) +; CPU address $8a90 +sound_08: + .incbin "assets/audio_data/sound_08.bin" + .addr sound_08 + +sound_08_part_00: + .incbin "assets/audio_data/sound_08_part_00.bin" + +; SHOTGUN1 - regular bullet firing (pulse 1 channel) +; CPU address $8ab3 +sound_0a: + .incbin "assets/audio_data/sound_0a.bin" + +; SHOTGUN1 - regular bullet firing (noise/dmc channel) +; CPU address $8adc +sound_0b: + .incbin "assets/audio_data/sound_0b.bin" + .addr sound_0b + +; CPU address $8aee +sound_0b_part_00: + .incbin "assets/audio_data/sound_0b_part_00.bin" + +; SHOTGUN2 - M weapon firing, turret man (pulse 1 channel) +; CPU address $8b05 +sound_0c: + .incbin "assets/audio_data/sound_0c.bin" + .addr sound_0c + +sound_0c_part_00: + .incbin "assets/audio_data/sound_0c_part_00.bin" + +; SHOTGUN2 - M weapon firing, turret man (noise/dmc channel) +; CPU address $8b32 +sound_0d: + .incbin "assets/audio_data/sound_0d.bin" + .addr sound_0d + +; CPU address $8b42 +sound_0d_part_00: + .incbin "assets/audio_data/sound_0d_part_00.bin" + +; LASER - L weapon firing (pulse 1 channel) +; CPU address $8b59 +sound_0e: + .incbin "assets/audio_data/sound_0e.bin" + +; LASER - L weapon firing (noise/dmc channel) +; CPU address $8b90 +sound_0f: + .incbin "assets/audio_data/sound_0f.bin" + +; PL FIRE - f bullet firing (pulse 1 channel) +; CPU address $8bf3 +sound_10: + .incbin "assets/audio_data/sound_10.bin" + .addr sound_10 + +sound_10_part_00: + .incbin "assets/audio_data/sound_10_part_00.bin" + +; PL FIRE - f bullet firing (noise/dmc channel) +; CPU address $8c07 +sound_11: + .incbin "assets/audio_data/sound_11.bin" + .addr sound_11 + +sound_11_part_00: + .incbin "assets/audio_data/sound_11_part_00.bin" + +; SPREAD - s bullet firing (pulse 1 channel) +; CPU address $8c19 +sound_12: + .incbin "assets/audio_data/sound_12.bin" + +; SPREAD - s bullet firing (noise/dmc channel) +; CPU address $8c27 +sound_13: + .incbin "assets/audio_data/sound_13.bin" + +; FIRE - energy zone fire beam (noise/dmc channel) +; CPU address $8c81 +sound_09: + .incbin "assets/audio_data/sound_09.bin" + +; HIBIWARE - bullet shielded wall plating ting (pulse 1 channel) +; CPU address $8cba +sound_14: + .incbin "assets/audio_data/sound_14.bin" + .addr sound_14 + +sound_14_part_00: + .incbin "assets/audio_data/sound_14_part_00.bin" + +; DAMEGE 1 - bullet to metal collision ting (pulse 1 channel) +; CPU address $8cd4 +sound_16: + .incbin "assets/audio_data/sound_16.bin" + +; DAMEGE 1 - bullet to metal collision ting (noise/dmc channel) +; CPU address $8cf7 +sound_17: + .incbin "assets/audio_data/sound_17.bin" + +; DAMEGE 2 - alien heart boss hit (pulse 1 channel) +; CPU address $8d1c +sound_18: + .incbin "assets/audio_data/sound_18.bin" + .addr sound_25_part_01 + +sound_18_part_00: + .incbin "assets/audio_data/sound_18_part_00.bin" + +; TEKI OUT - enemy destroyed (pulse 1 channel) +; CPU address $8d33 +sound_19: + .incbin "assets/audio_data/sound_19.bin" + +; CHAKUCHI - energy zone boss landing (pulse 1 channel) +; CPU address $8d49 +sound_15: + .incbin "assets/audio_data/sound_15.bin" + +; SENSOR - level 1 jungle boss siren (pulse 1 channel) +; CPU address $8d76 +sound_1b: + .incbin "assets/audio_data/sound_1b.bin" + .addr sound_1b_part_00 + .byte $fd + .addr sound_1b_part_00 + .byte $fd + .addr sound_1b_part_00 + .byte $ff + +; CPU address $8d84 +sound_1b_part_00: + .incbin "assets/audio_data/sound_1b_part_00.bin" + +; CPU address $8dae +sound_1b_part_01: + .byte $e1,$40,$a1,$41,$fe,$0c + .addr sound_1b_part_01 + +sound_1b_part_02: + .incbin "assets/audio_data/sound_1b_part_02.bin" + +; HIRAI 1 - ice grenade whistling noise (pulse 1 channel) +; CPU address $8ddd +sound_1a: + .incbin "assets/audio_data/sound_1a.bin" + +; CAR - tank advancing (pulse 1 channel) +; CPU address $8e2f +sound_1e: + .incbin "assets/audio_data/sound_1e.bin" + +; POWER UP - pick up weapon item (pulse 1 channel) +; CPU address $8e47 +sound_1f: + .incbin "assets/audio_data/sound_1f.bin" + +; 1UP - extra life (pulse 1 channel) +; CPU address $8e5e +sound_20: + .incbin "assets/audio_data/sound_20.bin" + +; KANDEN - electrocution sound (pulse 1 channel) +; CPU address $8ea5 +sound_1c: + .incbin "assets/audio_data/sound_1c.bin" + .addr sound_1c + +sound_1c_part_00: + .incbin "assets/audio_data/sound_1c_part_00.bin" + +; KANDEN - electrocution sound (noise/dmc channel) +; CPU address $8ec6 +sound_1d: + .incbin "assets/audio_data/sound_1d.bin" + .addr sound_1d + +sound_1d_part_00: + .incbin "assets/audio_data/sound_1d_part_00.bin" + +; HERI - helicopter rotors (pulse 1 and pulse 2 channel) +; CPU address $8ee3 +sound_21: + .incbin "assets/audio_data/sound_21.bin" + +; CPU address $8ef0 +sound_21_part_00: + .incbin "assets/audio_data/sound_21_part_00.bin" + .addr sound_21_part_00 + +; CPU address $8efd +sound_21_part_01: + .byte $83,$83,$43,$65,$33,$53,$f8,$13,$33,$fe,$10 + .addr sound_21_part_01 + +; CPU address $8f0a +sound_21_part_02: + .incbin "assets/audio_data/sound_21_part_02.bin" + .addr sound_21_part_02 + +; CPU address $8f17 +sound_21_part_03: + .incbin "assets/audio_data/sound_21_part_03.bin" + .addr sound_21_part_03 + +; CPU address $8f24 +sound_21_part_04: + .incbin "assets/audio_data/sound_21_part_04.bin" + .addr sound_21_part_04 + +; CPU address $8f31 +sound_21_part_05: + .incbin "assets/audio_data/sound_21_part_05.bin" + .addr sound_21_part_05 + +; CPU address $8f3e +sound_21_part_06: + .incbin "assets/audio_data/sound_21_part_06.bin" + .addr sound_21_part_06 + +; CPU address $8f4b +sound_21_part_07: + .incbin "assets/audio_data/sound_21_part_07.bin" + .addr sound_21_part_07 + +; CPU address $8f58 +sound_21_part_08: + .incbin "assets/audio_data/sound_21_part_08.bin" + .addr sound_21_part_08 + +; CPU address $8f66 +sound_21_part_09: + .incbin "assets/audio_data/sound_21_part_09.bin" + .addr sound_21_part_09 + +; CPU address $8f74 +sound_21_part_0a: + .incbin "assets/audio_data/sound_21_part_0a.bin" + .addr sound_21_part_0a + .byte $ff + +; HERI - helicopter rotors (noise/dmc channel) +; CPU address $8f83 +sound_23: + .incbin "assets/audio_data/sound_23.bin" + +; CPU address $8f85 +sound_23_part_00: + .incbin "assets/audio_data/sound_23_part_00.bin" + .addr sound_23_part_00 + +; CPU address $8f8f +sound_23_part_01: + .incbin "assets/audio_data/sound_23_part_01.bin" + .addr sound_23_part_01 + +; CPU address $8f97 +sound_23_part_02: + .incbin "assets/audio_data/sound_23_part_02.bin" + .addr sound_23_part_02 + +; CPU address $8f9f +sound_23_part_03: + .incbin "assets/audio_data/sound_23_part_03.bin" + .addr sound_23_part_03 + +; CPU address $8fa7 +sound_23_part_04: + .incbin "assets/audio_data/sound_23_part_04.bin" + .addr sound_23_part_04 + +; CPU address $8faf +sound_23_part_05: + .incbin "assets/audio_data/sound_23_part_05.bin" + .addr sound_23_part_05 + +; CPU address $8fb7 +sound_23_part_06: + .incbin "assets/audio_data/sound_23_part_06.bin" + .addr sound_23_part_06 + .byte $ff + +; BAKUHA 1 - explosion (noise/dmc channel) +; CPU address $8fc2 +sound_24: + .incbin "assets/audio_data/sound_24.bin" + .addr sound_24 + +sound_24_part_00: + .incbin "assets/audio_data/sound_24_part_00.bin" + +; BAKUHA 2 - game intro explosion, indoor wall explosion, and island explosion (noise/dmc channel) +; CPU address $9001 +sound_25: + .incbin "assets/audio_data/sound_25.bin" + .addr sound_25 + +sound_25_part_00: + .incbin "assets/audio_data/sound_25_part_00.bin" + +; CPU address $9035 +sound_25_part_01: + .incbin "assets/audio_data/sound_25_part_01.bin" + +; BOSS BK - tank, boss ufo, boss giant, alien guardian destroyed (pulse 1 channel) +; CPU address $903c +sound_55: + .incbin "assets/audio_data/sound_55.bin" + +; BOSS BK - tank, boss ufo, boss giant, alien guardian destroyed (noise channel) +; CPU address $9055 +sound_56: + .incbin "assets/audio_data/sound_56.bin" + .addr sound_56 + +sound_56_part_00: + .incbin "assets/audio_data/sound_56_part_00.bin" + +; BOSS OUT - boss destroyed (pulse 1 channel) +; CPU address $9094 +sound_57: + .incbin "assets/audio_data/sound_57.bin" + +; BOSS OUT - boss destroyed (pulse 2 channel) +; CPU address $9098 +sound_58: + .incbin "assets/audio_data/sound_58.bin" + +; BOSS OUT - boss destroyed (noise channel) +; CPU address $90dc +sound_59: + .incbin "assets/audio_data/sound_59.bin" + .addr sound_59 + +sound_59_part_00: + .incbin "assets/audio_data/sound_59_part_00.bin" + +; PL OUT - player death (pulse 1 channel) +; CPU address $9117 +sound_52: + .incbin "assets/audio_data/sound_52.bin" + +; PL OUT - player death (noise channel) +; CPU address $916a +sound_53: + .incbin "assets/audio_data/sound_53.bin" + +; TITLE - game intro tune (pulse 1 channel) +; CPU address $9195 +sound_26: + .incbin "assets/audio_data/sound_26.bin" + +; TITLE - game intro tune (pulse 2 channel) +; CPU address $91ab +sound_27: + .incbin "assets/audio_data/sound_27.bin" + +; TITLE - game intro tune (triangle channel) +; CPU address $91c3 +sound_28: + .incbin "assets/audio_data/sound_28.bin" + +; TITLE - game intro tune (noise/dmc channel) +; CPU address $91d3 +sound_29: + .incbin "assets/audio_data/sound_29.bin" + +; from pointer table at pulse_volume_ptr_tbl (level 1) +; volume envelop for pulse channel 2 on level 1 +lvl_1_pulse_volume_00: + .byte $05,$06,$07,$06,$05,$04,$03,$ff + +; volume control 3, 4, 3, 2 loops +lvl_1_pulse_volume_01: + .byte $03,$04,$03,$02,$ff + +lvl_1_pulse_volume_02: + .byte $07,$06,$05,$04,$03,$02,$01,$00,$00,$00,$02,$02,$ff + +lvl_1_pulse_volume_03: + .byte $06,$05,$04,$03,$02,$01,$00,$00,$00,$00,$01,$01,$ff + +lvl_1_pulse_volume_04: + .byte $07,$06,$05,$04,$03,$03,$02,$01,$00,$00,$00,$00,$00,$00,$00,$02 + .byte $02,$ff + +lvl_1_pulse_volume_05: + .byte $07,$06,$05,$04,$03,$00,$00,$00,$00,$00,$00,$00,$01,$01,$01,$01 + .byte $01,$01,$ff + +lvl_1_pulse_volume_06: + .byte $05,$04,$03,$02,$01,$01,$00,$00,$00,$00,$00,$00,$01,$01,$01,$01 + .byte $01,$01,$ff + +lvl_1_pulse_volume_07: + .byte $06,$05,$04,$03,$02,$02,$02,$01,$00,$00,$00,$00,$00,$00,$00,$01 + .byte $01,$ff + +; BGM 1 - level 1 jungle and level 7 hangar music (pulse 2 channel) +; CPU address $924e +sound_2b: + .byte $ec,$01,$eb,$2a,$22,$d6,$f7,$84,$00,$e1,$40,$20,$e2,$b0,$90,$b0 + .byte $90,$70,$60,$70,$60,$40,$20,$40,$e3,$90,$b0,$e2,$20 + +; CPU address $926b +sound_2b_part_00: + .incbin "assets/audio_data/sound_2b_part_00.bin" + .addr sound_2b_part_00 + +sound_2b_part_01: + .incbin "assets/audio_data/sound_2b_part_01.bin" + .addr sound_2b_part_00 + +; BGM 1 - level 1 jungle and level 7 hangar music (pulse 1 channel) +; CPU address $9428 +sound_2a: + .incbin "assets/audio_data/sound_2a.bin" + +; CPU address $9445 +sound_2a_part_00: + .incbin "assets/audio_data/sound_2a_part_00.bin" + .addr sound_2a_part_00 + +sound_2a_part_01: + .incbin "assets/audio_data/sound_2a_part_01.bin" + .addr sound_2a_part_00 + +; BGM 1 - level 1 jungle and level 7 hangar music (triangle channel) +; CPU address $95c7 - jungle/hangar triangle +sound_2c: + .incbin "assets/audio_data/sound_2c.bin" + +; CPU address $95df +sound_2c_part_00: + .incbin "assets/audio_data/sound_2c_part_00.bin" + .addr sound_2c_part_00 + +sound_2c_part_01: + .incbin "assets/audio_data/sound_2c_part_01.bin" + +; CPU address $96bc +sound_2c_part_02: + .incbin "assets/audio_data/sound_2c_part_02.bin" + .addr sound_2c_part_02 + +; CPU address $96c3 +sound_2c_part_03: + .incbin "assets/audio_data/sound_2c_part_03.bin" + .addr sound_2c_part_03 + +sound_2c_part_04: + .incbin "assets/audio_data/sound_2c_part_04.bin" + .addr sound_2c_part_00 + +; BGM 1 - level 1 jungle and level 7 hangar music (noise/dmc channel) +; CPU address $9775 +sound_2d: + .incbin "assets/audio_data/sound_2d.bin" + .addr sound_2d + +; CPU address $977e +sound_2d_part_00: + .incbin "assets/audio_data/sound_2d_part_00.bin" + +; CPU address $9782 +sound_2d_part_01: + .incbin "assets/audio_data/sound_2d_part_01.bin" + .addr sound_2d_part_01 + +sound_2d_part_02: + .incbin "assets/audio_data/sound_2d_part_02.bin" + +; CPU address $9856 +sound_2d_part_03: + .incbin "assets/audio_data/sound_2d_part_03.bin" + .addr sound_2d_part_03 + +sound_2d_part_04: + .incbin "assets/audio_data/sound_2d_part_04.bin" + +; CPU address $9877 +sound_2d_part_05: + .incbin "assets/audio_data/sound_2d_part_05.bin" + .addr sound_2d_part_05 + +sound_2d_part_06: + .incbin "assets/audio_data/sound_2d_part_06.bin" + +; CPU address $989a +sound_2d_part_07: + .incbin "assets/audio_data/sound_2d_part_07.bin" + .addr sound_2d_part_07 + +sound_2d_part_08: + .incbin "assets/audio_data/sound_2d_part_08.bin" + +; CPU address $98b9 +sound_2d_part_09: + .incbin "assets/audio_data/sound_2d_part_09.bin" + .addr sound_2d_part_09 + +sound_2d_part_0a: + .incbin "assets/audio_data/sound_2d_part_0a.bin" + .addr sound_2d_part_01 + +lvl_3_pulse_volume_07: + .byte $07,$06,$05,$04,$03,$03,$00,$00,$00,$02,$ff + +lvl_4_pulse_volume_00: + .byte $03,$04,$05,$06,$07,$07,$07,$06,$06,$06,$06,$05,$05,$05,$05,$04 + .byte $04,$04,$04,$03,$ff + +lvl_4_pulse_volume_01: + .byte $06,$07,$06,$05,$04,$03,$ff + +lvl_4_pulse_volume_02: + .byte $06,$07,$06,$05,$04,$03,$00,$00,$00,$00,$01,$ff + +lvl_4_pulse_volume_03: + .byte $04,$05,$06,$05,$04,$03,$ff + +lvl_4_pulse_volume_04: + .byte $04,$05,$04,$03,$ff + +lvl_4_pulse_volume_05: + .byte $03,$04,$03,$03,$03,$03,$00,$00,$00,$00,$01,$ff + +lvl_4_pulse_volume_06: + .byte $04,$05,$06,$05,$04,$03,$ff + +lvl_4_pulse_volume_07: + .byte $08,$07,$06,$05,$04,$03,$00,$00,$00,$00,$01,$ff + +lvl_5_pulse_volume_00: + .byte $ff + +; CPU address $9929 +sound_2e_part_00: + .incbin "assets/audio_data/sound_2e_part_00.bin" + .addr sound_2e_part_00 + +sound_2e_part_01: + .incbin "assets/audio_data/sound_2e_part_01.bin" + +; BGM 2 - level 3 waterfall music (pulse 1 channel) +; CPU address $9985 +sound_2e: + .incbin "assets/audio_data/sound_2e.bin" + +; CPU address $9999 +sound_2e_part_02: + .byte $fd + .addr sound_2e_part_00 + +sound_2e_part_03: + .incbin "assets/audio_data/sound_2e_part_03.bin" + .addr sound_2e_part_00 + +sound_2e_part_04: + .incbin "assets/audio_data/sound_2e_part_04.bin" + .addr sound_2e_part_02 + +; CPU address $9a2a +sound_2e_part_05: + .incbin "assets/audio_data/sound_2e_part_05.bin" + .addr sound_2e_part_05 + +sound_2e_part_06: + .incbin "assets/audio_data/sound_2e_part_06.bin" + +; BGM 2 - level 3 waterfall music (pulse 2 channel) +; CPU address $9a71 +sound_2f: + .incbin "assets/audio_data/sound_2f.bin" + +; CPU address $9a75 +sound_2f_part_00: + .incbin "assets/audio_data/sound_2f_part_00.bin" + .addr sound_2f_part_00 + +; CPU address $9a88 +sound_2f_part_01: + .byte $fd + .addr sound_2e_part_05 + +; CPU address $9a8b +sound_2f_part_02: + .incbin "assets/audio_data/sound_2f_part_02.bin" + +; CPU address $9a8f +sound_2f_part_03: + .incbin "assets/audio_data/sound_2f_part_03.bin" + .addr sound_2f_part_03 + .byte $fd + .addr sound_2e_part_05 + +sound_2f_part_04: + .incbin "assets/audio_data/sound_2f_part_04.bin" + .addr sound_2f_part_01 + +; CPU address $9b0c +sound_2f_part_05: + .incbin "assets/audio_data/sound_2f_part_05.bin" + .addr sound_2f_part_05 + +sound_2f_part_06: + .incbin "assets/audio_data/sound_2f_part_06.bin" + +; BGM 2 - level 3 waterfall music (triangle channel) +; CPU address $9b67 +sound_30: + .incbin "assets/audio_data/sound_30.bin" + +; CPU address $9b6a +sound_30_part_00: + .incbin "assets/audio_data/sound_30_part_00.bin" + .addr sound_30_part_00 + +; CPU address $9b6f +sound_30_part_01: + .byte $fd + .addr sound_2f_part_05 + +; CPU address $9b72 +sound_30_part_02: + .incbin "assets/audio_data/sound_30_part_02.bin" + .addr sound_30_part_02 + .byte $fd + .addr sound_2f_part_05 + +sound_30_part_03: + .incbin "assets/audio_data/sound_30_part_03.bin" + .addr sound_30_part_01 + +; BGM 2 - level 3 waterfall music (noise/dmc channel) +; CPU address $9bce +sound_31: + .byte $d6 + +; CPU address $9bcf +sound_31_part_00: + .byte $00,$fe,$50 + .addr sound_31_part_00 + +; CPU address $9bd4 +sound_31_part_01: + .incbin "assets/audio_data/sound_31_part_01.bin" + .addr sound_31_part_01 + +; CPU address $9bd9 +sound_31_part_02: + .incbin "assets/audio_data/sound_31_part_02.bin" + .addr sound_31_part_02 + +sound_31_part_03: + .incbin "assets/audio_data/sound_31_part_03.bin" + .addr sound_31_part_02 + +lvl_5_pulse_volume_03: + .byte $08,$07,$06,$05,$04,$00,$00,$00,$00,$00,$00,$01,$ff + +lvl_5_pulse_volume_04: + .byte $08,$07,$06,$05,$04,$03,$02,$01,$ff + +lvl_5_pulse_volume_05: + .byte $07,$06,$05,$04,$03,$02,$01,$00,$00,$00,$01,$ff + +lvl_5_pulse_volume_06: + .byte $04,$03,$02,$02,$02,$00,$00,$00,$00,$00,$01,$ff + +; CPU address $9c71 +sound_32_part_00: + .incbin "assets/audio_data/sound_32_part_00.bin" + +; CPU address $9c92 +sound_32_part_01: + .incbin "assets/audio_data/sound_32_part_01.bin" + .addr sound_32_part_01 + +sound_32_part_02: + .incbin "assets/audio_data/sound_32_part_02.bin" + +; BGM 3 - level 5 snow field music (pulse 1 channel) +; CPU address $9ca4 +sound_32: + .incbin "assets/audio_data/sound_32.bin" + +; CPU address $9caa +sound_32_part_03: + .incbin "assets/audio_data/sound_32_part_03.bin" + .addr sound_32_part_03 + +sound_32_part_04: + .byte $e8 + +; CPU address $9cb0 +sound_32_part_05: + .incbin "assets/audio_data/sound_32_part_05.bin" + .addr sound_32_part_05 + +sound_32_part_06: + .incbin "assets/audio_data/sound_32_part_06.bin" + +; CPU address $9cd7 +sound_32_part_07: + .incbin "assets/audio_data/sound_32_part_07.bin" + .addr sound_32_part_07 + .byte $fd + .addr sound_32_part_00 + +sound_32_part_08: + .incbin "assets/audio_data/sound_32_part_08.bin" + .addr sound_32_part_00 + +sound_32_part_09: + .incbin "assets/audio_data/sound_32_part_09.bin" + .addr sound_32_part_05 + +; CPU address $9d08 +sound_32_part_0a: + .incbin "assets/audio_data/sound_32_part_0a.bin" + +; BGM 3 - level 5 snow field music (pulse 2 channel) +; CPU address $9d32 +sound_33: + .incbin "assets/audio_data/sound_33.bin" + +; CPU address $9d37 +sound_33_part_00: + .incbin "assets/audio_data/sound_33_part_00.bin" + .addr sound_33_part_00 + +; CPU address $9d3c +sound_33_part_01: + .incbin "assets/audio_data/sound_33_part_01.bin" + .addr sound_33_part_01 + +sound_33_part_02: + .incbin "assets/audio_data/sound_33_part_02.bin" + +; CPU address $9d65 +sound_33_part_03: + .incbin "assets/audio_data/sound_33_part_03.bin" + .addr sound_33_part_03 + .byte $fd + .addr sound_32_part_0a + +sound_33_part_04: + .incbin "assets/audio_data/sound_33_part_04.bin" + .addr sound_32_part_0a + +sound_33_part_05: + .incbin "assets/audio_data/sound_33_part_05.bin" + +; BGM 3 - level 5 snow field music (triangle channel) +; CPU address $9d9a +sound_34: + .incbin "assets/audio_data/sound_34.bin" + +; CPU address $9d9d +sound_34_part_00: + .incbin "assets/audio_data/sound_34_part_00.bin" + .addr sound_34_part_00 + +; CPU address $9da2 +sound_34_part_01: + .incbin "assets/audio_data/sound_34_part_01.bin" + .addr sound_34_part_01 + +; CPU address $9db4 +sound_34_part_02: + .incbin "assets/audio_data/sound_34_part_02.bin" + .addr sound_34_part_02 + +; CPU address $9dc6 +sound_34_part_03: + .incbin "assets/audio_data/sound_34_part_03.bin" + .addr sound_34_part_03 + +sound_34_part_04: + .incbin "assets/audio_data/sound_34_part_04.bin" + +; CPU address $9de9 +sound_34_part_05: + .incbin "assets/audio_data/sound_34_part_05.bin" + .addr sound_34_part_05 + +; CPU address $9dee +sound_34_part_06: + .incbin "assets/audio_data/sound_34_part_06.bin" + .addr sound_34_part_06 + +sound_34_part_07: + .incbin "assets/audio_data/sound_34_part_07.bin" + +; CPU address $9df7 +sound_34_part_08: + .incbin "assets/audio_data/sound_34_part_08.bin" + .addr sound_34_part_08 + +; CPU address $9dfc +sound_34_part_09: + .incbin "assets/audio_data/sound_34_part_09.bin" + .addr sound_34_part_09 + .byte $fe,$ff + .addr sound_34_part_01 + +; CPU address $9e06 +sound_34_part_0a: + .incbin "assets/audio_data/sound_34_part_0a.bin" + .addr sound_34_part_0a + +sound_34_part_0b: + .incbin "assets/audio_data/sound_34_part_0b.bin" + +; BGM 3 - level 5 snow field music (noise/dmc channel) +; CPU address $9e1e +sound_35: + .byte $d6 + +; CPU address $9e1f +sound_35_part_00: + .incbin "assets/audio_data/sound_35_part_00.bin" + .addr sound_35_part_00 + +; CPU address $9e24 +sound_35_part_01: + .byte $fd + .addr sound_34_part_0a + .byte $fd + .addr sound_34_part_0a + +; CPU address $9e2a +sound_35_part_02: + .incbin "assets/audio_data/sound_35_part_02.bin" + .addr sound_35_part_02 + +sound_35_part_03: + .incbin "assets/audio_data/sound_35_part_03.bin" + +; CPU address $9e41 +sound_35_part_04: + .incbin "assets/audio_data/sound_35_part_04.bin" + .addr sound_35_part_04 + +sound_35_part_05: + .incbin "assets/audio_data/sound_35_part_05.bin" + +; CPU address $9e57 +sound_35_part_06: + .incbin "assets/audio_data/sound_35_part_06.bin" + .addr sound_35_part_06 + +sound_35_part_07: + .incbin "assets/audio_data/sound_35_part_07.bin" + .addr sound_35_part_01 + +lvl_5_pulse_volume_01: + .byte $08,$07,$06,$05,$04,$03,$00,$00,$00,$00,$01,$ff + +lvl_5_pulse_volume_02: + .byte $06,$07,$06,$05,$04,$03,$ff + +lvl_6_pulse_volume_03: + .byte $05,$04,$03,$03,$03,$03,$00,$00,$00,$00,$01,$ff + +lvl_6_pulse_volume_04: + .byte $08,$07,$06,$05,$04,$03,$03,$00,$00,$00,$01,$ff + +lvl_6_pulse_volume_05: + .byte $06,$05,$04,$03,$02,$00,$00,$00,$00,$00,$01,$ff + +; BGM 4 - level 6 energy zone (pulse 1 channel) +; CPU address $9ea8 +sound_36: + .byte $e8,$d6,$f9,$87,$00,$e3,$c8 + +; CPU address $9eaf +sound_36_part_00: + .incbin "assets/audio_data/sound_36_part_00.bin" + +; CPU address $9ee5 +sound_36_part_01: + .incbin "assets/audio_data/sound_36_part_01.bin" + .addr sound_36_part_01 + +; CPU address $9ef8 +sound_36_part_02: + .incbin "assets/audio_data/sound_36_part_02.bin" + .addr sound_36_part_02 + +sound_36_part_03: + .incbin "assets/audio_data/sound_36_part_03.bin" + .addr sound_36_part_00 + +; BGM 4 - level 6 energy zone (pulse 2 channel) +; CPU address $9f46 +sound_37: + .incbin "assets/audio_data/sound_37.bin" + +; CPU address $9f4c +sound_37_part_00: + .incbin "assets/audio_data/sound_37_part_00.bin" + +; CPU address $9f54 +sound_37_part_01: + .incbin "assets/audio_data/sound_37_part_01.bin" + .addr sound_37_part_01 + +sound_37_part_02: + .incbin "assets/audio_data/sound_37_part_02.bin" + +; CPU address $9f6e +sound_37_part_03: + .incbin "assets/audio_data/sound_37_part_03.bin" + .addr sound_37_part_03 + +; CPU address $9f79 +sound_37_part_04: + .incbin "assets/audio_data/sound_37_part_04.bin" + .addr sound_37_part_04 + +sound_37_part_05: + .incbin "assets/audio_data/sound_37_part_05.bin" + .addr sound_37_part_00 + +; BGM 4 - level 6 energy zone (triangle channel) +; CPU address $9fb8 +sound_38: + .incbin "assets/audio_data/sound_38.bin" + +; CPU address $9fbc +sound_38_part_00: + .incbin "assets/audio_data/sound_38_part_00.bin" + +; CPU address $9fc2 +sound_38_part_01: + .incbin "assets/audio_data/sound_38_part_01.bin" + .addr sound_38_part_01 + +sound_38_part_02: + .incbin "assets/audio_data/sound_38_part_02.bin" + +; CPU address $9fcc +sound_38_part_03: + .incbin "assets/audio_data/sound_38_part_03.bin" + .addr sound_38_part_03 + +sound_38_part_04: + .incbin "assets/audio_data/sound_38_part_04.bin" + +; CPU address $9fd8 +sound_38_part_05: + .incbin "assets/audio_data/sound_38_part_05.bin" + .addr sound_38_part_05 + +; CPU address $9fe1 +sound_38_part_06: + .incbin "assets/audio_data/sound_38_part_06.bin" + .addr sound_38_part_06 + +sound_38_part_07: + .incbin "assets/audio_data/sound_38_part_07.bin" + .addr sound_38_part_00 + +; BGM 4 - level 6 energy zone (noise/dmc channel) +; CPU address $a003 +sound_39: + .byte $d6 + +; CPU address $a004 +sound_39_part_00: + .incbin "assets/audio_data/sound_39_part_00.bin" + .addr sound_39_part_00 + +; CPU address $a009 +sound_39_part_01: + .incbin "assets/audio_data/sound_39_part_01.bin" + +; CPU address $a00d +sound_39_part_02: + .incbin "assets/audio_data/sound_39_part_02.bin" + .addr sound_39_part_02 + +sound_39_part_03: + .incbin "assets/audio_data/sound_39_part_03.bin" + +; CPU address $a025 +sound_39_part_04: + .incbin "assets/audio_data/sound_39_part_04.bin" + .addr sound_39_part_04 + +sound_39_part_05: + .incbin "assets/audio_data/sound_39_part_05.bin" + +; CPU address $a03d +sound_39_part_06: + .incbin "assets/audio_data/sound_39_part_06.bin" + .addr sound_39_part_06 + +sound_39_part_07: + .incbin "assets/audio_data/sound_39_part_07.bin" + +; CPU address $a056 +sound_39_part_08: + .incbin "assets/audio_data/sound_39_part_08.bin" + .addr sound_39_part_08 + +sound_39_part_09: + .incbin "assets/audio_data/sound_39_part_09.bin" + .addr sound_39_part_01 + +lvl_5_pulse_volume_07: + .byte $07,$06,$05,$04,$03,$03,$03,$03,$00,$00,$00,$00,$01,$ff + +lvl_6_pulse_volume_00: + .byte $05,$04,$03,$03,$03,$03,$03,$03,$00,$00,$00,$00,$01,$ff + +lvl_6_pulse_volume_01: + .byte $05,$06,$07,$06,$05,$04,$03,$ff + +lvl_6_pulse_volume_02: + .byte $0b,$0a,$09,$08,$07,$06,$05,$04,$03,$ff + +; BGM 5 - level 8 alien's lair music (pulse 1 channel) +; CPU address $a092 +sound_3a: + .byte $dc,$f7,$29,$02,$e1,$1d,$7d,$3d,$9d + +; CPU address $a09b +sound_3a_part_00: + .incbin "assets/audio_data/sound_3a_part_00.bin" + .addr sound_3a_part_00 + +sound_3a_part_01: + .incbin "assets/audio_data/sound_3a_part_01.bin" + +; CPU address $a0c5 +sound_3a_part_02: + .incbin "assets/audio_data/sound_3a_part_02.bin" + .addr sound_3a_part_02 + +sound_3a_part_03: + .incbin "assets/audio_data/sound_3a_part_03.bin" + +; CPU address $a0e6 +sound_3a_part_04: + .incbin "assets/audio_data/sound_3a_part_04.bin" + .addr sound_3a_part_04 + +sound_3a_part_05: + .incbin "assets/audio_data/sound_3a_part_05.bin" + +; CPU address $a114 +sound_3a_part_06: + .incbin "assets/audio_data/sound_3a_part_06.bin" + .addr sound_3a_part_06 + +sound_3a_part_07: + .incbin "assets/audio_data/sound_3a_part_07.bin" + +; CPU address $a143 +sound_3a_part_08: + .incbin "assets/audio_data/sound_3a_part_08.bin" + .addr sound_3a_part_08 + +; CPU address $a14b +sound_3a_part_09: + .incbin "assets/audio_data/sound_3a_part_09.bin" + +; CPU address $a15a +sound_3a_part_0a: + .incbin "assets/audio_data/sound_3a_part_0a.bin" + .addr sound_3a_part_0a + +sound_3a_part_0b: + .incbin "assets/audio_data/sound_3a_part_0b.bin" + .addr sound_3a_part_00 + +; BGM 5 - level 8 alien's lair music (pulse 2 channel) +; CPU address $a1a7 +sound_3b: + .incbin "assets/audio_data/sound_3b.bin" + +; CPU address $a1b3 +sound_3b_part_00: + .incbin "assets/audio_data/sound_3b_part_00.bin" + .addr sound_3b_part_00 + +; CPU address $a1d3 +sound_3b_part_01: + .incbin "assets/audio_data/sound_3b_part_01.bin" + .addr sound_3b_part_01 + +sound_3b_part_02: + .incbin "assets/audio_data/sound_3b_part_02.bin" + .addr sound_3b_part_00 + +; BGM 5 - level 8 alien's lair music (triangle channel) +; CPU address $a295 +sound_3c: + .incbin "assets/audio_data/sound_3c.bin" + .addr sound_3c + +; CPU address $a2a2 +sound_3c_part_00: + .incbin "assets/audio_data/sound_3c_part_00.bin" + .addr sound_3c_part_00 + +sound_3c_part_01: + .incbin "assets/audio_data/sound_3c_part_01.bin" + +; CPU address $a2c6 +sound_3c_part_02: + .incbin "assets/audio_data/sound_3c_part_02.bin" + .addr sound_3c_part_02 + +sound_3c_part_03: + .incbin "assets/audio_data/sound_3c_part_03.bin" + +; CPU address $a30a +sound_3c_part_04: + .incbin "assets/audio_data/sound_3c_part_04.bin" + .addr sound_3c_part_04 + +sound_3c_part_05: + .incbin "assets/audio_data/sound_3c_part_05.bin" + .addr sound_3c_part_00 + +; BGM 5 - level 8 alien's lair music (noise/dmc channel) +; CPU address $a32f +sound_3d: + .byte $d7 + +; CPU address $a330 +sound_3d_part_00: + .incbin "assets/audio_data/sound_3d_part_00.bin" + +; CPU address $a333 +sound_3d_part_01: + .addr sound_3d_part_00 + +; CPU address $a335 +sound_3d_part_02: + .incbin "assets/audio_data/sound_3d_part_02.bin" + +; CPU address $a33d +sound_3d_part_03: + .incbin "assets/audio_data/sound_3d_part_03.bin" + .addr sound_3d_part_03 + +; CPU address $a357 +sound_3d_part_04: + .incbin "assets/audio_data/sound_3d_part_04.bin" + .addr sound_3d_part_04 + +sound_3d_part_05: + .incbin "assets/audio_data/sound_3d_part_05.bin" + +; CPU address $a36f +sound_3d_part_06: + .incbin "assets/audio_data/sound_3d_part_06.bin" + .addr sound_3d_part_06 + +sound_3d_part_07: + .incbin "assets/audio_data/sound_3d_part_07.bin" + .addr sound_3d_part_03 + +lvl_2_pulse_volume_00: + .byte $07,$07,$06,$05,$04,$03,$00,$00,$00,$01,$01,$01,$ff + +lvl_2_pulse_volume_01: + .byte $07,$06,$05,$04,$03,$ff + +lvl_2_pulse_volume_02: + .byte $03,$04,$05,$04,$03,$02,$ff + +lvl_2_pulse_volume_03: + .byte $06,$07,$06,$05,$04,$03,$00,$00,$00,$00,$01,$01,$ff + +lvl_2_pulse_volume_04: + .byte $07,$06,$05,$04,$03,$00,$00,$00,$00,$00,$01,$01,$ff + +lvl_2_pulse_volume_05: + .byte $05,$04,$03,$03,$03,$00,$00,$00,$00,$00,$01,$01,$ff + +; CPU address $a41c +sound_3e_part_00: + .incbin "assets/audio_data/sound_3e_part_00.bin" + .addr sound_3e_part_00 + +sound_3e_part_01: + .incbin "assets/audio_data/sound_3e_part_01.bin" + +; CPU address $a44a +sound_3e_part_02: + .incbin "assets/audio_data/sound_3e_part_02.bin" + .addr sound_3e_part_02 + +sound_3e_part_03: + .incbin "assets/audio_data/sound_3e_part_03.bin" + +; 3D BGM - indoor/base level music (pulse 1 channel) +; CPU address $a468 +sound_3e: + .incbin "assets/audio_data/sound_3e.bin" + .addr sound_3e + +sound_3e_part_04: + .incbin "assets/audio_data/sound_3e_part_04.bin" + +; CPU address $a48f +sound_3e_part_05: + .incbin "assets/audio_data/sound_3e_part_05.bin" + .addr sound_3e_part_05 + +sound_3e_part_06: + .incbin "assets/audio_data/sound_3e_part_06.bin" + +; CPU address $a4b5 +sound_3e_part_07: + .incbin "assets/audio_data/sound_3e_part_07.bin" + .addr sound_3e_part_00 + +; CPU address $a4fb +sound_3e_part_08: + .incbin "assets/audio_data/sound_3e_part_08.bin" + .addr sound_3e_part_08 + +; CPU address $a508 +sound_3e_part_09: + .incbin "assets/audio_data/sound_3e_part_09.bin" + .addr sound_3e_part_09 + .byte $fd + .addr sound_3e_part_00 + .byte $fe,$ff + .addr sound_3e_part_07 + +; CPU address $a51b +sound_3e_part_0a: + .incbin "assets/audio_data/sound_3e_part_0a.bin" + .addr sound_3e_part_0a + +sound_3e_part_0b: + .incbin "assets/audio_data/sound_3e_part_0b.bin" + +; CPU address $a545 +sound_3e_part_0c: + .incbin "assets/audio_data/sound_3e_part_0c.bin" + .addr sound_3e_part_0c + +sound_3e_part_0d: + .incbin "assets/audio_data/sound_3e_part_0d.bin" + +; 3D BGM - indoor/base level music (pulse 2 channel) +; CPU address $a570 +sound_3f: + .incbin "assets/audio_data/sound_3f.bin" + .addr sound_3e_part_0a + +sound_3f_part_00: + .incbin "assets/audio_data/sound_3f_part_00.bin" + .addr sound_3e_part_0a + +; CPU address $a5b9 +sound_3f_part_01: + .incbin "assets/audio_data/sound_3f_part_01.bin" + .addr sound_3f_part_01 + +; CPU address $a5c9 +sound_3f_part_02: + .incbin "assets/audio_data/sound_3f_part_02.bin" + .addr sound_3f_part_02 + .byte $fe,$ff + .addr sound_3f + +; CPU address $a5dd +sound_3f_part_03: + .incbin "assets/audio_data/sound_3f_part_03.bin" + +; CPU address $a5e0 +sound_3f_part_04: + .incbin "assets/audio_data/sound_3f_part_04.bin" + .addr sound_3f_part_04 + +; CPU address $a5e5 +sound_3f_part_05: + .incbin "assets/audio_data/sound_3f_part_05.bin" + .addr sound_3f_part_05 + .byte $ff + +; 3D BGM - indoor/base level music (triangle channel) +; CPU address $a5eb +sound_40: + .incbin "assets/audio_data/sound_40.bin" + .addr sound_40 + +; CPU address $a5f7 +sound_40_part_00: + .incbin "assets/audio_data/sound_40_part_00.bin" + .addr sound_40_part_00 + +; CPU address $a5fd +sound_40_part_01: + .incbin "assets/audio_data/sound_40_part_01.bin" + .addr sound_3f_part_03 + +; CPU address $a630 +sound_40_part_02: + .incbin "assets/audio_data/sound_40_part_02.bin" + .addr sound_40_part_02 + +; CPU address $a63b +sound_40_part_03: + .incbin "assets/audio_data/sound_40_part_03.bin" + .addr sound_40_part_03 + .byte $fd + .addr sound_3f_part_03 + .byte $fe,$ff + .addr sound_40_part_01 + +; CPU address $a64e +sound_40_part_04: + .incbin "assets/audio_data/sound_40_part_04.bin" + .addr sound_40_part_04 + +sound_40_part_05: + .incbin "assets/audio_data/sound_40_part_05.bin" + +; CPU address $a663 +sound_40_part_06: + .incbin "assets/audio_data/sound_40_part_06.bin" + .addr sound_40_part_06 + +sound_40_part_07: + .incbin "assets/audio_data/sound_40_part_07.bin" + +; 3D BGM - indoor/base level music (noise/dmc channel) +; CPU address $a67a +sound_41: + .incbin "assets/audio_data/sound_41.bin" + .addr sound_41 + +sound_41_part_00: + .incbin "assets/audio_data/sound_41_part_00.bin" + +; CPU address $a68b +sound_41_part_01: + .incbin "assets/audio_data/sound_41_part_01.bin" + .addr sound_41_part_01 + +sound_41_part_02: + .incbin "assets/audio_data/sound_41_part_02.bin" + +; CPU address $a6a0 +sound_41_part_03: + .incbin "assets/audio_data/sound_41_part_03.bin" + .addr sound_41_part_03 + +sound_41_part_04: + .incbin "assets/audio_data/sound_41_part_04.bin" + .addr sound_40_part_04 + +sound_41_part_05: + .incbin "assets/audio_data/sound_41_part_05.bin" + +; CPU address $a6e4 +sound_41_part_06: + .incbin "assets/audio_data/sound_41_part_06.bin" + .addr sound_41_part_06 + +; CPU address $a6ec +sound_41_part_07: + .incbin "assets/audio_data/sound_41_part_07.bin" + +; CPU address $a6f0 +sound_41_part_08: + .incbin "assets/audio_data/sound_41_part_08.bin" + .addr sound_41_part_08 + +sound_41_part_09: + .incbin "assets/audio_data/sound_41_part_09.bin" + .addr sound_40_part_04 + .byte $fe,$ff + .addr sound_41_part_03 + +lvl_2_pulse_volume_06: + .byte $05,$06,$05,$04,$03,$02,$00,$00,$00,$00,$00,$01,$ff + +lvl_6_pulse_volume_06: + .byte $0c,$0b,$0a,$09,$08,$07,$06,$ff + +lvl_6_pulse_volume_07: + .byte $08,$07,$06,$05,$04,$03,$03,$00,$00,$00,$00,$01,$ff + +lvl_7_pulse_volume_00: + .byte $0a,$09,$08,$07,$06,$05,$04,$00,$00,$00,$00,$01,$ff + +; CPU address $a735 +sound_43_part_00: + .incbin "assets/audio_data/sound_43_part_00.bin" + .addr sound_43_part_00 + +; CPU address $a746 +sound_43_part_01: + .incbin "assets/audio_data/sound_43_part_01.bin" + .addr sound_43_part_01 + +; CPU address $a759 +sound_43_part_02: + .incbin "assets/audio_data/sound_43_part_02.bin" + .addr sound_43_part_02 + +sound_43_part_03: + .incbin "assets/audio_data/sound_43_part_03.bin" + +; BOSS - indoor/base boss screen music (pulse 1 channel) +; CPU address $a793 +sound_42: + .incbin "assets/audio_data/sound_42.bin" + .addr sound_42 + +sound_42_part_00: + .incbin "assets/audio_data/sound_42_part_00.bin" + .addr sound_42 + +; BOSS - indoor/base boss screen music (pulse 2 channel) +; CPU address $a878 +sound_43: + .incbin "assets/audio_data/sound_43.bin" + .addr sound_43_part_00 + .byte $fd + .addr sound_43_part_00 + +sound_43_part_04: + .incbin "assets/audio_data/sound_43_part_04.bin" + .addr sound_43 + +; BOSS - indoor/base boss screen music (triangle channel) +; CPU address $a8fb +sound_44: + .incbin "assets/audio_data/sound_44.bin" + .addr sound_44 + +sound_44_part_00: + .incbin "assets/audio_data/sound_44_part_00.bin" + .addr sound_44 + +; CPU address $a9fd +sound_45_part_00: + .incbin "assets/audio_data/sound_45_part_00.bin" + +; CPU address $aa03 +sound_45_part_01: + .incbin "assets/audio_data/sound_45_part_01.bin" + .addr sound_45_part_01 + .byte $ff + +; BOSS - indoor/base boss screen music (noise/dmc channel) +; CPU address $aa0e +sound_45: + .incbin "assets/audio_data/sound_45.bin" + .addr sound_45 + +sound_45_part_02: + .incbin "assets/audio_data/sound_45_part_02.bin" + .addr sound_45_part_00 + .byte $fd + .addr sound_45_part_00 + .byte $fd + .addr sound_45_part_00 + .byte $fd + .addr sound_45_part_00 + +sound_45_part_03: + .incbin "assets/audio_data/sound_45_part_03.bin" + .addr sound_45 + +lvl_2_pulse_volume_07: + .byte $08,$07,$06,$05,$04,$03,$03,$00,$00,$00,$00,$01,$ff + +lvl_3_pulse_volume_00: + .byte $06,$07,$06,$05,$04,$03,$ff + +lvl_3_pulse_volume_01: + .byte $04,$05,$04,$03,$ff + +lvl_3_pulse_volume_06: + .byte $09,$08,$07,$06,$05,$04,$03,$00,$00,$00,$00,$01,$ff + +lvl_7_pulse_volume_05: + .byte $08,$07,$06,$05,$04,$03,$03,$00,$00,$00,$00,$01,$ff + +; PCLR - end of level tune (pulse 1 channel) +; CPU address $aa92 - end of level tune +sound_46: + .incbin "assets/audio_data/sound_46.bin" + +; PCLR - end of level tune (pulse 2 channel) +; CPU address $aab3 +sound_47: + .incbin "assets/audio_data/sound_47.bin" + +; PCLR - end of level tune (triangle channel) +; CPU address $aad4 +sound_48: + .incbin "assets/audio_data/sound_48.bin" + +; PCLR - end of level tune (noise/dmc channel) +; CPU address $aaef +sound_49: + .incbin "assets/audio_data/sound_49.bin" + +lvl_3_pulse_volume_02: + .byte $08,$07,$06,$05,$04,$03,$03,$00,$00,$00,$00,$01,$ff + +lvl_3_pulse_volume_03: + .byte $06,$05,$04,$03,$03,$03,$03,$00,$00,$00,$00,$01,$ff + +lvl_3_pulse_volume_04: + .byte $05,$04,$03,$02,$02,$02,$00,$00,$00,$01,$ff + +lvl_3_pulse_volume_05: + .byte $08,$07,$06,$05,$04,$03,$00,$00,$00,$01,$ff + +; OVER - game over/after end credits, presented by konami (pulse 1 channel) +; CPU address $ab34 +sound_4e: + .incbin "assets/audio_data/sound_4e.bin" + +; OVER - game over/after end credits, presented by konami (pulse 2 channel) +; CPU address $ab5c +sound_4f: + .incbin "assets/audio_data/sound_4f.bin" + +; OVER - game over/after end credits, presented by konami (triangle channel) +; CPU address $ab86 +sound_50: + .incbin "assets/audio_data/sound_50.bin" + +; OVER - game over/after end credits, presented by konami (noise/dmc channel) +; CPU address $abb2 +sound_51: + .incbin "assets/audio_data/sound_51.bin" + +lvl_7_pulse_volume_01: + .byte $07,$08,$07,$06,$05,$04,$03,$ff + +lvl_7_pulse_volume_02: + .byte $07,$06,$05,$04,$03,$03,$03,$03,$03,$03,$00,$00,$00,$00,$01,$ff + +lvl_7_pulse_volume_03: + .byte $ff + +lvl_7_pulse_volume_04: + .byte $ff + +; CPU address $abec +sound_4a_part_00: + .incbin "assets/audio_data/sound_4a_part_00.bin" + +; ENDING - end credits (pulse 1 channel) +; CPU address $ac9f +sound_4a: + .incbin "assets/audio_data/sound_4a.bin" + .addr sound_4a_part_00 + +sound_4a_part_01: + .incbin "assets/audio_data/sound_4a_part_01.bin" + .addr sound_4a_part_00 + +sound_4a_part_02: + .incbin "assets/audio_data/sound_4a_part_02.bin" + +; CPU address $acad +sound_4a_part_03: + .incbin "assets/audio_data/sound_4a_part_03.bin" + +; ENDING - end credits (pulse 2 channel) +; CPU address $ad1a +sound_4b: + .byte $fd + .addr sound_4a_part_03 + +sound_4b_part_00: + .incbin "assets/audio_data/sound_4b_part_00.bin" + .addr sound_4a_part_03 + +; CPU address $ad22 +sound_4b_part_01: + .incbin "assets/audio_data/sound_4b_part_01.bin" + +; CPU address $ad25 +sound_4b_part_02: + .incbin "assets/audio_data/sound_4b_part_02.bin" + .addr sound_4b_part_02 + +; CPU address $ad36 +sound_4b_part_03: + .incbin "assets/audio_data/sound_4b_part_03.bin" + +; CPU address $ad59 +sound_4b_part_04: + .incbin "assets/audio_data/sound_4b_part_04.bin" + .addr sound_4b_part_04 + +; CPU address $ad69 +sound_4b_part_05: + .incbin "assets/audio_data/sound_4b_part_05.bin" + .addr sound_4b_part_05 + +; CPU address $ad7a +sound_4b_part_06: + .incbin "assets/audio_data/sound_4b_part_06.bin" + +; CPU address $ad9d +sound_4b_part_07: + .incbin "assets/audio_data/sound_4b_part_07.bin" + .addr sound_4b_part_07 + +sound_4b_part_08: + .incbin "assets/audio_data/sound_4b_part_08.bin" + +; ENDING - end credits (triangle channel) +; CPU address $ae05 +sound_4c: + .byte $fd + .addr sound_4b_part_02 + +sound_4c_part_00: + .incbin "assets/audio_data/sound_4c_part_00.bin" + .addr sound_4b_part_02 + +sound_4c_part_01: + .incbin "assets/audio_data/sound_4c_part_01.bin" + +; CPU address $ae1e +sound_4c_part_02: + .incbin "assets/audio_data/sound_4c_part_02.bin" + .addr sound_4c_part_02 + +sound_4c_part_03: + .incbin "assets/audio_data/sound_4c_part_03.bin" + +; CPU address $ae36 +sound_4c_part_04: + .incbin "assets/audio_data/sound_4c_part_04.bin" + .addr sound_4c_part_04 + +; CPU address $ae43 +sound_4c_part_05: + .incbin "assets/audio_data/sound_4c_part_05.bin" + +; CPU address $ae4c +sound_4c_part_06: + .incbin "assets/audio_data/sound_4c_part_06.bin" + .addr sound_4c_part_06 + +sound_4c_part_07: + .incbin "assets/audio_data/sound_4c_part_07.bin" + +; CPU address $ae63 +sound_4c_part_08: + .incbin "assets/audio_data/sound_4c_part_08.bin" + .addr sound_4c_part_08 + +; CPU address $ae70 +sound_4c_part_09: + .incbin "assets/audio_data/sound_4c_part_09.bin" + +; CPU address $ae79 +sound_4c_part_0a: + .incbin "assets/audio_data/sound_4c_part_0a.bin" + .addr sound_4c_part_0a + .byte $ff + +; ENDING - end credits (noise/dmc channel) +; CPU address $ae87 +sound_4d: + .byte $fd + .addr sound_4c_part_02 + +sound_4d_part_00: + .incbin "assets/audio_data/sound_4d_part_00.bin" + .addr sound_4c_part_02 + +sound_4d_part_01: + .incbin "assets/audio_data/sound_4d_part_01.bin" + +; end of sound code logic, beginning of sprite code logic + +; writes encoded sprite data into memory address $0200-$02ff +; which is later the address set for OAMDMA for display +; for non-hud sprites this label loads #$19 #$4-byte entries per call (#$4c bytes total) +; when SPRITE_LOAD_TYPE is #01, then the sprites on top of the screen (medals, game over) +; CPU address $ae97 +draw_sprites: + lda $35 ; OAMDMA_CPU_BUFFER write offset + clc ; clear carry in preparation for addition + adc #$4c ; add #$4c to OAMDMA_CPU_BUFFER, this is to support "sprite cycling"/"sprite flickering" + ; since the NES can draw a maximum of 8 sprites per scan line, Contra adjusts the starting locations + ; so that sprites move around in PPU memory. This will allow different sprites to load each frame + ; on the same scan line. Human eyes won't notice a sprite not visible for a single frame + sta $35 ; OAMDMA_CPU_BUFFER write offset + sta $04 ; store OAMDMA_CPU_BUFFER write offset to $04 + ldy #$3f ; y = #$3f (maximum number of sprite pattern tiles that NES supports) + sty $07 ; set maximum number of sprites in $07 + lda SPRITE_LOAD_TYPE ; if 0, load regular sprites to cpu, else load hud sprites + beq @load_sprites_to_cpu_mem ; load sprites to cpu + jsr draw_hud_sprites ; draw hud sprites + +@load_sprites_to_cpu_mem: + ldx #$19 ; up to #$19 sprites possible to load + +; loops through the total number of possible sprites +@load_sprite_loop: + stx $05 ; store current sprite number offset into x + lda CPU_SPRITE_BUFFER,x ; load sprite offset into sprite_ptr_tbl + beq @adv_sprite ; move to next contra sprite if this current wasn't used + ldy SPRITE_ATTR,x ; load sprite attribute data + sty $00 ; set $00 to hold SPRITE_ATTR + ldy SPRITE_Y_POS,x ; sprite y position on screen + sty $01 ; set $01 to hold base y position of sprite (sprite tiles are positioned relative to this point) + ldy SPRITE_X_POS,x ; sprite x position on screen + sty $02 ; set $02 to hold base x position of sprite (sprite tiles are positioned relative to this point) + jsr load_sprite_to_cpu_mem ; loads all the sprite pattern tiles to the OAMDMA buffer + +@adv_sprite: + ldy $07 ; load number of sprite tiles available to draw + bmi draw_sprites_exit ; exit if the OAMDMA is full + ldx $05 ; load the remaining number of contra sprites left to render (sprites, not tiles) + dex ; decrement total sprites remaining + bpl @load_sprite_loop ; if contra sprites left to drawn, loop to draw it + ldx $04 ; load OAMDMA_CPU_BUFFER write offset + +; take the remaining sprite tiles that are available in the OAMDMA and blank them +@fill_unused_OAM: + lda #$f4 ; hide any sprite whose byte 0 is $ef-$ff is not displayed + sta OAMDMA_CPU_BUFFER,x ; write y position as #$f4 (hidden) + jsr adv_OAMDMA_addr ; add #$c4 to x (move to next sprite tile location) + dey ; decrement remaining sprite tiles counter + bpl @fill_unused_OAM ; loop to hide next sprite tile if haven't hidden all leftover tiles + +draw_sprites_exit: + rts + +; write sprite data (OAM) to OAMDMA_CPU_BUFFER from CPU SPRITE buffer data +; if a >= #$80 offset is into sprite_ptr_tbl_1 +; input +; * a - sprite code, offset into sprite_ptr_tbl_0 or sprite_ptr_tbl_1 +; * $00 - sprite attribute +; * $01 - sprite y position on screen +; * $02 - sprite x position +; output +; * $04 - the next starting position of the OAMDMA_CPU_BUFFER write offset +; * $07 - lowers the number of remaining sprite tiles that can be drawn by the +; NES based on how many tiles were in the sprite drawn +load_sprite_to_cpu_mem: + asl ; double since each entry in sprite_ptr_tbl is 2 bytes + tay + bcs @load_sprite_tbl_1 ; offset is >= #$80, use second sprite table + lda sprite_ptr_tbl_0-2,y ; use first sprite table a < #$80 + sta $08 ; store low byte of sprite address into $08 + lda sprite_ptr_tbl_0-1,y ; load high byte of sprite address into a + jmp @continue_load_sprite ; jump to store low byte and continue loading sprite + +@load_sprite_tbl_1: + lda sprite_ptr_tbl_1,y ; load low byte of sprite address + sta $08 ; store low byte of sprite address into $08 + lda sprite_ptr_tbl_1+1,y ; load high byte of sprite address + +@continue_load_sprite: + sta $09 ; store high byte of sprite address into $09 + ldy #$00 ; y = #$00 + lda ($08),y ; load number of tiles first byte from sprite + beq draw_sprites_exit ; if #$00, exit + iny ; increment sprite_xx read offset + cmp #$fe ; check if "small" sprite + bne @load_regular_sprite + jmp @load_small_sprite ; handle #$fe + +@load_regular_sprite: + sta $03 ; store number of pattern tiles in sprite into $03 + lda $00 ; load SPRITE_ATTR + and #$c8 ; keep bits xx.. x..., keep horizontal and vertical flip bits + ; bit 3 signals whether to add 1 to y position for regular sprites (not small sprites) + sta $0b ; store in $0b, this is later used when rendering the sprite tile + ; e.g. if the sprite tile within the sprite is defined as flipped vertically or horizontally, + ; and the SPRITE_ATTR specifies flipping the entire sprite, then we have to 'flip the flip', i.e. change from a 1 to a 0 + lda $00 ; reload SPRITE_ATTR, looking to see if bit 2 is set + lsr + lsr + lsr + ldx #$fc ; x = #$fc (1111 1100) used to strip palette from sprite code's sprite tile definition + lda #$23 ; a = #$23 (0010 0011) used to keep palette and background priority from SPRITE_ATTR + bcs @prep_sprite_attributes ; branch if bit 2 of SPRITE_ATTR is 1 + ; this means bg priority and sprite palette will be from SPRITE_ATTR and not sprite code byte 2 definition + ldx #$ff ; x = #$ff (1111 1111) used to keep palette and flip bits from sprite code byte 2 definition + lda #$20 ; a = #$20 (0010 0000) used to strip palette from SPRITE_ATTR and instead use sprite code byte 2 definition + +@prep_sprite_attributes: + and $00 ; keep/strip the sprite palette and bg priority from SPRITE_ATTR depending on bit 2 of SPRITE_ATTR + sta $00 ; update $00 with new value + stx $0d ; store palette and flip bits from sprite code byte 2 definition if not overwritten by SPRITE_ATTR + ldx $04 ; load OAMDMA_CPU_BUFFER write offset + +@write_sprite_tile: + lda ($08),y ; load first byte of sprite (y position), or #$80 for shared sprite + cmp #$80 ; see if using shared sprite + bne @continue ; not using shared sprite, continue like normal + ; using shared sprite, need to update cpu read address + lda $0b ; load sprite effect + and #$f7 ; strip bit 3 to prevent adding #$01 to y position + sta $0b ; save sprite effect + iny ; increment sprite_xx read offset + lda ($08),y ; load low byte of new sprite read address + sta $06 ; store low byte into $06 + iny + lda ($08),y ; load high byte of new sprite read address + sta $09 ; replace existing read high byte in $09 with new address + lda $06 ; load low byte back into $06 + sta $08 ; replace existing read low byte in $08 with new address + ldy #$00 ; clear sprite_xx read offset since starting at new address + +@continue: + lda ($08),y ; read sprite relative y position (Byte 0) + sta $0c ; store sprite tile relative y offset + lda $0b + asl ; shift sprite effect left + and #$10 ; keep bits ...x .... + beq @prep_y_position ; see if bit 3 of SPRITE_ATTR is set + inc $0c ; bit 3 was set, add #$01 to relative y position + ; used for creating recoil effect when firing gun + +@prep_y_position: + lda $0c ; load sprite tile relative offset + bcc @write_sprite_tile_data ; branch bit 7 of $0b is 0 + sta $0c ; store sprite tile relative y offset + lda #$f0 ; a = #$f0 + sbc $0c ; #$f0 minus sprite tile relative y offset + clc + +@write_sprite_tile_data: + adc $01 ; add SPRITE_Y_POS to sprite y offset (sets tile's absolute position) + sta OAMDMA_CPU_BUFFER,x ; write y position to CPU buffer + iny ; increment sprite_xx read offset + lda ($08),y ; read 2nd byte, which specifies pattern table tile (Byte 1) + sta OAMDMA_CPU_BUFFER+1,x ; write pattern tile number to CPU buffer + iny ; increment sprite data read offset + lda ($08),y ; read 3rd byte (sprite tile attributes) (Byte 2) + and $0d ; keep/strip sprite palette from sprite tile byte 2, based on bit 2 of SPRITE_ATTR + ora $00 ; merge sprite code tile byte 2 with attribute data from SPRITE_ATTR + eor $0b ; exclusive to handle flipping a flipped sprite tile, e.g. flip + flip = no flip + sta OAMDMA_CPU_BUFFER+2,x ; write sprite attributes to CPU buffer + iny ; increment sprite_xx read offset + lda $0b + asl + asl + lda ($08),y ; read next byte of sprite_xx + bcc @continue2 ; branch if no horizontal flip + sta $0c ; horizontal flip, store x position + lda #$f8 ; a = #$f8 + sbc $0c + clc + +@continue2: + bmi @set_sprite_tile_x_adv_addr + adc $02 ; add to sprite x position + bcs @move_next_sprite_tile ; increment read offset, set next write offset, decrement total sprite tiles + +@set_x_adv_addr: + jsr set_x_adv_OAMDMA_addr ; set sprite tile x position (a) in OAMDMA and advance OAMDMA write address + +@move_next_sprite_tile: + iny ; increment sprite_xx read offset + dec $03 ; decrement remaining sprite tiles in sprite that is being drawn + bne @write_sprite_tile ; move to the next tile in the sprite to move into OAMDMA + stx $04 ; write next OAMDMA write offset to $04 + rts + +; set sprite tile's relative x position, and set next OAMDMA write address +; input +; a - the x offset of the sprite tile from the SPRITE_X_POS +@set_sprite_tile_x_adv_addr: + adc $02 ; add to sprite x base position + bcs @set_x_adv_addr ; set sprite tile x position (a) in OAMDMA and advance OAMDMA write address + bcc @move_next_sprite_tile ; increment read offset, set next write offset, decrement total sprite tiles + +; sprite code is made of a single #$03-byte entry (including #$fe) +; the second byte is the pattern table tile, and the third byte is the sprite attributes +; the X position is set to #$fc (-4 decimal) and the Y position is set to #$f8 (-8 decimal) from SPRITE_Y_POS. +@load_small_sprite: + ldx $04 ; load sprite y position + lda #$f8 ; a = -#$08 + clc ; clear carry in preparation for addition + adc $01 ; subtract #$08 from y position + sta OAMDMA_CPU_BUFFER,x ; set y position of small sprite + lda ($08),y ; load pattern tile of sprite + sta OAMDMA_CPU_BUFFER+1,x ; set pattern tile of small sprite + iny ; increment sprite_xx read offset + lda ($08),y ; load SPRITE_ATTR + ora $00 ; merge with $00 + sta OAMDMA_CPU_BUFFER+2,x ; store SPRITE_ATTR of small sprite + lda $02 ; load sprite's base x position + sec ; set carry flag in preparation for subtraction + sbc #$04 ; subtract #$04 + bcc @exit ; exit if overflow + jsr set_x_adv_OAMDMA_addr ; set sprite tile x position (a) in OAMDMA and advance OAMDMA write address + stx $04 ; store next OAMDMA write address in $04 + +@exit: + iny ; increment sprite_xx read offset + rts + +; places the "heads up display" (HUD) sprites in the correct locations in memory $0200-$02ff +; so they are on the screen when written by OAMDMA +draw_hud_sprites: + lda PLAYER_MODE ; #$00 single player, #$01 for 2nd player + ldy DEMO_MODE ; #$00 not in demo mode, #$01 demo mode on + beq @continue + lda #$01 ; start with player 2 when not in demo mode + +@continue: + sta $00 ; store current player number (used for HUD sprite palette) + ldx $04 + +draw_player_hud_sprites: + ldy #$04 ; y = #$04 + lda DEMO_MODE ; #$00 not in demo mode, #$01 demo mode on + bne @four_sprites ; demo mode always shows GAME OVER + ldy $00 ; load current player + lda P1_GAME_OVER_STATUS,y ; player y game over state (1 = game over) + ldy #$04 ; set hud_sprites base offset to show GAME OVER + lsr + bcs @four_sprites ; branch if in game over + ldy $00 ; not in game over, load number of lives remaining + lda P1_NUM_LIVES,y ; player y lives + ldy #$00 ; set hud_sprites base offset to show medals + cmp #$04 + bcc @draw_sprites ; if less than #$04 lives, show that number of medals + +@four_sprites: + lda #$04 ; show 4 medals on player HUD + +@draw_sprites: + sta $01 ; store number of medals to draw in $01 + +@draw_p_sprite: + dec $01 ; decrement number of medals to draw + bmi @move_to_next_player ; if done drawing all sprites, move to next player + lda #$10 ; a = #$10 + sta OAMDMA_CPU_BUFFER,x ; set y position of medal/game over hud sprite to #$10 + lda hud_sprites,y ; load HUD sprite (either medal, or game over text) + sta OAMDMA_CPU_BUFFER+1,x ; write tile number to OAM + lda $00 + sta OAMDMA_CPU_BUFFER+2,x ; write the tile attributes (blue palette for p1, red palette for p2) + lsr ; set carry flag if on player 2 + lda sprite_sprite_medal_x_offset,y ; load x offset based on sprite number + bcc @continue + adc #$af ; add #$af to sprite x offset if on player 2 (see previous lsr) + +@continue: + jsr set_x_adv_OAMDMA_addr ; set sprite tile x position (a) in OAMDMA and advance OAMDMA write address + iny ; increment medal sprite number (#$00 to #$04) + jmp @draw_p_sprite + +@move_to_next_player: + dec $00 ; decrement player number + bpl draw_player_hud_sprites ; load player 1 HUD if we were on player 2 + stx $04 + rts + +; table for hud sprites (#$10 bytes) +; medals for number of lives or +; GAME +; OVER +hud_sprites: + .byte $0a,$0a,$0a,$0a ; medals + .byte $02,$04,$06,$08 ; game over text + +sprite_sprite_medal_x_offset: + .byte $10,$1c,$28,$34 + .byte $10,$1c,$28,$34 + +; set sprite tile x position (a) in OAMDMA and advance OAMDMA write address +set_x_adv_OAMDMA_addr: + sta OAMDMA_CPU_BUFFER+3,x ; set X position of sprite + dec $07 ; decrement number of remaining sprite tiles that NES can draw + +; adds #$c4 to x for next write location of OAMDMA sprite data +adv_OAMDMA_addr: + txa ; move current write offset to a + clc ; clear carry in preparation for addition + adc #$c4 ; add #$c4 to current write offset () + tax ; move new offset back to x + rts + +; enormous pointer table for sprites (#$cf * #$2 = #$19e bytes) +sprite_ptr_tbl_0: + .addr sprite_01 ; CPU address $b1ce - blank + .addr sprite_02 ; CPU address $b1cf - player walking (frame 1) + .addr sprite_03 ; CPU address $b1e4 - player walking (frame 2) + .addr sprite_04 ; CPU address $b1f9 - player walking (frame 3) + .addr sprite_05 ; CPU address $b20e - player walking (frame 4) + .addr sprite_06 ; CPU address $b21a - player walking (frame 5) + .addr sprite_07 ; CPU address $b226 - enemy bullet (snow field) + .addr sprite_08 ; CPU address $b229 - player curled up (frame 1) + .addr sprite_09 ; CPU address $b23a - player curled up (frame 2) + .addr sprite_0a ; CPU address $b247 - player hit (frame 1) + .addr sprite_0b ; CPU address $b258 - player hit (frame 2) + .addr sprite_0c ; CPU address $b269 - player lying on ground + .addr sprite_0d ; CPU address $b27a - player walking holding weapon out (frame 1) + .addr sprite_0e ; CPU address $b28a - player walking holding weapon out (frame 2) + .addr sprite_0f ; CPU address $b29a - player walking holding weapon out (frame 3) + .addr sprite_10 ; CPU address $b2aa - player aiming angled up (frame 1) + .addr sprite_11 ; CPU address $b2b6 - player aiming angled up (frame 2) + .addr sprite_12 ; CPU address $b2c2 - player aiming angled up (frame 3) + .addr sprite_13 ; CPU address $b2ce - player aiming angled down (frame 1) + .addr sprite_14 ; CPU address $b2de - player aiming angled down (frame 2) + .addr sprite_15 ; CPU address $b2ee - player aiming angled down (frame 3) + .addr sprite_16 ; CPU address $b2fe - player aiming straight up + .addr sprite_17 ; CPU address $b30e - player prone + .addr sprite_18 ; CPU address $b31f - water splash/puddle (shown after sprite_73) + .addr sprite_19 ; CPU address $b328 - player in water + .addr sprite_1a ; CPU address $b331 - player climbing out of water + .addr sprite_1b ; CPU address $b33a - player in water aiming straight up + .addr sprite_1c ; CPU address $b34a - player in water aiming angled up + .addr sprite_1d ; CPU address $b356 - player in water aiming forward + .addr sprite_1e ; CPU address $b366 - default bullet + .addr sprite_1f ; CPU address $b369 - M bullet + .addr sprite_20 ; CPU address $6cb3 - S bullet, mortar + .addr sprite_21 ; CPU address $6fb3 - boss turret bullet + .addr sprite_22 ; CPU address $72b3 - F bullet and snow field level boss ufo bomb + .addr sprite_23 ; CPU address $75b3 - L bullet (up) + .addr sprite_24 ; CPU address $78b3 - L bullet + .addr sprite_25 ; CPU address $81b3 - L bullet (angled) + .addr sprite_26 ; CPU address $84b3 - soldier crouched shooting + .addr sprite_27 ; CPU address $95b3 - soldier running 1 + .addr sprite_28 ; CPU address $a6b3 - soldier running 2 + .addr sprite_29 ; CPU address $b2b3 - soldier shooting angled up + .addr sprite_2a ; CPU address $c3b3 - hangar mine cart (frame 1) + .addr sprite_2b ; CPU address $dcb3 - hangar mine cart (frame 2) + .addr sprite_2c ; CPU address $e8b3 - soldier shooting + .addr sprite_2d ; CPU address $fdb3 - soldier shooting angled down + .addr sprite_2e ; CPU address $12b4 - unknown (doesn't seem to be used) + .addr sprite_2f ; CPU address $23b4 - S weapon item + .addr sprite_30 ; CPU address $30b4 - B weapon item + .addr sprite_31 ; CPU address $38b4 - F weapon item + .addr sprite_32 ; CPU address $40b4 - L weapon item + .addr sprite_33 ; CPU address $48b4 - R weapon item + .addr sprite_34 ; CPU address $50b4 - M weapon item + .addr sprite_35 ; CPU address $58b4 - big explosion + .addr sprite_36 ; CPU address $79b4 - explosion + .addr sprite_37 ; CPU address $8eb4 - small explosion + .addr sprite_38 ; CPU address $97b4 - round explosion + .addr sprite_39 ; CPU address $b0b4 - thick explosion ring + .addr sprite_3a ; CPU address $d1b4 - wide explosion ring + .addr sprite_3b ; CPU address $f2b4 - soldier running + .addr sprite_3c ; CPU address $03b5 - soldier running + .addr sprite_3d ; CPU address $14b5 - soldier running + .addr sprite_3e ; CPU address $25b5 - soldier running + .addr sprite_3f ; CPU address $31b5 - soldier running + .addr sprite_40 ; CPU address $3db5 - soldier shooting + .addr sprite_41 ; CPU address $4db5 - soldier shooting downward + .addr sprite_42 ; CPU address $62b5 - soldier shooting up angled + .addr sprite_43 ; CPU address $72b5 - rifle man shooting + .addr sprite_44 ; CPU address $82b5 - rifle man behind bush (frame 1) + .addr sprite_45 ; CPU address $87b5 - rifle man behind bush (frame 2) + .addr sprite_46 ; CPU address $94b5 - rifle man behind bush (frame 3) + .addr sprite_47 ; CPU address $98b5 - small ring explosion + .addr sprite_48 ; CPU address $9bb5 - floating rock (waterfall level) + .addr sprite_49 ; CPU address $b8b5 - bridge fire (waterfall level) + .addr sprite_4a ; CPU address $c1b5 - boulder (waterfall level) + .addr sprite_4b ; CPU address $dab5 - scuba soldier hiding + .addr sprite_4c ; CPU address $e3b5 - scuba soldier out of water shooting up + .addr sprite_4d ; CPU address $f0b5 - weapon zeppelin + .addr sprite_4e ; CPU address $fdb5 - flashing falcon weapon + .addr sprite_4f ; CPU address $05b6 - unused blank sprite + .addr sprite_50 ; CPU address $06b6 - indoor player facing up + .addr sprite_51 ; CPU address $23b6 - indoor player strafing (frame 1) + .addr sprite_52 ; CPU address $33b6 - indoor player strafing (frame 2) + .addr sprite_53 ; CPU address $43b6 - indoor player strafing (frame 3) + .addr sprite_54 ; CPU address $53b6 - indoor player crouch + .addr sprite_55 ; CPU address $70b6 - indoor player electrocuted + .addr sprite_56 ; CPU address $91b6 - indoor player lying dead + .addr sprite_57 ; CPU address $b2b6 - indoor player running + .addr sprite_58 ; CPU address $cbb6 - indoor player running + .addr sprite_59 ; CPU address $e4b6 - unused blank sprite + .addr sprite_59 ; CPU address $e4b6 - unused blank sprite + .addr sprite_59 ; CPU address $e4b6 - unused blank sprite + .addr sprite_59 ; CPU address $e4b6 - unused blank sprite + .addr sprite_5d ; CPU address $e5b6 - boss eye + .addr sprite_5e ; CPU address $06b7 - boss eye + .addr sprite_5f ; CPU address $1ab7 - boss eye + .addr sprite_60 ; CPU address $2eb7 - boss eye + .addr sprite_61 ; CPU address $42b7 - boss eye + .addr sprite_62 ; CPU address $4eb7 - boss eye + .addr sprite_63 ; CPU address $56b7 - small boss eye projectile (unused) + .addr sprite_64 ; CPU address $5fb7 - boss eye projectile + .addr sprite_65 ; CPU address $80b7 - unused blank sprite + .addr sprite_65 ; CPU address $80b7 - unused blank sprite + .addr sprite_65 ; CPU address $80b7 - unused blank sprite + .addr sprite_68 ; CPU address $81b7 - base 2 boss metal helmet (Godomuga) (frame 1) + .addr sprite_69 ; CPU address $a2b7 - base 2 boss metal helmet (Godomuga) (frame 2) + .addr sprite_6a ; CPU address $beb7 - base 2 boss metal helmet (Godomuga) (frame 3) + .addr sprite_6b ; CPU address $d2b7 - base 2 boss metal helmet (Godomuga) (frame 4) + .addr sprite_6c ; CPU address $deb7 - base 2 boss metal helmet (Godomuga) (frame 5) + .addr sprite_6d ; CPU address $eab7 - base 2 boss metal helmet (Godomuga) bubble projectile + .addr sprite_6e ; CPU address $f3b7 - base 2 boss metal helmet (Godomuga) bubble projectile + .addr sprite_6f ; CPU address $fcb7 - base 2 boss metal helmet (Godomuga) bubble projectile + .addr sprite_70 ; CPU address $ffb7 - base 2 boss metal helmet (Godomuga) bubble projectile + .addr sprite_71 ; CPU address $08b8 - base 2 boss metal helmet (Godomuga) bubble projectile + .addr sprite_72 ; CPU address $11b8 - base 2 boss metal helmet (Godomuga) bubble projectile + .addr sprite_73 ; CPU address $14b8 - water splash + .addr sprite_74 ; CPU address $1db8 - ice grenade + .addr sprite_75 ; CPU address $26b8 - ice grenade + .addr sprite_76 ; CPU address $2fb8 - ice grenade + .addr sprite_77 ; CPU address $38b8 - ice grenade (vertical) + .addr sprite_78 ; CPU address $41b8 - !(UNUSED) duplicate of sprite_74 (ice grenade lean right), unused in game + .addr sprite_79 ; CPU address $4ab8 - dragon boss projectile + .addr sprite_7a ; CPU address $53b8 - dragon arm interior orb (gray) + .addr sprite_7b ; CPU address $5cb8 - dragon arm hand orb (red) + .addr sprite_7c ; CPU address $65b8 - snow field boss mini UFO + .addr sprite_7d ; CPU address $72b8 - snow field boss mini UFO + .addr sprite_7e ; CPU address $7fb8 - snow field boss mini UFO + .addr sprite_7f ; CPU address $8cb8 - unknown (doesn't seem to be used) + +sprite_ptr_tbl_1: + .addr sprite_80 ; (offset 00) CPU address $8cb8 - unknown (doesn't seem to be used) + .addr sprite_81 ; (offset 01) CPU address $95b8 - unknown (doesn't seem to be used) + .addr sprite_82 ; (offset 02) CPU address $9eb8 - l bullet indoor level + .addr sprite_83 ; (offset 03) CPU address $a3b8 - l bullet indoor level + .addr sprite_84 ; (offset 04) CPU address $a8b8 - l bullet indoor level + .addr sprite_85 ; (offset 05) CPU address $adb8 - base 2 boss blue soldier + .addr sprite_86 ; (offset 06) CPU address $c2b8 - base 2 boss blue soldier + .addr sprite_87 ; (offset 07) CPU address $d3b8 - base 2 boss blue soldier + .addr sprite_88 ; (offset 08) CPU address $e0b8 - base 2 blue soldier facing out (frame 1) + .addr sprite_89 ; (offset 09) CPU address $f1b8 - base 2 blue soldier facing out (frame 2) + .addr sprite_8a ; (offset 0a) CPU address $02b9 - base 2 blue soldier flying (frame 1) + .addr sprite_8b ; (offset 0b) CPU address $17b9 - base 2 blue soldier flying (frame 2) + .addr sprite_8c ; (offset 0c) CPU address $34b9 - base boss level 4 base 2 red soldier + .addr sprite_8d ; (offset 0d) CPU address $3cb9 - base boss level 4 base 2 red soldier + .addr sprite_8e ; (offset 0e) CPU address $44b9 - base boss level 4 base 2 red soldier + .addr sprite_8f ; (offset 0f) CPU address $4cb9 - base boss level 4 base 2 red soldier facing player + .addr sprite_90 ; (offset 10) CPU address $54b9 - base boss level 4 base 2 red soldier facing player with weapon + .addr sprite_91 ; (offset 11) CPU address $65b9 - indoor boss defeated elevator with player on top + .addr sprite_92 ; (offset 12) CPU address $75b9 - l bullet indoor level + .addr sprite_93 ; (offset 13) CPU address $7ab9 - jumping man + .addr sprite_94 ; (offset 14) CPU address $8bb9 - jumping man + .addr sprite_95 ; (offset 15) CPU address $9cb9 - jumping man + .addr sprite_96 ; (offset 16) CPU address $adb9 - indoor soldier hit by bullet (indoor soldier, jumping man, grenade launcher, group of four soldiers) + .addr sprite_97 ; (offset 17) CPU address $beb9 - jumping man in air + .addr sprite_98 ; (offset 18) CPU address $d3b9 - jumping man facing player + .addr sprite_99 ; (offset 19) CPU address $e0b9 - small indoor rolling grenade + .addr sprite_9a ; (offset 1a) CPU address $e9b9 - closer indoor rolling grenade + .addr sprite_9b ; (offset 1b) CPU address $f2b9 - even closer indoor rolling grenade + .addr sprite_9c ; (offset 1c) CPU address $ffb9 - closest indoor rolling grenade + .addr sprite_9d ; (offset 1d) CPU address $0cba - indoor base enemy kill explosion (frame 1) + .addr sprite_9e ; (offset 1e) CPU address $0fba - indoor base enemy kill explosion (frame 2) + .addr sprite_9f ; (offset 1f) CPU address $18ba - indoor base enemy kill explosion (frame 3) + .addr sprite_a0 ; (offset 20) CPU address $21ba - indoor hand grenade + .addr sprite_a1 ; (offset 21) CPU address $2aba - indoor hand grenade + .addr sprite_a2 ; (offset 22) CPU address $2fba - indoor hand grenade + .addr sprite_a3 ; (offset 23) CPU address $32ba - indoor hand grenade + .addr sprite_a4 ; (offset 24) CPU address $37ba - indoor hand grenade + .addr sprite_a5 ; (offset 25) CPU address $3cba - indoor hand grenade + .addr sprite_a6 ; (offset 26) CPU address $41ba - indoor hand grenade + .addr sprite_a7 ; (offset 27) CPU address $44ba - indoor hand grenade + .addr sprite_a8 ; (offset 28) CPU address $49ba - indoor hand grenade + .addr sprite_a9 ; (offset 29) CPU address $4cba - indoor hand grenade + .addr sprite_aa ; (offset 2a) CPU address $50ba - falcon (player select icon) + .addr sprite_ab ; (offset 2b) CPU address $59ba - Bill and Lance's hair and shirt + .addr sprite_ac ; (offset 2c) CPU address $b2ba - alien's lair bundle (crustacean-like alien) + .addr sprite_ad ; (offset 2d) CPU address $cbba - alien's lair bundle (crustacean-like alien) mouth open + .addr sprite_ae ; (offset 2e) CPU address $d7ba - alien's lair bundle (crustacean-like alien) + .addr sprite_af ; (offset 2f) CPU address $f0ba - alien's lair bundle (crustacean-like alien) + .addr sprite_b0 ; (offset 30) CPU address $fcba - poisonous insect gel + .addr sprite_b1 ; (offset 31) CPU address $05bb - poisonous insect gel (frame 1) + .addr sprite_b2 ; (offset 32) CPU address $0ebb - poisonous insect gel (frame 2) + .addr sprite_b3 ; (offset 33) CPU address $17bb - boss alien bugger insect/spider (frame 1) + .addr sprite_b4 ; (offset 34) CPU address $30bb - boss alien bugger insect/spider (frame 2) + .addr sprite_b5 ; (offset 35) CPU address $51bb - boss alien bugger insect/spider (frame 3) + .addr sprite_b6 ; (offset 36) CPU address $6ebb - boss alien eggron (alien egg) + .addr sprite_b7 ; (offset 37) CPU address $77bb - energy zone boss giant armored soldier gordea + .addr sprite_b7 ; (offset 38) CPU address $77bb - energy zone boss giant armored soldier gordea + .addr sprite_b9 ; (offset 39) CPU address $d0bb - energy zone boss giant armored soldier gordea (legs together) + .addr sprite_ba ; (offset 3a) CPU address $19bc - energy zone boss giant armored soldier gordea (running, jumping) + .addr sprite_bb ; (offset 3b) CPU address $45bc - energy zone boss projectile (spiked disk) + .addr sprite_bc ; (offset 3c) CPU address $4ebc - energy zone boss projectile (spiked disk) + .addr sprite_bd ; (offset 3d) CPU address $57bc - turret man (basquez) + .addr sprite_be ; (offset 3e) CPU address $74bc - turret man (basquez) + .addr sprite_bf ; (offset 3f) CPU address $91bc - energy zone wall fire + .addr sprite_c0 ; (offset 40) CPU address $96bc - energy zone wall fire + .addr sprite_c1 ; (offset 41) CPU address $9bbc - energy zone ceiling fire + .addr sprite_c2 ; (offset 42) CPU address $a0bc - energy zone ceiling fire + .addr sprite_c3 ; (offset 43) CPU address $a5bc - energy zone boss giant armored soldier gordea (throwing) + .addr sprite_c4 ; (offset 44) CPU address $cdbc - snow field ground separator + .addr sprite_c5 ; (offset 45) CPU address $d2bc - green helicopter ending scene (frame 1) + .addr sprite_c6 ; (offset 46) CPU address $dbbc - green helicopter ending scene (frame 2) + .addr sprite_c7 ; (offset 47) CPU address $e8bc - green helicopter ending scene (frame 3) + .addr sprite_c8 ; (offset 48) CPU address $f1bc - green helicopter ending scene (frame 4) + .addr sprite_c9 ; (offset 49) CPU address $febc - green helicopter facing forward (frame 1) + .addr sprite_ca ; (offset 4a) CPU address $13bd - green helicopter facing forward (frame 2) + .addr sprite_cb ; (offset 4b) CPU address $30bd - green helicopter facing forward (frame 3) + .addr sprite_cc ; (offset 4c) CPU address $59bd - green helicopter facing forward (frame 4) + .addr sprite_cd ; (offset 4d) CPU address $7ebd - green helicopter facing forward (frame 5) + .addr sprite_ce ; (offset 4e) CPU address $abbd - green helicopter facing forward (frame 6) + .addr sprite_cf ; (offset 4f) CPU address $d0bd - ending sequence mountains + +; each sprite entry is defined as follows +; first byte (n) is number of tiles for sprite +; followed by n 4-byte groups +; each 4-byte group defines a block that is 2 tiles tall (8x16 pixels) +; * byte 0: Y position in pixels (relative to sprite base position (SPRITE_Y_POS), can be negative) +; * byte 1: pattern table tile index/code +; * if even, tile is pulled from 0x0000 (sprite section) (left pattern table) +; * if odd, tile is pulled from 0x1000 (background section) (right pattern table) +; * one byte is for both tiles (8x16) +; * 1st tile is byte specified +; * 2nd tile is (byte + 1), i.e. the tile immediately after the one specified +; * byte 2: palette code, flipping, drawing priority (foreground or background) +; * x... .... - Vertical flip +; * .x.. .... - Horizontal flip +; * ..x. .... - Drawing priority (whether to draw behind background or not) +; * .... ..xx - Palette code +; * byte 3: X position in pixels (relative to sprite position (SPRITE_X_POS), can be negative) + +; EXCEPTIONS +; if first byte of sprite sequence is #$fe (e.g. sprite_07), then the sprite is considered a "small sprite" +; in this case, the sprite is made of a single entry. The entire byte sequence is 3 bytes long (including #$fe) +; the second byte is the pattern table tile, and the third byte is the sprite attributes +; the X position is set to #$fc (-4 decimal) and the Y position is set to #$f8 (-8 decimal). + +; if the first byte of a group sequence is #$80 (shared sprite), then the next two bytes are a CPU address to continue reading at. +; this address is then read to get the sprite CPU read address. +; this allows sprites to share parts of each other + +; blank +sprite_01: + .byte $00 + +; player walking (frame 1) +sprite_02: + .byte $05 + .byte $ee,$28,$01,$fb + .byte $ee,$2a,$01,$03 + +; no sprite code, only part of other sprite codes +player_walking_1_bottom: + .byte $fe,$34,$00,$f8 + .byte $fe,$36,$00,$00 + .byte $0e,$40,$00,$f8 + +; player walking (frame 2) +sprite_03: + .byte $05 + .byte $ef,$2c,$01,$fc + .byte $ef,$2e,$01,$04 + +; no sprite code, only part of other sprite codes +player_walking_2_bottom: + .byte $fd,$38,$00,$f8 + .byte $fd,$3a,$00,$00 + .byte $0d,$42,$00,$04 + +; player walking (frame 3) +sprite_04: + .byte $05 + .byte $ee,$30,$01,$fa + .byte $ee,$32,$01,$02 + +; no sprite code, only part of other sprite codes +player_bottom: + .byte $fe,$3c,$00,$f7 + .byte $fe,$3e,$00,$ff + .byte $0e,$42,$00,$fe + +; player walking (frame 4) +; player falling through floor, or walk off ledge +sprite_05: + .byte $05 + .byte $ee,$30,$01,$fa + .byte $ee,$32,$01,$02 + .byte $80 + .addr player_walking_1_bottom + +; player walking (frame 5) +sprite_06: + .byte $05 + .byte $ee,$28,$01,$fb + .byte $ee,$2a,$01,$03 + .byte $80 + .addr player_bottom + +; enemy bullet (snow field) +sprite_07: + .byte $fe,$ec,$02 + +; player curled up (frame 1) +sprite_08: + .byte $04 + .byte $f2,$44,$01,$f8 + .byte $f2,$48,$01,$00 + .byte $02,$46,$00,$f8 + .byte $02,$4a,$00,$00 + +; player curled up (frame 2) +sprite_09: + .byte $03 + .byte $f8,$4c,$00,$f6 + .byte $f8,$4e,$01,$fe + .byte $f8,$50,$01,$06 + +; player hit (frame 1) +sprite_0a: + .byte $04 + .byte $f3,$68,$01,$f6 + .byte $03,$6a,$00,$f6 + .byte $f7,$6c,$00,$fe + .byte $f7,$6e,$00,$06 + +; player hit (frame 2) +sprite_0b: + .byte $04 + .byte $f9,$70,$00,$f8 + .byte $fa,$74,$00,$ff + .byte $ea,$72,$00,$fc + .byte $ff,$76,$01,$fa + +; player lying on ground +sprite_0c: + .byte $04 + .byte $00,$78,$01,$f0 + .byte $00,$7a,$01,$f8 + .byte $00,$7c,$00,$00 + .byte $00,$7e,$00,$08 + +; player walking holding weapon out (frame 1) +sprite_0d: + .byte $06 + .byte $ee,$94,$01,$f9 + .byte $ee,$96,$01,$01 + .byte $ee,$98,$01,$09 + .byte $80 + .addr player_walking_1_bottom + +; player walking holding weapon out (frame 2) +sprite_0e: + .byte $06 + .byte $ef,$94,$01,$f9 + .byte $ef,$96,$01,$01 + .byte $ef,$98,$01,$09 + .byte $80 + .addr player_walking_2_bottom + +; player walking holding weapon out (frame 3) +sprite_0f: + .byte $06 + +player_facing_side: + .byte $ee,$94,$01,$f9 + .byte $ee,$96,$01,$01 + .byte $ee,$98,$01,$09 + .byte $80 + .addr player_bottom + +; player aiming angled up (frame 1) +sprite_10: + .byte $05 + .byte $ee,$8c,$01,$fb + .byte $ee,$8e,$01,$03 + .byte $80 + .addr player_walking_1_bottom + +; player aiming angled up (frame 2) +sprite_11: + .byte $05 + .byte $ef,$8c,$01,$fb + .byte $ef,$8e,$01,$03 + .byte $80 + .addr player_walking_2_bottom + +; player aiming angled up (frame 3) +sprite_12: + .byte $05 + .byte $ee,$8c,$01,$fb + .byte $ee,$8e,$01,$03 + .byte $80 + .addr player_bottom + +; player aiming angled down (frame 1) +sprite_13: + .byte $06 + .byte $ee,$86,$01,$f8 + .byte $ee,$88,$01,$00 + .byte $f3,$8a,$01,$05 + .byte $80 + .addr player_walking_1_bottom + +; player aiming angled down (frame 2) +sprite_14: + .byte $06 + .byte $ef,$86,$01,$f8 + .byte $ef,$88,$01,$00 + .byte $f4,$8a,$01,$05 + .byte $80 + .addr player_walking_2_bottom + +; player aiming angled down (frame 3) +sprite_15: + .byte $06 + .byte $ee,$86,$01,$f8 + .byte $ee,$88,$01,$00 + .byte $f3,$8a,$01,$05 + .byte $80 + .addr player_bottom + +; player aiming straight up +sprite_16: + .byte $06 + .byte $de,$82,$01,$01 + .byte $ee,$80,$01,$f9 + .byte $ee,$84,$01,$01 + .byte $80 + .addr player_bottom + +; player prone +sprite_17: + .byte $04 + .byte $00,$9c,$00,$f0 + .byte $00,$9e,$00,$f8 + .byte $00,$a0,$01,$00 + .byte $00,$a2,$01,$08 + +; water splash/puddle +sprite_18: + .byte $02 + +water_splash: + .byte $fa,$dc,$01,$f8 + .byte $fa,$dc,$41,$00 + +; player in water +sprite_19: + .byte $02 + .byte $f2,$de,$01,$f8 + .byte $f2,$e0,$01,$00 + +; player climbing out of water +sprite_1a: + .byte $02 + .byte $fa,$e2,$01,$f8 + .byte $fa,$e4,$01,$00 + +; player in water aiming straight up +sprite_1b: + .byte $05 + .byte $df,$82,$01,$01 + .byte $ef,$80,$01,$f9 + .byte $ef,$84,$01,$01 + .byte $80 + .addr water_splash + +; player in water aiming angled up +sprite_1c: + .byte $04 + .byte $ef,$8c,$01,$fb + .byte $ef,$8e,$01,$03 + .byte $80 + .addr water_splash + +; player in water aiming forward +sprite_1d: + .byte $05 + .byte $ef,$94,$01,$f9 + .byte $ef,$96,$01,$01 + .byte $ef,$98,$01,$09 + .byte $80 + .addr water_splash + +; default bullet +sprite_1e: + .byte $fe,$0e,$02 + +; M bullet +sprite_1f: + .byte $fe,$10,$02 + +; S bullet, mortar +sprite_20: + .byte $fe,$12,$02 + +; boss turret bullet +sprite_21: + .byte $fe,$14,$02 + +; F bullet and snow field level boss ufo bomb +sprite_22: + .byte $fe,$16,$02 + +; L bullet (up) +sprite_23: + .byte $fe,$18,$02 + +; L bullet +sprite_24: + .byte $02 + .byte $f8,$92,$02,$f4 + .byte $f8,$92,$02,$fc + +; L bullet (angled) +sprite_25: + .byte $fe,$90,$02 + +; soldier crouching shooting +sprite_26: + .byte $04 + .byte $00,$dc,$03,$f0 + .byte $00,$de,$01,$f8 + .byte $00,$e0,$01,$00 + .byte $00,$e2,$03,$08 + +; soldier running 1 +sprite_27: + .byte $04 + .byte $f0,$d6,$01,$f8 + .byte $f0,$d8,$01,$00 + +soldier_bottom_0: + .byte $00,$ce,$03,$f8 + .byte $00,$cc,$03,$00 + +; soldier running 2 +sprite_28: + .byte $04 + .byte $f0,$be,$01,$f8 + .byte $f0,$c0,$01,$00 + .byte $80 + .addr soldier_bottom_0 + +; soldier shooting angled up +sprite_29: + .byte $04 + .byte $f0,$ae,$01,$f8 + .byte $f0,$b0,$01,$00 + .byte $00,$ba,$01,$f8 + .byte $00,$bc,$01,$00 + +; hangar mine cart (frame 1) +sprite_2a: + .byte $06 + +hangar_mine_cart: + .byte $f6,$e4,$02,$f0 + .byte $f6,$e6,$02,$f8 + .byte $f6,$e8,$02,$00 + .byte $f6,$fc,$02,$08 + .byte $04,$ea,$00,$f9 ; wheel + .byte $04,$ea,$00,$05 ; wheel + +; hangar mine cart (frame 2) +sprite_2b: + .byte $06 + .byte $04,$ec,$00,$f9 + .byte $04,$ec,$00,$05 + .byte $80 + .addr hangar_mine_cart + +; sniper type #$04 shooting (boss rifle man), compare sprite_43 +sprite_2c: + .byte $05 + .byte $f0,$f2,$41,$f8 + .byte $f0,$b6,$01,$00 + .byte $f0,$b8,$01,$08 + .byte $00,$f4,$41,$f8 + .byte $00,$bc,$01,$00 + +; soldier shooting angled down +sprite_2d: + .byte $05 + .byte $f0,$f6,$41,$f8 + .byte $f0,$aa,$01,$00 + .byte $f8,$ac,$01,$08 + .byte $00,$f4,$41,$f8 + .byte $00,$bc,$01,$00 + +; unknown (doesn't seem to be used) +sprite_2e: + .byte $04 + .byte $f0,$fa,$41,$f8 + .byte $f0,$f8,$41,$00 + .byte $00,$f4,$41,$f8 + .byte $00,$fc,$41,$00 + +; S weapon item +sprite_2f: + .byte $03 + .byte $f8,$1c,$01,$fc + +weapon_wings: + .byte $f8,$1a,$01,$f4 + .byte $f8,$1a,$41,$04 + +; B weapon item +sprite_30: + .byte $03 + .byte $f8,$1e,$01,$fc + .byte $80 + .addr weapon_wings + +; F weapon item +sprite_31: + .byte $03 + .byte $f8,$20,$01,$fc + .byte $80 + .addr weapon_wings + +; L weapon item +sprite_32: + .byte $03 + .byte $f8,$22,$01,$fc + .byte $80 + .addr weapon_wings + +; R weapon item +sprite_33: + .byte $03 + .byte $f8,$24,$01,$fc + .byte $80 + .addr weapon_wings + +; M weapon item +sprite_34: + .byte $03 + .byte $f8,$26,$01,$fc + .byte $80 + .addr weapon_wings + +; big explosion +sprite_35: + .byte $08 + .byte $f0,$54,$02,$f0 + .byte $f0,$56,$02,$f8 + .byte $f0,$58,$02,$00 + .byte $f0,$5a,$02,$08 + .byte $00,$5a,$c2,$f0 + .byte $00,$58,$c2,$f8 + .byte $00,$56,$c2,$00 + .byte $00,$54,$c2,$08 + +; explosion +sprite_36: + .byte $05 + .byte $f0,$56,$02,$fc + .byte $f0,$5a,$02,$04 + .byte $f8,$52,$02,$f4 + .byte $00,$56,$c2,$fc + .byte $00,$54,$c2,$04 + +; small explosion +sprite_37: + .byte $02 + .byte $f8,$52,$82,$f8 + .byte $f4,$5a,$02,$00 + +; round explosion +sprite_38: + .byte $06 + .byte $f0,$5e,$02,$f8 + .byte $f0,$5e,$42,$00 + .byte $f8,$5c,$02,$f0 + .byte $f8,$5c,$c2,$08 + .byte $00,$5e,$82,$f8 + .byte $00,$5e,$c2,$00 + +; thick explosion ring +sprite_39: + .byte $08 + .byte $f0,$60,$02,$f0 + .byte $f0,$62,$02,$f8 + .byte $f0,$62,$42,$00 + .byte $f0,$60,$42,$08 + .byte $00,$60,$82,$f0 + .byte $00,$62,$82,$f8 + .byte $00,$62,$c2,$00 + .byte $00,$60,$c2,$08 + +; wide explosion ring +sprite_3a: + .byte $08 + .byte $f0,$64,$02,$f0 + .byte $f0,$66,$02,$f8 + .byte $f0,$66,$42,$00 + .byte $f0,$64,$42,$08 + .byte $00,$64,$82,$f0 + .byte $00,$66,$82,$f8 + .byte $00,$66,$c2,$00 + .byte $00,$64,$c2,$08 + +; soldier running +sprite_3b: + .byte $04 + .byte $f0,$be,$01,$f8 + .byte $f0,$c0,$01,$00 + +soldier_bottom_1: + .byte $00,$ca,$03,$f8 + .byte $00,$cc,$03,$00 + +; soldier running +sprite_3c: + .byte $04 + .byte $f0,$c2,$01,$f8 + .byte $f0,$c4,$01,$00 + .byte $00,$ce,$03,$f8 + .byte $00,$d0,$03,$00 + +; soldier running +sprite_3d: + .byte $04 + .byte $f0,$c6,$01,$f8 + .byte $f0,$c8,$01,$00 + +soldier_bottom_2: + .byte $00,$d2,$03,$f8 + .byte $00,$d4,$03,$00 + +; soldier running +sprite_3e: + .byte $04 + .byte $f0,$be,$01,$f8 + .byte $f0,$c0,$01,$00 + .byte $80 + .addr soldier_bottom_2 + +; soldier running +sprite_3f: + .byte $04 + .byte $f0,$c6,$01,$f8 + .byte $f0,$c8,$01,$00 + .byte $80 + .addr soldier_bottom_1 + +; soldier shooting +sprite_40: + .byte $05 + .byte $f0,$d6,$01,$f8 + .byte $f0,$d8,$01,$00 + .byte $f0,$da,$03,$08 + .byte $80 + .addr soldier_bottom_2 + +; soldier shooting downward +sprite_41: + .byte $05 + .byte $f0,$a8,$01,$f8 + .byte $f0,$aa,$01,$00 + .byte $f8,$ac,$01,$08 + +soldier_bottom_3: + .byte $00,$ba,$01,$f8 + .byte $00,$bc,$01,$00 + +; soldier shooting up angled +sprite_42: + .byte $05 + .byte $f0,$ae,$01,$f8 + .byte $f0,$b0,$01,$00 + .byte $e0,$b2,$01,$02 + .byte $80 + .addr soldier_bottom_3 + +; rifle man shooting (sniper type #$00 and #$01) +sprite_43: + .byte $05 + +rifle_man_top: + .byte $f0,$b4,$01,$f8 + .byte $f0,$b6,$01,$00 + .byte $f0,$b8,$01,$08 + .byte $80 + .addr soldier_bottom_3 + +; rifle man behind bush (frame 1) +sprite_44: + .byte $01 + .byte $f0,$ea,$41,$fc + +; rifle man behind bush (frame 2) +sprite_45: + .byte $03 + .byte $f0,$f0,$41,$f8 + .byte $f0,$ee,$41,$00 + .byte $f0,$ec,$41,$08 + +; rifle man behind bush (frame 3) +; by specifying #$03 entries, only top half of soldier is drawn +; soldier_bottom_3 isn't used +sprite_46: + .byte $03 + .byte $80 + .addr rifle_man_top + +; small ring explosion +sprite_47: + .byte $fe,$0c,$02 + +; floating rock (waterfall level) +sprite_48: + .byte $07 + .byte $f0,$ee,$03,$f0 + .byte $f0,$f0,$03,$f8 + .byte $f0,$f0,$43,$00 + .byte $f0,$ee,$43,$08 + .byte $00,$f2,$03,$f4 + .byte $00,$fc,$03,$fc + .byte $00,$f2,$43,$04 + +; bridge fire (waterfall level) +sprite_49: + .byte $02 + .byte $f8,$ea,$02,$f8 + .byte $f8,$ec,$02,$00 + +; boulder (waterfall level) +sprite_4a: + .byte $06 + .byte $f0,$e6,$03,$f4 + .byte $f0,$e8,$03,$fc + .byte $f0,$d7,$03,$04 + .byte $00,$d9,$03,$f4 + .byte $00,$db,$03,$fc + .byte $00,$dd,$03,$04 + +; scuba soldier hiding +sprite_4b: + .byte $02 + .byte $f8,$dc,$01,$f8 + .byte $f8,$de,$01,$00 + +; scuba soldier out of water shooting up +sprite_4c: + .byte $03 + .byte $e8,$e2,$03,$00 + .byte $f8,$e0,$01,$f8 + .byte $f8,$e4,$01,$00 + +; weapon zeppelin +sprite_4d: + .byte $03 + .byte $f8,$a4,$01,$f4 + .byte $f8,$a6,$01,$fc + .byte $f8,$a4,$41,$04 + +; flashing falcon weapon +sprite_4e: + .byte $03 + .byte $f8,$00,$01,$fc + .byte $80 + .addr weapon_wings + +; unused blank sprite +sprite_4f: + .byte $00 + +; indoor player facing up +sprite_50: + .byte $07 + .byte $de,$7c,$01,$fa + .byte $ee,$7e,$01,$f8 + .byte $ee,$80,$01,$00 + .byte $fe,$78,$00,$f8 + .byte $fe,$78,$40,$ff + .byte $0e,$7a,$00,$f6 + .byte $0e,$7a,$40,$01 + +; indoor player strafing (frame 1) +sprite_51: + .byte $05 + .byte $de,$7c,$01,$fb + .byte $ee,$8c,$01,$fa + .byte $ee,$8e,$01,$02 + .byte $80 + .addr player_walking_1_bottom + +; indoor player strafing (frame 2) +sprite_52: + .byte $06 + .byte $df,$7c,$01,$fb + .byte $ef,$8c,$01,$fa + .byte $ef,$8e,$01,$02 + .byte $80 + .addr player_walking_2_bottom + +; indoor player strafing (frame 3) +sprite_53: + .byte $06 + .byte $de,$7c,$01,$fb + .byte $ee,$8c,$01,$fa + .byte $ee,$8e,$01,$02 + .byte $80 + .addr player_bottom + +; indoor player crouch +sprite_54: + .byte $07 + .byte $f4,$82,$01,$f8 + .byte $f4,$84,$01,$00 + .byte $0a,$86,$00,$f4 + .byte $0a,$86,$40,$04 + .byte $04,$88,$00,$f8 + .byte $04,$88,$40,$00 + .byte $fa,$8a,$00,$fc + +; indoor player electrocuted +; indoor player hit by bullet frame #$01 +sprite_55: + .byte $08 + .byte $e2,$70,$01,$01 + .byte $f5,$72,$01,$f0 + .byte $ed,$74,$01,$f8 + .byte $ed,$76,$01,$00 + .byte $fd,$78,$00,$f8 + .byte $fd,$78,$40,$ff + .byte $0d,$7a,$00,$f6 + .byte $0d,$7a,$40,$01 + +; indoor player lying dead (frame #$02) +sprite_56: + .byte $08 + .byte $fa,$68,$00,$f0 + .byte $fa,$6a,$00,$f8 + .byte $fa,$6a,$40,$00 + .byte $fa,$68,$40,$08 + .byte $0a,$6c,$01,$f0 + .byte $0a,$6e,$01,$f8 + .byte $0a,$6e,$41,$00 + .byte $0a,$6c,$41,$08 + +; indoor player running +sprite_57: + .byte $06 + .byte $f4,$94,$01,$f4 + .byte $ef,$96,$01,$fc + .byte $ef,$98,$01,$04 + .byte $ff,$be,$00,$f8 + .byte $ff,$90,$00,$00 + .byte $0f,$92,$00,$00 + +; indoor player running +sprite_58: + .byte $06 + .byte $f1,$9a,$01,$f4 + .byte $f1,$9c,$01,$fc + .byte $f2,$9e,$01,$04 + .byte $01,$90,$40,$fa + .byte $01,$be,$40,$02 + .byte $11,$92,$40,$fa + +; unused blank sprite +sprite_59: + .byte $00 + +; boss eye +sprite_5d: + .byte $08 + .byte $fe,$a6,$02,$f8 + .byte $fe,$a6,$42,$00 + .byte $fe,$9e,$03,$f8 + .byte $fe,$9e,$43,$00 + +boss_eye_top: + .byte $ee,$9a,$03,$f8 + .byte $ee,$9a,$43,$00 + .byte $f6,$9c,$03,$f0 + .byte $f6,$9c,$43,$08 + +; boss eye +sprite_5e: + .byte $08 + .byte $fe,$a6,$02,$f8 + .byte $fe,$a6,$42,$00 + .byte $fe,$a0,$03,$f8 + .byte $fe,$a0,$43,$00 + .byte $80 + .addr boss_eye_top + +; boss eye +sprite_5f: + .byte $08 + +boss_eye_part: + .byte $f6,$a8,$02,$f0 + .byte $f6,$a8,$42,$08 + .byte $fe,$a2,$03,$f8 + .byte $fe,$a2,$43,$00 + .byte $80 + .addr boss_eye_top + +; boss eye +sprite_60: + .byte $08 + .byte $fe,$aa,$02,$f8 + .byte $fe,$aa,$42,$00 + +boss_eye_int: + .byte $fe,$a4,$03,$f8 + .byte $fe,$a4,$43,$00 + .byte $80 + .addr boss_eye_top + +; boss eye +sprite_61: + .byte $08 + .byte $fe,$ac,$02,$f8 + .byte $fe,$ac,$42,$00 + .byte $80 + .addr boss_eye_int + +; boss eye +sprite_62: + .byte $09 + .byte $fe,$ae,$02,$fc + .byte $80 + .addr boss_eye_part + +; small boss eye projectile (unused) +sprite_63: + .byte $02 + .byte $f8,$b0,$03,$f8 + .byte $f8,$b2,$03,$00 + +; boss eye projectile +sprite_64: + .byte $08 + .byte $f0,$b4,$03,$f0 + .byte $f0,$b6,$03,$f8 + .byte $f0,$b8,$03,$00 + .byte $f0,$ba,$03,$08 + .byte $00,$bc,$03,$f0 + .byte $00,$be,$03,$f8 + .byte $00,$c0,$03,$00 + .byte $00,$c2,$03,$08 + +; unused blank sprite +sprite_65: + .byte $00 + +; base 2 boss metal helmet (Godomuga) (frame 1) +sprite_68: + .byte $08 + .byte $f0,$dc,$03,$f8 + .byte $f0,$dc,$43,$00 + .byte $00,$d2,$03,$f8 + .byte $00,$d2,$43,$00 + .byte $00,$d0,$03,$f0 + .byte $00,$d0,$43,$08 + +metal_helmet_ears: + .byte $f0,$da,$03,$f0 + .byte $f0,$da,$43,$08 + +; base 2 boss metal helmet (Godomuga) (frame 2) +sprite_69: + .byte $08 + .byte $f0,$de,$03,$f8 + .byte $f0,$de,$43,$00 + +metal_helmet_part: + .byte $00,$d6,$03,$f8 + .byte $00,$d6,$43,$00 + +metal_helmet_mouth: + .byte $00,$d4,$03,$f0 + .byte $00,$d4,$43,$08 + .byte $80 + .addr metal_helmet_ears + +; base 2 boss metal helmet (Godomuga) (frame 3) +sprite_6a: + .byte $08 + .byte $f0,$e0,$03,$f8 + .byte $f0,$e0,$43,$00 + +metal_helmet_mouth_inside: + .byte $00,$d8,$03,$f8 + .byte $00,$d8,$43,$00 + .byte $80 + .addr metal_helmet_mouth + +; base 2 boss metal helmet (Godomuga) (frame 4) +sprite_6b: + .byte $08 + .byte $f0,$e2,$02,$f8 + .byte $f0,$e2,$42,$00 + .byte $80 + .addr metal_helmet_part + +; base 2 boss metal helmet (Godomuga) (frame 5) +sprite_6c: + .byte $08 + .byte $f0,$e4,$02,$f8 + .byte $f0,$e4,$42,$00 + .byte $80 + .addr metal_helmet_mouth_inside + +; base 2 boss metal helmet (Godomuga) bubble projectile +sprite_6d: + .byte $02 + .byte $f8,$e8,$03,$f8 + .byte $f8,$e6,$02,$fe + +; base 2 boss metal helmet (Godomuga) bubble projectile +sprite_6e: + .byte $02 + .byte $f8,$ec,$03,$fa + .byte $f8,$ea,$02,$01 + +; base 2 boss metal helmet (Godomuga) bubble projectile +sprite_6f: + .byte $fe,$ec,$03 + +; base 2 boss metal helmet (Godomuga) bubble projectile +sprite_70: + .byte $02 + .byte $f8,$ea,$42,$f7 + .byte $f8,$ec,$43,$fe + +; base 2 boss metal helmet (Godomuga) bubble projectile +sprite_71: + .byte $02 + .byte $f8,$e6,$42,$fa + .byte $f8,$e8,$43,$00 + +; base 2 boss metal helmet (Godomuga) bubble projectile +sprite_72: + .byte $fe,$ec,$02 + +; water splash +sprite_73: + .byte $02 + .byte $f8,$e6,$00,$f8 + .byte $f8,$e6,$40,$00 + +; ice grenade lean right +sprite_74: + .byte $02 + .byte $f8,$e6,$00,$f8 + .byte $f8,$e6,$c0,$00 + +; ice grenade horizontal +sprite_75: + .byte $02 + .byte $f8,$e8,$00,$f8 + .byte $f8,$e8,$40,$00 + +; ice grenade tipping downward +sprite_76: + .byte $02 + .byte $f8,$e6,$80,$f8 + .byte $f8,$e6,$40,$00 + +; ice grenade vertical +sprite_77: + .byte $02 + .byte $f8,$ea,$80,$f8 + .byte $f8,$ea,$40,$00 + +; !(UNUSED) duplicate of sprite_74 (ice grenade lean right), unused in game +sprite_78: + .byte $02 + .byte $f8,$e6,$00,$f8 + .byte $f8,$e6,$c0,$00 + +; dragon boss projectile +sprite_79: + .byte $02 + .byte $f8,$f4,$02,$f8 + .byte $f8,$f6,$02,$00 + +; dragon arm interior orb (gray) +sprite_7a: + .byte $02 + .byte $f8,$f8,$03,$f8 + .byte $f8,$f8,$43,$00 + +; dragon arm hand orb (red) +sprite_7b: + .byte $02 + .byte $f8,$fa,$01,$f8 + .byte $f8,$fa,$41,$00 + +; snow field boss mini UFO +sprite_7c: + .byte $03 + .byte $f8,$fc,$03,$f4 + .byte $f8,$33,$03,$fc + .byte $f8,$35,$03,$04 + +; snow field boss mini UFO +sprite_7d: + .byte $03 + .byte $f8,$37,$03,$f4 + .byte $f8,$39,$03,$fc + .byte $f8,$3b,$03,$04 + +; snow field boss mini UFO +sprite_7e: + .byte $03 + .byte $f8,$3d,$03,$f4 + .byte $f8,$3f,$03,$fc + .byte $f8,$41,$03,$04 + +; unknown (doesn't seem to be used) +sprite_7f: +sprite_80: + .byte $02 + .byte $f8,$ca,$02,$f8 + .byte $f8,$ca,$42,$00 + +; unknown (doesn't seem to be used) +sprite_81: + .byte $02 + .byte $f8,$cc,$02,$f8 + .byte $f8,$cc,$42,$00 + +; l bullet indoor level shot from +; * 27%-43% horizontal portion of the playable screen +; * 58%-70% horizontal portion of the playable screen +sprite_82: + .byte $01 + .byte $f8,$a2,$02,$fc + +; l bullet indoor level shot from +; * 20%-27% horizontal portion of the playable screen +; * 70%-80% horizontal portion of the playable screen +sprite_83: + .byte $01 + .byte $f8,$a0,$02,$fc + +; l bullet indoor level shot from +; * 10%-20% horizontal portion of the playable screen +; * 80%-90% horizontal portion of the playable screen +sprite_84: + .byte $01 + .byte $f8,$a2,$02,$fc + +; base 2 boss blue soldier +sprite_85: + .byte $05 + .byte $f0,$f2,$03,$08 + +base_2_soldier_part: + .byte $f0,$ee,$03,$f8 + .byte $f0,$f0,$03,$00 + .byte $00,$f4,$03,$f6 + .byte $00,$f6,$03,$fe + +; base 2 boss blue soldier +sprite_86: + .byte $04 + +base_2_soldier_bottom: + .byte $e8,$f8,$03,$fe + .byte $f8,$fa,$03,$f6 + .byte $f8,$ce,$03,$fe + .byte $08,$dd,$03,$03 + +; base 2 boss blue soldier +sprite_87: + .byte $03 + +base_2_soldier_bottom_2: + .byte $f0,$df,$03,$f8 + .byte $f0,$e1,$03,$00 + .byte $00,$e3,$03,$fc + +; base 2 blue soldier facing out (frame 1) +sprite_88: + .byte $04 + +blue_soldier_facing_out: + .byte $f0,$e5,$43,$f9 + .byte $f0,$e5,$03,$00 + .byte $00,$e7,$43,$f9 + .byte $00,$e7,$03,$00 + +; base 2 blue soldier facing out (frame 2) +sprite_89: + .byte $04 + .byte $f0,$e9,$43,$f9 + .byte $f0,$e9,$03,$00 + .byte $00,$eb,$43,$f9 + .byte $00,$eb,$03,$00 + +; base 2 blue soldier flying (frame 1) +sprite_8a: + .byte $05 + .byte $f0,$ed,$03,$f4 + .byte $f0,$ef,$03,$fc + .byte $f0,$ed,$43,$04 + .byte $00,$f1,$03,$f8 + .byte $00,$f3,$03,$00 + +; base 2 blue soldier flying (frame 2) +sprite_8b: + .byte $07 + .byte $f2,$f5,$03,$f4 + .byte $f2,$f7,$03,$fc + .byte $f2,$f5,$43,$04 + .byte $02,$f9,$03,$f8 + .byte $02,$fb,$03,$00 + .byte $12,$b0,$03,$f7 + .byte $12,$b2,$03,$00 + +; base boss level 4 base 2 red soldier +sprite_8c: + .byte $05 + .byte $f0,$fc,$02,$08 + .byte $80 + .addr base_2_soldier_part + +; base boss level 4 base 2 red soldier +sprite_8d: + .byte $05 + .byte $f8,$a6,$02,$04 + .byte $80 + .addr base_2_soldier_bottom + +; base boss level 4 base 2 red soldier +sprite_8e: + .byte $04 + .byte $ff,$c4,$02,$00 + .byte $80 + .addr base_2_soldier_bottom_2 + +; base boss level 4 base 2 red soldier facing player +sprite_8f: + .byte $05 + .byte $00,$c6,$02,$f4 + .byte $80 + .addr blue_soldier_facing_out + +; base boss level 4 base 2 red soldier facing player with weapon +sprite_90: + .byte $04 + .byte $f0,$c8,$42,$f9 + .byte $f0,$c8,$02,$00 + .byte $00,$ca,$02,$f8 + .byte $00,$cc,$02,$00 + +; indoor boss defeated elevator with player on top +sprite_91: + .byte $08 + .byte $0c,$fd,$02,$f4 + .byte $0c,$fd,$02,$fc + .byte $0c,$fd,$02,$04 + .byte $80 + .addr player_facing_side + +; l bullet indoor level shot from +; * <= 10% horizontal portion of the playable screen +; * >= 90% horizontal portion of the playable screen +sprite_92: + .byte $01 + .byte $f8,$a0,$02,$fc + +; jumping man +sprite_93: + .byte $04 + .byte $eb,$ce,$01,$ff + .byte $ec,$a8,$03,$fb + .byte $fc,$aa,$03,$f6 + .byte $fb,$ac,$03,$fe + +; jumping man +sprite_94: + .byte $04 + .byte $ed,$ae,$01,$fe + .byte $ed,$b0,$03,$fb + .byte $fd,$b2,$03,$f7 + .byte $fd,$b4,$03,$ff + +; jumping man +sprite_95: + .byte $04 + .byte $ed,$b6,$01,$ff + .byte $ed,$b8,$03,$fb + .byte $fd,$ba,$03,$f4 + .byte $fd,$bc,$03,$fc + +; indoor soldier hit by bullet sprite +; indoor soldier, jumping man, grenade launcher, group of four soldiers firing at player +sprite_96: + .byte $04 + .byte $eb,$da,$01,$ff + .byte $ed,$c0,$03,$fc + .byte $fd,$c2,$43,$f9 + .byte $fd,$c2,$03,$00 + +; jumping man in air +sprite_97: + .byte $05 + .byte $e8,$d0,$01,$fa + .byte $e8,$d2,$03,$fc + .byte $f8,$d4,$03,$f4 + .byte $f8,$d6,$03,$fc + .byte $f8,$d8,$03,$04 + +; jumping man facing player +sprite_98: + .byte $03 + .byte $f0,$da,$01,$ff + .byte $fc,$dc,$03,$fb + .byte $00,$de,$03,$03 + +; small indoor rolling grenade +sprite_99: + .byte $02 + .byte $fe,$e0,$01,$f8 + .byte $fe,$e0,$41,$00 + +; closer indoor rolling grenade +sprite_9a: + .byte $02 + .byte $fe,$e2,$01,$f8 + .byte $fe,$e2,$41,$00 + +; even closer indoor rolling grenade +sprite_9b: + .byte $03 + .byte $fd,$e4,$01,$f4 + .byte $fd,$e6,$01,$fc + .byte $fd,$e4,$41,$04 + +; closest indoor rolling grenade +sprite_9c: + .byte $03 + .byte $fc,$e8,$01,$f4 + .byte $fc,$ea,$41,$fc + .byte $fc,$e8,$41,$04 + +; indoor base enemy kill explosion (frame 1) +sprite_9d: + .byte $fe,$c8,$02 + +; indoor base enemy kill explosion (frame 2) +sprite_9e: + .byte $02 + .byte $f8,$ca,$02,$f8 + .byte $f8,$ca,$c2,$00 + +; indoor base enemy kill explosion (frame 3) +sprite_9f: + .byte $02 + .byte $f8,$cc,$02,$f8 + .byte $f8,$cc,$c2,$00 + +; indoor hand grenade +sprite_a0: + .byte $02 + .byte $f8,$ec,$01,$f8 + .byte $f8,$ee,$01,$00 + +; indoor hand grenade +sprite_a1: + .byte $01 + .byte $f8,$f0,$01,$fd + +; indoor hand grenade +sprite_a2: + .byte $fe,$f2,$01 + +; indoor hand grenade +sprite_a3: + .byte $01 + .byte $f8,$f4,$01,$fb + +; indoor hand grenade +sprite_a4: + .byte $01 + .byte $f8,$f6,$01,$fd + +; indoor hand grenade +sprite_a5: + .byte $01 + .byte $f8,$f8,$01,$fd + +; indoor hand grenade +sprite_a6: + .byte $fe,$fa,$01 + +; indoor hand grenade +sprite_a7: + .byte $01 + .byte $f8,$fc,$01,$fd + +; indoor hand grenade +sprite_a8: + .byte $fe,$c4,$01 + +; indoor hand grenade +sprite_a9: + .byte $fe,$c6,$01,$00 + +; falcon (player select icon) +sprite_aa: + .byte $02 + .byte $f8,$ce,$01,$f8 + .byte $f8,$d0,$01,$00 + +; Bill and Lance's hair and shirt +sprite_ab: + .byte $16 + .byte $00,$da,$01,$00 + .byte $00,$de,$01,$08 + .byte $00,$e2,$01,$10 + .byte $10,$dc,$02,$00 + .byte $10,$e0,$02,$08 + .byte $10,$e4,$02,$10 + .byte $20,$ea,$00,$05 + .byte $20,$f4,$00,$15 + .byte $30,$ec,$00,$05 + .byte $30,$f6,$00,$15 + .byte $40,$ee,$00,$05 + .byte $40,$f8,$00,$15 + .byte $fd,$d6,$02,$f8 + .byte $02,$d4,$02,$f0 + .byte $04,$d2,$02,$e8 + .byte $0c,$d8,$02,$f8 + .byte $1f,$e6,$00,$f5 + .byte $1f,$e8,$00,$fd + .byte $24,$f0,$00,$0d + .byte $24,$fa,$00,$1d + .byte $34,$f2,$00,$0d + .byte $34,$fc,$00,$1d + +; alien's lair bundle (crustacean-like alien) +sprite_ac: + .byte $06 + +alien_bundle: + .byte $00,$b0,$c2,$04 + .byte $00,$b2,$c2,$fc + .byte $00,$b4,$c2,$f4 + .byte $f0,$b6,$c2,$04 + .byte $f0,$b8,$c2,$fc + .byte $f0,$ba,$c2,$f4 + +; alien's lair bundle (crustacean-like alien) mouth open +sprite_ad: + .byte $06 + .byte $f0,$ea,$c2,$fc + .byte $f0,$ec,$c2,$f4 + .byte $80 + .addr alien_bundle + +; alien's lair bundle (crustacean-like alien) +sprite_ae: + .byte $06 + +alien_bundle_2: + .byte $00,$f0,$c2,$04 + .byte $00,$f2,$c2,$fc + .byte $00,$f4,$c2,$f4 + .byte $f0,$aa,$c2,$f4 + .byte $f0,$ac,$c2,$fc + .byte $f0,$ae,$c2,$fc + +; alien's lair bundle (crustacean-like alien) +sprite_af: + .byte $06 + .byte $f0,$f6,$c2,$04 + .byte $f0,$a8,$c2,$fc + .byte $80 + .addr alien_bundle_2 + +; alien pink blob +sprite_b0: + .byte $02 + .byte $f8,$fa,$03,$f8 + .byte $f8,$fa,$43,$00 + +; small alien boss spider (poisonous insect gel) (frame 1) +sprite_b1: + .byte $02 + .byte $f8,$fc,$03,$f8 + .byte $f8,$fc,$43,$00 + +; small alien boss spider (poisonous insect gel) (frame 2) +sprite_b2: + .byte $02 + .byte $f8,$f8,$03,$f8 + .byte $f8,$f8,$43,$00 + +; boss alien bugger insect/spider (frame 1) +sprite_b3: + .byte $06 + .byte $f0,$c0,$03,$f8 + .byte $f0,$c2,$03,$00 + .byte $00,$c4,$03,$f0 + .byte $00,$c6,$03,$f8 + .byte $00,$c8,$03,$00 + .byte $00,$ca,$03,$08 + +; boss alien bugger insect/spider (frame 2) +sprite_b4: + .byte $08 + .byte $f1,$cc,$03,$f0 + .byte $f1,$ce,$03,$f8 + .byte $f1,$d0,$03,$00 + .byte $f1,$d2,$03,$08 + .byte $01,$d4,$03,$f0 + .byte $01,$d6,$03,$f8 + .byte $01,$d8,$03,$00 + .byte $01,$da,$03,$08 + +; boss alien bugger insect/spider (frame 3) +sprite_b5: + .byte $07 + .byte $f0,$dc,$03,$f8 + .byte $f0,$de,$03,$00 + .byte $f0,$e0,$03,$08 + .byte $00,$e2,$03,$f0 + .byte $00,$e4,$03,$f8 + .byte $00,$e6,$03,$00 + .byte $00,$e8,$03,$08 + +; boss alien eggron (alien egg) +sprite_b6: + .byte $02 + .byte $f8,$bc,$03,$f8 + .byte $f8,$be,$03,$00 + +; energy zone boss giant armored soldier +sprite_b7: + .byte $16 + +giant_soldier_top: + .byte $d8,$dc,$02,$f0 + .byte $d8,$de,$02,$f8 + .byte $d8,$e0,$02,$00 + .byte $d8,$e2,$02,$08 + .byte $e4,$fd,$03,$e8 + .byte $e8,$e4,$03,$e0 + .byte $e8,$e6,$02,$f0 + .byte $e8,$e8,$02,$f8 + .byte $e8,$ea,$02,$00 + .byte $e7,$a8,$03,$06 + .byte $e7,$aa,$03,$0e + .byte $f7,$b4,$03,$0b + +giant_soldier_bottom: + .byte $f8,$ac,$03,$e8 + .byte $f8,$ae,$03,$f0 + .byte $f8,$b0,$03,$f8 + .byte $f8,$b2,$03,$00 + .byte $08,$b8,$03,$e8 + .byte $08,$ba,$03,$f0 + .byte $08,$bc,$03,$f8 + .byte $08,$be,$03,$00 + .byte $08,$c0,$03,$08 + .byte $08,$c2,$03,$10 + +; energy zone boss giant armored soldier (legs together) +sprite_b9: + .byte $12 + .byte $f9,$d8,$03,$f0 + .byte $f9,$da,$03,$f8 + .byte $f9,$ff,$03,$00 + .byte $09,$b6,$03,$f0 + .byte $09,$07,$03,$f8 + .byte $09,$09,$03,$00 + .byte $da,$dc,$02,$f0 + .byte $da,$de,$02,$f8 + .byte $da,$e0,$02,$00 + .byte $da,$e2,$02,$08 + .byte $e6,$fd,$03,$e8 + .byte $ea,$e4,$03,$e0 + .byte $ea,$e6,$02,$f0 + .byte $ea,$e8,$02,$f8 + .byte $ea,$ea,$02,$00 + .byte $e9,$a8,$03,$06 + .byte $e9,$aa,$03,$0e + .byte $f9,$b4,$03,$0b + +; energy zone boss giant armored soldier (running, jumping) +sprite_ba: + .byte $15 + .byte $f8,$ca,$03,$e8 + .byte $f8,$cc,$03,$f0 + .byte $f8,$ce,$03,$f8 + .byte $f8,$d0,$03,$00 + .byte $f7,$d4,$03,$08 + .byte $f7,$d6,$03,$10 + .byte $08,$c4,$03,$d8 + .byte $08,$c6,$03,$e0 + .byte $08,$c8,$03,$e8 + .byte $08,$d2,$03,$00 + .byte $80 + .addr giant_soldier_top + +; energy zone boss projectile (spiked disk) +sprite_bb: + .byte $02 + .byte $f8,$df,$00,$f8 + .byte $f8,$df,$40,$00 + +; energy zone boss projectile (spiked disk) +sprite_bc: + .byte $02 + .byte $f8,$fb,$00,$f8 + .byte $f8,$fb,$40,$00 + +; mounted soldier (basquez) +sprite_bd: + .byte $07 + .byte $f4,$ee,$03,$f0 + .byte $f4,$f2,$03,$f8 + .byte $04,$f0,$03,$f0 + .byte $04,$f4,$03,$f8 + .byte $04,$f8,$00,$00 + .byte $f4,$f6,$01,$00 + .byte $f4,$fa,$01,$08 + +; mounted soldier (basquez) +sprite_be: + .byte $07 + .byte $f4,$ee,$03,$f2 + .byte $f4,$f2,$03,$fa + .byte $04,$f0,$03,$f2 + .byte $04,$f4,$03,$fa + .byte $04,$f8,$00,$01 + .byte $f4,$f6,$01,$01 + .byte $f4,$fa,$01,$09 + +; energy zone wall fire +sprite_bf: + .byte $01 + .byte $ef,$33,$02,$ff + +; energy zone wall fire +sprite_c0: + .byte $01 + .byte $ef,$35,$02,$ff + +; energy zone ceiling fire +sprite_c1: + .byte $01 + .byte $ef,$37,$02,$fc + +; energy zone ceiling fire +sprite_c2: + .byte $01 + .byte $ef,$39,$02,$fc + +; energy zone boss giant armored soldier (throwing) +sprite_c3: + .byte $13 + .byte $d8,$9d,$02,$f0 + .byte $d8,$d7,$02,$f8 + .byte $d8,$d9,$02,$00 + .byte $e0,$3d,$03,$e0 + .byte $e0,$47,$03,$e8 + .byte $e8,$0b,$03,$d8 + .byte $e8,$9f,$02,$f0 + .byte $e8,$db,$03,$f8 + .byte $e8,$dd,$03,$00 + .byte $80 + .addr giant_soldier_bottom + +; snow field ground separator +sprite_c4: + .byte $01 + .byte $ff,$fd,$00,$fc + +; green helicopter ending scene (frame 1) +sprite_c5: + .byte $02 + .byte $f8,$a8,$00,$f8 + .byte $f8,$aa,$00,$00 + +; green helicopter ending scene (frame 2) +sprite_c6: + .byte $03 + .byte $f8,$ac,$00,$f8 + .byte $f8,$ae,$00,$00 + .byte $f8,$a6,$01,$07 + +; green helicopter ending scene (frame 3) +sprite_c7: + .byte $02 + .byte $f8,$b0,$00,$f8 + .byte $f8,$b2,$00,$00 + +; green helicopter ending scene (frame 4) +sprite_c8: + .byte $03 + .byte $f8,$b4,$00,$f8 + .byte $f8,$b6,$00,$00 + .byte $f8,$a6,$01,$05 + +; green helicopter facing forward (frame 1) +sprite_c9: + .byte $05 + .byte $f8,$b8,$00,$f3 + .byte $f8,$ba,$00,$fb + .byte $f8,$bc,$00,$03 + .byte $03,$a6,$01,$fb + .byte $03,$a6,$01,$05 + +; green helicopter facing forward (frame 2) +sprite_ca: + .byte $07 + .byte $f0,$be,$40,$08 + .byte $f0,$c0,$40,$00 + .byte $f0,$c2,$40,$f8 + .byte $f0,$c4,$40,$f0 + .byte $00,$c6,$40,$08 + .byte $00,$c8,$40,$00 + .byte $00,$ca,$40,$f8 + +; green helicopter facing forward (frame 3) +sprite_cb: + .byte $0a + .byte $f0,$cc,$00,$e8 + .byte $f0,$ce,$00,$f0 + .byte $f0,$d0,$00,$f8 + .byte $f0,$d2,$00,$00 + .byte $f0,$d4,$00,$08 + .byte $f0,$d6,$00,$10 + .byte $00,$d8,$00,$f8 + .byte $00,$da,$00,$00 + .byte $04,$a6,$01,$f8 + .byte $04,$a6,$01,$07 + +; green helicopter facing forward (frame 4) +sprite_cc: + .byte $09 + .byte $f0,$dc,$40,$14 + .byte $f0,$de,$40,$0c + .byte $f0,$e0,$40,$04 + .byte $f0,$e2,$40,$fc + .byte $f0,$e4,$40,$f4 + .byte $f0,$e6,$40,$ec + .byte $00,$e8,$40,$04 + .byte $00,$ea,$40,$fc + .byte $00,$ec,$40,$f4 + +; green helicopter facing forward (frame 5) +sprite_cd: + .byte $0b + .byte $f0,$ee,$40,$14 + .byte $f0,$f0,$40,$0c + .byte $f0,$f2,$40,$04 + .byte $f0,$e2,$40,$fc + .byte $f0,$e4,$40,$f4 + .byte $f0,$f4,$40,$ec + .byte $00,$e8,$40,$04 + .byte $00,$ea,$40,$fc + .byte $00,$ec,$40,$f4 + .byte $0a,$a6,$01,$08 + .byte $02,$a6,$01,$f7 + +; green helicopter facing forward (frame 6) +sprite_ce: + .byte $09 + .byte $f0,$f6,$40,$14 + .byte $f0,$de,$40,$0c + .byte $f0,$e0,$40,$04 + .byte $f0,$f8,$40,$fc + .byte $f0,$fa,$40,$f4 + .byte $f0,$fc,$40,$ec + .byte $00,$e8,$40,$04 + .byte $00,$ea,$40,$fc + .byte $00,$ec,$40,$f4 + +; ending sequence mountains +sprite_cf: + .byte $05 + .byte $00,$63,$03,$00 + .byte $00,$65,$03,$08 + .byte $00,$65,$03,$10 + .byte $00,$65,$03,$18 + .byte $00,$89,$03,$20 + +; unused space (#$21b bytes) +; these $ff bytes can technically be deleted here because the contra.cfg +; specifies that any free bytes in a ROM bank will be filled with $ff +; and each ROM bank is 16 KiB +bank_1_unused_space: + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff \ No newline at end of file diff --git a/src/bank2.asm b/src/bank2.asm new file mode 100644 index 0000000..a4ab56c --- /dev/null +++ b/src/bank2.asm @@ -0,0 +1,3092 @@ +; Contra US Disassembly - v1.0 +; https://github.com/vermiceli/nes-contra-us +; Bank 2 starts with RLE-encoded level data (graphic super tiles for the level +; screens). It then contains compressed tile data and alternate tile data and +; occasional attribute table data. Then, bank 2 contains logic for setting the +; players' sprite based on player state. Next, bank 2 contains the level +; headers, which define specifics about each level. Bank 2 then has the data +; that specifies which enemies are on which screen and their attributes. Bank +; 2 also contains the soldier enemy generation code. + +.segment "BANK_2" + +.include "constants.asm" + +; import labels from bank 7 +.import find_next_enemy_slot_6_to_0, get_bg_collision_far, remove_all_enemies +.import initialize_enemy, find_bullet_slot, find_next_enemy_slot, play_sound +.import run_routine_from_tbl_below, get_bg_collision + +; import labels from bank 3 +.import level_1_supertile_data, level_2_supertile_data +.import level_3_supertile_data, level_4_supertile_data +.import level_5_supertile_data, level_6_supertile_data +.import level_7_supertile_data, level_8_supertile_data + +.import level_1_palette_data, level_2_palette_data +.import level_3_palette_data, level_4_palette_data +.import level_5_palette_data, level_6_palette_data +.import level_7_palette_data, level_8_palette_data + +; export labels for bank 7 +.export level_headers, graphic_data_02 +.export alt_graphic_data_00, alt_graphic_data_01 +.export alt_graphic_data_02, alt_graphic_data_03 +.export alt_graphic_data_04 +.export load_screen_enemy_data +.export exe_soldier_generation +.export set_players_paused_sprite_attr +.export set_player_sprite_and_attrs +.export level_2_4_boss_supertiles_screen_ptr_table + +; Every PRG ROM bank starts with a single byte specifying which number it is +.byte $02 ; The PRG ROM bank number (2) + +; pointer table for level 1 super tile data (#$e * #$2 = #$1c bytes) +; each entry is a screen (256 pixels wide) worth of super-tile indexes +; CPU address $8001 +level_1_supertiles_screen_ptr_table: + .addr level_1_supertiles_screen_00 ; CPU address $801d + .addr level_1_supertiles_screen_01 ; CPU address $8048 + .addr level_1_supertiles_screen_02 ; CPU address $8079 + .addr level_1_supertiles_screen_03 ; CPU address $80a7 + .addr level_1_supertiles_screen_04 ; CPU address $80cf + .addr level_1_supertiles_screen_05 ; CPU address $80fc + .addr level_1_supertiles_screen_06 ; CPU address $8118 + .addr level_1_supertiles_screen_07 ; CPU address $8132 + .addr level_1_supertiles_screen_08 ; CPU address $8149 + .addr level_1_supertiles_screen_09 ; CPU address $8169 + .addr level_1_supertiles_screen_0a ; CPU address $8191 + .addr level_1_supertiles_screen_0b ; CPU address $81b0 + .addr level_1_supertiles_screen_0c ; CPU address $81d6 + .addr level_1_supertiles_screen_00 ; CPU address $801d + +; beginning of level 1 data table +; each label is a screen of the level +; each byte is a super-tile block number (a block of 4x4 tiles) +; bit 7 set means the next byte is repeated by the amount of bits 0-6 of current byte +; each label expands to #$38 bytes for horizontal levels +; each label expands to #$40 bytes for vertical levels +; 8x = make x consecutive units of the following byte +; ef = make 6f consecutive units (max) +; fx = ? +level_1_supertiles_screen_00: + .byte $21,$20,$21,$2b,$23,$22,$23,$2a,$20,$54,$54,$2f,$27,$26,$27,$2e + .byte $54,$1c,$1d,$1e,$1c,$1e,$1c,$1c,$54,$87,$00,$0c,$84,$51,$83,$00 + .byte $0d,$83,$09,$0a,$83,$51,$84,$10,$11,$83,$12 + +level_1_supertiles_screen_01: + .byte $21,$20,$2b,$23,$22,$23,$22,$23,$54,$54,$2f,$27,$26,$27,$26,$27 + .byte $83,$1d,$1e,$1c,$1d,$1d,$1e,$88,$00,$51,$51,$3b,$51,$51,$00,$00 + .byte $51,$00,$51,$51,$00,$0b,$51,$51,$08,$17,$39,$39,$16,$14,$12,$12 + .byte $13 + +level_1_supertiles_screen_02: + .byte $22,$23,$22,$23,$2a,$54,$21,$54,$26,$27,$26,$27,$2e,$21,$20,$54 + .byte $1e,$1c,$1d,$1d,$1c,$1d,$1d,$1e,$88,$00,$84,$51,$83,$04,$51,$09 + .byte $0a,$51,$51,$83,$07,$08,$10,$11,$17,$39,$39,$16,$12,$13 + +level_1_supertiles_screen_03: + .byte $21,$20,$54,$21,$20,$20,$54,$21,$54,$54,$21,$20,$21,$21,$83,$54 + .byte $20,$54,$54,$1c,$83,$1d,$03,$29,$29,$37,$84,$00,$84,$0c,$84,$51 + .byte $0e,$0c,$0c,$0d,$84,$09,$88,$10 + +level_1_supertiles_screen_04: + .byte $83,$54,$20,$21,$54,$20,$2b,$20,$20,$54,$21,$83,$54,$2f,$1e,$84 + .byte $54,$1c,$1d,$1d,$00,$03,$29,$29,$37,$83,$00,$51,$84,$0c,$51,$51 + .byte $3e,$09,$0e,$0c,$0c,$0d,$09,$0a,$51,$86,$10,$11,$12 + +level_1_supertiles_screen_05: + .byte $23,$2a,$20,$2d,$84,$2c,$27,$2e,$21,$85,$52,$1d,$1d,$1e,$8a,$00 + .byte $8a,$51,$04,$87,$51,$07,$83,$12,$17,$83,$39,$16 + +level_1_supertiles_screen_06: + .byte $88,$2c,$88,$52,$88,$00,$83,$51,$3e,$84,$51,$04,$3b,$86,$00,$07 + .byte $0b,$86,$51,$12,$14,$83,$12,$17,$39,$18 + +level_1_supertiles_screen_07: + .byte $88,$2c,$83,$52,$85,$28,$83,$00,$85,$52,$51,$3e,$87,$00,$8b,$51 + .byte $00,$00,$51,$85,$00,$84,$51 + +level_1_supertiles_screen_08: + .byte $88,$2c,$85,$52,$83,$28,$85,$00,$28,$52,$52,$00,$84,$51,$52,$00 + .byte $00,$51,$51,$04,$51,$83,$00,$51,$00,$51,$07,$84,$51,$3b,$88,$51 + +level_1_supertiles_screen_09: + .byte $88,$2c,$86,$28,$52,$52,$85,$28,$52,$00,$00,$28,$52,$52,$28,$28 + .byte $00,$00,$51,$28,$00,$00,$52,$52,$51,$51,$04,$28,$51,$83,$00,$51 + .byte $51,$07,$28,$00,$84,$51,$00,$51 + +level_1_supertiles_screen_0a: + .byte $2c,$87,$24,$28,$38,$86,$2c,$28,$52,$52,$86,$28,$00,$00,$84,$52 + .byte $28,$28,$51,$85,$00,$28,$28,$86,$51,$52,$28,$84,$51,$83,$00 + +level_1_supertiles_screen_0b: + .byte $88,$24,$88,$2c,$86,$28,$52,$52,$84,$28,$52,$52,$00,$00,$28,$52 + .byte $52,$28,$00,$00,$51,$04,$28,$00,$00,$28,$51,$3e,$51,$07,$28,$51 + .byte $51,$28,$51,$51,$00,$00 + +level_1_supertiles_screen_0c: + .byte $83,$24,$25,$84,$54,$84,$2c,$19,$1a,$02,$1b,$52,$52,$28,$28,$01 + .byte $05,$06,$32,$00,$00,$52,$28,$61,$64,$06,$32,$04,$04,$00,$52,$30 + .byte $31,$33,$36,$07,$3e,$51,$00,$34,$35,$06,$32,$85,$00,$15,$3a,$32 + +; pointer table for level 2 (#$18 * #$2 = #$30 bytes) +level_2_supertiles_screen_ptr_table: + .addr level_2_supertiles_screen_00 ; CPU address $8276 (0) Normal Room + .addr level_2_supertiles_screen_01 ; CPU address $8300 (1) + .addr level_2_supertiles_screen_02 ; CPU address $832e (2) + .addr level_2_supertiles_screen_03 ; CPU address $835c (3) + + .addr level_2_supertiles_screen_00 ; CPU address $8276 (0) Normal Room + .addr level_2_supertiles_screen_01 ; CPU address $8300 (1) + .addr level_2_supertiles_screen_02 ; CPU address $832e (2) + .addr level_2_supertiles_screen_03 ; CPU address $835c (3) + + .addr level_2_supertiles_screen_00 ; CPU address $8276 (0) Normal Room + .addr level_2_supertiles_screen_01 ; CPU address $8300 (1) + .addr level_2_supertiles_screen_02 ; CPU address $832e (2) + .addr level_2_supertiles_screen_03 ; CPU address $835c (3) + + .addr level_2_supertiles_screen_04 ; CPU address $82d2 (4) Room with Holes for Roller Rows + .addr level_2_supertiles_screen_01 ; CPU address $8300 (1) + .addr level_2_supertiles_screen_02 ; CPU address $832e (2) + .addr level_2_supertiles_screen_03 ; CPU address $835c (3) + + .addr level_2_supertiles_screen_05 ; CPU address $82a4 (5) Room with Big Core + .addr level_2_supertiles_screen_01 ; CPU address $8300 (1) + .addr level_2_supertiles_screen_02 ; CPU address $832e (2) + .addr level_2_supertiles_screen_03 ; CPU address $835c (3) + + .addr level_2_supertiles_screen_00 ; CPU address $8276 (0) First Room again + .addr level_2_supertiles_screen_01 ; CPU address $8300 (1) + .addr level_2_supertiles_screen_02 ; CPU address $832e (2) + .addr level_2_supertiles_screen_03 ; CPU address $835c (3) + +; pointer table for level 4 (#$20 * #$2 = #$40 bytes) +level_4_supertiles_screen_ptr_table: + .addr level_4_supertiles_screen_00 ; CPU address $838a (0) + .addr level_4_supertiles_screen_01 ; CPU address $842c (1) + .addr level_4_supertiles_screen_02 ; CPU address $8462 (2) + .addr level_4_supertiles_screen_03 ; CPU address $8498 (3) + + .addr level_4_supertiles_screen_00 ; CPU address $838a (0) + .addr level_4_supertiles_screen_01 ; CPU address $842c (1) + .addr level_4_supertiles_screen_02 ; CPU address $8462 (2) + .addr level_4_supertiles_screen_03 ; CPU address $8498 (3) + + .addr level_4_supertiles_screen_00 ; CPU address $838a (0) + .addr level_4_supertiles_screen_01 ; CPU address $842c (1) + .addr level_4_supertiles_screen_02 ; CPU address $8462 (2) + .addr level_4_supertiles_screen_03 ; CPU address $8498 (3) + + .addr level_4_supertiles_screen_00 ; CPU address $838a (0) + .addr level_4_supertiles_screen_01 ; CPU address $842c (1) + .addr level_4_supertiles_screen_02 ; CPU address $8462 (2) + .addr level_4_supertiles_screen_03 ; CPU address $8498 (3) + + .addr level_4_supertiles_screen_00 ; CPU address $838a (0) + .addr level_4_supertiles_screen_01 ; CPU address $842c (1) + .addr level_4_supertiles_screen_02 ; CPU address $8462 (2) + .addr level_4_supertiles_screen_03 ; CPU address $8498 (3) + + .addr level_4_supertiles_screen_04 ; CPU address $83f6 (4) + .addr level_4_supertiles_screen_01 ; CPU address $842c (1) + .addr level_4_supertiles_screen_02 ; CPU address $8462 (2) + .addr level_4_supertiles_screen_03 ; CPU address $8498 (3) + + .addr level_4_supertiles_screen_00 ; CPU address $838a (0) + .addr level_4_supertiles_screen_01 ; CPU address $842c (1) + .addr level_4_supertiles_screen_02 ; CPU address $8462 (2) + .addr level_4_supertiles_screen_03 ; CPU address $8498 (3) + + .addr level_4_supertiles_screen_05 ; CPU address $83c0 (5) + .addr level_4_supertiles_screen_01 ; CPU address $842c (1) + .addr level_4_supertiles_screen_02 ; CPU address $8462 (2) + .addr level_4_supertiles_screen_03 ; CPU address $8498 (3) + +level_2_supertiles_screen_00: + .byte $62,$86,$61,$63,$60,$04,$05,$06,$07,$01,$02,$5e,$60,$08,$09,$69 + .byte $0c,$0b,$03,$5e,$60,$10,$0d,$6a,$6f,$0f,$10,$5e,$60,$15,$16,$14 + .byte $14,$12,$13,$5e,$60,$18,$84,$11,$17,$5e,$5d,$86,$5f,$5d + +level_2_supertiles_screen_05: + .byte $62,$86,$61,$63,$60,$04,$05,$06,$07,$01,$02,$5e,$60,$08,$09,$6b + .byte $6e,$0b,$03,$5e,$60,$10,$0d,$6c,$6d,$0f,$10,$5e,$60,$15,$16,$14 + .byte $14,$12,$13,$5e,$60,$18,$84,$11,$17,$5e,$5d,$86,$5f,$5d + +level_2_supertiles_screen_04: + .byte $62,$86,$61,$63,$60,$04,$05,$06,$07,$01,$02,$5e,$60,$08,$09,$69 + .byte $0c,$0b,$03,$5e,$60,$10,$0d,$70,$71,$0f,$10,$5e,$60,$15,$16,$14 + .byte $14,$12,$13,$5e,$60,$18,$84,$11,$17,$5e,$5d,$86,$5f,$5d + +level_2_supertiles_screen_01: + .byte $62,$86,$61,$63,$60,$5c,$1d,$1e,$1f,$20,$5b,$5e,$60,$08,$21,$22 + .byte $22,$24,$03,$5e,$60,$10,$25,$26,$26,$27,$10,$5e,$60,$28,$29,$2a + .byte $2a,$2b,$2c,$5e,$60,$2d,$84,$2e,$23,$5e,$5d,$86,$5f,$5d + +level_2_supertiles_screen_02: + .byte $62,$86,$61,$63,$60,$2f,$30,$31,$32,$33,$34,$5e,$60,$35,$36,$37 + .byte $38,$39,$3a,$5e,$60,$3b,$3c,$3d,$3e,$3f,$40,$5e,$60,$41,$42,$43 + .byte $43,$44,$45,$5e,$60,$46,$84,$11,$47,$5e,$5d,$86,$5f,$5d + +level_2_supertiles_screen_03: + .byte $62,$86,$61,$63,$60,$48,$58,$49,$4a,$59,$5a,$5e,$60,$4b,$4c,$4d + .byte $4e,$4f,$50,$5e,$60,$4b,$4c,$51,$52,$4f,$50,$5e,$60,$53,$54,$55 + .byte $55,$56,$57,$5e,$60,$46,$84,$11,$47,$5e,$5d,$86,$5f,$5d + +level_4_supertiles_screen_00: + .byte $64,$65,$66,$65,$66,$65,$66,$64,$67,$04,$05,$06,$07,$01,$02,$74 + .byte $68,$08,$09,$69,$0c,$0b,$03,$75,$67,$10,$0d,$6a,$6f,$0f,$10,$74 + .byte $68,$15,$16,$14,$14,$12,$13,$75,$67,$18,$84,$11,$17,$74,$64,$72 + .byte $73,$72,$73,$72,$73,$64 + +level_4_supertiles_screen_05: + .byte $64,$65,$66,$65,$66,$65,$66,$64,$67,$04,$05,$06,$07,$01,$02,$74 + .byte $68,$08,$09,$6b,$6e,$0b,$03,$75,$67,$10,$0d,$6c,$6d,$0f,$10,$74 + .byte $68,$15,$16,$14,$14,$12,$13,$75,$67,$18,$84,$11,$17,$74,$64,$72 + .byte $73,$72,$73,$72,$73,$64 + +level_4_supertiles_screen_04: + .byte $64,$65,$66,$65,$66,$65,$66,$64,$67,$04,$05,$06,$07,$01,$02,$74 + .byte $68,$08,$09,$69,$0c,$0b,$03,$75,$67,$10,$0d,$70,$71,$0f,$10,$74 + .byte $68,$15,$16,$14,$14,$12,$13,$75,$67,$18,$84,$11,$17,$74,$64,$72 + .byte $73,$72,$73,$72,$73,$64 + +level_4_supertiles_screen_01: + .byte $64,$65,$66,$65,$66,$65,$66,$64,$67,$5c,$1d,$1e,$1f,$20,$5b,$74 + .byte $68,$08,$21,$22,$22,$24,$03,$75,$67,$10,$25,$26,$26,$27,$10,$74 + .byte $68,$28,$29,$2a,$2a,$2b,$2c,$75,$67,$2d,$84,$2e,$23,$74,$64,$72 + .byte $73,$72,$73,$72,$73,$64 + +level_4_supertiles_screen_02: + .byte $64,$65,$66,$65,$66,$65,$66,$64,$67,$2f,$30,$31,$32,$33,$34,$74 + .byte $68,$35,$36,$37,$38,$39,$3a,$75,$67,$3b,$3c,$3d,$3e,$3f,$40,$74 + .byte $68,$41,$42,$43,$43,$44,$45,$75,$67,$46,$84,$11,$47,$74,$64,$72 + .byte $73,$72,$73,$72,$73,$64 + +level_4_supertiles_screen_03: + .byte $64,$65,$66,$65,$66,$65,$66,$64,$67,$48,$58,$49,$4a,$59,$5a,$74 + .byte $68,$4b,$4c,$4d,$4e,$4f,$50,$75,$67,$4b,$4c,$51,$52,$4f,$50,$74 + .byte $68,$53,$54,$55,$55,$56,$57,$75,$67,$46,$84,$11,$47,$74,$64,$72 + .byte $73,$72,$73,$72,$73,$64 + +; pointer table for level 3 (#$a * #$2 = #$14 bytes) +level_3_supertiles_screen_ptr_table: + .addr level_3_supertiles_screen_00 ; CPU address $8667 + .addr level_3_supertiles_screen_01 ; CPU address $8635 + .addr level_3_supertiles_screen_02 ; CPU address $860d + .addr level_3_supertiles_screen_03 ; CPU address $85d2 + .addr level_3_supertiles_screen_04 ; CPU address $8599 + .addr level_3_supertiles_screen_05 ; CPU address $855c + .addr level_3_supertiles_screen_06 ; CPU address $8531 + .addr level_3_supertiles_screen_07 ; CPU address $8515 + .addr level_3_supertiles_screen_08 ; CPU address $84e4 + .addr level_3_supertiles_screen_09 ; CPU address $84e2 + +; level data blocks are stored in reverse order +; first pointer for top screen +level_3_supertiles_screen_09: + .byte $c0,$60 + +level_3_supertiles_screen_08: + .byte $60,$60,$35,$36,$37,$38,$84,$60,$39,$3a,$3b,$3c,$83,$60,$3d,$3e + .byte $70,$71,$41,$42,$60,$43,$44,$45,$46,$22,$10,$02,$03,$40,$40,$30 + .byte $31,$32,$33,$40,$40,$3f,$3f,$30,$34,$6f,$33,$3f,$3f,$88,$04,$88 + .byte $06 + +level_3_supertiles_screen_07: + .byte $88,$04,$84,$06,$84,$05,$06,$05,$05,$8a,$06,$83,$05,$84,$04,$87 + .byte $06,$83,$04,$06,$06,$83,$04,$06,$84,$05,$88,$06 + +level_3_supertiles_screen_06: + .byte $87,$04,$83,$06,$1e,$20,$1f,$83,$04,$1e,$20,$84,$60,$23,$04,$87 + .byte $60,$1f,$83,$1d,$24,$84,$1b,$5d,$4a,$18,$2a,$84,$5e,$47,$83,$00 + .byte $25,$83,$12,$5d,$5b,$18,$0b,$0e,$5b,$01,$5b + +level_3_supertiles_screen_05: + .byte $4a,$0b,$0c,$5e,$5e,$0d,$00,$5d,$18,$0e,$83,$00,$27,$00,$00,$2e + .byte $19,$01,$21,$0b,$5e,$0d,$4a,$28,$16,$01,$21,$11,$12,$12,$15,$5e + .byte $26,$83,$5d,$47,$18,$2a,$5e,$28,$16,$5d,$00,$00,$2f,$5e,$00,$27 + .byte $83,$00,$18,$2a,$5e,$5b,$0f,$0d,$5b,$5b,$00,$00,$0f + +level_3_supertiles_screen_04: + .byte $5d,$0f,$28,$16,$01,$01,$5d,$0f,$4a,$0f,$5e,$2d,$16,$5d,$18,$2a + .byte $18,$2a,$5e,$5e,$26,$00,$2f,$5e,$2e,$84,$5e,$0c,$5e,$5e,$83,$00 + .byte $0f,$84,$5e,$16,$83,$00,$27,$83,$00,$26,$5d,$18,$0c,$28,$16,$01 + .byte $21,$00,$00,$2f,$5c,$5c,$2d,$16,$5b + +level_3_supertiles_screen_03: + .byte $4a,$01,$11,$1a,$5e,$5e,$0c,$0c,$01,$01,$18,$17,$83,$00,$0f,$16 + .byte $18,$2e,$28,$16,$83,$00,$2d,$2e,$5e,$5e,$2d,$16,$84,$00,$07,$08 + .byte $08,$09,$00,$00,$01,$21,$0f,$5e,$5e,$28,$16,$4a,$5d,$21,$11,$12 + .byte $1a,$5e,$2d,$16,$83,$5b,$18,$0f,$5c,$5c,$2d + +level_3_supertiles_screen_02: + .byte $84,$5d,$84,$00,$16,$00,$00,$18,$84,$0c,$2d,$16,$8a,$00,$18,$16 + .byte $18,$16,$01,$21,$0b,$0c,$2e,$2d,$2e,$2d,$5d,$5d,$14,$85,$13,$88 + .byte $00,$5b,$5b,$01,$01,$5b,$83,$00 + +level_3_supertiles_screen_01: + .byte $84,$00,$0a,$83,$5d,$16,$00,$00,$5d,$47,$18,$16,$18,$26,$00,$0a + .byte $5d,$84,$00,$19,$01,$83,$5d,$0a,$01,$21,$5d,$85,$01,$0a,$18,$84 + .byte $01,$47,$5d,$5d,$2f,$16,$0a,$83,$01,$5d,$18,$2a,$26,$16,$84,$5d + .byte $00,$00 + +level_3_supertiles_screen_00: + .byte $00,$00,$5d,$5d,$83,$01,$21,$5d,$00,$00,$5d,$5d,$18,$16,$5d,$84 + .byte $00,$5d,$83,$00,$16,$5d,$5d,$84,$00,$5d,$26,$83,$5d,$83,$00,$18 + .byte $28,$16,$83,$5d,$8b,$00,$88,$5d + +; pointer table for level 5 (#$16 * #$2 = #$2c bytes) +level_5_supertiles_screen_ptr_table: + .addr level_5_supertiles_screen_00 ; CPU address $86bb + .addr level_5_supertiles_screen_01 ; CPU address $86e1 + .addr level_5_supertiles_screen_02 ; CPU address $8700 + .addr level_5_supertiles_screen_03 ; CPU address $8733 + .addr level_5_supertiles_screen_04 ; CPU address $8763 + .addr level_5_supertiles_screen_05 ; CPU address $8792 + .addr level_5_supertiles_screen_06 ; CPU address $87c6 + .addr level_5_supertiles_screen_07 ; CPU address $87fa + .addr level_5_supertiles_screen_08 ; CPU address $882a + .addr level_5_supertiles_screen_09 ; CPU address $8851 + .addr level_5_supertiles_screen_0a ; CPU address $8855 + .addr level_5_supertiles_screen_09 ; CPU address $8851 + .addr level_5_supertiles_screen_0b ; CPU address $8863 + .addr level_5_supertiles_screen_09 ; CPU address $8851 + .addr level_5_supertiles_screen_0a ; CPU address $8855 + .addr level_5_supertiles_screen_09 ; CPU address $8851 + .addr level_5_supertiles_screen_0c ; CPU address $8890 + .addr level_5_supertiles_screen_0d ; CPU address $88c4 + .addr level_5_supertiles_screen_0e ; CPU address $88eb + .addr level_5_supertiles_screen_09 ; CPU address $8851 + .addr level_5_supertiles_screen_0f ; CPU address $890e + .addr level_5_supertiles_screen_00 ; CPU address $86bb + +level_5_supertiles_screen_00: + .byte $00,$00,$3c,$00,$3b,$3c,$83,$00,$3c,$00,$83,$3c,$3b,$00,$00,$3c + .byte $83,$3b,$3c,$85,$00,$3c,$00,$00,$3c,$00,$01,$02,$01,$02,$01,$02 + .byte $01,$02,$88,$23,$88,$38 + +level_5_supertiles_screen_01: + .byte $00,$3c,$3b,$3c,$00,$3b,$00,$00,$05,$86,$18,$44,$06,$86,$19,$45 + .byte $87,$07,$43,$01,$02,$01,$02,$01,$02,$01,$02,$88,$23,$88,$38 + +level_5_supertiles_screen_02: + .byte $00,$00,$3b,$00,$00,$3c,$3b,$84,$00,$3c,$3b,$3b,$3c,$00,$00,$3c + .byte $3b,$83,$00,$3c,$83,$00,$28,$01,$01,$0f,$00,$00,$01,$3e,$2a,$51 + .byte $03,$14,$00,$22,$23,$24,$0c,$04,$04,$13,$30,$46,$38,$42,$29,$83 + .byte $16,$11,$08 + +level_5_supertiles_screen_03: + .byte $00,$3b,$3c,$3c,$00,$3b,$3c,$83,$00,$83,$3c,$84,$00,$3b,$00,$3b + .byte $00,$00,$2f,$09,$86,$00,$29,$16,$01,$01,$2b,$00,$00,$28,$01,$01 + .byte $23,$2c,$2e,$09,$09,$0c,$25,$23,$38,$50,$29,$16,$16,$11,$41,$38 + +level_5_supertiles_screen_04: + .byte $00,$3b,$3c,$3b,$3c,$00,$05,$44,$00,$3c,$00,$3b,$00,$00,$06,$45 + .byte $09,$30,$00,$00,$17,$09,$0e,$47,$16,$0a,$00,$00,$12,$16,$0d,$0d + .byte $02,$01,$02,$01,$02,$3e,$25,$2c,$85,$23,$2c,$00,$00,$88,$38 + +level_5_supertiles_screen_05: + .byte $3c,$00,$3b,$05,$18,$44,$00,$00,$3b,$3c,$00,$06,$19,$45,$00,$00 + .byte $30,$00,$2f,$0e,$0e,$47,$30,$00,$0a,$00,$29,$83,$16,$0a,$00,$22 + .byte $02,$01,$02,$01,$02,$3e,$00,$25,$83,$23,$24,$2a,$3d,$00,$84,$38 + .byte $42,$25,$24,$22 + +level_5_supertiles_screen_06: + .byte $00,$3b,$3c,$00,$00,$3c,$3b,$00,$00,$3c,$00,$00,$3b,$3b,$3c,$00 + .byte $00,$2f,$09,$09,$30,$84,$00,$2a,$51,$0d,$33,$2f,$30,$22,$00,$25 + .byte $23,$23,$0a,$29,$11,$46,$00,$22,$02,$01,$02,$01,$3e,$46,$3e,$46 + .byte $84,$03,$3d,$46 + +level_5_supertiles_screen_07: + .byte $00,$00,$3b,$3c,$00,$3b,$00,$3c,$00,$3b,$3c,$3c,$00,$3c,$3b,$3c + .byte $00,$3c,$86,$00,$3e,$86,$00,$22,$3d,$00,$22,$3e,$00,$40,$00,$46 + .byte $3d,$36,$46,$3d,$36,$46,$36,$46,$3d,$3f,$37,$37,$3a,$37,$3f,$37 + +level_5_supertiles_screen_08: + .byte $85,$00,$3c,$83,$00,$3c,$00,$00,$3c,$87,$00,$3c,$83,$00,$3e,$87 + .byte $00,$3d,$00,$22,$02,$01,$02,$3e,$00,$3d,$36,$46,$83,$03,$3d,$1a + .byte $37,$3a,$46,$83,$34,$3d,$1a + +level_5_supertiles_screen_09: + .byte $a8,$00,$90,$1a + +level_5_supertiles_screen_0a: + .byte $98,$00,$60,$1c,$1d,$85,$00,$1e,$61,$62,$85,$00,$90,$1a + +level_5_supertiles_screen_0b: + .byte $84,$00,$3c,$00,$3b,$83,$00,$3b,$00,$3c,$00,$3c,$00,$00,$3b,$3c + .byte $00,$3b,$85,$00,$2f,$09,$30,$85,$00,$29,$16,$11,$00,$22,$3e,$22 + .byte $02,$01,$02,$01,$3e,$46,$3d,$25,$84,$23,$2c,$46,$3d + +level_5_supertiles_screen_0c: + .byte $3c,$00,$3c,$3b,$00,$3b,$3c,$00,$00,$3b,$05,$83,$18,$44,$3c,$00 + .byte $3c,$06,$83,$19,$45,$00,$3b,$00,$84,$07,$43,$00,$00,$28,$02,$01 + .byte $02,$01,$2b,$00,$1a,$0c,$0b,$21,$23,$23,$2c,$2f,$1a,$46,$26,$10 + .byte $38,$38,$50,$27 + +level_5_supertiles_screen_0d: + .byte $83,$00,$3b,$3c,$00,$3c,$00,$3b,$00,$3b,$3c,$89,$00,$3b,$00,$00 + .byte $28,$01,$02,$01,$02,$01,$2b,$00,$2a,$03,$51,$83,$03,$2d,$22,$0c + .byte $85,$04,$32,$46,$87,$11,$08 + +level_5_supertiles_screen_0e: + .byte $00,$3b,$3c,$84,$00,$3c,$83,$00,$3b,$00,$3b,$84,$00,$3b,$00,$3c + .byte $3c,$8a,$00,$02,$01,$02,$01,$02,$01,$3e,$00,$86,$23,$2c,$1a,$86 + .byte $38,$50,$1a + +level_5_supertiles_screen_0f: + .byte $95,$00,$4d,$4e,$4f,$85,$00,$4a,$4b + .byte $4c,$85,$00,$31,$48,$49,$84,$1a,$15,$1b,$1f,$20,$88,$1a + +; pointer table for level 6 (#$e * #$2 = #$1c bytes) +level_6_supertiles_screen_ptr_table: + .addr level_6_supertiles_screen_00 ; CPU address $8941 + .addr level_6_supertiles_screen_01 ; CPU address $8973 + .addr level_6_supertiles_screen_02 ; CPU address $89a3 + .addr level_6_supertiles_screen_03 ; CPU address $89d5 + .addr level_6_supertiles_screen_04 ; CPU address $8a07 + .addr level_6_supertiles_screen_05 ; CPU address $8a3d + .addr level_6_supertiles_screen_06 ; CPU address $8a71 + .addr level_6_supertiles_screen_07 ; CPU address $8aa7 + .addr level_6_supertiles_screen_08 ; CPU address $8ada + .addr level_6_supertiles_screen_09 ; CPU address $8b0e + .addr level_6_supertiles_screen_0a ; CPU address $8b40 + .addr level_6_supertiles_screen_0b ; CPU address $8b64 + .addr level_6_supertiles_screen_0c ; CPU address $8b6a + .addr level_6_supertiles_screen_00 ; CPU address $8941 + +level_6_supertiles_screen_00: + .byte $1d,$30,$31,$1d,$30,$31,$1d,$30,$3f,$31,$49,$3f,$31,$49,$3f,$31 + .byte $60,$32,$4d,$40,$32,$4d,$40,$32,$57,$3e,$3f,$57,$3e,$3f,$60,$3e + .byte $57,$60,$60,$00,$60,$00,$57,$00,$00,$57,$00,$60,$00,$57,$00,$00 + .byte $88,$01 + +level_6_supertiles_screen_01: + .byte $31,$1d,$30,$31,$1d,$30,$31,$1d,$49,$3f,$4d,$40,$3f,$4d,$40,$3f + .byte $4d,$40,$3f,$57,$00,$3d,$60,$57,$30,$28,$02,$85,$03,$32,$02,$06 + .byte $4c,$61,$69,$63,$4c,$02,$06,$4c,$4c,$65,$62,$64,$4c,$14,$87,$22 + +level_6_supertiles_screen_02: + .byte $30,$31,$1d,$3f,$40,$1d,$3f,$4d,$31,$40,$3f,$60,$40,$3f,$57,$3e + .byte $00,$57,$00,$00,$57,$00,$00,$60,$0b,$08,$0c,$51,$83,$58,$59,$05 + .byte $57,$15,$0e,$57,$12,$10,$53,$05,$00,$60,$00,$00,$09,$17,$13,$24 + .byte $87,$01 + +level_6_supertiles_screen_03: + .byte $1d,$3f,$40,$1d,$4d,$3f,$4a,$4a,$3f,$60,$57,$3e,$3f,$57,$3e,$3f + .byte $00,$60,$60,$57,$00,$60,$00,$57,$38,$57,$08,$0c,$51,$58,$58,$59 + .byte $54,$33,$12,$53,$54,$10,$33,$15,$17,$23,$09,$17,$13,$17,$23,$28 + .byte $88,$01 + +level_6_supertiles_screen_04: + .byte $4a,$4b,$4a,$29,$4a,$4b,$4a,$29,$32,$00,$57,$3e,$3f,$00,$3e,$3f + .byte $57,$00,$60,$60,$57,$00,$57,$60,$38,$00,$0f,$03,$04,$34,$34,$03 + .byte $0e,$69,$00,$11,$52,$12,$10,$18,$28,$3f,$60,$00,$57,$09,$17,$17 + .byte $01,$68,$00,$50,$84,$01 + +level_6_supertiles_screen_05: + .byte $4b,$4a,$43,$66,$4a,$29,$4a,$43,$00,$3d,$67,$2d,$0a,$0a,$3c,$1b + .byte $00,$60,$1b,$4e,$00,$20,$66,$1b,$47,$57,$67,$2d,$1c,$1a,$4e,$2f + .byte $46,$60,$2f,$2e,$0a,$1f,$2d,$00,$23,$60,$83,$00,$20,$4e,$00,$85 + .byte $01,$1e,$42,$01 + +level_6_supertiles_screen_06: + .byte $66,$29,$4a,$29,$4b,$29,$4a,$29,$4e,$3e,$3f,$3d,$00,$21,$0a,$0a + .byte $2d,$1c,$0d,$4f,$83,$00,$1b,$2e,$0a,$1f,$4e,$00,$00,$2b,$5d,$00 + .byte $00,$20,$2d,$69,$00,$21,$1f,$00,$02,$2c,$4e,$41,$00,$00,$5e,$01 + .byte $14,$25,$42,$83,$01,$1e + +level_6_supertiles_screen_07: + .byte $29,$4a,$43,$66,$2a,$29,$4a,$29,$3c,$00,$20,$4e,$39,$3e,$3d,$3e + .byte $66,$1c,$1a,$2d,$44,$83,$00,$4e,$0a,$1f,$2e,$39,$00,$0f,$03,$2d + .byte $83,$00,$39,$12,$10,$18,$4e,$03,$0b,$00,$39,$09,$17,$17,$42,$25 + .byte $24,$85,$01 + +level_6_supertiles_screen_08: + .byte $4a,$2a,$4b,$29,$2a,$29,$4b,$29,$3f,$36,$00,$3e,$36,$3d,$00,$3d + .byte $00,$39,$00,$00,$39,$83,$00,$04,$84,$34,$03,$47,$08,$46,$45,$00 + .byte $00,$39,$11,$52,$12,$23,$39,$00,$00,$44,$00,$00,$09,$01,$01,$68 + .byte $00,$50,$83,$01 + +level_6_supertiles_screen_09: + .byte $4a,$29,$2a,$29,$4a,$29,$2a,$29,$3e,$3f,$39,$3e,$3f,$00,$39,$3e + .byte $00,$00,$44,$83,$00,$44,$00,$0c,$51,$59,$38,$00,$0f,$03,$34,$53 + .byte $54,$37,$54,$10,$10,$18,$10,$17,$13,$48,$83,$35,$3b,$17,$86,$01 + .byte $16,$00 + +level_6_supertiles_screen_0a: + .byte $4a,$29,$4b,$29,$4a,$83,$00,$3f,$3d,$00,$3e,$3f,$8b,$00,$34,$03 + .byte $47,$85,$00,$33,$11,$52,$85,$00,$23,$00,$00,$02,$84,$03,$00,$50 + .byte $01,$14,$84,$22 + +level_6_supertiles_screen_0b: + .byte $a8,$00,$88,$03,$88,$22 + +level_6_supertiles_screen_0c: + .byte $8e,$00,$19,$27,$86,$00,$3a,$55,$86,$00,$56,$5b,$86,$00,$56,$5a + .byte $86,$03,$5f,$5a,$87,$22,$5c + +; pointer table for level 7 (10 * 2 = 20 bytes) +level_7_supertiles_screen_ptr_table: + .addr level_7_supertiles_screen_00 ; CPU address $8ba1 + .addr level_7_supertiles_screen_01 ; CPU address $8bd2 + .addr level_7_supertiles_screen_02 ; CPU address $8bf8 + .addr level_7_supertiles_screen_03 ; CPU address $8c27 + .addr level_7_supertiles_screen_04 ; CPU address $8c58 + .addr level_7_supertiles_screen_05 ; CPU address $8c8a + .addr level_7_supertiles_screen_06 ; CPU address $8ca7 + .addr level_7_supertiles_screen_07 ; CPU address $8cd7 + .addr level_7_supertiles_screen_08 ; CPU address $8d03 + .addr level_7_supertiles_screen_09 ; CPU address $8d2a + .addr level_7_supertiles_screen_0a ; CPU address $8d58 + .addr level_7_supertiles_screen_0b ; CPU address $8d82 + .addr level_7_supertiles_screen_0c ; CPU address $8db6 + .addr level_7_supertiles_screen_0d ; CPU address $8dea + .addr level_7_supertiles_screen_0e ; CPU address $8e10 + .addr level_7_supertiles_screen_00 ; CPU address $8ba1 + +level_7_supertiles_screen_00: + .byte $05,$05,$43,$43,$09,$17,$0e,$0e,$05,$43,$05,$3f,$09,$1b,$23,$23 + .byte $3f,$43,$3f,$3f,$30,$30,$6d,$6d,$6e,$38,$6f,$6f,$34,$34,$6d,$6d + .byte $84,$11,$13,$13,$11,$47,$87,$45,$37,$49,$4a,$49,$4a,$49,$4a,$49 + .byte $32 + +level_7_supertiles_screen_01: + .byte $83,$0e,$02,$84,$0e,$83,$23,$0d,$0f,$0f,$23,$23,$83,$6d,$4d,$6f + .byte $6f,$54,$6d,$10,$11,$11,$13,$11,$11,$12,$6d,$3e,$85,$3d,$2c,$6d + .byte $87,$6f,$40,$87,$20,$48 + +level_7_supertiles_screen_02: + .byte $0e,$02,$85,$0e,$02,$23,$09,$23,$22,$0f,$22,$23,$0d,$6d,$30,$6d + .byte $6d,$6e,$6d,$3c,$4d,$6d,$34,$6d,$3c,$6f,$54,$10,$13,$6d,$34,$6d + .byte $10,$11,$12,$3e,$0f,$11,$13,$47,$85,$6f,$45,$45,$32,$85,$20 + +level_7_supertiles_screen_03: + .byte $87,$0e,$1d,$05,$05,$3f,$05,$43,$05,$05,$50,$39,$3f,$05,$43,$3f + .byte $3f,$43,$50,$12,$3f,$6e,$6e,$3f,$38,$6f,$3b,$2c,$38,$83,$6f,$40 + .byte $11,$3a,$6f,$40,$83,$11,$31,$45,$45,$20,$48,$49,$4a,$49,$4a,$49 + .byte $4a + +level_7_supertiles_screen_04: + .byte $0e,$1d,$0e,$1d,$84,$0e,$43,$50,$05,$50,$83,$23,$6e,$05,$50,$3f + .byte $50,$6d,$6d,$3c,$6f,$6f,$3b,$6f,$3b,$33,$54,$10,$11,$11,$3a,$11 + .byte $3a,$11,$47,$3e,$3d,$85,$45,$37,$6f,$6f,$49,$4a,$49,$4a,$49,$32 + .byte $20,$20 + +level_7_supertiles_screen_05: + .byte $88,$0e,$83,$3d,$6e,$84,$23,$84,$6f,$54,$83,$6d,$84,$11,$12,$83 + .byte $6d,$84,$3d,$2c,$6d,$6d,$10,$85,$6f,$83,$33,$88,$20 + +level_7_supertiles_screen_06: + .byte $0e,$02,$86,$0e,$23,$35,$83,$23,$3d,$3d,$23,$64,$09,$83,$6d,$6e + .byte $6e,$24,$10,$1c,$83,$6d,$38,$6f,$41,$0c,$30,$6d,$3c,$33,$40,$11 + .byte $47,$33,$36,$33,$40,$11,$31,$45,$37,$83,$20,$48,$49,$4a,$49,$32 + +level_7_supertiles_screen_07: + .byte $83,$0e,$02,$84,$0e,$83,$23,$35,$84,$23,$25,$25,$26,$09,$24,$25 + .byte $25,$26,$29,$29,$2a,$09,$28,$29,$29,$2a,$27,$2b,$6d,$1f,$27,$2b + .byte $27,$2b,$2e,$2f,$6d,$34,$2e,$2f,$2e,$2f,$88,$20 + +level_7_supertiles_screen_08: + .byte $0e,$02,$86,$0e,$23,$35,$84,$23,$21,$2d,$6d,$09,$6d,$27,$2b,$6d + .byte $2e,$2f,$6d,$09,$6d,$2e,$2f,$6d,$10,$11,$6d,$1f,$6d,$10,$11,$0c + .byte $83,$6d,$34,$86,$6d,$88,$20 + +level_7_supertiles_screen_09: + .byte $83,$0e,$1d,$0e,$1d,$0e,$1d,$23,$23,$04,$50,$6e,$50,$6e,$50,$6d + .byte $6d,$38,$3b,$6f,$3b,$6f,$3b,$0c,$6d,$10,$3a,$11,$3a,$11,$3a,$6d + .byte $6d,$3e,$85,$3d,$6d,$6d,$38,$85,$6f,$00,$6d,$1e,$85,$20 + +level_7_supertiles_screen_0a: + .byte $88,$0e,$84,$23,$83,$04,$23,$54,$83,$6d,$83,$6e,$6d,$12,$6d,$6d + .byte $3c,$83,$6f,$54,$2c,$3c,$33,$40,$83,$11,$47,$6f,$40,$11,$31,$83 + .byte $45,$37,$20,$48,$49,$4a,$49,$4a,$49,$32 + +level_7_supertiles_screen_0b: + .byte $0e,$0e,$02,$01,$84,$03,$23,$23,$35,$17,$44,$06,$07,$14,$6d,$6d + .byte $09,$1b,$08,$0a,$0b,$18,$6d,$6d,$09,$09,$6d,$6d,$4f,$4f,$6d,$6d + .byte $30,$30,$6d,$6d,$6e,$6e,$33,$33,$36,$36,$33,$33,$6f,$6f,$11,$11 + .byte $13,$13,$84,$11 + +level_7_supertiles_screen_0c: + .byte $83,$03,$01,$01,$05,$43,$43,$15,$16,$44,$17,$17,$3f,$05,$43,$19 + .byte $1a,$08,$1b,$1b,$05,$43,$43,$4f,$6d,$6d,$09,$09,$3f,$43,$3f,$6e + .byte $6e,$6d,$30,$0d,$43,$3f,$43,$6f,$6f,$33,$36,$4d,$83,$6f,$83,$11 + .byte $13,$13,$83,$11 + +level_7_supertiles_screen_0d: + .byte $43,$3f,$3f,$6e,$83,$3f,$6e,$86,$3f,$43,$3f,$6e,$84,$3f,$6e,$83 + .byte $3f,$6e,$3f,$6e,$3f,$6e,$6e,$3f,$3f,$38,$87,$6f,$40,$87,$11,$31 + .byte $4a,$49,$4a,$49,$4a,$49 + +level_7_supertiles_screen_0e: + .byte $43,$6e,$3f,$3f,$6e,$51,$52,$53,$6e,$3f,$3f,$6e,$3f,$55,$56,$57 + .byte $3f,$6e,$3f,$3f,$58,$59,$5a,$5b,$6e,$3f,$6e,$6e,$5c,$5d,$5e,$5f + .byte $83,$6f,$39,$4e,$60,$67,$61,$11,$11,$42,$46,$42,$46,$76,$62,$4a + .byte $49,$4c,$4b,$4c,$4b,$4a,$63 + +; pointer table for level 8 (#$c * #$2 = #$18 bytes) +level_8_supertiles_screen_ptr_table: + .addr level_8_supertiles_screen_00 ; CPU address $8e5f + .addr level_8_supertiles_screen_01 ; CPU address $8e8e + .addr level_8_supertiles_screen_02 ; CPU address $8eb4 + .addr level_8_supertiles_screen_03 ; CPU address $8ee7 + .addr level_8_supertiles_screen_04 ; CPU address $8f0b + .addr level_8_supertiles_screen_05 ; CPU address $8f31 + .addr level_8_supertiles_screen_06 ; CPU address $8f59 + .addr level_8_supertiles_screen_07 ; CPU address $8f88 + .addr level_8_supertiles_screen_08 ; CPU address $8fac + .addr level_8_supertiles_screen_09 ; CPU address $8fd9 + .addr level_8_supertiles_screen_0a ; CPU address $8fef + .addr level_8_supertiles_screen_00 ; CPU address $8e5f + +level_8_supertiles_screen_00: + .byte $83,$4c,$2b,$84,$27,$4c,$4c,$2b,$85,$26,$4c,$4c,$3e,$26,$26,$23 + .byte $32,$32,$4c,$4c,$3e,$26,$26,$43,$00,$02,$4c,$4c,$3e,$2e,$04,$35 + .byte $12,$15,$4c,$4c,$47,$43,$24,$83,$11,$83,$01,$02,$24,$83,$11 + +level_8_supertiles_screen_01: + .byte $88,$27,$88,$26,$42,$44,$85,$32,$42,$3e,$08,$84,$01,$02,$3e,$47 + .byte $0c,$83,$10,$13,$12,$05,$4c,$07,$47,$0a,$0b,$0e,$11,$24,$3a,$11 + .byte $3a,$37,$36,$37,$11,$11 + +level_8_supertiles_screen_02: + .byte $27,$27,$2c,$4c,$61,$62,$63,$64,$26,$26,$2e,$4c,$65,$66,$67,$68 + .byte $26,$26,$2e,$4c,$5a,$59,$69,$2b,$26,$26,$2f,$41,$3d,$4c,$2b,$26 + .byte $06,$47,$2e,$40,$3c,$4c,$3e,$26,$24,$72,$35,$3f,$3c,$35,$05,$05 + .byte $24,$87,$11 + +level_8_supertiles_screen_03: + .byte $88,$11,$2b,$27,$1c,$1b,$20,$49,$49,$11,$26,$26,$2f,$1f,$2b,$27 + .byte $2c,$20,$83,$26,$2a,$83,$26,$29,$88,$26,$06,$42,$44,$85,$32,$11 + .byte $3a,$39,$85,$01 + +level_8_supertiles_screen_04: + .byte $90,$11,$49,$49,$84,$11,$49,$49,$83,$27,$2c,$1f,$31,$2b,$27,$26 + .byte $26,$44,$32,$2a,$83,$26,$43,$07,$4c,$03,$47,$0a,$05,$06,$02,$24 + .byte $3a,$11,$3a,$37,$11,$24 + +level_8_supertiles_screen_05: + .byte $8b,$11,$21,$31,$30,$1b,$33,$21,$31,$1f,$2b,$26,$2f,$1f,$2b,$28 + .byte $32,$45,$83,$32,$45,$42,$08,$85,$01,$02,$3e,$2d,$84,$48,$14,$12 + .byte $05,$00,$83,$01,$02,$24,$49,$49 + +level_8_supertiles_screen_06: + .byte $88,$11,$2b,$1c,$1f,$2b,$84,$27,$26,$29,$2a,$2e,$04,$05,$05,$06 + .byte $26,$44,$32,$43,$11,$11,$49,$49,$26,$08,$83,$01,$02,$2b,$27,$05 + .byte $16,$12,$15,$48,$48,$47,$32,$49,$11,$11,$24,$00,$01,$01,$02 + +level_8_supertiles_screen_07: + .byte $88,$11,$88,$27,$42,$0a,$86,$05,$3e,$0e,$85,$11,$49,$46,$29,$27 + .byte $27,$30,$1f,$31,$2b,$43,$04,$83,$05,$34,$44,$32,$3a,$11,$11,$49 + .byte $49,$36,$3a,$00 + +level_8_supertiles_screen_08: + .byte $88,$11,$27,$27,$2c,$20,$84,$11,$05,$06,$42,$2c,$20,$83,$11,$49 + .byte $22,$3e,$26,$29,$1c,$1f,$4c,$27,$27,$46,$83,$26,$29,$30,$32,$32 + .byte $43,$04,$83,$05,$35,$01,$38,$3a,$11,$11,$49,$49,$11 + +level_8_supertiles_screen_09: + .byte $87,$11,$22,$84,$11,$25,$4c,$33,$1b,$11,$49,$49,$25,$83,$4c,$1f + .byte $90,$4c,$88,$35,$88,$11 + +level_8_supertiles_screen_0a: + .byte $88,$18,$33,$83,$4c,$19,$6a,$6a,$18,$84,$4c,$1d,$4e,$4d,$18,$85 + .byte $4c,$50,$4f,$18,$84,$4c,$1a,$17,$17,$18,$84,$35,$1e,$6e,$6e,$18 + .byte $18,$49,$86,$18 + +; pointer table for indoor levels boss rooms (#$2 * #$2 = #$4 bytes) +level_2_4_boss_supertiles_screen_ptr_table: + .addr level_2_4_boss_supertiles_screen_00 ; CPU address $9017 + .addr level_2_4_boss_supertiles_screen_01 ; CPU address $9057 + +; table for level 2 boss room (#$40 bytes) +; indexes into level_2_4_boss_supertile_data +level_2_4_boss_supertiles_screen_00: + .byte $01,$02,$03,$09,$0e,$0f,$10,$11,$04,$05,$06,$00,$00,$12,$0b,$13 + .byte $07,$08,$4a,$53,$56,$4a,$0d,$0c,$07,$05,$59,$71,$65,$59,$0b,$0a + .byte $07,$14,$5c,$6e,$6b,$5c,$1d,$0a,$16,$15,$17,$19,$1c,$18,$1e,$1f + .byte $1a,$1b,$21,$22,$23,$24,$1b,$20,$00,$00,$00,$00,$00,$00,$00,$00 + +; table for level 4 boss room (#$40 bytes) +; indexes into level_2_4_boss_supertile_data +level_2_4_boss_supertiles_screen_01: + .byte $2c,$2d,$2e,$2f,$3b,$3c,$3d,$3e,$30,$31,$32,$62,$5f,$40,$41,$42 + .byte $30,$34,$35,$50,$68,$35,$44,$42,$37,$38,$4a,$53,$56,$4a,$46,$47 + .byte $30,$48,$49,$28,$39,$39,$3a,$42,$29,$2a,$2b,$45,$36,$33,$27,$26 + .byte $43,$3f,$3f,$3f,$3f,$3f,$3f,$25,$00,$00,$00,$00,$00,$00,$00,$00 + +; intro screen nametable layout (#$1bb bytes) +; nametable entries point to pattern table data in graphic_data_01 +; CPU address $9097 +; nametable and attribute table used for intro screen +; nametable data - writes addresses [$2000-$2400) +; "1 PLAYER" +; "2 PLAYERS" +; "TM AND C 1988" +; "KONAMI INDUSTRY" +; "CO.,LTD" +; "LICENSED BY" +; "NINTENDO OF" +; "AMERICA INC" +graphic_data_02: + .incbin "assets/graphic_data/graphic_data_02.bin" + +; compressed alternate graphics data for level 1 (length ?) +; CPU address $9252 +alt_graphic_data_00: + .incbin "assets/graphic_data/alt_graphic_data_00.bin" + +; CPU address $97d2 +alt_graphic_data_01: + .incbin "assets/graphic_data/alt_graphic_data_01.bin" + +; CPU address $a372 +alt_graphic_data_02: + .incbin "assets/graphic_data/alt_graphic_data_02.bin" + +; CPU address $a712 +alt_graphic_data_03: + .incbin "assets/graphic_data/alt_graphic_data_03.bin" + +; CPU address $ab52 +alt_graphic_data_04: + .incbin "assets/graphic_data/alt_graphic_data_04.bin" + +; ensure player sprite attributes continue to animate while paused +set_players_paused_sprite_attr: + ldx #$01 ; x = #$01 + +@loop: + jsr set_player_paused_sprite_attr + dex + bpl @loop + rts + +; set player sprite based on player state, level, and animation sequence +; input +; * x - current player +set_player_sprite_and_attrs: + jsr set_player_sprite + +; ensure player sprite attributes continue to animate while paused +set_player_paused_sprite_attr: + lda PLAYER_HIDDEN,x ; 0 - visible; #$01/#$ff = invisible (any non-zero) + bne @continue ; branch if player is hidden + ldy PLAYER_SPRITE_CODE,x ; player not hidden, load the player's sprite code + lda NEW_LIFE_INVINCIBILITY_TIMER,x ; load timer for invincibility after dying + beq @set_y_to_player_sprite ; branch if timer is #$00 + lda FRAME_COUNTER ; load frame counter + lsr + bcc @set_y_to_player_sprite ; branch if even frame + +@continue: + ldy #$00 ; init player sprite attr to #$00 (player is either hidden, or odd frame flashing having just spawned) + +@set_y_to_player_sprite: + tya ; move player sprite code to a register + sta PLAYER_SPRITES,x ; update PLAYER_SPRITES with sprite from PLAYER_SPRITE_CODE + lda sprite_attr_start_tbl,x ; load sprite palette based on player index + ldy ELECTROCUTED_TIMER,x ; counter for being electrocuted + beq @invincibility_check ; jump if not being electrocuted + lda #$02 ; set sprite attr bit for player being electrocuted + bne @continue2 ; always jump + +@invincibility_check: + ldy INVINCIBILITY_TIMER,x + beq @set_player_recoil_and_bg_priority ; jump if not invincible + lda #$04 ; a = #$04 + +@continue2: + sta $00 ; store sprite code for player in $00 + lda FRAME_COUNTER ; load frame counter + eor player_effect_xor_tbl,x + ldy #$04 + and $00 + beq @continue3 ; branch if sprite attribute is still #$00 + ldy #$05 ; y = #$05 (override sprite code palette with palette 1) + +@continue3: + tya ; transfer sprite attribute to a + +@set_player_recoil_and_bg_priority: + ldy PLAYER_BG_FLAG_EDGE_DETECT,x ; determine if player's sprite should be behind the background + bpl @check_sprite_recoil ; branch if sprite is front of background + ora #$20 ; set bit 5, the SPRITE_ATTR for background priority + +@check_sprite_recoil: + ldy PLAYER_RECOIL_TIMER,x ; see if there is any recoil effect + beq @set_player_sprite_attr ; skip if no recoil + cpy #$0c ; weapon still out/still handing recoil compare time left to #$0c + bcc @set_player_sprite_attr ; branch if PLAYER_RECOIL_TIMER,x < #$0c + ora #$08 ; set bit 3, this moves the player torso down 1 pixel to simulate recoil (see SPRITE_ATTR documentation) + +@set_player_sprite_attr: + sta $00 + lda PLAYER_SPRITE_FLIP,x ; load player sprite horizontal and vertical flip flags + and #$c8 ; keep bits xx.. x... + ora $00 + sta SPRITE_ATTR,x ; set the sprite modifier for player (if invincible or have electrocution effect, blink) + rts + +; determine sprite palette based on player index +; first byte is player 1 +; second byte is player 2 +sprite_attr_start_tbl: + .byte $00,$05 + +; first byte is player 1 effect xor value +; second byte is player 2 effect xor value +player_effect_xor_tbl: + .byte $00,$ff + +set_player_sprite: + lda PLAYER_WATER_STATE,x + beq @set_out_of_water_sprite + jmp set_player_water_sprite_and_state + +@set_out_of_water_sprite: + lda EDGE_FALL_CODE,x + beq @set_non_falling_player_sprite ; branch if player isn't falling through floor or walking off ledge + jmp set_player_sprite_05 ; sprite_05 (player falling through floor, or walk off ledge) + +@set_non_falling_player_sprite: + lda PLAYER_JUMP_STATUS,x ; low nibble 1 = jumping, 0 not jumping; high nibble = facing direction + beq @set_sprite_for_sequence ; set sprite when player isn't jumping + jmp set_player_jump_sprite ; player jumping, set appropriate sprite + +@set_sprite_for_sequence: + lda LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor + beq @set_outdoor_player_sprite_for_sequence ; branch for outdoor level + bmi @indoor_boss_set_player_sprite ; branch if indoor boss screen + jmp set_indoor_player_sprite_for_sequence ; indoor level, set the player sprite + ; based on PLAYER_SPRITE_SEQUENCE and which frame for indoor levels + +@indoor_boss_set_player_sprite: + jmp indoor_boss_set_player_sprite + +@set_outdoor_player_sprite_for_sequence: + ldy PLAYER_SPRITE_SEQUENCE,x ; player animation frame + cpy #$03 + bcs big_seq_sprite ; branch if PLAYER_SPRITE_SEQUENCE >= #$03 + lda player_small_seq_sprite_tbl,y ; player sprite sequence is (#$00-#$03) load sprite + jmp set_player_sprite_to_a + +; sprite_0f - player walking holding weapon out +; sprite_16 - player aiming straight up +; sprite_17 - player prone +player_small_seq_sprite_tbl: + .byte $0f,$16,$17 + +big_seq_sprite: + bne set_outdoor_player_death_sprite ; branch if PLAYER_SPRITE_SEQUENCE > #$03 + +set_player_frame_sprite: + ldy PLAYER_AIM_DIR,x ; which direction the player is aiming/looking + lda player_frame_sprite_type_tbl,y + bne set_player_frame_sprite_from_a + ldy PLAYER_RECOIL_TIMER,x ; player_frame_sprite_type_tbl was #$00, see if player has recoil still + beq set_player_frame_sprite_from_a ; branch if no recoil + lda #$01 ; player has recoil, use player_frame_sprite_tbl_01 + +; sets the player sprite based on walking direction and aim direction +; input +; * a - which player sprite animation to use (see player_frame_sprite_ptr_tbl) +set_player_frame_sprite_from_a: + asl + tay + lda player_frame_sprite_ptr_tbl,y + sta $01 + lda player_frame_sprite_ptr_tbl+1,y + sta $02 + lda PLAYER_ANIMATION_FRAME_INDEX,x + tay + lda ($01),y + sta PLAYER_SPRITE_CODE,x + inc PLAYER_ANIM_FRAME_TIMER,x ; increment #$08 player moving frame delay before moving to next animation frame + lda PLAYER_ANIM_FRAME_TIMER,x ; load delay between frames + and #$07 ; see if #$08 frames have elapsed + bne @set_player_horizontal_flip ; branch if #$08 frames haven't elapsed + ; to set PLAYER_SPRITE_FLIP bit 6 (horizontal flip) if facing left + inc PLAYER_ANIMATION_FRAME_INDEX,x ; #$08 frames have elapsed move to next player animation sprite + lda PLAYER_ANIMATION_FRAME_INDEX,x ; load player animation sprite index + cmp #$06 ; see if past last sprite in animation + bcc @set_player_horizontal_flip ; branch if not past last sprite to continue + lda #$00 ; reset player sprite index + sta PLAYER_ANIMATION_FRAME_INDEX,x ; reset player sprite index + +@set_player_horizontal_flip: + jmp set_player_horizontal_flip ; set PLAYER_SPRITE_FLIP bit 6 (horizontal flip) if facing left + +; player aim direction offsets, specify which sprite table to use based on aim direction +; #$00 means to use player_frame_sprite_tbl_00 (walking with gun in hand) +; unless there is still recoil from a shot, then use player_frame_sprite_tbl_01 +player_frame_sprite_type_tbl: + .byte $00,$02,$00,$03,$00,$00,$03,$00,$02,$00 + +; pointer table for which sprite to load for player based on aim direction and whether there is gun recoil (#$5 * #$2 = #$a bytes) +player_frame_sprite_ptr_tbl: + .addr player_frame_sprite_tbl_00 ; CPU address $b0d1 - walking with gun in hand + .addr player_frame_sprite_tbl_01 ; CPU address $b0d7 - walking with gun and recoil + .addr player_frame_sprite_tbl_02 ; CPU address $b0dd - outdoor walking aiming up + .addr player_frame_sprite_tbl_03 ; CPU address $b0e3 - outdoor walking aiming down + .addr player_frame_sprite_tbl_04 ; CPU address $b0e9 - indoor level + +; walking with gun in hand +player_frame_sprite_tbl_00: + .byte $02,$03,$04,$05,$03,$06 + +; outdoor walking aiming in walking direction +; sprite_0d, sprite_0e, sprite_0f +player_frame_sprite_tbl_01: + .byte $0d,$0e,$0f,$0d,$0e,$0f + +; outdoor walking aiming up +; sprite_10, sprite_11, sprite_12 +player_frame_sprite_tbl_02: + .byte $10,$11,$12,$10,$11,$12 + +; outdoor walking aiming down +; sprite_13, sprite_14, sprite_15 +player_frame_sprite_tbl_03: + .byte $13,$14,$15,$13,$14,$15 + +; indoor level +; sprite_51, sprite_52, sprite_53 +player_frame_sprite_tbl_04: + .byte $51,$52,$53,$51,$52,$53 + +set_outdoor_player_death_sprite: + inc PLAYER_SPECIAL_SPRITE_TIMER,x ; increment player death sprite timer + lda PLAYER_SPECIAL_SPRITE_TIMER,x ; load player death sprite timer + and #$07 ; move to next animation, every #$08 frames + bne @continue ; don't move to next frame, timer isn't divisible by #$08 + inc PLAYER_ANIMATION_FRAME_INDEX,x ; #$08th frame move to next sprite in animation + lda PLAYER_ANIMATION_FRAME_INDEX,x ; load sprite animation number + cmp #$05 ; see if past the last frame of the animation + bcc @continue ; continue if not the last frame of animation + lda #$04 ; went past last frame, show last frame of sprite (sprite_0c) + sta PLAYER_ANIMATION_FRAME_INDEX,x ; set sprite frame number to #$04 (sprite_0c) + +@continue: + lda PLAYER_ANIMATION_FRAME_INDEX,x ; load sprite animation frame number + asl ; double since each entry in player_death_sprite_tbl is #$02 bytes + tay ; transfer to y for offset + lda player_death_sprite_tbl,y ; load sprite_code + sta PLAYER_SPRITE_CODE,x ; set sprite code + lda player_death_sprite_tbl+1,y ; load sprite attributes + sta PLAYER_SPRITE_FLIP,x ; store whether sprite is flipped horizontally and/or vertically + lda PLAYER_DEATH_FLAG,x ; player death flag + and #$02 ; keep bit 1 (player facing left when hit flag) + beq @exit ; branch to not flip sprite if player was facing right when hit + lda PLAYER_SPRITE_FLIP,x ; facing left when hit, load player sprite horizontal and vertical flip flags + eor #$40 ; flip bit 6 (swap horizontal flip direction) + sta PLAYER_SPRITE_FLIP,x ; save updated horizontal flip information + +@exit: + rts + +; PLAYER_SPRITE_CODE and sprite horizontal/vertical flip data (#$0a bytes) +; byte 0 - PLAYER_SPRITE_CODE +; byte 1 - SPRITE_ATTR +player_death_sprite_tbl: + .byte $0a,$00 ; sprite_0a - player hit (frame 1) (sprite_0a) + .byte $0b,$00 ; sprite_0b - player hit (frame 2) (sprite_0b) + .byte $0a,$c0 ; sprite_0a - player hit (frame 1) (sprite_0a) - flip vertically and horizontally + .byte $0b,$c0 ; sprite_0b - player hit (frame 2) (sprite_0b) - flip vertically and horizontally + .byte $0c,$00 ; sprite_0c - player lying on ground (sprite_0c) + +set_player_sprite_05: + lda #$05 ; a = #$05 (sprite_05) - player walking (frame 4) + +set_player_sprite_to_a: + sta PLAYER_SPRITE_CODE,x ; load player sprite (sprite code) + +set_player_horizontal_flip: + lda PLAYER_SPRITE_FLIP,x ; load player sprite horizontal and vertical flip flags + and #$3f ; keep bits 6 and 7 (horizontal and vertical flip bits respectively) + ldy PLAYER_AIM_DIR,x ; which direction the player is aiming/looking + cpy #$05 ; compare to crouching facing left + bcc @continue ; branch if player facing right + ora #$40 ; player is facing left, set horizontal flip bit (bit 6) + +@continue: + sta PLAYER_SPRITE_FLIP,x ; store whether sprite is flipped horizontally and/or vertically + rts + +; sets appropriate curled up player sprite for jumping +set_player_jump_sprite: + lda PLAYER_JUMP_STATUS,x ; low nibble 1 = jumping, 0 not jumping; high nibble = facing direction + asl ; shift bit 7 to carry (jumping left flag) + lda #$00 ; a = #$00 (no horizontal sprite flip) + bcc @continue ; continue if jumping right + lda #$40 ; jumping left, set bit 6 (sprite horizontal flip bit) + +@continue: + sta $08 ; set sprite flip bit information in $08 + lda PLAYER_SPRITE_FLIP,x ; load player sprite horizontal and vertical flip flags + and #$3f ; clear PLAYER_SPRITE_FLIP horizontal and vertical flip bits + ldy PLAYER_ANIMATION_FRAME_INDEX,x ; load which frame of the player animation + cpy #$02 ; + bcc @set_sprite_inc_frame + ora #$c0 ; flip vertically and horizontally + +@set_sprite_inc_frame: + eor $08 ; merge with sprite bit flip information, use eor in case a flip of a flip is needed + sta PLAYER_SPRITE_FLIP,x ; store whether sprite is flipped horizontally and/or vertically + ldy PLAYER_ANIMATION_FRAME_INDEX,x ; load the current animation frame of the jumping curl + lda player_curled_sprite_code_tbl,y ; load specific curled player sprite + sta PLAYER_SPRITE_CODE,x ; set player sprite curl (sprite code) + inc PLAYER_SPECIAL_SPRITE_TIMER,x + lda PLAYER_SPECIAL_SPRITE_TIMER,x + cmp #$05 + bcc @exit + lda #$00 ; a = #$00 + sta PLAYER_SPECIAL_SPRITE_TIMER,x + inc PLAYER_ANIMATION_FRAME_INDEX,x + lda PLAYER_ANIMATION_FRAME_INDEX,x + cmp #$04 + bcc @set_anim_frame_exit + lda #$00 ; a = #$00 + +@set_anim_frame_exit: + sta PLAYER_ANIMATION_FRAME_INDEX,x + +@exit: + rts + +; table for player animation frames when spinning during a jump (#$4 bytes) +; sprite_08, sprite_09 +player_curled_sprite_code_tbl: + .byte $08,$09,$08,$09 + +set_player_water_sprite_and_state: + lda PLAYER_WATER_STATE,x + and #$04 ; keep bits .... .x.. + bne set_player_in_water_sprite ; branch if set player fully in water or walking out of water + lda PLAYER_WATER_STATE,x ; bit 2 clear, player entering water, load player water state + and #$10 ; keep bit 4 + bne @set_enter_water_sprite ; branch if bit 4 set + lda #$00 ; branch 4 clear, entering water for first time, set a = #$00 + sta PLAYER_ANIMATION_FRAME_INDEX,x ; initialize frame index to #$00 + lda #$05 ; a = #$05 (sprite_05) - player walking (frame 4) + sta PLAYER_SPRITE_CODE,x ; set player sprite code, player standing for #$08 frames before splash + lda SPRITE_Y_POS,x ; player y position on screen + clc ; clear carry in preparation for addition + adc #$10 ; move down #$10 pixels + sta SPRITE_Y_POS,x ; update player y position on screen + lda PLAYER_AIM_DIR,x ; which direction the player is aiming/looking + cmp #$05 ; see if facing left or right + bcc @continue ; branch if facing right + lda PLAYER_WATER_STATE,x ; player facing left, load current PLAYER_WATER_STATE + ora #$02 ; set bit 1 to signify sprite flip needed during animation + sta PLAYER_WATER_STATE,x ; update PLAYER_WATER_STATE + +@continue: + lda #$10 ; a = #$10 + sta PLAYER_WATER_TIMER,x ; set entering water animation timer to #$10 + lda PLAYER_WATER_STATE,x ; load player water state + ora #$90 ; set bits x..x ...., specifies initialized water entrance animation + sta PLAYER_WATER_STATE,x ; update player water state + +@set_enter_water_sprite: + lda #$00 ; a = #$00 + sta PLAYER_X_VELOCITY,x ; stop player x velocity + lda PLAYER_WATER_TIMER,x ; load water animation timer + beq set_player_in_water_sprite ; if timer has elapsed, branch to set the correct sprite + cmp #$0c ; see if timer is greater than or equal to #$0c + bcs set_player_water_sprite_flip ; branch if timer >= #$0c to set player sprite flip (horizontal flip) for standing up above water + lda #$73 ; PLAYER_WATER_TIMER less than #$0c, set splash sprite + sta PLAYER_SPRITE_CODE,x ; set player sprite (sprite_73) water splash + cmp #$08 ; !(BUG?) always branch, doesn't compare to PLAYER_WATER_TIMER, but instead compares against #$73 + ; no lda PLAYER_WATER_TIMER,x before this line, so part of splash animation is missing + ; same issue appears in Probotector and Japanese version of game + bcs set_player_water_sprite_flip ; always branch, branch to set player sprite attribute to ensure horizontal direction is correct + lda #$18 ; dead code - a = #$18 (sprite_18) water splash/puddle + sta PLAYER_SPRITE_CODE,x ; dead code - set player animation frame to water splash/puddle + +set_player_water_sprite_flip: + dec PLAYER_WATER_TIMER,x ; decrement water animation timer + lda PLAYER_WATER_STATE,x ; load player water state + and #$02 ; keep bit 1 (horizontal flip flag) + beq @exit ; exit if no horizontal flip + lda PLAYER_SPRITE_FLIP,x ; load player sprite horizontal and vertical flip flags + ora #$40 ; set horizontal flip (bit 6) + sta PLAYER_SPRITE_FLIP,x ; set sprite horizontal flip + +@exit: + rts + +; set sprite for player in water or walking out of water +set_player_in_water_sprite: + lda PLAYER_WATER_STATE,x ; load player water state + and #$08 ; keep bit 3 (player walking out of water flag) + beq @set_player_water_sprite_and_state ; branch if player is not walking out of water + jmp player_walk_out_of_water ; jump if player is walking out of water + +@set_player_water_sprite_and_state: + lda PLAYER_WATER_STATE,x ; load player water state + ora #$04 ; set bit 2 + and #$7f ; strip bit 7 + sta PLAYER_WATER_STATE,x ; update player water state + lda PLAYER_AIM_DIR,x ; which direction the player is aiming/looking + asl ; double since each entry is #$02 bytes + tay ; transfer offset to y + lda PLAYER_RECOIL_TIMER,x ; load current player recoil timer + beq @in_water_not_firing ; branch if no recoil timer + lda player_water_firing_sprite_tbl,y ; player is firing, show firing sprite code + sta PLAYER_SPRITE_CODE,x ; load player sprite (sprite code) + lda player_water_firing_sprite_tbl+1,y ; load PLAYER_SPRITE_FLIP flag (whether to flip the sprite horizontally) + jmp @set_sprite_flip_check_collision ; set PLAYER_SPRITE_FLIP, check if collision with ground + +@in_water_not_firing: + lda player_water_sprite_tbl,y ; load non-firing player in water sprite code + sta PLAYER_SPRITE_CODE,x ; load player sprite (sprite code) + lda player_water_sprite_tbl+1,y ; load non-firing player in water sprite attribute + +; input +; * a - PLAYER_SPRITE_FLIP horizontal and vertical flip values (bit 6 and 7) +@set_sprite_flip_check_collision: + sta $01 ; set to whether to flip the sprite horizontally or vertically + lda PLAYER_SPRITE_FLIP,x ; load current player sprite horizontal and vertical flip flags + and #$0f ; strip high nibble + ora $01 ; merge updated horizontal and vertical flip bits + sta PLAYER_SPRITE_FLIP,x ; set whether sprite is flipped horizontally and/or vertically + lda FRAME_COUNTER ; load frame counter + and #$0f ; keep bits .... xxxx + bne @check_anim_frame_and_collision ; continue if not the #$fth frame + inc PLAYER_ANIMATION_FRAME_INDEX,x ; move to next animation every #$f frames + +@check_anim_frame_and_collision: + lda PLAYER_ANIMATION_FRAME_INDEX,x ; load frame of the player animation + and #$01 ; keep bit 0 + bne @clear_sprite_flip_check_collision ; branch if odd player animation frame + lda PLAYER_SPRITE_FLIP,x ; load player sprite horizontal and vertical flip flags + ora #$08 ; set bit 3 + bne @check_ground_collision ; always branch, see if player has collided with the ground + ; and should begin animation to step out of water + +@clear_sprite_flip_check_collision: + lda PLAYER_SPRITE_FLIP,x ; load player sprite horizontal and vertical flip flags + and #$f7 ; strip bit 3 + +@check_ground_collision: + sta PLAYER_SPRITE_FLIP,x ; store whether sprite is flipped horizontally and/or vertically + lda SPRITE_Y_POS,x ; load player y position on screen + tay ; transfer y position to y + lda SPRITE_X_POS,x ; load player x position on screen + jsr get_bg_collision ; determine player background collision code at position (a,y) + cmp #$02 ; see if collision code #$02 (water) + beq @exit ; exit if in water + lda PLAYER_WATER_STATE,x ; player colliding with land, set transition to start walking out of water + ora #$88 ; set bits 3 and 7 to signal walking out of water + sta PLAYER_WATER_STATE,x ; update player water state + lda #$0c ; set timer to animate player walking out of water + sta PLAYER_WATER_TIMER,x ; update timer + lda #$1a ; a = #$1a (sprite_1a) player climbing out of water + sta PLAYER_SPRITE_CODE,x ; set player sprite to be climbing out of water + lda PLAYER_AIM_DIR,x ; which direction the player is aiming/looking + cmp #$05 ; determine facing direction + bcc @clear_horizontal_flip_exit ; branch if player is facing right + lda PLAYER_WATER_STATE,x ; player is facing left, flip player sprite horizontally + ora #$02 ; set bit 1 (horizontal flip flag) + bne @set_horizontal_flip_flag_exit ; set bit 1 in PLAYER_WATER_STATE and exit + +@clear_horizontal_flip_exit: + lda PLAYER_WATER_STATE,x ; load player water transition data + and #$fd ; strip bit 1 (horizontal flip flag) + +@set_horizontal_flip_flag_exit: + sta PLAYER_WATER_STATE,x ; set horizontal flip flag + +@exit: + rts + +; logic when player is walking out of water +; moves player forward and sets appropriate sprite +; executed repeatedly until PLAYER_WATER_TIMER,x is #$00 +player_walk_out_of_water: + lda #$06 ; !(OBS) unnecessary, because overwritten to #$00 by handle_player_state_calc_x_vel + sta PLAYER_X_VELOCITY,x ; unnecessary, set player x velocity to #$06 + lda PLAYER_WATER_TIMER,x ; load animation timer + beq clear_water_vars_set_y_pos ; timer elapsed, player is now out of water + cmp #$05 ; timer not elapsed, see if less than #$05 + bcc @almost_out_of_water ; branch if timer is almost elapsed to set player standing sprite + jmp set_player_water_sprite_flip ; set player sprite attribute to ensure horizontal direction is correct + +@almost_out_of_water: + lda #$05 ; a = #$05 (sprite_05) player walking (frame 4) + sta PLAYER_SPRITE_CODE,x ; set player sprite to walking frame + jmp set_player_water_sprite_flip ; set player sprite attribute to ensure horizontal direction is correct + dec PLAYER_WATER_TIMER,x ; decrement animation timer + rts + +; player out of water, clear water variables and adjust y position to be on land +clear_water_vars_set_y_pos: + lda #$00 ; a = #$00 + sta PLAYER_WATER_STATE,x ; clear player in water animation + sta PLAYER_ANIMATION_FRAME_INDEX,x ; set player frame to first frame of walking + lda SPRITE_Y_POS,x ; player y position on screen + sec ; set carry flag in preparation for subtraction + sbc #$10 ; move player up so that they are on the ground and out of the water + sta SPRITE_Y_POS,x + rts + +; table for sprites and sprite flip values for player in water when not firing (#$14 bytes) +; (cf. player_water_firing_sprite_tbl) +; each entry is for a specific PLAYER_AIM_DIR [#$00-#$09] +; sprite_19, sprite_18 +player_water_sprite_tbl: + .byte $19,$00 ; up facing right - sprite_19 - player in water + .byte $19,$00 ; up right - sprite_19 - player in water + .byte $19,$00 ; right - sprite_19 - player in water + .byte $18,$00 ; down right - sprite_18 - water splash/puddle + .byte $18,$00 ; down facing right - sprite_18 - water splash/puddle + .byte $18,$40 ; down facing left - sprite_18 - water splash/puddle (flipped horizontally) + .byte $18,$40 ; down left - sprite_18 - water splash/puddle (flipped horizontally) + .byte $19,$40 ; left - sprite_19 - player in water (flipped horizontally) + .byte $19,$40 ; left up - sprite_19 - player in water (flipped horizontally) + .byte $19,$40 ; left facing up - sprite_19 - player in water (flipped horizontally) + +; table for sprites and sprite flip values for player in water and firing (#$14 bytes) +; (cf. player_water_sprite_tbl) +; each entry is for a specific PLAYER_AIM_DIR [#$00-#$09] +; sprite_18, sprite_1b, sprite_1c, sprite_1d +player_water_firing_sprite_tbl: + .byte $1b,$00 ; up facing right - sprite_1b - player in water aiming straight up + .byte $1c,$00 ; up right - sprite_1c - player in water aiming angled up + .byte $1d,$00 ; right - sprite_1d - player in water aiming forward + .byte $18,$00 ; down right - sprite_18 - water splash/puddle + .byte $18,$00 ; down facing right - sprite_18 - water splash/puddle + .byte $18,$40 ; down facing left - sprite_18 - water splash/puddle (flipped horizontally) + .byte $18,$40 ; down left - sprite_18 - water splash/puddle (flipped horizontally) + .byte $1d,$40 ; left - sprite_1d - player in water aiming forward (flipped horizontally) + .byte $1c,$40 ; left up - sprite_1c - player in water aiming angled up (flipped horizontally) + .byte $1b,$40 ; left facing up - sprite_1b - player in water aiming straight up (flipped horizontally) + +; sets the appropriate player sprite based on which sequence and which frame for indoor levels +set_indoor_player_sprite_for_sequence: + lda PLAYER_SPRITE_SEQUENCE,x + jsr run_routine_from_tbl_below ; run routine a in the following table (indoor_player_sprite_tbl) + +; pointer table for setting appropriate player sprite (#$8 * #$2 = #$10 bytes) +indoor_player_sprite_tbl: + .addr player_sprite_indoor_facing_up ; CPU address $b2c6 + .addr player_sprite_indoor_electrocuted ; CPU address $b2ca + .addr player_sprite_indoor_crouch ; CPU address $b2ce + .addr player_sprite_indoor_walking_animation ; CPU address $b2b9 + .addr player_sprite_indoor_walking_animation ; CPU address $b2b9 + .addr player_sprite_indoor_walking_to_back ; CPU address $b2d3 + .addr player_sprite_indoor_dead ; CPU address $b2ec + .addr player_sprite_indoor_elevator ; CPU address $b2c2 !(UNUSED) + +player_sprite_indoor_walking_animation: + lda PLAYER_RECOIL_TIMER,x + beq @continue + lda #$04 ; a = #$04 + +; indoor level, set player sprite based on aim direction +@continue: + jmp set_player_frame_sprite_from_a ; player_frame_sprite_tbl_04 + +; unused !(UNUSED) +; elevator sprite is set with (set_player_on_elevator_sprite) +; sprite elevator isn't used on non-boss indoor level +player_sprite_indoor_elevator: + lda #$91 ; a = #$91 (sprite_91) indoor boss defeated elevator with player on top + bne set_player_sprite_code_to_a + +player_sprite_indoor_facing_up: + lda #$50 ; a = #$50 (sprite_50) indoor player facing up + bne set_player_sprite_code_to_a + +player_sprite_indoor_electrocuted: + lda #$55 ; a = #$55 (sprite_55) indoor player electrocuted + bne set_player_sprite_code_to_a + +player_sprite_indoor_crouch: + lda #$54 ; a = #$54 (sprite_54) indoor player crouch + +set_player_sprite_code_to_a: + sta PLAYER_SPRITE_CODE,x ; load player sprite (sprite code) + rts + +player_sprite_indoor_walking_to_back: + dec PLAYER_ANIM_FRAME_TIMER,x + bpl @continue + lda #$03 ; a = #$03 (sound_03) + jsr play_sound ; play player landing on ground or water sound + lda #$0a ; a = #$0a + sta PLAYER_ANIM_FRAME_TIMER,x + inc PLAYER_ANIMATION_FRAME_INDEX,x + +@continue: + lda PLAYER_ANIMATION_FRAME_INDEX,x + and #$01 ; keep bits .... ...x + clc ; clear carry in preparation for addition + adc #$57 ; a = #$57 (sprite_57) indoor player running + sta PLAYER_SPRITE_CODE,x ; load player sprite (sprite code) + rts + +player_sprite_indoor_dead: + lda PLAYER_SPECIAL_SPRITE_TIMER,x + cmp #$1b + lda #$56 ; a = #$56 (sprite_56) indoor player lying dead (frame #$02) + bcs @continue + inc PLAYER_SPECIAL_SPRITE_TIMER,x + lda #$55 ; a = #$55 (sprite_55) indoor player hit by bullet frame #$01 + +@continue: + sta PLAYER_SPRITE_CODE,x ; load player sprite (sprite code) sprite_55, or sprite_56 + lda #$00 ; a = #$00 + sta PLAYER_SPRITE_FLIP,x ; store whether sprite is flipped horizontally and/or vertically + rts + +indoor_boss_set_player_sprite: + lda PLAYER_SPRITE_SEQUENCE,x + jsr run_routine_from_tbl_below ; run routine a in the following table (indoor_boss_player_sprite_tbl) + +; pointer table for ? (#$5 * #$2 = #$a bytes) +indoor_boss_player_sprite_tbl: + .addr indoor_boss_player_aiming_up_sprite ; CPU address $b30e + .addr indoor_boss_player_aiming_up_sprite ; CPU address $b30e + .addr indoor_boss_player_aiming_up_sprite ; CPU address $b30e + .addr set_player_frame_sprite ; CPU address $b086 + .addr player_sprite_indoor_dead ; CPU address $b2ec + +indoor_boss_player_aiming_up_sprite: + lda PLAYER_SPRITE_FLIP,x ; load player sprite horizontal and vertical flip flags + and #$3f ; keep bits ..xx xxxx + sta PLAYER_SPRITE_FLIP,x ; store whether sprite is flipped horizontally and/or vertically + lda #$50 ; a = #$50 (sprite_50) indoor player facing up + sta PLAYER_SPRITE_CODE,x ; load player sprite (sprite code) + rts + +; Level Headers - A description of each level +; loaded into memory by load_level_header in bank 7 +; Level 1 - jungle +level_headers: +level_1_header: + .byte $00 ; location type: outdoor level (0 = outdoor ; 1 = indoor ; ff = indoor boss room) (LEVEL_LOCATION_TYPE) + .byte $00 ; outdoor scrolling type: horizontal scroll (0 = horizontal ; 1 = vertical) + .addr level_1_supertiles_screen_ptr_table ; pointer to which super-tiles are on each screen (bank 2 memory address) (see LEVEL_SCREEN_SUPERTILES_PTR) + .addr level_1_supertile_data ; composition of super-tiles (bank 3 memory address) (see LEVEL_SUPERTILE_DATA_PTR) + .addr level_1_palette_data ; super-tile palette data (bank 3 memory address) (see LEVEL_SUPERTILE_PALETTE_DATA) + .byte $0b ; alternate graphics loading (+2) (screen where they start loading) (see LEVEL_ALT_GRAPHICS_POS) + .byte $06,$f9,$ff ; tile collision limits (see COLLISION_CODE_X_TILE_INDEX) + .byte $05,$08,$05,$08 ; palette indexes into game_palettes for cycling the 4th nametable palette index (see LEVEL_PALETTE_CYCLE_INDEXES) + .byte $02,$03,$04,$05 ; indexes into game_palettes specifying initial background tile palette colors (see LEVEL_PALETTE_INDEX) + .byte $00,$01,$22,$07 ; indexes into game_palettes specifying sprite palette colors (see LEVEL_PALETTE_INDEX) + .byte $0b ; section to stop scrolling at (+2) (level length) (see LEVEL_STOP_SCROLL) + .byte $00 ; specifies whether to check for bullet and/or weapon item solid bg collisions (LEVEL_SOLID_BG_COLLISION_CHECK) + .byte $00,$00,$00,$00,$00,$00 ; unused + +; header for level 2 - base 1 +level_2_header: + .byte $01 ; location type: indoor level (0 = outdoor ; 1 = indoor ; ff = indoor boss room) + .byte $00 ; outdoor scrolling type: not used for indoor level + .addr level_2_supertiles_screen_ptr_table ; pointer to which super-tiles are on each screen (bank 2 memory address) (see LEVEL_SCREEN_SUPERTILES_PTR) + .addr level_2_supertile_data ; composition of super-tiles (bank 3 memory address) (see LEVEL_SUPERTILE_DATA_PTR) + .addr level_2_palette_data ; super-tile palette data (bank 3 memory address) (see LEVEL_SUPERTILE_PALETTE_DATA) + .byte $04 ; alternate graphics loading (+2) (screen where they start loading) (see LEVEL_ALT_GRAPHICS_POS) + .byte $00,$ff,$ff ; tile collision limits (see COLLISION_CODE_X_TILE_INDEX) (all tiles are empty collision code for indoor/base levels) + .byte $24,$28,$29,$28 ; palette indexes into game_palettes for cycling the 4th nametable palette index (see LEVEL_PALETTE_CYCLE_INDEXES) + .byte $09,$0a,$04,$24 ; indexes into game_palettes specifying initial background tile palette colors (see LEVEL_PALETTE_INDEX) + .byte $00,$01,$22,$2a ; indexes into game_palettes specifying sprite palette colors (see LEVEL_PALETTE_INDEX) + .byte $05 ; section to stop scrolling at (+2) (level length) (see LEVEL_STOP_SCROLL) + .byte $00 ; specifies whether to check for bullet and/or weapon item solid bg collisions (LEVEL_SOLID_BG_COLLISION_CHECK) + .byte $00,$00,$00,$00,$00,$00 ; unused + +; header for level 3 - waterfall +level_3_header: + .byte $00 ; location type: outdoor level (0 = outdoor ; 1 = indoor ; ff = indoor boss room) (LEVEL_LOCATION_TYPE) + .byte $01 ; scrolling type: vertical scroll (0 = horizontal ; 1 = vertical) + .addr level_3_supertiles_screen_ptr_table ; pointer to which super-tiles are on each screen (bank 2 memory address) (see LEVEL_SCREEN_SUPERTILES_PTR) + .addr level_3_supertile_data ; composition of super-tiles (bank 3 memory address) (see LEVEL_SUPERTILE_DATA_PTR) + .addr level_3_palette_data ; super-tile palette data (bank 3 memory address) (see LEVEL_SUPERTILE_PALETTE_DATA) + .byte $07 ; alternate graphics loading (+2) (screen where they start loading) (see LEVEL_ALT_GRAPHICS_POS) + .byte $07,$ff,$ff ; tile collision limits (see COLLISION_CODE_X_TILE_INDEX) + .byte $0d,$0e,$0f,$00 ; palette indexes into game_palettes for cycling the 4th nametable palette index (see LEVEL_PALETTE_CYCLE_INDEXES) + .byte $0b,$0c,$04,$0d ; indexes into game_palettes specifying initial background tile palette colors (see LEVEL_PALETTE_INDEX) + .byte $00,$01,$22,$07 ; indexes into game_palettes specifying sprite palette colors (see LEVEL_PALETTE_INDEX) + .byte $07 ; section to stop scrolling at (+2) (level length) (see LEVEL_STOP_SCROLL) + .byte $00 ; specifies whether to check for bullet and/or weapon item solid bg collisions (LEVEL_SOLID_BG_COLLISION_CHECK) + .byte $00,$00,$00,$00,$00,$00 ; unused + +; header for level 4 - base 2 +level_4_header: + .byte $01 ; location type: indoor level (0 = outdoor ; 1 = indoor ; ff = indoor boss room) + .byte $00 ; outdoor scrolling type: not used for indoor level + .addr level_4_supertiles_screen_ptr_table ; pointer to which super-tiles are on each screen (bank 2 memory address) (see LEVEL_SCREEN_SUPERTILES_PTR) + .addr level_4_supertile_data ; composition of super-tiles (bank 3 memory address) (see LEVEL_SUPERTILE_DATA_PTR) (same as level 2) + .addr level_4_palette_data ; super-tile palette data (bank 3 memory address) (see LEVEL_SUPERTILE_PALETTE_DATA) (same as level 2) + .byte $07 ; alternate graphics loading (+2) (screen where they start loading) (see LEVEL_ALT_GRAPHICS_POS) + .byte $00,$ff,$ff ; tile collision limits (see COLLISION_CODE_X_TILE_INDEX) (all tiles are empty collision code for indoor/base levels) + .byte $2e,$2f,$30,$2f ; palette indexes into game_palettes for cycling the 4th nametable palette index (see LEVEL_PALETTE_CYCLE_INDEXES) + .byte $2c,$2d,$04,$2e ; indexes into game_palettes specifying initial background tile palette colors (see LEVEL_PALETTE_INDEX) + .byte $00,$01,$22,$2a ; indexes into game_palettes specifying sprite palette colors (see LEVEL_PALETTE_INDEX) + .byte $08 ; section to stop scrolling at (+2) (level length) (see LEVEL_STOP_SCROLL) + .byte $00 ; specifies whether to check for bullet and/or weapon item solid bg collisions (LEVEL_SOLID_BG_COLLISION_CHECK) + .byte $00,$00,$00,$00,$00,$00 ; unused + +; header for level 5 - snow field +level_5_header: + .byte $00 ; location type: outdoor level (0 = outdoor ; 1 = indoor ; ff = indoor boss room) (LEVEL_LOCATION_TYPE) + .byte $00 ; outdoor scrolling type: horizontal scroll (0 = horizontal ; 1 = vertical) + .addr level_5_supertiles_screen_ptr_table ; pointer to which super-tiles are on each screen (bank 2 memory address) (see LEVEL_SCREEN_SUPERTILES_PTR) + .addr level_5_supertile_data ; composition of super-tiles (bank 3 memory address) (see LEVEL_SUPERTILE_DATA_PTR) + .addr level_5_palette_data ; super-tile palette data (bank 3 memory address) (see LEVEL_SUPERTILE_PALETTE_DATA) + .byte $13 ; alternate graphics loading (+2) (screen where they start loading) (see LEVEL_ALT_GRAPHICS_POS) + .byte $20,$f0,$f0 ; tile collision limits (see COLLISION_CODE_X_TILE_INDEX) + .byte $62,$63,$62,$63 ; palette indexes into game_palettes for cycling the 4th nametable palette index (see LEVEL_PALETTE_CYCLE_INDEXES) + .byte $3d,$3e,$04,$62 ; indexes into game_palettes specifying initial background tile palette colors (see LEVEL_PALETTE_INDEX) + .byte $00,$01,$22,$07 ; indexes into game_palettes specifying sprite palette colors (see LEVEL_PALETTE_INDEX) + .byte $13 ; section to stop scrolling at (+2) (level length) (see LEVEL_STOP_SCROLL) + .byte $01 ; specifies whether to check for bullet and/or weapon item solid bg collisions (LEVEL_SOLID_BG_COLLISION_CHECK) + .byte $00,$00,$00,$00,$00,$00 ; unused + +; header for level 6 - energy zone +level_6_header: + .byte $00 ; location type: outdoor level (0 = outdoor ; 1 = indoor ; ff = indoor boss room) (LEVEL_LOCATION_TYPE) + .byte $00 ; outdoor scrolling type: horizontal scroll (0 = horizontal ; 1 = vertical) + .addr level_6_supertiles_screen_ptr_table ; pointer to which super-tiles are on each screen (bank 2 memory address) (see LEVEL_SCREEN_SUPERTILES_PTR) + .addr level_6_supertile_data ; composition of super-tiles (bank 3 memory address) (see LEVEL_SUPERTILE_DATA_PTR) + .addr level_6_palette_data ; super-tile palette data (bank 3 memory address) (see LEVEL_SUPERTILE_PALETTE_DATA) + .byte $0b ; alternate graphics loading (+2) (screen where they start loading) (see LEVEL_ALT_GRAPHICS_POS) + .byte $0c,$de,$de ; tile collision limits (see COLLISION_CODE_X_TILE_INDEX) + .byte $35,$36,$37,$38 ; palette indexes into game_palettes for cycling the 4th nametable palette index (see LEVEL_PALETTE_CYCLE_INDEXES) + .byte $33,$34,$04,$35 ; indexes into game_palettes specifying initial background tile palette colors (see LEVEL_PALETTE_INDEX) + .byte $00,$01,$22,$07 ; indexes into game_palettes specifying sprite palette colors (see LEVEL_PALETTE_INDEX) + .byte $0b ; section to stop scrolling at (+2) (level length) (see LEVEL_STOP_SCROLL) + .byte $81 ; specifies whether to check for bullet and/or weapon item solid bg collisions (LEVEL_SOLID_BG_COLLISION_CHECK) + .byte $00,$00,$00,$00,$00,$00 ; unused + +; header for level 7 - hangar +level_7_header: + .byte $00 ; location type: outdoor level (0 = outdoor ; 1 = indoor ; ff = indoor boss room) (LEVEL_LOCATION_TYPE) + .byte $00 ; outdoor scrolling type: horizontal scroll (0 = horizontal ; 1 = vertical) + .addr level_7_supertiles_screen_ptr_table ; pointer to which super-tiles are on each screen (bank 2 memory address) (see LEVEL_SCREEN_SUPERTILES_PTR) + .addr level_7_supertile_data ; composition of super-tiles (bank 3 memory address) (see LEVEL_SUPERTILE_DATA_PTR) + .addr level_7_palette_data ; super-tile palette data (bank 3 memory address) (see LEVEL_SUPERTILE_PALETTE_DATA) + .byte $0d ; alternate graphics loading (+2) (screen where they start loading) (see LEVEL_ALT_GRAPHICS_POS) + .byte $0e,$f1,$f1 ; tile collision limits (see COLLISION_CODE_X_TILE_INDEX) + .byte $47,$57,$47,$58 ; palette indexes into game_palettes for cycling the 4th nametable palette index (see LEVEL_PALETTE_CYCLE_INDEXES) + .byte $45,$46,$04,$47 ; indexes into game_palettes specifying initial background tile palette colors (see LEVEL_PALETTE_INDEX) + .byte $00,$01,$22,$07 ; indexes into game_palettes specifying sprite palette colors (see LEVEL_PALETTE_INDEX) + .byte $0d ; section to stop scrolling at (+2) (level length) (see LEVEL_STOP_SCROLL) + .byte $81 ; specifies whether to check for bullet and/or weapon item solid bg collisions (LEVEL_SOLID_BG_COLLISION_CHECK) + .byte $00,$00,$00,$00,$00,$00 ; unused + +; header for level 8 - alien's lair +level_8_header: + .byte $00 ; location type: outdoor level (0 = outdoor ; 1 = indoor ; ff = indoor boss room) (LEVEL_LOCATION_TYPE) + .byte $00 ; outdoor scrolling type: horizontal scroll (0 = horizontal ; 1 = vertical) + .addr level_8_supertiles_screen_ptr_table ; pointer to which super-tiles are on each screen (bank 2 memory address) (see LEVEL_SCREEN_SUPERTILES_PTR) + .addr level_8_supertile_data ; composition of super-tiles (bank 3 memory address) (see LEVEL_SUPERTILE_DATA_PTR) + .addr level_8_palette_data ; super-tile palette data (bank 3 memory address) (see LEVEL_SUPERTILE_PALETTE_DATA) + .byte $09 ; alternate graphics loading (+2) (screen where they start loading) (see LEVEL_ALT_GRAPHICS_POS) + .byte $05,$ef,$ef ; tile collision limits (see COLLISION_CODE_X_TILE_INDEX) + .byte $4c,$4d,$4e,$4f ; palette indexes into game_palettes for cycling the 4th nametable palette index (see LEVEL_PALETTE_CYCLE_INDEXES) + .byte $48,$49,$4a,$4c ; indexes into game_palettes specifying initial background tile palette colors (see LEVEL_PALETTE_INDEX) + .byte $00,$01,$43,$44 ; indexes into game_palettes specifying sprite palette colors (see LEVEL_PALETTE_INDEX) + .byte $09 ; section to stop scrolling at (+2) (level length) (see LEVEL_STOP_SCROLL) + .byte $00 ; specifies whether to check for bullet and/or weapon item solid bg collisions (LEVEL_SOLID_BG_COLLISION_CHECK) + .byte $00,$00,$00,$00,$00,$00 ; unused + +load_screen_enemy_data: + lda CURRENT_LEVEL ; current level + asl ; double since each entry in level_enemy_screen_ptr_ptr_tbl is 2 bytes + tay + lda level_enemy_screen_ptr_ptr_tbl,y ; levels enemy type table for level (low byte) + sta $08 + lda level_enemy_screen_ptr_ptr_tbl+1,y ; levels enemy type table for level (high byte) + sta $09 + lda LEVEL_SCREEN_NUMBER ; load current screen number within the level + asl ; double since each address is 2 bytes + tay + lda ($08),y ; load the low byte of the pointer to the screen enemy type data (level_x_enemy_screen_xx) + sta $0a + iny + lda ($08),y ; load the high byte of the pointer to the screen enemy type data (level_x_enemy_screen_xx) + sta $0b + lda LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor + beq load_enemy_outdoor_level ; branch for outdoor level to load enemies for current screen + jmp load_enemy_indoor_level ; branch for indoor level to load enemies for current screen + +; outdoor +load_enemy_outdoor_level: + ldy ENEMY_SCREEN_READ_OFFSET ; current read offset for screen enemy data + lda ($0a),y ; load first byte of screen enemy data (level_x_enemy_screen_xx) + cmp #$ff ; if #$ff no more data to read + beq load_screen_enemy_data_exit ; just read #$ff, exit + sta $0f ; store x position of enemy into $0f + and #$fe ; look at the last bit + sec ; set the carry flag in preparation for subtraction + sbc LEVEL_SCREEN_SCROLL_OFFSET ; subtract scrolling offset in pixels within screen, vertical scroll for indoor levels + beq @continue ; if the player is at the horizontal position specified, create enemy + bcs load_screen_enemy_data_exit ; exit if haven't reached position + eor #$ff ; past the horizontal position for enemy type flip all bits and add one to get how far past + adc #$01 + +@continue: + sta $0e ; store distance past the from enemy load position (usually #$0, but can be positive if player was past position) + ; this happens on the vertical level when jumping + iny + lda ($0a),y ; read the enemy type and the enemy repeat data + tax ; temporary save a so that we can pull out the ENEMY_TYPE + and #$3f ; keep the enemy type bits ..xx xxxx + sta $08 ; store enemy type into $08 + txa ; restore full value for a + rol + rol + rol ; previous 3 rol operations moved the high 2 bits to low to bits + and #$03 ; keep only the last 2 bits, which is the repeat value + sta $09 ; store the number of times to repeat the enemy in $09 + +; read the 3rd byte of the enemy triple, which is the y position and the enemy attribute data +; also used when enemies are repeated to load repeat enemy data y position +read_enemy_data_byte_3: + iny + lda ($0a),y ; load vertical offset and enemy attributes + sta $0c ; store vertical offset and enemy attributes into $0c + jsr find_next_enemy_slot ; find next available enemy slot, put result in x register + beq set_enemy_slot_data ; found enemy slot to use + lda $0f ; no enemy slot available, load enemy x position + lsr ; shift least significant bit to carry flat + bcc load_enemy_repeat_data ; if carry flag clear, jump + jsr find_bullet_slot + +; updates enemy slot based on CPU memory values $08, $0c, $0e, etc. +set_enemy_slot_data: + lda $08 + sta ENEMY_TYPE,x ; store current enemy type + jsr initialize_enemy ; initialize enemy attributes + lda $0c ; load vertical position and enemy attributes + and #$0f ; keep the least significant 4 bits (enemy attributes) + sta ENEMY_ATTRIBUTES,x ; store enemy attributes + lda LEVEL_SCROLLING_TYPE ; 0 = horizontal, indoor/base; 1 = vertical + beq @handle_horizontal ; handle horizontal level + lda $0c ; load vertical position and enemy attributes + and #$f0 ; keep most significant 4 bits xxxx .... + sta ENEMY_X_POS,x ; set enemy x position on screen + lda $0e + sta ENEMY_Y_POS,x ; enemy y position on screen + jmp load_enemy_repeat_data ; if enemy is repeated, handle that + +@handle_horizontal: + lda $0c + and #$f0 ; keep bits xxxx .... + sta ENEMY_Y_POS,x ; enemy y position on screen + lda #$f0 ; a = #$f0 + sec ; set carry flag in preparation for subtraction + sbc $0e + sta ENEMY_X_POS,x ; set enemy x position on screen + +load_enemy_repeat_data: + dec $09 ; decrement enemy repeat value + bpl read_enemy_data_byte_3 ; read y offset and attributes for next repetition + iny ; increment read offset + sty ENEMY_SCREEN_READ_OFFSET + +load_screen_enemy_data_exit: + rts + +; indoor +; input +; * $08 - 2 byte memory address pointing to level_enemy_screen_ptr_ptr_tbl at correct offset +; * $a0 - 2 byte memory address pointing to correct level_x_enemy_screen_ptr_tbl offset +; (e.g. level_x_enemy_screen_xx) for the current screen +load_enemy_indoor_level: + lda ENEMY_SCREEN_READ_OFFSET ; offset for enemy data + bne load_screen_enemy_data_exit + inc ENEMY_SCREEN_READ_OFFSET ; offset for enemy data + lda #$00 ; a = #$00 + sta INDOOR_ENEMY_ATTACK_COUNT ; clear the number of enemy 'rounds' of attack + sta WALL_PLATING_DESTROYED_COUNT ; clear the number of boss platings destroyed + sta INDOOR_RED_SOLDIER_CREATED ; clear flag indicating that a red jumping soldier has been generated + sta GRENADE_LAUNCHER_FLAG ; clear the flag indicating there is a grenade launcher on screen + jsr remove_all_enemies + ldy #$00 ; y = #$00 + lda ($0a),y ; read first byte of level_x_enemy_screen_xx + ; i.e. the number of cores to destroy to advance to the next room + cmp #$ff ; see if end of data marker + beq load_screen_enemy_data_exit ; exit if read all of the data + sta WALL_CORE_REMAINING ; set remaining cores to destroy for screen + ; for level 4 boss, used to count remaining boss gemini + cmp #$00 ; see if number of cores is #$00 (no indoor screens are configured this way) + bne @continue ; continue if there are cores to destroy before marking screen clear + lda #$01 ; !(UNUSED) every screen in contra has at least one core, so this code is never executed + sta INDOOR_SCREEN_CLEARED ; indoor screen cleared flag (0 = not cleared; 1 = cleared) + ; immediately marks screen as cleared + +@continue: + iny ; increment level_x_enemy_screen_xx read offset + ldx #$0f ; x = #$0f + +; loop through enemy data in level_x_enemy_screen_xx +@load_indoor_enemy: + lda ($0a),y ; load enemy position + cmp #$ff ; see if end of data byte + beq load_screen_enemy_data_exit ; exit if read all of the data + sta $0f ; store enemy position in $0f + iny ; increment enemy data read offset + lda ($0a),y ; load enemy type (and enemy pos adjustment flags) + sta $08 ; store in $08 + and #$3f ; strip bits 6 and 7 (pos adjustment flags) to get enemy type + sta ENEMY_TYPE,x ; set current enemy type + jsr initialize_enemy ; initialize enemy attributes + lda $0f ; load enemy position + and #$f0 ; keep high nibble (y position) + asl $08 ; see enemy y pos adjustment flag set + bcc @get_x_pos ; branch if no y adjustment + adc #$07 ; enemy y pos adjustment flag set, adjust y position by #$07 + +@get_x_pos: + sta ENEMY_Y_POS,x ; set enemy y position on screen + lda $0f ; load enemy position + asl + asl + asl + asl ; shift low nibble to high nibble + asl $08 ; see enemy x pos adjustment flag set + bcc @set_y_attrs_continue ; branch if no x adjustment + adc #$07 ; enemy x pos adjustment flag set, adjust y position by #$07 + +@set_y_attrs_continue: + sta ENEMY_X_POS,x ; set enemy x position on screen + iny ; increment enemy data read offset + lda ($0a),y ; load enemy attributes + sta ENEMY_ATTRIBUTES,x ; set enemy attributes + iny ; increment enemy data read offset + dex ; decrement enemy slot + bpl @load_indoor_enemy ; continue to load next enemy if slots available + rts + +; levels enemy groups pointers (#$8 * #$2 = #$10 bytes) +level_enemy_screen_ptr_ptr_tbl: + .addr level_1_enemy_screen_ptr_tbl ; CPU address $b82b + .addr level_2_enemy_screen_ptr_tbl ; CPU address $b8aa + .addr level_3_enemy_screen_ptr_tbl ; CPU address $b90d + .addr level_4_enemy_screen_ptr_tbl ; CPU address $b9af + .addr level_5_enemy_screen_ptr_tbl ; CPU address $ba48 + .addr level_6_enemy_screen_ptr_tbl ; CPU address $bb24 + .addr level_7_enemy_screen_ptr_tbl ; CPU address $bbb7 + .addr level_8_enemy_screen_ptr_tbl ; CPU address $bca9 + +exe_soldier_generation: + lda LEVEL_SCREEN_NUMBER ; load current screen number within the level + cmp SOLDIER_GEN_SCREEN ; compare it to the screen the soldiers are being generated for + beq @run_soldier_gen_routine ; already reset the SOLDIER_GEN_SCREEN memory, skip initialization + sta SOLDIER_GEN_SCREEN ; update current soldier generation screen number + lda #$00 ; a = #$00 + sta SCREEN_GEN_SOLDIERS ; init the number of soldiers that have been generated for the screen + +@run_soldier_gen_routine: + lda SOLDIER_GENERATION_ROUTINE ; soldier generation routine index + jsr run_routine_from_tbl_below ; run routine a in the following table (soldier_generation_ptr_tbl) + +; pointer table for soldier generation (#$3 * #$2 = #$6 bytes) +soldier_generation_ptr_tbl: + .addr soldier_generation_00 ; CPU address $b53d + .addr soldier_generation_01 ; CPU address $b581 + .addr soldier_generation_02 ; CPU address $b657 + +; determines if should generate, and if so sets SOLDIER_GENERATION_TIMER +soldier_generation_00: + jsr get_soldier_gen_timer_for_lvl ; get the initial timer for the level + beq soldier_generation_00_exit ; exit if timer is #$00, don't generate soldier (indoor/base levels) + jsr adjust_generation_timer ; update timer based on how many times player beat game and weapon strength + inc SOLDIER_GENERATION_ROUTINE ; set to soldier_generation_01 + +soldier_generation_00_exit: + rts + +; get level's initial value for the soldier generation timer and store in a +get_soldier_gen_timer_for_lvl: + lda CURRENT_LEVEL ; load current level + tay ; move current level into y + lda level_soldier_generation_timer,y ; load soldier generation timer for level + rts + +; update timer based on how many times player beat game and weapon strength +adjust_generation_timer: + jsr get_soldier_gen_timer_for_lvl + sta SOLDIER_GENERATION_TIMER ; soldier generation timer + ldy GAME_COMPLETION_COUNT ; load the number of times the game has been completed + beq @adjust_timer_for_weapon ; branch if haven't yet beat the game any times + tya + cmp #$04 ; compare GAME_COMPLETION_COUNT to #$04 + bcc @continue ; branch if GAME_COMPLETION_COUNT < #$04 + ldy #$03 ; maximum multiplier of increase chance for generating soldier is #$03 + +@continue: + lda #$28 ; lower soldier generation timer + jsr @adjust_timer ; subtract #$28 * GAME_COMPLETION_COUNT from SOLDIER_GENERATION_TIMER + +; lower timer based on how strong the player's weapon is (#05 * PLAYER_WEAPON_STRENGTH) +@adjust_timer_for_weapon: + ldy PLAYER_WEAPON_STRENGTH ; load player weapon strength (damage strength) + beq @exit ; don't generate if player's weapon strength is #$00 + lda #$05 ; subtract #$05 * PLAYER_WEAPON_STRENGTH from SOLDIER_GENERATION_TIMER + +; SOLDIER_GENERATION_TIMER = SOLDIER_GENERATION_TIMER - (a*y) +@adjust_timer: + sta $08 + lda SOLDIER_GENERATION_TIMER ; soldier generation timer + +@loop: + sec ; set carry flag in preparation for subtraction + sbc $08 ; SOLDIER_GENERATION_TIMER - $08 + bcc @exit ; exit when overflow + sta SOLDIER_GENERATION_TIMER + dey + bne @loop + +@exit: + rts + +; table for soldier generation initial timer values (#$8 bytes) +; current level value stored in SOLDIER_GENERATION_TIMER +; the lower the value, the quicker a soldier is generated +; #$36 = lots of soldiers +; #$08 = insane +; #$00 = no soldier generation (indoor/base levels, and alien's lair) +level_soldier_generation_timer: + .byte $90,$00,$d8,$00,$d0,$c8,$c0,$00 + +soldier_generation_01: + lda FRAME_COUNTER ; load frame counter + ror + bcc @continue ; every other frame look at the FRAME_SCROLL to see if should only subtract by #$01 + lda FRAME_SCROLL ; odd frame, load whether or not the screen is scrolling (#00 or #01) + bne @frame_scrolling ; branch if frame is scrolling + +@continue: + lda SOLDIER_GENERATION_TIMER ; soldier generation timer + sec ; set carry flag in preparation for subtraction + sbc #$02 + sta SOLDIER_GENERATION_TIMER ; soldier generation timer + bcc gen_soldier_find_pos + rts ; exit soldier_generation_01 + +; only subtract one from SOLDIER_GENERATION_TIMER when scrolling +@frame_scrolling: + dec SOLDIER_GENERATION_TIMER ; soldier generation timer + beq gen_soldier_find_pos ; timer is now #$00 + rts ; exit soldier_generation_01 + +; find the appropriate position to generate the soldier +gen_soldier_find_pos: + jsr adjust_generation_timer ; reset the soldier generation timer for later + lda FRAME_COUNTER ; load frame counter + adc RANDOM_NUM ; add random number to frame counter + and #$01 ; just care about if result is even or odd + tay + lda SPRITE_Y_POS,y ; get player 1 or player 2's Y position + bne @search_for_position ; branch if player sprite exists + tya ; no player sprite (probably player 2), transfer y to a + eor #$01 ; flip least significant bit (0 to 1, or 1 to 0) + lda SPRITE_Y_POS,y ; reload #$00, !(BUG?) + ; the developers may have meant to do a tay before this instruction + ; to switch to use the other player's y position, but since + ; they didn't, the same #$00 is read + +@search_for_position: + sta $08 ; store player y position in $08 + sta $0a ; set stopping y position of search + lda FRAME_COUNTER ; load frame counter + and #$03 ; keep bits .... ..xx + tay + beq top_down_find_gen_soldier_pos ; 1/3 chance to start with y = #$00 (top) + dey + beq bottom_up_find_gen_soldier_pos ; 1/3 chance to start with y = #$f0 (bottom) + jsr get_x_pos_check_bg_collision ; 1/3 chance to start with y = player y position (then search up) + +; look at every #$10 pixels to see if can place a soldier at position +; start at player y position and move up +@find_y_pos: + lda $08 ; load previous soldier generation test y position + sec ; set carry flag in preparation for subtraction + sbc #$10 ; subtract #$10 pixels from soldier generation test y position (move up) + sta $08 ; update soldier generation test y position + cmp $0a ; compare to the player y position + beq gen_soldier_find_pos_exit ; exit if reached player y position (wrapped around screen) + jsr check_gen_soldier_bg_collision ; check if soldier can be generated at location + bcc @find_y_pos ; didn't have a ground. loop to next y test position. + +gen_soldier_find_pos_exit: + rts + +; look at every #$10 pixels to see if can place a soldier at position +; start at top of screen down until player y position +top_down_find_gen_soldier_pos: + lda #$00 ; a = #$00 + sta $08 ; set start y position (top of screen) + sta $0a ; set stopping y position of search + jsr get_x_pos_check_bg_collision ; determine the x position (don't bother with carry flag result) + +; look at every #$10 pixels to see if can place a soldier at position +; start at the top of the screen down to bottom +@find_y_pos: + lda $08 ; load previous soldier generation test y position + clc ; clear carry in preparation for addition + adc #$10 ; add #$10 pixels from soldier generation test y position (move down) + sta $08 ; update soldier generation test y position + cmp $0a ; compare to the top of the screen #$00 + beq gen_soldier_find_pos_exit ; exit if reached top of screen (wrapped around) + jsr check_gen_soldier_bg_collision ; check if soldier can be generated at location + bcc @find_y_pos ; didn't have a ground. loop to next y test position. + bcs gen_soldier_find_pos_exit ; always jump, found ground and possibly generated soldier + +; look at every #$10 pixels to see if can place a soldier at position +; start at the bottom of the screen up to top +bottom_up_find_gen_soldier_pos: + lda #$f0 ; a = #$f0 + sta $08 ; set start y position (bottom of screen) + sta $0a ; set stopping position of search + jsr get_x_pos_check_bg_collision ; determine the x position (don't bother with carry flag result) + +@find_y_pos: + lda $08 ; load previous soldier generation test y position + sec ; set carry flag in preparation for subtraction + sbc #$10 ; subtract #$10 pixels from soldier generation test y position (move up) + sta $08 ; update soldier generation test y position + cmp $0a ; compare to the bottom of the screen #$00 + beq gen_soldier_find_pos_exit ; exit if reached bottom of screen (wrapped around) + jsr check_gen_soldier_bg_collision ; check if soldier can be generated at location + bcc @find_y_pos ; didn't have a ground. loop to next y test position. + bcs gen_soldier_find_pos_exit ; always jump, found ground and possibly generated soldier + +; determine appropriate x position for soldier to generate and then +; see if there is a ground collision +get_x_pos_check_bg_collision: + lda FRAME_COUNTER ; load frame counter + adc RANDOM_NUM ; a = FRAME_COUNTER + RANDOM_NUM + and #$0f ; keep low nibble of FRAME_COUNTER + RANDOM_NUM + tay + lda gen_soldier_initial_x_pos,y ; load possible initial x position of generated soldier + ldy GAME_COMPLETION_COUNT ; load the number of times the game has been completed + bne @check_bg_collision ; player(s) have completed game at least once + ldy CURRENT_LEVEL ; current level + bne @check_bg_collision ; branch if not the first level + pha ; push gen_soldier_initial_x_pos value to stack + lda SCREEN_GEN_SOLDIERS ; load the number of soldiers that have been generated for the level + cmp #$1e + bcs @pop_x_pos_check_bg_collision ; branch if the number of soldiers generated for frame >= #$1e + pla ; pop a from stack, not going to use gen_soldier_initial_x_pos value + lda #$fc ; set soldier generation test x position to right edge of screen + bne @check_bg_collision ; always branch + +@pop_x_pos_check_bg_collision: + pla ; pop a from stack + +@check_bg_collision: + sta $09 ; put gen_soldier_initial_x_pos value, or #$fc into $09 + +check_gen_soldier_bg_collision: + lda $09 ; generated soldier possible x position + ldy $08 ; generated soldier possible y position + jsr get_bg_collision ; determine player background collision code at position (a,y) + bcs @set_pos_adv_routine ; floor collision, mark that soldier can be generated + rts ; exit soldier_generation_01 + +; soldier background collision +@set_pos_adv_routine: + lda $09 ; load generated soldier x position + sta SOLDIER_GENERATION_X_POS ; store generated soldier initial x position + lda $08 ; load generated soldier initial y position + sec ; set carry flag in preparation for subtraction + sbc #$10 ; subtract #$10 from generated soldier initial y position + sta SOLDIER_GENERATION_Y_POS ; store initial y position + lda SOLDIER_GENERATION_ROUTINE + cmp #$02 ; see if routine is set to soldier_generation_02 + beq @mark_and_exit ; see if still on soldier_generation_02 + ; check_gen_soldier_bg_collision is called initially and the result + ; is ignored. Don't want to advance routine multiple times + inc SOLDIER_GENERATION_ROUTINE ; move to soldier_generation_02 + +@mark_and_exit: + sec ; set carry flag, mark that a soldier can be generated at position + rts + +; table for possible initial x positions of generated soldiers (#$10 bytes) +; always either left edge (#$0a) or right edge (#$fa) +; level 1 doesn't use this table until at least #$1e (36 decimal) enemies have been +; generated for the screen. Instead soldiers always come from the right. +; Presumably this is to make the game easier in the beginning. +gen_soldier_initial_x_pos: + .byte $fa,$0a,$fa,$fa,$0a,$fa,$0a,$fa,$0a,$0a,$0a,$fa,$fa,$0a,$0a,$fa + +soldier_generation_02: + lda CURRENT_LEVEL ; current level + cmp #$02 ; compare current level to #$02 (level 3 - waterfall) + beq generate_soldier ; don't prevent top %25 from generating soldiers for level 3 waterfall (vertical level) + lda SOLDIER_GENERATION_Y_POS ; load soldier initial y position + cmp #$40 ; compare to 25% of the vertical space, don't generate top 25% of screen + bcs generate_soldier ; branch if in bottom 3/4 of the screen + +soldier_gen_exit_far: + jmp soldier_gen_exit + +generate_soldier: + lda SOLDIER_GENERATION_Y_POS ; generated soldier initial y position on screen + cmp #$e0 ; compare y position to bottom extremity of screen + bcs soldier_gen_exit_far ; exit if soldier generation position is too low + lda ENEMY_ATTACK_FLAG ; see if enemies should attack + beq soldier_gen_exit_far ; don't generate soldier if ENEMY_ATTACK_FLAG is #$00 + lda CURRENT_LEVEL ; current level + cmp #$04 ; check if level 5 (snow field) + bne @continue ; check if in the last screens of level 5 (snow field) + lda LEVEL_SCREEN_NUMBER ; load current screen number within the level + cmp #$11 ; screen #$11 of snow field + bcc @continue ; check for last screen of snow field + jsr get_soldier_gen_screen_side ; see which horizontal half of the screen soldier is being generated on + bcc soldier_gen_exit_far ; exit if from left half. don't generate soldiers from left + +@continue: + lda SOLDIER_GENERATION_X_POS ; load generated soldier initial x position + ldy SOLDIER_GENERATION_Y_POS ; load generated soldier initial y position + jsr get_bg_collision_far ; determine player background collision code at position (a,y) + bmi soldier_gen_exit_far ; exit if collision with solid object (#$80) + jsr get_soldier_gen_screen_side ; see which horizontal half of the screen soldier is being generated on + bcc @load_gen_soldier_attr ; branch if generating on left half of the screen + sbc #$10 ; soldier generation on right half of screen, subtract #$10 from x position + +@load_gen_soldier_attr: + clc ; clear carry in preparation for addition + adc #$08 ; subtract #$08 from soldier generation x position + ldy SOLDIER_GENERATION_Y_POS + jsr get_bg_collision_far ; determine player background collision code at position (a,y) + bmi soldier_gen_exit_far ; branch if collision with solid object + jsr find_next_enemy_slot_6_to_0 ; find next available enemy slot (0-6), put result in x register + bne soldier_gen_exit_far ; branch if no slot was found + lda CURRENT_LEVEL ; current level + asl + tay + lda soldier_level_attributes_ptr_tbl,y ; load the low byte of the address + sta $08 + lda soldier_level_attributes_ptr_tbl+1,y ; load the high byte of the address + sta $09 + lda LEVEL_SCREEN_NUMBER ; load current screen number within the level + beq soldier_gen_exit_far ; first screen never has enemies, skip + tay + dey ; subtract 1 since there is no screen 0 enemy + lda ($08),y ; load the yth screen's soldier's behavior + cmp #$ff ; see if #$ff, which means don't generate soldiers for screen + beq soldier_gen_exit_far ; exit + and #$80 ; keep bits msb + beq @cont_load_gen_soldier_attr ; branch if msb is 0 + lda FRAME_COUNTER ; when bit 7 is set there is a 50% probability of no generation + ror + bcc @cont_load_gen_soldier_attr ; branch if even frame number + bcs soldier_gen_exit ; don't generate if odd frame + +@cont_load_gen_soldier_attr: + lda ($08),y ; reload soldier generation attribute byte for screen + and #$40 ; keep bit 6. When bit 6 set there is a 25% probability of no generation + beq @gen_soldier ; bit 6 not set, continue + lda FRAME_COUNTER ; load frame counter + and #$03 ; keep bits .... ..xx + beq @gen_soldier ; if bit 0 or bit 1 is not 0, then don't generate soldier (25% chance) + bne soldier_gen_exit + +@gen_soldier: + lda SCREEN_GEN_SOLDIERS + cmp #$1e + bcs init_and_generate_soldier ; branch if more than #$1e (30 dec) soldiers have already been generated on screen + lda GAME_COMPLETION_COUNT ; load the number of times the game has been completed + bne init_and_generate_soldier ; branch if player(s) have completed game at least once + ; this skips preventing a soldier from being generated near a player + ; at the edge of the screen + jmp player_edge_check ; player(s) haven't completed game and less than #$1e soldiers + ; have been generated for screen, so jump + +init_and_generate_soldier: + lda CURRENT_LEVEL ; current level + cmp #$02 ; compare level to level 3 (waterfall) + beq @init_generated_soldier ; initialize generated soldier without checking FRAME_SCROLL for waterfall + lda FRAME_SCROLL ; how much to scroll the screen (#00 - no scroll) + beq @init_generated_soldier ; initialize generated soldier if not scrolling + lda RANDOM_NUM ; load a random number + and #$03 ; keep bits .... ..xx + beq create_default_soldiers ; 1/3 chance of branching to create a soldier for each player + +; initializes the ENEMY_TYPE to #$05 +; initializes ENEMY_ATTRIBUTES based on slightly random value from gen_soldier_initial_attr_tbl +@init_generated_soldier: + lda #$05 ; a = #$05 (soldier) + sta ENEMY_TYPE,x ; set current enemy type to soldier + jsr initialize_enemy ; initialize enemy attributes + lda ($08),y ; load soldier_level_attributes_xx (attributes for current level soldiers) + and #$3f ; strip bits 6 and 7 (soldier generation probably bits) + asl + asl + sta $0a ; start to determine random position into gen_soldier_initial_attr_tbl to load + lda FRAME_COUNTER ; load frame counter + and #$03 ; keep bits .... ..xx + clc ; clear carry in preparation for addition + adc $0a ; randomly add a value from 0 to 3 to soldier_level_attributes_xx value + tay ; move to be gen_soldier_initial_attr_tbl index + lda gen_soldier_initial_attr_tbl,y ; load initial ENEMY_ATTRIBUTES value for generated soldier + sta ENEMY_ATTRIBUTES,x + lda RANDOM_NUM ; load random number + adc FRAME_COUNTER ; add to the frame counter + and #$02 ; keep bits .... ..x. + beq @set_gen_soldier_pos_and_dir ; randomly skip setting how soldiers handle ledges to 1 + ora ENEMY_ATTRIBUTES,x ; set bit 1 of ENEMY_ATTRIBUTES (ledge handling behavior) + sta ENEMY_ATTRIBUTES,x ; save new ENEMY_ATTRIBUTES value + +@set_gen_soldier_pos_and_dir: + lda SOLDIER_GENERATION_Y_POS ; load the computed y position of the generated soldier + sta ENEMY_Y_POS,x ; copy the computed generated soldier y position to the enemy y position + jsr get_soldier_gen_screen_side ; see which horizontal half of the screen soldier is being generated on + sta ENEMY_X_POS,x ; copy the computed generated soldier x position to the enemy x position + bcs @inc_soldier_cnt_exit ; branch if right horizontal half of the screen + inc ENEMY_ATTRIBUTES,x ; left half of the screen, set soldier direction to run towards the right + +@inc_soldier_cnt_exit: + inc SCREEN_GEN_SOLDIERS ; increment the total number of generated soldiers for the current screen + +soldier_gen_exit: + lda #$00 ; a = #$00 + sta SOLDIER_GENERATION_ROUTINE ; reset soldier generation routine index to reset SOLDIER_GENERATION_TIMER + rts + +; compares SOLDIER_GENERATION_X_POS to the middle of the screen (#$80) +; clears carry if on left half of the screen, sets carry for right half of screen +get_soldier_gen_screen_side: + lda SOLDIER_GENERATION_X_POS ; generated soldier initial x position on screen + cmp #$80 + rts + +; 1/3 chance of happening on a screen with generating soldiers +; generates a 'default' soldier for each player, i.e. not using gen_soldier_initial_attr_tbl +create_default_soldiers: + lda #$00 ; a = #$00 + sta $07 ; initial soldier x direction is to the left + jsr get_soldier_gen_screen_side ; see which horizontal half of the screen soldier is being generated on + bcs @create_soldier ; branch if soldier is generated from the right side + inc $07 ; generated soldier generated from left side, have them run to the right + +@create_soldier: + ldy #$02 ; y = #$02 + +@loop: + inc $06 ; increment ENEMY_ATTRIBUTES value + tya ; save y in stack so it can be restored after logic + pha ; push a to the stack + jsr find_next_enemy_slot_6_to_0 ; find next available enemy slot, put result in x register + bne @pop_a_soldier_gen_exit ; can't generate soldier, clean up pushed a from stack and exit + lda #$05 ; a = #$05 (soldier) + sta ENEMY_TYPE,x ; set current enemy type to soldier + jsr initialize_enemy ; initialize enemy attributes + jsr @set_enemy_pos ; set SOLDIER_GENERATION_X_POS to ENEMY_POS_X and SOLDIER_GENERATION_Y_POS to ENEMY_POS_Y + lda $06 + and #$02 ; keep bits .... ..x. + sta ENEMY_ATTRIBUTES,x + pla ; pop a from stack to restore y + tay ; restore y value + asl + asl + asl + asl + clc ; clear carry in preparation for addition + adc ENEMY_ATTRIBUTES,x ; + ora $07 ; set soldier x direction + sta ENEMY_ATTRIBUTES,x ; save soldier direction back in ENEMY_ATTRIBUTES + jsr @set_enemy_pos ; seems redundant, already was set above + dey ; move to player 1 + bmi soldier_gen_exit ; finished player loop, exit + bpl @loop + +@pop_a_soldier_gen_exit: + pla + jmp soldier_gen_exit + +@set_enemy_pos: + lda SOLDIER_GENERATION_Y_POS ; generated soldier initial y position on screen + sta ENEMY_Y_POS,x ; enemy y position on screen + lda SOLDIER_GENERATION_X_POS ; generated soldier initial x position on screen + sta ENEMY_X_POS,x ; set enemy x position on screen + rts + +; player(s) haven't completed game and less than #$1e soldiers +; see if player is close to the edge where the soldier will be generated +; if so, don't generate soldier +player_edge_check: + tya ; need to save current value of y temporarily to restore after method + pha ; temporarily save current value of a to stack + ldy #$01 ; set y to start with player 2 for gen_soldier_right_side + jsr get_soldier_gen_screen_side ; see which horizontal half of the screen soldier is being generated on + bcs gen_soldier_right_side ; branch if soldier is to be generated on right half of the screen + +; soldier to be generated from left side of screen +@player_loop: + lda P1_GAME_OVER_STATUS,y ; game over state (1 = game over) + bne @move_next_player ; skip player if in game over state + lda SPRITE_X_POS,y ; player x position on screen + cmp #$40 ; left 25% of screen + bcc restore_y_soldier_gen_exit ; exit if player is close to the left + ; where the soldier would have been generated + +@move_next_player: + dey + bpl @player_loop + +restore_y_init_and_generate_soldier: + pla ; pop value a from stack + tay ; restore value of y from before jmp to player_edge_check + jmp init_and_generate_soldier + +restore_y_soldier_gen_exit: + pla + tay + jmp soldier_gen_exit + +; check players GAME_OVER_STATUS +; generated soldier on the right half of the screen +gen_soldier_right_side: + lda P1_GAME_OVER_STATUS,y ; game over state (1 = game over) + bne @next_player ; branch if game over for player + lda SPRITE_X_POS,y ; load player's x position + cmp #$c0 ; compare x position to 3/4 (75%) of the horizontal screen + bcs restore_y_soldier_gen_exit ; exit if player is close to the right + ; where the soldier would have been generated + +@next_player: + dey + bpl gen_soldier_right_side ; check player 1's game over status + bmi restore_y_init_and_generate_soldier + +; pointer table for soldier random generation (#$8 * #$2 = #$10 bytes) +soldier_level_attributes_ptr_tbl: + .addr soldier_level_attributes_00 ; CPU address $b7cb + .addr soldier_level_attributes_00 ; CPU address $b7cb + .addr soldier_level_attributes_01 ; CPU address $b7d7 + .addr soldier_level_attributes_00 ; CPU address $b7cb + .addr soldier_level_attributes_02 ; CPU address $b7e0 + .addr soldier_level_attributes_03 ; CPU address $b7f4 + .addr soldier_level_attributes_04 ; CPU address $b800 + .addr soldier_level_attributes_00 ; CPU address $b7cb + +; related to soldier generation according to level and screen number +; each byte represents a screen of a level and how the soldier's will +; be generated on that screen +; #$ff = don't generate soldier +; #$80 = random right/left, no shooting, freq. 1 on screen +; #$40 = random right/left, no shooting, freq. 50% of 80 +; #$00 = random right/left, no shooting, freq. 1 per 1.5s +; #$01 = random right/left, shoot 1-2 bullets ratio 75-25, freq. like 00 +; #$02 = random right/left, shoot 1-2 bullets ratio 67-33, freq. like 00 +; #$03 = random right/left, shoot 1-2 bullets ratio 67-33, freq. like 00 + +; #$c bytes +soldier_level_attributes_00: + .byte $80,$80,$80,$80,$80,$80,$80,$40,$40,$80,$ff,$ff + +; #$9 bytes +soldier_level_attributes_01: + .byte $00,$00,$00,$01,$00,$ff,$01,$ff,$ff + +; #$14 bytes +soldier_level_attributes_02: + .byte $01,$02,$03,$04,$03,$03,$03,$02,$ff,$04,$02,$03,$ff,$02,$03,$04 + .byte $02,$ff,$ff,$ff + +; #$c bytes +soldier_level_attributes_03: + .byte $00,$05,$02,$ff,$ff,$80,$05,$03,$82,$ff,$ff,$ff + +; #$f bytes +soldier_level_attributes_04: + .byte $80,$05,$06,$80,$80,$05,$07,$80,$80,$04,$ff,$04,$04,$ff,$ff + +; table for generated soldiers initial ENEMY_ATTRIBUTES value (#$1c bytes) +; bit 0 = running direction - 0 is left, 1 is right +; bit 1 = affects whether the soldier turns around on ledges ? +; bit 2 = whether or not the enemy shoots bullets +gen_soldier_initial_attr_tbl: + .byte $00,$00,$00,$00 + .byte $00,$00,$00,$04 + .byte $00,$00,$04,$04 + .byte $00,$04,$04,$04 + .byte $04,$04,$04,$04 + .byte $00,$00,$00,$08 + .byte $00,$00,$04,$08 + +; pointer table for level 1 enemy groups (d * 2 = 1a bytes) +level_1_enemy_screen_ptr_tbl: + .addr level_1_enemy_screen_00 ; CPU address $b845 + .addr level_1_enemy_screen_01 ; CPU address $b858 + .addr level_1_enemy_screen_02 ; CPU address $b85c + .addr level_1_enemy_screen_03 ; CPU address $b860 + .addr level_1_enemy_screen_04 ; CPU address $b864 + .addr level_1_enemy_screen_05 ; CPU address $b871 + .addr level_1_enemy_screen_06 ; CPU address $b87b + .addr level_1_enemy_screen_07 ; CPU address $b87f + .addr level_1_enemy_screen_08 ; CPU address $b886 + .addr level_1_enemy_screen_09 ; CPU address $b88d + .addr level_1_enemy_screen_0a ; CPU address $b895 + .addr level_1_enemy_screen_0b ; CPU address $b899 + .addr level_1_enemy_screen_0c ; CPU address $b8a9 + +; enemy format +; +; xx tt yy +; +; xx = x position +; xxxxxxxx x position +; +; tt = enemy type + repeat +; xx.. .... repeat +; ..xx xxxx enemy type +; +; yy = y position + attributes +; xxxx x... y position +; .... .xxx attributes +level_1_enemy_screen_00: + .byte $10,$05,$60 ; soldier - runs left, doesn't shoot + .byte $40,$05,$60 ; soldier - runs left, doesn't shoot + .byte $50,$06,$c0 ; sniper - standing, shoots once per attack + .byte $60,$02,$a1 ; pill box sensor - machine gun inside + .byte $80,$05,$60 ; soldier - runs left, doesn't shoot + .byte $f0,$03,$40 ; flying capsule - rapid fire + .byte $ff + +level_1_enemy_screen_01: + .byte $90,$06,$c0 ; sniper - standing, shoots one bullet at a time + .byte $ff + +level_1_enemy_screen_02: + .byte $20,$12,$80 ; exploding bridge + .byte $ff + +level_1_enemy_screen_03: + .byte $40,$12,$80 ; exploding bridge + .byte $ff + +level_1_enemy_screen_04: + .byte $00,$04,$a0 ; rotating gun, shoots once per attack + .byte $10,$06,$60 ; sniper - standing, shoots once per attack + .byte $50,$06,$61 ; sniper - crouching, shoots once per attack + .byte $60,$03,$43 ; flying capsule - spray gun + .byte $ff + +level_1_enemy_screen_05: + .byte $20,$06,$41 ; sniper (enemy type #$06), attribute: 001 (crouch and shoot one bullet at a time), location: (#$20, #$40) + .byte $40,$02,$a2 ; pill box sensor (enemy type #$02), attribute: 010 (F), location: (#$40, #$a0) + .byte $80,$04,$80 ; rotating gun (enemy type #$04), attribute: 000, location: (#$80, #$80) + .byte $ff + +level_1_enemy_screen_06: + .byte $40,$04,$80 ; rotating gun (enemy type #$04), attribute: 000, location: (#$40, #$80) + .byte $ff + +level_1_enemy_screen_07: + .byte $20,$07,$a0 ; red turret (enemy type #$07), attribute: 000, location: (#$20, #$a0) + .byte $a0,$07,$41 ; red turret (enemy type #$07), attribute: 001, location: (#$a0, #$40) + .byte $ff + +level_1_enemy_screen_08: + .byte $00,$02,$c3 ; pill box sensor (enemy type #$02), attribute: 011 (S), location: (#$00, #$c0) + .byte $50,$06,$80 ; sniper (enemy type #$06), attribute: 000 (stand shoot bullets 3 at a time), location: (#$50, #$80) + .byte $ff + +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 + +level_1_enemy_screen_0a: + .byte $c0,$04,$c0 ; rotating gun (enemy type #$04), attribute: 000, location: (#$c0, #$c0) + .byte $ff + +level_1_enemy_screen_0b: + .byte $40,$04,$c3 ; rotating gun (enemy type #$04), attribute: 011, location: (#$40, #$c0) + .byte $a8,$10,$81 ; bomb turret (enemy type #$10), attribute: 001, location: (#$a8, #$80) + .byte $b1,$11,$b0 ; plated door (enemy type #$11), attribute: 000, location: (#$b1, #$b0) + .byte $b4,$06,$52 ; sniper (enemy type #$06), attribute: 010 (boss screen sniper), location: (#$b4, #$50) + .byte $c0,$10,$80 ; bomb turret (enemy type #$10), attribute: 000, location: (#$c0, #$80) + .byte $ff + +level_1_enemy_screen_0c: + .byte $ff + +; pointer table for level 2 enemy groups (#$6 * #$2 = #$c bytes) +level_2_enemy_screen_ptr_tbl: + .addr level_2_enemy_screen_00 ; CPU address $b8b6 + .addr level_2_enemy_screen_01 ; CPU address $b8be + .addr level_2_enemy_screen_02 ; CPU address $b8c9 + .addr level_2_enemy_screen_03 ; CPU address $b8d7 + .addr level_2_enemy_screen_04 ; CPU address $b8e5 + .addr level_2_enemy_screen_05 ; CPU address $b8f6 + +; level 2 enemy data +; first byte specifies number of wall cores to destroy +; then enemies are described +; byte 0 = location of object +; * xxxx .... - y position * #$10 +; * .... xxxx - x position * #$10 +; byte 1 = enemy type and position adjustment +; * x... .... - y position + #$07 +; * .x.. .... - x position + #$07 +; * ..xx xxxx object type +; byte 2 = enemy attributes +; * xxxx .... - y position of enemy +; * .... xxxx - x position of enemy +level_2_enemy_screen_00: + .byte $01 ; number of cores to destroy to advance to next room, if set to #$00 no electric barrier, can't advance + .byte $11,$19,$00 ; enemy type #$19 (indoor soldier generator) + .byte $68,$94,$03 ; enemy type #$14 (core), not plated, #$f0 opening delay + .byte $ff + +level_2_enemy_screen_01: + .byte $01 ; number of cores to destroy to advance to next room + .byte $11,$59,$00 ; enemy type #$19 (indoor soldier generator) + .byte $66,$d4,$03 ; enemy type #$14 (core), not plated, #$f0 opening delay + .byte $69,$d3,$00 ; enemy type #$13 (wall turret) + .byte $ff + +level_2_enemy_screen_02: + .byte $01 ; number of cores to destroy to advance to next room + .byte $11,$59,$00 ; enemy type #$19 (indoor soldier generator) + .byte $78,$14,$03 ; enemy type #$14 (core), not plated, #$f0 opening delay + .byte $66,$d3,$00 ; enemy type #$13 (wall turret) + .byte $69,$d3,$00 ; enemy type #$13 (wall turret) + .byte $ff + +level_2_enemy_screen_03: + .byte $01 ; number of cores to destroy to advance to next room + .byte $11,$59,$00 ; enemy type #$19 (indoor soldier generator) + .byte $11,$1a,$00 ; enemy type #$1a (indoor roller generator) + .byte $58,$93,$00 ; enemy type #$13 (wall turret) + .byte $68,$94,$03 ; enemy type #$14 (core), not plated, #$f0 opening delay + .byte $ff + +level_2_enemy_screen_04: + .byte $01 ; number of cores to destroy to advance to next room + .byte $11,$59,$00 ; enemy type #$19 (indoor soldier generator) + .byte $68,$94,$0b ; enemy type #$14 (core), larger sized core + .byte $66,$d3,$00 ; enemy type #$13 (wall turret) + .byte $69,$d3,$00 ; enemy type #$13 (wall turret) + .byte $58,$13,$00 ; enemy type #$13 (wall turret) + .byte $ff + +level_2_enemy_screen_05: + .byte $01 ; number of cores to destroy to advance to next room + .byte $48,$10,$00 ; enemy type #$10 (boss eye) + .byte $65,$08,$00 ; enemy type #$08 (wall cannon) + .byte $68,$0a,$01 ; enemy type #$0a (wall plating) + .byte $6b,$08,$00 ; enemy type #$08 (wall cannon) + .byte $95,$0a,$00 ; enemy type #$0a (wall plating) + .byte $98,$0a,$00 ; enemy type #$0a (wall plating) + .byte $9b,$0a,$00 ; enemy type #$0a (wall plating) + .byte $ff + +; pointer table for level 3 enemy groups (#$a * #$2 = #$14 bytes) +; xx = x position +; xxxxxxxx x position +; +; tt = enemy type + repeat +; xx...... repeat +; ..xxxxxx enemy type +; +; yy = y position + attributes +; xxxxx... y position +; .....xxx attributes +level_3_enemy_screen_ptr_tbl: + .addr level_3_enemy_screen_00 ; CPU address $b921 + .addr level_3_enemy_screen_01 ; CPU address $b941 + .addr level_3_enemy_screen_02 ; CPU address $b948 + .addr level_3_enemy_screen_03 ; CPU address $b95a + .addr level_3_enemy_screen_04 ; CPU address $b96a + .addr level_3_enemy_screen_05 ; CPU address $b984 + .addr level_3_enemy_screen_06 ; CPU address $b994 + .addr level_3_enemy_screen_07 ; CPU address $b9a4 (boss) + .addr level_3_enemy_screen_08 ; CPU address $b9ae (no enemy) + .addr level_3_enemy_screen_08 ; CPU address $b9ae (no enemy) + +; level 3 enemy data - section 1 (#$20 bytes) +level_3_enemy_screen_00: + .byte $08,$05,$21 ; soldier (enemy type #$05), attribute: 001, location: (#$08, #$20) + .byte $22,$12,$35 ; rock cave (enemy type #$12), attribute: 101, location: (#$22, #$30) + .byte $40,$02,$92 ; pill box sensor (enemy type #$02), attribute: 010 (F), location: (#$40, #$90) + .byte $48,$05,$23 ; soldier (enemy type #$05), attribute: 011, location: (#$48, #$20) + .byte $62,$12,$d1 ; rock cave (enemy type #$12), attribute: 001, location: (#$62, #$d0) + .byte $68,$45,$33,$96 ; soldier (enemy type #$05), attribute: 011, location: (#$68, #$30) + ; repeat: 1 [(y = #$90, attr = 110)] + .byte $82,$12,$b5 ; rock cave (enemy type #$12), attribute: 101, location: (#$82, #$b0) + .byte $a2,$12,$55 ; rock cave (enemy type #$12), attribute: 101, location: (#$a2, #$50) + .byte $c0,$02,$94 ; pill box sensor (enemy type #$02), attribute: 100 (L), location: (#$c0, #$90) + .byte $e2,$12,$95 ; rock cave (enemy type #$12), attribute: 101, location: (#$e2, #$90) + .byte $ff + +level_3_enemy_screen_01: + .byte $38,$06,$e0 ; sniper (enemy type #$06), attribute: 000 (stand shoot bullets 3 at a time), location: (#$38, #$e0) + .byte $60,$0c,$a0 ; scuba diver (enemy type #$0c), attribute: 000, location: (#$60, #$a0) + .byte $ff + +level_3_enemy_screen_02: + .byte $40,$04,$f5 ; rotating gun (enemy type #$04), attribute: 101, location: (#$40, #$f0) + .byte $73,$51,$53,$b2 ; moving flame (enemy type #$11), attribute: 011, location: (#$73, #$50), repeat: 1 [(y = #$b0, attr = 010)] + .byte $80,$06,$e0 ; sniper (enemy type #$06), attribute: 000 (stand shoot bullets 3 at a time), location: (#$80, #$e0) + .byte $d0,$43,$35,$a0 ; flying capsule (enemy type #$03), attribute: 101 (B), location: (#$d0, #$30), repeat: 1 [(y = #$a0, attr = 000)] + .byte $e0,$04,$10 ; rotating gun (enemy type #$04), attribute: 000, location: (#$e0, #$10) + .byte $ff + +level_3_enemy_screen_03: + .byte $11,$10,$50 ; floating rock (enemy type #$10), attribute: 000, location: (#$11, #$50) + .byte $20,$06,$20 ; sniper (enemy type #$06), attribute: 000 (stand shoot bullets 3 at a time), location: (#$20, #$20) + .byte $81,$10,$71 ; floating rock (enemy type #$10), attribute: 001, location: (#$81, #$70) + .byte $c0,$04,$10 ; rotating gun (enemy type #$04), attribute: 000, location: (#$c0, #$10) + .byte $d0,$0c,$50 ; scuba diver (enemy type #$0c), attribute: 000, location: (#$d0, #$50) + .byte $ff + +level_3_enemy_screen_04: + .byte $38,$05,$72 ; soldier (enemy type #$05), attribute: 010, location: (#$38, #$70) + .byte $50,$0c,$e0 ; scuba diver (enemy type #$0c), attribute: 000, location: (#$50, #$e0) + .byte $60,$02,$b3 ; pill box sensor (enemy type #$02), attribute: 011 (S), location: (#$60, #$b0) + .byte $88,$05,$a6 ; soldier (enemy type #$05), attribute: 110, location: (#$88, #$a0) + .byte $a0,$04,$f0 ; rotating gun (enemy type #$04), attribute: 000, location: (#$a0, #$f0) + .byte $a8,$05,$46 ; soldier (enemy type #$05), attribute: 110, location: (#$a8, #$40) + .byte $d8,$45,$85,$e4 ; soldier (enemy type #$05), attribute: 101, location: (#$d8, #$80), repeat: 1 [(y = #$e0, attr = 100)] + .byte $e0,$04,$10 ; rotating gun (enemy type #$04), attribute: 000, location: (#$e0, #$10) + .byte $ff + +level_3_enemy_screen_05: + .byte $20,$02,$11 ; pill box sensor (enemy type #$02), attribute: 001 (M), location: (#$20, #$10) + .byte $30,$07,$e1 ; red turret (enemy type #$07), attribute: 001, location: (#$30, #$e0) + .byte $38,$05,$66 ; soldier (enemy type #$05), attribute: 110, location: (#$38, #$60) + .byte $40,$04,$30 ; rotating gun (enemy type #$04), attribute: 000, location: (#$40, #$30) + .byte $79,$10,$81 ; floating rock (enemy type #$10), attribute: 001, location: (#$79, #$80) + .byte $ff + +level_3_enemy_screen_06: + .byte $28,$05,$c6 ; soldier (enemy type #$05), attribute: 110, location: (#$28, #$c0) + .byte $40,$05,$15 ; soldier (enemy type #$05), attribute: 101, location: (#$40, #$10) + .byte $58,$05,$95 ; soldier (enemy type #$05), attribute: 101, location: (#$58, #$90) + .byte $78,$06,$20 ; sniper (enemy type #$06), attribute: 000 (stand shoot bullets 3 at a time), location: (#$78, #$20) + .byte $e8,$05,$65 ; soldier (enemy type #$05), attribute: 101, location: (#$e8, #$60) + .byte $ff + +level_3_enemy_screen_07: + .byte $08,$05,$e4 ; soldier (enemy type #$05), attribute: 100, location: (#$08, #$e0) + .byte $a8,$55,$51,$b0 ; dragon orb (enemy type #$15), attribute: 001, location: (#$a8, #$50) + ; repeat: 1 [(y = #$b0, attr = 000)] + .byte $c1,$14,$80 ; dragon (enemy type #$14), attribute: 000, location: (#$c1, #$80) + +level_3_enemy_screen_08: + .byte $ff + +; pointer table for level 4 enemy groups (#$9 * #$2 = #$12 bytes) +level_4_enemy_screen_ptr_tbl: + .addr level_4_enemy_screen_00 ; CPU address $b9c1 + .addr level_4_enemy_screen_01 ; CPU address $b9cf + .addr level_4_enemy_screen_02 ; CPU address $b9e0 + .addr level_4_enemy_screen_03 ; CPU address $b9ee + .addr level_4_enemy_screen_04 ; CPU address $b9fc + .addr level_4_enemy_screen_05 ; CPU address $ba0a + .addr level_4_enemy_screen_06 ; CPU address $ba15 + .addr level_4_enemy_screen_07 ; CPU address $ba20 + .addr level_4_enemy_screen_08 ; CPU address $ba31 + +; level 4 enemy data (#$87 bytes) +; first byte specifies number of wall cores to destroy +; then enemies are described +; byte 0 = location of object +; * xxxx .... - y position * #$10 +; * .... xxxx - x position * #$10 +; byte 1 = enemy type and position adjustment +; * x... .... - y position + #$07 +; * .x.. .... - x position + #$07 +; * ..xx xxxx object type +; byte 2 = enemy attributes +; * xxxx .... - y position of enemy +; * .... xxxx - x position of enemy +level_4_enemy_screen_00: + .byte $01 ; number of cores to destroy to advance to next room + .byte $11,$19,$01 ; enemy type #$19 (indoor soldier generator) + .byte $68,$94,$04 ; enemy type #$14 (core) + .byte $66,$d3,$00 ; enemy type #$13 (wall turret) + .byte $69,$d3,$00 ; enemy type #$13 (wall turret) + .byte $ff + +level_4_enemy_screen_01: + .byte $04 ; number of cores to destroy to advance to next room + .byte $11,$19,$01 ; enemy type #$19 (indoor soldier generator) + .byte $76,$54,$03 ; enemy type #$14 (core) + .byte $77,$54,$01 ; enemy type #$14 (core) + .byte $78,$54,$01 ; enemy type #$14 (core) + .byte $79,$54,$03 ; enemy type #$14 (core) + .byte $ff + +level_4_enemy_screen_02: + .byte $02 ; number of cores to destroy to advance to next room + .byte $11,$19,$01 ; enemy type #$19 (indoor soldier generator) + .byte $67,$d4,$04 ; enemy type #$14 (core) + .byte $68,$d4,$04 ; enemy type #$14 (core) + .byte $58,$93,$00 ; enemy type #$13 (wall turret) + .byte $ff + +level_4_enemy_screen_03: + .byte $02 ; number of cores to destroy to advance to next room + .byte $11,$19,$01 ; enemy type #$19 (indoor soldier generator) + .byte $68,$13,$00 ; enemy type #$13 (wall turret) + .byte $77,$54,$03 ; enemy type #$14 (core) + .byte $78,$54,$03 ; enemy type #$14 (core) + .byte $ff + +level_4_enemy_screen_04: + .byte $02 ; number of cores to destroy to advance to next room + .byte $11,$19,$01 ; enemy type #$19 (indoor soldier generator) + .byte $66,$d4,$03 ; enemy type #$14 (core) + .byte $68,$93,$00 ; enemy type #$13 (wall turret) + .byte $69,$d4,$03 ; enemy type #$14 (core) + .byte $ff + +level_4_enemy_screen_05: + .byte $01 ; number of cores to destroy to advance to next room + .byte $11,$1a,$01 ; enemy type #$1a (indoor roller generator) + .byte $11,$19,$01 ; enemy type #$19 (indoor soldier generator) + .byte $68,$94,$03 ; enemy type #$14 (core) + .byte $ff + +level_4_enemy_screen_06: + .byte $01 ; number of cores to destroy to advance to next room + .byte $11,$19,$01 ; enemy type #$19 (indoor soldier generator) + .byte $58,$94,$03 ; enemy type #$14 (core) + .byte $68,$93,$00 ; enemy type #$13 (wall turret) + .byte $ff + +level_4_enemy_screen_07: + .byte $01 ; number of cores to destroy to advance to next room + .byte $11,$19,$01 ; enemy type #$19 (indoor soldier generator) + .byte $58,$13,$00 ; enemy type #$13 (wall turret) + .byte $66,$d3,$00 ; enemy type #$13 (wall turret) + .byte $68,$94,$0b ; enemy type #$14 (core) + .byte $69,$d3,$00 ; enemy type #$13 (wall turret) + .byte $ff + +; boss screen +level_4_enemy_screen_08: + .byte $02 ; number of cores to destroy to advance to next room + .byte $11,$20,$00 ; enemy type #$20 (red and blue soldier generator) + .byte $36,$5c,$00 ; enemy type #$1c (boss gemini) + .byte $39,$5c,$00 ; enemy type #$1c (boss gemini) + .byte $58,$08,$00 ; enemy type #$08 (wall turret) + .byte $85,$0a,$00 ; enemy type #$0a (wall plating) + .byte $88,$0a,$01 ; enemy type #$0a (wall plating) + .byte $8b,$0a,$00 ; enemy type #$0a (wall plating) + .byte $ff + +; pointer table for level 5 enemy groups (#$16 * #$2 = #$2c bytes) +level_5_enemy_screen_ptr_tbl: + .addr level_5_enemy_screen_00 ; CPU address $ba74 + .addr level_5_enemy_screen_01 ; CPU address $ba81 + .addr level_5_enemy_screen_02 ; CPU address $ba8b + .addr level_5_enemy_screen_03 ; CPU address $ba92 + .addr level_5_enemy_screen_04 ; CPU address $ba9f + .addr level_5_enemy_screen_05 ; CPU address $baa9 + .addr level_5_enemy_screen_06 ; CPU address $baad + .addr level_5_enemy_screen_07 ; CPU address $bab6 + .addr level_5_enemy_screen_08 ; CPU address $babd + .addr level_5_enemy_screen_09 ; CPU address $baca + .addr level_5_enemy_screen_0a ; CPU address $bad1 + .addr level_5_enemy_screen_0b ; CPU address $bad8 + .addr level_5_enemy_screen_0c ; CPU address $badc + .addr level_5_enemy_screen_0d ; CPU address $bae6 + .addr level_5_enemy_screen_0e ; CPU address $baed + .addr level_5_enemy_screen_0f ; CPU address $baf7 + .addr level_5_enemy_screen_10 ; CPU address $bb01 + .addr level_5_enemy_screen_11 ; CPU address $bb05 + .addr level_5_enemy_screen_12 ; CPU address $bb12 + .addr level_5_enemy_screen_13 ; CPU address $bb1f + .addr level_5_enemy_screen_14 ; CPU address $bb20 + .addr level_5_enemy_screen_15 ; CPU address $bb23 + +level_5_enemy_screen_00: + .byte $20,$10,$60 ; ice grenade generator (enemy type #$10), attribute: 000, location: (#$20, #$60) + .byte $50,$10,$61 ; ice grenade generator (enemy type #$10), attribute: 001, location: (#$50, #$60) + .byte $60,$0e,$82 ; turret man (enemy type #$0e), attribute: 010, location: (#$60, #$80) + .byte $d8,$10,$60 ; ice grenade generator (enemy type #$10), attribute: 000, location: (#$d8, #$60) + .byte $ff + +level_5_enemy_screen_01: + .byte $00,$0e,$81 ; turret man (enemy type #$0e), attribute: 001, location: (#$00, #$80) + .byte $08,$10,$63 ; ice grenade generator (enemy type #$10), attribute: 011, location: (#$08, #$60) + .byte $80,$02,$a1 ; pill box sensor (enemy type #$02), attribute: 001 (M), location: (#$80, #$a0) + .byte $ff + +level_5_enemy_screen_02: + .byte $60,$0e,$82 ; turret man (enemy type #$0e), attribute: 010, location: (#$60, #$80) + .byte $c0,$0e,$b2 ; turret man (enemy type #$0e), attribute: 010, location: (#$c0, #$b0) + .byte $ff + +level_5_enemy_screen_03: + .byte $20,$0e,$51 ; turret man (enemy type #$0e), attribute: 001, location: (#$20, #$50) + .byte $60,$03,$30 ; flying capsule (enemy type #$03), attribute: 000 (R), location: (#$60, #$30) + .byte $d8,$10,$43 ; ice grenade generator (enemy type #$10), attribute: 011, location: (#$d8, #$40) + .byte $f0,$10,$43 ; ice grenade generator (enemy type #$10), attribute: 011, location: (#$f0, #$40) + .byte $ff + +level_5_enemy_screen_04: + .byte $80,$10,$53 ; ice grenade generator (enemy type #$10), attribute: 011, location: (#$80, #$50) + .byte $c0,$10,$53 ; ice grenade generator (enemy type #$10), attribute: 011, location: (#$c0, #$50) + .byte $d0,$0e,$81 ; turret man (enemy type #$0e), attribute: 001, location: (#$d0, #$80) + .byte $ff + +level_5_enemy_screen_05: + .byte $60,$02,$82 ; pill box sensor (enemy type #$02), attribute: 010 (F), location: (#$60, #$80) + .byte $ff + +level_5_enemy_screen_06: + .byte $40,$83,$30,$76,$b3 ; flying capsule (enemy type #$03), attribute: 000 (R), location: (#$40, #$30) + ; repeat: 2 [(y = #$70, attr = 110 (Falcon)), (y = #$b0, attr = 011 (S))] + .byte $40,$0c,$d8 ; scuba diver (enemy type #$0c), attribute: 000, location: (#$40, #$d8) + .byte $ff + +level_5_enemy_screen_07: + .byte $10,$0c,$d8 ; scuba diver (enemy type #$0c), attribute: 000, location: (#$10, #$d8) + .byte $a0,$06,$80 ; sniper (enemy type #$06), attribute: 000 (stand shoot bullets 3 at a time), location: (#$a0, #$80) + .byte $ff + +level_5_enemy_screen_08: + .byte $00,$13,$b0 ; ice pipe (enemy type #$13), attribute: 000, location: (#$00, #$b0) + .byte $40,$13,$b0 ; ice pipe (enemy type #$13), attribute: 000, location: (#$40, #$b0) + .byte $80,$13,$b0 ; ice pipe (enemy type #$13), attribute: 000, location: (#$80, #$b0) + .byte $c0,$13,$b0 ; ice pipe (enemy type #$13), attribute: 000, location: (#$c0, #$b0) + .byte $ff + +level_5_enemy_screen_09: + .byte $01,$12,$32 ; tank (enemy type #$12), attribute: 010, location: (#$01, #$30) + .byte $00,$13,$b0 ; ice pipe (enemy type #$13), attribute: 000, location: (#$00, #$b0) + .byte $ff + +level_5_enemy_screen_0a: + .byte $80,$13,$b0 ; ice pipe (enemy type #$13), attribute: 000, location: (#$80, #$b0) + .byte $c0,$13,$b0 ; ice pipe (enemy type #$13), attribute: 000, location: (#$c0, #$b0) + .byte $ff + +level_5_enemy_screen_0b: + .byte $a8,$0e,$a1 ; turret man (enemy type #$0e), attribute: 001, location: (#$a8, #$a0) + .byte $ff + +level_5_enemy_screen_0c: + .byte $40,$13,$b0 ; ice pipe (enemy type #$13), attribute: 000, location: (#$40, #$b0) + .byte $80,$13,$b0 ; ice pipe (enemy type #$13), attribute: 000, location: (#$80, #$b0) + .byte $c0,$13,$b0 ; ice pipe (enemy type #$13), attribute: 000, location: (#$c0, #$b0) + .byte $ff + +level_5_enemy_screen_0d: + .byte $01,$12,$33 ; tank (enemy type #$12), attribute: 011, location: (#$01, #$30) + .byte $00,$13,$b0 ; ice pipe (enemy type #$13), attribute: 000, location: (#$00, #$b0) + .byte $ff + +level_5_enemy_screen_0e: + .byte $80,$13,$b0 ; ice pipe (enemy type #$13), attribute: 000, location: (#$80, #$b0) + .byte $c0,$13,$b0 ; ice pipe (enemy type #$13), attribute: 000, location: (#$c0, #$b0) + .byte $d0,$0e,$a1 ; turret man (enemy type #$0e), attribute: 001, location: (#$d0, #$a0) + .byte $ff + +level_5_enemy_screen_0f: + .byte $60,$10,$52 ; ice grenade generator (enemy type #$10), attribute: 010, location: (#$60, #$50) + .byte $a0,$10,$53 ; ice grenade generator (enemy type #$10), attribute: 011, location: (#$a0, #$50) + .byte $e8,$10,$53 ; ice grenade generator (enemy type #$10), attribute: 011, location: (#$e8, #$50) + .byte $ff + +level_5_enemy_screen_10: + .byte $60,$02,$a4 ; pill box sensor (enemy type #$02), attribute: 100 (L), location: (#$60, #$a0) + .byte $ff + +level_5_enemy_screen_11: + .byte $00,$10,$80 ; ice grenade generator (enemy type #$10), attribute: 000, location: (#$00, #$80) + .byte $60,$10,$81 ; ice grenade generator (enemy type #$10), attribute: 001, location: (#$60, #$80) + .byte $b0,$0e,$81 ; turret man (enemy type #$0e), attribute: 001, location: (#$b0, #$80) + .byte $c0,$10,$82 ; ice grenade generator (enemy type #$10), attribute: 010, location: (#$c0, #$80) + .byte $ff + +level_5_enemy_screen_12: + .byte $00,$13,$b0 ; ice pipe (enemy type #$13), attribute: 000, location: (#$00, #$b0) + .byte $40,$13,$b0 ; ice pipe (enemy type #$13), attribute: 000, location: (#$40, #$b0) + .byte $80,$13,$b0 ; ice pipe (enemy type #$13), attribute: 000, location: (#$80, #$b0) + .byte $c0,$13,$b0 ; ice pipe (enemy type #$13), attribute: 000, location: (#$c0, #$b0) + .byte $ff + +level_5_enemy_screen_13: + .byte $ff + +level_5_enemy_screen_14: + .byte $01,$14,$30 ; alien carrier/boss ufo (enemy type #$14), attribute: 000, location: (#$01, #$30) + +level_5_enemy_screen_15: + .byte $ff + +level_6_enemy_screen_ptr_tbl: + .addr level_6_enemy_screen_00 + .addr level_6_enemy_screen_01 + .addr level_6_enemy_screen_02 + .addr level_6_enemy_screen_03 + .addr level_6_enemy_screen_04 + .addr level_6_enemy_screen_05 + .addr level_6_enemy_screen_06 + .addr level_6_enemy_screen_07 + .addr level_6_enemy_screen_08 + .addr level_6_enemy_screen_09 + .addr level_6_enemy_screen_0a + .addr level_6_enemy_screen_0b + .addr level_6_enemy_screen_0c + +level_6_enemy_screen_00: + .byte $40,$05,$84 ; soldier (enemy type #$05), attribute: 100, location: (#$40, #$80) + .byte $60,$05,$68 ; soldier (enemy type #$05), attribute: 000, location: (#$60, #$68) + .byte $c0,$02,$a1 ; pill box sensor (enemy type #$02), attribute: 001 (M), location: (#$c0, #$a0) + .byte $e0,$06,$60 ; sniper (enemy type #$06), attribute: 000 (stand shoot bullets 3 at a time), location: (#$e0, #$60) + .byte $ff + +level_6_enemy_screen_01: + .byte $80,$0e,$61 ; turret man (enemy type #$0e), attribute: 001, location: (#$80, #$60) + .byte $e0,$0e,$61 ; turret man (enemy type #$0e), attribute: 001, location: (#$e0, #$60) + .byte $f0,$0e,$c1 ; turret man (enemy type #$0e), attribute: 001, location: (#$f0, #$c0) + .byte $ff + +level_6_enemy_screen_02: + .byte $a0,$0e,$c1 ; turret man (enemy type #$0e), attribute: 001, location: (#$a0, #$c0) + .byte $a0,$06,$90 ; sniper (enemy type #$06), attribute: 000 (stand shoot bullets 3 at a time), location: (#$a0, #$90) + .byte $ff + +level_6_enemy_screen_03: + .byte $40,$02,$ac ; pill box sensor (enemy type #$02), attribute: 100 (L), location: (#$40, #$a8) + .byte $48,$10,$26 ; fire beam down (enemy type #$10), attribute: 110, location: (#$48, #$20) + .byte $c8,$10,$25 ; fire beam down (enemy type #$10), attribute: 101, location: (#$c8, #$20) + .byte $e0,$0e,$91 ; turret man (enemy type #$0e), attribute: 001, location: (#$e0, #$90) + .byte $f0,$0e,$c1 ; turret man (enemy type #$0e), attribute: 001, location: (#$f0, #$c0) + .byte $ff + +level_6_enemy_screen_04: + .byte $28,$10,$27 ; fire bean down (enemy type #$10), attribute: 111, location: (#$28, #$20) + .byte $b8,$91,$58,$84,$b2 ; fire bean left (enemy type #$11), attribute: 000, location: (#$b8, #$58) + ; repeat: 2 [(y = #$80, attr = 100), (y = #$b0, attr = 010)] + .byte $ff + +level_6_enemy_screen_05: + .byte $58,$51,$6c,$99 ; fire bean left (enemy type #$11), attribute: 100, location: (#$58, #$68), repeat: 1 [(y = #$98, attr = 001)] + .byte $a8,$10,$26 ; fire bean down (enemy type #$10), attribute: 110, location: (#$a8, #$20) + .byte $a0,$02,$ad ; pill box sensor (enemy type #$02), attribute: 101 (B), location: (#$a0, #$a8) + .byte $c8,$06,$30 ; sniper (enemy type #$06), attribute: 000 (stand shoot bullets 3 at a time), location: (#$c8, #$30) + .byte $f8,$11,$c2 ; fire bean left (enemy type #$11), attribute: 010, location: (#$f8, #$c0) + .byte $ff + +level_6_enemy_screen_06: + .byte $58,$51,$39,$64 ; fire bean left (enemy type #$11), attribute: 001, location: (#$58, #$38), repeat: 1 [(y = #$60, attr = 100)] + .byte $a8,$12,$6e ; fire bean right (enemy type #$12), attribute: 110, location: (#$a8, #$68) + .byte $ff + +level_6_enemy_screen_07: + .byte $40,$0e,$61 ; turret man (enemy type #$0e), attribute: 001, location: (#$40, #$60) + .byte $48,$12,$99 ; fire bean right (enemy type #$12), attribute: 001, location: (#$48, #$98) + .byte $68,$10,$25 ; fire bean down (enemy type #$10), attribute: 101, location: (#$68, #$20) + .byte $a8,$12,$c0 ; fire bean right (enemy type #$12), attribute: 000, location: (#$a8, #$c0) + .byte $e8,$10,$27 ; fire bean down (enemy type #$10), attribute: 111, location: (#$e8, #$20) + .byte $ff + +level_6_enemy_screen_08: + .byte $60,$4e,$91,$c1 ; turret man (enemy type #$0e), attribute: 001, location: (#$60, #$90), repeat: 1 [(y = #$c0, attr = 001)] + .byte $68,$52,$6a,$ca ; fire bean right (enemy type #$12), attribute: 010, location: (#$68, #$68), repeat: 1 [(y = #$c8, attr = 010)] + .byte $e8,$12,$64 ; fire bean right (enemy type #$12), attribute: 100, location: (#$e8, #$60) + .byte $f0,$0e,$91 ; turret man (enemy type #$0e), attribute: 001, location: (#$f0, #$90) + .byte $ff + +level_6_enemy_screen_09: + .byte $68,$10,$23 ; fire bean down (enemy type #$10), attribute: 011, location: (#$68, #$20) + .byte $ff + +level_6_enemy_screen_0a: + .byte $ff + +level_6_enemy_screen_0b: + .byte $b1,$13,$a0 ; giant boss soldier (enemy type #$13), attribute: 000, location: (#$b1, #$a0) + .byte $ff + +level_6_enemy_screen_0c: + .byte $ff + +; pointer table for level 7 enemy data (#$f * #$2 = #$1e bytes) +; enemy format +; xx tt yy +; xx = x position +; xxxxxxxx x position +; +; tt = enemy type + repeat +; xx...... repeat +; ..xxxxxx enemy type +; +; yy = y position + attributes +; xxxxx... y position +; .....xxx attributes +level_7_enemy_screen_ptr_tbl: + .addr level_7_enemy_screen_00 ; CPU address $bbd5 + .addr level_7_enemy_screen_01 ; CPU address $bbed + .addr level_7_enemy_screen_02 ; CPU address $bbfd + .addr level_7_enemy_screen_03 ; CPU address $bc0d + .addr level_7_enemy_screen_04 ; CPU address $bc17 + .addr level_7_enemy_screen_05 ; CPU address $bc36 + .addr level_7_enemy_screen_06 ; CPU address $bc40 + .addr level_7_enemy_screen_07 ; CPU address $bc44 + .addr level_7_enemy_screen_08 ; CPU address $bc4b + .addr level_7_enemy_screen_09 ; CPU address $bc67 + .addr level_7_enemy_screen_0a ; CPU address $bc77 + .addr level_7_enemy_screen_0b ; CPU address $bc81 + .addr level_7_enemy_screen_0c ; CPU address $bc91 + .addr level_7_enemy_screen_0d ; CPU address $bc9b + .addr level_7_enemy_screen_0e ; CPU address $bca8 + +level_7_enemy_screen_00: + .byte $47,$10,$a1 ; mechanical claw (lower level) + .byte $67,$10,$a5 ; mechanical claw (lower level) + .byte $87,$10,$a9 ; mechanical claw (lower level) + .byte $97,$10,$40 ; mechanical claw (top level) + .byte $a7,$50,$44 ; mechanical claw (top level) + .byte $ad,$b7,$10 ; mechanical claw (repeat once) + .byte $48 ; repeated mechanical claw top + .byte $c7,$50,$4c ; mechanical claw (repeat once) + .byte $a1 ; repeated mechanical claw (bottom) + .byte $ff + +level_7_enemy_screen_01: + .byte $00,$03,$a2 ; flying capsule + .byte $80,$15,$c0 ; mining cart (stationary) + .byte $97,$10,$42 ; mechanical claw + .byte $a7,$10,$46 ; mechanical claw + .byte $f7,$10,$ac ; mechanical claw + .byte $ff + +level_7_enemy_screen_02: + .byte $07,$10,$a8 ; mechanical claw + .byte $18,$0e,$60 ; turret man + .byte $64,$11,$b0 ; spiked wall + .byte $84,$11,$b1 ; spiked wall + .byte $90,$03,$b0 ; flying capsule + .byte $ff + +level_7_enemy_screen_03: + .byte $04,$12,$92 ; tall spiked wall + .byte $44,$12,$92 ; tall spiked wall + .byte $84,$12,$92 ; tall spiked wall + .byte $ff + +level_7_enemy_screen_04: + .byte $04,$11,$72 ; rising spiked wall (enemy type #$11), attribute: 010, location: (#$04, #$70) + .byte $07,$10,$ad ; mechanical claw (enemy type #$10), attribute: 101, location: (#$07, #$a8) + .byte $27,$50,$48,$a9 ; mechanical claw (enemy type #$10), attribute: 000, location: (#$27, #$48), repeat: 1 [(y = #$a8, attr = 001)] + .byte $47,$50,$44,$a5 ; mechanical claw (enemy type #$10), attribute: 100, location: (#$47, #$40), repeat: 1 [(y = #$a0, attr = 101)] + .byte $67,$50,$40,$a1 ; mechanical claw (enemy type #$10), attribute: 000, location: (#$67, #$40), repeat: 1 [(y = #$a0, attr = 001)] + .byte $70,$13,$40 ; mining cart generator (enemy type #$13), attribute: 000, location: (#$70, #$40) + .byte $84,$11,$73 ; rising spiked wall (enemy type #$11), attribute: 011, location: (#$84, #$70) + .byte $87,$10,$ad ; mechanical claw (enemy type #$10), attribute: 101, location: (#$87, #$a8) + .byte $a0,$15,$c0 ; stationary mining cart (enemy type #$15), attribute: 000, location: (#$a0, #$c0) + .byte $ff + +level_7_enemy_screen_05: + .byte $20,$02,$65 ; pill box sensor (enemy type #$02), attribute: 101 (B), location: (#$20, #$60) + .byte $c7,$10,$4a ; mechanical claw (enemy type #$10), attribute: 010, location: (#$c7, #$48) + .byte $e7,$10,$42 ; mechanical claw (enemy type #$10), attribute: 010, location: (#$e7, #$40) + .byte $ff + +level_7_enemy_screen_06: + .byte $30,$15,$c0 ; stationary mining cart (enemy type #$15), attribute: 000, location: (#$30, #$c0) + .byte $ff + +level_7_enemy_screen_07: + .byte $10,$13,$40 ; mining cart generator (enemy type #$13), attribute: 000, location: (#$10, #$40) + .byte $f0,$06,$60 ; sniper (enemy type #$06), attribute: 000 (stand shoot bullets 3 at a time), location: (#$f0, #$60) + .byte $ff + +level_7_enemy_screen_08: + .byte $00,$03,$31 ; flying capsule (enemy type #$03), attribute: 001 (M), location: (#$00, #$30) + .byte $58,$15,$c0 ; stationary mining cart (enemy type #$15), attribute: 000, location: (#$58, #$c0) + .byte $80,$03,$c3 ; flying capsule (enemy type #$03), attribute: 011 (S), location: (#$80, #$c0) + .byte $84,$12,$70 ; tall spiked wall (enemy type #$12), attribute: 000, location: (#$84, #$70) + .byte $87,$10,$ab ; mechanical claw (enemy type #$10), attribute: 011, location: (#$87, #$a8) + .byte $a7,$10,$ab ; mechanical claw (enemy type #$10), attribute: 011, location: (#$a7, #$a8) + .byte $c4,$12,$70 ; tall spiked wall (enemy type #$12), attribute: 000, location: (#$c4, #$70) + .byte $c7,$10,$a7 ; mechanical claw (enemy type #$10), attribute: 111, location: (#$c7, #$a0) + .byte $e7,$10,$a7 ; mechanical claw (enemy type #$10), attribute: 111, location: (#$e7, #$a0) + .byte $ff + +level_7_enemy_screen_09: + .byte $04,$12,$70 ; tall spiked wall (enemy type #$12), attribute: 000, location: (#$04, #$70) + .byte $07,$10,$a3 ; mechanical claw (enemy type #$10), attribute: 011, location: (#$07, #$a0) + .byte $a4,$11,$93 ; rising spiked wall (enemy type #$11), attribute: 011, location: (#$a4, #$90) + .byte $c4,$11,$97 ; rising spiked wall (enemy type #$11), attribute: 111, location: (#$c4, #$90) + .byte $e4,$11,$9b ; rising spiked wall (enemy type #$11), attribute: 011, location: (#$e4, #$98) + .byte $ff + +level_7_enemy_screen_0a: + .byte $d7,$10,$86 ; mechanical claw (enemy type #$10), attribute: 110, location: (#$d7, #$80) + .byte $e7,$10,$8e ; mechanical claw (enemy type #$10), attribute: 110, location: (#$e7, #$88) + .byte $f7,$10,$86 ; mechanical claw (enemy type #$10), attribute: 110, location: (#$f7, #$80) + .byte $ff + +level_7_enemy_screen_0b: + .byte $07,$10,$8e ; mechanical claw (enemy type #$10), attribute: 110, location: (#$07, #$88) + .byte $17,$10,$86 ; mechanical claw (enemy type #$10), attribute: 110, location: (#$17, #$80) + .byte $27,$10,$8e ; mechanical claw (enemy type #$10), attribute: 110, location: (#$27, #$88) + .byte $44,$11,$df ; rising spiked wall (enemy type #$11), attribute: 111, location: (#$44, #$d8) + .byte $f0,$0e,$c0 ; turret man (enemy type #$0e), attribute: 000, location: (#$f0, #$c0) + .byte $ff + +level_7_enemy_screen_0c: + .byte $20,$0e,$c0 ; turret man (enemy type #$0e), attribute: 000, location: (#$20, #$c0) + .byte $a8,$0e,$a0 ; turret man (enemy type #$0e), attribute: 000, location: (#$a8, #$a0) + .byte $fe,$0e,$a0 ; turret man (enemy type #$0e), attribute: 000, location: (#$fe, #$a0) + .byte $ff + +level_7_enemy_screen_0d: + .byte $69,$17,$d1 ; mortar launcher (enemy type #$17), attribute: 001, location: (#$69, #$d0) + .byte $a9,$17,$d0 ; mortar launcher (enemy type #$17), attribute: 000, location: (#$a9, #$d0) + .byte $d9,$16,$80 ; armored door (enemy type #$16), attribute: 000, location: (#$d9, #$80) + .byte $e1,$18,$a0 ; soldier generator (enemy type #$18), attribute: 000, location: (#$e1, #$a0) + .byte $ff + +level_7_enemy_screen_0e: + .byte $ff + +; pointer table for level 8 enemy groups (#$b * #$2 = #$16 bytes) +level_8_enemy_screen_ptr_tbl: + .addr level_8_enemy_screen_00 ; CPU address $bcc0 + .addr level_8_enemy_screen_01 ; CPU address $bcc8 + .addr level_8_enemy_screen_02 ; CPU address $bcd5 + .addr level_8_enemy_screen_03 ; CPU address $bcdc + .addr level_8_enemy_screen_04 ; CPU address $bcec + .addr level_8_enemy_screen_05 ; CPU address $bcf3 + .addr level_8_enemy_screen_06 ; CPU address $bcfd + .addr level_8_enemy_screen_07 ; CPU address $bd07 + .addr level_8_enemy_screen_08 ; CPU address $bd11 + .addr level_8_enemy_screen_09 ; CPU address $bd21 + .addr level_8_enemy_screen_0a ; CPU address $bd3c + +level_8_enemy_screen_ptr_tbl_end: + .byte $ff ; unused byte + +level_8_enemy_screen_00: + .byte $40,$43,$31,$c5 ; flying capsule (enemy type #$03), attribute: 001 (M), location: (#$40, #$30), repeat: 1 [(y = #$c0, attr = 101)] + .byte $f0,$11,$a1 ; alien fetus (enemy type #$11), attribute: 001, location: (#$f0, #$a0) + .byte $ff + +level_8_enemy_screen_01: + .byte $60,$11,$51 ; alien fetus (enemy type #$11), attribute: 001, location: (#$60, #$50) + .byte $a0,$11,$61 ; alien fetus (enemy type #$11), attribute: 001, location: (#$a0, #$60) + .byte $a8,$11,$61 ; alien fetus (enemy type #$11), attribute: 001, location: (#$a8, #$60) + .byte $b0,$10,$60 ; alien guardian (enemy type #$10), attribute: 000, location: (#$b0, #$60) + .byte $ff + +level_8_enemy_screen_02: + .byte $be,$12,$40 ; alien mouth (enemy type #$12), attribute: 000, location: (#$be, #$40) + .byte $de,$12,$40 ; alien mouth (enemy type #$12), attribute: 000, location: (#$de, #$40) + .byte $ff + +level_8_enemy_screen_03: + .byte $1e,$12,$60 ; alien mouth (enemy type #$12), attribute: 000, location: (#$1e, #$60) + .byte $3e,$12,$60 ; alien mouth (enemy type #$12), attribute: 000, location: (#$3e, #$60) + .byte $de,$12,$60 ; alien mouth (enemy type #$12), attribute: 000, location: (#$de, #$60) + .byte $e0,$03,$c3 ; flying capsule (enemy type #$03), attribute: 011 (S), location: (#$e0, #$c0) + .byte $fe,$12,$60 ; alien mouth (enemy type #$12), attribute: 000, location: (#$fe, #$60) + .byte $ff + +level_8_enemy_screen_04: + .byte $de,$12,$e0 ; alien mouth (enemy type #$12), attribute: 000, location: (#$de, #$e0) + .byte $fe,$12,$e0 ; alien mouth (enemy type #$12), attribute: 000, location: (#$fe, #$e0) + .byte $ff + +level_8_enemy_screen_05: + .byte $1e,$12,$e0 ; alien mouth (enemy type #$12), attribute: 000, location: (#$1e, #$e0) + .byte $de,$12,$80 ; alien mouth (enemy type #$12), attribute: 000, location: (#$de, #$80) + .byte $fe,$12,$80 ; alien mouth (enemy type #$12), attribute: 000, location: (#$fe, #$80) + .byte $ff + +level_8_enemy_screen_06: + .byte $7e,$12,$e0 ; alien mouth (enemy type #$12), attribute: 000, location: (#$7e, #$e0) + .byte $9e,$12,$e0 ; alien mouth (enemy type #$12), attribute: 000, location: (#$9e, #$e0) + .byte $fe,$12,$80 ; alien mouth (enemy type #$12), attribute: 000, location: (#$fe, #$80) + .byte $ff + +level_8_enemy_screen_07: + .byte $1e,$12,$80 ; alien mouth (enemy type #$12), attribute: 000, location: (#$1e, #$80) + .byte $be,$12,$e0 ; alien mouth (enemy type #$12), attribute: 000, location: (#$be, #$e0) + .byte $de,$12,$e0 ; alien mouth (enemy type #$12), attribute: 000, location: (#$de, #$e0) + .byte $ff + +level_8_enemy_screen_08: + .byte $3e,$12,$60 ; alien mouth (enemy type #$12), attribute: 000, location: (#$3e, #$60) + .byte $5e,$12,$60 ; alien mouth (enemy type #$12), attribute: 000, location: (#$5e, #$60) + .byte $a0,$14,$c8 ; alien spider (enemy type #$14), attribute: 000, location: (#$a0, #$c8) + .byte $c0,$14,$c8 ; alien spider (enemy type #$14), attribute: 000, location: (#$c0, #$c8) + .byte $f0,$14,$c8 ; alien spider (enemy type #$14), attribute: 000, location: (#$f0, #$c8) + .byte $ff + +level_8_enemy_screen_09: + .byte $30,$14,$c8 ; alien spider (enemy type #$14), attribute: 000, location: (#$30, #$c8) + .byte $3e,$12,$e0 ; alien mouth (enemy type #$12), attribute: 000, location: (#$3e, #$e0) + .byte $70,$14,$20 ; alien spider (enemy type #$14), attribute: 000, location: (#$70, #$20) + .byte $b8,$14,$c8 ; alien spider (enemy type #$14), attribute: 000, location: (#$b8, #$c8) + .byte $c0,$15,$40 ; spider spawn (enemy type #$15), attribute: 000, location: (#$c0, #$40) + .byte $c2,$15,$c0 ; spider spawn (enemy type #$15), attribute: 000, location: (#$c2, #$c0) + .byte $d1,$16,$70 ; alien heart (enemy type #$16), attribute: 000, location: (#$d1, #$70) + .byte $e0,$15,$40 ; spider spawn (enemy type #$15), attribute: 000, location: (#$e0, #$40) + .byte $e2,$15,$c0 ; spider spawn (enemy type #$15), attribute: 000, location: (#$e2, #$c0) + +; unused space (#$2c4 bytes) +; these $ff bytes can technically be deleted here because the contra.cfg +; specifies that any free bytes in a ROM bank will be filled with $ff +; and each ROM bank is 16 KiB +level_8_enemy_screen_0a: +bank_2_unused_space: + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff \ No newline at end of file diff --git a/src/bank3.asm b/src/bank3.asm new file mode 100644 index 0000000..8388c06 --- /dev/null +++ b/src/bank3.asm @@ -0,0 +1,1572 @@ +; Contra US Disassembly - v1.0 +; https://github.com/vermiceli/nes-contra-us +; Bank 3 starts with the data that specifies which pattern table tiles comprises +; super-tiles along with the color palettes. This bank also has the routines +; to manage the end of levels. + +.segment "BANK_3" + +.include "constants.asm" + +; import labels from bank 7 +.import run_routine_from_tbl_below, set_graphics_zero_mode, set_a_as_current_level_routine + +; export labels used by bank 2 +.export level_4_supertile_data, level_6_supertile_data +.export level_7_supertile_data + +; export labels used by bank 7 +.export level_1_nametable_update_supertile_data, level_1_nametable_update_palette_data +.export level_2_nametable_update_supertile_data, level_2_nametable_update_palette_data +.export level_3_nametable_update_supertile_data, level_3_nametable_update_palette_data +.export level_4_nametable_update_supertile_data, level_4_nametable_update_palette_data +.export level_5_nametable_update_supertile_data, level_5_nametable_update_palette_data +.export level_6_nametable_update_supertile_data, level_6_nametable_update_palette_data +.export level_7_nametable_update_supertile_data, level_7_nametable_update_palette_data +.export level_8_nametable_update_supertile_data, level_8_nametable_update_palette_data +.export level_2_4_nametable_update_supertile_data, level_2_4_boss_nametable_update_palette_data +.export level_2_4_boss_palette_data, run_end_level_sequence_routine +.export level_2_4_boss_supertile_data, level_2_4_tile_animation +.export level_6_tile_animation, level_7_tile_animation + +; export labels used by bank 2 and bank 7 +.export level_1_supertile_data, level_2_supertile_data +.export level_3_supertile_data, level_5_supertile_data +.export level_8_supertile_data + +.export level_1_palette_data, level_2_palette_data +.export level_3_palette_data, level_4_palette_data +.export level_5_palette_data, level_6_palette_data +.export level_7_palette_data, level_8_palette_data + +; Every PRG ROM bank starts with a single byte specifying which number it is +.byte $03 ; The PRG ROM bank number (3) + +; level 1 super-tiles (#$3b * #$10 = #$3b0 bytes) +; background nametable data for level 1 +; #$10 bytes per super-tile +; each entry is a pattern table tile +; CPU address $8001 +level_1_supertile_data: + .byte $02,$03,$04,$05,$12,$13,$14,$15,$58,$59,$5a,$5b,$1c,$1d,$1e,$1f + .byte $4c,$4d,$c4,$b5,$4c,$4d,$c4,$b3,$4c,$4d,$d4,$c6,$4c,$4d,$d5,$d6 + .byte $00,$00,$00,$00,$c7,$c8,$c8,$c8,$d7,$e7,$d8,$e7,$d7,$e7,$e8,$e7 + .byte $01,$01,$01,$01,$bf,$f1,$f8,$bf,$16,$17,$c0,$33,$f6,$f7,$bd,$be + .byte $08,$09,$0a,$0b,$18,$19,$1a,$1b,$02,$03,$04,$05,$12,$13,$14,$15 + .byte $c3,$c4,$b8,$c9,$d3,$d4,$b8,$c9,$e3,$e4,$e5,$c9,$b7,$ae,$af,$c9 + .byte $d7,$e7,$e8,$e7,$d7,$e7,$e8,$e7,$d7,$e7,$e8,$e7,$d7,$e7,$e8,$e7 + .byte $48,$49,$4a,$4b,$18,$19,$1a,$1b,$0c,$0d,$0e,$0f,$1c,$1d,$1e,$1f + .byte $08,$09,$0a,$0b,$18,$19,$1a,$1b,$ca,$eb,$ec,$ed,$ef,$c3,$c3,$c3 + .byte $08,$09,$0a,$0b,$18,$19,$1a,$1b,$ea,$eb,$ec,$ed,$c3,$c3,$c3,$c3 + .byte $08,$09,$0a,$0b,$18,$19,$1a,$1b,$ea,$eb,$ec,$c9,$c3,$c3,$c3,$d8 + .byte $08,$09,$0a,$0b,$18,$19,$1a,$1b,$ca,$eb,$ec,$c9,$ef,$c3,$c3,$d8 + .byte $c3,$c3,$c3,$c3,$c3,$c3,$c3,$c3,$c3,$c3,$c3,$c3,$c3,$c3,$c3,$c3 + .byte $c3,$c3,$c3,$cd,$c3,$c3,$c3,$d8,$c3,$c3,$c3,$e9,$c3,$c3,$c3,$c3 + .byte $ce,$c3,$c3,$c3,$ef,$c3,$c3,$c3,$ee,$c3,$c3,$c3,$c3,$c3,$c3,$c3 + .byte $c3,$c3,$c3,$c3,$c3,$c3,$c3,$c3,$c3,$c3,$c3,$cd,$c3,$c3,$c3,$d8 + .byte $fe,$fe,$fe,$fe,$fe,$fe,$fe,$fe,$c3,$c3,$c3,$c3,$c3,$c3,$c3,$c3 + .byte $fe,$fe,$fe,$ff,$fe,$fe,$fe,$fe,$c3,$c3,$c3,$c3,$c3,$c3,$c3,$c3 + .byte $f9,$fa,$fb,$fc,$fe,$fe,$fe,$fe,$c3,$c3,$c3,$c3,$c3,$c3,$c3,$c3 + .byte $fd,$fe,$fe,$fe,$fe,$fe,$fe,$fe,$c3,$c3,$c3,$c3,$c3,$c3,$c3,$c3 + .byte $fd,$fe,$fe,$ff,$fe,$fe,$fe,$fe,$c3,$c3,$c3,$c3,$c3,$c3,$c3,$c3 + .byte $02,$03,$c7,$c8,$12,$13,$d7,$e7,$58,$59,$d7,$e7,$1c,$1d,$d7,$e7 + .byte $f9,$fa,$fb,$fc,$ce,$fe,$fe,$fe,$fd,$fe,$fe,$fe,$fe,$fe,$fe,$fe + .byte $f9,$fa,$fb,$fc,$fe,$fe,$fe,$cd,$fe,$fe,$fe,$ff,$fe,$fe,$fe,$fe + .byte $02,$03,$04,$05,$12,$13,$14,$15,$ed,$c9,$5a,$5b,$c3,$d8,$1e,$1f + .byte $36,$00,$00,$00,$34,$35,$ff,$ba,$3a,$34,$b4,$b5,$5c,$5d,$c4,$b5 + .byte $00,$da,$a8,$a9,$ca,$c4,$b8,$b9,$b6,$c4,$b8,$c9,$b6,$c4,$b8,$c9 + .byte $00,$00,$00,$00,$c8,$c8,$c8,$c8,$d8,$e7,$d8,$e7,$e8,$e7,$e8,$e7 + .byte $06,$34,$35,$34,$37,$38,$39,$38,$3b,$3c,$3d,$3d,$5e,$5f,$32,$5f + .byte $35,$34,$35,$34,$39,$38,$39,$3a,$3d,$3c,$3d,$3b,$32,$32,$5f,$32 + .byte $35,$34,$35,$36,$39,$38,$39,$3a,$3d,$3c,$3d,$3e,$32,$5f,$5e,$5f + .byte $c3,$c3,$c3,$c3,$c3,$c3,$c3,$c3,$ce,$c3,$c3,$c3,$ef,$c3,$c3,$c3 + .byte $00,$00,$00,$4f,$00,$4f,$00,$00,$4f,$00,$00,$00,$00,$00,$00,$4f + .byte $00,$00,$00,$00,$00,$4f,$00,$4f,$4f,$00,$4f,$00,$00,$00,$00,$4f ; first super-tile of level 1 (stars in sky) + .byte $4f,$00,$00,$00,$00,$00,$4f,$00,$00,$00,$00,$00,$10,$00,$c6,$c7 + .byte $4f,$00,$00,$00,$00,$4f,$00,$00,$00,$00,$cb,$cc,$d9,$da,$db,$dc + .byte $40,$41,$42,$43,$50,$51,$52,$53,$44,$45,$46,$47,$54,$55,$56,$57 + .byte $36,$00,$00,$00,$57,$36,$00,$00,$45,$57,$3a,$00,$55,$55,$57,$36 + .byte $e0,$00,$d6,$d7,$f0,$e5,$e6,$e7,$e1,$e2,$e3,$e4,$00,$00,$f2,$00 + .byte $f4,$dd,$de,$df,$e8,$c8,$e2,$cf,$f3,$00,$f2,$f3,$00,$00,$00,$00 + .byte $4c,$4d,$4e,$4d,$4c,$4d,$4e,$4d,$4c,$4d,$4e,$4d,$4c,$4d,$4e,$4d + .byte $01,$01,$01,$01,$bf,$f1,$f8,$bf,$c0,$33,$c0,$33,$bd,$be,$bd,$be + .byte $00,$00,$00,$00,$00,$00,$4f,$00,$00,$00,$00,$00,$10,$00,$00,$4f + .byte $00,$4f,$00,$00,$00,$00,$4f,$00,$4f,$00,$00,$00,$00,$00,$c6,$c7 + .byte $40,$41,$42,$43,$50,$51,$52,$53,$44,$45,$46,$47,$5c,$5d,$3f,$4d + .byte $11,$41,$42,$43,$06,$51,$52,$53,$07,$45,$46,$47,$5c,$5d,$3f,$4d + .byte $e0,$00,$00,$00,$f0,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $00,$00,$d6,$d7,$00,$e5,$e6,$e7,$e1,$e2,$e3,$e4,$00,$00,$00,$00 + .byte $4c,$4d,$ab,$ac,$4c,$4d,$bb,$bc,$4c,$4d,$bb,$b0,$4c,$4d,$bb,$c0 + .byte $ac,$ac,$ad,$b9,$bc,$bc,$bd,$d9,$b1,$b2,$bd,$aa,$c1,$c2,$bd,$b9 + .byte $e8,$e7,$e8,$e7,$e8,$e7,$e8,$e7,$e8,$e7,$e8,$e7,$e8,$e7,$e8,$e7 + .byte $c7,$c8,$c8,$c8,$d7,$e7,$d8,$e7,$d7,$e7,$e8,$e7,$d7,$e7,$e8,$e7 + .byte $4c,$4d,$bb,$d0,$4c,$4d,$bb,$e0,$4c,$4d,$bb,$bc,$5e,$5f,$e9,$ea + .byte $d1,$d2,$bd,$d9,$e1,$e2,$bd,$aa,$bc,$bc,$bd,$b9,$eb,$ec,$bd,$d9 + .byte $c8,$c8,$c8,$c8,$d8,$e7,$d8,$e7,$e8,$e7,$e8,$e7,$e8,$e7,$e8,$e7 + .byte $01,$01,$01,$01,$bf,$f1,$f8,$bf,$c0,$33,$c0,$c1,$bd,$be,$d0,$d1 + .byte $3a,$41,$42,$43,$4c,$51,$52,$53,$4c,$37,$46,$47,$4c,$4d,$4e,$4d + .byte $02,$03,$04,$05,$12,$13,$14,$15,$f9,$fa,$fb,$fc,$c3,$c3,$c3,$c3 + .byte $c8,$e7,$e8,$e7,$d8,$e7,$e8,$e7,$e8,$e7,$e8,$e7,$e8,$e7,$e8,$e7 + +; level 1 enemy super-tiles (#$2c * #$10 = #$2c0 bytes) +; rotating gun, pill box sensor, and gulcan (buried surfacing cannon) super-tiles +; #$10 tiles for each super-tile, each byte is an offset into pattern table +; background nametable data for enemies in level 1 +level_1_nametable_update_supertile_data: + .byte $20,$21,$21,$22,$26,$30,$31,$27,$26,$30,$31,$27,$23,$24,$24,$25 ; #$00 - pill box sensor closed + .byte $20,$21,$21,$22,$26,$2c,$2d,$27,$26,$2e,$2f,$27,$23,$24,$24,$25 ; #$01 - pill box sensor partially open + .byte $20,$21,$21,$22,$26,$28,$29,$27,$26,$2a,$2b,$27,$23,$24,$24,$25 ; #$02 - pill box sensor open + .byte $20,$21,$21,$22,$26,$96,$97,$27,$26,$97,$96,$27,$23,$24,$24,$25 ; #$03 - rotating gun closed + .byte $20,$21,$21,$22,$26,$94,$95,$27,$26,$95,$94,$27,$23,$24,$24,$25 ; #$04 - rotating gun opening + .byte $20,$21,$21,$22,$8d,$8e,$8f,$27,$9d,$9e,$9f,$27,$23,$24,$24,$25 ; #$05 - rotating gun facing left + .byte $20,$21,$21,$22,$8a,$8b,$8c,$27,$26,$9b,$9c,$27,$23,$24,$24,$25 ; #$06 - rotating gun facing left-up + .byte $20,$82,$21,$22,$26,$92,$93,$27,$26,$a2,$a3,$27,$23,$24,$24,$25 ; #$07 - rotating gun facing left-up (closer to up) + .byte $20,$66,$67,$22,$26,$76,$77,$27,$26,$86,$87,$27,$23,$24,$24,$25 ; #$08 - rotating gun facing up + .byte $20,$21,$81,$22,$26,$90,$91,$27,$26,$a0,$a1,$27,$23,$24,$24,$25 ; #$09 - rotating gun facing up up right (closer to up) + .byte $20,$21,$21,$22,$26,$68,$69,$6a,$26,$78,$79,$27,$23,$24,$24,$25 ; #$0a - rotating gun facing up right + .byte $20,$21,$21,$22,$26,$6d,$6e,$6f,$26,$7d,$7e,$7f,$23,$24,$24,$25 ; #$0b - rotating gun facing right + .byte $20,$21,$21,$22,$26,$88,$89,$27,$26,$98,$99,$9a,$23,$24,$24,$25 ; #$0c - rotating gun facing right down + .byte $20,$21,$21,$22,$26,$62,$63,$27,$26,$72,$73,$27,$23,$24,$83,$25 ; #$0d - rotating gun facing right down down (closer to down) + .byte $20,$21,$21,$22,$26,$64,$65,$27,$26,$74,$75,$27,$23,$84,$85,$25 ; #$0e - rotating gun facing down + .byte $20,$21,$21,$22,$26,$60,$61,$27,$26,$70,$71,$27,$23,$80,$24,$25 ; #$0f - rotating gun facing down left + .byte $20,$21,$21,$22,$26,$6b,$6c,$27,$7a,$7b,$7c,$27,$23,$24,$24,$25 ; #$10 - rotating gun facing down left left (closer to left) + .byte $a4,$a5,$a6,$a7,$b4,$b5,$b6,$b7,$b0,$b1,$b2,$b3,$ac,$ad,$ae,$af ; #$11 - red turret facing left + .byte $bc,$a5,$a6,$a7,$a8,$a9,$b6,$b7,$b8,$b9,$b2,$b3,$ac,$ad,$ae,$af ; #$12 - red turret facing up left + .byte $aa,$ab,$a6,$a7,$ba,$bb,$b6,$b7,$b8,$b9,$b2,$b3,$ac,$ad,$ae,$af ; #$13 - red turret facing up up left (almost straight up) + .byte $08,$09,$0a,$0b,$18,$19,$1a,$1b,$a4,$a5,$a6,$a7,$b4,$b5,$b6,$b7 ; #$14 - red turret 1/2 rising from ground rocky background + .byte $4c,$4d,$4e,$4d,$4c,$4d,$4e,$4d,$a4,$a5,$a6,$a7,$b4,$b5,$b6,$b7 ; #$15 - red turret 1/2 rising from ground metal background + .byte $08,$09,$0a,$0b,$18,$19,$1a,$1b,$0c,$0d,$0e,$0f,$1c,$1d,$1e,$1f ; #$16 - red turret and rotating gun rock background + .byte $4c,$4d,$4e,$4d,$4c,$4d,$4e,$4d,$4c,$4d,$4e,$4d,$5e,$5f,$32,$5f ; #$17 - red turret metal background, green grass + .byte $00,$00,$00,$00,$a4,$a5,$a6,$a7,$b4,$b5,$b6,$b7,$b0,$b1,$b2,$b3 ; #$18 - red turret 3/4 rising from ground black background + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ; #$19 - blank super-tile, used for destroyed bridges + .byte $c2,$00,$00,$c4,$d2,$00,$00,$d4,$00,$00,$00,$c5,$d3,$00,$00,$d5 ; #$1a - exploding bridge partially destroyed both ends still exist + .byte $c2,$00,$00,$00,$d2,$00,$00,$00,$00,$00,$00,$00,$d3,$00,$00,$00 ; #$1b - exploding bridge partially destroyed left only + .byte $00,$00,$00,$c4,$00,$00,$00,$d4,$00,$00,$00,$c5,$00,$00,$00,$d5 ; #$1c - exploding bridge partially destroyed right only + .byte $00,$00,$00,$c4,$00,$00,$00,$d4,$00,$00,$00,$00,$00,$00,$00,$00 ; #$1d - exploding bridge partially destroyed right only (more destroyed) + .byte $4c,$4d,$ab,$ac,$4c,$4d,$bb,$bc,$4c,$4d,$bb,$f4,$4c,$4d,$f3,$00 + .byte $ac,$ac,$ad,$b9,$bc,$bc,$bd,$d9,$f5,$f6,$f7,$f8,$00,$00,$00,$00 + .byte $c7,$c8,$c8,$c8,$d7,$e7,$d8,$e7,$f8,$f8,$f8,$f8,$00,$00,$00,$00 + .byte $c8,$c8,$c8,$c8,$d8,$e7,$d8,$e7,$f8,$f8,$f8,$f8,$00,$00,$00,$00 + .byte $4c,$4d,$fb,$00,$4c,$4d,$fa,$f2,$4c,$4d,$bb,$bc,$5e,$5f,$e9,$ea + .byte $00,$00,$00,$00,$f9,$f9,$f9,$f9,$fc,$f2,$fd,$fe,$eb,$ec,$bd,$d9 + .byte $00,$00,$00,$00,$f9,$f9,$f9,$f9,$fe,$fe,$fe,$fe,$d7,$e7,$e8,$e7 + .byte $00,$00,$00,$00,$f9,$f9,$f9,$f9,$fe,$fe,$fe,$fe,$e8,$e7,$e8,$e7 + .byte $4c,$4d,$fb,$e6,$4c,$4d,$fb,$e6,$4c,$4d,$cb,$cc,$4c,$4d,$fb,$db ; jungle bg boss bomb turret frame #$00 (boss_bomb_turret_supertile_tbl) + .byte $4c,$4d,$fb,$e6,$4c,$4d,$fb,$e6,$4c,$4d,$ed,$ee,$4c,$4d,$fb,$db ; jungle bg boss bomb turret frame #$01 (boss_bomb_turret_supertile_tbl) + .byte $4c,$4d,$fb,$e6,$4c,$4d,$fb,$e6,$4c,$4d,$fb,$f0,$4c,$4d,$fb,$db ; jungle bg boss bomb turret destroy (boss_bomb_turret_supertile_tbl) + .byte $b7,$be,$bf,$c9,$b7,$be,$bf,$d9,$b7,$cd,$ce,$cf,$dc,$dd,$de,$df ; wall bg boss bomb turret frame #$00 (boss_bomb_turret_supertile_tbl) + .byte $b7,$be,$bf,$c9,$b7,$be,$bf,$d9,$b7,$cb,$ef,$cf,$dc,$dd,$de,$df ; wall bg boss bomb turret frame #$01 (boss_bomb_turret_supertile_tbl) + .byte $b7,$be,$bf,$c9,$b7,$be,$bf,$d9,$b7,$be,$f1,$f2,$dc,$dd,$de,$df ; wall bg boss bomb turret destroy (boss_bomb_turret_supertile_tbl) + +; palette data - level 1 (#$70 bytes) +; 1 byte per super-tile +; xx.. .... color for lower-right corner +; ..xx .... color for lower-left corner +; .... xx.. color for upper-right corner +; .... ..xx color for upper-left corner +level_1_palette_data: + .byte $50,$cc,$ff,$aa,$05,$ff,$ff,$55,$f5,$f5,$f5,$f5,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$dc,$ff,$ff,$70,$cc,$ff,$ff,$00,$00,$00,$ff + .byte $ff,$ff,$af,$ab,$00,$08,$aa,$aa,$00,$aa,$ee,$bf,$00,$00,$aa,$aa + .byte $8c,$ef,$ff,$ff,$c8,$fe,$ff,$aa,$00,$f0,$ff + +; each byte is the palette for an entire super-tile +; updates values in the attribute table +level_1_nametable_update_palette_data: + .byte $aa,$aa,$aa,$aa,$aa,$aa,$aa,$aa,$aa,$aa,$aa,$aa,$aa,$aa,$aa,$aa + .byte $aa,$aa,$aa,$aa,$a5,$a0,$55,$00,$aa,$aa,$aa,$aa,$aa,$aa,$cc,$ff + .byte $ff,$ff,$cc,$ff,$ff,$ff,$cc,$cc,$cc,$ff,$ff,$ff,$aa,$aa,$aa,$aa + .byte $aa,$aa,$aa,$aa,$aa + +; nametable animation pattern table tiles - level 2/4 (#$b * #$5 = #$37 bytes) +; byte 0 clear specifies to use the default of #$02 rows of #$02 pattern table tiles each row +; bytes 1 - 4 are the pattern table tiles to draw +; CPU address $86e1 +level_2_4_tile_animation: + .byte $00,$e2,$e3,$e4,$e5 ; #$80 core plating + .byte $00,$e6,$e7,$e8,$e9 ; #$81 core plating - cracked + .byte $00,$ea,$eb,$ec,$ed ; #$82 core plating - more cracks + .byte $00,$de,$df,$e0,$e1 ; #$83 core - destroyed + .byte $00,$ee,$ef,$f0,$f1 ; #$84 wall turret / core - closed + .byte $00,$f2,$f3,$f4,$f5 ; #$85 wall turret / core - opening frame 1 + .byte $00,$f6,$f7,$f8,$f9 ; #$86 core - opening frame 2 + .byte $00,$ca,$cb,$cc,$cd ; #$87 core - open + .byte $00,$fa,$fb,$53,$54 ; #$88 wall turret - opening frame 2 + .byte $00,$ce,$cf,$d0,$d1 ; #$89 wall turret - open + .byte $00,$d2,$d3,$d4,$d5 ; #$8a big core + +; super-tile data - level 2/4 (#$76 * #$10 = #$760 bytes) +level_2_supertile_data: +level_4_supertile_data: + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ; blank super-tile + .byte $00,$00,$00,$00,$0a,$0a,$0a,$0a,$0b,$00,$68,$60,$69,$60,$7f,$01 + .byte $00,$00,$68,$61,$68,$60,$01,$62,$01,$01,$01,$62,$13,$14,$15,$16 + .byte $17,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01 + .byte $11,$18,$00,$00,$12,$01,$10,$18,$12,$01,$01,$01,$66,$65,$64,$63 + .byte $00,$00,$00,$00,$0a,$0a,$0a,$0a,$10,$18,$00,$0b,$01,$2f,$10,$19 + .byte $00,$00,$00,$3b,$0a,$0a,$0a,$3c,$0b,$0b,$0b,$3d,$0d,$0d,$0d,$3e + .byte $8b,$00,$00,$00,$8c,$0a,$0a,$0a,$8d,$0b,$0b,$0b,$8e,$0d,$0d,$0d + .byte $01,$01,$01,$67,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01 + .byte $66,$1d,$1e,$1f,$01,$2e,$01,$31,$01,$30,$01,$31,$01,$30,$01,$31 + .byte $02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02 + .byte $6f,$6e,$6d,$16,$81,$01,$7e,$01,$81,$01,$80,$01,$81,$01,$80,$01 + .byte $02,$02,$02,$02,$a2,$a7,$02,$02,$02,$a9,$02,$02,$02,$a9,$02,$02 + .byte $01,$30,$01,$31,$01,$30,$77,$70,$01,$72,$78,$35,$74,$73,$33,$02 + .byte $02,$02,$02,$02,$06,$06,$06,$06,$03,$03,$03,$03,$02,$02,$02,$02 + .byte $81,$01,$80,$01,$20,$27,$80,$01,$85,$28,$22,$01,$02,$83,$23,$24 + .byte $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01 + .byte $02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$09,$09,$09,$09 + .byte $04,$04,$83,$01,$fd,$fd,$fd,$ff,$05,$05,$05,$05,$fd,$fd,$fd,$fd + .byte $26,$27,$01,$01,$fc,$40,$90,$01,$84,$01,$89,$24,$fd,$40,$90,$62 + .byte $04,$04,$04,$04,$fd,$fd,$fd,$fd,$05,$05,$05,$05,$fd,$fd,$fd,$fd + .byte $01,$01,$77,$76,$01,$40,$90,$fc,$74,$39,$01,$34,$12,$40,$90,$fd + .byte $01,$33,$04,$04,$fe,$fd,$fd,$fd,$05,$05,$05,$05,$fd,$fd,$fd,$fd + .byte $02,$02,$8a,$62,$02,$02,$02,$86,$02,$02,$02,$02,$09,$09,$09,$09 + .byte $12,$3a,$02,$02,$36,$02,$02,$02,$02,$02,$02,$02,$09,$09,$09,$09 + +level_2_nametable_update_supertile_data: +level_4_nametable_update_supertile_data: + .byte $5a,$5b,$5b,$5b,$5d,$00,$00,$00,$5d,$00,$00,$00,$5d,$00,$00,$00 ; #$00 - top left back wall destroyed + .byte $5b,$5b,$5b,$5c,$00,$00,$00,$5e,$00,$00,$00,$5e,$00,$00,$00,$5e ; #$01 - top right back wall destroyed + .byte $5d,$00,$00,$00,$4b,$5f,$5f,$5f,$03,$03,$03,$03,$02,$02,$02,$02 ; #$02 - bottom left back wall destroyed + .byte $00,$00,$00,$5e,$5f,$5f,$5f,$9b,$03,$03,$03,$03,$02,$02,$02,$02 ; #$03 - bottom right back wall destroyed + .byte $0a,$0a,$0a,$0a,$00,$00,$00,$00,$1b,$18,$0c,$0c,$2f,$01,$10,$0e + .byte $0a,$0a,$0a,$3b,$00,$00,$00,$3c,$0c,$0c,$0c,$3d,$0e,$0e,$0e,$3f + .byte $8b,$0a,$0a,$0a,$8c,$00,$00,$00,$8d,$0c,$0c,$0c,$8f,$0e,$0e,$0e + .byte $0a,$0a,$0a,$0a,$00,$00,$00,$00,$0c,$0c,$68,$6b,$0e,$60,$01,$7f + .byte $1c,$65,$64,$4c,$2e,$01,$01,$4c,$30,$01,$01,$4c,$30,$01,$01,$4c + .byte $59,$59,$59,$59,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $02,$02,$83,$62,$02,$02,$02,$86,$04,$04,$04,$04,$09,$09,$09,$09 + .byte $9c,$14,$15,$6c,$9c,$01,$01,$7e,$9c,$01,$01,$80,$9c,$01,$01,$80 + .byte $30,$01,$01,$4c,$30,$01,$77,$4c,$30,$79,$78,$4d,$7b,$7a,$34,$05 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$ac,$ac,$ac,$ac,$05,$05,$05,$05 + .byte $9c,$01,$01,$80,$9c,$27,$01,$80,$9d,$28,$29,$80,$05,$84,$2a,$2b + .byte $01,$01,$77,$76,$01,$79,$78,$01,$74,$7a,$01,$33,$12,$01,$33,$02 + .byte $2f,$33,$02,$02,$33,$04,$04,$04,$02,$02,$02,$02,$02,$02,$02,$02 + .byte $02,$02,$02,$02,$04,$04,$04,$04,$02,$02,$02,$02,$02,$02,$02,$02 + .byte $02,$02,$83,$7f,$04,$04,$04,$83,$02,$02,$02,$02,$02,$02,$02,$02 + .byte $26,$27,$01,$01,$01,$28,$29,$01,$83,$01,$2a,$24,$02,$83,$01,$62 + .byte $12,$33,$02,$02,$36,$02,$02,$02,$04,$04,$04,$04,$09,$09,$09,$09 + .byte $02,$02,$02,$02,$02,$02,$02,$02,$04,$04,$04,$04,$09,$09,$09,$09 + .byte $11,$18,$00,$00,$12,$01,$1b,$18,$12,$01,$2f,$01,$66,$65,$1a,$63 + .byte $00,$00,$00,$00,$00,$0b,$0b,$0b,$10,$18,$00,$00,$01,$01,$02,$ae + .byte $00,$00,$00,$3b,$0b,$0b,$0b,$3c,$00,$00,$00,$3d,$ad,$ae,$ad,$ae + .byte $8b,$00,$00,$00,$8c,$0b,$0b,$0b,$8d,$00,$00,$00,$ad,$ae,$ad,$ae + .byte $00,$00,$00,$00,$0b,$0b,$0b,$00,$00,$00,$68,$60,$ae,$02,$01,$01 + .byte $00,$00,$68,$61,$68,$6b,$01,$62,$01,$7f,$01,$62,$13,$6a,$15,$16 + .byte $01,$01,$2e,$67,$01,$01,$2e,$01,$01,$01,$30,$01,$01,$01,$30,$01 + .byte $66,$65,$4e,$00,$01,$01,$4f,$00,$01,$01,$4e,$00,$01,$01,$4f,$00 + .byte $00,$41,$00,$00,$00,$00,$43,$52,$00,$00,$45,$00,$00,$00,$44,$58 + .byte $00,$00,$91,$00,$52,$93,$00,$00,$00,$95,$00,$00,$58,$94,$00,$00 + .byte $00,$9e,$15,$16,$00,$9e,$01,$01,$00,$9f,$01,$01,$00,$9f,$01,$01 + .byte $17,$7e,$01,$01,$01,$7e,$01,$01,$01,$80,$01,$01,$01,$80,$01,$01 + .byte $01,$01,$30,$01,$01,$01,$30,$01,$01,$01,$30,$01,$01,$01,$30,$01 + .byte $01,$01,$4e,$00,$01,$01,$4f,$00,$01,$79,$4e,$00,$74,$7a,$02,$b1 + .byte $00,$42,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$b0,$b1,$b0,$b2 + .byte $00,$00,$92,$00,$00,$00,$00,$00,$00,$00,$00,$00,$b0,$b1,$b0,$b2 + .byte $00,$9e,$01,$01,$00,$9f,$01,$01,$00,$9e,$29,$01,$b1,$02,$2a,$24 + .byte $01,$80,$01,$01,$01,$80,$01,$01,$01,$80,$01,$01,$01,$80,$01,$01 + .byte $01,$01,$7d,$76,$01,$79,$7c,$01,$74,$7a,$2f,$34,$12,$01,$33,$02 + .byte $01,$33,$07,$07,$33,$02,$02,$02,$05,$05,$05,$05,$02,$02,$02,$02 + .byte $07,$07,$07,$07,$02,$02,$02,$02,$05,$05,$05,$05,$02,$02,$02,$02 + .byte $07,$07,$83,$01,$02,$02,$02,$83,$05,$05,$05,$05,$02,$02,$02,$02 + .byte $26,$2d,$01,$01,$01,$2c,$29,$01,$84,$7f,$2a,$24,$02,$83,$01,$62 + .byte $12,$33,$02,$02,$36,$02,$02,$02,$02,$02,$02,$02,$09,$09,$09,$09 + .byte $02,$02,$83,$62,$02,$02,$02,$86,$02,$02,$02,$02,$09,$09,$09,$09 + .byte $11,$18,$00,$00,$12,$01,$10,$37,$12,$01,$01,$38,$66,$65,$64,$38 + .byte $00,$00,$00,$3b,$0f,$0f,$0f,$3c,$b3,$b4,$b5,$b3,$00,$00,$00,$00 + .byte $8b,$00,$00,$00,$8c,$0f,$0f,$0f,$b3,$b4,$b5,$b3,$00,$00,$00,$00 + .byte $01,$01,$01,$38,$01,$01,$01,$38,$01,$01,$01,$38,$01,$01,$01,$38 + .byte $51,$00,$00,$00,$50,$00,$00,$00,$25,$00,$00,$00,$51,$00,$00,$00 + .byte $46,$47,$57,$57,$48,$49,$00,$00,$48,$49,$00,$00,$48,$49,$00,$00 + .byte $57,$57,$97,$96,$00,$00,$99,$98,$00,$00,$99,$98,$00,$00,$99,$98 + .byte $00,$00,$00,$a1,$00,$00,$00,$a0,$00,$00,$00,$75,$00,$00,$00,$a1 + .byte $88,$01,$01,$01,$88,$01,$01,$01,$88,$01,$01,$01,$88,$01,$01,$01 + .byte $48,$4a,$52,$52,$4a,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $52,$52,$9a,$98,$00,$00,$00,$9a,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $01,$01,$77,$38,$01,$79,$71,$38,$74,$7a,$01,$32,$12,$01,$33,$02 + .byte $51,$00,$00,$00,$02,$b7,$b8,$b6,$08,$08,$08,$08,$02,$02,$02,$02 + .byte $00,$00,$00,$00,$b8,$b6,$b7,$b8,$08,$08,$08,$08,$02,$02,$02,$02 + .byte $00,$00,$00,$a1,$b6,$b7,$b8,$02,$08,$08,$08,$08,$02,$02,$02,$02 + .byte $88,$27,$01,$01,$88,$21,$29,$01,$82,$01,$2a,$24,$02,$83,$01,$62 + .byte $00,$00,$00,$00,$0f,$0f,$0f,$0f,$02,$b3,$b3,$b4,$50,$00,$00,$00 + .byte $00,$00,$00,$00,$0f,$0f,$0f,$0f,$b4,$b5,$b5,$02,$00,$00,$00,$a0 + .byte $00,$00,$68,$61,$87,$60,$01,$62,$88,$01,$01,$62,$88,$14,$15,$16 + .byte $0a,$0a,$68,$61,$68,$60,$01,$62,$01,$01,$01,$62,$13,$14,$15,$16 + .byte $11,$18,$0a,$0a,$12,$01,$10,$18,$12,$01,$01,$01,$66,$65,$64,$63 + .byte $bf,$b9,$ba,$bb,$c0,$bc,$bd,$be,$bf,$c0,$c1,$c6,$c0,$ba,$bd,$c0 + .byte $c8,$bc,$bd,$be,$c8,$c0,$c1,$bb,$c9,$ba,$bd,$ba,$c8,$c0,$bf,$c0 + .byte $c4,$b9,$c5,$c4,$c0,$bc,$bd,$be,$bf,$c0,$c1,$c6,$c1,$ba,$bd,$c0 + .byte $bf,$b9,$ba,$c6,$c5,$bc,$bd,$c7,$bb,$c0,$c1,$c6,$c0,$ba,$bd,$c7 + .byte $bf,$b9,$ba,$bb,$c0,$bc,$bd,$be,$bf,$c0,$c1,$bb,$c3,$c2,$c3,$c2 + .byte $bf,$b9,$ba,$bb,$c0,$bc,$bd,$be,$bf,$c0,$c1,$bb,$c0,$ba,$bd,$c1 + .byte $bf,$b9,$ba,$bb,$c0,$bc,$bd,$be,$bf,$c0,$b9,$c1,$c9,$bf,$bc,$bb + .byte $b9,$be,$c8,$c9,$bd,$00,$c2,$bd,$c6,$c7,$bf,$bd,$c8,$c9,$bf,$be + .byte $bd,$be,$be,$bc,$bb,$be,$be,$bf,$c8,$c9,$c5,$c0,$c1,$c1,$c1,$c1 + .byte $c0,$bd,$bf,$b9,$c9,$bd,$bf,$bd,$c0,$bb,$be,$bc,$c1,$c1,$c1,$c1 + .byte $c7,$c0,$c0,$c4,$c5,$bd,$00,$c4,$c9,$c0,$c0,$c4,$00,$bb,$be,$c4 + .byte $be,$ba,$00,$c4,$c2,$bd,$c0,$c4,$bf,$bd,$c0,$c4,$bf,$bd,$00,$c4 + .byte $02,$02,$02,$02,$02,$02,$a6,$a2,$02,$02,$a8,$02,$02,$02,$a8,$02 + .byte $02,$02,$a8,$02,$06,$06,$aa,$06,$03,$03,$03,$03,$02,$02,$02,$02 + .byte $02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$d6,$02,$02,$da,$d2 + .byte $02,$02,$db,$d4,$06,$06,$06,$d8,$03,$03,$03,$03,$02,$02,$02,$02 + .byte $d5,$dd,$02,$02,$d9,$06,$06,$06,$03,$03,$03,$03,$02,$02,$02,$02 + .byte $02,$02,$02,$02,$02,$02,$02,$02,$d7,$02,$02,$02,$d3,$dc,$02,$02 + .byte $02,$a9,$02,$02,$06,$ab,$06,$06,$03,$03,$03,$03,$02,$02,$02,$02 + .byte $06,$06,$aa,$06,$55,$a4,$56,$a3,$03,$03,$03,$03,$02,$02,$02,$02 + .byte $06,$ab,$06,$06,$a4,$56,$a3,$a5,$03,$03,$03,$03,$02,$02,$02,$02 + .byte $c1,$c1,$c1,$c1,$b9,$be,$ba,$be,$c6,$c7,$bd,$c2,$c8,$c9,$bb,$bf + .byte $c1,$c1,$c1,$c1,$be,$ba,$c5,$00,$c0,$bd,$c0,$c0,$be,$bb,$be,$be + .byte $c3,$c6,$c7,$c0,$c3,$c8,$c9,$c5,$c3,$be,$be,$ba,$c3,$00,$c2,$bd + .byte $c3,$c5,$bf,$bb,$c3,$be,$bf,$be,$c3,$00,$bf,$bf,$c3,$c0,$c0,$c0 + +; palette data - level 2/4 (#$76 bytes) +; 1 byte per block +; xx.. .... color for lower-right corner +; ..xx .... color for lower-left corner +; .... xx.. color for upper-right corner +; .... ..xx color for upper-left corner +level_2_palette_data: +level_4_palette_data: + .byte $55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$ff,$55,$55,$55 + .byte $55,$55,$55,$55,$55,$55,$55,$55,$55 + +; each byte is the palette for an entire super-tile +; updates values in the attribute table +level_2_nametable_update_palette_data: +level_4_nametable_update_palette_data: + .byte $55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55 + .byte $55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55 + .byte $55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55 + .byte $55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55 + .byte $55,$55,$55,$55,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $ff,$5f,$ff,$5f,$5f,$ff,$5f,$5f,$5f,$00,$00,$00,$00,$00,$00,$00 + .byte $00,$00,$00,$00,$00,$00,$00 + +; CPU address $8ef8 +level_3_supertile_data: + .byte $01,$02,$01,$02,$11,$12,$11,$12,$44,$45,$46,$47,$54,$55,$56,$57 + .byte $40,$41,$42,$43,$50,$51,$52,$53,$01,$02,$01,$02,$11,$12,$11,$12 + .byte $00,$00,$7c,$7d,$00,$8b,$8c,$00,$8b,$9b,$9c,$00,$aa,$ab,$00,$8b + .byte $00,$00,$00,$00,$00,$00,$00,$00,$7c,$7d,$00,$00,$8c,$00,$00,$00 + .byte $01,$02,$01,$02,$11,$12,$11,$12,$0c,$0d,$0e,$0f,$1c,$1d,$1e,$1f + .byte $08,$09,$0a,$0b,$18,$19,$1a,$1b,$01,$02,$01,$02,$11,$12,$11,$12 + .byte $08,$09,$0a,$0b,$18,$19,$1a,$1b,$0c,$0d,$0e,$0f,$1c,$1d,$1e,$1f + .byte $03,$04,$04,$04,$d1,$d2,$d2,$d2,$be,$3f,$3e,$3f,$d4,$5f,$5e,$5f + .byte $04,$05,$05,$04,$d2,$32,$32,$d2,$3e,$3f,$3e,$3f,$5e,$5f,$5e,$5f + .byte $04,$04,$04,$06,$d2,$d2,$d2,$33,$3e,$3f,$3e,$bd,$5e,$5f,$5f,$c3 + .byte $10,$00,$07,$38,$00,$00,$00,$00,$00,$00,$00,$3a,$39,$00,$00,$d5 + .byte $be,$35,$36,$37,$d4,$5f,$5e,$5f,$d3,$4f,$4e,$4f,$d4,$5f,$5e,$5f + .byte $34,$35,$36,$37,$5e,$5f,$5e,$5f,$4e,$4f,$4e,$4f,$5e,$5f,$5e,$5f + .byte $34,$35,$36,$bd,$5e,$5f,$5e,$c3,$4e,$4f,$4e,$c9,$5e,$5f,$5e,$c3 + .byte $4e,$4f,$4e,$c9,$5e,$5f,$5e,$c3,$4e,$4f,$4e,$c9,$5e,$5f,$5e,$c3 + .byte $d3,$4f,$4e,$4f,$d4,$5f,$5e,$5f,$d3,$4f,$4e,$4f,$d4,$5f,$5e,$5f + .byte $ea,$eb,$aa,$ab,$ff,$98,$da,$d6,$fc,$70,$ea,$eb,$c6,$dc,$ff,$98 + .byte $d3,$4f,$4e,$4f,$d4,$5f,$5e,$5f,$01,$02,$01,$02,$11,$12,$11,$12 + .byte $4e,$4f,$4e,$4f,$5e,$5f,$5e,$5f,$01,$02,$01,$02,$11,$12,$11,$12 + .byte $4a,$4b,$4c,$4d,$5a,$5b,$5c,$5d,$d0,$bf,$cc,$cd,$ca,$cb,$ce,$cf + .byte $49,$4b,$4c,$4d,$59,$5b,$5c,$5d,$48,$bf,$cc,$cd,$58,$cb,$ce,$cf + .byte $34,$35,$36,$37,$5e,$5f,$5e,$5f,$d3,$4f,$4e,$4f,$d4,$5f,$5e,$5f + .byte $40,$41,$42,$43,$3b,$51,$52,$53,$c4,$00,$3c,$47,$5e,$c5,$3d,$57 + .byte $d3,$4f,$4e,$c9,$d4,$5f,$5e,$c3,$4e,$4f,$4e,$c9,$5e,$5f,$5e,$c3 + .byte $40,$41,$42,$43,$50,$51,$52,$c2,$44,$c1,$00,$c8,$54,$c0,$c7,$5f + .byte $4e,$4f,$4e,$c9,$5e,$5f,$5e,$c3,$01,$02,$01,$02,$11,$12,$11,$12 + .byte $4e,$4f,$4e,$4f,$5e,$5f,$5e,$5f,$d3,$4f,$4e,$4f,$d4,$5f,$5e,$5f + .byte $3e,$3f,$3e,$3f,$5e,$5f,$5e,$5f,$4e,$4f,$4e,$4f,$5e,$5f,$5e,$5f + .byte $4e,$4f,$4e,$4f,$5e,$5f,$5e,$5f,$4e,$4f,$4e,$c9,$5e,$5f,$5e,$c3 + .byte $3e,$3f,$3e,$3f,$5e,$5f,$5e,$5f,$01,$02,$01,$02,$11,$12,$11,$12 + .byte $08,$09,$0a,$0b,$18,$19,$1a,$15,$0c,$0d,$14,$00,$1c,$13,$00,$00 + .byte $08,$09,$0a,$0b,$00,$19,$1a,$1b,$00,$16,$17,$0f,$00,$00,$00,$1f + .byte $08,$09,$00,$00,$18,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $40,$41,$42,$43,$50,$51,$52,$c2,$01,$02,$01,$02,$11,$12,$11,$12 + .byte $e6,$e7,$e8,$e9,$f7,$f7,$f6,$d9,$f7,$f7,$fa,$fb,$62,$f7,$f7,$9f + .byte $01,$02,$01,$02,$11,$12,$11,$12,$00,$16,$17,$0f,$00,$00,$00,$1f + .byte $3e,$3f,$3e,$3f,$5e,$5f,$5e,$5f,$d3,$4f,$4e,$4f,$d4,$5f,$5e,$5f + .byte $d3,$4f,$4e,$4f,$d4,$5f,$5e,$5f,$d3,$4f,$4e,$c9,$d4,$5f,$5e,$c3 + .byte $4e,$4f,$c4,$c6,$5e,$5f,$5e,$c3,$4e,$4f,$4e,$c9,$5e,$5f,$5e,$c3 + .byte $d3,$4f,$4e,$c9,$d4,$5f,$5e,$c3,$d3,$4f,$4e,$c9,$d4,$5f,$5e,$c3 + .byte $4e,$4f,$4e,$c9,$5e,$5f,$5e,$c3,$4e,$4f,$4e,$4f,$5e,$5f,$5e,$5f + .byte $40,$41,$42,$43,$50,$51,$52,$c2,$34,$35,$36,$37,$5e,$5f,$5e,$5f + .byte $d3,$4f,$4e,$4f,$d4,$5f,$5e,$5f,$4e,$4f,$4e,$4f,$5e,$5f,$5e,$5f + .byte $40,$41,$42,$43,$50,$51,$52,$53,$be,$35,$36,$37,$d4,$5f,$5e,$5f + .byte $40,$41,$42,$43,$50,$51,$52,$53,$34,$35,$36,$bd,$5e,$5f,$5e,$c3 + .byte $4e,$4f,$c4,$c6,$5e,$5f,$5e,$5f,$4e,$4f,$4e,$4f,$5e,$5f,$5e,$5f + .byte $c4,$c6,$4e,$4f,$5e,$5f,$5e,$5f,$4e,$4f,$4e,$4f,$5e,$5f,$5e,$5f + .byte $c4,$c6,$4e,$4f,$d4,$5f,$5e,$5f,$d3,$4f,$4e,$4f,$d4,$5f,$5e,$5f + .byte $08,$09,$dc,$c3,$18,$19,$dc,$c3,$0c,$0d,$dc,$c3,$1c,$1d,$dc,$c3 + .byte $b8,$f7,$f7,$62,$b8,$f7,$f7,$f7,$4c,$4d,$f7,$72,$fa,$70,$70,$82 + .byte $62,$f7,$f7,$9f,$f7,$f7,$f7,$9f,$79,$f7,$4e,$4f,$89,$70,$70,$f3 + .byte $c6,$dc,$0a,$0b,$c6,$dc,$1a,$1b,$c6,$dc,$0e,$0f,$c6,$dc,$1e,$1f + .byte $f7,$dc,$dc,$92,$f7,$dc,$dc,$69,$f7,$dc,$dc,$f7,$f7,$dc,$dc,$f7 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$91,$9a,$00,$00,$50,$5a + .byte $00,$f4,$f5,$51,$c8,$c5,$c5,$55,$51,$cd,$93,$6d,$52,$53,$54,$55 + .byte $49,$f8,$f9,$00,$56,$6e,$6e,$6f,$46,$47,$48,$49,$56,$57,$58,$59 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$4a,$4b,$00,$00,$5a,$5b,$00,$00 + .byte $00,$00,$00,$00,$60,$61,$00,$00,$00,$71,$80,$00,$00,$81,$90,$80 + .byte $f0,$63,$64,$65,$00,$f0,$74,$75,$00,$00,$84,$85,$00,$c8,$94,$95 + .byte $66,$67,$63,$98,$76,$77,$98,$00,$86,$87,$00,$00,$96,$97,$6f,$00 + .byte $00,$00,$00,$00,$00,$00,$7c,$7d,$00,$8b,$8c,$00,$8b,$9b,$9c,$00 + .byte $00,$00,$00,$00,$00,$00,$60,$61,$00,$00,$00,$71,$00,$00,$00,$81 + .byte $73,$b0,$b1,$a1,$83,$c0,$c1,$db,$80,$00,$ea,$eb,$90,$80,$d5,$d1 + .byte $08,$09,$0a,$0b,$18,$19,$1a,$1b,$0c,$0d,$0e,$0f,$1c,$1d,$1e,$1f + .byte $08,$09,$fd,$fe,$18,$19,$1a,$1b,$0c,$0d,$0e,$0f,$1c,$1d,$1e,$1f + .byte $aa,$ba,$b0,$bb,$d0,$ca,$c0,$cb,$e0,$e1,$00,$8b,$da,$d6,$8b,$9b + .byte $00,$00,$00,$00,$7c,$7d,$00,$00,$8c,$00,$00,$00,$00,$00,$00,$00 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$60,$61,$00,$00,$00,$71 + .byte $60,$61,$00,$00,$00,$71,$80,$00,$00,$81,$90,$80,$80,$00,$a0,$a1 + .byte $a0,$a1,$e0,$e1,$d5,$d1,$f0,$5c,$e0,$e1,$70,$f1,$f0,$5c,$dc,$c3 + .byte $e2,$e3,$e4,$e5,$b3,$68,$f7,$f7,$f2,$f3,$f7,$f7,$b8,$f7,$f7,$62 + +level_3_nametable_update_supertile_data: + .byte $20,$21,$21,$22,$26,$30,$31,$27,$26,$30,$31,$27,$23,$24,$24,$25 ; #$00 - pill box sensor closed + .byte $20,$21,$21,$22,$26,$2c,$2d,$27,$26,$2e,$2f,$27,$23,$24,$24,$25 ; #$01 - pill box sensor partially open + .byte $20,$21,$21,$22,$26,$28,$29,$27,$26,$2a,$2b,$27,$23,$24,$24,$25 ; #$02 - pill box sensor open + .byte $20,$21,$21,$22,$26,$96,$97,$27,$26,$97,$96,$27,$23,$24,$24,$25 ; #$03 - rotating gun closed + .byte $20,$21,$21,$22,$26,$94,$95,$27,$26,$95,$94,$27,$23,$24,$24,$25 ; #$04 - rotating gun opening + .byte $20,$21,$21,$22,$8d,$8e,$8f,$27,$9d,$9e,$9f,$27,$23,$24,$24,$25 ; #$05 - rotating gun facing left + .byte $20,$21,$21,$22,$8a,$8b,$8c,$27,$26,$9b,$9c,$27,$23,$24,$24,$25 ; #$06 - rotating gun facing left-up + .byte $20,$82,$21,$22,$26,$92,$93,$27,$26,$a2,$a3,$27,$23,$24,$24,$25 ; #$07 - rotating gun facing left-up (closer to up) + .byte $20,$66,$67,$22,$26,$76,$77,$27,$26,$86,$87,$27,$23,$24,$24,$25 ; #$08 - rotating gun facing up + .byte $20,$21,$81,$22,$26,$90,$91,$27,$26,$a0,$a1,$27,$23,$24,$24,$25 ; #$09 - rotating gun facing up up right (closer to up) + .byte $20,$21,$21,$22,$26,$68,$69,$6a,$26,$78,$79,$27,$23,$24,$24,$25 ; #$0a - rotating gun facing up right + .byte $20,$21,$21,$22,$26,$6d,$6e,$6f,$26,$7d,$7e,$7f,$23,$24,$24,$25 ; #$0b - rotating gun facing right + .byte $20,$21,$21,$22,$26,$88,$89,$27,$26,$98,$99,$9a,$23,$24,$24,$25 ; #$0c - rotating gun facing right down + .byte $20,$21,$21,$22,$26,$62,$63,$27,$26,$72,$73,$27,$23,$24,$83,$25 ; #$0d - rotating gun facing right down down (closer to down) + .byte $20,$21,$21,$22,$26,$64,$65,$27,$26,$74,$75,$27,$23,$84,$85,$25 ; #$0e - rotating gun facing down + .byte $20,$21,$21,$22,$26,$60,$61,$27,$26,$70,$71,$27,$23,$80,$24,$25 ; #$0f - rotating gun facing down left + .byte $20,$21,$21,$22,$26,$6b,$6c,$27,$7a,$7b,$7c,$27,$23,$24,$24,$25 ; #$10 - rotating gun facing down left left (closer to left) + .byte $a4,$a5,$a6,$a7,$b4,$b5,$b6,$b7,$b0,$b1,$b2,$b3,$ac,$ad,$ae,$af ; #$11 - red turret facing left + .byte $bc,$a5,$a6,$a7,$a8,$a9,$b6,$b7,$b8,$b9,$b2,$b3,$ac,$ad,$ae,$af ; #$12 - red turret facing up left + .byte $aa,$ab,$a6,$a7,$ba,$bb,$b6,$b7,$b8,$b9,$b2,$b3,$ac,$ad,$ae,$af ; #$13 - red turret facing up up left (almost straight up) + .byte $40,$41,$42,$43,$50,$51,$52,$53,$a4,$a5,$a6,$a7,$b4,$b5,$b6,$b7 ; #$14 - red turret 1/2 rising from ground rocky background + .byte $4e,$4f,$4e,$4f,$5e,$5f,$5e,$5f,$a4,$a5,$a6,$a7,$b4,$b5,$b6,$b7 ; #$15 - red turret 1/2 rising from ground waterfall background + .byte $40,$41,$42,$43,$50,$51,$52,$53,$44,$45,$46,$47,$54,$55,$56,$57 ; #$16 - red turret and rotating gun rock background + .byte $4e,$4f,$4e,$4f,$5e,$5f,$5e,$5f,$4e,$4f,$4e,$4f,$5e,$5f,$5e,$5f ; #$17 - red turret waterfall background + .byte $00,$00,$00,$00,$a4,$a5,$a6,$a7,$b4,$b5,$b6,$b7,$b0,$b1,$b2,$b3 ; #$18 - red turret 3/4 rising from ground black background + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ; #$19 - blank super-tile + .byte $00,$00,$00,$00,$00,$00,$00,$be,$00,$00,$be,$dd,$00,$00,$ec,$ed + .byte $be,$00,$00,$00,$ce,$00,$00,$00,$de,$df,$00,$00,$ee,$ef,$00,$00 + .byte $e2,$e3,$5d,$00,$b3,$8f,$5f,$00,$f2,$8f,$5d,$00,$b8,$af,$00,$00 + .byte $5d,$5e,$e8,$e9,$00,$8f,$5f,$d9,$00,$7f,$5f,$fb,$00,$00,$7f,$9f + .byte $b8,$5d,$00,$00,$b8,$00,$00,$00,$4c,$5e,$00,$00,$fa,$70,$6c,$00 + .byte $00,$00,$5f,$9f,$00,$00,$00,$9f,$00,$00,$5d,$4f,$00,$00,$6c,$f3 + .byte $64,$65,$66,$67,$74,$75,$76,$77,$84,$85,$86,$87,$94,$95,$96,$97 ; #$20 - boss mouth closed (top half) + .byte $a4,$a5,$a6,$a7,$b4,$b5,$b6,$b7,$c4,$b5,$b6,$c7,$d4,$a6,$a5,$d7 ; #$21 - boss mouth closed (bottom half) + .byte $64,$6a,$6b,$67,$74,$7a,$7b,$77,$78,$00,$00,$7e,$88,$8a,$8d,$8e ; #$22 - boss mouth partially open (top half) + .byte $9d,$9e,$ad,$ae,$b4,$b5,$b6,$b7,$c4,$b5,$b6,$c7,$d4,$a6,$a5,$d7 ; #$23 - boss mouth partially open (bottom half) + .byte $64,$bc,$bd,$67,$74,$7a,$7b,$77,$78,$00,$00,$7e,$ac,$00,$00,$bf ; #$24 - boss mouth fully open (top half) + .byte $88,$8a,$8d,$8e,$cc,$9e,$ad,$cf,$c4,$b5,$b6,$c7,$d4,$a6,$a5,$d7 ; #$25 - boss mouth fully open (bottom half) + .byte $f7,$dc,$dc,$af,$f7,$dc,$dc,$69,$f7,$dc,$dc,$f7,$f7,$dc,$dc,$f7 + .byte $6c,$6c,$dc,$f7,$69,$dc,$dc,$f7,$f7,$dc,$dc,$f7,$f7,$dc,$dc,$f7 + .byte $99,$dc,$dc,$f7,$69,$dc,$dc,$f7,$f7,$dc,$dc,$f7,$f7,$dc,$dc,$f7 + .byte $a2,$a3,$a4,$a5,$b2,$a4,$b4,$b5,$c2,$b4,$c4,$b5,$d2,$d3,$d4,$a6 + .byte $a6,$a7,$a8,$a9,$b6,$b7,$a7,$b9,$b6,$c7,$b7,$c9,$a5,$d7,$d8,$d2 + +level_3_palette_data: + .byte $50,$05,$aa,$20,$00,$00,$00,$fa,$fa,$fa,$55,$ff,$ff,$ff,$ff,$ff + .byte $9a,$0f,$0f,$ff,$ff,$ff,$75,$ff,$d5,$0f,$ff,$ff,$ff,$0f,$00,$00 + .byte $00,$05,$55,$00,$ff,$ff,$ff,$ff,$ff,$f5,$ff,$f5,$f5,$ff,$ff,$ff + .byte $55,$55,$55,$55,$55,$aa,$ba,$ea,$aa,$aa,$aa,$aa,$aa,$aa,$aa,$55 + .byte $55,$aa,$aa,$aa,$aa,$6a,$55 + +; each byte is the palette for an entire super-tile +; updates values in the attribute table +level_3_nametable_update_palette_data: + .byte $aa,$aa,$aa,$aa,$aa,$aa,$aa,$aa,$aa,$aa,$aa,$aa,$aa,$aa,$aa,$aa + .byte $aa,$aa,$aa,$aa,$a5,$af,$55,$ff,$aa,$00,$88,$22,$55,$55,$51,$44 + .byte $aa,$aa,$aa,$aa,$aa,$aa,$55,$55,$55,$aa,$aa,$00,$00,$00,$00,$00 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00 + +; CPU address $9698 +level_5_supertile_data: + .byte $69,$69,$69,$69,$69,$69,$69,$69,$69,$69,$69,$69,$69,$69,$69,$69 + .byte $02,$03,$03,$12,$0c,$0d,$0e,$0f,$42,$43,$44,$45,$46,$47,$48,$49 + .byte $05,$12,$04,$0b,$0c,$0d,$0e,$0f,$42,$43,$44,$45,$46,$47,$48,$49 + .byte $4a,$4b,$4c,$4d,$4e,$4f,$50,$51,$4d,$52,$4c,$4f,$49,$4d,$4e,$48 + .byte $48,$4c,$4e,$4f,$aa,$a9,$aa,$a9,$05,$12,$04,$0b,$0c,$0d,$0e,$0f + .byte $69,$69,$69,$69,$69,$69,$69,$b6,$69,$bb,$5e,$b7,$5f,$bc,$60,$b8 + .byte $61,$be,$62,$b9,$bf,$b0,$b2,$b3,$61,$be,$62,$b9,$bf,$b0,$b2,$b3 + .byte $69,$b4,$67,$ac,$00,$ab,$67,$ac,$69,$ab,$67,$ac,$00,$ab,$00,$ac + .byte $a9,$aa,$69,$b6,$69,$69,$69,$b7,$69,$bb,$5e,$b8,$5f,$bc,$60,$b9 + .byte $69,$00,$69,$00,$00,$00,$00,$00,$02,$03,$03,$12,$0c,$0d,$0e,$0f + .byte $42,$43,$4e,$5d,$46,$47,$50,$5b,$4a,$4b,$4a,$a8,$a9,$aa,$aa,$00 + .byte $4a,$4b,$4c,$4d,$a9,$aa,$a9,$aa,$02,$03,$03,$12,$0c,$0d,$0e,$0f + .byte $58,$4b,$4c,$4d,$69,$a9,$a9,$aa,$05,$03,$03,$12,$0c,$0d,$0e,$0f + .byte $42,$43,$44,$45,$46,$47,$48,$49,$4a,$4b,$4c,$4d,$4e,$4f,$50,$51 + .byte $69,$b4,$67,$ac,$69,$ab,$67,$ac,$05,$12,$04,$0b,$0c,$0d,$0e,$0f + .byte $03,$57,$69,$69,$0e,$0f,$69,$69,$44,$59,$69,$69,$4c,$5a,$69,$69 + .byte $f5,$5d,$69,$b6,$50,$5b,$bb,$b8,$f3,$5c,$66,$64,$52,$59,$65,$62 + .byte $42,$43,$44,$49,$46,$47,$4c,$5a,$4a,$4b,$4e,$5d,$a9,$aa,$a9,$a8 + .byte $69,$69,$54,$4d,$69,$69,$55,$4f,$69,$69,$56,$52,$69,$69,$a9,$aa + .byte $4c,$5d,$69,$69,$a9,$a8,$69,$69,$02,$03,$03,$12,$0c,$0d,$0e,$0f + .byte $4e,$5d,$00,$00,$50,$5b,$00,$00,$4a,$5c,$00,$00,$52,$59,$00,$00 + .byte $06,$06,$06,$1a,$11,$11,$11,$11,$f2,$f2,$f2,$f2,$f2,$f2,$f2,$f2 + .byte $42,$43,$44,$45,$46,$47,$48,$49,$4a,$4b,$4c,$4d,$a9,$aa,$a9,$aa + .byte $69,$00,$69,$00,$00,$00,$00,$00,$69,$00,$01,$03,$00,$00,$0c,$0d + .byte $69,$00,$69,$00,$63,$00,$00,$b6,$64,$bb,$5e,$b7,$65,$bc,$60,$b8 + .byte $66,$be,$62,$b9,$ba,$b0,$b2,$b3,$66,$be,$62,$b9,$ba,$b0,$b2,$b3 + .byte $06,$06,$06,$06,$11,$11,$11,$11,$f2,$f2,$f2,$f2,$f2,$f2,$f2,$f2 + .byte $17,$1b,$16,$1c,$11,$11,$d8,$d9,$f2,$f2,$f2,$f2,$f2,$f2,$f2,$f2 + .byte $00,$6a,$6a,$6a,$7b,$7c,$6b,$6c,$6d,$7f,$6e,$80,$84,$85,$86,$87 + .byte $6a,$6a,$69,$00,$6b,$6c,$7d,$00,$6f,$81,$70,$00,$6f,$88,$89,$00 + .byte $71,$8a,$72,$85,$71,$8a,$8d,$8e,$71,$8a,$75,$94,$71,$8a,$00,$95 + .byte $15,$19,$14,$13,$1d,$18,$18,$18,$f2,$f2,$f2,$f2,$f2,$f2,$f2,$f2 + .byte $13,$e4,$13,$e4,$18,$18,$18,$18,$f2,$f2,$f2,$f2,$f2,$f2,$f2,$f2 + .byte $4a,$4b,$4c,$4d,$a9,$aa,$50,$51,$03,$57,$58,$4f,$0e,$0f,$00,$a9 + .byte $01,$03,$03,$12,$0c,$0d,$0e,$0f,$f0,$0d,$f1,$0f,$50,$4c,$48,$49 + .byte $4a,$4b,$4c,$4d,$4e,$4f,$50,$51,$4d,$52,$4c,$4f,$aa,$a9,$aa,$a9 + .byte $f3,$4b,$f5,$5d,$4b,$4c,$50,$5b,$4c,$4d,$f4,$5c,$a9,$aa,$52,$59 + .byte $54,$4b,$4c,$4d,$55,$4f,$4d,$51,$58,$52,$4a,$4f,$00,$a9,$aa,$a9 + .byte $f0,$43,$f1,$45,$46,$47,$48,$49,$f3,$4b,$f4,$4d,$4e,$4f,$50,$51 + .byte $f0,$43,$f1,$5d,$55,$47,$48,$5b,$58,$4b,$f4,$5a,$00,$a9,$aa,$aa + .byte $01,$12,$04,$0b,$0c,$0d,$0e,$0f,$55,$43,$44,$45,$50,$4c,$48,$49 + .byte $42,$43,$44,$45,$46,$47,$48,$49,$58,$4b,$4c,$4d,$00,$a9,$a9,$aa + .byte $54,$4b,$4c,$4d,$55,$4f,$50,$51,$56,$52,$4c,$4f,$55,$4d,$4e,$48 + .byte $02,$03,$03,$57,$0c,$0d,$0e,$0f,$42,$43,$44,$59,$46,$47,$4c,$5a + .byte $4a,$4b,$4e,$5d,$4e,$4f,$50,$5b,$4d,$52,$4a,$a8,$aa,$a9,$aa,$00 + .byte $4a,$4b,$4e,$5d,$4e,$4f,$50,$5b,$4d,$52,$4c,$5c,$49,$4d,$52,$59 + .byte $4a,$4b,$4e,$a8,$a9,$aa,$aa,$00,$02,$03,$03,$12,$0c,$0d,$0e,$0f + .byte $69,$00,$69,$00,$00,$00,$00,$00,$01,$12,$04,$0b,$0c,$0d,$0e,$0f + .byte $69,$00,$69,$00,$00,$00,$00,$00,$02,$03,$03,$57,$0c,$0d,$0e,$0f + .byte $c6,$d7,$c0,$c0,$c6,$d7,$c0,$c0,$c6,$d7,$c0,$c0,$c6,$d7,$c0,$c0 + .byte $48,$4c,$4e,$4f,$aa,$a9,$aa,$a9,$02,$03,$03,$57,$0c,$0d,$0e,$0f + .byte $42,$43,$44,$59,$46,$47,$4c,$5a,$4a,$4b,$4e,$5d,$4e,$4f,$50,$5b + .byte $48,$4c,$47,$4d,$4a,$4a,$4b,$4c,$4e,$4b,$4c,$50,$a9,$aa,$a9,$aa + .byte $c0,$c1,$c1,$c0,$c1,$c1,$c0,$c1,$c0,$c1,$c0,$c0,$c0,$c0,$c0,$c0 + .byte $c2,$c2,$c2,$c2,$c8,$c8,$c8,$c8,$c0,$c0,$c0,$c0,$c0,$c1,$c0,$c1 + .byte $ed,$ec,$ed,$ec,$c0,$c0,$c0,$c1,$c1,$c0,$c0,$c0,$c0,$c0,$c1,$c0 + .byte $63,$00,$00,$bb,$64,$bb,$5e,$b7,$65,$bc,$60,$b8,$66,$be,$64,$b0 + .byte $69,$bb,$5e,$00,$5f,$bc,$60,$bd,$61,$be,$62,$b1,$61,$b0,$b2,$b1 + .byte $c0,$c0,$c0,$c0,$ee,$af,$ef,$c0,$c0,$c0,$c0,$c1,$c1,$c0,$c0,$c0 + .byte $69,$00,$69,$68,$00,$ae,$00,$00,$69,$00,$69,$00,$00,$00,$00,$00 + .byte $68,$00,$69,$00,$00,$00,$00,$68,$69,$68,$69,$00,$00,$00,$00,$ae + .byte $f3,$4b,$f5,$5d,$4e,$4f,$50,$5b,$4d,$52,$f4,$5c,$49,$4d,$52,$59 + .byte $05,$03,$03,$57,$0c,$0d,$0e,$0f,$f0,$0d,$f1,$0f,$46,$47,$48,$59 + .byte $ee,$af,$ef,$c0,$c1,$c0,$c0,$c0,$c0,$c0,$c1,$c0,$c0,$c0,$c0,$c0 + .byte $01,$03,$03,$57,$0c,$0d,$0e,$0f,$f0,$43,$f1,$45,$46,$47,$48,$49 + .byte $00,$00,$69,$b6,$00,$bb,$5e,$b7,$ba,$b0,$5e,$b8,$61,$b0,$60,$b9 + .byte $63,$00,$a9,$aa,$64,$bd,$00,$00,$62,$bb,$5e,$00,$b2,$bc,$60,$bd + .byte $69,$b4,$69,$00,$00,$ab,$b5,$00,$69,$ab,$69,$00,$00,$ab,$b5,$00 + .byte $69,$00,$69,$00,$63,$00,$00,$00,$64,$bb,$5e,$00,$65,$bc,$60,$bd + .byte $66,$be,$62,$b1,$ba,$b0,$b2,$b3,$66,$be,$62,$b1,$ba,$b0,$b2,$b3 + .byte $f6,$4b,$f4,$4d,$55,$4f,$50,$51,$f7,$52,$f4,$4f,$55,$4d,$4e,$48 + .byte $69,$ac,$69,$00,$00,$ac,$00,$00,$05,$12,$04,$0b,$0c,$0d,$0e,$0f + .byte $ea,$c6,$c5,$c0,$ea,$c6,$c5,$c0,$ea,$c6,$c5,$c0,$ea,$c6,$e3,$c0 + .byte $c7,$e8,$c0,$c0,$c3,$e7,$c7,$e8,$c0,$e4,$c3,$e7,$c0,$e4,$c0,$e4 + .byte $69,$d4,$cd,$ce,$de,$d3,$0a,$eb,$cc,$d6,$09,$eb,$c6,$d7,$c0,$c0 + .byte $ce,$ce,$cf,$db,$eb,$1f,$e0,$e1,$e9,$cc,$ca,$c0,$ea,$c6,$e3,$c0 + .byte $fa,$c4,$fe,$69,$e6,$fb,$c4,$fe,$c0,$e6,$fb,$c4,$c0,$c0,$e6,$fb + .byte $69,$00,$69,$00,$00,$00,$00,$00,$69,$69,$f8,$e2,$00,$d1,$d2,$d2 + .byte $69,$00,$69,$00,$00,$00,$00,$00,$f9,$f9,$f9,$f9,$d2,$d2,$d2,$da + .byte $69,$00,$69,$00,$00,$00,$00,$00,$c9,$69,$69,$69,$e5,$fe,$69,$69 + .byte $63,$69,$00,$00,$64,$b6,$63,$00,$65,$be,$62,$b1,$65,$bc,$b2,$b3 + +level_5_nametable_update_supertile_data: + .byte $20,$21,$21,$22,$26,$30,$31,$27,$26,$30,$31,$27,$23,$24,$24,$25 ; #$00 - pill box sensor closed + .byte $20,$21,$21,$22,$26,$2c,$2d,$27,$26,$2e,$2f,$27,$23,$24,$24,$25 ; #$01 - pill box sensor partially open + .byte $20,$21,$21,$22,$26,$28,$29,$27,$26,$2a,$2b,$27,$23,$24,$24,$25 ; #$02 - pill box sensor open + .byte $6c,$6d,$69,$69,$71,$72,$73,$69,$78,$76,$97,$95,$78,$99,$98,$96 ; #$03 - boss ufo top closing (right) (boss_ufo_supertile_update_ptr_tbl_2) + .byte $6c,$6d,$69,$69,$71,$72,$73,$69,$78,$76,$9a,$69,$78,$9b,$98,$96 ; #$04 - boss ufo top closing (right) (boss_ufo_supertile_update_ptr_tbl_2) + .byte $6c,$6d,$69,$69,$71,$72,$9c,$69,$78,$9d,$69,$69,$78,$9e,$98,$96 ; #$05 - boss ufo top closing (right) (boss_ufo_supertile_update_ptr_tbl_2) + .byte $7f,$7e,$7e,$80,$84,$82,$81,$69,$a1,$9f,$85,$69,$a3,$69,$69,$69 ; #$06 - boss ufo - bottom thruster half throttle (right) (boss_ufo_supertile_update_ptr_tbl_3) + .byte $7d,$7e,$7e,$7f,$69,$81,$82,$83,$69,$85,$86,$87,$69,$69,$69,$89 ; #$07 - boss ufo - bottom thruster full throttle (left) (boss_ufo_supertile_update_ptr_tbl/boss_ufo_supertile_update_ptr_tbl_3) + .byte $7f,$7e,$7e,$80,$84,$82,$81,$69,$88,$86,$85,$69,$8a,$69,$69,$69 ; #$08 - boss ufo - bottom thruster full throttle (right) (boss_ufo_supertile_update_ptr_tbl/boss_ufo_supertile_update_ptr_tbl_3) + .byte $69,$69,$6a,$6b,$69,$6e,$6f,$70,$8b,$8d,$76,$77,$8c,$8e,$8f,$77 ; #$09 - boss ufo top closing (left) (boss_ufo_supertile_update_ptr_tbl_2) + .byte $69,$69,$6a,$6b,$69,$6e,$6f,$70,$69,$90,$76,$77,$8c,$8e,$91,$77 ; #$0a - boss ufo top closing (left) (boss_ufo_supertile_update_ptr_tbl_2) + .byte $69,$69,$6a,$6b,$69,$92,$6f,$70,$69,$69,$93,$77,$8c,$8e,$94,$77 ; #$0b - boss ufo top closing (left) (boss_ufo_supertile_update_ptr_tbl_2) + .byte $7d,$7e,$7e,$7f,$69,$81,$82,$83,$69,$85,$9f,$a0,$69,$69,$69,$a2 ; #$0c - boss ufo - bottom thruster half throttle (left) (boss_ufo_supertile_update_ptr_tbl_3) + .byte $69,$69,$6a,$6b,$69,$6e,$6f,$70,$74,$75,$76,$77,$74,$7b,$7c,$77 ; #$0d - boss ufo - blue top fully open (left) (boss_ufo_supertile_update_ptr_tbl/boss_ufo_supertile_update_ptr_tbl_2) + .byte $6c,$6d,$69,$69,$71,$72,$73,$69,$78,$76,$79,$7a,$78,$7c,$7b,$7a ; #$0e - boss ufo - blue top fully open (right) (boss_ufo_supertile_update_ptr_tbl/boss_ufo_supertile_update_ptr_tbl_2) + .byte $69,$00,$69,$00,$9a,$9b,$9c,$7b,$69,$00,$69,$7e,$00,$00,$82,$83 ; #$0f - tank turret aim code #$0c (straight left) + .byte $73,$8b,$69,$87,$91,$92,$8f,$87,$76,$77,$78,$6c,$96,$97,$98,$99 ; #$10 - tank wheel + .byte $74,$8c,$6c,$00,$90,$91,$92,$93,$6b,$76,$77,$78,$99,$96,$97,$98 ; #$11 - tank wheel + .byte $69,$00,$69,$00,$00,$00,$9f,$7b,$9d,$9e,$a0,$7e,$00,$00,$82,$83 ; #$12 - tank turret aim code #$0b (down to the left) + .byte $69,$00,$69,$00,$00,$00,$a1,$7b,$69,$a2,$a3,$7e,$00,$a4,$82,$83 ; #$13 - tank turret aim code #$0a (far down as possible) + .byte $73,$8b,$69,$87,$91,$92,$8f,$87,$79,$77,$7a,$6c,$a5,$a6,$a7,$99 ; #$14 - tank wheel + .byte $74,$8c,$6c,$00,$90,$91,$92,$93,$6b,$79,$77,$7a,$99,$a5,$a6,$a7 ; #$15 - tank wheel + .byte $cd,$ce,$ce,$ce,$d5,$dc,$dd,$de,$cb,$69,$69,$cc,$69,$69,$00,$c6 ; #$16 - boss screen open door top + .byte $69,$69,$69,$c6,$69,$69,$69,$c6,$69,$69,$69,$c6,$69,$69,$69,$c6 ; #$17 - boss screen open door + .byte $07,$19,$14,$14,$1d,$18,$18,$18,$f2,$f2,$f2,$f2,$f2,$f2,$f2,$f2 ; #$18 - boss screen open door bottom + .byte $4a,$4b,$4c,$4d,$4e,$4f,$50,$51,$4d,$52,$4c,$4f,$49,$4d,$4e,$48 ; #$19 - snowy rock tile + .byte $42,$43,$44,$45,$46,$47,$48,$49,$4a,$4b,$4c,$4d,$4e,$4f,$50,$51 ; #$1a - snowy rock tile + .byte $69,$69,$69,$69,$69,$69,$69,$69,$69,$69,$69,$69,$69,$69,$69,$69 ; #$1b - all black (used to make boss ufo invisible, and tank) + +level_5_palette_data: + .byte $55,$00,$00,$00,$00,$55,$55,$55,$54,$00,$00,$00,$00,$00,$05,$00 + .byte $44,$00,$00,$00,$00,$00,$00,$00,$55,$55,$00,$00,$ea,$ba,$ea,$00 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $00,$00,$00,$00,$00,$ff,$ff,$ff,$55,$55,$ff,$00,$00,$00,$00,$ff + .byte $00,$55,$51,$55,$55,$55,$00,$05,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $55 + +; each byte is the palette for an entire super-tile +; updates values in the attribute table +level_5_nametable_update_palette_data: + .byte $aa,$aa,$aa,$aa,$aa,$aa,$ff,$ff,$ff,$aa,$aa,$aa,$ff,$aa,$aa,$8a + .byte $ae,$ab,$a8,$a8,$ae,$ab,$00,$11,$00,$00,$00,$00,$00,$00,$00 + +; nametable animation pattern table tiles - level 6 (#$e * #$5 = #$46 bytes) +; bytes 1 - 4 are the pattern table tiles to draw +; byte 0 specifies to use the default of #$02 rows of #$02 pattern table tiles each row +; byte 0 specifies only #$01 row of #$02 tiles is updated instead of the default of #$02 +; CPU address $9dd8 +level_6_tile_animation: + .byte $01,$00,$00,$00,$00 ; 80 beam - nothing (black tiles) + .byte $01,$1a,$0c,$1b,$1c ; 81 beam right - beam origin + .byte $01,$0e,$0c,$1e,$1c ; 82 beam right - beam middle + .byte $01,$0c,$00,$1c,$00 ; 83 beam right - beam end + .byte $01,$0d,$0f,$1d,$1f ; 84 beam left - beam origin + .byte $01,$0d,$0e,$1d,$1e ; 85 beam left - beam middle + .byte $01,$00,$0d,$00,$1d ; 86 beam left - beam end + .byte $01,$18,$19,$14,$15 ; 87 beam down - beam origin + .byte $01,$16,$17,$14,$15 ; 88 beam down - beam middle + .byte $01,$14,$15,$00,$00 ; 89 beam down - beam end + .byte $01,$01,$a8,$76,$77 ; 8a boss screen door opening bottom (blank opening and floor) + .byte $01,$c9,$f8,$c9,$a9 ; 8b boss screen door + .byte $01,$c9,$a9,$c9,$00 ; 8c boss screen door (blank opening and top of door) + .byte $01,$c9,$00,$c9,$00 ; 8d boss screen door (side wall and blank opening) + +; super-tile data - level 6 (#$750 bytes) +level_6_supertile_data: + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ; #$00 - blank super-tile + .byte $02,$02,$02,$02,$62,$63,$63,$63,$90,$90,$90,$90,$63,$63,$62,$63 + .byte $70,$43,$02,$02,$7a,$7b,$7c,$7d,$72,$73,$e9,$6a,$67,$74,$7e,$48 + .byte $02,$02,$02,$02,$75,$76,$76,$77,$41,$5c,$5d,$5e,$4b,$4d,$4e,$4f + .byte $02,$02,$02,$02,$75,$76,$76,$77,$41,$5c,$5d,$5e,$4b,$b9,$3e,$3f + .byte $e5,$85,$00,$00,$e5,$85,$00,$00,$e5,$85,$00,$00,$e5,$85,$00,$00 + .byte $01,$79,$40,$48,$75,$76,$76,$77,$41,$5c,$5d,$5e,$4b,$4d,$4e,$4f + .byte $71,$00,$00,$00,$00,$00,$00,$00,$02,$02,$02,$02,$75,$76,$76,$77 + .byte $00,$00,$70,$43,$00,$00,$7a,$7b,$00,$00,$72,$84,$00,$00,$00,$78 + .byte $72,$84,$58,$6a,$00,$78,$40,$48,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$02,$02,$02,$02,$63,$63,$63,$63 + .byte $01,$04,$00,$00,$75,$77,$00,$00,$e0,$57,$00,$00,$e5,$85,$00,$00 + .byte $02,$02,$02,$02,$7c,$7d,$75,$76,$58,$6a,$41,$5c,$40,$48,$4b,$4d + .byte $02,$02,$02,$02,$63,$63,$63,$63,$00,$00,$e4,$54,$00,$00,$82,$64 + .byte $5b,$5b,$71,$00,$6b,$6b,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $70,$43,$02,$02,$7a,$7b,$7c,$7d,$72,$84,$58,$6a,$00,$78,$40,$48 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$02,$02,$02,$02,$75,$76,$76,$77 + .byte $4a,$5b,$5b,$5b,$44,$6b,$6b,$6b,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$70,$43,$02,$02,$7a,$7b,$7c,$7d + .byte $41,$5c,$5d,$5e,$4b,$b9,$3e,$3f,$71,$00,$00,$00,$00,$00,$00,$00 + .byte $01,$79,$40,$48,$62,$63,$63,$63,$90,$90,$90,$90,$63,$63,$62,$63 + .byte $00,$00,$4a,$5b,$00,$00,$44,$6b,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $02,$02,$01,$04,$62,$63,$63,$63,$90,$90,$90,$90,$63,$63,$62,$63 + .byte $41,$5c,$5d,$5e,$4b,$4d,$4e,$4f,$4a,$5b,$5b,$5b,$44,$6b,$6b,$6b + .byte $4a,$5b,$5b,$5b,$44,$6b,$6b,$6b,$02,$02,$02,$02,$75,$76,$76,$77 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$aa,$ae,$ab,$ac,$ad,$f8 + .byte $02,$02,$e1,$80,$63,$63,$63,$63,$00,$00,$e4,$54,$00,$00,$82,$64 + .byte $00,$00,$e3,$54,$00,$00,$e3,$64,$00,$00,$e3,$64,$00,$00,$e3,$64 + .byte $02,$02,$02,$02,$63,$63,$63,$63,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $64,$60,$63,$63,$63,$63,$51,$63,$64,$64,$64,$00,$64,$64,$54,$00 + .byte $02,$02,$e1,$80,$62,$63,$63,$63,$90,$90,$90,$90,$63,$63,$62,$63 + .byte $00,$00,$e3,$54,$00,$00,$e3,$64,$02,$02,$e1,$80,$63,$63,$63,$63 + .byte $00,$00,$e4,$54,$00,$00,$82,$64,$00,$00,$e3,$64,$00,$00,$e3,$64 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$03,$42,$00,$00,$49,$53 + .byte $96,$00,$68,$69,$63,$63,$63,$6f,$90,$90,$90,$90,$63,$63,$62,$63 + .byte $41,$57,$00,$00,$4b,$85,$00,$00,$71,$00,$00,$00,$00,$00,$00,$00 + .byte $96,$97,$02,$02,$62,$63,$63,$63,$90,$90,$90,$90,$63,$63,$62,$63 + .byte $4b,$5b,$5b,$5b,$4b,$6b,$6b,$6b,$90,$90,$90,$90,$63,$63,$62,$63 + .byte $4a,$5b,$5b,$5b,$44,$6b,$6b,$6b,$70,$43,$02,$02,$7a,$7b,$7c,$7d + .byte $00,$00,$00,$00,$00,$00,$00,$00,$ae,$af,$b0,$b1,$b2,$b3,$b4,$b5 + .byte $00,$00,$00,$00,$63,$63,$62,$63,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $66,$66,$66,$66,$5f,$5f,$5f,$5f,$54,$00,$64,$00,$64,$63,$63,$62 + .byte $4c,$56,$66,$66,$4c,$56,$5f,$5f,$4c,$56,$00,$00,$4c,$56,$63,$63 + .byte $00,$00,$03,$42,$00,$00,$49,$53,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $02,$02,$e1,$80,$75,$76,$76,$77,$41,$5c,$5d,$5e,$4b,$4d,$4e,$4f + .byte $f7,$8e,$f0,$8f,$e8,$ed,$ee,$ef,$f3,$f4,$f4,$8a,$62,$63,$63,$63 + .byte $f1,$f2,$f2,$89,$f3,$f4,$f4,$8a,$f5,$f6,$f6,$93,$94,$87,$88,$92 + .byte $00,$00,$e3,$54,$00,$00,$e3,$64,$00,$00,$e3,$64,$00,$00,$45,$80 + .byte $64,$64,$64,$00,$63,$50,$64,$62,$64,$00,$64,$00,$64,$00,$64,$00 + .byte $64,$00,$64,$00,$64,$63,$50,$63,$64,$00,$00,$00,$64,$00,$00,$00 + .byte $54,$00,$00,$00,$64,$00,$00,$00,$64,$00,$00,$00,$64,$00,$00,$00 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$01,$04,$00,$00,$75,$77,$00,$00 + .byte $02,$02,$02,$02,$75,$76,$76,$77,$41,$5c,$5d,$5e,$6e,$3e,$3e,$3f + .byte $41,$5c,$5d,$5e,$6e,$3e,$3e,$3f,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $4c,$56,$64,$00,$4c,$56,$64,$00,$4c,$56,$64,$00,$4c,$56,$64,$00 + .byte $4c,$56,$4a,$5b,$4c,$56,$44,$6b,$02,$02,$02,$02,$75,$76,$76,$77 + .byte $02,$02,$01,$04,$76,$77,$75,$77,$5d,$5e,$41,$57,$4e,$4f,$4b,$85 + .byte $4c,$56,$00,$00,$4c,$56,$00,$00,$4c,$56,$00,$00,$4c,$56,$00,$00 + .byte $b6,$b7,$b8,$f8,$b6,$ba,$bb,$bc,$c3,$c4,$c5,$c6,$c7,$e2,$c9,$f8 + .byte $41,$5c,$5d,$5e,$6e,$3e,$3e,$3f,$4c,$56,$00,$00,$4c,$56,$00,$00 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$02,$02,$01,$04,$63,$63,$63,$63 + .byte $54,$00,$64,$00,$64,$00,$64,$00,$64,$00,$64,$00,$00,$00,$64,$00 + .byte $64,$00,$00,$00,$61,$63,$63,$63,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $64,$64,$64,$00,$63,$52,$64,$00,$54,$00,$54,$00,$00,$00,$64,$00 + .byte $62,$63,$63,$63,$62,$63,$63,$63,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $63,$52,$64,$00,$62,$63,$52,$00,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $f5,$f6,$f6,$93,$62,$63,$63,$63,$90,$90,$90,$90,$63,$63,$62,$63 + .byte $66,$66,$e3,$54,$5f,$5f,$e3,$64,$00,$00,$e3,$64,$00,$00,$e3,$64 + .byte $4c,$56,$00,$00,$4c,$56,$00,$00,$4c,$55,$00,$00,$4c,$65,$00,$00 + .byte $4c,$55,$00,$00,$4c,$65,$00,$00,$4c,$56,$00,$00,$4c,$56,$00,$00 + .byte $71,$00,$00,$00,$00,$00,$00,$00,$01,$04,$00,$00,$75,$77,$00,$00 + .byte $01,$04,$00,$00,$75,$77,$00,$00,$41,$57,$00,$00,$4b,$85,$00,$00 + .byte $41,$5c,$5d,$5e,$6e,$3e,$3e,$3f,$4c,$55,$00,$00,$4c,$65,$00,$00 + .byte $54,$00,$00,$00,$63,$63,$63,$63,$64,$00,$00,$00,$64,$00,$00,$00 + .byte $66,$66,$66,$66,$5f,$5f,$5f,$5f,$64,$00,$00,$64,$64,$63,$63,$52 + .byte $66,$66,$66,$66,$5f,$5f,$6c,$6d,$00,$00,$00,$00,$63,$62,$00,$00 + .byte $4b,$3a,$3a,$3a,$4b,$3b,$3b,$3b,$4b,$3b,$3b,$3b,$4b,$3b,$3b,$3b + .byte $54,$63,$51,$63,$64,$60,$64,$63,$64,$64,$64,$00,$64,$64,$64,$00 + .byte $eb,$ea,$ea,$8b,$8c,$ec,$ec,$8d,$f5,$f6,$f6,$93,$f1,$f2,$f2,$89 + .byte $02,$02,$01,$04,$63,$63,$63,$63,$e6,$95,$e7,$91,$f1,$f2,$f2,$89 + .byte $03,$42,$02,$02,$81,$9a,$63,$63,$83,$86,$90,$90,$98,$99,$63,$63 + .byte $02,$02,$02,$02,$76,$77,$75,$76,$5d,$5e,$41,$5c,$4e,$4f,$4b,$b9 + .byte $71,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $00,$00,$4a,$5b,$00,$00,$44,$6b,$02,$02,$02,$02,$75,$76,$76,$77 + .byte $5b,$5b,$71,$00,$6b,$6b,$00,$00,$02,$02,$02,$02,$75,$76,$76,$77 + .byte $bd,$be,$bf,$c0,$c1,$c2,$f8,$b6,$ca,$cb,$f8,$b6,$cc,$b6,$f8,$b6 + .byte $cd,$cf,$c9,$f8,$b6,$cc,$c9,$f8,$d4,$cc,$c9,$f8,$c7,$e2,$c9,$f8 + .byte $5a,$00,$00,$00,$00,$00,$00,$5a,$00,$00,$00,$00,$00,$59,$00,$00 + .byte $02,$02,$02,$02,$76,$77,$75,$76,$5d,$5e,$41,$5c,$3e,$3f,$6e,$3e + .byte $02,$02,$02,$02,$76,$77,$75,$76,$5d,$5e,$41,$5c,$3e,$3f,$4b,$4d + .byte $e2,$d4,$f8,$b6,$cf,$c7,$a3,$a4,$cc,$cd,$c8,$a0,$cc,$b6,$a1,$a2 + .byte $e2,$d0,$d1,$d2,$cf,$c7,$d3,$a0,$cc,$cd,$c8,$a0,$cc,$b6,$a1,$a2 + .byte $a5,$a6,$f8,$b6,$11,$9b,$a5,$a7,$90,$90,$90,$90,$63,$63,$62,$63 + .byte $02,$02,$e1,$80,$63,$63,$63,$63,$00,$00,$e3,$64,$00,$00,$e3,$64 + .byte $00,$00,$e3,$64,$00,$00,$e3,$64,$00,$00,$e4,$54,$00,$00,$82,$64 + .byte $02,$02,$01,$ce,$75,$76,$76,$77,$41,$5c,$5d,$5e,$4b,$4d,$4e,$4f + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$5a,$00,$00,$00,$00,$00,$00 + .byte $4b,$3a,$3a,$60,$4b,$3b,$3b,$64,$4b,$3b,$3b,$64,$4b,$3b,$3b,$64 + .byte $62,$63,$63,$63,$4b,$3b,$3b,$3b,$4b,$3b,$3b,$3b,$4b,$3b,$3b,$3b + .byte $13,$3a,$3a,$3a,$64,$3b,$3b,$3b,$64,$3b,$3b,$3b,$64,$3b,$3b,$3b + .byte $52,$3a,$3a,$3a,$4b,$3b,$3b,$3b,$4b,$3b,$3b,$3b,$4b,$3b,$3b,$3b + .byte $4b,$3a,$3a,$61,$4b,$3b,$3b,$3b,$4b,$3b,$3b,$3b,$4b,$3b,$3b,$3b + .byte $e6,$95,$e7,$91,$f1,$f2,$f2,$89,$f3,$f4,$f4,$8a,$f5,$f6,$f6,$93 + .byte $00,$00,$e3,$64,$00,$00,$e3,$64,$59,$00,$e3,$64,$00,$00,$e3,$64 + .byte $01,$04,$00,$00,$62,$63,$00,$00,$90,$90,$00,$00,$63,$63,$00,$00 + +level_6_nametable_update_supertile_data: + .byte $20,$21,$21,$22,$26,$30,$31,$27,$26,$30,$31,$27,$23,$24,$24,$25 ; pill box sensor closed + .byte $20,$21,$21,$22,$26,$2c,$2d,$27,$26,$2e,$2f,$27,$23,$24,$24,$25 ; pill box sensor partially open + .byte $20,$21,$21,$22,$26,$28,$29,$27,$26,$2a,$2b,$27,$23,$24,$24,$25 ; pill box sensor open + .byte $12,$60,$63,$51,$60,$64,$62,$64,$64,$10,$00,$64,$54,$63,$63,$52 + .byte $00,$60,$63,$12,$60,$51,$13,$00,$64,$64,$64,$00,$54,$64,$64,$00 + +level_6_palette_data: + .byte $ff,$00,$00,$a0,$a0,$22,$a0,$01,$00,$00,$05,$20,$80,$30,$05,$00 + .byte $0f,$05,$00,$1a,$00,$04,$00,$5a,$05,$ff,$30,$00,$f0,$55,$00,$00 + .byte $03,$00,$00,$de,$00,$05,$05,$ff,$05,$55,$55,$00,$a0,$55,$55,$00 + .byte $55,$55,$55,$0f,$a0,$fa,$55,$05,$a0,$11,$ff,$1a,$0f,$55,$55,$55 + .byte $55,$f5,$05,$01,$d5,$1d,$cd,$ec,$da,$15,$55,$d5,$55,$55,$55,$50 + .byte $00,$a0,$cd,$04,$05,$ff,$ff,$d0,$a0,$a0,$ff,$ff,$0f,$00,$30,$a0 + .byte $00,$55,$55,$55,$55,$55,$55,$10,$00 + +; each byte is the palette for an entire super-tile +; updates values in the attribute table +level_6_nametable_update_palette_data: + .byte $aa,$aa,$aa,$55,$55,$00,$00 + +; namespace animation tiles codes - level 7 (#$c * #$5 = #$3c bytes) +; first byte is number of groups of length 2 to update of data being written +; e.g. byte 0 (#$83) means to draw three rows of two tiles each +; $0c $0d store the ppu write address +; CPU address $a56e +level_7_tile_animation: + .byte $83,$de,$df,$ee,$ef ; 80 mechanical claw + .byte $00,$00,$00,$00,$00 ; 81 mechanical claw - nothing (black tiles) + .byte $83,$de,$df,$ee,$ef ; 82 mechanical claw + .byte $47,$48,$00,$00,$00 ; 83 safety rail - top + .byte $83,$de,$df,$ee,$ef ; 84 mechanical claw + .byte $57,$58,$00,$00,$00 ; 85 safety rail - bottom + .byte $83,$ce,$cf,$de,$df ; 86 mechanical claw - top + .byte $ee,$ef,$00,$00,$00 ; 87 mechanical claw - bottom + .byte $00,$30,$31,$30,$31 ; 88 mortar launcher - frame 0 (closed) + .byte $00,$c5,$c6,$c7,$c8 ; 89 mortar launcher - frame 1 (partially open/partially closed) + .byte $00,$ea,$eb,$ec,$ed ; 8a mortar launcher - frame 2 (opened) + .byte $00,$4a,$4f,$4a,$52 ; 8b mortar launcher - destroyed + +; super-tile data - level 7 +level_7_supertile_data: + .byte $04,$05,$70,$70,$cd,$dd,$61,$61,$af,$af,$0e,$0f,$b1,$b2,$72,$73 + .byte $74,$75,$f9,$a7,$66,$67,$f8,$a7,$76,$89,$f8,$a7,$76,$89,$f8,$a7 + .byte $76,$89,$f7,$a7,$76,$89,$f7,$a7,$af,$53,$f7,$a7,$b1,$b2,$f7,$a7 + .byte $74,$75,$74,$75,$66,$67,$66,$67,$76,$89,$76,$89,$76,$89,$76,$89 + .byte $5c,$5d,$5c,$5d,$6c,$6d,$6c,$6d,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $00,$42,$43,$00,$42,$43,$00,$42,$00,$00,$43,$42,$42,$43,$00,$43 + .byte $af,$af,$af,$53,$a1,$a1,$a1,$a3,$a1,$a1,$a1,$a2,$a1,$a2,$69,$b3 + .byte $af,$af,$af,$53,$89,$89,$a0,$a1,$a1,$a1,$a2,$a3,$89,$89,$b0,$b3 + .byte $a1,$a1,$a1,$a2,$00,$00,$00,$00,$62,$63,$0e,$0f,$72,$73,$72,$73 + .byte $a4,$a5,$f7,$a7,$a4,$a5,$f7,$a7,$a4,$a5,$f7,$a7,$a4,$a5,$f7,$a7 + .byte $a1,$a1,$89,$a2,$00,$00,$98,$75,$62,$63,$0e,$0f,$72,$73,$72,$73 + .byte $a3,$a0,$a2,$a2,$4d,$4d,$4d,$4d,$5c,$5d,$5c,$5d,$44,$45,$44,$45 + .byte $0b,$0c,$70,$70,$bd,$be,$61,$61,$af,$af,$0e,$0f,$b1,$b2,$72,$73 + .byte $a4,$a5,$f7,$a7,$a4,$b5,$b6,$b7,$a4,$a7,$00,$00,$a4,$a7,$00,$00 + .byte $76,$89,$76,$89,$76,$89,$76,$89,$af,$53,$af,$af,$b1,$b2,$b1,$b2 + .byte $5c,$5d,$5c,$5d,$44,$45,$44,$45,$de,$df,$de,$df,$ee,$ef,$ee,$ef + .byte $06,$07,$08,$08,$7f,$9e,$be,$bd,$7f,$00,$af,$53,$ad,$ae,$b1,$b2 + .byte $08,$08,$08,$08,$bd,$bd,$bd,$bd,$af,$af,$af,$53,$b1,$b2,$b1,$b2 + .byte $08,$08,$0b,$0c,$bd,$bd,$bd,$be,$af,$af,$af,$53,$b1,$b2,$b1,$b2 + .byte $09,$0a,$08,$08,$bd,$bd,$bd,$bd,$af,$af,$af,$53,$b1,$b2,$b1,$b2 + .byte $af,$af,$af,$53,$a1,$a3,$a0,$a1,$00,$a0,$a3,$00,$a0,$b0,$b3,$a3 + .byte $af,$af,$af,$53,$a1,$a3,$89,$89,$a0,$a2,$a1,$a1,$b0,$b3,$89,$89 + .byte $af,$af,$af,$53,$a0,$a1,$a1,$a2,$a2,$a1,$a1,$a2,$b0,$68,$a2,$a2 + .byte $af,$af,$f8,$a7,$9c,$7a,$f8,$a7,$9c,$8a,$f9,$a7,$9c,$9b,$9f,$a7 + .byte $a3,$b0,$b3,$a0,$4d,$4d,$4d,$4d,$5c,$5d,$5c,$5d,$44,$45,$44,$45 + .byte $a2,$a2,$a3,$a0,$4d,$4d,$4d,$4d,$5c,$5d,$5c,$5d,$44,$45,$44,$45 + .byte $a2,$89,$a1,$a2,$af,$99,$00,$00,$62,$63,$0e,$0f,$72,$73,$72,$73 + .byte $9c,$9a,$f7,$a7,$a4,$a5,$f7,$a7,$a4,$a5,$f7,$a7,$a4,$a5,$f7,$a7 + .byte $09,$f0,$f7,$a7,$bd,$be,$f7,$a7,$af,$af,$f7,$a7,$b1,$b2,$f7,$a7 + .byte $76,$89,$f7,$cc,$76,$89,$f7,$cc,$af,$53,$f7,$cc,$b1,$b2,$f7,$cc + .byte $01,$02,$03,$03,$7f,$9e,$dd,$cd,$fe,$00,$af,$53,$bf,$ae,$b1,$b2 + .byte $a4,$a5,$f7,$a7,$a4,$a5,$f7,$a7,$a4,$a5,$f7,$a7,$a4,$b5,$b6,$b7 + .byte $03,$03,$03,$03,$cd,$cd,$cd,$cd,$af,$af,$af,$53,$b1,$b2,$b1,$b2 + .byte $5c,$5d,$5c,$5d,$6c,$6d,$6c,$6d,$62,$63,$4b,$4c,$72,$5a,$5b,$00 + .byte $5c,$5d,$5c,$5d,$44,$45,$44,$45,$54,$55,$54,$55,$00,$00,$00,$00 + .byte $5c,$5d,$5c,$5d,$6c,$6d,$6c,$6d,$62,$63,$0e,$0f,$72,$73,$72,$73 + .byte $60,$70,$6e,$6f,$70,$71,$7f,$74,$62,$63,$bf,$67,$72,$73,$7f,$89 + .byte $6f,$6f,$6f,$6f,$af,$75,$74,$75,$66,$67,$66,$67,$76,$89,$76,$89 + .byte $6f,$7e,$70,$70,$74,$75,$61,$61,$66,$67,$0e,$0f,$76,$89,$72,$73 + .byte $60,$70,$70,$70,$70,$71,$61,$61,$62,$63,$4b,$4c,$72,$5a,$5b,$00 + .byte $60,$70,$bf,$89,$70,$71,$ad,$4d,$62,$63,$0e,$0f,$72,$73,$72,$73 + .byte $76,$89,$76,$89,$4d,$4d,$4d,$4d,$62,$63,$0e,$0f,$72,$73,$72,$73 + .byte $76,$89,$70,$70,$4d,$4d,$61,$61,$62,$63,$0e,$0f,$72,$73,$72,$73 + .byte $60,$70,$70,$70,$70,$71,$61,$61,$4c,$4e,$0e,$0f,$00,$5e,$5f,$73 + .byte $5c,$5d,$5c,$5d,$44,$45,$6c,$6d,$54,$55,$00,$00,$00,$00,$00,$00 + .byte $5c,$5d,$5c,$5d,$6c,$6d,$6c,$6d,$4c,$4e,$0e,$0f,$00,$5e,$5f,$73 + .byte $60,$6a,$00,$00,$70,$6a,$00,$00,$62,$6a,$00,$00,$72,$6a,$8c,$8c + .byte $00,$00,$6a,$70,$00,$00,$6a,$61,$00,$00,$6a,$0f,$8c,$8d,$6a,$73 + .byte $a4,$a5,$f7,$a7,$a4,$b5,$b6,$b7,$a4,$a7,$0e,$0f,$a4,$a7,$72,$73 + .byte $0b,$0c,$89,$10,$bd,$be,$88,$b4,$af,$af,$99,$11,$b1,$b2,$b1,$b2 + .byte $12,$12,$fa,$89,$b4,$b4,$b4,$88,$13,$13,$fb,$98,$b1,$b2,$b1,$b2 + .byte $60,$70,$70,$70,$70,$71,$61,$61,$47,$48,$47,$48,$57,$58,$57,$58 + .byte $a4,$a7,$70,$70,$a4,$a7,$61,$61,$a4,$a7,$0e,$0f,$a4,$a7,$72,$73 + .byte $5c,$5d,$f7,$a7,$6c,$6d,$f7,$a7,$a4,$a5,$f7,$a7,$a4,$a5,$f7,$a7 + .byte $a4,$a7,$70,$70,$a4,$a7,$61,$61,$a4,$a7,$47,$48,$a4,$a7,$57,$58 + .byte $12,$12,$fa,$89,$b4,$b4,$b4,$88,$13,$13,$fb,$89,$b1,$b2,$b1,$89 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$46,$48,$47,$48,$56,$58,$57,$58 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$48,$49,$00,$00,$58,$59,$00,$00 + .byte $08,$da,$0d,$cc,$bd,$bd,$bd,$bd,$af,$af,$af,$53,$b1,$b2,$b1,$b2 + .byte $b9,$ba,$fd,$cc,$b9,$ba,$fd,$cc,$b8,$ba,$fd,$cc,$c9,$ca,$fd,$cc + .byte $60,$70,$70,$70,$70,$71,$61,$61,$46,$48,$47,$48,$56,$58,$57,$58 + .byte $5c,$5d,$5c,$5d,$44,$45,$44,$45,$54,$55,$de,$df,$00,$00,$ee,$ef + .byte $00,$00,$5c,$5d,$00,$00,$6c,$6d,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $00,$42,$00,$00,$00,$00,$00,$43,$00,$00,$00,$00,$42,$00,$00,$00 + .byte $06,$07,$08,$08,$7f,$9e,$be,$bd,$fe,$00,$78,$53,$bf,$ae,$89,$b2 + .byte $60,$70,$bf,$89,$70,$71,$ad,$4d,$48,$49,$0e,$0f,$58,$59,$72,$73 + .byte $08,$08,$08,$08,$bd,$bd,$bd,$bd,$af,$af,$af,$53,$b1,$20,$21,$21 + .byte $43,$42,$00,$00,$00,$00,$43,$00,$00,$42,$00,$00,$00,$00,$42,$00 + .byte $af,$af,$af,$53,$a1,$a1,$a1,$a2,$a1,$a1,$a1,$a2,$a1,$a1,$a1,$a2 + .byte $12,$12,$12,$10,$b4,$b4,$b4,$b4,$13,$13,$13,$11,$b1,$b2,$b1,$b2 + .byte $08,$08,$08,$08,$bd,$bd,$bd,$bd,$af,$af,$af,$53,$22,$b2,$b1,$b2 + .byte $08,$08,$0b,$0c,$bd,$bd,$bd,$be,$af,$af,$fc,$79,$b1,$b2,$b1,$89 + .byte $04,$05,$89,$10,$cd,$dd,$88,$b4,$af,$af,$99,$11,$b1,$b2,$b1,$b2 + .byte $14,$15,$15,$15,$15,$a6,$12,$10,$16,$16,$13,$11,$b1,$b2,$b1,$b2 + .byte $17,$18,$19,$10,$1a,$1b,$1c,$b4,$1d,$32,$33,$11,$b1,$b2,$b1,$b2 + .byte $27,$15,$15,$15,$27,$a6,$12,$10,$25,$16,$13,$11,$b1,$b2,$b1,$b2 + .byte $14,$26,$30,$31,$15,$26,$30,$31,$16,$23,$24,$24,$b1,$b2,$b1,$b2 + .byte $a4,$a7,$00,$00,$a4,$a7,$00,$00,$a4,$a7,$47,$48,$a4,$a7,$57,$58 + .byte $00,$a0,$40,$89,$00,$b0,$b0,$89,$00,$41,$41,$88,$a0,$40,$40,$89 + .byte $ce,$cf,$ce,$cf,$ce,$cf,$ce,$cf,$de,$df,$de,$df,$ee,$ef,$ee,$ef + .byte $b9,$ba,$fd,$cc,$b9,$ba,$fd,$cc,$b9,$ba,$fd,$cc,$b9,$ba,$fd,$cc + .byte $00,$87,$8b,$00,$00,$80,$81,$00,$00,$97,$8b,$00,$00,$87,$8b,$00 + .byte $f6,$b4,$b4,$88,$f5,$35,$36,$88,$f6,$b4,$b4,$88,$f6,$b4,$b4,$88 + .byte $87,$7c,$7c,$8b,$87,$7b,$7c,$8b,$97,$7c,$7b,$8b,$97,$7c,$ff,$8b + .byte $60,$70,$70,$70,$70,$71,$61,$61,$48,$49,$0e,$0f,$58,$59,$72,$73 + .byte $a0,$97,$8b,$00,$b0,$87,$8b,$00,$41,$90,$91,$00,$b0,$97,$8b,$00 + .byte $f6,$b4,$b4,$88,$f6,$b4,$b4,$88,$f4,$36,$37,$88,$f6,$b4,$b4,$88 + .byte $87,$7b,$7c,$d6,$90,$d4,$d5,$e6,$97,$7c,$7b,$8b,$87,$7c,$7c,$8b + .byte $00,$00,$00,$78,$00,$00,$00,$88,$00,$00,$00,$89,$00,$00,$00,$89 + .byte $af,$97,$8b,$00,$41,$87,$8b,$00,$b0,$82,$84,$00,$b0,$92,$94,$00 + .byte $f6,$b4,$b4,$88,$f6,$b4,$b4,$88,$7d,$53,$53,$99,$b1,$b1,$b1,$b1 + .byte $87,$ff,$7c,$8b,$87,$7c,$7b,$8b,$82,$83,$83,$84,$92,$93,$93,$94 + .byte $00,$00,$00,$88,$00,$00,$00,$89,$00,$00,$a0,$89,$00,$00,$41,$88 + .byte $41,$87,$8b,$00,$b0,$97,$8b,$00,$40,$87,$8b,$00,$41,$85,$86,$00 + .byte $f2,$34,$38,$39,$3a,$3b,$3c,$3d,$f3,$3e,$3f,$cb,$e0,$e1,$e2,$e3 + .byte $87,$7b,$7c,$8b,$87,$7c,$7b,$8b,$97,$7c,$7c,$8b,$87,$7b,$ff,$8b + .byte $40,$97,$8b,$00,$b0,$87,$8b,$00,$41,$87,$8b,$00,$40,$95,$96,$00 + .byte $85,$e7,$e8,$e9,$87,$7c,$7c,$8b,$97,$7c,$7b,$8b,$87,$7b,$7c,$8b + .byte $f1,$7c,$ff,$8b,$95,$e4,$e5,$8b,$97,$7b,$d7,$d8,$87,$7b,$7c,$8b + .byte $87,$7c,$7b,$8b,$97,$7c,$ff,$8b,$97,$7b,$7c,$8b,$87,$7c,$7c,$8b + +level_7_nametable_update_supertile_data: + .byte $20,$21,$21,$22,$26,$30,$31,$27,$26,$30,$31,$27,$23,$24,$24,$25 ; #$00 - pill box sensor closed + .byte $20,$21,$21,$22,$26,$2c,$2d,$27,$26,$2e,$2f,$27,$23,$24,$24,$25 ; #$01 - pill box sensor partially open + .byte $20,$21,$21,$22,$26,$28,$29,$27,$26,$2a,$2b,$27,$23,$24,$24,$25 ; #$02 - pill box sensor open + .byte $f6,$30,$31,$27,$f6,$30,$31,$27,$f6,$30,$31,$27,$f6,$30,$31,$27 ; #$03 - closed armored door (boss_soldier_nametable_update_tbl) + .byte $50,$51,$51,$52,$bd,$9d,$9d,$9d,$af,$af,$af,$53,$b1,$b2,$b1,$b2 ; #$04 - spiked wall destroyed floor + .byte $4a,$4f,$4f,$4f,$4a,$00,$00,$00,$4a,$00,$00,$00,$4a,$00,$00,$00 + .byte $00,$d9,$db,$dc,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ; #$06 - tall spiked wall destroyed top (parting hanging from ceiling) + .byte $f6,$00,$00,$27,$f6,$00,$00,$27,$f6,$00,$00,$27,$f6,$00,$00,$27 ; #$07 - open armored door (see #$12) (boss_soldier_nametable_update_tbl) + .byte $4a,$00,$00,$00,$4a,$00,$00,$00,$4a,$00,$00,$00,$4a,$00,$00,$00 + .byte $60,$70,$70,$70,$70,$71,$61,$61,$62,$63,$0e,$0f,$72,$73,$72,$73 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ; #$0a - blank super-tile + .byte $00,$00,$00,$00,$00,$00,$00,$00,$47,$48,$47,$48,$57,$58,$57,$58 ; #$0b - fence + .byte $47,$48,$47,$48,$c0,$c1,$57,$58,$08,$c2,$c3,$c4,$bd,$bd,$bd,$bd ; #$0c - rising wall (frame 0) (barely out of ground) + .byte $47,$48,$47,$48,$d0,$d1,$d2,$8f,$08,$da,$d3,$bc,$bd,$bd,$bd,$bd ; #$0d - rising wall (frame 1) (slightly out of ground) + .byte $a8,$aa,$ab,$8e,$c9,$ca,$bb,$bc,$08,$da,$0d,$cc,$bd,$bd,$bd,$bd ; #$0e - rising wall (frame 2) (first row of spikes visible) + .byte $00,$00,$00,$00,$a9,$aa,$ab,$ac,$b8,$ba,$bb,$bc,$c9,$ca,$fd,$cc ; #$0f - rising wall (frame 3) (two rows of spikes visible) + .byte $a9,$aa,$ab,$ac,$b9,$ba,$bb,$bc,$b8,$ba,$fd,$cc,$c9,$ca,$fd,$cc ; #$10 - rising wall (frame 4) (three rows of spikes visible) + .byte $a9,$aa,$ab,$ac,$b9,$ba,$bb,$bc,$b9,$ba,$fd,$cc,$b9,$ba,$fd,$cc ; #$11 - rising wall (frame 5) (four rows of spikes visible) + .byte $08,$1e,$1f,$27,$bd,$bd,$bd,$bd,$af,$af,$af,$53,$b1,$b2,$b1,$b2 ; #$12 - closed armored door floor (boss_soldier_nametable_update_tbl) + .byte $f6,$64,$65,$27,$f6,$64,$65,$27,$f6,$64,$65,$27,$f6,$64,$65,$27 ; #$13 - partial open armored door (boss_soldier_nametable_update_tbl) + .byte $08,$08,$6b,$27,$bd,$bd,$bd,$bd,$af,$af,$af,$53,$b1,$b2,$b1,$b2 ; #$14 - partial open armored door floor (boss_soldier_nametable_update_tbl) + .byte $08,$08,$08,$27,$bd,$bd,$bd,$bd,$af,$af,$af,$53,$b1,$b2,$b1,$b2 ; #$15 - open armored door floor (boss_soldier_nametable_update_tbl) + +level_7_palette_data: + .byte $de,$11,$01,$55,$05,$ff,$00,$00,$f0,$00,$f0,$00,$dc,$00,$05,$00 + .byte $40,$50,$50,$50,$00,$00,$00,$00,$00,$00,$f0,$00,$10,$01,$5a,$00 + .byte $5a,$f5,$55,$f5,$77,$55,$dd,$ff,$f7,$f5,$fd,$ff,$55,$f5,$ff,$ff + .byte $c0,$54,$55,$0f,$cc,$01,$0c,$55,$00,$00,$50,$00,$0f,$11,$04,$ff + .byte $40,$c7,$50,$ff,$00,$55,$50,$50,$56,$55,$55,$55,$59,$00,$55,$00 + .byte $00,$55,$aa,$55,$cf,$55,$aa,$55,$55,$55,$aa,$55,$55,$55,$ff,$55 + .byte $55,$55,$55,$55 + +; each byte is the palette for an entire super-tile +; updates values in the attribute table +level_7_nametable_update_palette_data: + .byte $aa,$aa,$aa,$00,$50,$00,$00,$00,$00,$ff,$00,$00,$00,$00,$00,$00 + .byte $00,$00,$50,$00,$50,$50,$00,$00,$00,$00,$00,$00 + +; CPU address $adca +level_8_supertile_data: + .byte $01,$03,$02,$03,$11,$13,$12,$13,$2c,$2d,$2e,$2f,$3c,$3d,$3e,$3f + .byte $02,$03,$02,$03,$12,$13,$12,$13,$2c,$2d,$2e,$2f,$3c,$3d,$3e,$3f + .byte $02,$03,$02,$04,$12,$13,$12,$14,$2c,$2d,$2e,$2f,$3c,$3d,$3e,$4c + .byte $01,$03,$02,$04,$11,$13,$12,$14,$2c,$2d,$2e,$2f,$3c,$3d,$3e,$4c + .byte $9b,$9b,$89,$89,$00,$00,$00,$00,$01,$03,$02,$03,$11,$13,$12,$13 + .byte $00,$9c,$9b,$89,$00,$00,$00,$00,$02,$03,$02,$03,$12,$13,$12,$13 + .byte $00,$9c,$9c,$9c,$00,$00,$00,$00,$02,$03,$02,$04,$12,$13,$12,$14 + .byte $9b,$89,$9c,$9c,$00,$00,$00,$00,$01,$03,$02,$04,$11,$13,$12,$14 + .byte $9b,$00,$01,$03,$a8,$00,$11,$13,$a8,$00,$2e,$2f,$a8,$00,$3e,$3f + .byte $02,$04,$00,$9c,$12,$14,$00,$a9,$2c,$2d,$00,$a9,$3c,$3d,$00,$a9 + .byte $8e,$8f,$9b,$89,$9e,$9b,$00,$00,$9b,$00,$01,$03,$00,$00,$11,$13 + .byte $89,$9c,$8e,$8f,$00,$00,$9c,$9f,$02,$04,$00,$9c,$12,$14,$00,$ac + .byte $a8,$00,$0e,$0f,$ab,$00,$10,$1f,$ae,$ab,$00,$00,$8c,$8d,$99,$99 + .byte $0c,$0d,$00,$a9,$1c,$1d,$00,$a9,$00,$00,$00,$ac,$99,$ac,$ac,$8d + .byte $ab,$00,$0e,$0f,$9b,$00,$10,$1f,$a8,$00,$2e,$2f,$ab,$00,$90,$3f + .byte $0c,$0d,$00,$9c,$1c,$1d,$00,$a9,$2c,$2d,$00,$a9,$3c,$b1,$00,$ac + .byte $0c,$0d,$0e,$0f,$1c,$1d,$10,$1f,$00,$00,$00,$00,$99,$99,$99,$99 + .byte $0c,$0d,$0e,$0f,$1c,$1d,$1e,$1f,$2c,$2d,$2e,$2f,$3c,$3d,$3e,$3f + .byte $0c,$0d,$0e,$0f,$1c,$1d,$10,$1f,$02,$03,$02,$03,$12,$13,$12,$13 + .byte $0c,$0d,$0e,$0f,$1c,$1d,$1e,$1f,$00,$00,$01,$03,$ab,$00,$11,$13 + .byte $0c,$0d,$0e,$0f,$1c,$1d,$10,$1f,$01,$03,$02,$03,$11,$13,$12,$13 + .byte $0c,$0d,$0e,$0f,$1c,$1d,$1e,$1f,$02,$03,$02,$04,$12,$13,$12,$14 + .byte $9b,$00,$0e,$0f,$00,$00,$10,$1f,$02,$03,$02,$03,$12,$13,$12,$13 + .byte $fb,$81,$a3,$a0,$00,$91,$b2,$b0,$fb,$a1,$82,$81,$b0,$00,$92,$93 + .byte $f2,$41,$f1,$43,$50,$51,$52,$53,$f0,$61,$ef,$63,$70,$71,$72,$73 + .byte $00,$82,$00,$92,$00,$92,$93,$a0,$00,$00,$91,$b0,$00,$a0,$a1,$a3 + .byte $00,$00,$00,$a0,$00,$00,$b0,$b0,$00,$a0,$a1,$81,$00,$a3,$00,$91 + .byte $83,$83,$83,$85,$83,$83,$83,$85,$83,$83,$84,$85,$84,$83,$94,$95 + .byte $00,$00,$83,$85,$ab,$00,$84,$85,$ae,$a8,$94,$95,$8c,$a8,$a4,$a5 + .byte $00,$00,$00,$81,$00,$00,$00,$92,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $00,$b2,$a0,$a1,$00,$82,$b0,$00,$02,$03,$02,$03,$12,$13,$12,$13 + .byte $94,$83,$a4,$a5,$a4,$84,$85,$00,$00,$94,$95,$00,$00,$a4,$a5,$00 + .byte $0c,$0d,$0e,$0f,$1c,$1d,$1e,$1f,$00,$80,$2e,$2f,$00,$00,$90,$3f + .byte $0c,$0d,$0e,$0f,$1c,$1d,$1e,$1f,$2c,$2d,$a2,$00,$3c,$b1,$00,$00 + .byte $0c,$0d,$0e,$0f,$1c,$1d,$1e,$1f,$2c,$2d,$2e,$2f,$3c,$3d,$3e,$4c + .byte $8e,$8f,$8e,$8f,$9e,$9f,$9e,$9f,$ae,$af,$9b,$89,$8c,$8d,$a8,$00 + .byte $f7,$0d,$f9,$0f,$1c,$1d,$1e,$1f,$f8,$2d,$fa,$2f,$3c,$3d,$3e,$3f + .byte $0c,$0d,$0e,$0f,$1c,$1d,$10,$1f,$2c,$2d,$a2,$00,$3c,$b1,$00,$00 + .byte $8e,$8f,$8e,$8f,$9e,$9f,$9e,$9f,$ae,$af,$ae,$af,$8c,$8d,$8c,$8d + .byte $00,$00,$00,$00,$99,$ac,$ab,$99,$ae,$af,$ae,$af,$8c,$8d,$8c,$8d + .byte $00,$00,$00,$ac,$99,$99,$ac,$9f,$ae,$9b,$89,$89,$9b,$00,$00,$00 + .byte $ab,$00,$00,$00,$9e,$ab,$99,$99,$ae,$af,$ae,$af,$8c,$8d,$8c,$8d + .byte $00,$00,$00,$ac,$99,$99,$ac,$9f,$ae,$af,$ae,$af,$9c,$8d,$8c,$8d + .byte $00,$00,$00,$00,$00,$00,$ac,$99,$00,$ac,$ae,$af,$00,$ac,$8c,$8d + .byte $00,$00,$00,$00,$99,$00,$00,$00,$ae,$ab,$99,$00,$8c,$8d,$8c,$ab + .byte $9b,$00,$0e,$0f,$00,$00,$10,$1f,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $8e,$8f,$a8,$00,$9e,$9f,$a8,$00,$ae,$af,$9b,$00,$8c,$9b,$00,$00 + .byte $8e,$8f,$a8,$00,$9e,$9f,$ab,$00,$ae,$af,$ae,$ab,$8c,$8d,$8c,$ab + .byte $00,$00,$00,$00,$ac,$ab,$00,$00,$a9,$af,$ab,$00,$8c,$8d,$a8,$00 + .byte $00,$00,$84,$85,$00,$00,$94,$95,$ac,$00,$a4,$a5,$8c,$ab,$00,$00 + .byte $8e,$8f,$8e,$8f,$9e,$9f,$9e,$9f,$9b,$89,$89,$9c,$00,$00,$00,$00 + .byte $83,$85,$94,$95,$84,$85,$a4,$a5,$94,$95,$00,$00,$a4,$a5,$00,$00 + .byte $00,$00,$ac,$8f,$00,$00,$9c,$9f,$02,$04,$00,$9c,$12,$14,$00,$a9 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$02,$03,$02,$03,$12,$13,$12,$13 + .byte $0c,$0d,$00,$00,$1c,$1d,$00,$00,$2c,$2d,$9d,$9d,$3c,$3d,$ad,$ad + .byte $00,$00,$0e,$0f,$00,$00,$10,$1f,$9d,$9d,$2e,$2f,$ad,$ad,$90,$3f + .byte $02,$04,$00,$00,$12,$14,$00,$00,$2c,$2d,$9d,$9d,$3c,$3d,$ad,$ad + .byte $00,$00,$01,$03,$00,$00,$11,$13,$9d,$9d,$2e,$2f,$ad,$ad,$90,$3f + .byte $00,$00,$00,$00,$00,$00,$00,$00,$9d,$9d,$9d,$9d,$ad,$ad,$ad,$ad + .byte $ef,$63,$f0,$61,$72,$73,$70,$71,$02,$03,$02,$03,$12,$13,$12,$13 + .byte $ef,$63,$f0,$61,$72,$73,$70,$71,$f1,$43,$f2,$fd,$52,$53,$50,$fe + .byte $00,$00,$00,$00,$00,$00,$00,$00,$f1,$43,$fc,$fd,$52,$53,$50,$fe + .byte $00,$ac,$8e,$8f,$00,$9c,$9e,$9f,$00,$a9,$ae,$af,$00,$ac,$8c,$8d + .byte $00,$00,$f0,$61,$00,$00,$70,$71,$02,$03,$f2,$fd,$12,$13,$50,$fe + .byte $00,$00,$f0,$61,$00,$00,$70,$71,$00,$00,$f2,$41,$00,$00,$50,$51 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$fc,$41,$00,$00,$50,$51 + .byte $8e,$8f,$8e,$8f,$9e,$9f,$9e,$9f,$9c,$9c,$ae,$af,$00,$a9,$8c,$8d + .byte $8e,$8f,$a8,$00,$9e,$9f,$9b,$00,$89,$a8,$00,$00,$00,$00,$00,$00 + .byte $8e,$8f,$8e,$8f,$9e,$9f,$9e,$9f,$ae,$9b,$89,$89,$9b,$00,$00,$00 + .byte $ab,$00,$00,$ac,$9e,$ab,$ac,$9f,$89,$89,$89,$89,$00,$00,$00,$00 + .byte $00,$a9,$8e,$8f,$00,$ac,$9e,$9f,$99,$af,$ae,$af,$8c,$8d,$8c,$8d + .byte $00,$9c,$8e,$8f,$00,$a9,$9e,$9f,$00,$00,$89,$9c,$00,$00,$00,$00 + .byte $0c,$0d,$0e,$0f,$1c,$1d,$10,$1f,$00,$00,$00,$00,$00,$00,$00,$00 + +level_8_nametable_update_supertile_data: + .byte $44,$45,$46,$47,$54,$55,$56,$57,$64,$65,$66,$67,$74,$75,$76,$77 ; #$00 - alien mouth (wadder) closed + .byte $48,$49,$4a,$4b,$58,$59,$5a,$5b,$68,$69,$6a,$6b,$78,$79,$7a,$7b ; #$01 - alien mouth (wadder) open + .byte $8e,$8f,$8e,$8f,$9e,$9b,$9c,$9f,$8e,$ab,$ac,$8f,$9e,$9f,$9e,$9f ; #$02 - alien mouth (wadder) destroyed + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ; #$03 - blank super-tile + .byte $cc,$a3,$ce,$cf,$dc,$dd,$de,$00,$ec,$ed,$ee,$b6,$bc,$bd,$be,$bf + .byte $f3,$a3,$b0,$d9,$93,$d9,$da,$e9,$fb,$e9,$ea,$eb,$00,$c2,$c3,$bb + .byte $d5,$d6,$d7,$00,$d6,$e6,$e7,$e8,$fe,$ff,$c0,$c1,$91,$92,$b9,$bf + .byte $f3,$d2,$d3,$d4,$a0,$e2,$bb,$bc,$fb,$b9,$fe,$fd,$91,$92,$93,$00 + .byte $cc,$a3,$ce,$cf,$dc,$dd,$de,$00,$ec,$ed,$ee,$b6,$bc,$bd,$c8,$bf + .byte $f3,$81,$b0,$d0,$93,$d0,$da,$e0,$fb,$e0,$ea,$eb,$00,$b7,$ba,$bb + .byte $e5,$d6,$d8,$00,$d6,$e6,$c9,$e8,$c4,$c6,$c7,$c1,$91,$92,$b9,$bf + .byte $f3,$d1,$e3,$e4,$a0,$e1,$bb,$bc,$fb,$b8,$c4,$c5,$82,$92,$a3,$00 + .byte $f3,$a3,$ce,$cf,$f3,$f3,$ca,$f3,$f3,$f3,$cb,$b6,$f3,$f3,$db,$bf + .byte $f3,$a3,$b0,$f3,$93,$81,$f3,$f3,$fb,$f3,$f3,$f3,$f3,$f3,$f3,$f3 + .byte $f3,$f3,$f3,$f3,$f3,$f3,$cd,$e8,$f3,$f3,$df,$c1,$91,$92,$b9,$bf + .byte $f3,$f3,$f3,$f3,$a0,$f3,$f3,$f3,$fb,$b0,$f3,$f3,$91,$92,$93,$f3 + .byte $e3,$d4,$d5,$c4,$aa,$e4,$e5,$cd,$b3,$b4,$b5,$b6,$c3,$db,$dc,$00 ; #$10 - alien guardian jaw mouth closed (top right) + .byte $00,$e0,$e1,$e1,$00,$60,$40,$40,$00,$00,$8a,$8b,$00,$00,$c1,$c2 ; #$11 - alien guardian top teeth and lower left jaw mouth closed (top-left) + .byte $e3,$d4,$d5,$c4,$df,$00,$62,$cd,$00,$dd,$de,$b6,$ec,$ed,$ee,$d2 ; #$12 - alien guardian jaw mouth open (top right) + .byte $00,$e0,$e1,$e1,$00,$60,$40,$40,$00,$00,$00,$00,$00,$00,$00,$00 ; #$13 - alien guardian top teeth mouth open (top left) + .byte $5c,$6c,$4d,$08,$bc,$bd,$be,$00,$00,$00,$00,$00,$00,$00,$00,$00 ; #$14 - alien guardian lower jaw mouth open + .byte $ca,$cb,$cc,$cd,$cb,$cc,$cd,$c5,$a7,$b9,$c5,$c6,$00,$00,$00,$00 ; #$15 - alien guardian body destroyed + .byte $00,$c7,$c8,$c9,$00,$d6,$c9,$ca,$00,$00,$e6,$98,$00,$00,$00,$00 ; #$16 - alien guardian body destroyed + .byte $b9,$bf,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ; #$17 - alien guardian body destroyed + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$c7,$00,$da,$c7,$c8 + .byte $00,$c7,$c8,$c9,$c7,$c8,$c9,$ca,$c8,$c9,$ca,$cb,$c9,$d8,$d9,$cc ; #$19 - alien guardian body + .byte $ca,$cb,$cc,$cd,$cb,$cc,$cd,$c5,$cc,$cd,$c5,$c6,$cd,$c5,$c6,$00 + .byte $ce,$cf,$cd,$0f,$c6,$c6,$d7,$1f,$00,$00,$e7,$2f,$00,$e7,$c3,$3f + .byte $00,$ea,$eb,$c9,$00,$97,$87,$ca,$00,$ba,$bb,$bb,$00,$d0,$d1,$d1 + .byte $ca,$e8,$e9,$cd,$d8,$b7,$00,$c5,$b7,$b8,$00,$c6,$d3,$c4,$c6,$00 + .byte $c5,$c6,$00,$00,$c6,$00,$00,$e7,$00,$00,$e7,$bf,$00,$e7,$d2,$00 + .byte $e7,$bf,$00,$00,$bf,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $e7,$c0,$e2,$00,$c0,$c3,$9a,$00,$dc,$00,$00,$00,$00,$00,$00,$00 + .byte $20,$21,$22,$23,$30,$31,$32,$33,$fb,$00,$92,$91,$92,$93,$a0,$a1 ; #$21 - alien spider spawn on ceiling closed (frame 1) + .byte $24,$25,$26,$27,$34,$35,$36,$37,$fb,$f3,$92,$91,$92,$93,$a0,$a1 ; #$22 - alien spider spawn on ceiling open (frame 2) + .byte $28,$29,$2a,$2b,$38,$39,$3a,$3b,$fb,$f3,$92,$91,$92,$93,$a0,$a1 ; #$23 - alien spider spawn on ceiling open (frame 3) + .byte $20,$4e,$4f,$23,$00,$00,$00,$00,$fb,$f3,$92,$91,$92,$93,$a0,$a1 ; #$24 - destroyed alien spider spawn on ceiling + .byte $f6,$09,$0a,$0b,$18,$19,$1a,$1b,$02,$03,$02,$03,$12,$13,$12,$13 ; #$25 - alien spider spawn on ground closed (frame 1) + .byte $f5,$05,$06,$07,$5d,$15,$16,$17,$02,$03,$02,$03,$12,$13,$12,$13 ; #$26 - alien spider spawn on ground open (frame 2) + .byte $f4,$6d,$6e,$6f,$7c,$7d,$7e,$7f,$02,$03,$02,$03,$12,$13,$12,$13 ; #$27 - alien spider spawn on ground open (frame 3) + .byte $00,$00,$00,$00,$18,$5e,$5f,$1b,$02,$03,$02,$03,$12,$13,$12,$13 ; #$28 - destroyed alien spider spawn on ground + .byte $00,$00,$00,$00,$00,$00,$00,$00,$02,$03,$02,$03,$12,$13,$12,$13 ; #$29 - empty ground + +; palette data - level 8 (#$73 bytes) ?? (not #$80 bytes) +level_8_palette_data: + .byte $05,$05,$05,$05,$5f,$5f,$5f,$5f,$37,$cd,$7f,$df,$f3,$fc,$33,$cc + .byte $f0,$00,$50,$70,$50,$50,$53,$ff,$00,$ff,$ff,$00,$33,$ff,$5f,$00 + .byte $00,$00,$00,$ff,$00,$00,$ff,$ff,$ff,$ff,$ff,$fc,$f3,$03,$ff,$ff + .byte $f3,$30,$ff,$00,$dc,$50,$c0,$30,$c1,$34,$f0,$50,$00,$00,$ff,$10 + .byte $00,$00,$ff,$3f,$ff,$ff,$ff,$cf,$00 + +; each byte is the palette for an entire super-tile +; updates values in the attribute table +level_8_nametable_update_palette_data: + .byte $aa,$aa,$00,$55,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$cf,$3f,$fc,$f3 + .byte $55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55,$55 + .byte $55,$fa,$fa,$fa,$fa,$5a,$5a,$5a,$5a,$50,$00,$00,$00,$00,$00,$00 + .byte $00,$00,$00,$00,$00,$00,$00 + +; super-tile data - level 2/4 boss room +; CPU address $b57a +level_2_4_boss_supertile_data: + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ; #$00 - blank super-tile + .byte $33,$07,$00,$00,$34,$07,$00,$09,$35,$07,$36,$00,$00,$07,$37,$00 + .byte $00,$20,$21,$01,$09,$20,$21,$01,$00,$20,$21,$01,$00,$20,$21,$02 + .byte $00,$08,$00,$a3,$a2,$a3,$a3,$a2,$00,$a3,$a2,$08,$30,$04,$04,$04 + .byte $00,$07,$38,$33,$00,$07,$00,$34,$00,$07,$00,$35,$00,$07,$00,$00 + .byte $00,$20,$21,$05,$00,$20,$21,$06,$00,$20,$21,$01,$00,$20,$21,$01 + .byte $31,$00,$00,$00,$31,$00,$00,$00,$31,$00,$00,$00,$31,$00,$00,$00 + .byte $00,$07,$00,$00,$00,$07,$00,$00,$00,$07,$00,$00,$00,$07,$00,$00 + .byte $00,$20,$21,$02,$00,$20,$21,$01,$0a,$20,$21,$01,$00,$20,$21,$02 + .byte $a2,$a3,$08,$a3,$00,$a3,$a3,$a2,$08,$a2,$a3,$00,$04,$04,$04,$04 + .byte $00,$00,$07,$00,$00,$00,$07,$00,$00,$00,$07,$00,$00,$00,$07,$00 + .byte $05,$51,$50,$00,$06,$51,$50,$00,$01,$51,$50,$00,$01,$51,$50,$00 + .byte $00,$00,$07,$00,$09,$00,$07,$00,$00,$00,$07,$00,$00,$00,$07,$00 + .byte $02,$51,$50,$00,$01,$51,$50,$09,$01,$51,$50,$00,$02,$51,$50,$00 + .byte $00,$08,$a2,$08,$a3,$a3,$00,$a3,$a3,$a2,$08,$a3,$04,$04,$04,$04 + .byte $a2,$a3,$00,$a2,$a3,$08,$a3,$08,$a2,$a3,$08,$00,$04,$04,$04,$60 + .byte $01,$51,$50,$00,$01,$51,$50,$00,$01,$51,$50,$00,$02,$51,$50,$00 + .byte $00,$00,$07,$63,$00,$00,$07,$64,$00,$66,$07,$65,$00,$67,$07,$00 + .byte $00,$00,$00,$61,$00,$00,$00,$61,$00,$00,$00,$61,$00,$00,$00,$61 + .byte $63,$68,$07,$00,$64,$00,$07,$00,$65,$00,$07,$00,$00,$00,$07,$00 + .byte $00,$20,$21,$01,$22,$23,$24,$02,$25,$26,$27,$9a,$28,$29,$2a,$9b + .byte $2b,$2c,$2d,$10,$0c,$2e,$2f,$10,$0d,$0d,$0d,$0d,$0e,$0e,$0e,$0e + .byte $00,$07,$00,$00,$0c,$07,$0c,$0c,$0d,$07,$0d,$0d,$0e,$07,$0e,$0e + .byte $10,$10,$10,$10,$10,$10,$10,$10,$0d,$0d,$0d,$0d,$0e,$0e,$0e,$4a + .byte $10,$10,$10,$10,$10,$10,$10,$10,$0d,$0d,$0d,$0d,$7a,$0e,$0e,$0e + .byte $10,$10,$10,$10,$32,$03,$03,$03,$4b,$4d,$98,$98,$80,$81,$82,$83 + .byte $0f,$07,$0f,$0f,$0f,$07,$0f,$0f,$0f,$0f,$0f,$0f,$11,$11,$11,$11 + .byte $0f,$0f,$0f,$0f,$0f,$0f,$0f,$0f,$0f,$0f,$0f,$0f,$11,$11,$11,$11 + .byte $10,$10,$10,$10,$03,$03,$03,$62,$98,$98,$7d,$7b,$84,$85,$85,$86 + .byte $01,$51,$50,$00,$02,$54,$53,$52,$9a,$57,$56,$55,$9b,$5a,$59,$58 + .byte $10,$5d,$5c,$5b,$10,$5f,$5e,$0c,$0d,$0d,$0d,$0d,$0e,$0e,$0e,$0e + .byte $00,$00,$07,$00,$0c,$0c,$07,$0c,$0d,$0d,$07,$0d,$0e,$0e,$07,$0e + .byte $0f,$0f,$07,$0f,$0f,$0f,$07,$0f,$0f,$0f,$0f,$0f,$11,$11,$11,$11 + .byte $0f,$0f,$49,$87,$0f,$49,$4d,$98,$49,$4d,$98,$98,$11,$11,$11,$11 + .byte $88,$89,$8a,$8b,$98,$98,$90,$91,$98,$98,$94,$95,$11,$11,$11,$11 + .byte $8c,$8d,$8e,$8f,$92,$93,$98,$98,$95,$96,$98,$98,$11,$11,$11,$11 + .byte $7d,$79,$0f,$0f,$98,$7d,$79,$0f,$98,$98,$7d,$79,$11,$11,$11,$11 + .byte $0f,$7c,$07,$00,$0f,$0f,$07,$00,$0f,$0f,$0f,$7c,$0f,$0f,$0f,$0f + .byte $9d,$00,$07,$00,$9d,$00,$07,$00,$9d,$00,$07,$00,$6d,$00,$07,$00 + .byte $97,$97,$7e,$7f,$a0,$9e,$9e,$6c,$09,$09,$09,$9d,$9e,$9e,$9e,$9f + .byte $a4,$a5,$a4,$a5,$a6,$a7,$a6,$a7,$a4,$a5,$a4,$a5,$a1,$45,$a1,$a1 + .byte $00,$07,$00,$9d,$00,$07,$00,$9d,$00,$07,$00,$9d,$00,$07,$00,$3d + .byte $7e,$7f,$97,$97,$3c,$9e,$9e,$a0,$9d,$09,$09,$09,$9f,$9e,$9e,$9e + .byte $05,$97,$3c,$a0,$06,$97,$9d,$7e,$09,$09,$9d,$97,$9e,$9e,$9f,$9e + .byte $00,$07,$00,$3c,$00,$07,$00,$9d,$00,$07,$00,$9d,$00,$07,$00,$9d + .byte $9e,$9e,$9e,$9e,$97,$39,$3a,$3a,$97,$3b,$00,$00,$97,$3b,$00,$00 + .byte $4f,$4e,$9e,$a0,$3a,$3a,$3a,$3a,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $a0,$9e,$9e,$a0,$3a,$3a,$3a,$9c,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $00,$07,$00,$9d,$00,$07,$00,$9d,$00,$07,$00,$9d,$00,$07,$00,$9d + .byte $97,$3b,$00,$00,$97,$3b,$00,$00,$39,$3a,$3a,$3a,$3b,$00,$00,$00 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$3a,$3a,$3a,$3a,$00,$00,$00,$00 + .byte $a0,$6c,$97,$05,$9e,$9f,$97,$06,$7e,$7f,$09,$09,$9e,$9e,$9e,$9e + .byte $46,$00,$a4,$a5,$43,$00,$a6,$a7,$43,$00,$a4,$a5,$43,$00,$a6,$a7 + .byte $a4,$a5,$a4,$a5,$a6,$a7,$a6,$a7,$a4,$a5,$a4,$a5,$a6,$a7,$a6,$a7 + .byte $9e,$9e,$a0,$9e,$a0,$3c,$9e,$9e,$09,$9d,$7e,$7f,$9e,$6d,$97,$a0 + .byte $00,$07,$00,$3e,$00,$07,$00,$9d,$00,$07,$00,$9d,$00,$07,$00,$9d + .byte $9e,$9e,$9e,$47,$3f,$48,$48,$40,$42,$00,$00,$41,$43,$00,$00,$00 + .byte $a4,$a5,$a4,$a5,$a6,$a7,$a6,$a7,$a4,$a5,$a4,$a5,$a1,$a1,$75,$a1 + .byte $a4,$a5,$00,$73,$a6,$a7,$00,$73,$a4,$a5,$00,$73,$a1,$a1,$a1,$74 + .byte $a0,$9e,$9e,$a0,$9c,$6a,$6a,$6a,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $a0,$9e,$4f,$4e,$6a,$6a,$6a,$6a,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $9e,$9e,$9e,$9e,$6a,$6a,$69,$97,$00,$00,$6b,$97,$00,$00,$6b,$97 + .byte $6c,$00,$07,$00,$9d,$00,$07,$00,$9d,$00,$07,$00,$9d,$00,$07,$00 + .byte $0f,$0f,$0f,$0f,$0f,$0f,$0f,$0f,$0f,$0f,$0f,$0f,$0f,$0f,$0f,$0f + .byte $00,$00,$00,$00,$00,$00,$00,$00,$6a,$6a,$6a,$6a,$00,$00,$00,$00 + .byte $00,$00,$6b,$97,$00,$00,$6b,$97,$6a,$6a,$6a,$69,$00,$00,$00,$6b + .byte $9d,$00,$07,$00,$9d,$00,$07,$00,$9d,$00,$07,$00,$9d,$00,$07,$00 + .byte $00,$07,$4c,$0f,$00,$07,$0f,$0f,$4c,$0f,$0f,$0f,$0f,$0f,$0f,$0f + .byte $a4,$a5,$00,$76,$a6,$a7,$00,$73,$a4,$a5,$00,$73,$a6,$a7,$00,$73 + .byte $9e,$a0,$9e,$9e,$7f,$97,$a0,$9d,$7e,$7f,$09,$9d,$a0,$97,$9e,$9f + .byte $77,$9e,$9e,$9e,$70,$78,$78,$6f,$71,$00,$00,$76,$00,$00,$00,$73 + .byte $6e,$00,$07,$00,$9d,$00,$07,$00,$9d,$00,$07,$00,$9d,$00,$07,$00 + .byte $43,$00,$a4,$a5,$43,$00,$a6,$a7,$43,$00,$a4,$a5,$44,$a1,$a1,$a1 + .byte $a4,$a5,$a4,$a5,$a6,$a7,$a6,$a7,$a4,$a5,$a4,$a5,$a1,$45,$a1,$a1 + +level_2_4_nametable_update_supertile_data: + .byte $12,$13,$14,$15,$16,$17,$18,$19,$1a,$1b,$1c,$1d,$1e,$1f,$a8,$a9 ; closed wall cannon + .byte $12,$aa,$ab,$15,$c1,$ae,$af,$c2,$b2,$b3,$b4,$b5,$b9,$ba,$bb,$bc ; partial open wall cannon + .byte $12,$ac,$ad,$15,$c1,$b0,$b1,$c2,$b2,$b6,$b7,$b8,$bd,$be,$bf,$c0 ; fully open wall cannon + .byte $12,$aa,$ab,$15,$c1,$c3,$c4,$c2,$c7,$c8,$c9,$ca,$b9,$ba,$bb,$bc ; wall plating #$01 + .byte $12,$ac,$ad,$15,$c1,$c5,$c6,$c2,$c7,$cb,$cc,$cd,$bd,$be,$bf,$c0 ; wall plating #$02 + .byte $ce,$cf,$d0,$d1,$d2,$00,$00,$d3,$d4,$d5,$d6,$d7,$d8,$d9,$da,$db ; destroyed wall plating + .byte $a4,$a5,$1a,$1b,$a6,$a7,$1e,$1f,$a4,$a5,$a4,$a5,$a6,$a7,$a6,$a7 + .byte $a4,$a5,$b2,$b3,$a6,$a7,$b9,$ba,$a4,$a5,$a4,$a5,$a6,$a7,$a6,$a7 + .byte $a4,$a5,$b2,$b6,$a6,$a7,$bd,$be,$a4,$a5,$a4,$a5,$a6,$a7,$a6,$a7 + .byte $9e,$9e,$12,$13,$9e,$47,$16,$17,$48,$40,$1a,$1b,$00,$41,$1e,$1f + .byte $9e,$9e,$12,$aa,$9e,$47,$c1,$c3,$48,$40,$c7,$c8,$00,$41,$b9,$ba + .byte $9e,$9e,$12,$ac,$9e,$47,$c1,$c5,$48,$40,$c7,$cb,$00,$41,$bd,$be + .byte $14,$15,$9e,$9e,$18,$19,$77,$9e,$1c,$1d,$70,$78,$a8,$a9,$71,$00 + .byte $ab,$15,$9e,$9e,$c4,$c2,$77,$9e,$c9,$ca,$70,$78,$bb,$bc,$71,$00 + .byte $ad,$15,$9e,$9e,$c6,$c2,$77,$9e,$cc,$cd,$70,$78,$bf,$c0,$71,$00 + .byte $0b,$0b,$0b,$0b,$09,$09,$09,$09,$12,$13,$14,$15,$16,$17,$18,$19 + .byte $0b,$0b,$0b,$0b,$09,$09,$09,$09,$12,$aa,$ab,$15,$c1,$c3,$c4,$c2 + .byte $0b,$0b,$0b,$0b,$09,$09,$09,$09,$12,$ac,$ad,$15,$c1,$c5,$c6,$c2 + .byte $1a,$1b,$1c,$1d,$1e,$1f,$a8,$a9,$9a,$9a,$9a,$9a,$9b,$9b,$9b,$9b + .byte $c7,$c8,$c9,$ca,$b9,$ba,$bb,$bc,$9a,$9a,$9a,$9a,$9b,$9b,$9b,$9b + .byte $c7,$cb,$cc,$cd,$bd,$be,$bf,$c0,$9a,$9a,$9a,$9a,$9b,$9b,$9b,$9b + .byte $00,$00,$00,$00,$00,$00,$00,$00,$14,$15,$6a,$6a,$18,$19,$00,$00 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$ab,$15,$6a,$6a,$af,$19,$00,$00 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$ad,$15,$6a,$6a,$b1,$19,$00,$00 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$3a,$3a,$12,$13,$00,$00,$16,$17 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$3a,$3a,$12,$aa,$00,$00,$16,$ae + .byte $00,$00,$00,$00,$00,$00,$00,$00,$3a,$3a,$12,$ac,$00,$00,$16,$b0 + .byte $0b,$0b,$0b,$0b,$09,$09,$7e,$7f,$14,$15,$9e,$9e,$18,$19,$01,$01 + .byte $0b,$0b,$0b,$0b,$09,$09,$7e,$7f,$ab,$15,$9e,$9e,$c4,$c2,$01,$01 + .byte $0b,$0b,$0b,$0b,$09,$09,$7e,$7f,$ad,$15,$9e,$9e,$c6,$c2,$01,$01 + .byte $1c,$1d,$a4,$a5,$a8,$a9,$a6,$a7,$a4,$a5,$a4,$a5,$a6,$a7,$a6,$a7 + .byte $b4,$b5,$a4,$a5,$bb,$bc,$a6,$a7,$a4,$a5,$a4,$a5,$a6,$a7,$a6,$a7 + .byte $b7,$b8,$a4,$a5,$bf,$c0,$a6,$a7,$a4,$a5,$a4,$a5,$a6,$a7,$a6,$a7 + .byte $1c,$1d,$a0,$a0,$a8,$a9,$0a,$0a,$9a,$9a,$9a,$9a,$9b,$9b,$9b,$9b + .byte $c9,$ca,$a0,$a0,$bb,$bc,$0a,$0a,$9a,$9a,$9a,$9a,$9b,$9b,$9b,$9b + .byte $cc,$cd,$a0,$a0,$bf,$c0,$0a,$0a,$9a,$9a,$9a,$9a,$9b,$9b,$9b,$9b + .byte $a0,$a0,$1a,$1b,$0a,$0a,$1e,$1f,$9a,$9a,$9a,$9a,$9b,$9b,$9b,$9b + .byte $a0,$a0,$c7,$c8,$0a,$0a,$b9,$ba,$9a,$9a,$9a,$9a,$9b,$9b,$9b,$9b + .byte $a0,$a0,$c7,$cb,$0a,$0a,$bd,$be,$9a,$9a,$9a,$9a,$9b,$9b,$9b,$9b + .byte $0b,$0b,$0b,$0b,$7e,$7f,$09,$09,$9e,$9e,$12,$13,$01,$01,$16,$17 + .byte $0b,$0b,$0b,$0b,$7e,$7f,$09,$09,$9e,$9e,$12,$aa,$00,$00,$c1,$c3 + .byte $0b,$0b,$0b,$0b,$7e,$7f,$09,$09,$9e,$9e,$12,$ac,$00,$00,$c1,$c5 + .byte $a4,$a5,$d4,$d5,$a6,$a7,$d8,$d9,$a4,$a5,$a4,$a5,$a6,$a7,$a6,$a7 + .byte $9e,$9e,$ce,$cf,$9e,$47,$d2,$00,$48,$40,$d4,$d5,$00,$41,$d8,$d9 + .byte $d0,$d1,$9e,$9e,$00,$d3,$77,$9e,$d6,$d7,$70,$00,$da,$db,$71,$00 + .byte $0b,$0b,$0b,$0b,$09,$09,$09,$09,$ce,$cf,$d0,$d1,$d2,$00,$00,$d3 + .byte $d4,$d5,$d6,$d7,$d8,$d9,$da,$db,$9a,$9a,$9a,$9a,$9b,$9b,$9b,$9b + .byte $00,$00,$00,$00,$00,$00,$00,$00,$d0,$d1,$6a,$6a,$00,$d3,$00,$00 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$3a,$3a,$ce,$cf,$00,$00,$d2,$00 + .byte $0b,$0b,$0b,$0b,$09,$09,$7e,$7f,$d0,$d1,$9e,$9e,$00,$d3,$01,$01 + .byte $d6,$d7,$a4,$a5,$da,$db,$a6,$a7,$a4,$a5,$a4,$a5,$a6,$a7,$a6,$a7 + .byte $d6,$d7,$a0,$a0,$da,$db,$0a,$0a,$9a,$9a,$9a,$9a,$9b,$9b,$9b,$9b + .byte $a0,$a0,$d4,$d5,$0a,$0a,$d8,$d9,$9a,$9a,$9a,$9a,$9b,$9b,$9b,$9b + .byte $0b,$0b,$0b,$0b,$7e,$7f,$09,$09,$9e,$9e,$ce,$cf,$01,$01,$d2,$00 + +; color data for level 2 boss room (#$80 bytes) +level_2_4_boss_palette_data: + .byte $00,$04,$55,$5f,$11,$55,$11,$11,$55,$5e,$44,$55,$45,$55,$5e,$5b + .byte $55,$00,$44,$44,$55,$04,$00,$05,$05,$45,$00,$00,$15,$55,$01,$00 + .byte $00,$00,$05,$05,$00,$04,$55,$55,$00,$55,$55,$55,$55,$15,$05,$05 + .byte $55,$55,$50,$55,$11,$00,$55,$55,$55,$00,$00,$05,$05,$45,$55,$00 + .byte $50,$54,$55,$01,$44,$55,$55,$55,$01,$00 + +; each byte is the palette for an entire super-tile +; updates values in the attribute table +level_2_4_boss_nametable_update_palette_data: + .byte $af,$af,$af,$af,$af,$55,$08,$08,$08,$9d,$9d,$9d,$67,$67,$67,$f5 + .byte $f5,$f5,$5a,$5a,$5a,$70,$70,$70,$d0,$d0,$d0,$75,$75,$75,$02,$02 + .byte $02,$56,$56,$56,$59,$59,$59,$d5,$d5,$d5,$04,$55,$55,$55,$55,$50 + .byte $50,$55,$01,$55,$55,$55 + +run_end_level_sequence_routine: + lda #$00 ; a = #$00 + sta CONTROLLER_STATE ; clear player 1 input + sta CONTROLLER_STATE+1 ; clear player 2 input + sta CONTROLLER_STATE_DIFF ; clear player 1 input difference + sta CONTROLLER_STATE_DIFF+1 ; clear player 2 input difference + lda END_LEVEL_ROUTINE_INDEX ; load end_level_sequence_ptr_tbl index to jump to + jsr run_routine_from_tbl_below ; run routine a in the following table (end_level_sequence_ptr_tbl) + +; pointer table for end of level sequences (#$3 * #$2 = #$6 bytes) +end_level_sequence_ptr_tbl: + .addr end_level_sequence_00 ; CPU address $be0f + .addr end_level_sequence_01 ; CPU address $be3c + .addr end_level_sequence_02 ; CPU address $bf78 + +; wait for both players to land from jumping to initiate sequence +end_level_sequence_00: + lda #$00 ; a = #$00 + sta $08 ; initialize jump status check + ldx #$01 ; start with player 2 + +@player_loop: + lda #$00 ; a = #$00 + ldy P1_GAME_OVER_STATUS,x ; game over state (1 = game over) + bne @move_next_player ; skip player if in game over state + lda PLAYER_JUMP_STATUS,x ; low nibble 1 = jumping, 0 not jumping; high nibble = facing direction + ora $08 ; merge players' jump status + sta $08 ; update with merged jump status + lda #$01 ; a = #$01 + +@move_next_player: + sta LEVEL_END_LVL_ROUTINE_STATE,x ; set to #$01 + dex ; decrement player index + bpl @player_loop ; if not already on player 1, move to player 1 + lda $08 ; load merged jump status + beq @continue ; if 0, neither player is jumping, branch + rts ; one of the players is jumping, don't begin ending sequence + +; players are on the ground, move to end_level_sequence_01 +@continue: + lda #$81 ; a = #$81 + sta BOSS_DEFEATED_FLAG + lda #$f0 ; a = #$f0 + sta LEVEL_END_SQ_1_TIMER + lda #$20 ; a = #$20 + jmp set_delay_adv_routine ; set LEVEL_END_DELAY_TIMER to #$20 and advance to end_level_sequence_01 + +end_level_sequence_01: + lda LEVEL_END_DELAY_TIMER ; load level ending sequence delay timer + beq @continue ; continue if elapsed + dec LEVEL_END_DELAY_TIMER ; timer hasn't elapsed, decrement and exit + rts + +@continue: + ldx #$01 ; x = #$01 + +@player_lvl_routine_loop: + ldy LEVEL_END_LVL_ROUTINE_STATE,x + beq @continue2 + dey ; decrement level routine state index + sty $08 ; store LEVEL_END_LVL_ROUTINE_STATE in $08 + jsr run_end_of_lvl_lvl_routine + jsr make_off_screen_player_invisible_exit ; make invisible if necessary and exit + +@continue2: + dex + bpl @player_lvl_routine_loop + lda FRAME_COUNTER ; load frame counter + lsr + bcc @set_0192_exit + dec LEVEL_END_SQ_1_TIMER + beq @set_level_delay_adv_routine + +@set_0192_exit: + lda LEVEL_END_LVL_ROUTINE_STATE + ora LEVEL_END_LVL_ROUTINE_STATE+1 ; set player 2 value + bne end_level_sequence_01_exit + +@set_level_delay_adv_routine: + ldy CURRENT_LEVEL ; current level + lda level_end_level_delay_timer_tbl,y + +set_delay_adv_routine: + sta LEVEL_END_DELAY_TIMER + inc END_LEVEL_ROUTINE_INDEX ; go to next method in end_level_sequence_ptr_tbl + +end_level_sequence_01_exit: + rts + +; table for delay timer used in level-specif ending routines (#$8 bytes) +; waterfall is the only different value at #$e0 +level_end_level_delay_timer_tbl: + .byte $a0,$a0,$e0,$a0,$a0,$a0,$a0,$a0 + +; runs the routines for handling end of level +; input +; * $08 - LEVEL_END_LVL_ROUTINE_STATE +run_end_of_lvl_lvl_routine: + lda CURRENT_LEVEL ; current level + jsr run_routine_from_tbl_below ; run routine a in the following table (end_of_lvl_lvl_routine_ptr_tbl) + +; end of level routines (#$8 * #$2 = #$10 bytes) +end_of_lvl_lvl_routine_ptr_tbl: + .addr end_of_lvl_routine_lvl_1 ; CPU address $be92 + .addr end_of_lvl_routine_indoor ; CPU address $bec4 + .addr end_of_lvl_routine_lvl_3 ; CPU address $bf13 + .addr end_of_lvl_routine_indoor ; CPU address $bec4 + .addr end_of_lvl_routine_lvl_5 ; CPU address $bf4c + .addr end_of_lvl_routine_lvl_6 ; CPU address $bf63 + .addr end_of_lvl_routine_lvl_7 ; CPU address $bf67 + .addr end_of_lvl_routine_lvl_8 ; CPU address $bf6b + +; end of level 1 +; animate moving player right until enter tunnel +; 3 states +; * #$00 - walk to the right +; * #$01 - jump to the right +; * #$02 - walk to the right +end_of_lvl_routine_lvl_1: + lda SPRITE_X_POS,x ; player x position on screen + cmp #$98 ; see where the player is at horizontally + lda #$00 ; a = #$00 + bcc @continue ; branch if player is in the left 60% of the screen + lda #$80 ; player close to right edge, set background priority for player sprite + +@continue: + sta PLAYER_BG_FLAG_EDGE_DETECT,x ; set player sprite attribute so background takes priority + ldy $08 ; load LEVEL_END_LVL_ROUTINE_STATE + bne end_of_lvl_routine_lvl_1_01 ; branch if player should jump into the tunnel (state #$01) + jsr press_d_pad_right ; press the d-pad right button to move the player to the right + lda SPRITE_X_POS,x ; player x position on screen + cmp #$90 ; trigger point to jump into tunnel + bcc routine_lvl_1_exit ; branch if not yet reached jump trigger point + +routine_lvl_1_adv_lvl_state_exit: + inc LEVEL_END_LVL_ROUTINE_STATE,x + +routine_lvl_1_exit: + rts + +end_of_lvl_routine_lvl_1_01: + dey + bne press_d_pad_right ; branch to press the d-pad right button to move the player to the right (state #$02) + lda PLAYER_JUMP_STATUS,x ; low nibble 1 = jumping, 0 not jumping; high nibble = facing direction + bne routine_lvl_1_adv_lvl_state_exit ; exit if jump already started + lda #$81 ; need to jump, set controller input A button and right arrow button pressed + sta CONTROLLER_STATE,x ; store this value into player input + sta $f5,x ; store this value into player input + rts + +; press the d-pad right button to move the player to the right +press_d_pad_right: + lda #$01 ; controller input D-pad right arrow + sta CONTROLLER_STATE,x ; store this value into player input + rts + +; indoor/base (level 2 and 4) end of level routine +end_of_lvl_routine_indoor: + ldy $08 ; load current LEVEL_END_LVL_ROUTINE_STATE + bne end_of_lvl_routine_indoor_01 ; see if in state #$01 + lda indoor_lvl_end_input_tbl,x ; state #$00 - load the appropriate d-pad input based on current player (left or right) + sta CONTROLLER_STATE,x ; controller buttons held + lda SPRITE_X_POS,x ; player x position on screen + sec ; set carry flag in preparation for subtraction + sbc indoor_lvl_elevator_pos_tbl,x ; subtract elevator x position from player x position + bcs @cmp_elevator_distance ; branch if player to the right of the elevator + eor #$ff ; to the left of the elevator (negative result), flip all bits and add 1 (two's complement) + adc #$01 ; to get a positive number (the distance to the elevator) + +@cmp_elevator_distance: + cmp #$02 ; see if within 2 pixels of elevator + bcs routine_indoor_lvl_exit ; not near elevator, exit + +routine_indoor_lvl_adv_lvl_state_exit: + inc LEVEL_END_LVL_ROUTINE_STATE,x + +routine_indoor_lvl_exit: + rts + +end_of_lvl_routine_indoor_01: + dey ; test next LEVEL_END_LVL_ROUTINE_STATE + bne end_of_lvl_routine_indoor_02 ; branch if not in state #$01 + lda #$03 ; state #$01 - load a = #$03 + sta PLAYER_STATE,x ; set player can't move state + lda indoor_lvl_elevator_attr_tbl,x ; load the correct sprite attribute (color) for the player + sta SPRITE_ATTR,x ; set the sprite attribute in the sprite cpu buffer + jsr set_player_on_elevator_sprite ; set the elevator sprite code in the sprite cpu buffer + bne routine_indoor_lvl_adv_lvl_state_exit ; always jump, move to next level state and then exit + +; wait for other player to get on elevator +end_of_lvl_routine_indoor_02: + dey ; test next LEVEL_END_LVL_ROUTINE_STATE + bne end_of_lvl_routine_indoor_03 ; branch if not in state #$02 + stx $10 ; state #$02, see which LEVEL_END_LVL_ROUTINE_STATE the other player is in, backup current x + txa ; move x to a so can flip bit 0 + eor #$01 ; swap to other player's state + tax ; move other player offset to x + lda LEVEL_END_LVL_ROUTINE_STATE,x ; load other player's LEVEL_END_LVL_ROUTINE_STATE + ldx $10 ; restore player offset back to original player + tay ; move other player LEVEL_END_LVL_ROUTINE_STATE to y + beq routine_indoor_lvl_adv_lvl_state_exit ; branch if other player is in state #$00, i.e. player 2 not playing + cmp #$03 ; compare other player's state to #$03 + bcs routine_indoor_lvl_adv_lvl_state_exit ; branch and move to state #$03 if other player is also on elevator (in state #$02) + rts + +; ride the elevator up +end_of_lvl_routine_indoor_03: + dec SPRITE_Y_POS,x ; move player and elevator up + +; set player on elevator sprite in cpu sprite memory +set_player_on_elevator_sprite: + lda #$91 ; player on elevator sprite code sprite_91 + sta CPU_SPRITE_BUFFER,x ; ensure sprite code is set + rts + +; end of level 3 +; 2 states +; * walk to middle of screen (dragon gate) +; * jump into gate +end_of_lvl_routine_lvl_3: + ldy $08 ; load current LEVEL_END_LVL_ROUTINE_STATE + bne end_of_lvl_routine_lvl_3_01 + lda #$01 ; controller input D-pad right arrow + ldy SPRITE_X_POS,x ; load player horizontal position + cpy #$80 ; see where player is in relation to middle of screen + bcc @continue ; branch if player to the left of the middle + lda #$02 ; player to the right or in middle of screen, set controller input D-pad left arrow + +@continue: + sta CONTROLLER_STATE,x ; set controller input (eight left or right) + tya ; move player x position to a + sec ; set carry flag in preparation for subtraction + sbc #$80 ; subtract #$80 to get distance from middle of screen + bcs @cmp_dist ; result is not negative, continue to compare distance to middle of screen + eor #$ff ; player to left of middle, (negative result), flip all bits and add 1 (two's complement) + adc #$01 ; to get a positive number (the distance to the middle) + +@cmp_dist: + cmp #$08 ; see if within 8 pixels of the middle of the screen horizontally + bcs routine_lvl_3_exit ; not yet close to center, exit + inc LEVEL_END_LVL_ROUTINE_STATE,x ; can now jump into dragon gate + +routine_lvl_3_exit: + rts + +; jump into dragon gate +end_of_lvl_routine_lvl_3_01: + lda #$80 ; controller A button pressed (jump) + sta CONTROLLER_STATE_DIFF,x ; store as new input + lda PLAYER_JUMP_STATUS,x ; low nibble 1 = jumping, 0 not jumping; high nibble = facing direction + beq routine_lvl_3_exit ; exit if just starting the jump + lda PLAYER_Y_FAST_VELOCITY,x ; get current jump velocity + bmi routine_lvl_3_exit ; exit if still jumping up (not falling back down) + lda SPRITE_Y_POS,x ; falling back down, see if should hide player behind wall yet + cmp #$b0 ; compare to height of wall below dragon gate + bcc routine_lvl_3_exit ; exit if not yet fallen down to the top of the wall + jmp make_player_invisible ; falling 'behind' wall, make player invisible to simulate effect + +; end of level 5 +end_of_lvl_routine_lvl_5: + ldy #$00 ; y = #$00 + +; move the player right and set PLAYER_BG_FLAG_EDGE_DETECT appropriate based on x position +; x trigger position based on x_pos_bg_priority_trigger_tbl,y +; input +; * y - offset into x_pos_bg_priority_trigger_tbl +move_right_set_bg_priority: + jsr press_d_pad_right ; press the d-pad right button to move the player to the right + lda SPRITE_X_POS,x ; load player's x position + cmp x_pos_bg_priority_trigger_tbl,y ; see if player has crossed trigger point to put player in background + lda #$01 ; always set bit 0 (player continues walking off horizontally off ledge) + bcc @set_bg_priority_exit ; branch if player x position is to left of x_pos_bg_priority_trigger_tbl position + ora #$80 ; set bit 7 to specify sprite draws behind background + +@set_bg_priority_exit: + sta PLAYER_BG_FLAG_EDGE_DETECT,x + rts + +; table for horizontal trigger points for setting PLAYER_BG_FLAG_EDGE_DETECT (#$3 bytes) +; byte 0 - level 5 (snow field) +; byte 1 - level 6 (energy zone) +; byte 2 - level 7 (hangar) +x_pos_bg_priority_trigger_tbl: + .byte $b8,$d0,$d0 + +; end of level 6 +end_of_lvl_routine_lvl_6: + ldy #$01 ; y = #$01 + bne move_right_set_bg_priority + +; end of level 7 +end_of_lvl_routine_lvl_7: + ldy #$02 ; y = #$02 + bne move_right_set_bg_priority + +; end of level 8 +end_of_lvl_routine_lvl_8: + ldy $08 + bne @exit + lda #$40 ; a = #$40 + sta LEVEL_END_SQ_1_TIMER + inc LEVEL_END_LVL_ROUTINE_STATE,x + +@exit: + rts + +; mark end of level routines as complete and move to level_routine_05 +end_level_sequence_02: + lda #$02 ; a = #$02 + sta BOSS_DEFEATED_FLAG + dec LEVEL_END_DELAY_TIMER + bne @exit ; exit if level end delay timer hasn't elapsed + jsr set_graphics_zero_mode ; set GRAPHICS_BUFFER_MODE to #$00 to prepare writing text to screen (write_text_palette_to_mem) + lda #$05 ; a = #$05 + jmp set_a_as_current_level_routine ; set current level_routine to level_routine_05 + +@exit: + rts + +; make the player invisible if off the screen to the right, or off screen to the top +make_off_screen_player_invisible_exit: + lda SPRITE_Y_POS,x + cmp #$08 + bcc make_player_invisible ; branch if player at top of screen + lda SPRITE_X_POS,x + cmp #$f8 + bcs make_player_invisible ; branch if player at right edge of screen + cmp #$04 + bcs end_level_sequence_02_exit ; exit if player not at left edge of screen + +make_player_invisible: + lda #$ff ; a = #$ff + sta PLAYER_HIDDEN,x ; set player to be invisible + lda #$00 + sta LEVEL_END_LVL_ROUTINE_STATE,x + sta PLAYER_SPRITES,x ; clear player sprite + +end_level_sequence_02_exit: + rts + +; tables for end of indoor/base level 2/4 +; player 1 walks to the left elevator - d-pad left (#$02) +; player 2 walks to the right elevator - d-pad right right (#$01) +; controller input - player 1/2 (#$2 bytes) +indoor_lvl_end_input_tbl: + .byte $02,$01 + +; x position for elevator (#$02 bytes) +indoor_lvl_elevator_pos_tbl: + .byte $0c,$f4 + +; table for sprite attribute for being on elevator (#$2 bytes) +; sprite_91 - indoor boss defeated elevator with player on top +; byte 0 is for player 1, byte 1 is for player 2 +indoor_lvl_elevator_attr_tbl: + .byte $00,$45 + +; unused space (#$51 bytes) +; these $ff bytes can technically be deleted here because the contra.cfg +; specifies that any free bytes in a ROM bank will be filled with $ff +; and each ROM bank is 16 KiB +bank_3_unused_space: + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff \ No newline at end of file diff --git a/src/bank4.asm b/src/bank4.asm new file mode 100644 index 0000000..fcdfa66 --- /dev/null +++ b/src/bank4.asm @@ -0,0 +1,814 @@ +; Contra US Disassembly - v1.0 +; https://github.com/vermiceli/nes-contra-us +; Bank 4 mostly contains compressed graphic data. The rest of bank 4 is the code +; for the ending scene animation and the ending credits, including the ending +; credits text data. + +.segment "BANK_4" + +.include "constants.asm" + +; import labels from bank 7 +.import advance_graphic_read_addr, decrement_delay_timer +.import init_APU_channels, init_game_routine_reset_timer_low_byte +.import load_A_offset_graphic_data, load_alternate_graphics +.import load_palette_indexes, play_sound +.import reset_delay_timer, run_routine_from_tbl_below +.import zero_out_nametables + +; export labels used by bank 7 +.export graphic_data_01, graphic_data_03 +.export graphic_data_04, graphic_data_06 +.export graphic_data_08, graphic_data_09 +.export graphic_data_0a, graphic_data_0f +.export graphic_data_10, graphic_data_11 +.export graphic_data_12, graphic_data_13 +.export run_game_end_routine + +; Every PRG ROM bank starts with a single byte specifying which number it is +.byte $04 ; The PRG ROM bank number (4) + +; compressed graphics data - code 03 (#$5ad bytes) +; used in every level, except intro and ending scenes +; pattern table data - writes addresses [$0000-$0680) +; * bill and lance blocks +; * game over letters +; * lives medals +; * power-ups (SBFLRM) +; * explosions +; CPU address $8001 +graphic_data_03: + .incbin "assets/graphic_data/graphic_data_03.bin" + +; compressed graphics data - code 04 (#$1f3 bytes) +; character facing up +; pattern table data - writes addresses [$0680-$08c0) +; CPU address $85ae +graphic_data_04: + .incbin "assets/graphic_data/graphic_data_04.bin" + +; compressed graphics data - code 13 (#$cb bytes) +; left pattern table data - writes addresses [$08c0-$09a0) +; CPU address $87a1 +graphic_data_13: + .incbin "assets/graphic_data/graphic_data_13.bin" + +; compressed graphics data - code 08 (#$1161 bytes) +; pattern table data - writes addresses [$09a0-$2000) +; CPU address $886c +graphic_data_08: + .incbin "assets/graphic_data/graphic_data_08.bin" + +; compressed graphics data - code 09 (#$2f bytes) +; left pattern table data - writes addresses [$0b00-$0b40) +; CPU address $99cd +graphic_data_09: + .incbin "assets/graphic_data/graphic_data_09.bin" + +; compressed graphics data - code 06 (#$607 bytes) +; most Base graphics +; pattern table data - writes addresses [$08c0-$1100) +; CPU address $99fc +graphic_data_06: + .incbin "assets/graphic_data/graphic_data_06.bin" + +; compressed graphics data - code 10 (#$343 bytes) +; horizontal flip +; CPU address $a003 +graphic_data_10: + .byte $00,$16 + +; compressed graphics data - code 0a (#$341 bytes) +; right pattern table data - writes addresses [$1100-$1520) +; CPU address $a005 +graphic_data_0a: + .incbin "assets/graphic_data/graphic_data_0a.bin" + +; compressed graphics data - code 0f (#$a1 bytes) +; right pattern table data - writes addresses [$1520-$1600) +; CPU address $a346 +graphic_data_0f: + .incbin "assets/graphic_data/graphic_data_0f.bin" + +; compressed graphics data - code 11 (#$559 bytes) +; right pattern table data - writes addresses [$a120-$2000) +; CPU address $a3e7 +graphic_data_11: + .incbin "assets/graphic_data/graphic_data_11.bin" + +; compressed graphics data - code 12 (#$ed bytes) +; Base 2 Graphics +; right pattern table data - writes addresses [$1b90-$1ca0) +; CPU Address $a940 +graphic_data_12: + .incbin "assets/graphic_data/graphic_data_12.bin" + +; compressed graphics data - code 01 (#$e8c bytes) +; pattern table for intro, level title, game over screens +; used by graphic_data_02 nametable data +; pattern table data - writes addresses [$0ce0-$1f80) +; last #$80 bytes of pattern table not used +; CPU address $aa2d +graphic_data_01: + .incbin "assets/graphic_data/graphic_data_01.bin" + +run_game_end_routine: + lda GAME_END_ROUTINE_INDEX + jsr run_routine_from_tbl_below ; run routine a in the following table (game_end_routine_tbl) + +; pointer table for ending (6 * 2 = c bytes) +game_end_routine_tbl: + .addr game_end_routine_00 ; CPU address $b8ca (fade away) + .addr game_end_routine_01 ; CPU address $b8d3 (screen melt and init for game_end_routine_02) + .addr game_end_routine_02 ; CPU address $b93e (helicopter flying away and island exploding) + .addr game_end_routine_03 ; CPU address $b941 (congratulations text scrolling and credits) + .addr game_end_routine_04 ; CPU address $bae3 (music change and presented by Konami) + .addr game_end_routine_05 ; CPU address $bb87 + +; set level to #$08 (ending routing) +game_end_routine_00: + lda #$08 ; a = #$08 + sta CURRENT_LEVEL ; set current level to 'level 9' (special ending level) + dec GRAPHICS_BUFFER_MODE ; set GRAPHICS_BUFFER_MODE to #$ff to prepare writing tile data + jmp init_game_routine_reset_timer_low_byte ; set timer and increment GAME_END_ROUTINE_INDEX + +game_end_routine_01: + lda $40 ; for end of game sequence, this is used to know which part of screen to blank + asl ; and no longer means location type + tay + ldx GRAPHICS_BUFFER_OFFSET ; load graphics buffer offset + lda #$02 ; a = #$02 + sta CPU_GRAPHICS_BUFFER,x ; vram_address_increment offset #$02, set VRAM address increment to 1 (write down) + lda #$20 ; a = #$20 + inx + sta CPU_GRAPHICS_BUFFER,x ; #$20 tiles to be written per group + lda #$01 ; a = #$01 + inx + sta CPU_GRAPHICS_BUFFER,x ; #$01 group of tiles to write + lda screen_melt_ppu_add_tbl+1,y ; load PPU write address low byte + inx + sta CPU_GRAPHICS_BUFFER,x + lda screen_melt_ppu_add_tbl,y ; load PPU write address high byte + clc ; clear carry in preparation for addition + adc $41 ; add to byte offset into pattern table tile to write + inx + sta CPU_GRAPHICS_BUFFER,x + ldy #$20 ; number of tiles to write + lda #$00 ; a = #$00 + inx + +; writes a blank tile #$20 tiles to CPU_GRAPHICS_BUFFER +@write_tile_to_buffer: + sta CPU_GRAPHICS_BUFFER,x + inx + dey + bne @write_tile_to_buffer + stx GRAPHICS_BUFFER_OFFSET ; update graphics buffer write offset + lda $40 ; load current screen_melt_ppu_add_tbl offset + clc ; clear carry in preparation for addition + adc #$01 + cmp #$08 + bne @set_screen_melt_offset + inc $41 ; increment byte offset into pattern table tile to write + lda $41 + cmp #$10 + beq @load_alt_graphics + lda #$00 ; set screen_melt_ppu_add_tbl offset to #$00 + +@set_screen_melt_offset: + sta $40 ; update screen_melt_ppu_add_tbl offset + rts + +@load_alt_graphics: + lda #$40 ; a = #$40 + sta DELAY_TIME_LOW_BYTE ; various delays (low byte) + jsr init_game_routine_reset_timer_low_byte ; set timer and increment GAME_END_ROUTINE_INDEX + jsr load_alternate_graphics + lda #$0c ; level_graphic_data_tbl offset (ending_graphic_data) + jmp load_A_offset_graphic_data ; load ending_graphic_data + +; a list of PPU write addresses for use to clear the screen a portion at a time +; PPU write low byte, then high (#$8 items * #$02 = #$10 bytes) +screen_melt_ppu_add_tbl: + .byte $00,$10 + .byte $00,$14 + .byte $00,$18 + .byte $00,$1c + .byte $10,$10 + .byte $10,$14 + .byte $10,$18 + .byte $10,$1c + +game_end_routine_02: + jmp init_game_routine_reset_timer_low_byte + +game_end_routine_03: + jsr load_palette_indexes ; load the palette colors + lda END_LEVEL_ROUTINE_INDEX ; routine index for ending scene + jsr run_routine_from_tbl_below ; run routine a in the following table (end_game_sequence_ptr_tbl) + +; pointer table for ending scenes (#$03 * #$02 = #06 bytes) +; analogous to end_level_sequence_ptr_tbl, but for end of game +end_game_sequence_ptr_tbl: + .addr end_game_sequence_00 ; CPU address $b94f + .addr end_game_sequence_01 ; CPU address $b98f + .addr end_game_sequence_02 ; CPU address $bac8 + +; ending scene - pointer 0 +end_game_sequence_00: + lda #$cf ; a = #$cf (3 mountains) + sta ENEMY_SPRITES+8 ; enemy 8 sprite code + lda #$c5 ; a = #$c5 (green helicopter frame 1) + sta ENEMY_SPRITES+9 ; helicopter sprite code + lda #$ff ; a = #$ff + sta ENEMY_X_VELOCITY_FAST+9 ; helicopter x velocity (high byte) + sta ENEMY_Y_VELOCITY_FAST+9 ; helicopter y velocity (high byte) + lda #$60 ; a = #$60 + sta ENEMY_X_VELOCITY_FRACT+9 ; helicopter x velocity (low byte) + lda #$70 ; a = #$70 + sta ENEMY_Y_VELOCITY_FRACT+9 ; helicopter y velocity (low byte) + ldx #$09 ; x = #$09 + ldy #$00 ; y = #$00 + +@set_ending_sprite_animations: + lda end_scene_sprite_anim_tbl,y + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + lda end_scene_sprite_anim_tbl+1,y + sta ENEMY_X_POS,x ; set enemy x position on screen + lda end_scene_sprite_anim_tbl+2,y + sta ENEMY_Y_POS,x ; enemy y position on screen + iny + iny + iny + dex + bpl @set_ending_sprite_animations + lda #$21 ; a = #$21 (sound_21) + jsr play_sound ; play helicopter rotors sound + inc END_LEVEL_ROUTINE_INDEX ; increment ending scene index (end_game_sequence_ptr_tbl) + rts + +; ending scene - pointer 1 +end_game_sequence_01: + lda ENEMY_SPRITES+9 ; helicopter sprite code + cmp #$01 ; see if helicopter is hidden + beq start_ending_seq_enemy_loop ; branch if helicopter is hidden + lda ENEMY_X_VEL_ACCUM+9 + clc ; clear carry in preparation for addition + adc ENEMY_X_VELOCITY_FRACT+9 + sta ENEMY_X_VEL_ACCUM+9 + lda ENEMY_X_POS+9 + adc ENEMY_X_VELOCITY_FAST+9 + sta ENEMY_X_POS+9 + lda ENEMY_Y_VEL_ACCUM+9 + clc ; clear carry in preparation for addition + adc ENEMY_Y_VELOCITY_FRACT+9 + sta ENEMY_Y_VEL_ACCUM+9 + lda ENEMY_Y_POS+9 + adc ENEMY_Y_VELOCITY_FAST+9 + sta ENEMY_Y_POS+9 + bcs @continue + lda #$01 ; a = #$01 + sta ENEMY_SPRITES+9 ; helicopter sprite code (blank sprite) + bne start_ending_seq_enemy_loop + +@continue: + lda ENEMY_X_VELOCITY_FRACT+9 + clc ; clear carry in preparation for addition + adc #$02 + sta ENEMY_X_VELOCITY_FRACT+9 + lda ENEMY_X_VELOCITY_FAST+9 + adc #$00 + sta ENEMY_X_VELOCITY_FAST+9 + lda ENEMY_X_POS+9 + lda ENEMY_ANIMATION_DELAY+9 + lsr + lsr + tay + lda helicopter_sprite_anim_tbl,y ; load appropriate sprite for helicopter + sta ENEMY_SPRITES+9 ; update helicopter animation sprite + lda FRAME_COUNTER ; load frame counter + and #$01 ; keep bits .... ...x + bne sequence_01_exit + inc ENEMY_ANIMATION_DELAY+9 ; helicopter animation frame delay counter + +; #$08 enemies on screen for ending sequence +start_ending_seq_enemy_loop: + ldx #$07 ; x = #$07 (volcano) + +; loop through enemies +ending_seq_enemy_loop: + lda ENEMY_ANIMATION_DELAY,x ; load current enemy animation frame delay counter + bne @continue ; branch if delay hasn't elapsed + txa ; animation delay has elapsed for enemy, advance to next routine + beq adv_routine_exit + +@continue: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + cmp #$60 + bcc @set_sprite_00_next_enemy ; branch if animation delay is less than #$60 + cmp #$80 + bcs @play_explosion ; branch if animation delay is between #$60 and #$80 + lsr + lsr + lsr + and #$03 ; keep bits .... ..xx + tay + lda ending_sequence_explosion_tbl,y + bne @set_sprite_a_next_enemy + +@play_explosion: + bne @set_sprite_00_next_enemy + lda #$25 ; a = #$25 (sound_25) + jsr play_sound ; play big island explosion sound + cpx #$03 + beq draw_destroyed_island ; draw destroyed and burned island + +@set_sprite_00_next_enemy: + lda #$00 ; a = #$00 + +@set_sprite_a_next_enemy: + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + +sequence_01_next_enemy: + dex + bpl ending_seq_enemy_loop + +sequence_01_exit: + rts + +draw_destroyed_island: + lda #$00 ; a = #$00 + sta ENEMY_SPRITES+8 ; blank sprite code + stx $00 + ldx GRAPHICS_BUFFER_OFFSET ; load graphics buffer offset + ldy #$00 ; y = #$00 + +; draw the #$43 pattern table tiles +@loop: + lda destroyed_island_tile_tbl,y + sta CPU_GRAPHICS_BUFFER,x + inx + iny + cpy #$43 + bne @loop + stx GRAPHICS_BUFFER_OFFSET + ldx $00 + jmp sequence_01_next_enemy ; go to next enemy in ending sequence + +adv_routine_exit: + inc END_LEVEL_ROUTINE_INDEX ; increment ending scene offset (end_game_sequence_ptr_tbl) + rts + +; table for explosions sprite codes (#$4 bytes) +ending_sequence_explosion_tbl: + .byte $37,$36,$35,$37 + +; table for helicopter sprite codes for animation (#$20 bytes) +; each byte is a sprite code, e.g. sprite_c5, sprite_c6, etc. +helicopter_sprite_anim_tbl: + .byte $c5,$c6,$c7,$c5,$c6,$c7,$c5,$c6,$c7,$c5,$c8,$c9,$ca,$cb,$cc,$cd + .byte $ce,$cc,$cd,$ce,$cc,$cd,$ce,$cc,$cd,$ce,$cc,$cd,$ce,$cc,$cd,$ce + +; pattern table tile codes and palette codes - after destruction (#$43 bytes) +destroyed_island_tile_tbl: + .byte $01,$0e,$04,$22,$29,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $00,$00,$00,$22,$49,$00,$00,$00,$00,$00,$00,$00,$00,$00,$70,$71 + .byte $72,$00,$00,$22,$69,$7c,$00,$00,$70,$00,$7c,$74,$7e,$74,$80,$81 + .byte $74,$73,$7f,$23,$da,$aa,$aa,$aa,$aa,$aa,$aa,$aa,$aa,$aa,$aa,$aa + .byte $aa,$aa,$aa + +; tables for ending scene sprites (#$a * #$3 = #$1e bytes) +; byte 0: delay +; byte 1: x position +; byte 2: y position +end_scene_sprite_anim_tbl: + .byte $00,$80,$90 ; helicopter + .byte $00,$50,$86 ; mountain peaks + .byte $a8,$60,$8c ; explosion 0 + .byte $b4,$98,$8a ; explosion 1 + .byte $b8,$70,$94 ; explosion 2 + .byte $d0,$50,$96 ; explosion 3 + .byte $d3,$a8,$98 ; explosion 4 + .byte $d6,$78,$94 ; explosion 5 + .byte $db,$68,$96 ; explosion 6 + .byte $ef,$88,$94 ; explosion 7 + +; ending scene - pointer 2 +end_game_sequence_02: + jsr decrement_delay_timer + bne game_end_routine_exit + lda #$20 ; a = #$20 + sta LEVEL_SUPERTILE_DATA_PTR + jsr init_APU_channels + jsr reset_delay_timer ; reset 2-byte delay timer to #$0240 + lda #$4a ; a = #$4a (sound_4a) + jsr play_sound ; play end credits music + jsr zero_out_nametables ; erase name tables 0-1 + jmp init_game_routine_reset_timer_low_byte + +game_end_routine_exit: + rts + +game_end_routine_04: + lda FRAME_COUNTER ; load frame counter + and #$03 ; keep bits .... ..xx (speed of credits text) + bne game_end_routine_exit ; exit if not the 8th frame (scroll every 8 frames) + inc VERTICAL_SCROLL ; vertical scroll offset + lda VERTICAL_SCROLL + cmp #$f0 ; see if the view window needs to be set back to $2000 (scroll reached bottom of $2800 namespace) + bne @continue ; branch if no need to reset base nametable write address and vertical scroll + lda #$20 ; initialize PPU write address to #$200 and set/reset VERTICAL_SCROLL + sta $44 ; write nametable address high byte for PPU address #$2000 + lda #$00 ; a = #$00 + sta $43 ; write nametable address low byte for PPU address #$2000 + sta VERTICAL_SCROLL ; set vertical scroll offset back to #$00 + +@continue: + and #$0f ; keep bits .... xxxx of VERTICAL_SCROLL + cmp #$04 ; see if we need to draw the line of text + beq @draw_next_line ; if scroll offset ends in #$4, write next line of credits text + cmp #$0c ; check to see if need to draw blank line between text when VERTICAL_SCROLL ends in #$0c + bne game_end_routine_exit_2 ; exit until scroll offset ends in #$c + ldy #$00 ; y = #$00 (first line of ending credits) + beq load_credits_line_text ; always branch to draw blank line of tiles (ending_credits_00) + +@draw_next_line: + lda $42 ; load line credits text offset (initialized to 0 in level_routine_05 clear_memory_starting_a_x) + inc $42 ; increment credits line offset + asl ; double since each entry is #$02 bytes + tay + +load_credits_line_text: + lda ending_credits_ptr_tbl,y ; read low byte of the end credits address + sta $00 ; store low byte into $00 + lda ending_credits_ptr_tbl+1,y ; read high byte of the end credits address + beq end_credits_text ; last entry, finished reading credits + sta $01 ; store high byte of the end credits address + ldy #$00 ; set read index to #$00 + lda ($00),y ; read number of characters in line text + sta $03 ; store into $03 + iny ; increment line text read offset + lda ($00),y ; read the horizontal offset of the line of text + sta $02 ; store distance from left side of screen + lda #$20 ; text line width is #$20 characters + sec ; set the carry in preparation for sbc + sbc $02 ; #$20 minus horizontal offset + sbc $03 ; and then subtract line's total number of characters + sta $04 ; store the remaining spaces after text tiles + ldx GRAPHICS_BUFFER_OFFSET ; load graphics buffer offset + lda #$01 ; a = #$01 + sta CPU_GRAPHICS_BUFFER,x ; set VRAM address increment to write across horizontally + sta CPU_GRAPHICS_BUFFER+2,x ; set number of graphics groups to write to 1 + lda #$20 ; a = #$20 + inx + sta CPU_GRAPHICS_BUFFER,x ; set number of pattern tiles in group to #$20 (writing #$20 tiles) + inx + lda $44 ; load PPU write address high byte + inx + sta CPU_GRAPHICS_BUFFER,x ; set PPU address write high byte + lda $43 ; load PPU write address low byte + inx + sta CPU_GRAPHICS_BUFFER,x ; set PPU address write low byte + inx + jsr draw_credits_space_characters + +@draw_credits_line_text: + lda $03 ; number of tiles to draw + beq @draw_right_padding ; if no characters to draw, draw right padding space + iny ; increment credits text character read offset + lda ($00),y ; read next credits text character to draw + sta CPU_GRAPHICS_BUFFER,x ; write value in CPU_GRAPHICS_BUFFER + dec $03 ; decrement number of tiles to draw + inx ; increment CPU_GRAPHICS_BUFFER write offset + bne @draw_credits_line_text + +@draw_right_padding: + lda $04 + sta $02 + jsr draw_credits_space_characters + stx GRAPHICS_BUFFER_OFFSET ; update graphics buffer offset + ldx #$43 ; x = #$43 + lda #$20 ; a = #$20 + jsr advance_graphic_read_addr ; advance read address ($00-$01) a bytes + +game_end_routine_exit_2: + rts + +; delay before returning to intro screen +; 300 = 768 frames / 60 = 12.8 seconds +; the timer starts roughly 1 second before the text stops scrolling +end_credits_text: + sta DELAY_TIME_LOW_BYTE ; various delays (low byte) + lda #$03 ; a = #$03 + sta DELAY_TIME_HIGH_BYTE ; various delays (high byte) + jmp init_game_routine_reset_timer_low_byte + +; draw either the left or right spaces for padding of credits text (tile #$00) +draw_credits_space_characters: + lda $02 ; load the horizontal offset of the line of text + beq game_end_routine_exit_2 ; if completed drawing blank space, exit + lda #$00 ; a = #$00 + sta CPU_GRAPHICS_BUFFER,x ; specify buffer to draw blank space (empty char) for left padding + dec $02 ; decrement horizontal offset of text + inx ; increment loop value + bne draw_credits_space_characters ; always loop + +game_end_routine_05: + jsr decrement_delay_timer ; decrease delay by 1 (return 1 when delay is 0) + bne game_end_routine_exit_2 ; exit if timer hasn't elapsed + lda #$00 ; a = #$00 + sta GRAPHICS_BUFFER_MODE ; set GRAPHICS_BUFFER_MODE to #$00 to prepare writing text to screen (write_text_palette_to_mem) + sta CURRENT_LEVEL ; set current level to #$00 + dec GAME_MODE ; reset game mode to normal (not demo, not intro) + rts + +; pointer table for ending credits text (#$49 * #$2 = #$90 bytes) +ending_credits_ptr_tbl: + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_01 ; CPU address $bd55: CONGRATULATIONS! + .addr ending_credits_02 ; CPU address $bd67: YOU'VE DESTROYED THE VILE RED + .addr ending_credits_03 ; CPU address $bd86: FALCON AND SAVED THE UNIVERSE. + .addr ending_credits_04 ; CPU address $bda6: CONSIDER YOURSELF A HERO + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_05 ; CPU address $bc29: S T A F F + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_06 ; CPU address $bc34: PROGRAMMERS + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_07 ; CPU address $bc41: S.UMEZAKI + .addr ending_credits_08 ; CPU address $bc4c: S.KISHIWADA + .addr ending_credits_09 ; CPU address $bc59: K.YAMASHITA + .addr ending_credits_0a ; CPU address $bc66: T.DANJYO + .addr ending_credits_0b ; CPU address $bc70: M.OGAWA + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_0c ; CPU address $bc79: GRAPHIC DESIGNERS + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_0d ; CPU address $bc8c: T.UEYAMA + .addr ending_credits_0e ; CPU address $bc96: S.MURAKI + .addr ending_credits_0f ; CPU address $bca0: M.FUJIWARA + .addr ending_credits_10 ; CPU address $bcac: T.NISHIKAWA + .addr ending_credits_11 ; CPU address $bcb9: C.OZAWA + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_12 ; CPU address $bce4: SOUND CREATORS + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_13 ; CPU address $bcf4: H.MAEZAWA + .addr ending_credits_14 ; CPU address $bcff: K.SADA + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_15 ; CPU address $bd07: SPECIAL THANKS TO + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_16 ; CPU address $bd1a: K.SHIMONETA + .addr ending_credits_17 ; CPU address $bd27: N.SATO + .addr ending_credits_18 ; CPU address $bd2f: AC CONTRA TEAM + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_19 ; CPU address $bcc2: DIRECTED BY + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_1a ; CPU address $bccf: UMECHAN + .addr ending_credits_1b ; CPU address $bcd8: S.KITAMOTO + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_1c ; CPU address $bd3f: PRESENTED BY + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_1d ; CPU address $bd4d: KONAMI + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_00 ; CPU address $bc27: blank line + .addr ending_credits_00 ; CPU address $bc27: blank line + .byte $00,$00 + +; blank line +ending_credits_00: + .byte $00,$20 + +; Text Table +; Same for Intro and Ending, except 87 (!) unique to Ending +; $00 = Space +; $30 = 0 +; $31 = 1 +; $32 = 2 +; $33 = 3 +; $34 = 4 +; $35 = 5 +; $36 = 6 +; $37 = 7 +; $38 = 8 +; $39 = 9 +; $40 = . +; $41 = A +; $42 = B +; $43 = C +; $44 = D +; $45 = E +; $46 = F +; $47 = G +; $48 = H +; $49 = I +; $4a = J +; $4b = K +; $4c = L +; $4d = M +; $4e = N +; $4f = O +; $50 = P +; $51 = Q +; $52 = R +; $53 = S +; $54 = T +; $55 = U +; $56 = V +; $57 = W +; $58 = X +; $59 = Y +; $5a = Z +; $87 = ! +; $b0 = , +; $c3 = ? +; $f7 = ' +; ending credits text data +; byte 0: number of bytes to process after byte 1 +; byte 1: starting x position +; S T A F F +ending_credits_05: + .byte $09,$0b,$53,$00,$54,$00,$41,$00,$46,$00,$46 + +; PROGRAMMERS +ending_credits_06: + .byte $0b,$09,$50,$52,$4f,$47,$52,$41,$4d,$4d,$45,$52,$53 + +; S.UMEZAKI +ending_credits_07: + .byte $09,$0b,$53,$40,$55,$4d,$45,$5a,$41,$4b,$49 + +; S.KISHIWADA +ending_credits_08: + .byte $0b,$0b,$53,$40,$4b,$49,$53,$48,$49,$57,$41,$44,$41 + +; K.YAMASHITA +ending_credits_09: + .byte $0b,$0b,$4b,$40,$59,$41,$4d,$41,$53,$48,$49,$54,$41 + +; T.DANJYO +ending_credits_0a: + .byte $08,$0b,$54,$40,$44,$41,$4e,$4a,$59,$4f + +; M.OGAWA +ending_credits_0b: + .byte $07,$0b,$4d,$40,$4f,$47,$41,$57,$41 + +; GRAPHIC DESIGNERS +ending_credits_0c: + .byte $11,$09,$47,$52,$41,$50,$48,$49,$43,$00,$44,$45,$53,$49,$47,$4e + .byte $45,$52,$53 + +; T.UEYAMA +ending_credits_0d: + .byte $08,$0b,$54,$40,$55,$45,$59,$41,$4d,$41 + +; S.MURAKI +ending_credits_0e: + .byte $08,$0b,$53,$40,$4d,$55,$52,$41,$4b,$49 + +; M.FUJIWARA +ending_credits_0f: + .byte $0a,$0b,$4d,$40,$46,$55,$4a,$49,$57,$41,$52,$41 + +; T.NISHIKAWA +ending_credits_10: + .byte $0b,$0b,$54,$40,$4e,$49,$53,$48,$49,$4b,$41,$57,$41 + +; C.OZAWA +ending_credits_11: + .byte $07,$0b,$43,$40,$4f,$5a,$41,$57,$41 + +; DIRECTED BY +ending_credits_19: + .byte $0b,$09,$44,$49,$52,$45,$43,$54,$45,$44,$00,$42,$59 + +; UMECHAN +ending_credits_1a: + .byte $07,$0b,$55,$4d,$45,$43,$48,$41,$4e + +; S.KITAMOTO +ending_credits_1b: + .byte $0a,$0b,$53,$40,$4b,$49,$54,$41,$4d,$4f,$54,$4f + +; SOUND CREATORS +ending_credits_12: + .byte $0e,$09,$53,$4f,$55,$4e,$44,$00,$43,$52,$45,$41,$54,$4f,$52,$53 + +; H.MAEZAWA +ending_credits_13: + .byte $09,$0b,$48,$40,$4d,$41,$45,$5a,$41,$57,$41 + +; K.SADA +ending_credits_14: + .byte $06,$0b,$4b,$40,$53,$41,$44,$41 + +; SPECIAL THANKS TO +ending_credits_15: + .byte $11,$09,$53,$50,$45,$43,$49,$41,$4c,$00,$54,$48,$41,$4e,$4b,$53 + .byte $00,$54,$4f + +; K.SHIMONETA +ending_credits_16: + .byte $0b,$0b,$4b,$40,$53,$48,$49,$4d,$4f,$4e,$45,$54,$41 + +; N.SATO +ending_credits_17: + .byte $06,$0b,$4e,$40,$53,$41,$54,$4f + +; AC CONTRA TEAM +ending_credits_18: + .byte $0e,$0b,$41,$43,$00,$43,$4f,$4e,$54,$52,$41,$00,$54,$45,$41,$4d + +; PRESENTED BY +ending_credits_1c: + .byte $0c,$0a,$50,$52,$45,$53,$45,$4e,$54,$45,$44,$00,$42,$59 + +; KONAMI +ending_credits_1d: + .byte $06,$0d,$4b,$4f,$4e,$41,$4d,$49 + +; CONGRATULATIONS! +ending_credits_01: + .byte $10,$01,$43,$4f,$4e,$47,$52,$41,$54,$55,$4c,$41,$54,$49,$4f,$4e + .byte $53,$87 + +; YOU'VE DESTROYED THE VILE RED +ending_credits_02: + .byte $1d,$01,$59,$4f,$55,$f7,$56,$45,$00,$44,$45,$53,$54,$52,$4f,$59 + .byte $45,$44,$00,$54,$48,$45,$00,$56,$49,$4c,$45,$00,$52,$45,$44 + +; FALCON AND SAVED THE UNIVERSE. +ending_credits_03: + .byte $1e,$01,$46,$41,$4c,$43,$4f,$4e,$00,$41,$4e,$44,$00,$53,$41,$56 + .byte $45,$44,$00,$54,$48,$45,$00,$55,$4e,$49,$56,$45,$52,$53,$45,$40 + +; CONSIDER YOURSELF A HERO +ending_credits_04: + .byte $19,$01,$43,$4f,$4e,$53,$49,$44,$45,$52,$00,$59,$4f,$55,$52,$53 + .byte $45,$4c,$46,$00,$41,$00,$48,$45,$52,$4f,$40 + +; unused space (#$23f bytes) +; these $ff bytes can technically be deleted here because the contra.cfg +; specifies that any free bytes in a ROM bank will be filled with $ff +; and each ROM bank is 16 KiB +bank_4_unused_space: + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff \ No newline at end of file diff --git a/src/bank5.asm b/src/bank5.asm new file mode 100644 index 0000000..0bd1c67 --- /dev/null +++ b/src/bank5.asm @@ -0,0 +1,420 @@ +; Contra US Disassembly - v1.0 +; https://github.com/vermiceli/nes-contra-us +; Bank 5 mostly contains compressed graphic data. The rest of bank 5 is the +; code and lookup tables for automated input for the 3 demo (attract) levels. + +.segment "BANK_5" + +.include "constants.asm" + +; export labels used by bank 7 +.export load_demo_input_table +.export graphic_data_05, graphic_data_07 +.export graphic_data_0b, graphic_data_14 +.export graphic_data_17, graphic_data_18 +.export graphic_data_19, graphic_data_1a + +; Every PRG ROM bank starts with a single byte specifying which number it is +.byte $05 ; The PRG ROM bank number (5) + +; compressed graphics data - Code 05 (#$A60 bytes) +; character when immobile, prone, weapon zeppelin +; writes to same PPU addresses as graphic_data_07 and graphic_data_0b +; pattern table data - writes addresses +; * [$09a0-$0a80) +; * [$0dc0-$1200) +; * [$1320-$1600) +; * [$1bd0-$2000) +; CPU address $8001 +graphic_data_05: + .incbin "assets/graphic_data/graphic_data_05.bin" + +; compressed graphics data - Code 07 (#$97F bytes) +; level 3 +; writes to same PPU addresses as graphic_data_05 and graphic_data_0b +; pattern table data - writes addresses +; * [$09a0-$0a80) +; * [$0dc0-$1200) +; * [$1320-$1600) +; * [$1bd0-$2000) +; CPU address $8a61 +graphic_data_07: + .incbin "assets/graphic_data/graphic_data_07.bin" + +; compressed graphics data - Code 0B (#$f3b bytes) +; CPU address $93e0 +; pattern table data - writes addresses +; * [$09a0-$0a80) +; * [$0dc0-$1200) +; * [$1320-$1600) +; * [$1bd0-$2000) +graphic_data_0b: + .incbin "assets/graphic_data/graphic_data_0b.bin" + +; compressed graphics data - Code 19 (#$1e5 bytes) +; left pattern table data - writes addresses [$0680-$08c0) +; CPU address $a31b +graphic_data_19: + .incbin "assets/graphic_data/graphic_data_19.bin" + +; compressed graphics data - Code 1A (#$314 bytes) +; left pattern table data - writes addresses [$0a80-$0dc0) +; CPU address $a500 +graphic_data_1a: + .incbin "assets/graphic_data/graphic_data_1a.bin" + +; compressed graphics data - Code 14 (#$5cb bytes) +; Turrets, Gray and Red +; right pattern table data - writes addresses [$1600-$1bd0) +; CPU address $a814 +graphic_data_14: + .incbin "assets/graphic_data/graphic_data_14.bin" + +; compressed graphics data - Code 17 (#$52e bytes) +; End Scene +; pattern table data - writes addresses +; * [$0a60-$0fe0) +; * [$15b0-$18a0) +; CPU address $addf +graphic_data_17: + .incbin "assets/graphic_data/graphic_data_17.bin" + +; compressed graphics data - Code 18 (#$51 bytes) +; nametable data - writes addresses [$2000-$2400) +; CPU address $b30d +graphic_data_18: + .incbin "assets/graphic_data/graphic_data_18.bin" + +; run as part of showing the demo +; DEMO_FIRE_DELAY_TIMER starts at 0 increments to #$ff and stops +load_demo_input_table: + lda CONTROLLER_STATE_DIFF ; get player input + and #$30 ; start and select button + bne end_demo_level ; exit demo if player has pressed start or select + inc DEMO_FIRE_DELAY_TIMER ; increase DEMO_FIRE_DELAY_TIMER by 1 + bne @player_loop ; branch when DEMO_FIRE_DELAY_TIMER is not 0 (hasn't wrapped around) + dec DEMO_FIRE_DELAY_TIMER ; decrease DEMO_FIRE_DELAY_TIMER by 1 (setting to -1), this means delay is complete + +@player_loop: + ldx #$01 ; initialize X to 1 (player loop starting at player 2) + +; sets values specific for demo player input +; x is player number, starts at 1 and goes to 0 +; $2c stores the temporary value of A before +set_player_demo_input: + lda FRAME_COUNTER ; frame counter + lsr ; shift right, pushing lsb (0th bit) to carry flag + bcc @continue ; skip for even-numbered frames, don't decrement DEMO_INPUT_NUM_FRAMES + lda DEMO_INPUT_NUM_FRAMES,x ; load into accumulator the number of frames for the demo input table + bne @dec_input_frame_count ; if number of frames from previous input hasn't completed, then skip reading next input instruction + lda CURRENT_LEVEL ; current the current level + asl ; each entry in demo_input_pointer_table is 2 bytes, so double + asl ; since each level has 2 entries (1 for each player), double again. This determines the player 1 entry for the level + sta $08 ; store demo_input_pointer_table entry offset into $08 + txa ; move player number to A + asl ; if player 1, nothing happens, but if player 2, then double offset since each entry is #$2 bytes + adc $08 ; add result to demo_input_pointer_table entry offset into $08 to get player-specific offset + tay ; move result to Y + lda demo_input_pointer_table,y ; read low byte of input pointer table + sta $08 ; store pointer address value in $08 + lda demo_input_pointer_table+1,y ; load high byte of input pointer table (demo_input_pointer_table + 1) + sta $09 ; store pointer address value in $09 + ldy DEMO_INPUT_TBL_INDEX,x ; the offset into demo_input_tbl_lX_pX of to read + lda ($08),y ; load the 1-byte controller input from the 2-byte address + ; ($08 and $09) from the input table offset by Y + ; this is indirect indexed addressing mode + cmp #$ff ; #$ff signals end of demo input + beq end_demo_level ; set DEMO_LEVEL_END_FLAG to #$01 and exit if we've read $ff byte (end of code) + sta DEMO_INPUT_VAL,x ; store the controller input for the demo input table + iny ; increment 1 to get the number of frames + lda ($08),y ; load the 1-byte number of frames to use input from the 2-byte address + ; ($08 and $09) from the input table offset by Y + ; this is indirect indexed addressing mode + sta DEMO_INPUT_NUM_FRAMES,x ; store the number of frames for the input + iny ; increment table read offset + tya + sta DEMO_INPUT_TBL_INDEX,x ; increment byte read offset into demo_input_tbl_lX_pX + +@dec_input_frame_count: + dec DEMO_INPUT_NUM_FRAMES,x ; decrement number of frames to press input + +@continue: + lda DEMO_INPUT_VAL,x ; read controller input to execute + sta CONTROLLER_STATE_DIFF,x ; store controller input as new input + sta CONTROLLER_STATE,x ; store controller input + lda DEMO_FIRE_DELAY_TIMER ; load delay timer before firing weapon + cmp #$50 ; wait #$50 frames before firing weapon + bcc player_demo_input_chg_player ; move to next player if DEMO_FIRE_DELAY_TIMER < #$50 + lda P1_CURRENT_WEAPON,x ; get current player's weapon + and #$0f ; strip out rapid fire flag + cmp #$01 ; see if current weapon is machine gun + beq @m_or_laser ; branch if M weapon + cmp #$04 ; see if laser + bne @fire_weapon_input ; branch if not laser + +; hold down b button for m or laser weapon during demo +@m_or_laser: + lda CONTROLLER_STATE,x + ora #$40 ; set 6th bit to 1 (b button), this is used later in run_create_bullet_routine (bank 6) + sta CONTROLLER_STATE,x ; save toggled flag back to CONTROLLER_STATE + bne player_demo_input_chg_player ; go to next player (always jumps due to ora instruction) + +; for non M, nor L weapon, press b button every #$07 frames +@fire_weapon_input: + lda FRAME_COUNTER ; load frame counter + and #$07 ; checking every 7th frame + bne player_demo_input_chg_player ; move to next player without firing weapon + lda CONTROLLER_STATE_DIFF,x ; load current controller input + ora #$40 ; press b button + sta CONTROLLER_STATE_DIFF,x ; store input + +player_demo_input_chg_player: + dex ; go from player 2 to player 1 + bpl set_player_demo_input ; continue loop until x is 0 + rts ; x is 0, done + +; finished reading all of demo data, end demo for level +end_demo_level: + inc DEMO_LEVEL_END_FLAG ; set demo end flag to #$01 + rts + +; pointer table for demo inputs #$06 * #$02 = #$0c bytes +; contains the memory addresses of the demo input tables +; CPU memory address $b3d2 +demo_input_pointer_table: + .addr demo_input_tbl_l1_p1 ; CPU address $b3de + .addr demo_input_tbl_l1_p2 ; CPU address $b438 + .addr demo_input_tbl_l2_p1 ; CPU address $b48a + .addr demo_input_tbl_l2_p2 ; CPU address $b4fc + .addr demo_input_tbl_l3_p1 ; CPU address $b540 + .addr demo_input_tbl_l3_p2 ; CPU address $b5b2 + +; the following area contains the automated input for the demo levels +; * first byte is input code (up, down, left, right, b, a, start, select) +; * second byte is number of even-numbered frames to apply the input for +; while possible, player firing isn't specified in these input tables +; instead, that is handled automatically as part of running the demo +; * m or l weapons are always firing, other weapons fire every #$07 frames +; $00, $00 is filler so the demo level doesn't end by reading a #$ff +; input table for level 1 player 1 for demo (#$5A bytes) +demo_input_tbl_l1_p1: + .byte $00,$21,$01,$03,$00,$0e,$01,$3d,$04,$06,$05,$33,$00,$0e,$04,$0a + .byte $05,$01,$01,$29,$09,$01,$08,$02,$09,$08,$08,$0f,$09,$18,$01,$05 + .byte $00,$04,$01,$02,$00,$1f,$01,$24,$05,$33,$01,$05,$81,$15,$01,$0b + .byte $09,$02,$01,$22,$81,$11,$89,$02,$81,$03,$01,$70,$09,$1c,$01,$25 + .byte $09,$2f,$01,$03,$05,$06,$01,$0a,$08,$14,$09,$01,$01,$12,$09,$06 + .byte $08,$05,$00,$00,$00,$00,$00,$00,$ff,$ff + +; input table for level 1 player 2 for demo (#$52 bytes) +demo_input_tbl_l1_p2: + .byte $01,$76,$05,$1f,$00,$03,$80,$04,$84,$0a,$05,$02,$01,$86,$00,$0b + .byte $01,$0b,$81,$0b,$85,$06,$84,$06,$04,$07,$00,$02,$01,$39,$81,$0d + .byte $01,$13,$81,$09,$01,$17,$81,$06,$01,$31,$00,$3e,$01,$19,$81,$0b + .byte $01,$14,$81,$08,$01,$17,$81,$0d,$01,$25,$00,$01,$80,$03,$84,$08 + .byte $04,$0a,$05,$01,$01,$08,$00,$03,$02,$04,$00,$07,$01,$17,$00,$06 + .byte $ff,$ff + +; input table for level 2 player 1 for demo (#$72 bytes) +demo_input_tbl_l2_p1: + .byte $00,$49,$02,$16,$00,$1f,$01,$0a,$00,$0d,$04,$2a,$00,$0b,$01,$0b + .byte $00,$1b,$02,$13,$82,$0a,$80,$03,$00,$04,$01,$1c,$00,$14,$08,$05 + .byte $00,$4b,$02,$15,$00,$24,$04,$0b,$05,$01,$01,$18,$00,$07,$02,$0f + .byte $00,$01,$01,$13,$00,$01,$02,$0c,$82,$04,$80,$03,$00,$05,$01,$0a + .byte $00,$01,$02,$05,$08,$01,$01,$01,$00,$01,$08,$01,$0a,$03,$02,$04 + .byte $0a,$06,$02,$0c,$04,$0a,$00,$27,$01,$0f,$81,$0a,$01,$07,$00,$08 + .byte $02,$1e,$00,$90,$01,$09,$81,$0a,$01,$04,$00,$03,$02,$1e,$00,$00 + .byte $ff,$ff + +; input table for level 2 player 2 for demo (#$44 bytes) +demo_input_tbl_l2_p2: + .byte $00,$41,$02,$04,$00,$2b,$04,$19,$00,$16,$04,$16,$00,$1d,$04,$2a + .byte $00,$3a,$08,$4b,$00,$17,$01,$1c,$00,$23,$04,$12,$00,$1f,$02,$19 + .byte $00,$01,$01,$02,$81,$0d,$01,$04,$00,$07,$02,$10,$82,$0e,$02,$02 + .byte $00,$1e,$02,$15,$00,$06,$02,$04,$00,$0b,$04,$19,$00,$3a,$08,$03 + .byte $00,$2e,$ff,$ff + +;input table for level 3 player 1 for demo (#$72 bytes) +demo_input_tbl_l3_p1: + .byte $00,$17,$01,$29,$81,$05,$01,$13,$00,$1b,$80,$0d,$00,$13,$80,$0b + .byte $00,$1a,$80,$12,$00,$0d,$80,$12,$00,$0b,$80,$0a,$81,$03,$01,$0d + .byte $00,$09,$01,$0d,$00,$03,$02,$02,$00,$4d,$02,$0e,$82,$11,$02,$10 + .byte $00,$08,$08,$03,$88,$12,$08,$03,$0a,$04,$02,$05,$00,$02,$80,$0e + .byte $00,$08,$02,$0e,$82,$0e,$02,$02,$00,$0e,$80,$08,$00,$06,$01,$02 + .byte $00,$17,$80,$0a,$00,$16,$80,$0b,$00,$17,$80,$0c,$00,$20,$80,$0d + .byte $00,$0f,$80,$06,$81,$03,$01,$09,$00,$22,$01,$0b,$81,$0f,$01,$25 + .byte $ff,$ff + +; input table for level 3 player 2 for demo (#$82 bytes) +demo_input_tbl_l3_p2: + .byte $00,$23,$01,$2c,$81,$0d,$01,$0a,$00,$07,$80,$0d,$00,$10,$80,$07 + .byte $82,$05,$02,$0f,$82,$0a,$02,$0f,$00,$0f,$80,$0a,$00,$35,$04,$09 + .byte $00,$05,$80,$01,$82,$07,$02,$02,$00,$10,$01,$01,$81,$0c,$01,$0c + .byte $00,$1f,$80,$04,$00,$14,$01,$0b,$00,$0a,$04,$24,$05,$01,$85,$02 + .byte $81,$06,$80,$01,$00,$07,$02,$04,$00,$03,$01,$0c,$00,$07,$01,$08 + .byte $81,$0d,$01,$0f,$00,$04,$80,$0c,$02,$08,$00,$2d,$02,$14,$82,$11 + .byte $80,$03,$01,$0a,$00,$02,$02,$02,$82,$0f,$02,$0c,$00,$03,$80,$05 + .byte $82,$0f,$02,$04,$00,$4a,$81,$0b,$01,$04,$00,$11,$80,$0d,$00,$00 + .byte $ff,$ff + +; unused space #$9cc bytes +; these $ff bytes can technically be deleted here because the contra.cfg +; specifies that any free bytes in a ROM bank will be filled with $ff +; and each ROM bank is 16 KiB +bank_5_unused_space: + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff \ No newline at end of file diff --git a/src/bank6.asm b/src/bank6.asm new file mode 100644 index 0000000..0105406 --- /dev/null +++ b/src/bank6.asm @@ -0,0 +1,1885 @@ +; Contra US Disassembly - v1.0 +; https://github.com/vermiceli/nes-contra-us +; Bank 6 contains compressed graphics data, data for short text sequences like +; level names and menu options. Bank 6 also contains the code for the players' +; weapons and bullets. + +.segment "BANK_6" + +.include "constants.asm" + +; import labels from bank 7 +.import play_sound, run_routine_from_tbl_below +.import set_vel_for_speed_vars, get_bg_collision_far, set_bullet_routine_to_2 + +; export labels for bank 7 +.export short_text_pointer_table +.export graphic_data_0c, graphic_data_0d +.export graphic_data_0e, graphic_data_15 +.export graphic_data_16 +.export run_player_bullet_routines, check_player_fire + +; Every PRG ROM bank starts with a single byte specifying which number it is +.byte $06 ; The PRG ROM bank number (6) + +; compressed graphics data - code 0c (#$cdb bytes) +; same PPU addresses as graphic_data_0d +; pattern table data - writes addresses +; * [$09a0-$0a80) +; * [$0dc0-$0ee0) +; * [$0fc0-$1200) +; * [$1320-$2000) +; CPU address $8001 +graphic_data_0c: + .incbin "assets/graphic_data/graphic_data_0c.bin" + +; compressed graphics data - code 0d (#$efa bytes) +; same PPU addresses as graphic_data_0c +; pattern table data - writes addresses +; * [$09a0-$0a80) +; * [$0dc0-$0ee0) +; * [$0fc0-$1200) +; * [$1320-$2000) +; CPU address $8cdc +graphic_data_0d: + .incbin "assets/graphic_data/graphic_data_0d.bin" + +; compressed graphics data - code 0e (#$14a4 bytes) +; pattern table data - writes addresses [$09a0-$2000) +; CPU address $9bd6 +graphic_data_0e: + .incbin "assets/graphic_data/graphic_data_0e.bin" + +; compressed graphics data - code 15 (#$e2 bytes) +; Turret Guy +; left pattern data - writes addresses [$0ee0-$0fc0) +; CPU address $b07a +graphic_data_15: + .incbin "assets/graphic_data/graphic_data_15.bin" + +; compressed graphics data - code 16 (#$106 bytes) +; Weapon Box +; right pattern data - writes addresses [$1200-$1320) +; CPU address $b15c +graphic_data_16: + .incbin "assets/graphic_data/graphic_data_16.bin" + +; pointer table for all the strings, and palette data (#$02 bytes * #$19 strings = #$32 bytes) +short_text_pointer_table: + .addr text_1_player ; CPU address $b2e1 - 1 PLAYER + .addr text_1_player ; CPU address $b2e1 - 1 PLAYER + .addr text_2_players ; CPU address $b2ec - 2 PLAYERS + .addr text_play_select ; CPU address $b2d3 - PLAY SELECT + .addr intro_background_palette2 ; CPU address $b3b4 - These are the intro screen palettes and intro palettes for Bill and Lance (players) + .addr text_jungle ; CPU address $b356 - JUNGLE + .addr transition_screen_palettes ; CPU address $b333 - These are the intro screen palettes and intro palettes for Bill and Lance (players) + .addr text_rest ; CPU address $b2f8 - REST + .addr text_rest2 ; CPU address $b302 - REST + .addr text_hi ; CPU address $b30c - HI + .addr text_1p ; CPU address $b319 - 1P + .addr text_2p ; CPU address $b326 - 2P + .addr text_stage ; CPU address $b294 - STAGE + .addr text_game_over ; CPU address $b29e - GAME OVER + .addr text_continue ; CPU address $b2c2 - CONTINUE + .addr text_game_over2 ; CPU address $b2aa - GAME OVER + .addr text_game_over3 ; CPU address $b2b6 - GAME OVER + .addr text_jungle ; CPU address $b356 - JUNGLE + .addr text_base1 ; CPU address $b360 - BASE1 + .addr text_waterfall ; CPU address $b36a - WATERFALL + .addr text_base2 ; CPU address $b376 - BASE2 + .addr text_snow_field ; CPU address $b380 - SNOW FIELD + .addr text_energy_zone ; CPU address $b38d - ENERGY ZONE + .addr text_hangar ; CPU address $b39b - HANGAR + .addr text_alien_lair ; CPU address $b3a5 - ALIEN'S LAIR + +; first two bytes are PPU address, followed by text +; $fe is the end of text string +; the two bytes after $fd specify the PPU address +; "STAGE" text +text_stage: + .byte $22,$0c,$53,$54,$41,$47,$45,$00,$00,$fe + +; "GAME OVER" text +text_game_over: + .byte $22,$2a,$47,$41,$4d,$45,$00,$4f,$56,$45,$52,$fe + +; "GAME OVER" text +text_game_over2: + .byte $20,$c2,$47,$41,$4d,$45,$00,$4f,$56,$45,$52,$fe + +; "GAME OVER" text +text_game_over3: + .byte $20,$d2,$47,$41,$4d,$45,$00,$4f,$56,$45,$52,$fe + +; "CONTINUE" text +text_continue: + .byte $22,$8c,$43,$4f,$4e,$54,$49,$4e,$55,$45,$fd + .byte $22,$cc ; change to PPU address $22cc and read next text (text_end) + +; "END" text, written when writing previous text text_continue +text_end: + .byte $45,$4e,$44,$fe + +; "PLAY SELECT" text +text_play_select: + .byte $22,$8a,$50,$4c,$41,$59,$00,$53,$45,$4c,$45,$43,$54,$fe + +; "1 PLAYER" text +text_1_player: + .byte $22,$87,$31,$00,$50,$4c,$41,$59,$45,$52,$fe + +; "2 PLAYERS" text +text_2_players: + .byte $22,$c7,$32,$00,$50,$4c,$41,$59,$45,$52,$53,$fe + +; "REST" text (PPU address $20c2) +text_rest: + .byte $20,$c2,$52,$45,$53,$54,$00,$00,$00,$fe + +; "REST" text (PPU address $20d2) +text_rest2: + .byte $20,$d2,$52,$45,$53,$54,$00,$00,$00,$fe + +; "HI" text +text_hi: + .byte $21,$2a,$48,$49,$00,$00,$00,$00,$00,$00,$00,$00,$fe + +; "1P" text +text_1p: + .byte $20,$82,$31,$50,$00,$00,$00,$00,$00,$00,$00,$00,$fe + +; "2P" text +text_2p: + .byte $20,$92,$32,$50,$00,$00,$00,$00,$00,$00,$00,$00,$fe + +; background palette data for use with graphic_data_02 nametable +; palettes for intro screen, and screen showing level name and high score (#$12 bytes) +; when reading this label, reads continue through intro_sprite_palettes until #$fe +; CPU address $b333 +; PPU address $3f00 (start of palette data) +transition_screen_palettes: + .byte $3f,$00 ; PPU address $3f00 - palette address start + .byte COLOR_BLACK_0f ; universal background color + .byte COLOR_LT_GRAY_10,COLOR_LT_OLIVE_28,COLOR_MED_RED_16 ; background palette 0 + .byte $0f + .byte COLOR_WHITE_30 ,COLOR_LT_GRAY_10 ,COLOR_MED_PINK_15 ; background palette 1 + .byte $0f + .byte COLOR_LT_GRAY_10,COLOR_LT_OLIVE_28,COLOR_PALE_OLIVE_38 ; background palette 2 + .byte $0f + .byte COLOR_BLACK_0f ,COLOR_BLACK_0f ,COLOR_BLACK_0f ; background palette 3 (black, black, black) + +; sprite palettes for intro guys (#$11 bytes) +; loaded along with transition_screen_palettes +intro_sprite_palettes: + .byte $0f + .byte COLOR_WHITE_30,COLOR_LT_GRAY_10,COLOR_DARK_GRAY_00 ; sprite palette 0 + .byte $0f + .byte COLOR_WHITE_30,COLOR_PALE_OLIVE_38,COLOR_LT_OLIVE_28 ; sprite palette 1 + .byte $0f + .byte COLOR_LT_TEAL_2c,COLOR_MED_TEAL_1c,COLOR_DARK_TEAL_0c ; sprite palette 2 + .byte $0f + .byte COLOR_DARK_GRAY_00,COLOR_DARK_GRAY_00,COLOR_DARK_GRAY_00 ; sprite palette 3 + .byte $fe ; end of palette data signal + +; "JUNGLE" text +text_jungle: + .byte $22,$4b,$00,$4a,$55,$4e,$47,$4c,$45,$fe + +; "BASE1" text +text_base1: + .byte $22,$4b,$00,$00,$42,$41,$53,$45,$31,$fe + +; "WATERFALL" text +text_waterfall: + .byte $22,$4b,$57,$41,$54,$45,$52,$46,$41,$4c,$4c,$fe + +; "BASE2" text +text_base2: + .byte $22,$4b,$00,$00,$42,$41,$53,$45,$32,$fe + +; "SNOW FIELD" text +text_snow_field: + .byte $22,$4b,$53,$4e,$4f,$57,$00,$46,$49,$45,$4c,$44,$fe + +; "ENERGY ZONE" text +text_energy_zone: + .byte $22,$4b,$45,$4e,$45,$52,$47,$59,$00,$5a,$4f,$4e,$45,$fe + +; "HANGAR" text +text_hangar: + .byte $22,$4b,$00,$48,$41,$4e,$47,$41,$52,$fe + +; "ALIEN'S LAIR" text +text_alien_lair: + .byte $22,$4b,$41,$4c,$49,$45,$4e,$f7,$53,$00,$4c,$41,$49,$52,$fe + +; background palettes for intro screen (when Bill and Lance appear) (#$13 bytes) +; PPU address $3f00 +intro_background_palette2: + .byte $3f,$00 ; PPU address $3f00 + .byte COLOR_BLACK_0f ; universal background color + .byte COLOR_LT_GRAY_10,COLOR_LT_OLIVE_28,COLOR_MED_RED_16 ; background palette 0 + .byte $0f + .byte COLOR_WHITE_30 ,COLOR_LT_GRAY_10 ,COLOR_MED_PINK_15 ; background palette 1 + .byte $0f + .byte COLOR_LT_GRAY_10,COLOR_LT_OLIVE_28,COLOR_PALE_OLIVE_38 ; background palette 2 + .byte $0f + .byte COLOR_WHITE_30 ,COLOR_PALE_RED_36,COLOR_LT_RED_26 ; background palette 3 + .byte $fe + +; check to see if the player is trying to fire a bullet (B button pressed) +; ensure player in valid state to fire a bullet, e.g. not being electrocuted +check_player_fire: + lda PLAYER_HIDDEN,x ; 0 - visible; #$01/#$ff = invisible (any non-zero) + ora $c8,x ; counter for electrocution + bne check_player_fire_exit ; exit if being electrocuted or $ba,x is set + lda PLAYER_WATER_STATE,x ; see if player in water + beq @player_shoot_test + lda PLAYER_AIM_DIR,x ; can't shoot downward if in water + cmp #$03 + bcc @player_shoot_test ; continue if < 3 (up, up-right, and right) + cmp #$07 + bcc check_player_fire_exit ; exit if less than 7 and greater than 3 (down-right, down, down-left) + +@player_shoot_test: + lda P1_CURRENT_WEAPON,x ; current player's weapon + and #$0f ; strip to weapon type without rapid fire bit + tay ; run_create_bullet_routine looks in y for the weapon type + lda #$40 ; a = #$40 + cpy #$01 ; see if current weapon is M weapon + beq @m_or_l_weapon + cpy #$04 ; see if current weapon is L weapon + bne @weapon + +; M and L weapons will run_create_bullet_routine twice if first iteration returned #$00 +@m_or_l_weapon: + and CONTROLLER_STATE,x ; see if B button pressed (#$40) + bne run_create_bullet_routine ; fire weapon if B button pressed + beq @continue ; no B button pressed, continue + +@weapon: + and CONTROLLER_STATE_DIFF,x ; see if B button pressed (#$40) + bne run_create_bullet_routine ; fire weapon if B button pressed + +; B button not pressed, strip PLAYER_M_WEAPON_FIRE_TIME to low nibble, then +; increment PLAYER_M_WEAPON_FIRE_TIME up to #$07 when fire button isn't pressed +@continue: + lda PLAYER_M_WEAPON_FIRE_TIME,x ; load how many frames the fire button has been pressed + and #$0f ; keep only low nibble + cmp #$07 ; compare to #$07 + bcs @set_and_exit ; branch if >= #$07 + adc #$01 + +@set_and_exit: + sta PLAYER_M_WEAPON_FIRE_TIME,x + +check_player_fire_exit: + rts + +; creates the appropriate bullet when firing weapon +run_create_bullet_routine: + stx $11 ; store current player index into $11 + jsr @run_create_bullet_routine + ldx $11 ; restore current player index to x + rts + +; y is the current weapon type (low byte of P1_CURRENT_WEAPON) +; x and $11 are current player +; run the appropriate weapon routine based on what weapon the player has +@run_create_bullet_routine: + sty $08 ; store weapon type (Standard, M, F, S, L) in $08 + lda P1_CURRENT_WEAPON,x ; current player's weapon + lsr + lsr + lsr + lsr ; ignore low nibble (weapon type) + and #$01 ; keep bit 4 of P1_CURRENT_WEAPON (rapid fire flag) + sta $09 ; store weapon rapid fire flag in $09 + lda PLAYER_AIM_DIR,x ; which direction the player is aiming/looking + ldy PLAYER_JUMP_STATUS,x ; load player jump status + sty $0a ; store jump status in $0a + beq @fire_weapon_routine ; branch if PLAYER_JUMP_STATUS is #$00 (not jumping) + cmp #$04 ; see if shooting down facing right while jumping + beq @continue ; branch if shooting down + cmp #$05 ; branch if shooting down facing left while jumping + bne @fire_weapon_routine ; branch if not shooting down and to the left + +@continue: + lda #$0b ; a = #$0b + +; runs the appropriate weapon routine, based on current weapon type (Standard, M, F, S, L) +@fire_weapon_routine: + sta $0b ; store PLAYER_AIM_DIR in $0b + lda SPRITE_X_POS,x ; load player x position on screen + sta $0c ; store player x position on screen + lda SPRITE_Y_POS,x ; load player y position on screen + sta $0d ; store player y position on screen + lda $08 ; load weapon type (Standard, M, F, S, L) + ldy LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor + bmi fire_weapon_routine ; branch if base/indoor boss level screen + beq fire_weapon_routine ; branch for outdoor level + clc ; clear carry in preparation for addition + adc #$05 ; indoor/base level add #$05 to weapon routine to run + +; runs weapon routine at offset A into fire_weapon_routine_ptr_tbl +fire_weapon_routine: + jsr run_routine_from_tbl_below ; run routine a in the following table (fire_weapon_routine_ptr_tbl) + +; pointer table for weapon routines (a * 2 = 14 bytes) +fire_weapon_routine_ptr_tbl: + .addr fire_weapon_routine_standard ; Standard Weapon CPU address $b455 + .addr fire_weapon_routine_m ; M Weapon CPU address $b46e + .addr fire_weapon_routine_f ; F Weapon CPU address $b4ae + .addr fire_weapon_routine_s ; S Weapon CPU address $b4ca + .addr fire_weapon_routine_l ; L Weapon CPU address $b508 + .addr fire_weapon_routine_indoor_standard ; indoor/base level Standard Weapon CPU address $b460 + .addr fire_weapon_routine_indoor_m ; indoor/base level M Weapon address $b476 + .addr fire_weapon_routine_indoor_f ; indoor/base level F Weapon address $b4b9 + .addr fire_weapon_routine_s ; indoor/base level S Weapon address $b4ca + .addr fire_weapon_routine_l ; indoor/base level L Weapon address $b508 + +; standard weapon +fire_weapon_routine_standard: + jsr create_bullet_max_04 ; create bullet if possible + bne weapon_routine_exit ; exit if no bullet slot found + +init_bullet_pos_and_velocity: + jsr init_bullet_sprite_pos ; set initial bullet sprite position + jmp set_bullet_velocity ; set the bullet x and y velocities + +; indoor/base level Standard Weapon +fire_weapon_routine_indoor_standard: + jsr create_bullet_max_04 ; create bullet if possible + bne weapon_routine_exit ; exit if no bullet slot found + +init_indoor_bullet_pos_and_vel: + jsr set_indoor_bullet_pos_and_slot + jsr set_indoor_bullet_vel + jmp set_indoor_bullet_delay + +; m weapon +fire_weapon_routine_m: + jsr gen_m_bullet_if_delay_met ; possibly generate a bullet based on PLAYER_M_WEAPON_FIRE_TIME + beq init_bullet_pos_and_velocity ; set initial bullet position and velocity if bullet was created + +gen_m_bullet_exit: + lda #$01 ; a = #$01 + +weapon_routine_exit: + rts + +; indoor/base level M Weapon +fire_weapon_routine_indoor_m: + jsr gen_m_bullet_if_delay_met ; possibly generate a bullet based on PLAYER_M_WEAPON_FIRE_TIME + beq init_indoor_bullet_pos_and_vel ; set initial bullet position and velocity if bullet was created + rts + +; indoor and outdoor m weapon +; checks PLAYER_M_WEAPON_FIRE_TIME to determine if a bullet should be generated +; updates PLAYER_M_WEAPON_FIRE_TIME based on logic +gen_m_bullet_if_delay_met: + inc PLAYER_M_WEAPON_FIRE_TIME,x ; increment bullet generation delay + lda PLAYER_M_WEAPON_FIRE_TIME,x ; load its current value + cmp #$60 ; compare to #$60 (burst fire time limit) + ldy #$08 ; set y = #$08 (bullet generation delay while holding B button) + bcc @continue ; branch if less than #$06 bullets have fired in a row while holding B + ldy #$0f ; set y = #$0f (prevents bullet from generating) + +@continue: + sty $0f ; store either #$08 or #$0f in $0f + and #$0f ; low nibble of of burst fire time + cmp $0f ; compare to burst fire time time + bcc gen_m_bullet_exit ; exit if less than #$08 (delay between bullets hasn't elapsed) + ; also exit when low nibble less than #$0f when burst time >= #$60 + ; e.g. must wait a full #$0f frames before next bullet will generate + lda PLAYER_M_WEAPON_FIRE_TIME,x ; re-load full value of PLAYER_M_WEAPON_FIRE_TIME + adc #$0f ; add #$0f to burst fire time to see if the full #$0f frames have elapsed during burst fire 'cool down'. + ; !(OBS) could have just added #$01 since can't get here unless low nibble #$0f + cmp #$70 ; see if #$10 frame delay has elapsed and burst fire time is now #$70 + bcc @gen_m_bullet ; continue if not elapsed + lda #$00 ; reset burst fire time back to #$00, skipped bullet delay has elapsed + +; generate bullet for M weapon if slot available +@gen_m_bullet: + and #$f0 ; keep high nibble + sta PLAYER_M_WEAPON_FIRE_TIME,x ; set new time value with only high nibble set (or #$00) + lda #$05 ; a = #$05 (up to #$06 bullets on screen for m weapon) + jsr create_bullet_max_a_p2_0a ; create bullet if possible, p2 starting at slot #$0a + bne @no_bullet_created ; if #$06 bullets on screen, branch + rts + +@no_bullet_created: + ldy $11 ; load player index + lda #$07 ; set a = #$07, clears zero flag + ; this will allow the bullet to be reattempted to be created the next frame + sta PLAYER_M_WEAPON_FIRE_TIME,y ; reset $ac back down to #$07 + rts + +; f weapon (outdoor) +fire_weapon_routine_f: + jsr create_bullet_max_04 ; create bullet if possible + bne weapon_routine_exit ; exit if no bullet slot found + jsr init_bullet_pos_and_velocity ; set initial bullet position and velocity + jmp f_bullet_outdoor_init_center ; initialize the center x and y point that is swirled around + +; indoor/base level F Weapon +fire_weapon_routine_indoor_f: + jsr create_bullet_max_04 ; create bullet if possible + bne weapon_routine_exit ; exit if no bullet slot found + jsr init_indoor_bullet_pos_and_vel + jsr indoor_f_weapon_set_fs_x + lda $09 ; rapid fire flag + sta PLAYER_BULLET_F_RAPID,x ; store in memory specifically for F weapon + rts + +; S (Spray Gun/Spread Gun) weapon routine +fire_weapon_routine_s: + lda #$00 ; a = #$00 + sta $17 + +; loop through creating up to #$05 bullets in a spray pattern +@loop: + lda #$09 ; a = #$09 (up to #$0a bullets total for s weapon) + ldx #$06 ; set p2 bullet slot starting offset at #$06 + jsr create_bullet_max_a ; create bullet if slot available + bne weapon_s_l_exit ; exit if no bullet was generated + jsr init_s_bullet_pos_and_vel + inc $17 ; increment number of bullets created in current shot + lda $17 + cmp #$05 ; max bullets per shot for s weapon + bcc @loop ; loop if more bullets to create for current shot + +weapon_s_l_exit: + rts + +init_s_bullet_pos_and_vel: + lda $17 ; current bullet counter within shot + sta PLAYER_BULLET_S_BULLET_NUM,x + ldy LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor + bmi @outdoor_or_indoor_boss ; branch if indoor boss + beq @outdoor_or_indoor_boss ; branch if outdoor + jsr set_indoor_bullet_pos_and_slot ; indoor/base, set bullet position and slot + jsr set_indoor_bullet_vel + lda PLAYER_BULLET_X_POS,x ; load bullet's x position + sta PLAYER_BULLET_FS_X,x + lda $09 ; rapid fire flag + sta PLAYER_BULLET_S_RAPID,x ; store in memory specifically for S weapon + jmp set_indoor_bullet_delay + +@outdoor_or_indoor_boss: + jsr init_bullet_sprite_pos ; set initial bullet sprite position + jmp s_weapon_init_bullet_velocities + +; l weapon +fire_weapon_routine_l: + lda #$01 ; a = #$01 + sta $09 ; set rapid fire flag + ldy #$0a ; y = #$0a + txa + bne @continue ; branch if player 2 + ldy #$00 ; y = #$00 + +@continue: + sty $10 + lda #$03 ; a = #$03 + sta $00 ; set number of bullet slots to #$03 + lda CONTROLLER_STATE_DIFF,x ; controller 1/2 buttons pressed + and #$40 ; keep bits .x.. .... (b button) + bne @create_3_lasers ; branch if b button pressed + +@set_slot_loop: + lda PLAYER_BULLET_SLOT,y + bne weapon_s_l_exit ; exit if slot already populated + iny + dec $00 + bpl @set_slot_loop + +@create_3_lasers: + lda #$03 ; a = #$03 (number of laser "bullets") + sta $00 + +@create_bullet_loop: + ldx $10 + jsr create_enemy_bullet + jsr @l_bullet_created + inc $10 + dec $00 + bpl @create_bullet_loop + rts + +; $00 is the bullet number (3 down to 0) +@l_bullet_created: + ldy $00 + lda LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor + beq @init_l_bullet ; branch for outdoor level + bmi @init_l_bullet ; branch for indoor boss screen + iny + iny + iny + iny + +@init_l_bullet: + lda laser_bullet_delay_tbl,y + sta PLAYER_BULLET_TIMER,x + lda LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor + beq @init_bullet_pos_and_velocity ; branch for outdoor level + bmi @init_bullet_pos_and_velocity ; branch for indoor boss screen + jsr set_indoor_bullet_pos_and_slot + jmp set_indoor_bullet_vel + +@init_bullet_pos_and_velocity: + jmp init_bullet_pos_and_velocity ; set initial bullet position and velocity + +; table for laser bullet delays (#$8 bytes) +laser_bullet_delay_tbl: + .byte $0a,$07,$04,$01 ; outdoor and indoor boss + .byte $07,$05,$03,$01 ; indoor + +; creates a bullet up to #$04 bullets on the screen +create_bullet_max_04: + lda #$03 ; a standard weapon can fire #$04 bullets at a time + ; @find_bullet_slot creates a + 1 number of bullets + +; creates a bullet up to the amount specified in accumulator + one +; sets player 2 bullet offset to start at #$0a +create_bullet_max_a_p2_0a: + ldx #$0a ; set player 2 bullet slot start offset + +; creates a bullet up to the amount specified in accumulator + one +create_bullet_max_a: + sta $00 ; store max bullets in $00 + ldy $11 ; load current player being evaluated + bne @find_bullet_slot ; branch if player 2 and start at slot specified in x register + ldx #$00 ; player 1, start at #$00 + +@find_bullet_slot: + lda PLAYER_BULLET_SLOT,x ; get value of current bullet slot + beq create_enemy_bullet ; create bullet if slot is empty + inx ; slot is already occupied, move to next available slot + dec $00 ; decrement max bullets counter + bpl @find_bullet_slot ; if still slots to search, continue + rts ; no bullet slots found, exit + +create_enemy_bullet: + ldy $11 ; load player index (0-1) + lda #$0f ; a = #$0f + sta PLAYER_RECOIL_TIMER,y ; set recoil timer to #$0f + jsr clear_bullet_values ; initialize bullet memory values to #$00 + tya + sta PLAYER_BULLET_OWNER,x ; bullet owner (#$00 for p1, #$01 for p2) + lda $08 ; load weapon type (Standard, M, F, S, L) in $08 + clc ; clear carry in preparation for addition + adc #$01 ; add #$01 to weapon type + sta PLAYER_BULLET_SLOT,x ; store weapon type + #$01 in bullet slot + tay + lda weapon_sound_tbl-1,y ; table for weapon sounds (actually one byte off from table data, and y starts at #$01) + jsr play_sound ; play sound for bullet firing + lda weapon_bullet_sprite_code_tbl,y ; table for weapon bullet sprite codes + sta PLAYER_BULLET_SPRITE_CODE,x ; player bullet tile code + lda $0b ; load player aim direction PLAYER_AIM_DIR from @fire_weapon_routine + sta PLAYER_BULLET_AIM_DIR,x ; set the bullet direction based on player direction when firing + lda #$00 ; a = #$00 + rts + +; table for weapon sounds (#$4 bytes) +; sound_0a, sound_0c, sound_10, sound_12 +; next table byte 0 is sound_0e for L +weapon_sound_tbl: + .byte $0a,$0c,$10,$12 + +; table for weapon bullet types (#$5 bytes) +; subtract 2 to get sprite code +; * #$0e - belongs to previous table L weapon sound (sound_0e) +; - not used for sprites, as read offset for this table is at least #$01 +; * #$1f - sprite_1f (M Bullet) +; * #$22 - sprite_22 (F bullet) +; * #$00 - for l bullet, but not used, overwritten with #$24 from l_bullet_sprite_code_tbl +weapon_bullet_sprite_code_tbl: + .byte $0e,$1e,$1f,$22,$1f,$00 + +set_indoor_bullet_delay: + ldy #$2a ; default y = #$2a + lda $08 ; load weapon type (Standard, M, F, S, L) in $08 + cmp #$04 ; compare to #$04 (laser) + beq @continue ; branch if laser + lda $09 ; not laser, check rapid fire flag + beq @continue ; branch if rapid fire is not set + ldy #$15 ; y = #$15 + +@continue: + tya + sta PLAYER_BULLET_TIMER,x + rts + +; set initial bullet sprite position +init_bullet_sprite_pos: + ldy #$00 ; y = #$00 + lda LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor + beq @handle_outdoor ; start at offset #$00 for outdoor levels + ldy #$04 ; indoor level, increase offset by #$04 (#$02 entries) + +@handle_outdoor: + lda $0a ; load player jump status + beq @continue ; branch if player isn't jumping, don't adjust offset + iny ; player is jumping, increment offset into bullet_initial_pos_ptr_tbl by an additional #$02 bytes + iny + +@continue: + lda bullet_initial_pos_ptr_tbl,y + sta $0e + lda bullet_initial_pos_ptr_tbl+1,y + sta $0f + lda $0b ; load PLAYER_AIM_DIR + asl ; double entry + tay ; set as offset into bullet_dir_XX + lda ($0e),y ; load bullet initial offset based on player aim direction + clc ; clear carry in preparation for addition + adc $0c ; add offset to player x position on screen + sta PLAYER_BULLET_X_POS,x + iny + lda ($0e),y + clc ; clear carry in preparation for addition + adc $0d ; add offset to player y position on screen + sta PLAYER_BULLET_Y_POS,x + rts + +; pointer table for initial bullet offsets (4 * 2 = 8 bytes) +bullet_initial_pos_ptr_tbl: + .addr bullet_initial_pos_00 ; outdoor CPU address $b5fa + .addr bullet_initial_pos_01 ; outdoor - jumping CPU address $b60e + .addr bullet_initial_pos_02 ; indoor CPU address $b626 + .addr bullet_initial_pos_03 ; indoor jumping CPU address $b63a + +; initial bullet offset - outdoor - on ground (#$14 bytes) +; byte #$01 - x offset from player position +; byte #$02 - y offset from player position +bullet_initial_pos_00: + .byte $05,$e5 ; $05 -1b Up + .byte $0d,$f0 ; $0d -10 Up-Right + .byte $10,$fb ; $10 -05 Right + .byte $0d,$06 ; $0d 06 Down-Right + .byte $10,$09 ; $10 09 Prone Facing Right + .byte $f0,$09 ; -$10 09 Prone Facing Left + .byte $f3,$06 ; -$0d 06 Down-Left + .byte $f0,$fb ; -$10 -05 Left + .byte $f3,$f0 ; -$0d -10 Up-Left + .byte $fb,$e5 ; -$05 -1b Down (impossible) + +; initial bullet offset - outdoor - jumping (#$18 bytes) +; byte #$01 - x offset from player position +; byte #$02 - y offset from player position +bullet_initial_pos_01: + .byte $00,$f0 + .byte $0f,$f1 + .byte $10,$00 + .byte $0f,$0f + .byte $00,$10 + .byte $00,$10 + .byte $f1,$0f + .byte $f0,$00 + .byte $f1,$f1 + .byte $00,$f0 + .byte $00,$10 + .byte $00,$10 + +; initial bullet offsets - indoor - on ground (#$14 bytes) +; byte #$01 - x offset from player position +; byte #$02 - y offset from player position +bullet_initial_pos_02: + .byte $ff,$e8 + .byte $0f,$f0 + .byte $10,$fa + .byte $0f,$08 + .byte $10,$0b + .byte $f0,$0b + .byte $f1,$08 + .byte $f0,$fa + .byte $f1,$f0 + .byte $ff,$e8 + +; initial bullet offsets - indoor - jumping (#$18 bytes) +; byte #$01 - x offset from player position +; byte #$02 - y offset from player position +bullet_initial_pos_03: + .byte $00,$f0 + .byte $0f,$f1 + .byte $10,$00 + .byte $0f,$0f + .byte $00,$10 + .byte $00,$10 + .byte $f1,$0f + .byte $f0,$00 + .byte $f1,$f1 + .byte $00,$f0 + .byte $00,$f0 + .byte $00,$f0 + +set_indoor_bullet_pos_and_slot: + lda #$00 ; a = #$00 + sta $0e ; set bullet x position offset to #$00 + ldy #$00 ; set bullet y offset position to #$00 + lda $0a ; load jump status + bne @set_pos_and_slot ; branch if jumping + ldy #$f4 ; start with assuming player is crouching + ; set bullet y offset position to #$f4 + dec $0e ; set bullet x position offset to #$ff + lda $0b ; load player aim direction + cmp #$04 ; see if aiming down facing right + beq @set_pos_and_slot ; branch if aiming down facing right + cmp #$05 ; see if aiming dow facing left + beq @set_pos_and_slot ; branch if aiming down facing left + ldy #$e8 ; set bullet y position offset to #$e8 + bcs @set_pos_and_slot + lda #$01 ; a = #$01 + sta $0e ; set bullet x position offset to #$01 + +; y is bullet y offset from player +; a is bullet x offset from player +@set_pos_and_slot: + tya + clc ; clear carry in preparation for addition + adc $0d ; add offset to player y position + sta PLAYER_BULLET_Y_POS,x ; store bullet y position + lda $0e ; load bullet x position offset + clc ; clear carry in preparation for addition + adc $0c ; add bullet offset to player x position + sta PLAYER_BULLET_X_POS,x ; store bullet x position + cpy #$f4 ; compare bullet y offset to #$f4 + bne @continue ; branch if not crouching while shooting + lda PLAYER_BULLET_SLOT,x ; player is crouching (pressing down while shooting) + ora #$80 ; set bits x... .... + sta PLAYER_BULLET_SLOT,x ; mark bullet as special ? + +@continue: + lda $0a + beq @exit + lda PLAYER_BULLET_Y_POS,x + cmp #$98 + bcc @exit + lda #$98 ; a = #$98 + sta PLAYER_BULLET_Y_POS,x + +@exit: + rts + +; not used for s weapon +set_bullet_velocity: + ldy #$04 ; y = #$04 + lda $08 ; load weapon type (Standard, M, F, S, L) in $08 + cmp #$02 ; 02 = f weapon + beq @check_rapid_flag ; branch if f weapon + ldy #$02 ; y = #$02 + cmp #$04 ; 04 = l weapon + beq @check_rapid_flag + ldy #$00 ; y = #$00 + +@check_rapid_flag: + lda $09 ; rapid fire flag + beq @continue + iny ; rapid fire is set for weapon, increment bullet_velocity_ptr_tbl read offset + +@continue: + tya + asl ; double offset since each entry is #$02 bytes + tay + lda bullet_velocity_ptr_tbl,y ; load appropriate table of velocities low byte + sta $01 + lda bullet_velocity_ptr_tbl+1,y ; load appropriate table of velocities high byte + sta $02 + lda $0b ; load player aim direction + asl ; each entry is #$04 bytes + asl ; double twice to get correct offset + tay + lda ($01),y ; load x velocity fast value + sta PLAYER_BULLET_VEL_X_FAST,x ; store x velocity fast value + iny ; increment velocity table read offset + lda ($01),y ; load x fractional velocity value + sta PLAYER_BULLET_X_VEL_FRACT,x ; store x fractional velocity value + iny ; increment velocity table read offset + lda ($01),y ; load y velocity fast value + sta PLAYER_BULLET_Y_VEL_FAST,x ; store y velocity fast value + iny ; increment velocity table read offset + lda ($01),y ; load y fractional velocity byte + sta PLAYER_BULLET_Y_VEL_FRACT,x ; store y fractional velocity byte + rts + +; pointer table for player bullet velocity (6 * 2 = c bytes) +bullet_velocity_ptr_tbl: + .addr bullet_velocity_normal ; Standard, M - Normal CPU address $b6e9 + .addr bullet_velocity_rapid ; Standard, M - Rapid Fire CPU address $b719 + .addr bullet_velocity_rapid ; L Weapon - Normal CPU address $b719 + .addr bullet_velocity_rapid ; L Weapon - Rapid Fire CPU address $b719 + .addr bullet_velocity_f ; F Weapon - Normal CPU address $b779 + .addr bullet_velocity_f_rapid ; F Weapon - Rapid Fire CPU address $b749 + +; player bullet velocity - standard, and m weapon - normal (#$30 bytes) +; #$4 bytes per angle (#$2 bytes for x, #$2 bytes for y) +; #$c velocities in table, but only #$0a are accessible due to possible PLAYER_AIM_DIR values !(WHY?) +; all values are calculated assuming a velocity of #$300 (768 decimal) +; format: XX XX YY YY +; ex: +; 00 00 - X Velocity - (high byte, low byte) - #$0000 = 0 +; FD 00 - Y Velocity - (high byte, low byte) - #$fd00 = -768 +; for angled shots use sin(45 deg) = 0.707 +; 768 * 0.707 = 543 -> #$21f +bullet_velocity_normal: + .byte $00,$00,$fd,$00 ; #$00 , -#$300 - up facing right + .byte $02,$1f,$fd,$e1 ; #$21f , -#$21f - up right + .byte $03,$00,$00,$00 ; #$300 , #$000 - right + .byte $02,$1f,$02,$1f ; #$21f , #$21f - right down + .byte $03,$00,$00,$00 ; #$300 , #$000 - down facing right + .byte $fd,$00,$00,$00 ; -#$300 , #$000 - down facing left + .byte $fd,$e1,$02,$1f ; -#$21f , #$21f - down left + .byte $fd,$00,$00,$00 ; -#$300 , #$000 - left + .byte $fd,$e1,$fd,$e1 ; -#$21f , -#$21f - up left + .byte $00,$00,$fd,$00 ; #$000 , -#$300 - up facing left + .byte $00,$00,$fd,$00 ; #$000 , -#$300 - unused + .byte $00,$00,$03,$00 ; #$000 , #$300 - unused + +; player bullet velocity - standard, and m weapon - rapid fire (#$30 bytes) +; l weapon uses this table for both normal and rapid fire +; #$4 bytes per angle (#$2 bytes for x, #$2 bytes for y) +; #$c velocities in table, but only #$0a are accessible due to possible PLAYER_AIM_DIR values !(HUH) +; all values are calculated assuming a velocity of #$400 (1024 decimal) +; format: XX XX YY YY +; ex: +; 00 00 - X Velocity - (high byte, low byte) - #$0000 = 0 +; FC 00 - Y Velocity - (high byte, low byte) - #$fc00 = -1024 +; for angled shots use sin(45 deg) = 0.707 +; 1024 * 0.707 = 724 -> #$2d4 +bullet_velocity_rapid: + .byte $00,$00,$fc,$00 ; #$000 , -#$400 - up facing right + .byte $02,$d4,$fd,$2c ; #$2d4 , -#$2d4 - up right + .byte $04,$00,$00,$00 ; #$400 , #$000 - right + .byte $02,$d4,$02,$d4 ; #$2d4 , #$2d4 - right down + .byte $04,$00,$00,$00 ; #$400 , #$000 - down facing right + .byte $fc,$00,$00,$00 ; -#$400 , #$000 - down facing left + .byte $fd,$2c,$02,$d4 ; -#$2d4 , #$2d4 - down left + .byte $fc,$00,$00,$00 ; -#$400 , #$000 - left + .byte $fd,$2c,$fd,$2c ; -#$2d4 , -#$2d4 - up left + .byte $00,$00,$fc,$00 ; #$000 , -#$400 - up facing left + .byte $00,$00,$fc,$00 ; - unused + .byte $00,$00,$04,$00 ; - unused + +; player bullet velocity - f weapon - rapid fire (#$30 bytes) +; #$4 bytes per angle (#$2 bytes for x, #$2 bytes for y) +; #$c velocities in table, but only #$0a are accessible due to possible PLAYER_AIM_DIR values !(HUH) +; all values are calculated assuming a velocity of #$200 (512 decimal) +; format: XX XX YY YY +; ex: +; 00 00 - X Velocity - (high byte, low byte) - #$0000 = 0 +; FE 00 - Y Velocity - (high byte, low byte) - #$fe00 = -512 +; for angled shots use sin(45 deg) = 0.707 +; 512 * 0.707 = 362 -> #$16a +bullet_velocity_f_rapid: + .byte $00,$00,$fe,$00 ; #$000 , -#$200 ; up facing left + .byte $01,$6a,$fe,$96 ; #$16a , -#$16a ; up right + .byte $02,$00,$00,$00 ; #$200 , #$000 ; right + .byte $01,$6a,$01,$6a ; #$16a , #$16a ; right down + .byte $02,$00,$00,$00 ; #$200 , #$000 ; down facing right + .byte $fe,$00,$00,$00 ; -#$200 , #$000 ; down facing left + .byte $fe,$96,$01,$6a ; -#$16a , #$16a ; down left + .byte $fe,$00,$00,$00 ; -#$200 , #$000 ; left + .byte $fe,$96,$fe,$96 ; -#$16a , -#$16a ; up left + .byte $00,$00,$fe,$00 ; #$000 , -#$200 ; up facing left + .byte $00,$00,$fe,$00 ; #$000 , -#$200 ; unused + .byte $00,$00,$02,$00 ; #$000 , #$200 ; unused + +; player bullet velocity - f weapon - normal (#$30 bytes) +; #$4 bytes per angle (#$2 bytes for x, #$2 bytes for y) +; #$c velocities in table, but only #$0a are accessible due to possible PLAYER_AIM_DIR values !(HUH) +; all values are calculated assuming a velocity of #$180 (384 decimal) +; format: XX XX YY YY +; ex: +; 00 00 - X Velocity - (high byte, low byte) - #$0000 = 0 +; FE 80 - Y Velocity - (high byte, low byte) - #$fe80 = -384 +; for angled shots use sin(45 deg) = 0.707 +; 384 * 0.707 = 271 -> #$10f +bullet_velocity_f: + .byte $00,$00,$fe,$80 ; #$000 , -#$180 ; up facing left + .byte $01,$0f,$fe,$f1 ; #$10f , -#$10f ; up right + .byte $01,$80,$00,$00 ; #$180 , #$000 ; right + .byte $01,$0f,$01,$0f ; #$10f , #$10f ; right down + .byte $01,$80,$00,$00 ; #$180 , #$000 ; down facing right + .byte $fe,$80,$00,$00 ; -#$180 , #$000 ; down facing left + .byte $fe,$f1,$01,$0f ; -#$10f , #$10f ; down left + .byte $fe,$80,$00,$00 ; -#$180 , #$000 ; left + .byte $fe,$f1,$fe,$f1 ; -#$10f , -#$10f ; up left + .byte $00,$00,$fe,$80 ; #$000 , -#$180 ; up facing left + .byte $00,$00,$fe,$80 ; #$000 , -#$180 ; unused + .byte $00,$00,$01,$80 ; #$000 , #$180 ; unused + +set_indoor_bullet_vel: + lda #$00 ; a = #$00 + sta $12 ; specify to branch to @negate_bullet_velocities in set_vel_for_speed_vars + lda #$40 ; a = #$40 (bullet speed code) + jsr @set_vel_for_speed_code ; convert 'speed code' to fast and fractional velocities based on rapid fire flag + lda $0f ; load resulting fast velocity + sta PLAYER_BULLET_Y_VEL_FAST,x ; set indoor bullet fast y velocity + lda $0e ; load resulting fractional velocity + sta PLAYER_BULLET_Y_VEL_FRACT,x ; set indoor bullet fractional y velocity + lda $0c + sec ; set carry flag in preparation for subtraction + sbc #$80 + sta $12 ; when non-negative, negate result velocities from @set_vel_for_speed_code + bcs @set_x_vel + eor #$ff ; underflow, flip all bits and add one + adc #$01 + +@set_x_vel: + jsr @set_vel_for_speed_code ; determine fast and fractional velocity based on a and whether rapid fire is enabled + lda $0f ; load resulting fast velocity + sta PLAYER_BULLET_VEL_X_FAST,x ; set indoor bullet fast x velocity + lda $0e ; load resulting fractional velocity + sta PLAYER_BULLET_X_VEL_FRACT,x ; set indoor bullet fractional x velocity + rts + +; input +; * a - a sort-of speed code, this value is split into fast and fractional velocity based on rapid fire flag +; output +; * $09 - rapid fire flag +; * $0e - bullet x or y fractional velocity +; * $0f - bullet x or y fast velocity +@set_vel_for_speed_code: + sta $0f ; set initial 'speed code' + lda #$00 ; a = #$00 + sta $0e ; reset fractional velocity to #$00 + ldy #$06 ; y = #$06, specifies to shift 6 bits of $0f into high bits of $0e (ror) + lda $09 ; load rapid fire flag + beq @continue ; continue if rapid fire not set + dey ; only shift #$05 bits of $0f into the high bits of $0e + +@continue: + jmp set_vel_for_speed_vars ; set fast ($0f) and fractional ($0e) velocities based on $0f and y + +; f weapon (outdoor) initialization of the center x and y point that is swirled around +f_bullet_outdoor_init_center: + lda $0b ; load player aim direction + asl + clc ; clear carry in preparation for addition + adc $0b ; #$02 * $0b + $0b => #$03 * $0b (each entry in f_bullet_initialization_tbl is #$03 bytes) + tay ; transfer to offset register + lda f_bullet_initialization_tbl,y ; load initial bullet timer value + sta PLAYER_BULLET_TIMER,x ; set initial bullet timer value + lda f_bullet_initialization_tbl+1,y ; load PLAYER_BULLET_FS_X offset amount + clc ; clear carry in preparation for addition + adc PLAYER_BULLET_X_POS,x ; add to bullet's generated x position + sta PLAYER_BULLET_FS_X,x ; set center x position on screen f bullet swirls around + lda f_bullet_initialization_tbl+2,y ; load PLAYER_BULLET_F_Y offset amount + clc ; clear carry in preparation for addition + bmi @negative_f_y_offset ; branch if PLAYER_BULLET_F_Y offset value is negative (aiming upwards) + adc PLAYER_BULLET_Y_POS,x ; PLAYER_BULLET_F_Y offset was positive, add to f bullet generated y position + bcs @clear_bullet_values ; branch if had an overflow (shouldn't happen) to remove bullet + bcc @set_fs_y ; always branch to set PLAYER_BULLET_F_Y and exit + +; aim direction facing up +@negative_f_y_offset: + adc PLAYER_BULLET_Y_POS,x ; add PLAYER_BULLET_F_Y (negative) to f bullet generated y position + bcc @clear_bullet_values ; if bullet y position plus negative amount didn't overflow, then remove bullet + ; this is because the player is at the top of the screen and the shot is off the screen above + +@set_fs_y: + sta PLAYER_BULLET_F_Y,x ; set center y position on screen f bullet swirls around + rts + +@clear_bullet_values: + jmp clear_bullet_values ; initialize bullet memory values to #$00 + +; table for initial bullet f data depending on player aim direction (#$24 bytes) +; byte 0 = initial PLAYER_BULLET_TIMER used to lookup into f_bullet_outdoor_x_swirl_amt_tbl and f_bullet_outdoor_y_swirl_amt_tbl +; byte 1 = PLAYER_BULLET_FS_X offset, added to x position to get center x position that f bullet swirls around +; byte 2 = PLAYER_BULLET_F_Y offset, added to y position to get center y position that f bullet swirls around +f_bullet_initialization_tbl: + .byte $0c,$00,$f0 ; (facing up) - vertical + .byte $0e,$0b,$f5 ; (up-right) - angled + .byte $00,$10,$00 ; (right) - flat + .byte $02,$0b,$0b ; (down-right) - angled + .byte $00,$10,$00 ; (crouching facing right) - flat + .byte $08,$f0,$00 ; (crouching facing left) - flat + .byte $06,$f5,$0b ; (down-left) - angled + .byte $08,$f0,$00 ; (left) - flat + .byte $0a,$f5,$f5 ; (up-left) - angled + .byte $0c,$00,$f0 ; (up) - vertical + .byte $0c,$00,$f0 ; (??) vertical + .byte $04,$00,$10 ; (??) vertical + +; indoor weapon only, set PLAYER_BULLET_FS_X and PLAYER_BULLET_F_Y +indoor_f_weapon_set_fs_x: + lda #$04 ; a = #$04 + sta PLAYER_BULLET_DIST,x ; set bullet size to #04 + lda PLAYER_BULLET_X_POS,x + sta PLAYER_BULLET_FS_X,x ; set center x position on screen f bullet swirls around + lda PLAYER_BULLET_Y_POS,x + sec ; set carry flag in preparation for subtraction + sbc #$02 + sta PLAYER_BULLET_F_Y,x ; set center y position on screen f bullet swirls around + rts + +; called for all 4 bullets created by the S weapon +s_weapon_init_bullet_velocities: + ldy #$00 ; y = #$00 + lda $09 ; load rapid fire flag + beq @continue ; branch if no rapid fire flag for player weapon + ldy #$02 ; y = #$02 + +@continue: + lda s_bullet_y_vel_ptr_tbl,y + sta $04 + lda s_bullet_y_vel_ptr_tbl+1,y + sta $05 + lda s_bullet_x_vel_ptr_tbl,y + sta $06 + lda s_bullet_x_vel_ptr_tbl+1,y + sta $07 + ldy $0b ; load PLAYER_AIM_DIR + lda player_aim_dir_ptr_tbl,y + ldy $17 ; bullets number of of current shot (#$00 to #$04 for each shot) + clc ; clear carry in preparation for addition + adc s_bullet_num_index_modifier_tbl,y ; load scalar to add to the player aim direction based on the S weapon bullet number + and #$1f ; max the result out at #$1f (...x xxxx) + asl + tay + lda ($04),y ; s_bullet_y_vel_normal_tbl,y or s_bullet_y_vel_rapid_fire_tbl,y + sta PLAYER_BULLET_Y_VEL_FRACT,x + lda ($06),y + sta PLAYER_BULLET_X_VEL_FRACT,x + iny + lda ($04),y + sta PLAYER_BULLET_Y_VEL_FAST,x + lda ($06),y + sta PLAYER_BULLET_VEL_X_FAST,x + rts + +; table for player aim direction (#$c bytes) +; #$00 - facing right aiming up +; #$01 - facing right aiming upwards right (diagonally up) +; #$02 - facing right +; #$03 - facing right aiming downwards right (diagonally down) +; #$04 - crouched facing right +; #$05 - crouched facing left +; #$06 - facing left aiming downwards left (diagonally down) +; #$07 - facing left +; #$08 - facing left aiming upwards left (diagonally up) +; #$09 - facing left aiming up +player_aim_dir_ptr_tbl: + .byte $00,$04,$08,$0c,$08,$18,$14,$18,$1c,$00,$00,$10 + +; table for initial index modifiers into velocity tables for each of the #$05 bullets created by the S weapon (#$5 bytes) +s_bullet_num_index_modifier_tbl: + .byte $00,$01,$ff,$02,$fe + +; pointer table for ? (2 * 2 = #$4 bytes) +s_bullet_y_vel_ptr_tbl: + .addr s_bullet_y_vel_normal_tbl ; CPU address $b8aa (no rapid fire) + .addr s_bullet_y_vel_rapid_fire_tbl ; CPU address $b8fa (rapid fire) + +; pointer table for ? (2 * 2 = #$4 bytes) +s_bullet_x_vel_ptr_tbl: + .addr s_bullet_x_vel_normal_tbl ; CPU address $b8ba + .addr s_bullet_x_vel_rapid_fire_tbl ; CPU address $b90a + +; bullet y velocity when no rapid fire (8 * 2 = #$10 bytes) +; byte 0 is PLAYER_BULLET_Y_VEL_FRACT +; byte 1 is PLAYER_BULLET_Y_VEL_FAST +s_bullet_y_vel_normal_tbl: + .byte $03,$fd + .byte $0f,$fd + .byte $3c,$fd + .byte $84,$fd + .byte $e1,$fd + .byte $56,$fe + .byte $dd,$fe + .byte $6d,$ff + +; table for bullet x velocity with no rapid fire (#$40 bytes) +; s_bullet_y_vel_normal_tbl,y can overflow into this table +s_bullet_x_vel_normal_tbl: + .byte $00,$00 + .byte $93,$00 + .byte $23,$01 + .byte $aa,$01 + .byte $1f,$02 + .byte $7c,$02 + .byte $c4,$02 + .byte $f1,$02 + .byte $fd,$02 + .byte $f1,$02 + .byte $c4,$02 + .byte $7c,$02 + .byte $1f,$02 + .byte $aa,$01 + .byte $23,$01 + .byte $93,$00 + .byte $00,$00 + .byte $6d,$ff + .byte $dd,$fe + .byte $56,$fe + .byte $e1,$fd + .byte $84,$fd + .byte $3c,$fd + .byte $0f,$fd + .byte $03,$fd + .byte $0f,$fd + .byte $3c,$fd + .byte $84,$fd + .byte $e1,$fd + .byte $56,$fe + .byte $dd,$fe + .byte $6d,$ff + +; bullet y velocity when rapid fire (8 * 2 = #$10 bytes) +; byte 0 is PLAYER_BULLET_Y_VEL_FRACT +; byte 1 is PLAYER_BULLET_Y_VEL_FAST +s_bullet_y_vel_rapid_fire_tbl: + .byte $84,$fc + .byte $92,$fc + .byte $c6,$fc + .byte $1a,$fd + .byte $87,$fd + .byte $0f,$fe + .byte $ad,$fe + .byte $55,$ff + +; table for bullet x velocity with rapid fire (#$40 bytes) +; s_bullet_y_vel_rapid_fire_tbl,y can overflow into this table +s_bullet_x_vel_rapid_fire_tbl: + .byte $00,$00 + .byte $ab,$00 + .byte $53,$01 + .byte $f1,$01 + .byte $79,$02 + .byte $e6,$02 + .byte $3a,$03 + .byte $6e,$03 + .byte $7c,$03 + .byte $6e,$03 + .byte $3a,$03 + .byte $e6,$02 + .byte $79,$02 + .byte $f1,$01 + .byte $53,$01 + .byte $ab,$00 + .byte $00,$00 + .byte $55,$ff + .byte $ad,$fe + .byte $0f,$fe + .byte $87,$fd + .byte $1a,$fd + .byte $c6,$fc + .byte $92,$fc + .byte $84,$fc + .byte $92,$fc + .byte $c6,$fc + .byte $1a,$fd + .byte $87,$fd + .byte $0f,$fe + .byte $ad,$fe + .byte $55,$ff + +run_player_bullet_routines: + ldx #$00 ; start at first bullet slot + +@loop: + lda PLAYER_BULLET_SLOT,x ; load bullet type + beq @advance_bullet_slot ; move to next slot if current slot doesn't have a bullet + stx $10 ; store bullet type (+1) in $10 + lda PLAYER_BULLET_OWNER,x ; load the player who fired the bullet (#$00 = p1, #$01 = p2) + sta $11 ; store player who fired the bullet in $11 + jsr run_player_bullet_routine ; run current bullet routine for the bullet slot x + +@advance_bullet_slot: + inx + cpx #$10 + bcc @loop + +; placeholder empty routine that isn't used since PLAYER_BULLET_SLOT starts at #$01 +player_bullet_routine_00_ptr_tbl: + rts + +; input +; * x - PLAYER_BULLET_SLOT index +; * $10 - PLAYER_BULLET_SLOT (bullet type + 1) +; * $11 - player who fired the bullet (#$00 = p1, #$01 = p2) +run_player_bullet_routine: + lda PLAYER_BULLET_SLOT,x ; load bullet type + #$01 + and #$0f ; keep bits .... xxxx + ldy LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor; #$80 = indoor boss + bmi @continue ; branch if indoor boss screen + beq @continue ; branch for outdoor level + clc ; clear carry in preparation for addition + adc #$06 ; indoor level, offset to use indoor player bullet routines (add #$06) + +@continue: + asl + tay + lda player_bullet_routine_ptr_tbl,y + sta $0a + lda player_bullet_routine_ptr_tbl+1,y + sta $0b + lda PLAYER_BULLET_ROUTINE,x + asl + tay + lda ($0a),y + sta $08 + iny + lda ($0a),y + sta $09 + jmp ($0008) + +; pointer table for ? (#$c * #$2 = #$18 bytes) +player_bullet_routine_ptr_tbl: + .addr player_bullet_routine_00_ptr_tbl ; CPU address $b960 (dead code, not used, placeholder since PLAYER_BULLET_SLOT starts at #$01) + .addr player_bullet_routine_01_ptr_tbl ; CPU address $b9a4 (default bullet) + .addr player_bullet_routine_02_ptr_tbl ; CPU address $b9aa (M bullet) + .addr player_bullet_routine_03_ptr_tbl ; CPU address $b9b0 (F bullet) + .addr player_bullet_routine_04_ptr_tbl ; CPU address $b9b6 (S bullet) + .addr player_bullet_routine_05_ptr_tbl ; CPU address $b9bc (L bullet) + + ; indoor routines + .addr player_bullet_routine_00_ptr_tbl ; CPU address $b960 (dead code, not used, placeholder since PLAYER_BULLET_SLOT starts at #$01) + .addr player_bullet_routine_indoor_01_ptr_tbl ; CPU address $b9c2 (default bullet) + .addr player_bullet_routine_indoor_02_ptr_tbl ; CPU address $b9c8 (M bullet) + .addr player_bullet_routine_indoor_03_ptr_tbl ; CPU address $b9ce (F bullet) + .addr player_bullet_routine_indoor_04_ptr_tbl ; CPU address $b9d4 (S bullet) + .addr player_bullet_routine_indoor_05_ptr_tbl ; CPU address $b9da (L bullet) + +; pointer table for default bullet routines (#$2 * #$3 = #$6 bytes) +player_bullet_routine_01_ptr_tbl: + .addr inc_player_bullet_routine_far ; CPU address $b9e0 + .addr player_shared_bullet_routine_01 ; CPU address $ba46 + .addr player_bullet_collision_routine ; CPU address $bc1e + +; pointer table for M bullet routines (#$2 * #$3 = #$6 bytes) +player_bullet_routine_02_ptr_tbl: + .addr inc_player_bullet_routine_far ; CPU address $b9e0 + .addr player_shared_bullet_routine_01 ; CPU address $ba46 + .addr player_bullet_collision_routine ; CPU address $bc1e + +; pointer table for F bullet routines (#$2 * #$3 = #$6 bytes) +player_bullet_routine_03_ptr_tbl: + .addr inc_player_bullet_routine_far ; CPU address $b9e0 + .addr player_f_bullet_routine_01 ; CPU address $ba4f + .addr player_bullet_collision_routine ; CPU address $bc1e + +; pointer table for S bullet routines (#$2 * #$3 = #$6 bytes) +player_bullet_routine_04_ptr_tbl: + .addr inc_player_bullet_routine_far ; CPU address $b9e0 + .addr player_s_bullet_routine_01 ; CPU address $ba60 - determines sprite (size) then calls player_shared_bullet_routine_01 + .addr player_bullet_collision_routine ; CPU address $bc1e + +; pointer table for L bullet routines (#$2 * #$3 = #$6 bytes) +player_bullet_routine_05_ptr_tbl: + .addr player_l_bullet_routine_00 ; CPU address $b9e3 + .addr player_shared_bullet_routine_01 ; CPU address $ba46 + .addr player_bullet_collision_routine ; CPU address $bc1e + +; pointer table for default bullet routines on indoor levels (#$2 * #$3 = #$6 bytes) +player_bullet_routine_indoor_01_ptr_tbl: + .addr inc_player_bullet_routine_far_2 ; CPU address $ba2f + .addr player_shared_indoor_bullet_routine_01 ; CPU address $ba7c + .addr player_bullet_collision_routine ; CPU address $bc1e + +; pointer table for M (#42 * #$3 = #$6 bytes) +player_bullet_routine_indoor_02_ptr_tbl: + .addr inc_player_bullet_routine_far_2 ; CPU address $ba2f + .addr player_shared_indoor_bullet_routine_01 ; CPU address $ba7c + .addr player_bullet_collision_routine ; CPU address $bc1e + +; pointer table for F bullet routines on indoor levels (#$2 * #$3 = #$6 bytes) +player_bullet_routine_indoor_03_ptr_tbl: + .addr inc_player_bullet_routine_far_2 ; CPU address $ba2f + .addr player_f_indoor_bullet_routine_01 ; CPU address $ba82 + .addr player_bullet_collision_routine ; CPU address $bc1e + +; pointer table for S bullet routines on indoor levels (#$2 * #$3 = #$6 bytes) +player_bullet_routine_indoor_04_ptr_tbl: + .addr inc_player_bullet_routine_far_2 ; CPU address $ba2f + .addr player_s_indoor_bullet_routine_01 ; CPU address $ba8b + .addr player_bullet_collision_routine ; CPU address $bc1e + +; pointer table for L bullet routines on indoor levels (#$2 * #$3 = #$6 bytes) +player_bullet_routine_indoor_05_ptr_tbl: + .addr player_l_indoor_bullet_routine_00 ; CPU address $ba32 + .addr player_l_indoor_bullet_routine_01 ; CPU address $baa7 + .addr player_bullet_collision_routine ; CPU address $bc1e + +inc_player_bullet_routine_far: + jmp inc_player_bullet_routine + +player_l_bullet_routine_00: + lda LEVEL_SCROLLING_TYPE ; 0 = horizontal, indoor/base; 1 = vertical + bne @handle_vertical ; branch for vertical level + lda PLAYER_BULLET_X_POS,x + sec ; set carry flag in preparation for subtraction + sbc FRAME_SCROLL ; how much to scroll the screen (#00 - no scroll) + sta PLAYER_BULLET_X_POS,x + jmp @adv_routine ; dec bullet timer, set sprite, and advance routine + +; player l bullet routine for vertical level +@handle_vertical: + lda PLAYER_BULLET_Y_POS,x + clc ; clear carry in preparation for addition + adc FRAME_SCROLL ; how much to scroll the screen (#00 - no scroll) + sta PLAYER_BULLET_Y_POS,x + +@adv_routine: + dec PLAYER_BULLET_TIMER,x + beq l_bullet_set_sprite_adv_bullet_routine + rts + +; sets correct l bullet sprite based on player aim direction +; advances bullet routine +l_bullet_set_sprite_adv_bullet_routine: + jsr inc_player_bullet_routine + lda PLAYER_BULLET_AIM_DIR,x + asl + tay + lda l_bullet_sprite_code_tbl,y + sta PLAYER_BULLET_SPRITE_CODE,x + lda l_bullet_sprite_code_tbl+1,y + sta PLAYER_BULLET_SPRITE_ATTR,x + +player_l_bullet_exit: + rts + +; table for l bullet sprite depending on direction (#$18 bytes) +; byte 0 is the sprite_code +; byte 1 is the sprite attribute for flipping sprite_code +l_bullet_sprite_code_tbl: + .byte $23,$00 ; (facing up) - vertical + .byte $25,$80 ; (up-right) - angled + .byte $24,$00 ; (right) - flat + .byte $25,$00 ; (down-right) - angled + .byte $24,$00 ; (crouching facing right) - flat + .byte $24,$40 ; (crouching facing left) - flat + .byte $25,$40 ; (down-left) - angled + .byte $24,$40 ; (left) - flat + .byte $25,$c0 ; (up-left) - angled + .byte $23,$00 ; (up) - vertical + .byte $23,$80 ; (??) vertical + .byte $23,$80 ; (??) vertical + +inc_player_bullet_routine_far_2: + jmp inc_player_bullet_routine + +player_l_indoor_bullet_routine_00: + dec PLAYER_BULLET_TIMER,x + bne player_l_bullet_exit + jsr inc_player_bullet_routine + lda #$15 ; a = #$15 + sta PLAYER_BULLET_TIMER,x + jmp set_indoor_l_bullet_sprite + +inc_player_bullet_routine: + inc PLAYER_BULLET_ROUTINE,x + +player_f_bullet_routine_01_exit: + rts + +; shared by many bullet routines for outdoor levels +; bullet moves according to velocity until it flies off screen or collides with solid object +; for enemy collision, this is handled by bullet_enemy_collision_test +; default, M, L, S bullets use this routine +player_shared_bullet_routine_01: + jsr update_player_bullet_pos ; update bullet position based on its velocities + jsr add_scroll_to_bullet_pos ; add scrolling to bullet position + jmp destroy_bullet_if_off_screen ; destroy bullet if it has gone off screen + +; F bullet logic for outdoor levels +; bullet moves according to velocity until it flies off screen or collides with solid object +; for enemy collision, this is handled by bullet_enemy_collision_test +player_f_bullet_routine_01: + jsr check_bullet_solid_bg_collision ; if specified, check for bullet collision with solid background + ; and if so move bullet routine to player_bullet_collision_routine + bmi player_f_bullet_routine_01_exit ; branch if collision with solid background + jsr adjust_f_bullet_if_scrolling ; adjust bullet variables if frame is scrolling + jsr update_player_f_bullet_center_pos ; update x and y center position (not including swirl effect) + jsr adjust_f_outdoor_bullet_pos_for_swirl ; determine swirl pattern position and adjust x and y position of bullet + jmp destroy_bullet_if_off_screen ; destroy bullet if it has gone off screen + +; outdoor S bullet - adjust bullet sprite based on distance traveled +; determines s bullet sprite then calls player_shared_bullet_routine_01 +player_s_bullet_routine_01: + inc PLAYER_BULLET_DIST,x ; increase bullet travel distance + lda PLAYER_BULLET_DIST,x ; load current bullet travel distance + ldy #$02 ; specify bullet frame (sprite_21) + cmp #$20 ; see if traveled for #$20 frames + bcs @continue ; branch if traveled for #$20 or more frames (use sprite_21) + dey ; traveled less than #$20 frames, decrement bullet frame (sprite_20) + cmp #$10 ; see if traveled for #$10 frames + bcs @continue ; branch if traveled for #$20 or more frames (use sprite_20) + dey ; traveled less than #$10 frames, decrement bullet frame (sprite_1f) + +@continue: + tya ; transfer bullet frame to a + clc ; clear carry in preparation for addition + adc #$1f ; add sprite offset to get actual sprite code + sta PLAYER_BULLET_SPRITE_CODE,x ; set bullet sprite: sprite_1f (small), sprite_20 (medium), sprite_21 (large) + jmp player_shared_bullet_routine_01 ; run regular shared bullet routine + +player_shared_indoor_bullet_routine_01: + jsr update_player_bullet_pos + jmp dec_bullet_delay_possibly_adv_routine ; decrement PLAYER_BULLET_TIMER, and if #$00, move to next bullet routine (remove bullet) + +player_f_indoor_bullet_routine_01: + jsr update_player_f_bullet_center_pos ; update x and y center position (not including swirl effect) + jsr f_bullet_indoor_update_pos ; updates position based on PLAYER_BULLET_DIST and PLAYER_BULLET_TIMER + jmp dec_bullet_delay_possibly_adv_routine ; decrement PLAYER_BULLET_TIMER, and if #$00, move to next bullet routine (remove bullet) + +player_s_indoor_bullet_routine_01: + ldy #$00 ; y = #$00 + lda PLAYER_BULLET_TIMER,x ; starts at #$2a (see set_indoor_bullet_delay) + cmp #$1a ; compare bullet delay to #$1a (1/2 of screen) + bcs @continue ; still in first half of distance, use #$1f as bullet sprite + iny ; bullet has traveled >= half distance, use #$20 as bullet sprite + cmp #$0a ; see if in last quarter of distance + bcs @continue ; in 50%-75% of trip, keep #$20 as bullet sprite + iny ; in last quarter of distance, use #$21 as bullet sprite (largest) + +; set bullet position and decrement bullet delay timer, once elapsed, remove bullet (advance bullet routine) +@continue: + tya + clc ; clear carry in preparation for addition + adc #$1f ; first S bullet sprite starts at #$1f + sta PLAYER_BULLET_SPRITE_CODE,x ; based on Y can be either sprite_1f (smallest), sprite_20, or sprite_21 (largest) + jsr update_s_bullet_indoor_pos ; update bullet position based on various velocities and spread configurations + jmp dec_bullet_delay_possibly_adv_routine ; decrement PLAYER_BULLET_TIMER, and if #$00, move to next bullet routine (remove bullet) + +player_l_indoor_bullet_routine_01: + jsr update_player_bullet_pos + jmp dec_bullet_delay_possibly_adv_routine ; decrement PLAYER_BULLET_TIMER, and if #$00, move to next bullet routine (remove bullet) + +; outdoor f bullet +; use PLAYER_BULLET_TIMER to determine swirl pattern position and adjust x and y pos of bullet +; sets new PLAYER_BULLET_TIMER depending on firing direction +adjust_f_outdoor_bullet_pos_for_swirl: + lda PLAYER_BULLET_TIMER,x ; load bullet timer value + and #$0f ; keep bits .... xxxx + tay ; transfer to offset register + lda f_bullet_outdoor_x_swirl_amt_tbl,y ; load x adjustment amount based on PLAYER_BULLET_TIMER + ; this means the swirl repeats every 16 frames + clc ; clear carry in preparation for addition + adc PLAYER_BULLET_FS_X,x ; add to center x position on screen f bullet swirls around + sta PLAYER_BULLET_X_POS,x ; set x position adjusted for swirl + lda f_bullet_outdoor_y_swirl_amt_tbl,y ; load y adjustment amount based on PLAYER_BULLET_TIMER + ; this means the swirl repeats every 16 frames + clc ; clear carry in preparation for addition + adc PLAYER_BULLET_F_Y,x ; add to base center y position on screen f bullet swirls around + sta PLAYER_BULLET_Y_POS,x ; set y position adjusted for swirl + lda PLAYER_BULLET_AIM_DIR,x ; load direction of the bullet (#$00 for up facing right, incrementing clockwise up to #09 for up facing left) + cmp #$0a ; PLAYER_BULLET_AIM_DIR can't ever be #$0a !(WHY?) + ; seems like code was added to prevent case when PLAYER_BULLET_AIM_DIR was #$0a, but that's not possible !(WHY?) + beq @set_bullet_delay_1 ; if #$0a, assume shooting up-right (#$01) + cmp #$05 ; see if facing left or right + lda #$ff ; a = #$ff + bcs @set_bullet_delay_a ; branch if shooting to the left + +@set_bullet_delay_1: + lda #$01 ; a = #$01 + +@set_bullet_delay_a: + clc ; clear carry in preparation for addition + adc PLAYER_BULLET_TIMER,x ; add 1 or subtract 1 from PLAYER_BULLET_TIMER depending on direction + sta PLAYER_BULLET_TIMER,x ; set new PLAYER_BULLET_TIMER + rts + +; table for y adjustment for f bullets to create swirl effect (#$4 bytes) +; overflows into next table +f_bullet_outdoor_y_swirl_amt_tbl: + .byte $00,$fa,$f5,$f2 + +; table for x adjustment for f bullets to create swirl effect (#$f bytes) +f_bullet_outdoor_x_swirl_amt_tbl: + .byte $f1,$f2,$f5,$fa,$00,$06,$0b,$0e,$0f,$0e,$0b,$06,$00,$fa,$f5,$f2 + +; pointer table for f indoor bullet x and y adjustment based on PLAYER_BULLET_DIST (6 * 2 = c bytes) +f_bullet_indoor_pos_adj_ptr_tbl: + .addr f_bullet_indoor_x_adj_tbl_00 ; CPU address $bb2a (farthest from player) + .addr f_bullet_indoor_y_adj_tbl_00 ; CPU address $bb26 + .addr f_bullet_indoor_x_adj_tbl_01 ; CPU address $bb16 + .addr f_bullet_indoor_y_adj_tbl_01 ; CPU address $bb12 + .addr f_bullet_indoor_x_adj_tbl_02 ; CPU address $bb02 + .addr f_bullet_indoor_y_adj_tbl_02 ; CPU address $bafe (closest from player) + +; table for f indoor bullet y adjustment based on PLAYER_BULLET_DIST (#$4 bytes) (bleeds into next table) +f_bullet_indoor_y_adj_tbl_02: + .byte $00,$fc,$f8,$f5 + +; table for f indoor x adjustment based on PLAYER_BULLET_DIST (#$10 bytes) +f_bullet_indoor_x_adj_tbl_02: + .byte $f5,$f5,$f8,$fc,$00,$04,$08,$0b,$0b,$0b,$08,$04,$00,$fc,$f8,$f5 + +; table for f indoor bullet y adjustment based on PLAYER_BULLET_DIST (#$4 bytes) (bleeds into next table) +f_bullet_indoor_y_adj_tbl_01: + .byte $00,$fd,$fb,$f9 + +; table for f indoor x adjustment based on PLAYER_BULLET_DIST (#$10 bytes) +f_bullet_indoor_x_adj_tbl_01: + .byte $f9,$f9,$fb,$fd,$00,$03,$05,$07,$07,$07,$05,$03,$00,$fd,$fb,$f9 + +; table for f indoor bullet y adjustment based on PLAYER_BULLET_DIST (#$4 bytes) (bleeds into next table) +f_bullet_indoor_y_adj_tbl_00: +.byte $00,$ff,$fe,$fd + +; table for f indoor x adjustment based on PLAYER_BULLET_DIST (#$10 bytes) +f_bullet_indoor_x_adj_tbl_00: + .byte $fd,$fd,$fe,$ff,$00,$01,$02,$03,$03,$03,$02,$01,$00,$ff,$fe,$fd + +add_scroll_to_bullet_pos: + lda FRAME_SCROLL ; how much to scroll the screen (#00 - no scroll) + beq add_scroll_to_bullet_pos_exit ; no adjustment needed, exit + lda LEVEL_SCROLLING_TYPE ; 0 = horizontal, indoor/base; 1 = vertical + bne @vertical_scroll ; branch if vertical scroll + dec PLAYER_BULLET_X_POS,x + jmp add_scroll_to_bullet_pos_exit + +@vertical_scroll: + lda PLAYER_BULLET_Y_POS,x + clc ; clear carry in preparation for addition + adc FRAME_SCROLL ; how much to scroll the screen (#00 - no scroll) + sta PLAYER_BULLET_Y_POS,x + +add_scroll_to_bullet_pos_exit: + rts + +; decrements PLAYER_BULLET_FS_X for horizontal/indoor levels +; adds FRAME_SCROLL to PLAYER_BULLET_F_Y for vertical levels +adjust_f_bullet_if_scrolling: + lda FRAME_SCROLL ; whether or not the screen is scrolling (#$00 or #$01) + beq @exit ; exit if not scrolling + ldy LEVEL_SCROLLING_TYPE ; 0 = horizontal, indoor/base; 1 = vertical + bne @vertical_scroll + dec PLAYER_BULLET_FS_X,x ; horizontal level, decrement center x position on screen f bullet swirls around + jmp add_scroll_to_bullet_pos_exit + +@vertical_scroll: + lda PLAYER_BULLET_F_Y,x ; load center y position on screen f bullet swirls around + clc ; clear carry in preparation for addition + adc FRAME_SCROLL ; how much to scroll the screen (#00 - no scroll) + sta PLAYER_BULLET_F_Y,x ; set center y position on screen f bullet swirls around + +@exit: + rts + +; updates the player's (indoor and outdoor) F bullet's X and Y center position (not including swirl effect) +; swirl effect adjustment happens update_player_bullet_pos +update_player_f_bullet_center_pos: + lda PLAYER_BULLET_VEL_F_Y_ACCUM,x ; load bullet fractional velocity accumulator (f) + clc ; clear carry in preparation for addition + adc PLAYER_BULLET_Y_VEL_FRACT,x ; add accumulator and factional velocity + sta PLAYER_BULLET_VEL_F_Y_ACCUM,x ; store new accumulator value + lda PLAYER_BULLET_F_Y,x ; load center y position on screen f bullet swirls around + adc PLAYER_BULLET_Y_VEL_FAST,x ; add fast velocity to y position (including any overflow from accumulator, i.e. fractional velocity) + sta PLAYER_BULLET_F_Y,x ; set new center y position + +; updates the player's F and S (indoor) bullet's PLAYER_BULLET_FS_X position based on the bullet's velocities +; includes additional variable compared to update_player_bullet_pos to incorporate swirl effect +update_player_fs_bullet_x_pos: + lda PLAYER_BULLET_VEL_FS_X_ACCUM,x ; load accumulator value for bullet X velocity + clc ; clear carry in preparation for addition + adc PLAYER_BULLET_X_VEL_FRACT,x ; add x fractional velocity, noting the carry being set if overflow + sta PLAYER_BULLET_VEL_FS_X_ACCUM,x ; add accumulated value back + lda PLAYER_BULLET_FS_X,x + adc PLAYER_BULLET_VEL_X_FAST,x ; add fast X velocity and any carry from accumulator + sta PLAYER_BULLET_FS_X,x + rts + +; updates the player bullet's X and Y position based on the bullet's velocities +update_player_bullet_pos: + jsr check_bullet_solid_bg_collision ; if specified, check for bullet collision with solid background + ; and if so move bullet routine to player_bullet_collision_routine + bmi bullet_logic_exit ; exit if bullet collided with solid object + lda PLAYER_BULLET_VEL_X_ACCUM,x ; load accumulator value for bullet X velocity + clc ; clear carry in preparation for addition + adc PLAYER_BULLET_X_VEL_FRACT,x ; add x fractional velocity, noting the carry being set if overflow + sta PLAYER_BULLET_VEL_X_ACCUM,x ; add accumulated value back + lda PLAYER_BULLET_X_POS,x ; load bullet X position + adc PLAYER_BULLET_VEL_X_FAST,x ; add fast X velocity and any carry from accumulator + sta PLAYER_BULLET_X_POS,x ; set new X position + +update_player_bullet_y_pos: + clc ; clear carry in preparation for addition + lda PLAYER_BULLET_VEL_Y_ACCUM,x ; load accumulator value for bullet Y velocity + adc PLAYER_BULLET_Y_VEL_FRACT,x ; add y fractional velocity, noting the carry being set if overflow + sta PLAYER_BULLET_VEL_Y_ACCUM,x ; add accumulated value back + lda PLAYER_BULLET_Y_POS,x ; load bullet Y position + adc PLAYER_BULLET_Y_VEL_FAST,x ; add fast Y velocity and any carry from accumulator + sta PLAYER_BULLET_Y_POS,x ; set new Y position + +bullet_logic_exit: + rts + +; depending on the level, checks if bullet has collided with solid background +; if collision with solid object move to next bullet routine (player_bullet_collision_routine) +; output +; * a - collision code #$00 (empty), #$01 (floor), #$02 (water), or #$80 (solid) +check_bullet_solid_bg_collision: + lda LEVEL_SOLID_BG_COLLISION_CHECK ; level header offset #$19 + ; determines whether to check player bullet - solid bg collision + bpl bullet_logic_exit ; exit if shouldn't check for solid bg collisions (level 6 energy zone and level 7 hangar) + ldy PLAYER_BULLET_Y_POS,x ; load bullet y position + lda PLAYER_BULLET_X_POS,x ; load bullet x position + jsr get_bg_collision_far ; determine player background collision code at position (a,y) + bpl bullet_logic_exit ; branch if not a collision with solid object + jsr set_bullet_routine_to_2 ; collided with solid object, move to bullet routine 2 and reset PLAYER_BULLET_TIMER to #$06 + lda #$80 ; a = #$80 (solid collision code) + rts + +; for outdoor levels, determines where bullet is and if it is off screen, the +; bullet is removed. +destroy_bullet_if_off_screen: + lda PLAYER_BULLET_X_POS,x + cmp #$05 ; see if bullet is off screen to the left + bcc clear_bullet_values ; destroy bullet + cmp #$fb ; see if bullet is off screen to the right + bcs clear_bullet_values ; destroy bullet + lda PLAYER_BULLET_Y_POS,x + cmp #$05 ; see if bullet is off screen to the top + bcc clear_bullet_values ; destroy bullet + cmp #$e8 ; see if bullet is off screen to the bottom + bcc clear_bullet_exit + +; initialize bullet memory values to #$00 +clear_bullet_values: + lda #$00 ; a = #$00 + sta PLAYER_BULLET_SLOT,x + sta PLAYER_BULLET_SPRITE_CODE,x + sta PLAYER_BULLET_SPRITE_ATTR,x + sta PLAYER_BULLET_ROUTINE,x + sta PLAYER_BULLET_OWNER,x + sta PLAYER_BULLET_TIMER,x + sta PLAYER_BULLET_FS_X,x + sta PLAYER_BULLET_VEL_FS_X_ACCUM,x + sta PLAYER_BULLET_F_Y,x + sta PLAYER_BULLET_VEL_F_Y_ACCUM,x + sta PLAYER_BULLET_F_RAPID,x + sta PLAYER_BULLET_DIST,x + sta PLAYER_BULLET_AIM_DIR,x + sta PLAYER_BULLET_VEL_X_FAST,x + sta PLAYER_BULLET_X_VEL_FRACT,x + sta PLAYER_BULLET_Y_VEL_FAST,x + sta PLAYER_BULLET_Y_VEL_FRACT,x + +clear_bullet_exit: + rts + +player_bullet_collision_routine: + lda INDOOR_SCREEN_CLEARED ; indoor screen cleared flag (0 = not cleared; 1 = cleared) + bne clear_bullet_values ; initialize bullet memory values to #$00 + lda #$47 ; a = #$47 (sprite_47) + sta PLAYER_BULLET_SPRITE_CODE,x ; change bullet to hollow ring + jsr add_scroll_to_bullet_pos ; add any scroll to bullet position + dec PLAYER_BULLET_TIMER,x ; decrement delay before deletion of bullet + beq clear_bullet_values ; initialize bullet memory values to #$00 + rts + +; called from f bullet indoor routine 01 +; updates f bullet indoor position based on PLAYER_BULLET_DIST and PLAYER_BULLET_TIMER +f_bullet_indoor_update_pos: + ldy #$00 ; y = #$00 + lda PLAYER_BULLET_F_RAPID,x ; load rapid fire flag for f weapon + beq @continue ; branch if rapid fire is not set + ldy #$02 ; y = #$02 + +@continue: + lda #$02 ; a = #$02 + sta $0b ; store initial f bullet offset (see f_bullet_indoor_pos_adj_ptr_tbl) + lda PLAYER_BULLET_TIMER,x ; load timer value for how long until bullet is removed + cmp #$02 ; compare to #$02 (about to be removed (too far deep in screen)) + bcs @loop ; branch if f bullet not about to be removed + lda PLAYER_BULLET_FS_X,x ; load center x position on screen f bullet swirls around + sta PLAYER_BULLET_X_POS,x ; set final x position + lda PLAYER_BULLET_F_Y,x ; load center y position on screen f bullet swirls around + sta PLAYER_BULLET_Y_POS,x ; set final y position + rts + +; f bullet indoor - update bullet position based on +; input +; * a - PLAYER_BULLET_TIMER +; * $0b - offset for f_bullet_indoor_pos_adj_ptr_tbl +; * y - offset for f_bullet_indoor_delay_cutoff_tbl +@loop: + cmp f_bullet_indoor_delay_cutoff_tbl,y ; (#$00 = rapid fire disabled, #$02 = rapid fire enabled) + bcs @cutoff_found + iny ; increment cutoff table read offset + dec $0b ; decrement f_bullet_indoor_pos_adj_ptr_tbl offset + bne @loop + +@cutoff_found: + lda $0b ; load f_bullet_indoor_pos_adj_ptr_tbl offset + asl ; double + asl ; double again + tay ; transfer to offset register + lda f_bullet_indoor_pos_adj_ptr_tbl,y ; load low byte of x adjustment table + sta $08 ; set low byte of x adjustment table + lda f_bullet_indoor_pos_adj_ptr_tbl+1,y ; load high byte of x adjustment table + sta $09 ; set high byte of x adjustment table + lda f_bullet_indoor_pos_adj_ptr_tbl+2,y ; load low byte of y adjustment table + sta $0a ; set low byte of y adjustment table + lda f_bullet_indoor_pos_adj_ptr_tbl+3,y ; load high byte of y adjustment table + sta $0b ; set high byte of y adjustment table + lda PLAYER_BULLET_DIST,x ; load how far a bullet has traveled (timer since fired) + and #$0f ; keep bits .... xxxx + tay + lda ($08),y ; load x adjustment amount + clc ; clear carry in preparation for addition + adc PLAYER_BULLET_FS_X,x ; add current swirl size [#$71-#$7b] + sta PLAYER_BULLET_X_POS,x ; store new x position + lda ($0a),y ; load y adjustment amount + clc ; clear carry in preparation for addition + adc PLAYER_BULLET_F_Y,x ; add to center y position on screen f bullet swirls around + sta PLAYER_BULLET_Y_POS,x + lda PLAYER_BULLET_F_RAPID,x + clc ; clear carry in preparation for addition + adc #$01 + clc ; clear carry in preparation for addition + adc PLAYER_BULLET_DIST,x + sta PLAYER_BULLET_DIST,x + rts + +; table for PLAYER_BULLET_TIMER cutoff values for use in determining f_bullet_indoor_pos_adj_ptr_tbl index (#$4 bytes) +f_bullet_indoor_delay_cutoff_tbl: + .byte $1c,$0e ; no f rapid fire + .byte $0e,$07 ; f rapid fire + +; updates indoor level S bullet positions +; y position is straightforward, but x position is more complicated for spread effect. +; x position calculation includes the _FS_* variables and PLAYER_BULLET_S_INDOOR_ADJ +update_s_bullet_indoor_pos: + jsr update_player_fs_bullet_x_pos ; update PLAYER_BULLET_FS_X based on _FS_ velocities + jsr update_player_bullet_y_pos ; update PLAYER_BULLET_Y_POS based on regular velocities + lda PLAYER_BULLET_VEL_FS_X_ACCUM,x ; ignore, no affect + clc ; ignore, no affect + adc PLAYER_BULLET_S_ADJ_ACCUM,x ; ignore, no affect + sta PLAYER_BULLET_VEL_X_ACCUM,x ; unused result, never read for S indoor bullets !(WHY?) + lda PLAYER_BULLET_FS_X,x ; load center x position on screen f bullet swirls around + clc ; clear carry in preparation for addition + adc PLAYER_BULLET_S_INDOOR_ADJ,x ; add the indoor adjustment + sta PLAYER_BULLET_X_POS,x ; set new x position + lda PLAYER_BULLET_S_RAPID,x ; load S weapon rapid fire flag + lsr ; shift rapid fire flag to carry register + lda PLAYER_BULLET_S_BULLET_NUM,x + bcc @load_bullet_pos_mod ; branch if rapid fire flag + adc #$04 ; rapid fire set, load rapid fire position modifications + +@load_bullet_pos_mod: + asl + tay + lda s_bullet_pos_mod_tbl,y + clc ; clear carry in preparation for addition + adc PLAYER_BULLET_S_ADJ_ACCUM,x + sta PLAYER_BULLET_S_ADJ_ACCUM,x ; add PLAYER_BULLET_S_ADJ_ACCUM to itself possibly causing overflow + lda s_bullet_pos_mod_tbl+1,y + adc PLAYER_BULLET_S_INDOOR_ADJ,x ; load indoor adjustment plus any carry from PLAYER_BULLET_S_ADJ_ACCUM addition + sta PLAYER_BULLET_S_INDOOR_ADJ,x ; set adjustment for next frame + rts + +; table for indoor S bullet X spread configuration (#$14 bytes) +; byte 0 - x spread fractional velocity - amount to add to PLAYER_BULLET_S_ADJ_ACCUM every frame (overflows into PLAYER_BULLET_S_INDOOR_ADJ) +; byte 1 - x spread velocity fast - amount to add to PLAYER_BULLET_S_INDOOR_ADJ every frame +s_bullet_pos_mod_tbl: + .byte $00,$00 ; S bullet 0 - no rapid fire + .byte $20,$00 ; S bullet 1 - no rapid fire + .byte $e0,$ff ; S bullet 2 - no rapid fire + .byte $40,$00 ; S bullet 3 - no rapid fire + .byte $c0,$ff ; S bullet 4 - no rapid fire + .byte $00,$00 ; S bullet 0 - rapid fire + .byte $40,$00 ; S bullet 1 - rapid fire + .byte $c0,$ff ; S bullet 2 - rapid fire + .byte $80,$00 ; S bullet 3 - rapid fire + .byte $80,$ff ; S bullet 4 - rapid fire + +; determine appropriate indoor l bullet sprite code and attribute based on x position on screen +set_indoor_l_bullet_sprite: + lda PLAYER_BULLET_X_POS,x + ldy #$08 ; y = #$08 + +@loop: + cmp l_bullet_indoor_x_cutoff_tbl-1,y ; compare to x cutoff from table + bcs @cutoff_found ; branch if l bullet X position > X cutoff from table + dey + bne @loop + +@cutoff_found: + lda l_bullet_indoor_sprite_code_tbl,y + sta PLAYER_BULLET_SPRITE_CODE,x + lda #$40 ; #40 specifies to flip l bullet sprite horizontally + cpy #$04 ; see if bullet x position is past midpoint of screen + bcc @continue ; if on left half of screen flip l bullet sprite horizontally + lda #$00 ; on right half of screen, do not flip l bullet sprite horizontally + +@continue: + sta PLAYER_BULLET_SPRITE_ATTR,x ; store the l bullet sprite attribute if any reflection needed + rts ; index #$00 of label is never read, so this is safe as rts + +; table for indoor l bullet x cutoff to specify angle to make the l bullet (#$8 bytes) +; actually starts at one byte less +l_bullet_indoor_x_cutoff_tbl: + .byte $40,$50,$60,$74,$8c,$a0,$b0,$c0 + +; table for indoor l bullet sprite codes, depending on bullet fired x position (#$9 bytes) +; sprite_23, sprite_82, sprite_83, sprite_84, sprite_92 +l_bullet_indoor_sprite_code_tbl: + .byte $92,$84,$83,$82,$23,$82,$83,$84,$92 + +; decrements PLAYER_BULLET_TIMER and if down to #$00, +; then move to bullet routine #$02 and set PLAYER_BULLET_TIMER to #$06 +dec_bullet_delay_possibly_adv_routine: + dec PLAYER_BULLET_TIMER,x ; decrement PLAYER_BULLET_TIMER + bne @exit ; exit if delay has not elapsed + jmp set_bullet_routine_to_2 ; move to bullet routine 2 and reset PLAYER_BULLET_TIMER to #$06 + +@exit: + rts + +; unused space (#$2da bytes) +; these $ff bytes can technically be deleted here because the contra.cfg +; specifies that any free bytes in a ROM bank will be filled with $ff +; and each ROM bank is 16 KiB +bank_6_unused_space: + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff \ No newline at end of file diff --git a/src/bank7.asm b/src/bank7.asm new file mode 100644 index 0000000..5840712 --- /dev/null +++ b/src/bank7.asm @@ -0,0 +1,10647 @@ +; Contra US Disassembly - v1.0 +; https://github.com/vermiceli/nes-contra-us +; Bank 7 is the core of the game's programming. Reset, NMI, and IRQ vectors are +; in this bank and is the entry point to the game. Bank 7 is always loaded in +; memory unlike other banks, which are memory-mapped and can be swapped out. +; Bank 7 contains the code for drawing of nametables and sprites, bank +; switching, routines for the intro sequence, controller input, score +; calculation, graphics decompression routines, palette codes, collision +; detection, pointer table for enemy routines, shared enemy logic, score table, +; enemy attributes, and bullet angles and speeds, and the NES undocumented +; footer, among other things. + +; locations of all 'vectors'. These are the 3 handles for NES interrupts +; stored in the .nes ROM as the last $06 bytes (CPU addresses $fffa-$ffff) +; these are stored at known locations so the NES can point the instruction +; pointer at known locations for triggering interrupts. +.segment "VECTORS" + .addr nmi_start, reset_vector, irq + +.segment "BANK_7" + +.include "constants.asm" + +; import labels needed from other banks +; this allows bank 7 to call into other banks + +; bank 0 imports - enemy routines +; bank 0 level 1 enemies +.import bomb_turret_routine_ptr_tbl +.import boss_wall_plated_door_routine_ptr_tbl +.import exploding_bridge_routine_ptr_tbl + +; bank 0 level 2 and 4 enemies +.import boss_eye_routine_ptr_tbl +.import roller_routine_ptr_tbl +.import grenade_routine_ptr_tbl +.import wall_turret_routine_ptr_tbl +.import wall_core_routine_ptr_tbl +.import indoor_soldier_routine_ptr_tbl +.import jumping_soldier_routine_ptr_tbl +.import grenade_launcher_routine_ptr_tbl +.import four_soldiers_routine_ptr_tbl +.import indoor_soldier_gen_routine_ptr_tbl +.import indoor_roller_gen_routine_ptr_tbl +.import eye_projectile_routine_ptr_tbl +.import boss_gemini_routine_ptr_tbl +.import spinning_bubbles_routine_ptr_tbl +.import blue_soldier_routine_ptr_tbl +.import red_soldier_routine_ptr_tbl +.import red_blue_soldier_gen_routine_ptr_tbl + +; bank 0 level 3 enemies +.import floating_rock_routine_ptr_tbl +.import moving_flame_routine_ptr_tbl +.import rock_cave_routine_ptr_tbl +.import falling_rock_routine_ptr_tbl +.import boss_mouth_routine_ptr_tbl +.import dragon_arm_orb_routine_ptr_tbl + +; bank 0 level 5 enemies +.import ice_grenade_generator_routine_ptr_tbl +.import ice_grenade_routine_ptr_tbl +.import tank_routine_ptr_tbl +.import ice_separator_routine_ptr_tbl +.import boss_ufo_routine_ptr_tbl +.import mini_ufo_routine_ptr_tbl +.import boss_ufo_bomb_routine_ptr_tbl + +; bank 0 level 6 enemies +.import fire_beam_down_routine_ptr_tbl +.import fire_beam_left_routine_ptr_tbl +.import fire_beam_right_routine_ptr_tbl +.import boss_giant_soldier_routine_ptr_tbl +.import boss_giant_projectile_routine_ptr_tbl + +; bank 0 level 7 enemies +.import claw_routine_ptr_tbl +.import rising_spiked_wall_routine_ptr_tbl +.import spiked_wall_routine_ptr_tbl +.import mine_cart_generator_routine_ptr_tbl +.import moving_cart_routine_ptr_tbl +.import immobile_cart_generator_routine_ptr_tbl +.import boss_door_routine_ptr_tbl +.import boss_mortar_routine_ptr_tbl +.import boss_soldier_generator_routine_ptr_tbl + +; bank 0 level 8 enemies +.import alien_guardian_routine_ptr_tbl +.import alien_fetus_routine_ptr_tbl +.import alien_mouth_routine_ptr_tbl +.import white_blob_routine_ptr_tbl +.import alien_spider_routine_ptr_tbl +.import alien_spider_spawn_routine_ptr_tbl +.import boss_heart_routine_ptr_tbl + +; bank 0 enemies that exist on multiple levels +.import enemy_bullet_routine_ptr_tbl +.import rotating_gun_routine_ptr_tbl +.import red_turret_routine_ptr_tbl +.import sniper_routine_ptr_tbl +.import soldier_routine_ptr_tbl +.import weapon_box_routine_ptr_tbl +.import weapon_item_routine_ptr_tbl +.import flying_capsule_routine_ptr_tbl + +; bank 1 imports +.import draw_sprites, init_pulse_and_noise_channels +.import init_sound_code_vars, handle_sound_slots + +; bank 2 imports +.import level_headers, graphic_data_02 +.import alt_graphic_data_00, alt_graphic_data_01 +.import alt_graphic_data_02, alt_graphic_data_03 +.import alt_graphic_data_04 +.import level_2_4_boss_supertiles_screen_ptr_table +.import load_screen_enemy_data +.import set_players_paused_sprite_attr +.import set_player_sprite_and_attrs +.import exe_soldier_generation + +; bank 3 imports +.import level_1_nametable_update_supertile_data, level_1_nametable_update_palette_data +.import level_2_nametable_update_supertile_data, level_2_nametable_update_palette_data +.import level_3_nametable_update_supertile_data, level_3_nametable_update_palette_data +.import level_4_nametable_update_supertile_data, level_4_nametable_update_palette_data +.import level_5_nametable_update_supertile_data, level_5_nametable_update_palette_data +.import level_6_nametable_update_supertile_data, level_6_nametable_update_palette_data +.import level_7_nametable_update_supertile_data, level_7_nametable_update_palette_data +.import level_8_nametable_update_supertile_data, level_8_nametable_update_palette_data +.import level_2_4_nametable_update_supertile_data, level_2_4_boss_nametable_update_palette_data +.import level_2_4_boss_palette_data, run_end_level_sequence_routine +.import level_1_supertile_data, level_2_supertile_data, level_3_supertile_data +.import level_5_supertile_data, level_8_supertile_data +.import level_2_4_boss_supertile_data, level_2_4_tile_animation +.import level_6_tile_animation, level_7_tile_animation + +; bank 4 imports +.import graphic_data_01, graphic_data_03 +.import graphic_data_04, graphic_data_06 +.import graphic_data_08, graphic_data_09 +.import graphic_data_0a, graphic_data_0f +.import graphic_data_10, graphic_data_11 +.import graphic_data_12, graphic_data_13 +.import run_game_end_routine + +; bank 5 imports +.import load_demo_input_table +.import graphic_data_05, graphic_data_07 +.import graphic_data_0b, graphic_data_14 +.import graphic_data_17, graphic_data_18 +.import graphic_data_19, graphic_data_1a + +; bank 6 imports +.import short_text_pointer_table +.import graphic_data_0c, graphic_data_0d +.import graphic_data_0e, graphic_data_15 +.import graphic_data_16 +.import run_player_bullet_routines, check_player_fire + +; export labels for use by other banks +; labels need by bank 0 +; - needed for enemy routines +.export load_bank_3_update_nametable_supertile +.export load_bank_3_update_nametable_tiles, load_palettes_color_to_cpu +.export get_cart_bg_collision, wall_core_routine_05, boss_defeated_routine +.export enemy_routine_init_explosion, mortar_shot_routine_03 +.export set_enemy_delay_adv_routine, advance_enemy_routine, roller_routine_04 +.export shared_enemy_routine_03, enemy_routine_explosion +.export enemy_routine_remove_enemy, shared_enemy_routine_clear_sprite +.export set_enemy_routine_to_a, update_enemy_pos +.export update_enemy_x_pos_rem_off_screen, set_enemy_y_vel_rem_off_screen +.export set_outdoor_weapon_item_vel, add_scroll_to_enemy_pos +.export set_enemy_velocity_to_0, set_enemy_y_velocity_to_0 +.export set_enemy_x_velocity_to_0, reverse_enemy_x_direction +.export set_destroyed_enemy_routine, destroy_all_enemies +.export clear_supertile_bg_collision, set_supertile_bg_collision +.export set_supertile_bg_collisions, create_explosion_89 +.export create_two_explosion_89, create_enemy_for_explosion, level_boss_defeated +.export set_delay_remove_enemy, disable_bullet_enemy_collision +.export disable_enemy_collision, enable_enemy_player_collision_check +.export enable_bullet_enemy_collision, enable_enemy_collision +.export add_a_to_enemy_y_pos, add_a_to_enemy_x_pos, set_08_09_to_enemy_pos +.export add_with_enemy_pos, add_10_to_enemy_y_fract_vel +.export add_a_to_enemy_y_fract_vel, generate_enemy_a, generate_enemy_at_pos +.export add_4_to_enemy_y_pos, add_a_with_vert_scroll_to_enemy_y_pos +.export update_nametable_tiles_set_delay, draw_enemy_supertile_a_set_delay +.export draw_enemy_supertile_a, update_2_enemy_supertiles +.export update_enemy_nametable_tiles_no_palette, update_enemy_nametable_tiles +.export check_enemy_collision_solid_bg, init_vars_get_enemy_bg_collision +.export add_y_to_y_pos_get_bg_collision, add_a_y_to_enemy_pos_get_bg_collision +.export set_flying_capsule_y_vel, set_flying_capsule_x_vel +.export red_turret_find_target_player, player_enemy_x_dist +.export find_far_segment_for_x_pos, find_far_segment_for_a +.export set_enemy_falling_arc_pos, set_weapon_item_indoor_velocity +.export find_next_enemy_slot, clear_sprite_clear_enemy_pt_3 +.export clear_enemy_custom_vars, initialize_enemy, aim_and_create_enemy_bullet +.export bullet_generation, create_enemy_bullet_angle_a, set_bullet_velocities +.export aim_var_1_for_quadrant_aim_dir_01, aim_var_1_for_quadrant_aim_dir_00 +.export get_rotate_00, get_rotate_01 +.export get_rotate_dir, dragon_arm_orb_seek_should_move +.export get_quadrant_aim_dir_for_player, remove_enemy + +; labels needed by bank 2 +.export get_bg_collision, remove_all_enemies, find_next_enemy_slot_6_to_0 +.export find_bullet_slot + +; labels needed by bank 3 +.export set_graphics_zero_mode, set_a_as_current_level_routine + +; labels needed by bank 4 +.export advance_graphic_read_addr, decrement_delay_timer +.export init_APU_channels, init_game_routine_reset_timer_low_byte +.export load_A_offset_graphic_data, load_alternate_graphics +.export load_palette_indexes, play_sound +.export reset_delay_timer, run_routine_from_tbl_below +.export zero_out_nametables + +; labels needed by bank 6 +.export set_vel_for_speed_vars, set_bullet_routine_to_2 + +; labels needed by multiple banks +.export run_routine_from_tbl_below ; bank 2, 3, 4, 6 +.export init_APU_channels ; bank 0, 4 +.export get_bg_collision_far ; bank 0, 6 +.export play_sound ; bank 0, 1, 2, 4, 6 + +; Every PRG ROM bank starts with a single byte specifying which number it is +.byte $07 ; The PRG ROM bank number (7) + +; interrupt called when the NES starts up, or the reset button is pressed +reset_vector: + cld ; disable decimal mode (NES chip 2A03 doesn't use decimal mode) + sei ; disable interrupts + +wait_til_vblank: + lda PPUSTATUS ; read PPU status with the following bit layout -> VSO- ---- + bpl wait_til_vblank ; Wait until V is 1 (accumulator is negative), i.e. in vertical blank VBLANK + +vertical_blank_entry: + lda PPUSTATUS ; read PPU status with bit layout -> VSO- ---- + bpl vertical_blank_entry ; ensure still in VBLANK + lda #$00 ; clear accumulator + sta GAME_MODE ; set the game mode to normal #$00 (not demo) + jsr clear_ppu ; initialize PPU + ldx #$ff + txs ; initialize stack pointer location to $01ff, stack range is from $01ff down to $0100 (descending stack) + ; clear (zero out) memory range $0000-$01bf and $0200-$07df + lda #$01 ; high byte of max memory address to clear + ldx #$01 ; number of #$ff-sized blocks of memory to clear + ldy #$bf ; low byte of max memory address to clear + jsr clear_memory ; clear memory range $0000-$01bf + lda #$07 ; high byte of max memory address to clear + ldx #$05 ; number of #$ff-sized blocks of memory to clear + ldy #$df ; low byte of max memory address to clear + jsr clear_memory ; clear memory range $0200-$07df + ldx #$f0 + +@loop: + txa + cmp CPU_GRAPHICS_BUFFER,x ; compare A (#$f0) to cpu memory $07f0 + bne init_07f0_through_high_score ; branch if is A #$f0 not equal to memory address $07f0 + inx ; !(OBS) not sure if this line is ever executed + bne @loop ; !(OBS) not sure if this line is ever executed + beq init_APU_and_PPU ; !(OBS) not sure if this line is ever executed + +; initialize memory $07f0-$07ff to be #$f0 to #$ff respectively +init_07f0_through_high_score: + ldx #$f0 + +@loop: + txa + sta CPU_GRAPHICS_BUFFER,x ; initialize $07f0 to $f0, $07f1 to $f1, etc. until $07ff !(WHY?) + ; don't think this range is used ever $07f0-$07ff + inx + bne @loop + lda #$c8 ; default high score is 20,000 + sta HIGH_SCORE_LOW ; store low byte of high score in HIGH_SCORE_LOW + lda #$00 + sta HIGH_SCORE_HIGH ; store high byte of high score (#$00) + +init_APU_and_PPU: + lda #$00 + sta CPU_GRAPHICS_BUFFER + jsr init_APU + jsr init_APU_channels + jsr configure_PPU + +; run between NMI interrupts after nmi_start code finishes +; loop forever updating RANDOM_NUM before NMI +forever_loop: + lda FRAME_COUNTER ; load frame counter + adc RANDOM_NUM ; add the frame number to RANDOM_NUM + sta RANDOM_NUM ; update RANDOM_NUM to new result + jmp forever_loop + +; NMI entry point, beginning of vertical blanking interval. This happens once per video frame and is triggered by PPU. +; The PPU is available for graphics updates +; The NES will automatically clear the screen so you do not have to worry about trying to clear it with code. +; The end of all the game code will end at RTI (return from interrupt). +nmi_start: + php ; push processor status to stack #$02 bytes (NV--DIZC) + pha ; push A on to the stack + txa ; transfer X to A + pha ; push A on to the stack + tya ; transfer Y to A + pha ; push A on to the stack + lda PPUSTATUS ; reset PPU latch + ldy NMI_CHECK ; see if nmi interrupted previous frame's game loop + bne handle_sounds_set_ppu_scroll_rti ; branch if nmi occurred before game loop was completed + ; to skip game loop and instead just check for sounds to play and play them + ; set ppu scroll, and rti + jsr clear_ppu ; first frame, so clear/init PPU + sta OAMADDR ; set OAM address to #00 (DMA is used instead) + ldy #>OAMDMA_CPU_BUFFER ; setting OAMDMA to #$02 tells PPU to load sprite data from $0200-$02ff + sty OAMDMA ; write #$100 (256 decimal) bytes ($0200 to $02ff) of data to PPU OAM (the entire screen) + jsr write_palette_colors_to_ppu ; writes the colors for all the palettes defined in CPU memory to the PPU $3f00 to $3f1f + jsr write_cpu_graphics_buffer_to_ppu ; draw the graphic data in memory at CPU_GRAPHICS_BUFFER to the PPU + lda PPUMASK_SETTINGS ; load settings for PPUMASK (#$1e) + ldx PPU_READY ; every time configure_PPU is called, PPU_READY is set to #$05 + ; this confirms that at least 5 nmi interrupts have happened + ; since the last configure_PPU was called + beq @continue ; PPU_READY is #$00, so continue setup + dec PPU_READY ; decrement PPU load loop (starts at #$05) + beq @continue ; PPU_READY is now #$00, so continue setup + lda #$00 ; set PPUMASK to #$00 since PPU isn't ready + +@continue: + sta PPUMASK ; set PPU mask, either #$00 to clear or #$1e (from PPUMASK_SETTINGS) when PPU ready + jsr set_ppu_addr_to_nametables ; set the PPU write address to $2000 to write the pattern table tile data + inc NMI_CHECK ; entering important part of game loop, set to #$01 + ; if next NMI occurs before set back to #$00, then game engine knows logic was + ; interrupted before completing. This is not good but does regularly occur + ; at beginning of levels when clearing memory + ldy #$01 + jsr load_bank_number ; load bank 1 + jsr handle_sound_slots ; loop through sound slots and execute appropriate sound codes + jsr load_controller_state ; read controller for p1 and p2, stores results into memory + jsr exe_game_routine ; go into game routine loop + ldy #$01 + jsr load_bank_number ; load bank 1 + jsr draw_sprites ; bank 1 + jsr write_0_to_cpu_graphics_buffer + lda #$00 + sta NMI_CHECK + +remove_registers_from_stack_and_rti: + pla ; remove byte from stack + tay ; store in y + pla ; remove byte from stack + tax ; story in x + pla ; remove byte stack + plp ; set cpu flags from stack + +; end of CPU code execution for the frame +irq: + rti ; return to forever_loop until nmi is triggered again + ; rti pops the processor flags and then the program counter + ; then starts executing at that location + +; NMI_CHECK is non-zero, meaning the previous frame's game loop was interrupted +handle_sounds_set_ppu_scroll_rti: + lda PPUMASK_SETTINGS ; load settings for PPUMASK + ldx PPU_READY ; load PPU status (#$00 is ready) + beq @handle_sound ; if ready keep PPU mask setting of #$1e + lda #$00 ; clear PPUMASK + +@handle_sound: + sta PPUMASK + lda BANK_NUMBER ; get currently loaded switchable bank number + pha ; backup bank number + lda NMI_CHECK ; see if nmi interrupted while loading sound variables (play_sound) + bmi @continue ; jump if NMI_CHECK is negative to skip handling sound entry + ldy #$01 + jsr set_rom_bank_to_y ; swap PRG ROM bank to 01 + jsr handle_sound_slots ; loop through sound slots and execute appropriate sound codes + +@continue: + pla ; pull previous bank from stack + tay ; store in y + jsr set_rom_bank_to_y + jsr set_ppu_scroll + jmp remove_registers_from_stack_and_rti + +; initialize the audio processing unit +; NES APU has 5 channels, Contra uses 4 of them +; * disable DMC (data modulation channel) +; * enable noise channel (static sound) +; * enable triangle channel (triangle wave) +; * enable pulse 1 channel (pulse wave) +; * enable pulse 2 channel (pulse wave) +init_APU: + lda #$0f ; 0000 1111 in binary + sta APU_STATUS + lda #$c0 ; 1100 0000 in binary + sta APU_FRAME_COUNT ; set frame sequencer for frame counter to 5 step sequence (~192 Hz) + ; clear frame interrupt flag + rts + +; configures the PPU to the following settings +; base nametable address: $2000 +; VRAM address increment: add 1 going across +; 8x8 sprite pattern table address (ignored since using 8x16 sprites) +; background pattern table address: $1000 (right pattern table) +; sprite size: 8x16 pixels +; generate NMI at start of VBLANK +; actually fills nametable with sprites and palettes +configure_PPU: + lda #$05 ; set a to #$05 + sta PPU_READY ; store into PPU_READY, prevents writes to PPUMASK until 5 nmi_start executions + lda #$b0 ; set a to %1011 0000 + sta PPUCTRL_SETTINGS ; store a into $ff + sta PPUCTRL + lda #$05 + sta PPU_READY ; set PPU_READY to #$05 + rts + +; set PPU write address to $2000. This is the start of the nametables in the PPU +set_ppu_addr_to_nametables: + lda PPUSTATUS ; read PPUSTATUS to reset PPU latch + ; setting the PPUADDR takes two writes, one for high byte and one for low + lda #$20 ; load high byte of address + sta PPUADDR + lda #$00 ; load low byte of address + sta PPUADDR + +set_ppu_scroll: + lda PPUSTATUS ; clear bit 7 and address latch used by PPUSCROLL and PPUADDR + lda HORIZONTAL_SCROLL ; load horizontal component of the PPUSCROLL [#$0 - #$ff] + sta PPUSCROLL ; write X position + lda VERTICAL_SCROLL + sta PPUSCROLL ; write Y position + lda PPUCTRL_SETTINGS ; saved PPUCTRL settings (see configure_PPU) + sta PPUCTRL + rts + +clear_ppu: + lda #$00 ; setting the PPUADDR takes two writes, one for high byte and one for low + sta PPUADDR ; write $00 high byte + sta PPUADDR ; write $00 low byte + sta PPUCTRL ; clear PPUCTRL + sta PPUMASK ; clear PPUMASK + rts + +; clears blocks of CPU memory +; starts with a Y byte sized block of memory starting at A +; then clears #$ff * X byte-sized blocks of memory preceding A +clear_memory: + sta $01 ; set pointer to beginning of addresses to clear + lda #$00 ; set $00 to use to clear + sta $00 ; store #$00 to $00 + +@loop: + sta ($00),y ; clear memory address Y-bytes away from address stored in $00 and $01 + dey + cpy #$ff ; #FF is equivalent to -1 here, loop until Y is -1 + bne @loop + dec $01 ; decrement to next #$ff-sized block of memory to clear + dex ; decrement number of blocks to clear counter + bpl @loop ; clear the next #$ff block of bytes + rts + +; ROM address $c139 +; backs up the currently-loaded bank number into PREVIOUS_ROM_BANK ($07ec) +; updates the currently-loaded bank to Y +load_bank_number: + lda BANK_NUMBER ; load the currently-loaded bank number (address $8000) + ; first byte of every bank is the bank number + sta PREVIOUS_ROM_BANK ; save bank number to $07ec + +; updates the active PRG ROM bank to bank specified by y +; CPU address $c139 +; swaps out the ROM available to CPU for addresses $8000 to $bfff +; this is because contra is a UxROM mapper, which swaps the active +; bank when detecting a write to CPU address between $8000 and $ffff inclusively +; the bank swapped in is the bank of the lowest 4 bits +set_rom_bank_to_y: + lda prg_rom_banks,y ; grab the bank number (should match y) + sta prg_rom_banks,y ; set the active bank number + rts + +; loads the previously-loaded ROM bank specified in $07ec (PREVIOUS_ROM_BANK) +load_previous_bank: + ldy PREVIOUS_ROM_BANK + jmp set_rom_bank_to_y + +; loads bank 1 into switchable memory without losing values of A and Y +load_bank_1: + pha ; save a to stack + tya ; transfer y to a + pha ; save a (y) to stack + lda BANK_NUMBER ; load currently loaded switchable rom number + sta PREVIOUS_ROM_BANK_1 ; set PREVIOUS_ROM_BANK_1 to match loaded bank + ldy #$01 + jsr set_rom_bank_to_y ; load ROM BANK 1 + pla ; restore a (y) from stack + tay ; move a to y + pla ; restore a from stack + rts + +; loads the bank specified in PREVIOUS_ROM_BANK_1 without losing values of A and Y +; !(WHY?) not sure why there are 2 variables to store the previous bank, both used differently +local_previous_1_bank: + pha ; save a to stack + tya + pha ; save y to stack + ldy PREVIOUS_ROM_BANK_1 + jsr set_rom_bank_to_y + pla ; pull y from stack + tay + pla ; pull a from stack + rts + +; CPU address $c16b +; input +; * a - the sound code to play +play_sound: + pha ; push sound code to stack + lda NMI_CHECK ; load NMI_CHECK flag, should always be #$01 here + ora #$80 ; ensure most significant bit is set (1) + sta NMI_CHECK ; while bank 1 is loaded and inside init_sound_code_vars + pla ; pop sound code back from stack + jsr load_bank_1 + jsr init_sound_code_vars ; bank 1 + jsr local_previous_1_bank ; load PREVIOUS_ROM_BANK_1 + lda NMI_CHECK ; load NMI_CHECK flag, should always be #$81 here + and #$7f ; finished with init_sound_code_vars, clear bit 7 + sta NMI_CHECK ; reset NMI_CHECK flag back to #$01 + rts + +init_APU_channels: + sty $f7 ; backup Y in $f7 + ldy #$01 + jsr load_bank_1 + jsr init_pulse_and_noise_channels ; bank 1, sets pulse channel duty cycle, volume, and sweep data, mute noise channel + jsr local_previous_1_bank ; load PREVIOUS_ROM_BANK_1 + ldy $f7 ; restore Y back from $f7 + rts + +; draw super-tile $10 at position (a,y) +; redraws parts of the nametable for things like bridge explosions, +; nametable enemy explosions, animation (pill box sensor), etc. +; also used to draw palette colors for super-tiles +; input +; * a is x position of nametable super-tile in pixels +; * y is y position of nametable super-tile in pixels +; * $10 is the super-tile or palette index to draw (level_x_nametable_update_supertile_data/level_x_nametable_update_palette_data offset) +; If bit 7 clear, then update palette, if bit 7 set do not update palette +; output +; * carry flag - clear when successful, set when CPU_GRAPHICS_BUFFER is full +load_bank_3_update_nametable_supertile: + sta $f3 ; save a value before swapping banks + sty $f7 ; save y value before swapping banks + ldy #$03 ; y = #$03 + jsr load_bank_number ; load bank y (03) + lda $f3 ; restore a value + ldy $f7 ; restore y value + jsr update_nametable_supertile ; draw super-tile $10 at position (a,y) + jmp load_previous_bank ; load previous bank + +; load bank 3 and update_nametable_tiles +; indoor/base levels for drawing wall turrets, and changing to explosion when destroyed +; input +; * a is x position +; * y is y position +; * $10 (multiplied by #$05) is the index into the tile animation table to start drawing +; if bit 7 clear, then update palette, if bit 7 set do not update palette +; output +; * carry - clear when successful, set when CPU_GRAPHICS_BUFFER is full +load_bank_3_update_nametable_tiles: + sta $f3 ; backup a in $f3 + sty $f7 ; backup y in $f7 + ldy #$03 ; y = #$03 + jsr load_bank_number ; switch to bank y (03) + lda $f3 ; restore a from $f3 + ldy $f7 ; restore y from $f7 + jsr update_nametable_tiles + jmp load_previous_bank + +; various tasks with different banks +load_bank_6_run_player_bullet_routines: + ldy #$06 ; y = #$06 + jsr load_bank_number ; switch bank to 06 + jmp run_player_bullet_routines ; CPU address $b94a + +; switch to bank 6 and see if player is trying to shoot +; if so and player should be able to fire, then generate bullet +load_bank_6_check_player_fire: + ldy #$06 ; y = #$06 + jsr load_bank_number ; switch bank to 06 + jmp check_player_fire ; generate bullet if player is shooting and allowed to shoot + +load_bank_0_exe_all_enemy_routine: + ldy #$00 ; y = #$00 + jsr load_bank_number ; switch bank to 00 + jmp exe_all_enemy_routine + +; enemy generation +load_bank_2_load_screen_enemy_data: + ldy #$02 ; y = #$02 + jsr load_bank_number ; switch bank to 02 + jmp load_screen_enemy_data ; CPU address $b419 + +; get table pointer for level-specific enemy routines +; ensure enemy routines bank (bank 00) is loaded +load_bank_0_load_level_enemies_to_mem: + ldy #$00 ; y = #$00 + jsr load_bank_number ; switch bank to 00 + jmp load_level_enemies_to_mem ; load the pointer to the level-specific enemy routines to $80 + +; soldier generation +load_bank_2_exe_soldier_generation: + ldy #$02 ; y = #$02 + jsr load_bank_number ; switch bank to 02 + jmp exe_soldier_generation ; CPU address $b523 + +; handles scrolling for the level if currently scrolling +; including writing tiles to nametable, writing to the attribute table, and loading alternate graphics +; includes handling auto scroll from boss reveal or tank +load_bank_3_handle_scroll: + ldy #$03 ; y = #$03 + jsr load_bank_number ; switch bank to 03 + jmp handle_scroll ; handles scroll for level if currently scrolling + +; load bank 3 and execute init_lvl_nametable_animation +; output +; * zero flag - set when LEVEL_TRANSITION_TIMER has elapsed, clear otherwise +load_bank_3_init_lvl_nametable_animation: + ldy #$03 ; y = #$03 + jsr load_bank_number ; switch bank to 03 + jmp init_lvl_nametable_animation ; animate initial level nametable drawing + +; load alternate tiles if necessary +load_bank_2_alternate_tile_loading: + ldy #$02 ; y = #$02 + jsr load_bank_number ; switch bank to 02 + jmp alternate_tile_loading ; alternate tiles loading code + +load_level_graphics: + jmp load_current_level_graphic_data + +; loads the graphic data for the level specified by offset into level_graphic_data_tbl (A register) +load_A_offset_graphic_data: + jmp load_level_graphic_data + +load_bank_2_set_player_sprite: + ldy #$02 + jsr load_bank_number + jmp set_player_sprite_and_attrs ; set player sprite based on player state, level, and animation sequence + +; game paused, jump set_players_paused_sprite_attr +load_bank_2_set_players_paused_sprite_attr: + ldy #$02 + jsr load_bank_number ; load bank 2 + jmp set_players_paused_sprite_attr ; ensure player sprite attributes continue while paused, e.g. flashing while invincible, electrocuted, etc. + +; loaded from bank #6 +; write pattern tile (text) or palette information (color) to CPU offset CPU_GRAPHICS_BUFFER +; this is used when GRAPHICS_BUFFER_MODE is #$00, which defines the CPU_GRAPHICS_BUFFER format for text and palette data +; input +; * a - first six bits are index into the short_text_pointer_table +; when bit 7 set, write all blank characters instead of actual characters. Used for flashing effect +load_bank_6_write_text_palette_to_mem: + sta $f3 ; store the specific text/palette to load into $f3 + ldy #$06 + jsr load_bank_number ; load bank 6 + lda $f3 ; load_bank_number sets A to the bank number, so reset it back to the item to load + jmp write_text_palette_to_mem + +; game routines - pointer 6 +; runs at the end of the game after defeating the alien +; runs end of game routines, and end of game sequence routines +; melts screen, ending helicopter animation and credits +game_routine_06: + ldy #$04 ; y = #$04 + jsr load_bank_number ; switch bank to y (04) + jmp run_game_end_routine ; CPU address $b8b9 + +; loads bank five and execute procedure to simulate player input for demo +simulate_input_for_demo: + ldy #$05 ; y = #$05 + jsr load_bank_number ; switch bank to y (05) + jmp load_demo_input_table ; label from bank 5 + +load_bank_3_run_end_lvl_sequence_routine: + ldy #$03 ; y = #$03 + jsr load_bank_number ; switch bank to y (03) + jmp run_end_level_sequence_routine ; CPU address $bdfa + +; determine which game routine to run and run it +; checks if player presses start to early exit animation +exe_game_routine: + inc FRAME_COUNTER ; increment frame counter + lda GAME_ROUTINE_INDEX ; index into game_routine_pointer_table + beq run_game_routine ; run game_routine_00 if GAME_ROUTINE_INDEX is 0, run only once to initialize intro + cmp #$03 + bcs run_game_routine ; skip decrementing timer if GAME_ROUTINE_INDEX >= 3 + ; timer is used for only intro animation and when showing demos + jsr dec_theme_delay_check_user_input ; decrements animation timers, checks for early exit, player mode change etc, if not fall through + +; run game routine for specified GAME_ROUTINE_INDEX +run_game_routine: + lda GAME_ROUTINE_INDEX ; offset into game_routine_pointer_table to execute + jsr run_routine_from_tbl_below ; run routine a in the following table (game_routine_pointer_table) + +; pointer table to code to run for game routines ($0E bytes total) +; CPU address $c24d +game_routine_pointer_table: + .addr game_routine_00 ; CPU address $c25b (initial intro scrolling) + .addr game_routine_01 ; CPU address $c274 (play sound and load Bill and Lance, show menu, konami check) + .addr game_routine_02 ; CPU address $c2b1 (wait for input until timer expires, then start demo) + .addr game_routine_03 ; CPU address $c2c7 (player pressed start to start game (1p or 2p)) + .addr game_routine_04 ; CPU address $c2f4 (clears player state and sprite data) + .addr game_routine_05 ; CPU address $ce30 (run level_routine execution, for actual playing of level, or demo) + .addr game_routine_06 ; CPU address $c223 (runs at the end of the game after defeating the alien) + +; The 1st game routine game_routine_pointer_table +; this label initializes the intro scrolling effect +game_routine_00: + jsr zero_out_nametables ; initialize nametables 0 and 1 to zeroes + jsr load_intro_graphics ; load the graphic data (pattern, nametable, and palette) to ppu, as well as palette data to cpu + ldy #$00 ; y = #$00 + sty KONAMI_CODE_NUM_CORRECT ; initialize konami check to #$0 (see konami_input_check) + iny + sty HORIZONTAL_SCROLL ; initialize the horizontal scroll offset to #$1 + iny + sty DELAY_TIME_HIGH_BYTE ; initialize delay high byte to #$02 (used for various delays) + lda #$b1 ; %1011 0001 (set nametable to $2400) + sta PPUCTRL_SETTINGS ; store PPUCTRL settings for next update of PPUCTRL + jmp inc_routine_index_set_timer + +; table for y positions of intro screen cursor +; same table is used for "CONTINUE"/"END" screen during game over +player_select_cursor_pos: + .byte $a2,$b2 + +; The 2nd game routine game_routine_pointer_table +; run once per frame for multiple frames while scrolling to right until intro screen is shown +; when scrolling complete, plays intro "explosion" sound and loads player select menu +game_routine_01: + jsr konami_input_check + lda HORIZONTAL_SCROLL ; load horizontal component of the PPUSCROLL [#$0 - #$ff] + beq game_routine_01_scroll_complete ; if scroll complete, show Bill and Lance and play sound + inc HORIZONTAL_SCROLL ; add 1 to the horizontal scroll offset + bne game_routine_01_exit ; if scrolling animation isn't complete, continue scrolling next frame + jsr load_intro_palette2_play_intro_sound ; scrolling complete, load 2nd intro background palette and play explosion sound + +; write the text "PLAY SELECT", "1 PLAYER", player select cursor, etc +; move to next game_routine once timer elapses +game_routine_01_scroll_complete: + lda #$2c ; a = #$2c (x position of cursor in intro) + sta SPRITE_X_POS ; store x position of cursor for player select (first sprite) + lda #$aa ; sprite_aa: player selector cursor (yellow falcon) + sta CPU_SPRITE_BUFFER ; store sprite number in CPU buffer + ldx PLAYER_MODE ; number of players (0 = 1 player) + lda player_select_cursor_pos,x ; load y position of cursor for player select + sta SPRITE_Y_POS ; store y position of cursor for player select + lda #$00 ; a = #$00 + sta SPRITE_ATTR ; reset sprite effect for player + lda #$ab ; sprite_ab: Bill and Lance's hair and shirt + sta CPU_SPRITE_BUFFER+1 ; store next sprite to load + lda #$b3 ; a = #$b3 (x position for sprite_ab) + sta SPRITE_X_POS+1 ; store x position for sprite_ab + lda #$77 ; a = #$77 (y position for sprite_ab) + sta SPRITE_Y_POS+1 ; store y position for sprite_ab + jsr decrement_delay_timer ; decrease delay and check if it reaches 0 + bne game_routine_01_exit ; timer not complete, wait + jmp increment_game_routine ; timer delay complete, increase game_routine to game_routine_02 + +game_routine_01_exit: + rts + +; The 3rd game routine (see game_routine_pointer_table) +; * loads demo level and plays the level +; * stops level when demo timer elapsed and loads next level to demo (only levels 0-2) +; * resets GAME_ROUTINE_INDEX to #$0 between demo levels to reshow intro scroll and player select +game_routine_02: + ldx GAME_ROUTINE_INIT_FLAG ; determine if game routine has been "initialized" + bne @continue ; if GAME_ROUTINE_INIT_FLAG is already 1, no need to load level, continue with demo level + inc GAME_ROUTINE_INIT_FLAG ; set GAME_ROUTINE_INIT_FLAG to indicate that demo level is loaded + jmp set_next_demo_level ; set memory addresses in preparation for next demo level + +@continue: + jsr run_level_routine_for_demo ; execute level routine with offset of current value of LEVEL_ROUTINE_INDEX + lda DEMO_LEVEL_END_FLAG ; whether or not the demo for the level is complete + beq game_routine_02_exit ; demo not complete, continue showing demo + lda #$00 ; demo of level complete, move to next level to demo + sta GRAPHICS_BUFFER_MODE ; start to read from beginning of CPU_GRAPHICS_BUFFER (see write_cpu_graphics_buffer_to_ppu) + beq set_game_routine_index_to_a ; reset GAME_ROUTINE_INDEX 0 to replay scrolling effect and player selection + +; The 4th game routine (see game_routine_pointer_table) +; player pressed start to start game (1p or 2p) +game_routine_03: + ldx GAME_ROUTINE_INIT_FLAG ; load whether game_routine_03 has been initialized + bne @continue ; level 1 data already set, continue + lda #$00 + sta DEMO_MODE ; set DEMO_MODE to off + lda #$40 + bne set_game_routine_init_flag ; (always jump due to lda in previous line) set timer low byte to #$40 + +@continue: + jsr dec_intro_theme_delay + lda DELAY_TIME_LOW_BYTE ; various delays (low byte) + beq @intro_timer_elapsed ; if the timer low byte is complete, jump + dec DELAY_TIME_LOW_BYTE ; various delays (low byte) + +@intro_timer_elapsed: + ora INTRO_THEME_DELAY ; combine DELAY_TIME_LOW_BYTE with the intro theme (with explosion) sound delay + beq increment_game_routine ; if both the intro theme and the delay timer are #$00 (elapsed), increment to next game routine #$04 + lda #$01 ; a = #$01 (text_1_player) + clc ; clear carry in preparation for addition + adc PLAYER_MODE ; number of players (0 = 1 player) + ; if 2 player mode, index is updated to text_2_players + sta $00 ; store player mode in $00 + lda #$08 ; a = #$08 + and FRAME_COUNTER ; flash #$08 frames at a time + asl + asl + asl + asl ; if FRAME_COUNTER bit 3 was set, then bit 7 is set here + ; indicating to blank the text (flashing animation) + ora $00 ; merge with short_text_pointer_table offset (#$01 or #$02) + jmp load_bank_6_write_text_palette_to_mem ; flash "1 PLAYER" or "2 PLAYER" depending on PLAYER_MODE + +; The 5th game routine (see game_routine_pointer_table) +game_routine_04: + jsr init_score_player_lives ; clear memory addresses $0028 to $00f0 then CPU_SPRITE_BUFFER to CPU_GRAPHICS_BUFFER + jmp increment_game_routine + +init_game_routine_reset_timer_low_byte: + lda #$80 ; a = #$80 + +set_game_routine_init_flag: + sta DELAY_TIME_LOW_BYTE ; various delays (low byte) + inc GAME_ROUTINE_INIT_FLAG ; set that the routine has been initialized + ; for game over routines, increments GAME_END_ROUTINE_INDEX (same memory address) + +; also fall through from game_routine_03 +game_routine_02_exit: + rts + +; sets low byte of delay timer to #$80 and increments game routine +inc_routine_index_set_timer: + lda #$80 ; set default delay (low byte) for next routine (game_routine_01 or level_routine_06) + sta DELAY_TIME_LOW_BYTE ; various delays (low byte) + +increment_game_routine: + inc GAME_ROUTINE_INDEX ; move to the next game_routine + +; called every time the game_routine index is incremented +init_game_routine_flags: + lda #$00 + sta DEMO_LEVEL_END_FLAG ; clear DEMO_LEVEL_END_FLAG + sta GAME_ROUTINE_INIT_FLAG ; reset GAME_ROUTINE_INIT_FLAG to #$00 + rts + +; update GAME_ROUTINE_INDEX to A +; reset delay timer to #$0240 +set_game_routine_index_to_a: + sta GAME_ROUTINE_INDEX + jsr reset_delay_timer ; reset 2-byte delay timer to #$0240 + bne init_game_routine_flags + +; decrement delay timer +; zero flag set (checked) when the timer has elapsed, otherwise zero flag will not be set +decrement_delay_timer: + lda DELAY_TIME_LOW_BYTE ; load the low byte of the delay timer + ora DELAY_TIME_HIGH_BYTE ; OR it together with high byte + beq @exit ; all bits both high and low byte are #$0, exit with #$0 in a register, zero flag set + dec DELAY_TIME_LOW_BYTE ; decrease delay (loops below #$00 to #$ff) + bne @exit ; low byte isn't #$0, exit with zero flag clear + lda DELAY_TIME_HIGH_BYTE ; low byte was #$0, check high byte + beq @exit_z_flag_clear ; high byte is #$0 as well, exit with #$01 in a register, zero flag clear + dec DELAY_TIME_HIGH_BYTE ; high byte wasn't #$0, subtract 1 from it + +@exit_z_flag_clear: + lda #$01 ; ensures the zero flag is clear + +@exit: + rts + +; set or reset delay before demo begins +; only if the intro screen is forced by pressing start/select +; NTSC is about #3c frames per second +; PAL is close to #$32 frames per second +; delay timer is strange in that once the high byte goes from #$01 to #$00, the low byte isn't rest to #$ff +; this means that although the timer is set to #$0240, the delay is only ~5 seconds (#$140 frames) and not ~9 seconds +reset_delay_timer: + ldx #$40 + stx DELAY_TIME_LOW_BYTE ; set low byte of timer to #$40 + ldx #$02 + stx DELAY_TIME_HIGH_BYTE ;set high byte of timer to #$02 + rts + +; checks if current input is part of Kazuhisa Hashimoto's famous Konami code (30-lives code) +; if completed input successfully, set KONAMI_CODE_STATUS to #$01 +konami_input_check: + ldy KONAMI_CODE_NUM_CORRECT ; load the number of successful inputs of Konami code + bmi konami_code_exit ; if #$ff (invalid Konami input), exit + lda CONTROLLER_STATE_DIFF ; buttons pressed (only care on input change so held button doesn't affect code) + and #$cf ; only care about input from d-pad and A/B buttons (not select nor start) + beq konami_code_exit ; if no input detected, exit + cmp konami_code_lookup_table,y ; compare with Konami code sequence at index y + beq konami_input_index_correct ; on success, goto konami_input_index_correct + lda #$ff ; incorrect sequence for Konami code + sta KONAMI_CODE_NUM_CORRECT ; since incorrect set KONAMI_CODE_NUM_CORRECT (number of successful inputs) to $ff + rts + +konami_input_index_correct: + iny ; add to number of successfully entered Konami code inputs + sty KONAMI_CODE_NUM_CORRECT ; store in KONAMI_CODE_NUM_CORRECT + cpy #$0a ; Konami code is 10 inputs, compare against how many successfully entered + bcc konami_code_exit ; Konami code not yet fully entered, exit + lda #$01 ; Konami code successfully entered, set flag to $01 + sta KONAMI_CODE_STATUS ; store success flag in memory + +konami_code_exit: + rts + +; table for Konami code (30-lives code) - up up down down ... +konami_code_lookup_table: + .byte $08,$08,$04,$04,$02,$01,$02,$01,$40,$80 + +; reads the controller for p1 and p2 +; ultimately stores results into CONTROLLER_STATE,x and CONTROLLER_STATE_DIFF,x +; due to DMC channel DPCM (Delta Pulse Coded Modulation) bug in the APU, input is read twice to confirm +; if the inputs match then it is assumed to be a valid read, otherwise, uses last known good read +load_controller_state: + jsr read_controller_state + lda $04 + sta $00 ; store p1 input in $00 in CPU memory + lda $05 + sta $01 ; store p2 input in $01 in CPU memory + jsr read_controller_state ; re-read input to confirm it was not affected by DMC channel DPCM bug + ldx #$01 ; start with player 2 + +ensure_input_valid: + lda $04,x ; read player's input from second attempt to read + cmp $00,x ; compare to 1st attempt to read input + beq @continue ; if input matches continue, the read input is valid + lda CTRL_KNOWN_GOOD,x ; read input is invalid, load last good value into a register + sta $04,x ; store previous good input into $04, can't trust just-read player input + +@continue: + dex + bpl ensure_input_valid ; move from p1 to p1 and ensure p1's input is valid + lda PLAYER_MODE_1D ; see player_mode_1d_table (#$01 or #$07) + and #$04 ; see if 2 player or single player (#$07 will have #$04 bit set) + bne write_input ; jump to store input to cpu memory if 2 player (PLAYER_MODE = #$01) + lda $04 ; single player mode, merge both controller inputs into single input + ; this allows the player to play 1 player with the 2nd controller port + ; or even have both players play as the same character! + ora $05 ; combine with p2 input + sta $04 ; store result into p1 input + +; store controller input for both players +write_input: + ldx #$01 + +; set the new input to memory for use in code +; also sets the differences between last input and new input for use in code +set_player_input: + lda $04,x ; read player input + tay ; move input into Y + eor CTRL_KNOWN_GOOD,x ; find the differences between previous known-good input and new input + and $04,x ; set a to only have differences in input between last known-good and new input + sta CONTROLLER_STATE_DIFF,x ; store input differences value into memory + sty CONTROLLER_STATE,x ; store new known-good input into $f1 + sty CTRL_KNOWN_GOOD,x ; store new known-good input into CTRL_KNOWN_GOOD (used only for controller input code) + dex ; move from player 2 to player 1 + bpl set_player_input ; read player 1 input + rts + +; reads the p1 and p2 controllers +; stores the inputs in a bit field in $04 and $05 respectively +; from msb to lsb: A, B, select, start, up, down, left, right +; sets and immediately clears strobe bit to read from controllers +read_controller_state: + ldx #$01 + stx CONTROLLER_1 ; set the strobe bit so controller input for both controllers can be read + dex + stx CONTROLLER_1 ; clear strobe bit before starting controller read + ldy #$08 ; loop counter to go through #$08 inputs + ; A, B, select, start, up, down, left, right + +; read controller input for individual button press +; pushing each entry into the resulting byte for each controller input +; this looks at both bit 0 and bit 1 from the NES to determine input +; this means the game supports both the standard controller as well as a Famicom expansion port controller +read_controller_button: + lda CONTROLLER_1 ; read controller input to determine if button is pressed + ; Contra is concerned with the 2 least significant bits (NES and Famicom inputs) + sta $07 ; store input value in $07 + lsr ; move lsb specifying if button is pressed for a standard controller into carry flag + ora $07 ; or the original value with the shifted value + ; this is essentially also checking if bit 1 (Famicom expansion port controller) is set + lsr ; move bit representing whether the button is pressed to the carry flag + rol $04 ; shift carry flag (button input flag) onto player 1 controller input bit-field + ; $04 by pushing the button state bit to the next bit + lda CONTROLLER_2 ; do the same thing for player 2 controller + sta $07 ; store input value in $07 + lsr ; move lsb specifying if button is pressed for a standard controller into carry flag + ora $07 ; or the original value with the shifted value + ; this is essentially also checking if bit 1 (Famicom expansion port controller) is set + lsr ; move bit representing whether the button is pressed to the carry flag + rol $05 ; shift carry flag (button input flag) onto player 1 controller input bit-field + ; $05 by pushing the button state bit to the next bit + dey ; decrement button loop counter + bne read_controller_button ; loop to see if next button is pressed + rts ; finished reading controller inputs. $04 and $05 contain button state + +; decrements intro theme delay timer, and checks if player pressed start or select +; if so, stop demo and show player select UI +dec_theme_delay_check_user_input: + jsr dec_intro_theme_delay ; decrement intro theme delay timer + lda CONTROLLER_STATE_DIFF ; controller 1 buttons pressed + and #$30 ; select and start buttons + beq timer_exit ; if neither start nor select pressed, exit to continue animation + jsr reset_delay_timer ; reset 2-byte delay timer to #$0240 + ldx GAME_ROUTINE_INDEX + cpx #$01 ; check if displaying a demo (in game_routine_02) + bne stop_demo_load_player_select_UI ; not in player select UI, stop demo and show player select UI + ldx HORIZONTAL_SCROLL ; load horizontal component of the PPUSCROLL [#$0 - #$ff] + bne load_intro_palette2_play_intro_sound ; if intro animation scroll wasn't complete, load graphics palette and play intro theme + and #$20 ; see if select button is pressed + bne player_mode_change ; if select was pressed, update the cursor to point to either 1 PLAYER or 2 PLAYERS + lda #$03 ; start button was pressed, set GAME_ROUTINE_INDEX to #$03 (start new game) + jmp set_game_routine_index_to_a ; go to next game routine + +; swaps player mode from 1 PLAYER (#$00) to 2 PLAYER (#$01) or vice versa +player_mode_change: + inc PLAYER_MODE ; add one to number of players (will correct if more than #$02 players below) + lda #$02 + sec ; set the carry flag for the next subtract statement + sbc PLAYER_MODE ; subtract player mode from #$02 + bne timer_exit ; player mode was #$00 and now is #$01, simply exit + sta PLAYER_MODE ; player mode was #$02, set PLAYER_MODE so it is #$00 (#$01 player) + +timer_exit: + rts + +; user has pressed start or select while intro scrolling +; skip scrolling animation and load player select UI +stop_demo_load_player_select_UI: + lda #$00 + sta GRAPHICS_BUFFER_MODE + jsr zero_out_nametables + jsr load_intro_graphics + jsr load_intro_palette2_play_intro_sound + lda #$01 + jmp set_game_routine_index_to_a ; set GAME_ROUTINE_INDEX to #$01 + +; loads the 2nd intro palette for when Bill and Lance are on screen +; also plays the intro explosion sound +load_intro_palette2_play_intro_sound: + lda #$00 + sta HORIZONTAL_SCROLL ; set the scroll to #$00 (completed) for next frame so player select UI is shown + lda #$b0 + sta PPUCTRL_SETTINGS ; set nametable to $2000 next update of PPUCTRL + lda #$a4 + sta INTRO_THEME_DELAY ; set intro theme delay timer to #$a4 (~5 seconds) + lda #$04 ; set background palettes for when Bill and Lance on screen (intro_background_palette2) + jsr load_bank_6_write_text_palette_to_mem ; write the palette data to CPU_GRAPHICS_BUFFER in CPU memory + lda #$26 ; play intro explosion sound + jmp play_sound + +; delay INTRO_THEME_DELAY on odd frames +dec_intro_theme_delay: + lda FRAME_COUNTER ; load frame counter + and #$01 ; only care about least significant bit + bne timer_exit ; if last bit is not 0 (even frame), jump to timer_exit + lda INTRO_THEME_DELAY + beq timer_exit ; if INTRO_THEME_DELAY is #$0 then jump to timer_exit + dec INTRO_THEME_DELAY ; decrement from delay + rts ; exit + +set_next_demo_level: + jsr clear_memory_3 ; clear $0028 to $00f0 then CPU_SPRITE_BUFFER up to CPU_GRAPHICS_BUFFER + lda #$07 ; see player_mode_1d_table + sta PLAYER_MODE_1D ; set to #$07 for 2 player + lda #$00 ; a = #$00 + sta FRAME_COUNTER ; reset frame counter + sta RANDOM_NUM ; set randomizer to #$00 + lda DEMO_LEVEL ; load level of demo to first level + cmp #$03 ; compare against 4th demo level + bcc @continue ; branch if less than 3 + lda #$00 ; reset DEMO_LEVEL back to 0 if DEMO_LEVEL >= 3 + ; only levels 0 to 2 are demoed + +@continue: + sta DEMO_LEVEL ; level of demo mode + sta CURRENT_LEVEL ; current level (0 = level 1) + inc DEMO_LEVEL ; increment level of demo mode + lda #$62 ; a = #$62 + sta P1_NUM_LIVES ; player 1 lives + sta P2_NUM_LIVES ; player 2 lives + rts + +; clears certain level and player data +; initializes player score and number of lives +init_score_player_lives: + jsr clear_memory_3 ; clear $0028 to $00f0 then CPU_SPRITE_BUFFER up to CPU_GRAPHICS_BUFFER + sta DEMO_LEVEL ; level of demo mode + lda #$03 ; a = #$03 + sta NUM_CONTINUES ; continues remaining + +reset_players_score: + ldx #$03 ; x = #$03 + lda #$00 ; a = #$00 + +clear_score_byte: + sta PLAYER_1_SCORE_LOW,x ; clear player 1 and player 2 scores + dex + bpl clear_score_byte + sta DEMO_MODE ; ensure demo mode is #$00 (not in demo mode) + sta P1_GAME_OVER_STATUS ; set game over status for p1 to #$0 (not in game over state) + ldx PLAYER_MODE ; load number of players #$00 is 1 player, #$01 is 2 player + lda player_mode_1d_table,x ; #$01 for 1 player, #$07 for 2 player + sta PLAYER_MODE_1D + lda p2_game_over_status_tbl,x ; load initial player 2 game over status + sta P2_GAME_OVER_STATUS ; set to 1 when 1 player game; set to 0 if 2 players are playing + +init_player_lives: + lda #$02 ; start of with #$02 lives + ldy KONAMI_CODE_STATUS ; 30-lives code switch ($01 = code activated) + beq init_player_num_lives ; if KONAMI_CODE_STATUS is not set, then just set 2 lives + lda #$1d ; KONAMI_CODE_STATUS active so set lives to #$1d (29 decimal) + +; set the player number of remaining lives to either #$02 or #$1d depending if Konami code used +; sets default score required for extra lives as well +init_player_num_lives: + sta P1_NUM_LIVES,x ; if X is 1, then set P2_NUM_LIVES to accumulator (a is either #$02 or #$1d) + dex ; decrement player number + bpl init_player_lives ; if more another player to set score, jump + lda #$c8 ; set default high score. #$c8 is 200 decimal + sta EXTRA_LIFE_SCORE_LOW ; starting score for extra life (20,000) + sta $3e ; player 2 default score for extra life + lda #$00 + sta EXTRA_LIFE_SCORE_HIGH ; clear high byte of score for extra life + sta KONAMI_CODE_NUM_CORRECT ; clear number of successful inputs to Konami code + rts + +; a lookup of whether 1 player game or 2 player game +; first byte #$00 is when PLAYER_MODE = #$00 (1 player) +; second byte #$07 is when PLAYER_MODE = #$01 (2 player) +player_mode_1d_table: + .byte $01,$07 + +; initial value for P2_GAME_OVER_STATUS +; set to #$01 for 1 player game +; set to #$00 for 2 player game +p2_game_over_status_tbl: + .byte $01,$00 + +; clear memory addresses $0028 to $00f0 then CPU_SPRITE_BUFFER up to CPU_GRAPHICS_BUFFER +clear_memory_3: + ldx #$28 + +; clear x to #$f0 bytes +; then clear CPU_SPRITE_BUFFER ($300) up to CPU_GRAPHICS_BUFFER ($700) +clear_memory_starting_a_x: + lda #$00 + +@loop: + sta $00,x + inx + cpx #$f0 + bne @loop + ; clear memory from CPU_SPRITE_BUFFER ($300) up to CPU_GRAPHICS_BUFFER ($700) + ldx #$07 ; ending high byte of CPU memory to clear (exclusive) + ldy #$03 ; starting high byte of CPU memory to clear + sty $01 + sta $00 + ldy #$00 + +; clear blocks of memory specified by the 2-byte $00 address +; clears until the memory address $X00, specified by the x register +; in this case clear memory from CPU_SPRITE_BUFFER to $06FF +clear_memory_block: + sta ($00),y + iny + bne clear_memory_block + inc $01 + cpx $01 + bne clear_memory_block + +add_player_score_exit: + rts + +; add enemy points to player score in memory +; determines if extra life is awarded and awards if necessary +; determines if high score is met and updates if necessary +; y is player number: either $00 (player 1) or $01 (player 2) +; $00 (low byte) and $01 (high byte) contain the score to add +; $01 is always #$00 +add_player_low_score: + lda DEMO_MODE ; #$00 not in demo mode, #$01 demo mode on + bne add_player_score_exit ; exit when in demo mode + lda #$00 ; a = #$00 + sta $01 ; set high byte of score to #$00 + +; add enemy points to player score in memory +; determines if extra life is awarded and awards if necessary +; determines if high score is met and updates if necessary +; y is player number: either $00 (player 1) or $01 (player 2) +; $00 (low byte) and $01 (high byte) contain the score to add +add_player_score: + tya ; transfer player number to a + sta $02 ; store player number in $02 + asl ; double since each player has 2 bytes representing score + tay ; transfer offset to y + lda $00 ; load low byte of score to add to player score + adc PLAYER_1_SCORE_LOW,y ; add low byte of score to add to player score (low byte) + sta PLAYER_1_SCORE_LOW,y ; store updated player score (low byte) + lda $01 ; load high byte of score to add to player score (always #$00 or #$50) + adc PLAYER_1_SCORE_HIGH,y ; add high byte of score to add to player score (high byte) + bcc @continue ; continue if no overflow occurred + lda #$ff ; overflow occurred in high byte, player maxed out score set low byte to #$ff + sta PLAYER_1_SCORE_LOW,y ; player score (low byte) + +@continue: + sta PLAYER_1_SCORE_HIGH,y ; store updated player score (high byte) + ldx $01 ; load high byte of player score to add + beq @set_if_extra_life ; branch if not special #$a0 score (all other scores have #$00 for high byte) + lda #$88 ; #$88 will cause a subtraction of #$50 as the extra life score low add byte + clc ; clear carry so always jump + bcc @set_extra_life_inc_num_lives ; always jump + +@set_if_extra_life: + cmp EXTRA_LIFE_SCORE_HIGH,y ; player score for extra life - high byte + bcc @set_if_new_high_score ; player did not get extra life, skip to check if player got new high score + bne @extra_life_logic ; score greater than necessary to get extra life + lda PLAYER_1_SCORE_LOW,y ; load player score low byte + cmp EXTRA_LIFE_SCORE_LOW,y ; player score for extra life - low byte + bcc @set_if_new_high_score ; player score less than EXTRA_LIFE_SCORE_LOW, no need to check if extra life + +; every time an extra life is obtained, #$12c is added to points required to get next extra life (300 decimal, 30,000 in game score) +; once score reaches #$7500 (2,995,200 in game score), no more extra lives are awarded +; if adding special score code #$0a, then the next extra life is given and number of points needed for next extra life is unchanged +; because #$1388 points are added to the extra life score, so distance until next 30,000 points is kept the same +@extra_life_logic: + lda EXTRA_LIFE_SCORE_HIGH,y ; player score for extra life - high byte + cmp #$75 ; compare EXTRA_LIFE_SCORE_HIGH to #$75 + bcs @set_if_new_high_score ; EXTRA_LIFE_SCORE_HIGH > #$75, score too high, don't give any more extra lives + lda #$2c ; a = #$2c (12c = 300 decimal) (high byte will be set to #$01 a few lines down) + +@set_extra_life_inc_num_lives: + adc EXTRA_LIFE_SCORE_LOW,y ; add 300 decimal to low byte of score required to get extra life + sta EXTRA_LIFE_SCORE_LOW,y ; player score for extra life - low byte + lda #$01 ; a = #$01 + ldx $01 ; determine if high byte is set + beq @continue_inc_num_lives ; branch if not special #$a0 score (all other scores have #$00 for high byte) + lda #$13 ; a = #$13 + +@continue_inc_num_lives: + adc EXTRA_LIFE_SCORE_HIGH,y ; player score for extra life - high byte + bcc @inc_num_lives + lda #$ff ; a = #$ff + sta EXTRA_LIFE_SCORE_LOW,y ; maxed out extra life high score, max out low byte + +@inc_num_lives: + sta EXTRA_LIFE_SCORE_HIGH,y ; player score for extra life - high byte + ldx $02 ; load the player number + inc P1_NUM_LIVES,x ; if X is $01, then set P2 number of lives + lda P1_NUM_LIVES,x ; load incremented number into memory + cmp #$63 ; compare to #$99 lives + bcc @set_num_lives ; can't have more than 99 lives + lda #$63 ; a = #$63 (63 = 99 decimal) + +@set_num_lives: + sta P1_NUM_LIVES,x ; number of lives + lda $01 + bne @set_if_new_high_score ; don't play sound for special #$a0 score code + lda #$20 ; a = #$20 (sound_20) + jsr play_sound ; play extra life sound sound + +@set_if_new_high_score: + lda PLAYER_1_SCORE_HIGH,y ; player score (high byte) + cmp HIGH_SCORE_HIGH ; high score (high byte) + bcc score_exit ; exit if no need to update high score score + bne @set_high_score ; high byte high score is greater, update high score score + lda PLAYER_1_SCORE_LOW,y ; player score (low byte) + cmp HIGH_SCORE_LOW ; high score (low byte) + bcc score_exit ; don't update high score score if player score isn't bigger than high score + +@set_high_score: + lda PLAYER_1_SCORE_LOW,y ; load player score low byte + sta HIGH_SCORE_LOW ; set new high score low byte to player score low byte + lda PLAYER_1_SCORE_HIGH,y ; load player score high byte + sta HIGH_SCORE_HIGH ; set new high score high byte to player score high byte + +score_exit: + rts + +; redraws parts of the nametable for things like bridge explosions, +; nametable enemy explosions, animation (pill box sensor), etc. +; also used to draw palette colors for super-tiles +; input +; * a is x position of nametable super-tile in pixels +; * y is y position of nametable super-tile in pixels +; * $10 is the super-tile or palette index to draw (level_x_nametable_update_supertile_data/level_x_nametable_update_palette_data offset) +; output +; * carry flag - clear when successful, set when CPU_GRAPHICS_BUFFER is full +update_nametable_supertile: + sta $11 ; store the x position (in pixels) of the location to redraw the nametable in $11 (except bit 7) + lda GRAPHICS_BUFFER_OFFSET ; read current offset + cmp #$40 ; see if graphics buffer is already full + bcs score_exit ; exit if offset is greater than or equal to #$40 + jsr set_ppu_addresses_in_mem ; determines attribute table PPU address, $14 (low) and $15 (high) + ; determines PPU nametable write address, $0c (low) and $0d (high) + ; for x ($11), y (y) coordinates + ; $00 is set to non zero if should update palette, #$00 for nametable update only + ldx GRAPHICS_BUFFER_OFFSET + lda CURRENT_LEVEL ; current level + ldy LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor + bpl @continue ; outdoor levels use level-specific super-tiles + lda #$08 ; indoor (base) levels use a shared super-tile set (level 2 and 4) + +@continue: + asl ; each address is 2 bytes, so double + asl ; each level has both super tile data and pattern data, so double + tay ; transfer offset into y + lda nametable_update_data_ptr_tbl,y ; load low byte of super-tile data address + sta $16 ; store in $16 + lda nametable_update_data_ptr_tbl+1,y ; load high byte of super-tile data address + sta $17 ; store in $17 + lda $0f ; determines if need to update the palette (#$00 meaning palette update is required, and needs to be added to CPU_GRAPHICS_BUFFER) + bne write_update_supertile_to_cpu ; go ahead and write the entire new super-tile bytes to the CPU_GRAPHICS_BUFFER, with no palette update instructions + lda nametable_update_data_ptr_tbl+2,y ; need to update palette, prep to write to CPU_GRAPHICS_BUFFER. load low byte of palette data address + sta $0e ; store low byte in $0e + lda nametable_update_data_ptr_tbl+3,y ; load high byte of palette data address + sta $0f ; store high byte in $0f + ldy $10 ; load level_X_nametable_update_palette_data read offset + lda $00 + bne update_supertile_palette ; if $00 is not #$00, then branch, updates palette based on super-tile data for level instead of from nametable_update_data_ptr_tbl + jsr set_graphics_buffer_header ; set CPU_GRAPHICS_BUFFER to write one tile to PPU at PPU address specified in $14 and $15 + lda ($0e),y ; read palette data byte for the super-tile + sta CPU_GRAPHICS_BUFFER,x ; write palette data byte to CPU_GRAPHICS_BUFFER (palette for entire super-tile) + inx + +; updates/overwrites a single super-tile on the nametable +write_update_supertile_to_cpu: + lda #$00 ; a = #$00 + sta $11 ; clear out address high byte overflow counter + lda $10 ; load level_X_nametable_update_supertile_data read offset + asl ; each entry is #$10 bytes, multiply by #$10 + asl ; keeping track of overflow + rol $11 + asl + rol $11 + asl + rol $11 + adc $16 ; add to nametable_update_data_ptr_tbl high byte + sta $16 ; PPU write address low byte + lda $11 ; load any overflow + adc $17 ; add to high byte of level_x_nametable_update_supertile_data offset + sta $17 ; PPU write address high byte + lda #$01 + sta CPU_GRAPHICS_BUFFER,x ; set VRAM address increment to 0, meaning to add #$1 every write to PPU (write horizontally) + inx + lda #$04 ; a super-tile is 4 rows of 4 pattern table tiles, set pattern table tile size to #$04 + sta $14 ; CPU_GRAPHICS_BUFFER graphic data group size + sta CPU_GRAPHICS_BUFFER,x ; each group of graphic data is #$04 bytes (4 rows in super-tile) + inx + sta CPU_GRAPHICS_BUFFER,x ; #$04 groups of #$04-byte-sized entries + inx + ldy #$00 ; initialize level_x_nametable_update_supertile_data entry offset to #$00 (beginning of data) + +; write the PPU write address and then the graphics data +@write_graphic_group: + lda $0d ; PPU write address high byte + sta CPU_GRAPHICS_BUFFER,x ; set PPU write address high byte + inx + lda $0c ; PPU write address low byte + sta CPU_GRAPHICS_BUFFER,x ; set PPU write address low byte + inx + +; write the #$04 bytes of the graphic group +@write_graphic_group_bytes: + lda ($16),y ; load tile from the level_X_nametable_update_supertile_data + sta CPU_GRAPHICS_BUFFER,x ; store in CPU_GRAPHICS_BUFFER + inx ; increment CPU_GRAPHICS_BUFFER write offset + iny ; increment level_x_nametable_update_supertile_data read offset + tya + and #$03 ; keep bits .... ..xx + bne @write_graphic_group_bytes + lda $0c ; load PPU write address low byte + clc ; clear carry in preparation for addition + adc #$20 ; move down a row on the nametable to prep for drawing the next 4 tiles + sta $0c ; update PPU write address low byte + lda $0d ; load PPU write address high byte + adc #$00 ; add any carry from previous adc + sta $0d ; update PPU write address high byte + dec $14 ; decrement from group size counter + bne @write_graphic_group + stx GRAPHICS_BUFFER_OFFSET ; update GRAPHICS_BUFFER_OFFSET + clc ; clear any leftover carry + rts + +; level 2/4 boss screen, waterfall (red turret) +; input +; * $00 - palette update mode (#$01 = 2 horizontally, #$02 = 2 vertically, #$03 = 4 (2x2)) +update_supertile_palette: + tax ; transfer palette update mode to x + lda ($0e),y ; load palette byte for super-tile + dex ; decrement palette update mode + stx $01 ; save in $01 + bne @update_palette_01 ; branch if palette command code was not #$01 + tax ; update 2 super-tiles' palettes horizontally, move palette byte to x + and #$33 ; keep bits ..xx ..xx + asl + asl + sta $08 + txa + and #$cc ; keep bits xx.. xx.. + lsr + lsr + sta $09 + ldx $02 ; load current screen super-tile offset (set in set_ppu_addresses_in_mem) + ; e.g. level_2_4_boss_supertiles_screen_00 + ldy LEVEL_SCREEN_SUPERTILES,x ; read byte specifying which super-tile to load (level_X_supertiles_screen_XX) + lda (LEVEL_SUPERTILE_PALETTE_DATA),y ; load the palette data for the current super-tile + and #$33 ; keep left half of super-tile palette data + ora $08 ; merge with right half of super-tile palette + sta $08 ; set super-tile palette data + ldy LEVEL_SCREEN_SUPERTILES+1,x ; read byte specifying which super-tile to load (level_X_supertiles_screen_XX) + lda (LEVEL_SUPERTILE_PALETTE_DATA),y ; load the palette data for the current super-tile + and #$cc ; keep right half of super-tile palette data + ora $09 ; merge with left half of super-tile palette + sta $09 ; set super-tile palette data + jmp @update_palette_continue + +; vertical two super-tiles +@update_palette_01: + dex ; decrement $00 value + bne @update_palette_02 + ldx #$00 ; x = #$00 + stx $09 + asl + rol $09 + asl + rol $09 + asl + rol $09 + asl + rol $09 + sta $08 + ldx $02 + ldy LEVEL_SCREEN_SUPERTILES,x ; read byte specifying which super-tile to load (level_X_supertiles_screen_XX) + lda (LEVEL_SUPERTILE_PALETTE_DATA),y ; level_x_palette_data + and #$0f ; keep bits .... xxxx + ora $08 + sta $08 ; store + ldy LEVEL_SCREEN_SUPERTILES+8,x + lda (LEVEL_SUPERTILE_PALETTE_DATA),y + and #$f0 ; keep bits xxxx .... + ora $09 + sta $09 + jmp @update_palette_continue + +; boss screen 2/4 +; loads 4 super-tile palette, 2x2 +@update_palette_02: + ldx #$00 ; x = #$00 + stx $08 + stx $0b + lsr + ror $08 + lsr + ror $08 + and #$0c ; keep bits .... xx.. + sta $0a + lda ($0e),y + asl + rol $0b + asl + rol $0b + and #$30 ; keep bits ..xx .... + sta $09 + ldx $02 + ldy LEVEL_SCREEN_SUPERTILES,x ; read byte specifying which super-tile to load (level_X_supertiles_screen_XX) + lda (LEVEL_SUPERTILE_PALETTE_DATA),y + and #$3f ; keep bits ..xx xxxx + ora $08 + sta $08 + ldy LEVEL_SCREEN_SUPERTILES+1,x + lda (LEVEL_SUPERTILE_PALETTE_DATA),y + and #$cf ; keep bits xx.. xxxx + ora $09 + sta $09 + ldy LEVEL_SCREEN_SUPERTILES+8,x + lda (LEVEL_SUPERTILE_PALETTE_DATA),y + and #$f3 ; keep bits xxxx ..xx + ora $0a + sta $0a + ldy LEVEL_SCREEN_SUPERTILES+9,x + lda (LEVEL_SUPERTILE_PALETTE_DATA),y + and #$fc ; keep bits xxxx xx.. + ora $0b + sta $0b + +@update_palette_continue: + lda $01 ; load updated palette update mode (#$00 = 2 horizontally, #$01 = 2 vertically, #$02 = 4 (2x2)) + asl ; double since to determine how many super-tile palettes are being updated + tay ; transfer number of super-tiles to update to offset register + ldx GRAPHICS_BUFFER_OFFSET ; load current offset into graphics buffer + lda #$01 ; a = #$01 + sta CPU_GRAPHICS_BUFFER,x ; set VRAM address increment to 0, meaning to add #$1 every write to PPU (write across) + inx ; increment graphics buffer offset + lda update_palette_cfg_tbl,y ; load how many tiles to draw per graphics group + sta CPU_GRAPHICS_BUFFER,x ; set how many pattern table tiles to draw per group + inx ; increment graphics buffer offset + lda update_palette_cfg_tbl+1,y ; load how many graphics groups there are to draw + sta CPU_GRAPHICS_BUFFER,x ; set how many graphics groups to draw + inx ; increment graphics buffer offset + ldy #$00 ; y = #$00 + +@write_palette_data: + lda $15 ; load PPU address (attribute table) high byte + sta CPU_GRAPHICS_BUFFER,x ; set PPU address (attribute table) high byte + inx ; increment graphics buffer offset + lda $14 ; load PPU address (attribute table) low byte + sta CPU_GRAPHICS_BUFFER,x ; set PPU address (attribute table) low byte + inx ; increment graphics buffer offset + lda $08,y ; load super-tile palette data + sta CPU_GRAPHICS_BUFFER,x ; set super-tile palette data + inx ; increment graphics buffer offset + lda $00 ; load original palette update mode (#$01 = 2 horizontally, #$02 = 2 vertically, #$03 = 4 (2x2)) + cmp #$02 ; compare to mode 2 + beq @mv_down_row ; branch if mode #$02 (2 vertical) + iny ; either mode #$01 or #$03 (mode contains 2 horizontal super-tiles) + ; increment number of super-tiles updated + lda $08,y ; load 2nd super-tile palette data + sta CPU_GRAPHICS_BUFFER,x ; set 2nd super-tile palette data + inx ; increment graphics buffer offset + +@mv_down_row: + lda $01 ; load updated palette update mode (#$00 = 2 horizontally, #$01 = 2 vertically, #$02 = 4 (2x2)) + beq @write_update_supertile ; finished writing super-tile palette data, move to update super-tile pattern tiles + iny ; increment number of super-tiles updated + lda $14 ; load super-tile palette PPU address (attribute table) low byte + clc ; clear carry in preparation for addition + adc #$08 ; move one super-tile row down + sta $14 ; set new super-tile palette PPU address (attribute table) low byte + lda #$00 ; a = #$00 + sta $01 ; change palette update mode from #$02 to #$01, or from #$01 to #$00 + beq @write_palette_data ; loop to write the next row (only for mode #$02 which has 4 super-tiles to update total) + +@write_update_supertile: + jmp write_update_supertile_to_cpu ; finished writing super-tile palette data, move to super-tile pattern tile data + +; configuration based on palette update mode +; #$00 = 2 horizontally, #$02 = 2 vertically, #$04 = 4 (2x2) +; byte 0 - number of pattern table tiles per graphics group +; byte 1 - number of graphics groups +update_palette_cfg_tbl: + .byte $02,$01 + .byte $01,$02 + .byte $02,$02 + +; bank 3 offsets +; pointer table for nametable super-tile nametable and palettes ($12 * $02 = $24 bytes) +; 2 pointers per level. +; * pointer 1: super-tile tile definitions +; * pointer 2: palette codes for the super-tile, values ultimately end up in attribute table +; each byte from level_X_nametable_update_palette_data is a quarter of a super-tile +nametable_update_data_ptr_tbl: + .addr level_1_nametable_update_supertile_data ; bank 3 label - CPU address $83b1 + .addr level_1_nametable_update_palette_data ; bank 3 label - CPU address $86ac + .addr level_2_nametable_update_supertile_data ; bank 3 label - CPU address $88a8 + .addr level_2_nametable_update_palette_data ; bank 3 label - CPU address $8e91 + .addr level_3_nametable_update_supertile_data ; bank 3 label - CPU address $9368 + .addr level_3_nametable_update_palette_data ; bank 3 label - CPU address $965f + .addr level_4_nametable_update_supertile_data ; bank 3 label - CPU address $88a8 + .addr level_4_nametable_update_palette_data ; bank 3 label - CPU address $8e91 + .addr level_5_nametable_update_supertile_data ; bank 3 label - CPU address $9ba8 + .addr level_5_nametable_update_palette_data ; bank 3 label - CPU address $9db9 + .addr level_6_nametable_update_supertile_data ; bank 3 label - CPU address $a4ae + .addr level_6_nametable_update_palette_data ; bank 3 label - CPU address $a567 + .addr level_7_nametable_update_supertile_data ; bank 3 label - CPU address $abea + .addr level_7_nametable_update_palette_data ; bank 3 label - CPU address $adae + .addr level_8_nametable_update_supertile_data ; bank 3 label - CPU address $b25a + .addr level_8_nametable_update_palette_data ; bank 3 label - CPU address $b543 + .addr level_2_4_nametable_update_supertile_data ; bank 3 label - CPU address $ba1a + .addr level_2_4_boss_nametable_update_palette_data ; bank 3 label - CPU address $bdc4 + +update_nametable_tiles_exit: + rts + +; updates #$02 columns of n rows (default #$02) of a nametable at position (a,y) with desired pattern table tiles +; bank 3 should be loaded +; input +; * a is ENEMY_X_POS +; * y is ENEMY_Y_POS +; * $10 (multiplied by #$05) is the index into the tile animation table to start drawing +; if bit 7 clear, then update palette, if bit 7 set do not update palette +; output +; * carry - clear when successful, set when CPU_GRAPHICS_BUFFER is full +; for example, indoor/base levels for drawing wall turrets, and changing to explosion when destroyed +; claw animations, etc. +update_nametable_tiles: + sta $11 ; store enemy x position in $11 + lda GRAPHICS_BUFFER_OFFSET ; load current GRAPHICS_BUFFER_OFFSET + cmp #$50 ; GRAPHICS_BUFFER_OFFSET goes from $700 to $750 + bcs update_nametable_tiles_exit ; graphics buffer full, exit + jsr set_ppu_addresses_in_mem ; determines attribute table PPU address, $14 (low) and $15 (high) + ; determines PPU nametable write address, $0c (low) and $0d (high) + ; for x ($11), y (y) coordinates + lda $10 ; load the current level's super-tile read offset ($16 read offset) + asl + asl + adc $10 ; multiply by 5 + tay + lda CURRENT_LEVEL ; load current level + asl ; each entry in level_tile_animation_ptr_tbl is a 2-byte address, so double + tax + lda level_tile_animation_ptr_tbl,x ; read the address low byte + sta $16 ; store the address low byte + lda level_tile_animation_ptr_tbl+1,x ; load the address high byte + sta $17 ; store the address high byte + ldx #$02 ; default to two tiles per read + lda ($16),y ; read first byte of tile animation table + ; #$00 means to update #$02 rows of #$02 pattern table tiles each row + bpl @continue ; if the msb is not set draw default number of rows (#$02), jump + and #$07 ; first bit set, mask its least significant 3 bits to see how many rows to draw + tax + +@continue: + stx $14 ; $14 (total number of tile groups) + ; store masked first byte of ($16) if its msb was set, otherwise store #$02 + ldx GRAPHICS_BUFFER_OFFSET ; load current GRAPHICS_BUFFER_OFFSET + lda $0f ; loads whether palette needs to be updated (#$00 = yes, #$80 = no) + bne @update_nametable_tiles ; branch if $0f is #$80 + lda ($16),y ; palette needs to be updated, re-read first byte of tile animation table + sty $0e ; store update tile offset in $0e + ldy $00 ; load palette update mode (#$01 = 2 horizontally, #$02 = 2 vertically, #$03 = 4 (2x2)) + beq @set_supertile_palette + dey + beq @shift_2 ; branch if palette update mode is #$01 (2 horizontally) + dey + beq @shift_4 ; branch if palette update mode is #$02 (2 vertically) + asl + asl + +@shift_4: + asl + asl + +@shift_2: + asl + asl + +@set_supertile_palette: + sta $08 + ldx $02 ; load the super tile to draw LEVEL_SCREEN_SUPERTILES offset + ldy LEVEL_SCREEN_SUPERTILES,x ; read byte specifying which super-tile to load (level_X_supertiles_screen_XX) + lda (LEVEL_SUPERTILE_PALETTE_DATA),y ; load palette data for super-tile + ldy $00 + and palette_mask_tbl,y ; strip out the 2 bits that need to change + ora $08 ; merge with new palette entry for quadrant of super-tile + sta $08 ; set updated palette data for super-tile + ldx GRAPHICS_BUFFER_OFFSET ; load graphics buffer offset + jsr set_graphics_buffer_header ; create entry saying to write one byte to PPU at PPU address specified by $14 (low PPU write address) and $15 (high PPU write address) + lda $08 ; load super-tile palette + sta CPU_GRAPHICS_BUFFER,x ; set super-tile palette to graphics buffer + inx ; increment graphics buffer read offset + ldy $0e ; restore tile animation offset + +; specifies VRAM address increment, # of tiles groups and size of tile group +@update_nametable_tiles: + iny + lda #$01 ; set VRAM address increment to 1 (write across) + sta CPU_GRAPHICS_BUFFER,x + inx + lda #$02 ; set number of tiles per group to #$02 + sta CPU_GRAPHICS_BUFFER,x + inx + lda $14 ; set total number of tile groups to $14 + sta CPU_GRAPHICS_BUFFER,x + inx + +; specifies PPU write address in CPU_GRAPHICS_BUFFER +; specifies #$02 pattern table tiles per row +prep_overwrite_nametable_tiles: + lda #$02 ; a = #$02 + sta $15 ; set number of tiles in each group in CPU_GRAPHICS_BUFFER + lda $0d ; load low byte of PPU write address + sta CPU_GRAPHICS_BUFFER,x ; store low byte of PPU write address + inx ; increment PPU write offset + lda $0c ; load PPU tile write address high byte + sta CPU_GRAPHICS_BUFFER,x ; store high byte of PPU write address + inx ; increment CPU_GRAPHICS_BUFFER write offset + +write_overwrite_tile_to_cpu_buffer: + lda ($16),y ; read tile from tile_animation_ptr data + sta CPU_GRAPHICS_BUFFER,x ; write tile to CPU_GRAPHICS_BUFFER + inx ; increment CPU_GRAPHICS_BUFFER offset + iny ; increment tile_animation_ptr read offset + dec $15 ; decrement tile read total + bne write_overwrite_tile_to_cpu_buffer ; if we aren't done writing tiles, loop + lda $0c ; load the low byte PPU write address + clc ; clear any previous carry + adc #$20 ; move next write address down by a row on the nametable (same column) + sta $0c ; store updated PPU tile write address for next loop + lda $0d ; load current PPU tile write address high byte + adc #$00 ; if there was a carry adding the #$20 to the low byte, add it to the high byte + sta $0d ; store updated PPU tile write address for next loop + dec $14 ; finished writing tile group to CPU_GRAPHICS_BUFFER, move to next group of overwrite tiles + bne prep_overwrite_nametable_tiles ; loop to next set of tiles to write to CPU_GRAPHICS_BUFFER + stx GRAPHICS_BUFFER_OFFSET ; ensure graphics buffer offset is up to date + clc ; clear any carry + rts + +; bank 3 labels +; pointer table to pattern table tiles that are used to modify nametable after fully drawn for animations ($09 * $02 = $12 bytes) +level_tile_animation_ptr_tbl: + .addr level_1_supertile_data ; level 1 - CPU address $8001 (unused) + .addr level_2_4_tile_animation ; level 2 - CPU address $86e1 + .addr level_3_supertile_data ; level 3 - CPU address $8ef8 (unused) + .addr level_2_4_tile_animation ; level 4 - CPU address $86e1 + .addr level_5_supertile_data ; level 5 - CPU address $9698 (unused) + .addr level_6_tile_animation ; level 6 - CPU address $9dd8 + .addr level_7_tile_animation ; level 7 - CPU address $a56e + .addr level_8_supertile_data ; level 8 - CPU address $adca (unused) + .addr level_2_4_boss_supertile_data ; level 2/4 boss rooms - CPU address $b57a + +; the following 3 tables are ppu addresses for the tile_animation tables entries +; used on all levels for animations +nametable_base_high_byte: + .byte $20,$24 + +attribute_base_high_byte: + .byte $23,$27 + +; the base offset into cpu graphics buffer where super-tile indexes are loaded (LEVEL_SCREEN_SUPERTILES) +; $0600 or $0640 +level_screen_mem_offset_tbl_00: + .byte $00,$40 + +palette_mask_tbl: + .byte $fc ; 1111 1100 + .byte $f3 ; 1111 0011 + .byte $cf ; 1100 1111 + .byte $3f ; 0011 1111 + +; determines ppu nametable and attribute table addresses for given x, y coordinate +; input +; * $10 - super-tile to draw, bit 7 used to load $0f (palette update marker) +; (level_x_nametable_update_supertile_data/level_x_nametable_update_palette_data offset) +; if bit 7 clear, then update palette, if bit 7 set do not update palette +; * $11 - x offset +; * y - y offset +; output +; * PPU nametable write address: $0c (low) and $0d (high) +; * PPU nametable collision address: $12 (low) and $13 (high) +; * used for nametable collision removal +; * always same as $0c and $0d +; * PPU attribute table write address: $14 (low) and $15 (high) +; * $00 - if set, then branch update_supertile_palette is executed +; palette update modes (#$01 = 2 horizontally, #$02 = 2 vertically, #$03 = 4 (2x2)) +; * $10 - same as input but with bit 7 stripped +; * $02 - super-tile index at location (level_x_supertiles_screen_xx offset) +; * $0f - #$00 if palette needs to be updated, #$80 otherwise +set_ppu_addresses_in_mem: + lda $10 ; load super-tile to draw + ; (offset into level_x_nametable_update_supertile_data/level_x_nametable_update_palette_data offset) + and #$80 ; keep most significant bit (palette update flag) + sta $0f ; if #$00, then mark palette to updated as well as super-tile, #$80 means do not update palette + lda $10 ; re-load super-tile to draw + and #$7f ; trim off most significant bit (palette update flag) + sta $10 ; save updated super-tile to draw + tya ; transfer y offset to a + clc ; clear carry in preparation for addition + adc VERTICAL_SCROLL ; add vertical scroll offset to y pixel offset, e.g. #$e0 (224 pixels/28 tiles) for outdoor levels + bcs @round_up ; branch if an overflow occurred to continue + cmp #$f0 ; no overflow + bcc @nametable + +@round_up: + adc #$0f + +@nametable: + and #$f8 ; keep bits xxxx x... + sta $12 ; set PPU nametable address low byte + lsr + lsr + tay + lsr + and #$02 ; keep bits .... ..x. + sta $00 + tya + and #$38 ; keep bits ..xx x... + sta $14 ; set PPU attribute table write address low byte + lda #$00 ; not used, next line overrides a + asl $12 ; PPU nametable address low byte + rol ; moves any carry from previous asl to bit 0 + asl $12 ; PPU nametable address low byte + rol ; moves any carry from previous asl to bit 0 + sta $13 ; set PPU nametable address high byte + lda $11 ; load x offset in pixels + clc ; clear carry in preparation for addition + adc HORIZONTAL_SCROLL ; add the horizontal scroll to the x position + sta $11 ; update x offset to include horizontal scroll + lda PPUCTRL_SETTINGS ; pull part of base nametable address, used to determine high byte for nametable and attribute table + and #$01 ; keep least significant bit (nametable base address 0 = $2000; 1 = $2400) + bcc @continue ; branch if no carry occurred on adc + eor #$01 ; carry occurred flip bits .... ...x + +@continue: + tay ; transfer nametable index offset to y (#$00 or #$01) + lda attribute_base_high_byte,y ; grab byte used to determine attribute table PPU address + ora #$03 ; ensure smallest 2 bits always set (.... ..xx) + sta $15 ; set PPU attribute table write address high byte + lda nametable_base_high_byte,y ; grab byte used to determine nametable PPU address + ora $13 ; merge with value already in $13 + sta $13 ; set PPU nametable collision address high byte + sta $0d ; store PPU write address high byte + lda level_screen_mem_offset_tbl_00,y ; load the base offset from LEVEL_SCREEN_SUPERTILES ($0600 or $0640) + sta $02 ; set base level screen supertile offset $0600 for nametable 0, $0640 for nametable 1 + lda $11 ; load x offset in pixels including horizontal scroll + and #$f8 ; keep bits xxxx x... + lsr + lsr + lsr + tay + lsr + tax + and #$01 ; keep bits .... ...x + ora $00 + sta $00 + txa + lsr + ora $14 ; merge with PPU attribute table low write address high byte + sta $03 ; set LEVEL_SCREEN_SUPERTILES offset + clc ; clear carry in preparation for addition + adc #$c0 + sta $14 ; set PPU attribute table low write address high byte + tya + ora $12 ; merge with PPU nametable collision address low byte + sta $12 ; set PPU nametable collision address low byte + sta $0c ; PPU write address low byte + lda $02 ; load base level screen supertile offset $0600 for nametable 0, $0640 for nametable 1 + ora $03 ; merge with offset into structure + sta $02 ; set super-tile index for current screen (level_x_supertiles_screen_xx offset) + rts + +; creates CPU_GRAPHICS_BUFFER entry to specify writing one byte to PPU +; at PPU address specified by $14 (low PPU write address) and $15 (high PPU write address) +set_graphics_buffer_header: + lda #$01 ; set VRAM address increment to 0, meaning to add #$1 every write to PPU (write horizontally) + sta CPU_GRAPHICS_BUFFER,x + inx + sta CPU_GRAPHICS_BUFFER,x ; writing #$01-byte long groups of tiles + inx + sta CPU_GRAPHICS_BUFFER,x ; writing #$01 group of #$01 byte tiles, i.e. writing #$01 tile total + inx + lda $15 + sta CPU_GRAPHICS_BUFFER,x ; set PPU write address low byte to $15 + inx + lda $14 + sta CPU_GRAPHICS_BUFFER,x ; set PPU write address high byte to $14 + inx + rts + +; execute the code at offset A from the pointer table underneath the jsr opcode that called this method +; this is done by reading with offset from the value of the stack before this call and adding 1 +; which effectively allows this method to read from the pointer table below the calling code +; CPU address $c857 +run_routine_from_tbl_below: + asl ; double A since each entry is a 2-byte label address + sty $03 ; store y into $03 temporarily since this method overrides y + tay ; store offset into y + iny ; add one since the stack pointer points to one byte before table to offset into + pla ; pull the low byte of the stack pointer into a + sta $00 ; store low byte of stack pointer address into $00 + pla ; pull the high byte of the stack pointer memory address into a + sta $01 ; store high byte into $01 + lda ($00),y ; read low byte of address of code to execute (offset into table) + sta $02 ; store the low byte into $02 + iny ; increment offset so high byte can be read + lda ($00),y ; read the high byte of the address of the code to execute (offset into table) + ldy $03 ; restore y register to what it was before the call to run_routine_from_tbl_below + sta $03 ; store high byte into $03 + jmp ($0002) ; jump to the code specified by the address at offset A into the pointer table table + +; dead code, never called !(UNUSED) +bank_7_unused_label_00: + stx $00 + sty $01 + +; Calculate next digit of the score and put $02 and A register +; +; This logic converts from a binary to decimal, decimal digit by +; decimal digit by left shifting and keeping track of when the right-most digit +; is greater than 10 decimal. +; +; For example, for a score of 255, the first call to calculate the next digit to +; display will return 5, and store 25 into memory for the next call. On the +; second call to calculate the next digit to display, the subroutine will return +; 5, and store 2 into memory for the last call. On the final call, 2 is returned +; and 0 is stored, letting the calling code know the score is finished. +; +; CPU Memory used +; 0x00 - low byte of the current score being calculated +; 0x01 - high byte of the current score being calculated +; 0x02 - the next decimal digit to display +; 0x03 - hard-coded 10 in decimal +calculate_score_digit: + lda #$00 ; set the accumulator register A to zero (#$00) + sta $02 ; zero out any previously calculated digit + ldy #$10 ; set the left-shift loop counter back to #$10 (16 decimal) + rol $00 ; shift the score low byte to the left by one bit + ; push the most significant bit (msb) to the carry flag + rol $01 ; shift the score high byte to the left by one byte + ; push the msb to the carry flag + ; pull in carry flag to least significant bit (lsb) + +shift_and_check_digit_carry: + rol $02 ; shift score high byte to the left by one bit + ; if the msb of the score high byte was 1, then carry into lsb + lda $02 ; load current digit into the accumulator register A + cmp $03 ; compare #$0a (10 decimal) to the current digit + + ; branch if current digit is less than #$0a (10 decimal) + ; - this means no subtraction and carry is needed + ; if greater than #$0a (10 decimal), don't jump + ; - subtract 10 and carry + bcc continue_shift_score + sbc $03 ; the current digit is greater than 10, subtract 10 + ; this also sets the carry flag, which will be moved to the + ; low byte of the score, which is the "Rest" of the number + ; this carry represents adding 10 to the "Rest" + sta $02 ; store the difference (new current digit) back in $02 + +; $02 (current digit) is less than #$0a, or has just been subtracted +; continue algorithm by shifting score left +continue_shift_score: + rol $00 ; if just set $02 to digit by subtraction, this will put 1 + ; in $00's lsb, signifying adding 10 to "Rest" + rol $01 ; if $00's msb is 1, then it'll carry to the lsb of $01 + dey ; Y goes from $10 to $00, once Y is $00, the algorithm is done + bne shift_and_check_digit_carry + rts + +; advance read address ($00,x - $01,x) by a bytes +; increment 2-byte read address in $00,$01 by 1 +; uses x register as offset from $00 +; a is the amount to add to the base address +advance_graphic_read_addr: + clc ; clear the carry bit + adc $00,x ; set a to the value at $00,x plus a + sta $00,x ; store result back into $00,x + bcc @exit ; if a carry is required (>255), then increment the byte at $01,x + inc $01,x ; increment high byte + +@exit: + rts + +; dead code, never called !(UNUSED) +@handle_overflow: + eor #$ff ; flip all bits + sec ; set carry flag + adc $00,x ; flip all bits add 1 to handle overflow + sta $00,x ; store result back in $00,x + bcs @handle_overflow_exit + dec $01,x ; decrement high bight + +@handle_overflow_exit: + rts + +; loads the pattern table (graphic_data_01), nametable (graphic_data_02), and palette data (transition_screen_palettes) +load_intro_graphics: + jsr init_APU_channels + jsr clear_memory_3 ; clear $0028 to $00f0 then CPU_SPRITE_BUFFER up to CPU_GRAPHICS_BUFFER + lda #$1e ; %0001 1110 no RGB emphasis, show sprites, show background + ; show sprites and background in leftmost 8 pixels of screen + ; no grayscale + sta PPUMASK_SETTINGS ; set PPUMASK setting to #$1e + ldy #$00 + sty SPRITE_LOAD_TYPE ; if 0, load regular sprites to cpu, else load hud sprites + iny + sty DEMO_MODE ; set DEMO_MODE to true + lda #$0b ; offset into level_graphic_data_tbl pointing to intro_graphic_data_01 (intro graphics) + jsr load_A_offset_graphic_data ; load all of intro graphics specified: $01 (graphic_data_01), $02 (graphic_data_02) + lda #$06 ; offset into short_text_pointer_table, which is transition_screen_palettes (bank 6 $b302) + jsr load_bank_6_write_text_palette_to_mem ; load palette data to CPU memory CPU_GRAPHICS_BUFFER + +exit_load_graphics_group: + rts + +; loads the graphic data for the current level +load_current_level_graphic_data: + lda CURRENT_LEVEL + +; loads the graphic data for the level specified by offset into level_graphic_data_tbl (A register) into the PPU +; most often the pattern table data, but can be nametable data (blank_nametables, graphic_data_02, graphic_data_18) +load_level_graphic_data: + asl ; each entry is 2 bytes, so multiply offset by 2 + tay ; store offset into y + lda level_graphic_data_tbl,y ; load low byte of graphic data address + sta $06 ; store low byte in $06 + lda level_graphic_data_tbl+1,y ; load high byte of graphic data address + sta $07 ; store high byte in $07 + ldy #$00 ; set graphic data index to 0 + sty $05 ; store graphic data index in $05 + +; loop through each graphic data and load it in PPU memory +@loop: + lda ($06),y ; read 2-byte memory address where graphics are located in memory for the current index (Y) + bmi exit_load_graphics_group ; if read #ff, then we are done with the graphic data + jsr write_graphic_data_to_ppu ; load the graphics into the PPU + inc $05 ; increment graphics offset + ldy $05 + bne @loop ; load next graphics + +; pointer table for graphic data codes (#$0d * #$02 = $1a bytes) CPU address $c8e3 +; each entry in this table points to a list of graphic data to load +level_graphic_data_tbl: + .addr level_1_graphic_data ; level 1 - CPU address $c8fd + .addr level_2_graphic_data ; level 2 - CPU address $c905 + .addr level_3_graphic_data ; level 4 - CPU address $c916 + .addr level_4_graphic_data ; level 3 - CPU address $c90d + .addr level_5_graphic_data ; level 5 - CPU address $c91e + .addr level_6_graphic_data ; level 6 - CPU address $c926 + .addr level_7_graphic_data ; level 7 - CPU address $c92e + .addr level_8_graphic_data ; level 8 - CPU address $c936 + .addr level_2_boss_graphic_data ; level 2 boss room - CPU address $c93b + .addr level_4_boss_graphic_data ; level 4 boss room - CPU address $c940 + .addr intro_graphic_data_00 ; intro graphics - CPU address $c946 + .addr intro_graphic_data_01 ; intro graphics and intro nametable - CPU address $c948 + .addr ending_graphic_data ; ending scene - CPU address $c94b + +; the following labels contains a list of bytes +; each byte is an offset into the graphic_data_ptr_tbl table +; each label ends in #$ff +; CPU memory $c8fd +level_1_graphic_data: + .byte $03,$13,$19,$1a,$14,$16,$05,$ff ; level 1 + +; CPU memory $c905 +level_2_graphic_data: + .byte $03,$04,$06,$0a,$0f,$10,$11,$ff ; level 2 + +; CPU memory $c90d +level_4_graphic_data: + .byte $03,$04,$06,$0a,$0f,$10,$11,$12,$ff ; level 4 + +level_3_graphic_data: + .byte $03,$13,$19,$1a,$14,$16,$07,$ff ; level 3 + +level_5_graphic_data: + .byte $03,$13,$19,$1a,$15,$16,$0b,$ff ; level 5 + +level_6_graphic_data: + .byte $03,$13,$19,$1a,$15,$16,$0c,$ff ; level 6 + +level_7_graphic_data: + .byte $03,$13,$19,$1a,$15,$16,$0d,$ff ; level 7 + +level_8_graphic_data: + .byte $03,$13,$19,$0e,$ff ; level 8 + +level_2_boss_graphic_data: + .byte $03,$04,$13,$08,$ff ; level 2 boss room + +level_4_boss_graphic_data: + .byte $03,$04,$13,$08,$09,$ff ; level 4 boss room + +intro_graphic_data_00: + .byte $01,$ff ; intro palette + +; graphic_data_01, graphic_data_02 +; #$01 - intro screen, level title screens, and game over screens pattern table tiles +; #$02 - game and level intro screen nametable data +intro_graphic_data_01: + .byte $01,$02,$ff ; intro pattern table tiles, and intro nametable + +ending_graphic_data: + .byte $01,$03,$17,$18,$ff ; ending scene + +; tables for graphic data data pointers (#$1b * #$03 = $51 bytes) +; contains nametable and pattern table data +; CPU address $c950 +; first 2 bytes are the memory address to load +; last byte specifies 2 things +; * bits 0-3 specify the rom bank to have loaded +; the exception is 0 means bank 7 instead of bank 0) +; * bit 7 is stored in $04, when set it means all tiles from +; the graphic data must be flipped horizontally +graphic_data_ptr_tbl: + ; reset both PPU nametables to zeros + ; bank 7 (not bank 0) + .addr blank_nametables ; $cb36 + .byte $00 ; bank 7 not bank 0 + + ; bank 4 - intro, level title, game over screen pattern table + .addr graphic_data_01 + .byte $04 ; bank where data located + + ; bank 2 - intro screen nametable + .addr graphic_data_02 + .byte $02 ; bank where data located + + ; bank 4 - character, medals, power-ups, explosions + .addr graphic_data_03 + .byte $04 ; bank where data located + + ; bank 4 - character facing up + .addr graphic_data_04 + .byte $04 ; bank where data located + + ; bank 5 + .addr graphic_data_05 + .byte $05 ; bank where data located + + ; bank 4 - most Base graphics + .addr graphic_data_06 + .byte $04 ; bank where data located + + ; bank 5 + .addr graphic_data_07 + .byte $05 ; bank where data located + + ; bank 4 + .addr graphic_data_08 + .byte $04 ; bank where data located + + ; bank 4 + .addr graphic_data_09 + .byte $04 ; bank where data located + + ; bank 4 + .addr graphic_data_0a + .byte $04 ; bank where data located + + ; bank 5 + .addr graphic_data_0b + .byte $05 ; bank where data located + + ; bank 6 + .addr graphic_data_0c + .byte $06 ; bank where data located + + ; bank 6 + .addr graphic_data_0d + .byte $06 ; bank where data located + + ; bank 6 + .addr graphic_data_0e + .byte $06 ; bank where data located + + ; bank 4 + .addr graphic_data_0f + .byte $04 ; bank where data located + + ; bank 4 + ; horizontal flip + .addr graphic_data_10 + .byte $84 ; bank where data located (with horizontal flip) + + ; bank 4 + .addr graphic_data_11 + .byte $04 ; bank where data located + + ; bank 4 - Base 2 Graphics + .addr graphic_data_12 + .byte $04 ; bank where data located + + ; bank 4 ; bank where data located + .addr graphic_data_13 + .byte $04 ; bank where data located + + ; bank 5 + .addr graphic_data_14 + .byte $05 ; bank where data located + + ; bank 6 + .addr graphic_data_15 + .byte $06 ; bank where data located + + ; bank 6 + .addr graphic_data_16 + .byte $06 ; bank where data located + + ; bank 5 + .addr graphic_data_17 + .byte $05 ; bank where data located + + ; bank 5 + .addr graphic_data_18 + .byte $05 ; bank where data located + + ; bank 5 + .addr graphic_data_19 + .byte $05 ; bank where data located + + ; bank 5 + .addr graphic_data_1a + .byte $05 ; bank where data located + +; CPU address $c9a2 +zero_out_nametables: + lda #$00 + +; loads and decompresses the entire graphic data specified by the A register as offset into graphic_data_ptr_tbl +write_graphic_data_to_ppu: + sta $04 ; use $04 as a temp location so we can triple a + asl ; double a data + adc $04 ; add $04 data to a (the previous 3 lines are simply multiplying by 3) + ; lookup table is 3 bytes each, so multiply by 3 to get offset + tax + lda graphic_data_ptr_tbl,x + sta $00 ; store graphic address low byte in $00 + lda graphic_data_ptr_tbl+1,x + sta $01 ; store graphic address high byte in $01 + lda graphic_data_ptr_tbl+2,x ; load byte that stores bank number as well as whether or not to horizontally flip + and #$80 ; %1000 0000 (check the msb), if msb is 1, then block will be flipped horizontally + sta $04 ; store whether or not flip the entire graphic data horizontally into $04 + lda graphic_data_ptr_tbl+2,x ; re-read the bank number to load the graphics from + and #$07 ; clear out the flip horizontal bit (only care about the last 3 bits which specify the bank number) + tay ; store the bank number where data is in Y + jsr load_bank_number ; store current bank in $07ec and load new bank + jsr clear_ppu ; resets A to #$00 as well + sta GRAPHICS_BUFFER_OFFSET ; set cpu graphics buffer offset + sta VERTICAL_SCROLL ; set vertical scroll offset to 0 + sta HORIZONTAL_SCROLL ; set horizontal scroll offset to 0 + +; reads 2-bytes of memory starting at address $00,$01, which is a specific graphic_data +; and sets the PPUADDR to that address +; then begins decompressing the graphic data and starts writing to PPU +begin_ppu_graphics_block_write: + lda PPUSTATUS ; reset PPU latch to prep for writing + ldy #$01 + lda ($00),y ; read high byte from ROM location specified by $00 and $01 + sta PPUADDR ; set high byte of PPU address location to write to + dey ; move to low byte + lda ($00),y ; read low byte from ROM location specified by $00 and $01 + sta PPUADDR ; set low byte of PPU address location to write to + lda #$02 ; a = #$02 + ldx $04 ; load horizontal flip bit + bpl init_graphic_data_index ; if set, then skip doubling + asl ; double a to be #$04 to skip 4 bytes + +; increment the next address to use for the PPU +init_graphic_data_index: + ldx #$00 + jsr advance_graphic_read_addr ; advance past the PPU address and start reading graphic data data + +; reads the next graphics byte compression sequence and writes it to the PPU +; multiple times depending on the number of repetitions specified +write_graphic_data_sequences_to_ppu: + ldy #$00 ; set offset so next line reads number of repetitions + lda ($00),y ; read the byte of the graphic data + cmp #$ff ; see if we are at the end of the graphic data + beq end_graphic_code ; loaded entire graphics data, restore previously loaded bank, re-init PPU + cmp #$7f ; used to specify the PPU write address should change to the address specified in the next 2 bytes) + beq change_ppu_write_address + tay ; store number of repetitions in Y + bpl write_graphic_byte_a_times ; if msb of graphic byte is 1 (> #$7f), write the following byte multiple times + and #$7f ; clear bit 7 + sta $02 ; store positive portion in $02 + ldy #$01 ; prepare to read next n graphic bytes + +; writes the next n bytes of the graphic compression sequence to the PPU, starting at offset Y +; where n is the value in $02 +write_next_n_sequence_bytes: + lda ($00),y ; read graphic byte + ldx $04 ; load graphic data horizontal flip bit value + bpl write_repetition_sequence_byte ; if not flipping graphic byte, go to write_repetition_sequence_byte + jsr horizontal_flip_graphic_byte + +; writes value of a to the PPU +; then determines if done with n bytes of graphic data +write_repetition_sequence_byte: + sta PPUDATA ; write graphic byte to PPU + cpy $02 ; see if Y has incremented to the positive portion of repetition byte + beq advance_graphic_read_addr_n_bytes ; advance offset past already-written bytes + iny ; have not written $02 times, repeat + bne write_next_n_sequence_bytes ; loop $02 times + +; advances the address of the current graphic byte offset by n bytes +; where n is the value in $02 + #$1 +advance_graphic_read_addr_n_bytes: + lda #$01 + clc ; clear carry in preparation for addition + adc $02 ; set A to the number of bytes to skip (they've already been written to the PPU) + +advance_ppu_write_addr: + ldx #$00 + jsr advance_graphic_read_addr + jmp write_graphic_data_sequences_to_ppu + +; write the next graphic byte to the PPU A times ($02) +write_graphic_byte_a_times: + ldy #$01 ; offset to read graphic data byte + sta $02 ; store number of repetitions of graphic byte to $02 + lda ($00),y ; load the graphic byte into a + ldy $02 ; load the number of repetitions into y + ldx $04 ; load whether or not to flip the graphic horizontally into x + bpl write_a_to_ppu_y_times ; write graphic byte to PPU Y times no horizontal flip needed + jsr horizontal_flip_graphic_byte ; flip graphics byte horizontally + +; writes the value of a to PPU repeatedly y times +write_a_to_ppu_y_times: + sta PPUDATA ; write PPU value address to PPU + dey ; decrement counter + bne write_a_to_ppu_y_times ; RLE loop Y times + lda #$02 ; prepare to read and write next graphic data byte + bne advance_ppu_write_addr ; continue updating PPU with data + +; horizontal flip routine +; swap bit 0 with 7, bit 1 with 6, bit 2 with 5, and bit 3 with 4 +horizontal_flip_graphic_byte: + sta $03 + asl $03 + ror a + asl $03 + ror a + asl $03 + ror a + asl $03 + ror a + asl $03 + ror a + asl $03 + ror a + asl $03 + ror a + asl $03 + ror a + rts + +; changes the PPU address where the graphic data bytes are written to +change_ppu_write_address: + lda #$01 ; load the low byte of second nametable address (#$00) + ldx #$00 ; load the high byte of the second nametable address (#$24) + jsr advance_graphic_read_addr + jmp begin_ppu_graphics_block_write ; start populating nametable with zeros + +; handle when entire graphic data code has been read +; restore previously-loaded bank and re-init the PPU +end_graphic_code: + jsr load_previous_bank + jmp configure_PPU + +; input +; * $1e - player index, #$00 = 1 player, #$01 = 2 player +; specifies REST text location, and which scores to load +draw_player_num_lives: + lda #$1e ; a = #$1e + sta $02 ; !(HUH) unused variable $02 set to #$1e + lda #$07 ; a = #$07 (text_rest) + clc ; clear carry in preparation for addition + adc $1e ; add #$00 or #$01 to use either text_rest (higher location) or text_rest2 (lower location) + jsr load_bank_6_write_text_palette_to_mem ; draw text string (REST) + ldx $1e ; #$00 = player 1, #$01 = player 2 + lda P1_GAME_OVER_STATUS,x ; load game over state for player x (1 = game over) + eor #$01 ; flip bit 0 (adds #$01 to remaining lives when not in game over) + clc ; clear carry in preparation for addition + adc P1_NUM_LIVES,x ; add the remaining player lives + beq draw_game_over_tex ; draw game over if no lives remaining + ; (for 2 player game when one of 2 players is game over) + bpl @continue ; continue if more than #$00 lives remaining + lda #$00 ; a = #$00 (remaining lives is negative, not sure if possible) !(WHY?) + +@continue: + ldx #$00 ; initialize 10s calculation 'multiplier' to #$00 + +; draws number of lives remaining left to right +; 10s digit is calculated in x by repeatedly subtracting 10 +; max number of lives able to printed is #$63 (99) +@draw_num_lives: + sta $00 ; store updated number of lives in $00 + cmp #$0a ; see if new number of lives is greater than 10 (more than one digit) + bcc @convert_digits ; branch if new amount is less than 10 lives to just draw the digit + sbc #$0a ; subtract 10 from remaining lives to draw 10s digit + inx ; increment 10s digit + cpx #$0a ; see if 10 loops have happened, i.e. 10s digit more than 9 + bcc @draw_num_lives ; continue subtracting 10 if number is larger than 10 + ldx #$09 ; score 10s digit was larger than 9, reset it to 9, i.e. largest number to draw is 99 + txa ; transfer 10s score to a + +; input +; * a - ones digit +; * x - 10s digit +@convert_digits: + ldy GRAPHICS_BUFFER_OFFSET ; load the cpu graphics buffer offset + ora #$30 ; converting the ones digit to pattern tile offset + ; #$31 = 1, #$32 = 2, etc. + cpx #$00 ; see if 10s digit is 0 + bne @write_num_lives ; branch to write 0 and exit if num lives is 0 + cmp #$30 ; 10s digit non-zero, see if + beq @exit ; exit if just drew a 0, meaning no more digits to draw + +@write_num_lives: + sta $06fe,y ; set ones digit + txa ; transfer 10s digit to a + beq @exit ; exit without drawing 10s digit if #$00 + ora #$30 ; converting number to pattern tile offset + ; #$31 = 1, #$32 = 2, etc. + sta $06fd,y ; set 10s digit of num lives + +@exit: + rts + +draw_game_over_tex: + txa + clc ; clear carry in preparation for addition + adc #$0f + jmp load_bank_6_write_text_palette_to_mem ; draw text string + +; draw text "stage " and the level's name +draw_stage_and_level_name: + lda #$0c ; a = $0c (text string "stage ") + jsr load_bank_6_write_text_palette_to_mem ; draw text string + lda CURRENT_LEVEL ; current level number + clc ; clear carry in preparation for addition + adc #$31 + sta $06fe,x ; draw current level + lda CURRENT_LEVEL ; current level + adc #$11 ; string id = level number + 11 + jmp load_bank_6_write_text_palette_to_mem ; draw text string + +; draw the scores +draw_the_scores: + lda #$09 ; a = #$09 (text string "hi") + jsr load_bank_6_write_text_palette_to_mem ; draw text string + lda HIGH_SCORE_LOW ; high score (low byte) + sta $00 + lda HIGH_SCORE_HIGH ; high score (high byte) + sta $01 + jsr @draw_score + lda #$0a ; a = $0a (text string "1p") + jsr load_bank_6_write_text_palette_to_mem ; draw text string + lda PLAYER_1_SCORE_LOW ; player 1 score (low byte) + sta $00 + lda PLAYER_1_SCORE_HIGH ; player 1 score (high byte) + sta $01 + jsr @draw_score + lda PLAYER_MODE ; single player vs two player ($00 = 1 player) + beq @exit ; don't draw player 2 if no player 2 + lda #$0b ; a = #$0b (text string "2p") + jsr load_bank_6_write_text_palette_to_mem ; draw text string + lda PLAYER_2_SCORE_LOW ; player 2 score (low byte) + sta $00 + lda PLAYER_2_SCORE_HIGH ; player 2 score (high byte) + sta $01 + +; prints the #$02-byte score stored in $01 and $02 on the screen +; $caf8 +@draw_score: + lda FRAME_COUNTER ; load frame counter + and #$10 ; only interested in 5th bit + bne @exit ; don't print score if frame counter low (used for flashing effect) + lda #$05 ; high score has a maximum of #$05 digits to print + sta $04 ; store maximum number of digits into $04 + +; digits are calculated from right to left, then two 0s are tacked to end (unless the score is 0) +@draw_score_digit: + lda #$0a ; ensuring the high score digits is below decimal 10 + sta $03 ; store $0a into $03 + jsr calculate_score_digit ; calculate next digit and store into $02 + lda $02 ; load the digit of the score to display from $02 + ora #$30 ; convert from number to character to display. In Contra 30 = 0, 31 = 1, etc. + sta $06fc,x ; draw digit + dex ; move to the previous digit (score drawn right to left) + lda $00 ; load current low byte of score + ora $01 ; combine low byte with high byte + beq @draw_score_end_zeros ; if both high and low bytes are 0, printing of the score is finished, add two 0s to end + dec $04 ; decrement the digit counter, only #$05 decimal digits are used + bne @draw_score_digit ; draw the next digit if less than #05 digits have been drawn + +@draw_score_end_zeros: + ldx GRAPHICS_BUFFER_OFFSET ; index int PPU character map + lda $04 ; load the number of digits remaining (starts at $05) + sec ; set carry flag in preparation for subtraction + sbc #$05 ; subtract 5 from total digits used, used to know if no digits have been printed + ora $02 ; see if score is #$00 + beq @draw_zero_score ; handle when the score is #$00 (don't tack on ending 00s) + lda #$30 ; set the character to display to 0 + sta $06fd,x ; display the character '0' + +@draw_final_0_exit: + sta $06fe,x ; display the character '0' + +@exit: + rts + +; handle the case when player score is $00, i.e. no points were scored +@draw_zero_score: + sta $06fc,x ; this is the first decimal digit of the score, set it to '0' + lda #$30 ; display the character '0' + bne @draw_final_0_exit ; always branch, to exit out of @draw_score_digit + +; graphic data to reset nametables (#$2a bytes) +; set nametable 0 ($2000) and nametable 1 ($2400) all to #$00 +; #$400 bytes (1 KiB) each +; first #$2 bytes are the nametable address ($2000) +; then repeatedly write #$78 zeros, then finally #$40 zeros. +; #$7f signifies keep reading to clear the $2400 nametable +; nametable data - writes addresses [$2000-$2800) +; CPU address $cb36 +graphic_data_00: +blank_nametables: + .byte $00,$20,$78,$00,$78,$00,$78,$00,$78,$00,$78,$00,$78,$00,$78,$00 + .byte $78,$00,$40,$00,$7f + +; first #$2 bytes are the nametable address (#$2400) +blank_nametable_2: + .byte $00,$24,$78,$00,$78,$00,$78,$00,$78,$00,$78,$00,$78,$00,$78,$00 + .byte $78,$00,$40,$00,$ff + +; write pattern tile (text) or palette information (color) to CPU offset CPU_GRAPHICS_BUFFER +; this is used when GRAPHICS_BUFFER_MODE is #$00, which defines the CPU_GRAPHICS_BUFFER format for text and palette data +; input +; * a - first six bits are index into the short_text_pointer_table +; when bit 7 set, write all blank characters instead of actual characters. Used for flashing effect +write_text_palette_to_mem: + pha ; push a to stack (index into short_text_pointer_table) + lda #$02 ; a = #$02 + sta $03 ; used when bit 7 of a is set, indicating to clear text + ; since the first #$02 bytes are PPU address, + ; $03 is used to prevent overwriting PPU address with #$00 + lda #$01 ; a = #$01 (next #$02 bytes are PPU address) + jsr write_a_to_cpu_graphics_buffer ; write #$01 to CPU_GRAPHICS_BUFFER,x + pla ; restore the index into short_text_pointer_table to print + sta $02 ; store index into short_text_pointer_table $02 + asl ; double index, since short_text_pointer_table is 2 bytes per label address + tax ; transfer index to x register + lda short_text_pointer_table,x ; read the low-byte memory address from pointer table + sta $00 ; store low byte of short_text_pointer_table address + lda short_text_pointer_table+1,x ; high byte of pointer table + sta $01 ; store high byte of short_text_pointer_table address + ldx GRAPHICS_BUFFER_OFFSET ; set X to store the next offset of CPU_GRAPHICS_BUFFER + ldy #$00 ; initialize character offset into string to 0 + +; read until #fe, #$fd, or #$ff and store CPU memory starting at CPU_GRAPHICS_BUFFER +@write_char_to_cpu_mem: + lda ($00),y ; read character from string + iny ; increment character offset + cmp #$ff ; #$ff signifies end of string, the #$ff isn't stored in CPU memory + beq set_x_to_offset_exit ; if #ff, the string has been completely loaded, restore x to GRAPHICS_BUFFER_OFFSET and exit + cmp #$fe ; like #$ff, #$fe is end of string, but #fe causes #$ff to be stored in CPU memory at end of string + beq @write_ff_to_cpu_memory ; store #$ff in CPU memory at the end of the string + cmp #$fd ; #fd specifies next two bytes are the PPU address, i.e. changing location on screen + beq @handle_fd ; branch if next two bytes are a new PPU address + sta CPU_GRAPHICS_BUFFER,x ; store character in CPU graphics buffer + lda $02 ; load the index into text string table into A, i.e. which string to print + bpl @next_char ; branch if text ins't being blanked (part of flashing animation) + lda $03 ; not writing characters, writing blanks to hide text + ; see if already written PPU address (#$02 bytes) + bne @dec_blank_delay ; branch if writing PPU address to CPU graphics buffer + sta CPU_GRAPHICS_BUFFER,x ; write #$00 character to cpu memory to blank the text for flashing animation + beq @next_char ; continue to next character to read + +@dec_blank_delay: + dec $03 ; decrement delay to allow writing PPU address before writing all #$00s for text + +@next_char: + inx ; move to next cpu graphics buffer write offset + bne @write_char_to_cpu_mem ; loop to read next character + +; #$fe encountered (end of string) +@write_ff_to_cpu_memory: + lda #$ff ; set the zero flag so next line jumps and #$ff is stored in CPU memory + bne write_to_700_offset ; always branch to write #$ff to CPU graphics buffer and exit + +; read next two bytes for PPU address +; used to write both CONTINUE and END together +@handle_fd: + lda #$ff ; #$ff tells CPU graphics buffer reading logic to prepare to read next segment of text + jsr write_to_700_offset ; write #$ff to cpu buffer + lda #$02 ; set to #$02 to skip blanking (zeroing) the PPU address when writing to CPU memory + sta $03 ; reset delay before blanking text (flashing animation) + lda #$01 ; vram_address_increment offset (#$01 = write across) + jsr write_to_700_offset ; write vram_address_increment offset + bne @write_char_to_cpu_mem ; branch to write next PPU address and string of text + lda #$ff ; not sure when this would execute !(WHY?) + bne write_a_to_cpu_graphics_buffer + +write_0_to_cpu_graphics_buffer: + lda #$00 + beq write_a_to_cpu_graphics_buffer ; always jumps since lda loads #$0, also it's the next line of code !(HUH) + +write_a_to_cpu_graphics_buffer: + ldx GRAPHICS_BUFFER_OFFSET ; update X to next location to write to in CPU_GRAPHICS_BUFFER offset + +; store A into string memory +write_to_700_offset: + sta CPU_GRAPHICS_BUFFER,x + inx + +set_x_to_offset_exit: + stx GRAPHICS_BUFFER_OFFSET + +vram_address_increment: + rts + +; table for PPU VRAM address increment (#$03 bytes) +; used to set whether the the VRAM address increments by 1 (across) or #$20 (down) +.byte $00,$04,$00 + +; writes the graphics data loaded in CPU_GRAPHICS_BUFFER to the PPU for drawing +write_cpu_graphics_buffer_to_ppu: + lda GRAPHICS_BUFFER_MODE ; load flag to determine if we should write the CPU_GRAPHICS_BUFFER to the PPU + bne @flush_cpu_graphics_buffer ; branch when non-zero. write CPU_GRAPHICS_BUFFER to the PPU + ldy #$00 ; GRAPHICS_BUFFER_MODE is #$00, zero out $08 + sty $08 ; clear $08 address override flag so graphics buffer can be read + +@read_cpu_mem_to_ppu: + lda $08 ; read previous high byte of PPU write address + cmp #$3f ; compare $08 to #$3f + bne @continue ; skip ahead if $08 is not equal to #$3f + sta PPUADDR ; !(WHY?) the following code doesn't make sense to me + lda #$00 ; it sets the PPUADDR to #$3f00, then #$0000 + sta PPUADDR ; but this is overwritten in the next few lines + sta PPUADDR ; that would have been executed regardless of what $08 was + sta PPUADDR ; this code seems to be able to be removed without issue !(WHY?) + +; CPU address $cbe5 +@continue: + ldx CPU_GRAPHICS_BUFFER,y ; read byte #$00 (used to set PPUCTRL) + beq @reset_graphics_buffer ; nothing to draw, exit + lda PPUCTRL_SETTINGS ; load PPUCTRL settings + and #$18 ; %0001 1000 + ora vram_address_increment,x ; include the VRAM increment offset + sta PPUCTRL ; set background and sprite pattern table addresses + iny + lda PPUSTATUS ; clear bit 7 and address latch used by PPUSCROLL and PPUADDR + lda CPU_GRAPHICS_BUFFER,y + sta $08 ; store high byte of PPU data write address into $08 + sta PPUADDR ; store high byte of PPU address + iny ; increment offset into CPU memory + lda CPU_GRAPHICS_BUFFER,y ; read next byte from CPU_GRAPHICS_BUFFER CPU memory address + sta PPUADDR ; store low byte of PPU data write address + iny ; increment CPU_GRAPHICS_BUFFER CPU read offset + cpx #$03 ; if vram_address_increment offset is 3 + bne @flush_graphics_buffer ; write graphics data until #$ff + lda CPU_GRAPHICS_BUFFER,y ; 0.3 mode - read total number of tiles/bytes to write to PPU + sta $09 ; store value in $09 + +; writes a block of bytes to PPU +@loop: + iny ; increment CPU_GRAPHICS_BUFFER read offset + lda CPU_GRAPHICS_BUFFER,y ; read graphic byte + sta PPUDATA ; store in PPU + dec $09 ; decrement total number of tiles/bytes to write + bne @loop ; loop if more tiles to write + +; sets first byte of CPU_GRAPHICS_BUFFER to #$00 so no drawing takes place for frame +@reset_graphics_buffer: + lda #$00 + sta CPU_GRAPHICS_BUFFER ; set initial byte to 0 + sta GRAPHICS_BUFFER_OFFSET ; set PPU pattern table offset to 0 + lda PPUCTRL_SETTINGS ; saved PPUCTRL settings (see configure_PPU) + sta PPUCTRL ; configure pattern table address, and sprite size + rts + +@write_ff_to_ppu: + lda #$ff ; a = $ff + +@write_byte_to_ppu: + sta PPUDATA + +; write to PPU until #$ff is encountered +@flush_graphics_buffer: + lda CPU_GRAPHICS_BUFFER,y ; read next byte to write to PPU + iny ; increment CPU_GRAPHICS_BUFFER read offset + cmp #$ff ; compare to end of data byte #$ff + bne @write_byte_to_ppu ; if not #$ff, then write to PPU + lda CPU_GRAPHICS_BUFFER,y ; byte was #$ff, see what next graphic byte is + cmp #$04 ; compare graphic byte to #$04 + bcs @write_ff_to_ppu ; branch if byte is greater than or equal to #$04 + bcc @read_cpu_mem_to_ppu ; continue writing CPU_GRAPHICS_BUFFER (0 mode or nonzero mode) + +; writes the entire graphics buffer to the PPU +; GRAPHICS_BUFFER_MODE nonzero mode +@flush_cpu_graphics_buffer: + ldx #$00 ; reset CPU_GRAPHICS_BUFFER read offset back to beginning + lda PPUCTRL_SETTINGS + and #$18 ; only care about nametable address and sprite pattern table address + sta $02 ; store in $02 for future use in PPUCTRL + +; every loop of this prints a few more columns of nametable +; every loop CPU_GRAPHICS_BUFFER is different +@write_to_ppu: + ldy CPU_GRAPHICS_BUFFER,x ; read first byte $700 + beq @reset_graphics_buffer ; if #$00, done - clear CPU_GRAPHICS_BUFFER + lda $02 + ora vram_address_increment,y + sta PPUCTRL + inx + lda CPU_GRAPHICS_BUFFER,x ; read first byte - length of data to write + sta $00 ; store length of data to write in group in $00 + inx ; increment CPU_GRAPHICS_BUFFER read offset + lda CPU_GRAPHICS_BUFFER,x ; read second byte - number of byte blocks to write + sta $01 ; set the number of blocks of memory to write + +; set the PPU write address based on the CPU_GRAPHICS_BUFFER +@set_PPU_write_address: + ldy $00 + inx + lda CPU_GRAPHICS_BUFFER,x + sta PPUADDR + inx + lda CPU_GRAPHICS_BUFFER,x + sta PPUADDR + +; loop through CPU_GRAPHICS_BUFFER block and write all bytes to PPU +@write_loop: + inx ; increment cpu read offset + lda CPU_GRAPHICS_BUFFER,x ; read graphics buffer byte + sta PPUDATA ; write byte to PPU + dey ; decrement PPU write byte count + bne @write_loop ; loop if not yet written entire block of bytes + dec $01 ; decrement number of byte blocks counter + bne @set_PPU_write_address ; if more byte blocks to write, loop to read PPU write address + inx ; no more data to write, increment cpu read offset + bne @write_to_ppu ; jump to see if another set of data to write, never fall through to line of code below + +; writes the palette data in cpu memory PALETTE_CPU_BUFFER to the ppu +; in the $3f00 ppu address range (palette data) +write_palette_colors_to_ppu: + ldx NUM_PALETTES_TO_LOAD ; load the number of palettes to write to PPU memory + beq graphics_loading_exit ; exit if #$00 + lda GRAPHICS_BUFFER_OFFSET + cmp #$30 + bcs graphics_loading_exit ; exit if the offset is >= #$30 + lda PPUCTRL_SETTINGS + and #$18 ; keep ...x x... + sta PPUCTRL ; ensure sprite and background pattern tables are configured correctly + lda #$3f ; set PPU write address to $3f00 (palette address range) + sta PPUADDR + lda #$00 + sta PPUADDR + tay + +; now write the palette data in the cpu buffer PALETTE_CPU_BUFFER +@loop: + lda PALETTE_CPU_BUFFER,y ; load the current palette + sta PPUDATA + iny + dex + bne @loop + lda #$3f + sta PPUADDR ; set ppu write address to $3f00 + stx PPUADDR ; store low byte of $3f00 + stx PPUADDR ; be sure it wrote correctly + stx PPUADDR ; be sure it wrote correctly + stx NUM_PALETTES_TO_LOAD ; set number of palettes to load to #$3f + ; don't think this value is ever read when it's #$3f, overwritten later + +graphics_loading_exit: + rts + +; load alternate tiles if necessary +alternate_tile_loading: + lda ALT_GRAPHIC_DATA_LOADING_FLAG ; whether or not to start loading alternate graphics + beq graphics_loading_exit ; if not loading alternate graphics, then exit + bmi set_alt_graphics_cpu_buffer ; already initialized alt graphics loading, continue loading. ALT_GRAPHIC_DATA_LOADING_FLAG is $80 + lda CURRENT_LEVEL ; load current level + asl + asl + adc CURRENT_LEVEL ; level = level * #$05 (each entry is #$05 bytes) + tay + lda alt_graphic_data_ptr_tbl,y ; load PPU write address low byte + sta $6c + lda alt_graphic_data_ptr_tbl+1,y ; load PPU write address high byte + sta $6d + lda alt_graphic_data_ptr_tbl+2,y ; load graphics data read location low byte + sta $6e + lda alt_graphic_data_ptr_tbl+3,y ; load graphics data read location high byte + sta $6f + lda alt_graphic_data_ptr_tbl+4,y ; load the number of tiles to change + beq alt_graphics_loading_complete ; if #$00 no data to change, so mark ALT_GRAPHIC_DATA_LOADING_FLAG as #$00 and exit + sta $70 + lda #$80 + sta ALT_GRAPHIC_DATA_LOADING_FLAG ; set flag to specify alternate graphics are currently loading + +set_alt_graphics_cpu_buffer: + ldx GRAPHICS_BUFFER_OFFSET + cpx #$10 + bcs graphics_loading_exit + lda #$01 ; a = #$01 + sta CPU_GRAPHICS_BUFFER,x + sta CPU_GRAPHICS_BUFFER+2,x + inx + lda #$20 ; a = #$20 + sta CPU_GRAPHICS_BUFFER,x + inx + inx + lda $6d ; PPU write address high byte + sta CPU_GRAPHICS_BUFFER,x + inx + lda $6c ; PPU write address low byte + sta CPU_GRAPHICS_BUFFER,x + inx + ldy #$00 ; y = #$00 + +@loop: + lda ($6e),y + sta CPU_GRAPHICS_BUFFER,x + iny + inx + cpy #$20 + bne @loop + stx GRAPHICS_BUFFER_OFFSET + dec $70 + beq alt_graphics_loading_complete + lda #$20 ; a = #$20 + ldx #$6e ; x = #$6e + jsr advance_graphic_read_addr ; advance address 6e-6f by 20 bytes + lda #$20 ; a = #$20 + ldx #$6c ; x = #$6c + jmp advance_graphic_read_addr ; advance address 6c-6d by 20 bytes + +alt_graphics_loading_complete: + lda #$00 ; a = #$00 + sta ALT_GRAPHIC_DATA_LOADING_FLAG ; mark flag as #$00 to specify no more alternate graphics data to load + rts + +; alternate graphics table, $08 entries each entry is $05 bytes long +; bank 2 +; Bytes 0-1: PPU address +; Bytes 2-3: CPU address +; Byte 4 : Number of Tiles to Change (x2) +alt_graphic_data_ptr_tbl: + .byte $80,$1a + .addr alt_graphic_data_00 + .byte $2c + + .byte $00,$10 + .addr alt_graphic_data_00 + .byte $00 + + .byte $60,$14 + .addr alt_graphic_data_01 + .byte $5d + + .byte $00,$10 + .addr alt_graphic_data_00 + .byte $00 + + .byte $a0,$16 + .addr alt_graphic_data_02 + .byte $1d + + .byte $80,$0a + .addr alt_graphic_data_03 + .byte $22 + + .byte $00,$10 + .addr alt_graphic_data_00 + .byte $00 + + .byte $60,$1b + .addr alt_graphic_data_04 + .byte $25 + +; create #$04 new pattern table tiles starting at PPU address $1fc0 +; it takes #$0f bytes per pattern table tile or #$40 bytes total +; it does this by taking a 'background' drawing of the tile, and then drawing electricity on top +animate_indoor_fence: + lda LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor + bmi @exit ; exit for indoor boss screen + lda INDOOR_SCREEN_CLEARED ; indoor screen cleared flag (0 = not cleared; 1 = cleared) + beq @draw_random_fence ; wall cores haven't been destroyed, draw random fence based on FRAME_COUNTER + lda #$80 ; screen cleared, don't draw fence, only floor, a = #$80 + sta INDOOR_SCREEN_CLEARED ; flag (0 = not cleared; 1 = cleared, #$80 = cleared, fence removed) + lda #$20 ; a = #$20 (pattern_tile_fence_tbl offset for no fence) + bne @continue ; always branch + +@draw_random_fence: + lda FRAME_COUNTER ; load frame counter + and #$03 ; random number [#$00-#$03] + bne @exit ; exit if not the 4th frame + lda FRAME_COUNTER ; change tiles every 4 frames + and #$0c ; grab either #$00, #$04, #$08, or #$0c + asl ; double to get #$00, #$08, #$10, or #$18 (offset into pattern_tile_fence_tbl) + +@continue: + sta $14 ; set pattern_tile_fence_tbl offset + ldx GRAPHICS_BUFFER_OFFSET ; load graphics buffer offset + lda #$01 ; a = #$01 + sta CPU_GRAPHICS_BUFFER,x ; set VRAM address increment to 0 (write across) + sta CPU_GRAPHICS_BUFFER+2,x ; set number of groups to #$01 group + inx ; increment graphics buffer write offset + lda #$40 ; a = #$40 + sta CPU_GRAPHICS_BUFFER,x ; set to group size to #$40 + inx ; increment graphics buffer write offset + inx ; increment graphics buffer write offset + lda #$1f ; a = #$1f + sta CPU_GRAPHICS_BUFFER,x ; set ppu address high byte to #$1f + inx ; increment graphics buffer write offset + lda #$c0 ; a = #$c0 + sta CPU_GRAPHICS_BUFFER,x ; set ppu address low byte to #$c0 + ; last #$04 bytes of pattern table 1 and then #$3c pattern tiles (PPU address $1fc0) + inx ; increment graphics buffer write offset + lda pattern_tile_bg_tbl ; load low byte of pattern_tile_bg_00 address + sta $10 ; store low byte of pattern_tile_bg_00 address + lda pattern_tile_bg_tbl+1 ; load high byte of pattern_tile_bg_00 address + sta $11 ; store high byte of pattern_tile_bg_00 address + lda #$07 ; a = #$07 + sta $13 ; set base address to CPU_GRAPHICS_BUFFER ($0700) + stx $12 ; store graphics buffer write offset + ; use ($12),y in @write_to_graphics_buffer instead of CPU_GRAPHICS_BUFFER,x + ; because x will be used for something else + ldx $14 ; load pattern_tile_fence_tbl offset + ldy #$00 ; y = #$00 + +@write_to_graphics_buffer: + lda ($10),y ; load pattern_tile_bg_00 byte + ; this is the 'background' portion of the pattern tile with no electricity + ora pattern_tile_fence_tbl,x ; merge with the electricity part of the drawing + sta ($12),y ; set graphics buffer byte + inx ; increment pattern_tile_fence_tbl offset + txa + and #$07 ; keep bits .... .xxx + ora $14 + tax + iny ; increment graphics buffer write offset used in this loop ($12),y + cpy #$40 ; see if written all #$40 pattern tile bytes (#$04 pattern tiles) to the graphics buffer + bcc @write_to_graphics_buffer ; branch if more tiles to write + tya + clc ; clear carry in preparation for addition + adc $12 + sta GRAPHICS_BUFFER_OFFSET + +@exit: + rts + +; pointer for the table directly below +; !(OBS) no need for a single entry table +pattern_tile_bg_tbl: + .addr pattern_tile_bg_00 + +; table for pattern tile backgrounds that will then have the electric fence drawn on top of (#$40 bytes) +; each #$f bytes is a single pattern table tile +pattern_tile_bg_00: + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$00,$00,$00,$00,$00,$00,$00,$00 ; solid square color 1 + .byte $00,$00,$00,$00,$00,$00,$00,$00,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff ; solid square color 2 + .byte $fe,$fc,$f8,$f0,$e0,$c0,$80,$00,$00,$01,$03,$07,$0f,$1f,$3f,$7f ; rising diagonal background (left side of fence) + .byte $7f,$3f,$1f,$0f,$07,$03,$01,$00,$00,$80,$c0,$e0,$f0,$f8,$fc,$fe ; falling diagonal background (right side of fence) + +; table for electric fence line that is drawn on top of the backgrounds +; in pattern_tile_bg_00 to create the pattern table tiles for the electric fence (#$28 bytes) +; first #$04 entries are squiggly electric fence, last is blank (no fence) +pattern_tile_fence_tbl: + .byte $00,$00,$04,$44,$eb,$32,$20,$00 + .byte $00,$00,$10,$30,$eb,$6a,$44,$00 + .byte $00,$00,$08,$0c,$d7,$56,$22,$00 + .byte $00,$00,$20,$22,$d7,$4c,$04,$00 + .byte $00,$00,$00,$00,$00,$00,$00,$00 ; no electric fence, just draw the ground/background part of the tile + +run_level_routine_for_demo: + lda LEVEL_ROUTINE_INDEX ; load the offset into the instruction pointer table + cmp #$04 + bne run_level_routine ; 4th subroutine is different, if not #$04, immediately go to run_level_routine + jsr simulate_input_for_demo ; if 4th subroutine (5th counting from 0), load bank 5 and run input simulation code + +; game routines - pointer 5 +; inside a level or in the demo. this routine runs the appropriate level_routine +game_routine_05: + lda LEVEL_ROUTINE_INDEX ; intro finished loading, start showing demo or actual game + +; runs the code specified at offset A in the level_routine_ptr_tbl +; run for all levels, but not for intro. Intro loads from game_routine_pointer_table +run_level_routine: + jsr run_routine_from_tbl_below ; run routine a in the following table (level_routine_ptr_tbl) + +; pointer table for main game (#$0b * #$02 = #$16 bytes) +; CPU address $ce35 +level_routine_ptr_tbl: + .addr level_routine_00 ; CPU address $ce4b - init APU, zero nametables, load default palette, and load level headers + .addr level_routine_01 ; CPU address $ce7e - display number of lives text for player(s) - REST xx + .addr level_routine_02 ; CPU address $ce9b - flashes score until timer expires, loads the pattern data, sets the sprite palettes, starts level music + .addr level_routine_03 ; CPU address $ced8 - animate nametable drawing, set sprite load type + .addr level_routine_04 ; CPU address $cee3 - routine run repeatedly while playing level + .addr level_routine_05 ; CPU address $cf2e - initialize level after finishing level, or game over + .addr level_routine_06 ; CPU address $cf9d - no more lives screen - shows score and "continue"/"end" option + .addr level_routine_07 ; CPU address $cfe1 - show game over screen until player presses start + .addr level_routine_08 ; CPU address $cfea - check for game over, otherwise wait for delay and play end of level tune + .addr level_routine_09 ; CPU address $d01f - run end of level sequence routines + .addr level_routine_0a ; CPU address $d02e - show game over score until GAME_OVER_DELAY_TIMER elapses, then move to level_routine_05 + +; main game - pointer 0 +; init APU, zero nametables, load default palette, and load level headers +level_routine_00: + jsr init_APU_channels + jsr zero_out_nametables ; reset both nametables to zeroes + lda #$06 ; load transition_screen_palettes (#$06th entry in short_text_pointer_table table bank 6) + jsr load_bank_6_write_text_palette_to_mem ; load palette for level name and hi score screen into PPU + ldy #$02 + jsr load_bank_number ; switch to bank 2 + lda CURRENT_LEVEL ; load current level + asl ; each level header is #$20 bytes so multiply by #20 + asl + asl + asl + asl + tay ; y is CPU memory read offset + ldx #$00 ; x is CPU memory write offset + stx BOSS_DEFEATED_FLAG ; set flag to false + stx LEVEL_END_PLAYERS_ALIVE ; clear players alive after defeating boss heart flag + +; ROM address $ce6a +; stores the $20 byte level header data in CPU memory in addresses $40 to $60 +; the level header is a $20 byte data structure containing information about the +; level. All 8 levels have their headers stored consecutively starting at +; $b319 in bank 2 +; +; For example, Level 1 contains the following information +; - $40 Location Type: Indoor ($00) +; - $41 Scrolling Type: Horizontal ($00) +; - $42 Level Screen Super-Tile Data Location : $8001 (Bank 2) +; - $44 Level Super-Tile Data Location: $8001 (Bank 3) +; - $46 Palette Data Location: $8671 (Bank 3) +; - $48 Alternate Graphics Loading Section: $0b (how far into level before loading alternate graphic data) +; - $49 Tile Collision Limits: $06 $f9 $ff +; - $4c Cycling Background Tile Palette Codes: $05 $08 $05 $08 +; - $50 Background Tile Palette Codes: $02 $03 $04 $05 +; - $54 Sprite Palette Codes: $00 $01 $22 $07 +; - $58 Stop Scrolling Section: $0b +; - $59 Mystery Byte: $00 +; - $5a-$5f Unused Bytes: $00 $00 $00 $00 $00 $00 +load_level_header: + lda level_headers,y ; load level header offset y from bank 2 + sta LEVEL_LOCATION_TYPE,x ; store into cpu addresses $40-$60 + iny ; increment CPU memory read offset + inx ; increment CPU memory write offset + cpx #$20 ; level header is #$20 bytes, check if all bytes been read + bne load_level_header ; read next byte if not complete + jsr init_ppu_write_screen_supertiles ; initialize PPU write addresses (nametable, attribute table), scroll, + ; and load super-tile indexes for current screen into LEVEL_SCREEN_SUPERTILES + jsr load_bank_0_load_level_enemies_to_mem ; load enemy routines bank (bank 0), and load level-specific enemies into $80 + +inc_level_routine_index: + inc LEVEL_ROUTINE_INDEX ; increment current level routine index + rts + +; display number of lives text for player(s) - REST xx +; executed once so it doesn't flash like the score +level_routine_01: + lda DEMO_MODE ; #$00 not in demo mode, #$01 demo mode on + bne skip_level_routine_01 ; jump when in demo mode + jsr draw_stage_and_level_name ; draw "STAGE" string + lda #$00 ; a = #$00 + sta $1e ; set PLAYER_MODE for use in draw_player_num_lives + jsr draw_player_num_lives ; draw player 1 number of lives (REST XX) + lda PLAYER_MODE ; number of players (0 = 1 player) + beq @continue ; branch if only one player number of lives to draw + sta $1e ; set player index to #$01, to draw number of lives for player 2 + jsr draw_player_num_lives ; draw player 2 number of lives (REST XX) + +@continue: + lda #$c0 ; set delay for score display screen for non-demo mode + +skip_level_routine_01: + sta DELAY_TIME_LOW_BYTE ; setup score display delays (only low byte used) + bne inc_level_routine_index ; set $2a to 02 to skip score display screen + +; flashes score until timer expires +; loads the pattern data for the level +; sets the sprite palettes +; starts level music +level_routine_02: + jsr decrement_delay_timer ; decrement timer (sets/clears zero flag) + bne draw_the_scores_1 ; jump if the timer has elapsed, i.e. finished flashing score, move on to load level + jsr zero_out_nametables ; the timer has elapsed, reset nametables (blank screen) + jsr load_level_graphics ; load level graphics + lda #$20 ; a = #$20 + jsr load_palettes_color_to_cpu ; load #$20 palette colors into PALETTE_CPU_BUFFER based on LEVEL_PALETTE_INDEX + lda CURRENT_LEVEL ; current level + asl + tay + lda level_vert_scroll_and_song,y + sta VERTICAL_SCROLL ; set initial vertical scroll + lda DEMO_MODE ; #$00 not in demo mode, #$01 demo mode on + bne @continue ; don't play level music/song when in demo mode + lda level_vert_scroll_and_song+1,y + jsr play_sound ; play level background music + +@continue: + lda #$ff ; a = #$ff + sta GRAPHICS_BUFFER_MODE ; non-zero graphics mode + inc LEVEL_ROUTINE_INDEX ; increment level routine + +level_routine_03_exit: + rts + +draw_the_scores_1: + jmp draw_the_scores + +; table for vertical adjustment and music theme ($08 * $02 = $10 bytes) +; first byte is the vertical adjustment +; second byte is the music theme code +level_vert_scroll_and_song: + .byte $e0,$2a ; level 1 - show bottom nametables (#$2800 and #$2c00) - sound_2a + .byte $e8,$3e ; level 2 - show bottom nametables (offset top #$8 pixels) (#$2800 and #$2c00) - sound_3e + .byte $00,$2e ; level 3 - vertical scroll is variable throughout level - sound_2e + .byte $e8,$3e ; level 4 - show bottom nametables (offset top #$8 pixels) (#$2800 and #$2c00) - sound_3e + .byte $e0,$32 ; level 5 - show bottom nametables (#$2800 and #$2c00) - sound_32 + .byte $e0,$36 ; level 6 - show bottom nametables (#$2800 and #$2c00) - sound_36 + .byte $e0,$2a ; level 7 - show bottom nametables (#$2800 and #$2c00) - sound_2a + .byte $e0,$3a ; level 8 - show bottom nametables (#$2800 and #$2c00) - sound_3a + +; animate nametable drawing, set sprite load type +level_routine_03: + jsr load_bank_3_init_lvl_nametable_animation ; load bank three and execute initial level nametable drawing animation + bne level_routine_03_exit ; exit if LEVEL_TRANSITION_TIMER has elapsed + lda #$ff ; a = #$ff + sta SPRITE_LOAD_TYPE ; set to load hud sprites + bne inc_level_routine_index ; increase level routine to level_routine_04 + +; CPU address $cee3 +; routine run repeatedly while playing level +level_routine_04: + jsr check_for_pause ; see if player is pausing or un-pausing + lda PAUSE_STATE ; #$00 for un-paused #$01 for paused + bne level_routine_04_exit ; exit level routine if the game is paused + lda BOSS_DEFEATED_FLAG ; 0 = boss not defeated, 1 = boss defeated + bne set_to_level_routine_08 ; if boss defeated, skip to level_routine_08 to begin level over sequence + +; level_routine_04 and level_routine_08 use this label +; checks to see if player(s) have game overed, if so, sets next level routine to +; be #$0a to begin game over sequence +; if not, then executes the various enemy logic +check_game_over_run_enemy_logic: + jsr set_frame_scroll_draw_player_bullets ; draw sprites, handle input + lda P1_GAME_OVER_STATUS ; player 1 game over state (1 = game over) + and P2_GAME_OVER_STATUS ; player 2 game over state (1 = game over) + bne init_game_over ; if both players have game over, show ending sequence + +; run various enemy logic that exists in bank 0 and bank 7 +; random enemies, all currently shown enemy logic, palette updates, etc. +; generate random soldiers if appropriate +; run in level_routine_04 and level_routine_0a +run_level_enemy_logic: + jsr load_bank_3_handle_scroll ; handles scrolling for the level if currently scrolling + ; handles updating nametable, attribute table, and loading alternate graphics as appropriate + jsr load_bank_0_exe_all_enemy_routine ; execute all enemy routine logic + jsr load_bank_2_load_screen_enemy_data + jsr load_bank_2_exe_soldier_generation ; run soldier generation routine + jsr load_palette_indexes + jsr load_bank_2_alternate_tile_loading ; load alternate tiles if necessary + +; also executed from level_routine_08 +level_routine_04_exit: + rts + +; initializes the game over timer (delay before showing score) +; goes to level routine #$0a +init_game_over: + lda #$60 ; set timer to delay showing scores + sta GAME_OVER_DELAY_TIMER ; initialize timer to #$60 + lda #$0a + bne set_a_as_current_level_routine ; set next level routine to #$0a to wait for delay + +set_to_level_routine_05: + lda #$00 ; a = #$00 + sta BOSS_DEFEATED_FLAG ; reset flag to false now that GAME_OVER_DELAY_TIMER has elapsed + lda #$05 ; a = #05 (level_routine_05) + jsr set_a_as_current_level_routine ; change level routine to show high score + +set_graphics_zero_mode: + lda #$00 ; a = #$00 + sta GRAPHICS_BUFFER_MODE ; + sta CPU_GRAPHICS_BUFFER + jmp init_APU_channels + +set_to_level_routine_08: + lda #$08 ; a = #$08 + +set_a_as_current_level_routine: + sta LEVEL_ROUTINE_INDEX ; set the next level routine to run + lda #$00 ; a = #$00 + sta END_LEVEL_ROUTINE_INDEX ; clear end of level routine index + rts + +; initialize level after finishing level, or game over +level_routine_05: + lda #$00 ; a = #$00 + sta SPRITE_LOAD_TYPE ; set to load normal sprites + sta INDOOR_SCREEN_CLEARED ; indoor screen cleared flag (0 = not cleared; 1 = cleared) + lda P1_CURRENT_WEAPON + sta $10 ; temporarily store current weapon in $10 + lda P2_CURRENT_WEAPON ; current weapon code (player 2) + sta $11 ; temporarily store current weapon in $11 + ldx #$40 ; x = #$40 (set to 30 for game over after lvl 1) + jsr clear_memory_starting_a_x ; clear level header data, player data, sprite buffer, and super-tile buffer + lda BOSS_DEFEATED_FLAG ; 0 = boss not defeated, 1 = boss defeated + beq show_game_over_screen ; in level_routine_05 and boss wasn't defeated, game over + ; unless demo mode (shouldn't happen because demos don't reach end of level), then just set DEMO_LEVEL_END_FLAG and exit + lda $10 ; restore current weapon from $10 for player 1 + sta P1_CURRENT_WEAPON + lda $11 ; restore current weapon from $11 for player 2 + sta P2_CURRENT_WEAPON ; current weapon code (player 2) + inc CURRENT_LEVEL ; increment current level + lda CURRENT_LEVEL ; current level + cmp #$08 ; if greater than last level, start game ending sequence + bcc load_level_intro ; jump if level is less than the last level + jsr inc_routine_index_set_timer ; completed last level, start ending sequence + inc GAME_COMPLETION_COUNT ; increment game completion count, used mainly to increase enemy difficulty every play-through + lda #$09 + sta CURRENT_LEVEL ; set current level to #$09, this is interpreted as the ending sequence + bne level_routine_05_exit ; always jump since lda #$09 will set the Z flag #$00 + +; loads the pattern table tiles to the level intro screen +; shows player scores, number of lives, high score, stage number, and level name +load_level_intro: + lda #$0a ; set offset to point to intro_graphic_data_00 -> graphic_data_01 -> (pattern table for screen) + jsr load_A_offset_graphic_data ; load graphic data 0a + +level_routine_05_exit: + lda #$00 + sta LEVEL_ROUTINE_INDEX ; go back to level_routine_00 + sta END_LEVEL_ROUTINE_INDEX ; clear end level routine + sta SPRITE_LOAD_TYPE ; set to load normal sprites + rts + +; game over, unless demo mode, then set DEMO_LEVEL_END_FLAG +show_game_over_screen: + lda DEMO_MODE ; #$00 not in demo mode, #$01 demo mode on + bne @set_demo_end_exit ; skip to end when in demo mode + jsr zero_out_nametables ; reset nametables 0-1 to zeroes + lda #$0a ; set offset to point to intro_graphic_data_00 -> graphic_data_01 -> (pattern table for screen) + jsr load_A_offset_graphic_data ; load graphic_data_01 + lda #$06 ; a = #$06 transition_screen_palettes (palettes for intro screen) + jsr load_bank_6_write_text_palette_to_mem ; draw text string + lda #$0d ; a = #$0d text_game_over (game over) + jsr load_bank_6_write_text_palette_to_mem ; draw text string + lda #$4e ; a = #$4e (sound_4e) + jsr play_sound ; play game over sound + dec NUM_CONTINUES ; subtract from number of lives remaining + bmi @no_continues_remaining ; if run out of continues, jump + lda #$0e ; a = #$0e (continue end) + jsr load_bank_6_write_text_palette_to_mem ; draw text string + jmp inc_level_routine_index + +@no_continues_remaining: + lda #$07 ; a = #$07 + jmp set_a_as_current_level_routine + +; in demo mode and +@set_demo_end_exit: + inc DEMO_LEVEL_END_FLAG ; set value indicating demo for level is complete + rts + +; no more lives screen - shows score and "continue"/"end" option +; resets player score and goes back to level_routine_00 if player selects continue +; resets game routine back to #$00 if player selects end +level_routine_06: + lda CONTROLLER_STATE_DIFF ; controller 1 buttons pressed + and #$10 ; keep bits ...x .... (start button) + beq @check_select_button ; if start button isn't pressed, jump + jsr init_APU_channels + lda CONT_END_SELECTION ; determine cursor position between "CONTINUE" and "END" + bne reset_game_routine ; exit game if end was selected + jsr reset_players_score ; continue was selected, reset the player scores back to #$00 + lda #$00 + sta LEVEL_ROUTINE_INDEX ; set to level_routine_00 + sta SPRITE_X_POS + sta SPRITE_Y_POS + sta CPU_SPRITE_BUFFER + rts + +@check_select_button: + lda CONTROLLER_STATE_DIFF ; controller 1 buttons pressed + and #$20 ; select button + beq @set_cursor_sprite_and_scores ; jump if select isn't pressed + lda CONT_END_SELECTION ; load which option is current selected + eor #$01 ; swap selection between "CONTINUE"/"END", i.e. flip bits .... ...x + sta CONT_END_SELECTION ; save swapped setting + +@set_cursor_sprite_and_scores: + lda #$52 ; hard-code the horizontal position of the cursor sprite + sta SPRITE_X_POS + lda #$aa ; sprite_aa: player selector cursor (yellow falcon) + sta CPU_SPRITE_BUFFER ; set cursor as first (and only) sprite to draw + ldx CONT_END_SELECTION ; load whether "CONTINUE" or "END" is selected + lda player_select_cursor_pos,x ; load the vertical position on the screen for the cursor + sta SPRITE_Y_POS ; set Y position in CPU memory + jmp draw_the_scores ; draw the scores + +; resets GAME_ROUTINE_INDEX to #$00 and resets delay timer +reset_game_routine: + lda #$00 + jmp set_game_routine_index_to_a ; set GAME_ROUTINE_INDEX to #$00 and reset delay timer to #$0240 + +; show game over screen until player presses start +; once player presses start, game is reset +level_routine_07: + lda CONTROLLER_STATE_DIFF ; controller 1 buttons pressed + and #$10 ; check for start button + bne reset_game_routine ; reset the game if start button was pressed + jmp draw_the_scores ; start button not pressed, continue to show score + +; check for game over, otherwise wait for delay and play end of level tune +level_routine_08: + jsr check_game_over_run_enemy_logic ; check to see if player(s) have game overed + ; if so, sets next level routine to be #$0a to begin game over sequence + ; otherwise, execute various enemy logic + lda LEVEL_ROUTINE_INDEX + cmp #$0a ; check_game_over_run_enemy_logic sets LEVEL_ROUTINE_INDEX to #$0a + ; when both players are in game over state + beq level_routine_exit ; if game over, simply exit, next routine will be level_routine_0a + ldy DELAY_TIME_LOW_BYTE ; not game over, load delay + beq @continue ; branch to continue if delay has elapsed + iny ; delay timer has not elapsed, increment + beq level_routine_exit ; exit if delay was #$ff + jsr decrement_delay_timer ; delay wasn't #$ff, decrement full #$02 byte delay + bne level_routine_exit ; exit if timer hasn't elapsed + +@continue: + ldx #$01 ; x = #$01 + ldy #$00 ; initialize number of alive players to #$00 + +@player_loop: + lda P1_GAME_OVER_STATUS,x ; load game over state for player x (1 = game over) + bne @next_player_adv_lvl_index ; branch if game over + lda PLAYER_STATE,x ; game not over, load player state (0 = dropping into level, 1 = normal, 2 = dead, 3 = can't move) + cmp #$01 ; compare to normal state + bne @next_player_adv_lvl_index ; branch if player not in normal state + iny + +@next_player_adv_lvl_index: + dex ; decrement player index (0 = p1, 1 = p2) + bpl @player_loop ; branch if still need to evaluate player 1 + tya ; transfer number of alive players to a + sta LEVEL_END_PLAYERS_ALIVE ; set total number of alive players at level end + beq level_routine_exit + lda #$46 ; a = #$46 (sound_46) + jsr play_sound ; play end of level sound + inc LEVEL_ROUTINE_INDEX ; move to next level routine + +level_routine_exit: + rts + +; run end of level sequence routines +level_routine_09: + jsr load_bank_3_run_end_lvl_sequence_routine + jsr set_frame_scroll_draw_player_bullets + jsr load_bank_3_handle_scroll ; handles scrolling for the level if currently scrolling + ; handles updating nametable, attribute table, and loading alternate graphics as appropriate + jsr load_bank_0_exe_all_enemy_routine + jmp load_palette_indexes + +; waits for GAME_OVER_DELAY_TIMER to elapse and then move to level_routine_05 +; to show game over score +level_routine_0a: + jsr run_level_enemy_logic + dec GAME_OVER_DELAY_TIMER ; decrement timer (initialized to #$60) + bne level_routine_exit ; wait for GAME_OVER_DELAY_TIMER to elapse + jmp set_to_level_routine_05 ; go to level_routine_05 to show game over high score + +; checks for start button and sets pause status as appropriate +; plays sound if entering pause +check_for_pause: + lda DEMO_MODE ; #$00 not in demo mode, #$01 demo mode on + ora $26 + ora PPU_READY ; #$00 when PPU is ready, > #$00 otherwise + bne pause_exit ; if in demo, PPU isn't ready, or $26 > 0, then exit + lda CONTROLLER_STATE_DIFF ; controller 1 buttons pressed + ldy PAUSE_STATE ; #$01 for paused, #$00 for not paused + bne @game_paused ; if game paused, jump + and #$10 ; keep bits ...x .... (check for start button) + beq pause_exit ; exit if start button isn't pressed + lda #$01 ; a = #$01 + sta PAUSE_STATE ; #$01 for paused, #$00 for not paused + lda #$54 ; a = #$54 (54 = game pausing jingle sound) + jmp play_sound ; play game pausing jingle sound + +; handle game paused state +; un-pauses if necessary +@game_paused: + jsr draw_player_bullet_sprites ; draw half of the bullets in alternating frames + jsr load_bank_2_set_players_paused_sprite_attr ; continue animating player sprite attributes while paused (electrocuted, invincible, etc.) + lda CONTROLLER_STATE_DIFF ; controller 1 buttons pressed + and #$10 ; keep bits ...x .... (check for start button) + beq pause_exit ; exit if start button isn't pressed + lda #$00 ; a = #$00 + sta PAUSE_STATE ; set game state to not paused + +pause_exit: + rts + +; load the alternate graphics +; CPU address $d064 +load_alternate_graphics: + lda #$ff ; a = #$ff + sta LEVEL_ALT_GRAPHICS_POS ; prevent any further attempt to load alternate graphics + lda CURRENT_LEVEL ; prepare to determine index into lvl_alt_collision_and_palette_tbl, each level has #$f bytes + asl + asl + asl + asl + sec ; set the carry flag in preparation for subtraction + sbc CURRENT_LEVEL ; multiply by 16 and subtract level to get 15 * level number, i.e. 16n - n == 15n + tay + ldx #$00 ; set cpu mem write offset to #$00 + +; loop to overwrite level palette and collision info [$49-$57] +@loop: + lda lvl_alt_collision_and_palette_tbl,y + sta COLLISION_CODE_1_TILE_INDEX,x + iny ; increment read offset + inx ; increment write offset + cpx #$0f ; see if all #$f bytes have been written to cpu buffer + bne @loop ; if not yet finished, loop + lda #$20 ; set to reload #$20 palettes + +; loads palette colors into PALETTE_CPU_BUFFER based on cpu memory LEVEL_PALETTE_INDEX +; a - the number of palette colors to load (including hard-coded black per palette) +; #$10 (4 palettes) or #$20 (8 palettes) depending on loading nametable colors, or both nametable and sprite colors +load_palettes_color_to_cpu: + sta $02 ; store number of palette colors to load to CPU memory + sta NUM_PALETTES_TO_LOAD ; set number of palette colors to load + lda FRAME_COUNTER ; load frame counter + and #$30 ; keep bits ..xx .... + sta $03 ; store the masked frame number in $03 (I don't believe this is used) !(WHY?) + ldx #$00 ; initialize colors written counter + stx $00 ; set LEVEL_PALETTE_INDEX read offset to #$00 + +; read $02 palette colors starting level palette index $00 +; store actual palette colors from table game_palettes into cpu memory +; starting at PALETTE_CPU_BUFFER +; input +; * x - number of colors already written +; * $00 - current palette index to load (indexes into LEVEL_PALETTE_INDEX) +; * $02 - total number of palette colors to load +; game_palette_ptr_tbl only has one entry, not sure why it was used in the first place !(HUH) +; it complicates loading the index from the game_palettes table +load_palette_colors_to_cpu: + lda #$00 ; a = #$00 + sta $07 ; reset $07 to #$00 + ldy $00 ; load LEVEL_PALETTE_INDEX read offset [#$00-#$08) + lda LEVEL_PALETTE_INDEX,y ; load background palette index into game_palette_ptr_tbl + asl + adc LEVEL_PALETTE_INDEX,y ; double and add one (multiply by 3) - palettes are 3 (1-byte) colors + ; at this point, the relative offset is known, but now the address must be computed + rol $07 ; if there was a carry (relative offset >= #$80), push into high byte + adc game_palette_ptr_tbl ; add relative offset to low byte of game_palettes address (#$27) + sta $06 ; store low byte of offset into game_palettes into $06 + lda $07 ; reload high byte (could be #$01 if relative palette was >= #$80) + adc game_palette_ptr_tbl+1 ; add high byte of game_palettes pointer address to high byte of game_palettes address + sta $07 ; store offset into game_palettes into $07, know the exact address is stored in $(06) + ldy #$00 ; y = #$00 + lda #$0f ; a = #$0f (basic black for all palettes) + sta PALETTE_CPU_BUFFER,x ; store the universal background color in cpu buffer + ; every palette has black as its first color + inx ; increment cpu buffer write offset + +read_palette_loop: + lda BG_PALETTE_ADJ_TIMER ; see if a palette color shift timer was used + bne shift_bg_palette_color ; adjust palette color if BG_PALETTE_ADJ_TIMER is non-zero + +; reads the palette color from the game_palettes table +read_palette_color: + lda ($06),y ; read the palette color + +; store the palette color in A register to CPU memory +write_palette_color_a_to_cpu_mem: + sta PALETTE_CPU_BUFFER,x ; store palette color into CPU memory + iny ; increment read offset + inx ; increment cpu memory buffer write offset + cpy #$03 ; see if read all #$03 colors of the palette + bne read_palette_loop ; branch if haven't yet loaded entire palette (palettes are 3 colors) + inc $00 ; finished reading palette, increment level palette index read offset + cpx $02 ; see if written all the colors to the cpu buffer + bne load_palette_colors_to_cpu ; if more palette colors to load, loop back and load them + lda BG_PALETTE_ADJ_TIMER ; load palette color shift timer + beq @exit ; exit if #$00 + bmi @exit ; exit if < #$00 + dec BG_PALETTE_ADJ_TIMER ; decrement palette color shift timer + +@exit: + rts + +; adjust nametable palette color based on BG_PALETTE_ADJ_TIMER to create fading effect +; while timer is out of range [#$01-#$09] only black is drawn, but once in range colors will be adjusted +; * for non-indoor boss screens, the first palette is not modified +; * only nametable palettes are modified and not sprite palettes +; * used on indoor levels between sections, dragon and boss ufo fade-in effect, and on boss mouth +; input +; * y - LEVEL_PALETTE_INDEX read offset +; * x - number of palette colors written (including hard-coded black) +shift_bg_palette_color: + sty $03 ; backup the read offset into y + ldy LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor + bmi @continue ; branch if indoor (base) level boss screen shown (LEVEL_LOCATION_TYPE set to #$80) + ldy $03 ; for non-indoor boss screens, the first palette is not modified, restore the read offset from $03 to check + cpx #$04 ; compare palette color offset to #$04 + bcc read_palette_color ; branch if still reading the first palette, as that palette isn't modified for non-indoor boss screens + +@continue: + ldy $03 ; load palette color read offset + cpx #$10 ; see if all of the nametable sprites have been written + bcs read_palette_color ; branch if loading a sprite palette, those aren't modified + lda BG_PALETTE_ADJ_TIMER ; load nametable palette modification timer for palette change + bmi @write_black_to_cpu_mem ; just write black if BG_PALETTE_ADJ_TIMER is negative + cmp #$09 ; see if BG_PALETTE_ADJ_TIMER is in valid range to start modifying palette color + bcs @write_black_to_cpu_mem ; just write black if BG_PALETTE_ADJ_TIMER isn't yet in range + tay ; transfer BG_PALETTE_ADJ_TIMER to offset register + lda palette_shift_amount_tbl-1,y ; load the amount to shift the palette color by (1-indexed) + sta $04 ; store palette color shift amount in $04 + ldy $03 ; load the palette color read offset + lda ($06),y ; read palette color + sec ; set carry flag in preparation for subtraction + sbc $04 ; subtract the amount specified in palette_shift_amount_tbl from palette color + bcs write_palette_color_a_to_cpu_mem ; write modified palette color if result wasn't negative + ; otherwise continue to just write black + +@write_black_to_cpu_mem: + lda #$0f ; a = #$0f + bne write_palette_color_a_to_cpu_mem ; always jump + +; amount to subtract from palette color when BG_PALETTE_ADJ_TIMER is between #$01 and #$09 (1-indexed) +palette_shift_amount_tbl: + .byte $00,$00,$10,$10,$20,$20,$30,$30 + +; load the appropriate palette colors based on level and LEVEL_PALETTE_CYCLE +; stores appropriate game_palettes indexes into LEVEL_PALETTE_INDEX+2 and LEVEL_PALETTE_INDEX+3 +load_palette_indexes: + lda NUM_PALETTES_TO_LOAD ; load the number of palette indexes to update + cmp GAME_ROUTINE_INDEX ; current game routine index + bcs palette_mod_exit ; exit if NUM_PALETTES_TO_LOAD >= GAME_ROUTINE_INDEX + lda FRAME_COUNTER ; load frame counter + and #$07 ; keep bits .... .xxx + cmp #$05 ; see if the last 3 bits are #$05 (every #$8 frames) + bne falcon_weapon_flash ; branch if not equal to #$07 (do not increment LEVEL_PALETTE_CYCLE) + lda PAUSE_PALETTE_CYCLE ; see if palette cycling has been paused (ice field tanks pause palette cycling) + bne falcon_weapon_flash + inc LEVEL_PALETTE_CYCLE ; move to next set of palette colors for the 4th background palette + lda LEVEL_PALETTE_CYCLE + ldy CURRENT_LEVEL ; current level + cmp lvl_palette_animation_count,y ; see how many palette cycles there are for the level (level 3 only has 3, every other level has 4) + bcc @continue ; branch if current cycle less than max (LEVEL_PALETTE_CYCLE < lvl_palette_animation_count,y) + lda #$00 ; a = #$00 + sta LEVEL_PALETTE_CYCLE ; exceeded number of palettes in level animation, set back to #$00 for next loop + +@continue: + tay + lda LEVEL_PALETTE_CYCLE_INDEXES,y ; load current palette colors for cycling background tiles + sta LEVEL_PALETTE_INDEX+3 ; store index into palette code 3 for background tiles + lda LEVEL_LOCATION_TYPE ; see if have gotten to the indoor (base) level boss screen + bmi set_indoor_boss_palette_2_animation ; player has gotten to indoor boss and value has been set to #$80, jump + lda CURRENT_LEVEL ; current level + beq load_palettes_color_to_cpu_2_index ; skip ahead to load level 1 palette indexes for enemy flashing red + cmp #$07 ; check if level 8 + beq load_10_sprite_palettes + cmp #$08 ; check if ending + beq set_ending_palette_animation + lda LEVEL_ALT_GRAPHICS_POS ; see status of loading alternate graphics + bmi load_10_sprite_palettes ; branch if alternate graphics are still being loaded (not yet done) + +; update the 3rd nametable palette colors (flashing red lights effect) +load_palettes_color_to_cpu_2_index: + lda level_palette_2_index,y + +set_a_to_palette_2: + sta LEVEL_PALETTE_INDEX+2 + +load_10_sprite_palettes: + lda #$10 ; a = #$10 + jsr load_palettes_color_to_cpu ; load #$10 palette colors into PALETTE_CPU_BUFFER based on LEVEL_PALETTE_INDEX + +falcon_weapon_flash: + lda FALCON_FLASH_TIMER ; falcon weapon flash timer + beq palette_mod_exit ; exit if timer has elapsed + dec FALCON_FLASH_TIMER ; falcon weapon flash timer + lda FALCON_FLASH_TIMER + lsr + bcs palette_mod_exit + and #$03 ; keep bits .... ..xx + tay + lda falcon_weapon_flash_tbl,y + sta PALETTE_CPU_BUFFER+16 + sta PALETTE_CPU_BUFFER+20 + sta PALETTE_CPU_BUFFER+24 + sta PALETTE_CPU_BUFFER+28 + lda #$20 ; offset #$20 into palette buffer + sta NUM_PALETTES_TO_LOAD ; number of palettes to write to cpu buffer + +palette_mod_exit: + rts + +; updates the 3rd palette code based on LEVEL_PALETTE_CYCLE for indoor (base) level boss screen +set_indoor_boss_palette_2_animation: + lda CURRENT_LEVEL ; current level (going to be either #$01 or #$03) + and #$02 ; differentiate which indoor level (#$00 for first base and #$02 for second base level) + asl ; double since each level has #$4 entries allowing second base to start at #$04 + adc LEVEL_PALETTE_CYCLE ; add to current palette cycle iteration + tay + lda indoor_boss_palette_2_index,y ; + sta LEVEL_PALETTE_INDEX+2 ; palette code 2 for background tiles + jmp load_10_sprite_palettes + +set_ending_palette_animation: + lda ending_palette_2_index,y + bne set_a_to_palette_2 + +; number of palettes to cycle through per level (LEVEL_PALETTE_CYCLE) +lvl_palette_animation_count: + .byte $04,$04,$03,$04,$04,$04,$04,$04,$04 + +; the palette indexes for the indoor boss screens +indoor_boss_palette_2_index: + .byte $13,$14,$15,$14 ; first indoor (base) palette code 2 animation cycle + .byte $1b,$1c,$1d,$1c ; second indoor (base) palette code 2 animation cycle + +; flashing effect color codes for falcon weapon ($04 bytes) +falcon_weapon_flash_tbl: + .byte $0f,$30,$16,$11 + +; palette code 2 palette indexes into game_palettes shared among all levels +; animation for flashing red colors on enemies +level_palette_2_index: + .byte $04,$5c,$04,$5d + +ending_palette_2_index: + .byte $66,$6a,$6b,$6a ; table for ending scene ($04 bytes) + +; tables for alternate collision limits and palettes ($08 * $0f = $78 bytes) +; corresponds to [$49-$57] +lvl_alt_collision_and_palette_tbl: + .byte $06,$a8,$a8,$23,$23,$23,$23,$02,$03,$04,$23,$00,$01,$22,$07 ; level 1 + .byte $00,$ff,$ff,$16,$17,$18,$17,$11,$12,$13,$16,$00,$01,$22,$21 ; level 2 + .byte $07,$ff,$ff,$27,$54,$55,$54,$0b,$25,$26,$27,$00,$01,$22,$07 ; level 3 + .byte $00,$ff,$ff,$1e,$1f,$20,$1f,$19,$1a,$1c,$1e,$00,$01,$22,$2b ; level 4 + .byte $20,$f0,$f0,$42,$42,$42,$42,$3d,$3e,$40,$42,$00,$01,$22,$06 ; level 5 + .byte $0c,$de,$de,$3a,$3b,$3a,$3c,$39,$39,$04,$3a,$00,$01,$22,$56 ; level 6 + .byte $0e,$f1,$f1,$5a,$5f,$5a,$5b,$45,$46,$59,$5f,$00,$01,$22,$07 ; level 7 + .byte $05,$b6,$b6,$4b,$50,$4b,$50,$48,$49,$4a,$4b,$00,$01,$43,$44 ; level 8 + .byte $00,$00,$00,$67,$68,$69,$68,$25,$65,$66,$67,$6d,$6c,$22,$64 ; ending animation + +; pointer for palettes table ($02 bytes) +; CPU address $d225 +game_palette_ptr_tbl: + .addr game_palettes ; CPU address $d227 + +; palettes ($6e * $03 = $14a bytes) +; CPU Address $d227 +game_palettes: + .byte COLOR_PALE_ORANGE_37 ,COLOR_MED_VIOLET_12 ,COLOR_BLACK_0f + .byte COLOR_PALE_RED_36 ,COLOR_MED_RED_16 ,COLOR_BLACK_0f + .byte COLOR_MED_FOREST_GREEN_19 ,COLOR_LT_FOREST_GREEN_29 ,COLOR_DARK_OLIVE_08 + .byte COLOR_LT_OLIVE_28 ,COLOR_MED_OLIVE_18 ,COLOR_DARK_OLIVE_08 + .byte COLOR_MED_RED_16 ,COLOR_WHITE_30 ,COLOR_LT_GRAY_10 + .byte COLOR_MED_BLUE_11 ,COLOR_LT_BLUE_21 ,COLOR_WHITE_30 + .byte COLOR_MED_RED_16 ,COLOR_WHITE_20 ,COLOR_DARK_GRAY_00 + .byte COLOR_WHITE_20 ,COLOR_DARK_GRAY_00 ,COLOR_BLACK_0f + .byte COLOR_MED_BLUE_11 ,COLOR_WHITE_30 ,COLOR_LT_BLUE_21 + .byte COLOR_LT_GRAY_10 ,COLOR_DARK_GRAY_00 ,COLOR_DARK_FOREST_GREEN_09 + .byte COLOR_DARK_GRAY_00 ,COLOR_DARK_TEAL_0c ,COLOR_WHITE_20 + .byte COLOR_DARK_GREEN_0a ,COLOR_MED_GREEN_1a ,COLOR_DARK_GRAY_00 + .byte COLOR_LT_OLIVE_28 ,COLOR_MED_OLIVE_18 ,COLOR_DARK_ORANGE_07 + .byte COLOR_MED_TEAL_1c ,COLOR_LT_TEAL_2c ,COLOR_DARK_TEAL_0c + .byte COLOR_DARK_TEAL_0c ,COLOR_MED_TEAL_1c ,COLOR_LT_TEAL_2c + .byte COLOR_LT_TEAL_2c ,COLOR_DARK_TEAL_0c ,COLOR_MED_TEAL_1c + .byte COLOR_LT_GRAY_10 ,COLOR_LT_BLUE_GREEN_2b ,COLOR_BLACK_0f + .byte COLOR_DARK_RED_06 ,COLOR_DARK_GRAY_00 ,COLOR_DARK_OLIVE_08 + .byte COLOR_WHITE_20 ,COLOR_DARK_GRAY_00 ,COLOR_DARK_OLIVE_08 + .byte COLOR_DARK_RED_06 ,COLOR_DARK_GRAY_00 ,COLOR_DARK_OLIVE_08 + .byte COLOR_MED_RED_16 ,COLOR_DARK_GRAY_00 ,COLOR_DARK_OLIVE_08 + .byte COLOR_LT_RED_26 ,COLOR_DARK_GRAY_00 ,COLOR_DARK_OLIVE_08 + .byte COLOR_WHITE_20 ,COLOR_DARK_GRAY_00 ,COLOR_DARK_RED_06 + .byte COLOR_WHITE_20 ,COLOR_DARK_GRAY_00 ,COLOR_MED_RED_16 + .byte COLOR_WHITE_20 ,COLOR_DARK_GRAY_00 ,COLOR_LT_RED_26 + .byte COLOR_DARK_TEAL_0c ,COLOR_MED_OLIVE_18 ,COLOR_DARK_FOREST_GREEN_09 + .byte COLOR_WHITE_20 ,COLOR_MED_OLIVE_18 ,COLOR_DARK_FOREST_GREEN_09 + .byte COLOR_DARK_RED_06 ,COLOR_MED_OLIVE_18 ,COLOR_DARK_FOREST_GREEN_09 + .byte COLOR_MED_RED_16 ,COLOR_MED_OLIVE_18 ,COLOR_DARK_FOREST_GREEN_09 + .byte COLOR_LT_RED_26 ,COLOR_MED_OLIVE_18 ,COLOR_DARK_FOREST_GREEN_09 + .byte COLOR_WHITE_20 ,COLOR_MED_OLIVE_18 ,COLOR_DARK_RED_06 + .byte COLOR_WHITE_20 ,COLOR_MED_OLIVE_18 ,COLOR_MED_RED_16 + .byte COLOR_WHITE_20 ,COLOR_MED_OLIVE_18 ,COLOR_LT_RED_26 + .byte COLOR_WHITE_20 ,COLOR_LT_VIOLET_22 ,COLOR_DARK_VIOLET_02 + .byte COLOR_WHITE_20 ,COLOR_LT_RED_26 ,COLOR_MED_RED_16 + .byte COLOR_DARK_BLUE_01 ,COLOR_WHITE_30 ,COLOR_LT_GRAY_10 + .byte COLOR_PALE_RED_36 ,COLOR_DARK_RED_06 ,COLOR_DARK_VIOLET_02 + .byte COLOR_WHITE_30 ,COLOR_LT_GRAY_10 ,COLOR_DARK_GRAY_00 + .byte COLOR_LT_OLIVE_28 ,COLOR_MED_OLIVE_18 ,COLOR_DARK_OLIVE_08 + .byte COLOR_DARK_PINK_05 ,COLOR_MED_OLIVE_18 ,COLOR_DARK_OLIVE_08 + .byte COLOR_PALE_RED_36 ,COLOR_DARK_RED_06 ,COLOR_LT_VIOLET_22 + .byte COLOR_PALE_RED_36 ,COLOR_DARK_RED_06 ,COLOR_PALE_VIOLET_32 + .byte COLOR_WHITE_20 ,COLOR_MED_BLUE_GREEN_1b ,COLOR_BLACK_0f + .byte COLOR_WHITE_20 ,COLOR_LT_VIOLET_22 ,COLOR_MED_TEAL_1c + .byte COLOR_LT_GRAY_10 ,COLOR_DARK_GRAY_00 ,COLOR_DARK_TEAL_0c + .byte COLOR_DARK_GRAY_00 ,COLOR_DARK_RED_06 ,COLOR_WHITE_20 + .byte COLOR_PALE_OLIVE_38 ,COLOR_DARK_FOREST_GREEN_09 ,COLOR_DARK_RED_06 + .byte COLOR_PALE_OLIVE_38 ,COLOR_DARK_FOREST_GREEN_09 ,COLOR_MED_RED_16 + .byte COLOR_PALE_OLIVE_38 ,COLOR_DARK_FOREST_GREEN_09 ,COLOR_LT_RED_26 + .byte COLOR_BLACK_0f ,COLOR_WHITE_20 ,COLOR_LT_TEAL_2c + .byte COLOR_BLACK_0f ,COLOR_WHITE_20 ,COLOR_LT_RED_26 + .byte COLOR_DARK_GRAY_00 ,COLOR_WHITE_20 ,COLOR_MED_GREEN_1a + .byte COLOR_DARK_BLUE_01 ,COLOR_WHITE_20 ,COLOR_DARK_GRAY_00 + .byte COLOR_WHITE_20 ,COLOR_LT_ORANGE_27 ,COLOR_MED_ORANGE_17 + .byte COLOR_WHITE_20 ,COLOR_LT_RED_26 ,COLOR_DARK_ORANGE_07 + .byte COLOR_WHITE_20 ,COLOR_LT_ORANGE_27 ,COLOR_MED_RED_16 + .byte COLOR_WHITE_20 ,COLOR_LT_RED_26 ,COLOR_DARK_RED_06 + .byte COLOR_DARK_GRAY_00 ,COLOR_LT_GRAY_10 ,COLOR_MED_PURPLE_13 + .byte COLOR_MED_RED_16 ,COLOR_WHITE_20 ,COLOR_DARK_GRAY_00 + .byte COLOR_DARK_RED_06 ,COLOR_WHITE_20 ,COLOR_DARK_GRAY_00 + .byte COLOR_LT_RED_26 ,COLOR_WHITE_20 ,COLOR_DARK_GRAY_00 + .byte COLOR_WHITE_20 ,COLOR_LT_GRAY_10 ,COLOR_MED_TEAL_1c + .byte COLOR_WHITE_20 ,COLOR_LT_GRAY_10 ,COLOR_DARK_GREEN_0a + .byte COLOR_WHITE_20 ,COLOR_DARK_GRAY_00 ,COLOR_MED_ORANGE_17 + .byte COLOR_WHITE_20 ,COLOR_MED_VIOLET_12 ,COLOR_DARK_GRAY_00 + .byte COLOR_DARK_ORANGE_07 ,COLOR_DARK_GRAY_00 ,COLOR_MED_ORANGE_17 + .byte COLOR_WHITE_20 ,COLOR_MED_RED_16 ,COLOR_DARK_GRAY_00 + .byte COLOR_WHITE_30 ,COLOR_LT_OLIVE_28 ,COLOR_MED_RED_16 + .byte COLOR_WHITE_30 ,COLOR_LT_PINK_25 ,COLOR_MED_MAGENTA_14 + .byte COLOR_LT_ORANGE_27 ,COLOR_MED_ORANGE_17 ,COLOR_DARK_RED_06 + .byte COLOR_WHITE_20 ,COLOR_LT_GRAY_10 ,COLOR_DARK_GRAY_00 + .byte COLOR_DARK_RED_06 ,COLOR_MED_BLUE_GREEN_1b ,COLOR_DARK_BLUE_GREEN_0b + .byte COLOR_LT_GRAY_10 ,COLOR_DARK_GRAY_00 ,COLOR_DARK_ORANGE_07 + .byte COLOR_PALE_RED_36 ,COLOR_MED_PINK_15 ,COLOR_DARK_RED_06 + .byte COLOR_PALE_PINK_35 ,COLOR_MED_PINK_15 ,COLOR_DARK_MAGENTA_04 + .byte COLOR_PALE_PINK_35 ,COLOR_MED_RED_16 ,COLOR_MED_TEAL_1c + .byte COLOR_MED_OLIVE_18 ,COLOR_MED_PURPLE_13 ,COLOR_DARK_PURPLE_03 + .byte COLOR_LT_RED_26 ,COLOR_MED_PURPLE_13 ,COLOR_DARK_PURPLE_03 + .byte COLOR_MED_MAGENTA_14 ,COLOR_MED_PURPLE_13 ,COLOR_DARK_PURPLE_03 + .byte COLOR_LT_BLUE_GREEN_2b ,COLOR_MED_PURPLE_13 ,COLOR_DARK_PURPLE_03 + .byte COLOR_LT_RED_26 ,COLOR_MED_RED_16 ,COLOR_DARK_PURPLE_03 + .byte COLOR_WHITE_20 ,COLOR_MED_PURPLE_13 ,COLOR_LT_ORANGE_27 + .byte COLOR_WHITE_20 ,COLOR_MED_RED_16 ,COLOR_LT_ORANGE_27 + .byte COLOR_DARK_GRAY_00 ,COLOR_DARK_GRAY_00 ,COLOR_DARK_GRAY_00 + .byte COLOR_MED_PINK_15 ,COLOR_MED_OLIVE_18 ,COLOR_DARK_OLIVE_08 + .byte COLOR_PALE_PINK_35 ,COLOR_MED_OLIVE_18 ,COLOR_DARK_OLIVE_08 + .byte COLOR_WHITE_20 ,COLOR_MED_VIOLET_12 ,COLOR_MED_ORANGE_17 + .byte COLOR_MED_PINK_15 ,COLOR_MED_BLUE_GREEN_1b ,COLOR_DARK_BLUE_GREEN_0b + .byte COLOR_BLACK_0f ,COLOR_MED_BLUE_GREEN_1b ,COLOR_DARK_BLUE_GREEN_0b + .byte COLOR_LT_MAGENTA_24 ,COLOR_MED_PURPLE_13 ,COLOR_DARK_PURPLE_03 + .byte COLOR_LT_ORANGE_27 ,COLOR_MED_ORANGE_17 ,COLOR_DARK_RED_06 + .byte COLOR_MED_ORANGE_17 ,COLOR_DARK_RED_06 ,COLOR_BLACK_0f + .byte COLOR_DARK_RED_06 ,COLOR_WHITE_30 ,COLOR_LT_GRAY_10 + .byte COLOR_LT_RED_26 ,COLOR_WHITE_30 ,COLOR_LT_GRAY_10 + .byte COLOR_DARK_GRAY_00 ,COLOR_DARK_GRAY_00 ,COLOR_DARK_GRAY_00 + .byte COLOR_WHITE_20 ,COLOR_LT_ORANGE_27 ,COLOR_MED_ORANGE_17 + .byte COLOR_LT_GRAY_10 ,COLOR_LT_RED_26 ,COLOR_DARK_RED_06 + .byte COLOR_LT_GRAY_10 ,COLOR_MED_RED_16 ,COLOR_DARK_ORANGE_07 + .byte COLOR_WHITE_20 ,COLOR_LT_VIOLET_22 ,COLOR_DARK_VIOLET_02 + .byte COLOR_LT_VIOLET_22 ,COLOR_WHITE_20 ,COLOR_DARK_VIOLET_02 + .byte COLOR_WHITE_20 ,COLOR_LT_GRAY_10 ,COLOR_DARK_GRAY_00 + .byte COLOR_MED_OLIVE_18 ,COLOR_DARK_ORANGE_07 ,COLOR_MED_GREEN_1a + .byte COLOR_MED_RED_16 ,COLOR_MED_OLIVE_18 ,COLOR_DARK_ORANGE_07 + .byte COLOR_WHITE_20 ,COLOR_LT_GRAY_10 ,COLOR_MED_BLUE_11 + .byte COLOR_WHITE_20 ,COLOR_MED_BLUE_11 ,COLOR_MED_BLUE_11 + .byte COLOR_WHITE_20 ,COLOR_LT_TEAL_2c ,COLOR_MED_BLUE_11 + .byte COLOR_LT_RED_26 ,COLOR_MED_OLIVE_18 ,COLOR_DARK_ORANGE_07 + .byte COLOR_MED_RED_16 ,COLOR_MED_OLIVE_18 ,COLOR_DARK_ORANGE_07 + .byte COLOR_MED_RED_16 ,COLOR_DARK_GRAY_00 ,COLOR_DARK_GRAY_00 + .byte COLOR_DARK_BLUE_GREEN_0b ,COLOR_MED_BLUE_GREEN_1b ,COLOR_PALE_BLUE_GREEN_3b + +; executed for indoor and outdoor levels +set_frame_scroll_draw_player_bullets: + jsr set_frame_scroll_weapon_strength ; set frame scroll, set weapon strength, update invincibility + lda INDOOR_SCROLL ; see if scrolling (0 = not scrolling; 1 = scrolling, 2 = finished scrolling) + cmp #$02 ; see if finished advancing and new screen has been shown + bcc @continue ; branch if still scrolling, or haven't started, or not indoor level + lda #$00 ; a = #$00 + +@continue: + sta INDOOR_SCROLL ; reset indoor scroll if completed, otherwise, keep its current value + lda AUTO_SCROLL_TIMER_00 + ora AUTO_SCROLL_TIMER_01 ; merge two auto-scrolling values + beq @run_player_bullet_routines ; branch if no auto-scrolling + lda #$01 ; a = #$01 + sta FRAME_SCROLL ; set scroll amount for frame + +@run_player_bullet_routines: + jsr load_bank_6_run_player_bullet_routines ; run player bullet routines + +; draw half the bullets, every other frame +; if only one bullet drawn every other frame +draw_player_bullet_sprites: + ldy #$07 ; maximum of #$08 player bullets + +; loads bullets to sprite buffer +@player_bullet_loop: + tya ; transfer player bullet offset to a + asl ; shift bullet offset to the left by one bit + sta $08 ; store shifted value into $08 + lda FRAME_COUNTER ; load the frame counter + and #$01 ; only care about bit 0 (odd/even) + ora $08 ; merge shifted bullet offset with frame counter odd/even flag + tax ; move specified bullet in memory into CPU_SPRITE_BUFFER so it can be drawn + lda PLAYER_BULLET_SPRITE_CODE,x ; load sprite code for specified bullet + sta PLAYER_SPRITES+2,y ; update bullet sprite in PLAYER_SPRITES + lda PLAYER_BULLET_SPRITE_ATTR,x ; load any bullet sprite attributes + sta SPRITE_ATTR+2,y ; set bullet sprite attributes + lda PLAYER_BULLET_Y_POS,x ; load player bullet y position + sta SPRITE_Y_POS+2,y ; set sprite bullet y position + lda PLAYER_BULLET_X_POS,x ; load player bullet x position + sta SPRITE_X_POS+2,y ; set sprite bullet x position + dey ; decrement to next bullet offset + bpl @player_bullet_loop ; loop if y still greater than or equal 0 + rts + +; initializes frame scroll, runs logic to set weapon strength, update invincibility +set_frame_scroll_weapon_strength: + lda #$00 ; a = #$00 + sta FRAME_SCROLL ; how much to scroll the screen (#00 - no scroll) + sta PLAYER_FRAME_SCROLL ; clear player 1 FRAME_SCROLL amount + sta PLAYER_FRAME_SCROLL+1 ; clear player 2 FRAME_SCROLL amount + sta ENEMY_ATTACK_FLAG ; stop enemies from attacking + sta PLAYER_WEAPON_STRENGTH ; clear player weapon strength + ldy P1_GAME_OVER_STATUS ; game over state of player 1 (1 = game over) + bne @p2_game_over_status ; skip assignment of a to #01 player 1 is in game over + ora #$01 ; set a to #$01 when player 1 is not in game over + +@p2_game_over_status: + ldy P2_GAME_OVER_STATUS ; player 2 game over state (1 = game over) + bne run_player_invincibility_and_weapon_strength ; branch if player 2 is in game over, or if this is a single player game + ora #$02 ; set bit 2 to #$01 when player 2 is not in game over + +; a will be #$00 when both players are in game over +; a will be #$01 when player 1 not game over, player 2 game over (or not playing) +; a will be #$02 when player 1 game over, player 2 not game over +; a will be #$03 when neither player 1 nor player 2 are in game over +run_player_invincibility_and_weapon_strength: + tax ; transfer game over statuses to x + beq player_state_routine_03 ; branch when both players are in game over + dex ; prep for setting in PLAYER_GAME_OVER_BIT_FIELD + stx PLAYER_GAME_OVER_BIT_FIELD ; #$00 = p1 not game over, p2 game over (or not playing) + ; #$01 = p1 game over, p2 not game over, #$02 = p1 nor p2 are in game over + txa ; transfer PLAYER_GAME_OVER_BIT_FIELD to a + and #$01 ; used to determine if only one player is active, if so run logic on that player + ; otherwise run logic on p1 first + tax ; transfer whether p2 is game over to x, set as current player + jsr handle_invincibility_and_weapon_strength ; run player state routine, checks invincibility, set weapon strength + lda PLAYER_GAME_OVER_BIT_FIELD ; #$00 = p1 not game over, p2 game over (or not playing) + ; #$01 = p1 game over, p2 not game over, #$02 = p1 nor p2 are in game over + cmp #$02 ; see if both players are active + bne player_state_routine_03 ; if one of the players is game over (or not playing) + ; already ran logic on the only active player, branch + inx ; both players active, already handled player 1, run logic for player 2 + jsr handle_invincibility_and_weapon_strength ; run player state routine, checks invincibility, set weapon strength + jsr scroll_player ; scroll player that isn't causing the screen to scroll if necessary + +player_state_routine_03: + lda LEVEL_ROUTINE_INDEX ; load current level routine + cmp #$04 + bne @exit ; jump if not level routine 4 (this code is call from level_routine_04 and level_routine_09) + lda PLAYER_MODE ; number of players (#$00 = 1 player) + beq @exit + ldx #$01 ; x = #$01 + +@player_loop: + lda P1_GAME_OVER_STATUS,x ; load game over state for player x (1 = game over) + bne @check_transfer_life + dex + bpl @player_loop + bmi @exit + +; related to lives transfer between players +; if game over player presses 'A', then a life is taken from the other player if possible +@check_transfer_life: + lda CONTROLLER_STATE_DIFF,x ; controller x buttons pressed + and #$80 ; keep bits x... .... (check for a button) + beq @exit ; if a isn't pressed, exit + txa + eor #$01 ; swap to other player by flipping bit 0 + tay + lda P1_NUM_LIVES,y ; load other player's number of lives + beq @exit ; if other player doesn't have any additional remaining lives, exit + cmp #$01 + bne @subtract_life + lda PLAYER_STATE,y ; player has load other player's player state + cmp #$01 ; make sure other player isn't dying and about to be on their last life + bne @exit ; if other player's player state isn't normal, exit + +@subtract_life: + lda P1_NUM_LIVES,y ; player x lives + sec ; set carry flag in preparation for subtraction + sbc #$01 ; lose a life from player Y + cmp #$ff ; check if out of lives + bne @revive_player + lda #$00 ; a = #$00 + +@revive_player: + sta P1_NUM_LIVES,y ; set new, lowered number of lives for other player + jsr init_player_and_weapon ; reset revived player's attributes and player weapon + sta PLAYER_STATE,x ; reset revived player's state to #$00 (normal) + sta P1_GAME_OVER_STATUS,x ; clear game over status + +@exit: + rts + +; find if a player needs to be scrolled back if they aren't causing the scroll +; for horizontal levels, this means the player is scrolled to the left +; for the vertical level, this means the player is scrolled down +scroll_player: + lda LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor + bne @exit2 ; exit for indoor level, or indoor boss screen + lda LEVEL_SCROLLING_TYPE ; outdoor level, load scrolling type 0 = horizontal, indoor/base; 1 = vertical + bne @vertical_scroll_player ; exit on vertical level + jsr find_scrolled_player ; find the player that isn't causing scroll and needs to be scrolled + beq @exit ; exit if both players are causing scroll, don't cause any player to be scrolled + dec SPRITE_X_POS,x ; move player that isn't causing scroll back + ; other player will remain at same relative position on screen + +@exit: + rts + +@vertical_scroll_player: + lda AUTO_SCROLL_TIMER_00 ; see if auto scroll is enabled + bne @exit2 ; exit if auto scroll is enabled, both players scroll down for auto scroll + jsr find_scrolled_player ; find player that should be scrolled down the screen, + ; since they aren't causing the screen to scroll + beq @exit2 ; exit if both players are causing the screen to scroll + lda SPRITE_Y_POS,x ; load the player to scroll's y position + clc ; clear carry in preparation for addition + adc FRAME_SCROLL ; add the amount the screen is about to scroll + sta SPRITE_Y_POS,x ; adjust player position by FRAME_SCROLL so they are scrolled down the screen + bcc @exit2 ; exit if no overflow occurred adding to player position + inc PLAYER_HIDDEN,x ; overflow occurred, set player as hidden (off screen) + ; doesn't ever seem to happen as off screen player dies before they can be hidden + +@exit2: + rts + +; determine player that is not causing scroll that should be moved in the opposite direction of the scroll +; output +; * x - 0 for p1, 1 for p2 +; * zero flag - clear when both players are causing scroll, set when only one +find_scrolled_player: + ldx #$00 ; x = #$00 + lda PLAYER_FRAME_SCROLL ; load player 1's frame scroll amount + cmp PLAYER_FRAME_SCROLL+1 ; compare to player 2's frame scroll amount + beq @exit ; exit if both are identical (default player 1 cause scroll, x = #$00) + bcc @exit ; exit if player 2 is causing scroll (mark player 1 cause scroll, x = #$00) + inx ; mark player 2 causing scroll (x = #$01) + +@exit: + rts + +; runs player state routine, checks new life invincibility timer, +; sets enemies to attack, sets weapon strength +; input +; * x - player offset +handle_invincibility_and_weapon_strength: + jsr run_player_state_routine ; run logic based on player's state (see PLAYER_STATE) + lda NEW_LIFE_INVINCIBILITY_TIMER,x ; timer for invincibility (after dying or start of level) + beq set_enemies_to_attack ; if invincibility timer is #$00, set enemies to attack + dec NEW_LIFE_INVINCIBILITY_TIMER,x ; decrement value + jmp decrement_invincibility_effect ; handle B (barrier) weapon (invincibility) + +; new life invincibility elapsed. Set enemies to attack +set_enemies_to_attack: + lda PLAYER_STATE,x ; load player state (0 = dropping into level, 1 = normal, 2 = dead, 3 = can't move) + cmp #$01 ; see if player is in normal state + bne decrement_invincibility_effect ; if not in normal state, jump + lda #$01 ; new life invincibility timer elapsed, set enemies to attack + sta ENEMY_ATTACK_FLAG ; set enemies to attack + +; decrement b weapon effect (invincibility) every 8th frame if active +decrement_invincibility_effect: + lda INVINCIBILITY_TIMER,x + beq @continue ; if no invincibility, jump + lda FRAME_COUNTER ; load frame counter + and #$07 ; clear all but last 3 bits + bne @continue ; only decrement every #$8 frames + dec INVINCIBILITY_TIMER,x ; decrement invincibility (b weapon effect) timer for current player + +@continue: + lda PLAYER_RECOIL_TIMER,x ; see if how many frames player will have recoil + beq set_player_weapon_strength + dec PLAYER_RECOIL_TIMER,x ; decrement player recoil timer + +; set the player weapon strength memory value based on current weapon +set_player_weapon_strength: + lda #$00 ; a = #$00 + sta PLAYER_FAST_X_VEL_BOOST,x ; clear x fast velocity boost from being on a non-dangerous moving enemy + lda P1_CURRENT_WEAPON,x ; get current player's weapon + and #$07 ; keep bits .... .xxx + tay + lda weapon_strength,y ; load how strong the weapon is + cmp PLAYER_WEAPON_STRENGTH ; compare against current weapon strength + bcc @exit ; exit and do not lower player's current weapon strength + sta PLAYER_WEAPON_STRENGTH ; store current weapon strength code (#$00-#$03) + +@exit: + rts + +; table for weapon strength code (#$05 bytes) +; Regular = Weak +; M = Strong +; F = Medium +; S = Very Strong +; L = Strong +weapon_strength: + .byte $00,$02,$01,$03,$02 + +; run logic based on players current state +; #$00 falling into level +; #$01 normal state +; #$02 dead +; #$03 can't move +run_player_state_routine: + ldy CURRENT_LEVEL ; current level + lda LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor + asl ; double the location type, shifting msb to carry + lda level_spawn_position_index,y ; load the spawn position offset into a + bcc @continue ; jump if not indoor boss screen + lda #$03 ; indoor boss screen, set $08 to #$03 + +@continue: + sta $08 ; store the offset into the spawn location into $08 + ; for player_state_routine_01 used to calculate offset into d_pad_player_aim_tbl + lda PLAYER_STATE,x ; load player state (0 = dropping into level, 1 = normal, 2 = dead, 3 = can't move) + jsr run_routine_from_tbl_below ; run routine a in the following table (player_state_routine) + +; pointer table for unknown ($04 * $02 = $08 bytes) +player_state_routine: + .addr player_state_routine_00 ; CPU address $d4c3 - finds location to 'drop in' the player, executed once + .addr player_state_routine_01 ; CPU address $d534 + .addr player_state_routine_02 ; CPU address $d593 - player has died, animate player falling backwards + .addr player_state_routine_03 ; CPU address $d3e6 + +; the offset into vertical_spawn_position and horizontal_spawn_position +; for player_state_routine_01 used to calculate offset into d_pad_player_aim_tbl +; based on the level +level_spawn_position_index: + .byte $00,$01,$02,$01,$00,$00,$00,$00 + +; player falling into level logic +; only run once to set player position +player_state_routine_00: + jsr init_player_attributes + txa ; set a to be current player + asl + asl + clc ; clear carry in preparation for addition + adc $08 ; offset index into spawn position tables + tay + lda vertical_spawn_position,y + sta SPRITE_Y_POS,x ; set player y position on screen + lda horizontal_spawn_position,y + sta SPRITE_X_POS,x ; set player x position on screen + lda #$01 ; a = #$01 + sta PLAYER_JUMP_STATUS,x + lda LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor + bne @set_frame_invincible_timer_exit ; branch if indoor level + jsr @check_if_floor_exit ; outdoor level, check if something to land on + bcc @set_frame_invincible_timer_exit ; branch if there is something to land on + lda #$10 ; nothing to land on at current position + ; move from left of screen to right to find a spot (increments of #$10 pixels at a time) + sta SPRITE_X_POS,x ; set x position to #$10 + +@find_landing: + jsr @check_if_floor_exit ; check if something to land on + bcc @set_frame_invincible_timer_exit ; branch if nothing to land on at current position + lda SPRITE_X_POS,x ; nothing to land on at current position, move to next position + clc ; clear carry in preparation for addition + adc #$10 ; add #$10 to player x position + sta SPRITE_X_POS,x ; set new x position + cmp #$e0 ; see if at end of screen + bcs @set_x_pos ; couldn't find a position with a place to land, just use #$30 + jmp @find_landing ; loop to see if next position to the right is appropriate for landing + +; couldn't find a position with a place to land, just use x position #$30 +@set_x_pos: + lda #$30 ; a = #$30 + sta SPRITE_X_POS,x ; set initial x position when dropping in level + +; sets PLAYER_ANIMATION_FRAME_INDEX to #$02, NEW_LIFE_INVINCIBILITY_TIMER to #$80, PLAYER_Y_FAST_VELOCITY to #$00 +; and moves to next player state before exiting +@set_frame_invincible_timer_exit: + lda #$02 ; a = #$02 + sta PLAYER_ANIMATION_FRAME_INDEX,x ; set frame index to #$02 sprite_08 (offset into player_curled_sprite_code_tbl) + ; see set_player_jump_sprite + lda #$00 ; a = #$00 + sta PLAYER_Y_FAST_VELOCITY,x ; set fast velocity to #$00 (fractional velocity still #$23 (.137)) + lda #$80 ; invincibility time in number of frames, 2 seconds for NTSC + sta NEW_LIFE_INVINCIBILITY_TIMER,x ; set timer for invincibility (after dying) + inc PLAYER_STATE,x ; finished initializing player, move to state #$01 (normal state) + rts + +; see if there is a place for the player +; output +; * carry flag - set when only empty collision codes below player; clear when solid, water, or ground beneath player +@check_if_floor_exit: + jsr get_player_bg_collision_code ; get player background collision code + asl ; push msb to carry flag (whether or not solid collision) + bcs @exit ; exit with carry set when collision code #$80 (solid) + ; this means there is a solid object at the top of the screen + lda SPRITE_Y_POS,x ; still falling, load sprite y position + clc ; clear carry in preparation for addition + adc #$20 ; add #$20 to sprite y position + jmp check_collision_below ; jump to check if bg collision below player + +@exit: + rts + +; player spawn positions, according to level and player index +; table for spawn y positions ($08 bytes) +vertical_spawn_position: + .byte $20,$60,$50,$60 ; player 1 + .byte $20,$60,$50,$60 ; player 2 + +; table for spawn x positions ($08 bytes) +horizontal_spawn_position: + .byte $30,$70,$30,$70 ; player 1 + .byte $20,$90,$20,$90 ; player 2 + +; normal player state +player_state_routine_01: + jsr player_state_routine_01_logic + jsr load_bank_2_set_player_sprite ; set player sprite based on player state, level, and animation sequence + lda PLAYER_AIM_DIR,x + sta PLAYER_AIM_PREV_FRAME,x + lda PLAYER_HIDDEN,x ; 0 - visible; #$01/#$ff = invisible (any non-zero) + bne @exit + lda SPRITE_Y_POS,x + cmp #$e8 ; check if falling at the bottom of the screen + bcs kill_player + +@exit: + rts + +; x is the current player +kill_player: + lda #$52 ; a = #$52 (sound_52) + jsr play_sound ; play player death sound + jsr init_player_data ; reset player data and set a to #$00 + sta PLAYER_WATER_STATE,x + sta ELECTROCUTED_TIMER,x + sta PLAYER_SPECIAL_SPRITE_TIMER,x + sta PLAYER_ANIMATION_FRAME_INDEX,x + sta PLAYER_ANIM_FRAME_TIMER,x + lda #$01 ; a = #$01 + sta PLAYER_DEATH_FLAG,x + lda #$fd ; initiate jump by setting y velocity to #$fd80 + sta PLAYER_Y_FAST_VELOCITY,x ; player y velocity + lda #$80 ; a = #$80 + sta PLAYER_Y_FRACT_VELOCITY,x + inc PLAYER_STATE,x + rts + +; set player aim direction based on d-pad input +; check if player is on an edge and should fall +; check if player is firing and generate bullet if so +; calculate player x velocity +; auto scroll player +player_state_routine_01_logic: + jsr set_player_aim_for_input ; set PLAYER_AIM_DIR based on d-pad input, facing direction, and jump status + jsr check_player_ledge ; see if player should check for walking off ledge and if so, walk off it + jsr load_bank_6_check_player_fire ; generate bullet if player is shooting and allowed to shoot + jsr handle_player_state_calc_x_vel + +; auto scroll the player position if auto-scroll enabled +auto_scroll_player: + lda AUTO_SCROLL_TIMER_00 ; load auto scroll timer + ora AUTO_SCROLL_TIMER_01 ; merge with auto scroll timer 01 + beq @exit ; branch if no auto scroll happening + lda LEVEL_SCROLLING_TYPE ; auto scroll happening, load scrolling type (0 = horizontal, indoor/base; 1 = vertical) + bne @inc_y_pos_exit ; increment y position and exit if vertical level with auto scroll (boss reveal) + lda SPRITE_X_POS,x ; horizontal, indoor/base level, load sprite x position + ldy #$00 ; y = #$00 + cmp level_left_edge_x_pos_tbl,y ; compare player position to left edge + bcc @exit ; exit if already at farthest left edge to keep the player there (push them) + dec SPRITE_X_POS,x ; otherwise decrement from x position to make scroll effect + rts + +@inc_y_pos_exit: + inc SPRITE_Y_POS,x + +@exit: + rts + +; player has died, animate player falling backwards +player_state_routine_02: + jsr auto_scroll_player ; auto scroll the player position if auto-scroll enabled + jsr sty_level_screen_type ; get screen type #$00 = outdoor, #$01 = indoor/base boss, #$02 = indoor/base + lda player_sprite_sequence_tbl,y ; load which animation to show for the player, .e.g. (4 = dead animation, 6 = indoor dead animation) + sta PLAYER_SPRITE_SEQUENCE,x ; set sprite sequence + lda PLAYER_ANIM_FRAME_TIMER,x ; see if animation timer has elapsed for sprite sequence + beq @animation_timer_elapsed ; branch if timer has elapsed + dec PLAYER_ANIM_FRAME_TIMER,x ; timer hasn't elapsed, decrement animation timer + bne @set_player_sprite_exit + jmp init_player_dec_num_lives ; init player variables to #$00, decrement number of lives, set game over if needed + +@animation_timer_elapsed: + lda player_died_x_velocity_tbl,y ; which direction the player flies when killed + sta PLAYER_X_VELOCITY,x ; set player x velocity + lda PLAYER_AIM_DIR,x + cmp #$05 ; see if facing left or right + bcc @continue ; branch if facing right + lda PLAYER_DEATH_FLAG,x ; facing left, set bit 1 of PLAYER_DEATH_FLAG + ora #$02 ; set bit 1, so that player dies with head towards right + sta PLAYER_DEATH_FLAG,x ; update PLAYER_DEATH_FLAG + lda #$00 ; a = #$00 + sec ; set carry flag in preparation for subtraction + sbc PLAYER_X_VELOCITY,x + sta PLAYER_X_VELOCITY,x + +@continue: + lda PLAYER_Y_FAST_VELOCITY,x ; load player's fast y velocity + bmi @set_pos_and_sprite ; branch if negative y velocity (ascending/falling back) + cmp #$02 ; compare y fast velocity to #$02 + bcc @set_pos_and_sprite ; branch if y fast velocity is less than #$02 + lda PLAYER_HIDDEN,x ; 0 - visible; #$01/#$ff = invisible (any non-zero) + cmp #$01 ; see if player hidden + beq @set_animation_timer ; branch if player is hidden + jsr get_player_bg_collision_code ; player not hidden, get player background collision code + beq @set_pos_and_sprite ; branch if collision code #$00 (empty) + cmp #$02 ; see if collision code is #$02 (water) + beq @set_pos_and_sprite ; branch if in water + jsr set_player_landing_y_offset ; set SPRITE_Y_POS,x + +@set_animation_timer: + lda #$40 ; a = #$40 + sta PLAYER_ANIM_FRAME_TIMER,x ; set animation timer to #$40 frames + +@set_pos_and_sprite: + jsr apply_gravity_set_y_pos ; increments y fractional velocity by #$23 (applying gravity) and sets y position + jsr calc_player_x_vel ; runs a series of checks to see if player's x velocity can be applied, and applies if possible + +@set_player_sprite_exit: + jmp load_bank_2_set_player_sprite ; set player sprite based on player state, level, and animation sequence + +; table for unknown ($03 bytes) +player_sprite_sequence_tbl: + .byte $04,$04,$06 + +; table for x velocities when dying (#$03 bytes) +; get screen type #$00 = outdoor, #$01 = indoor/base boss, #$02 = indoor/base +player_died_x_velocity_tbl: + .byte $ff ; outdoor + .byte $00 ; indoor/base boss + .byte $00 ; indoor/base + +handle_player_state_calc_x_vel: + lda INDOOR_PLAYER_ADV_FLAG,x ; load whether player is walking between screens for indoor level + bne @continue ; branch if player is advancing between screens for indoor level + lda #$00 ; a = #$00 + sta PLAYER_X_VELOCITY,x ; clear player x velocity to recalculate + sta INDOOR_TRANSITION_X_FRACT_VEL,x ; clear fractional x velocity for when walking between screens on indoor levels + +@continue: + jsr handle_player_state + +; runs a series of checks to see if player's x velocity can be applied, and applies if it possible +; e.g. checks if colliding with solid object, checks if in exiting water animation, checks if stuck due to boss screen limit +calc_player_x_vel: + lda PLAYER_X_VELOCITY,x ; load player X velocity + clc ; clear carry in preparation for addition + adc PLAYER_FAST_X_VEL_BOOST,x ; add any additional boost to velocity by being on a non-dangerous moving enemy + sta PLAYER_X_VELOCITY,x ; update player x velocity + lda PLAYER_WATER_STATE,x ; load player in water state flags + bmi @exit ; exit if player is walking out of water, don't want to stop animation + lda PLAYER_X_VELOCITY,x ; load player X velocity + ora INDOOR_TRANSITION_X_FRACT_VEL,x ; merge with fractional x velocity for when walking between screens on indoor levels + beq @exit ; exit if player isn't moving + lda PLAYER_X_VELOCITY,x ; player is moving, load player X velocity + bmi @player_negative_x_vel ; branch if player going left + lda BOSS_DEFEATED_FLAG ; player has positive velocity, see if currently executing post-boss defeated walking animation + bmi @set_scroll_apply_x_vel ; branch to apply velocity if part of end of level walk off screen animation + lda #$08 ; checking bg collision #$08 pixels to right of player + jsr check_player_solid_bg_collision ; see if player is about to collide with solid background object + bcs @exit ; exit if collided with solid background object, like lvl 1 boss screen plated door + jsr sty_level_screen_type ; get screen type #$00 = outdoor, #$01 = indoor/base boss, #$02 = indoor/base + lda SPRITE_X_POS,x ; load player's x position + cmp level_right_edge_x_pos_tbl,y ; compare player position to right edge of screen + bcs @exit ; don't apply velocity if player at the right edge + ldy BOSS_AUTO_SCROLL_COMPLETE ; see if boss reveal auto-scroll has completed (0 = not-complete, 1 = complete) + beq @set_scroll_apply_x_vel ; branch to apply velocity if auto scroll hasn't completed (or started) + ldy CURRENT_LEVEL ; auto-scroll has completed, load current level + cmp @lvl_boss_max_x_scroll_tbl,y ; compare player x position to the maximum x position for boss screen + bcs @exit ; exit if can't move past x position + +@set_scroll_apply_x_vel: + jsr set_frame_scroll_if_appropriate ; set FRAME_SCROLL and PLAYER_FRAME_SCROLL if player is causing screen to scroll + jmp @apply_vel_to_player_x_pos + +; table for maximum x position on boss screen to allow player to walk ($08 bytes) +; each byte is for each level +; for lvl 1, the x position isn't possible due to a solid bg object at #$88 +; if you remove the collision code, the player won't walk past #$90 +@lvl_boss_max_x_scroll_tbl: + .byte $90,$ff,$ff,$ff,$a0,$d0,$b0,$b0 + +; player is going left +@player_negative_x_vel: + lda #$f8 ; a = #$f8 (#$08 pixels to left of player) + jsr check_player_solid_bg_collision ; see if player is about to collide with solid background object + bcs @exit ; exit if collided with solid background object + lda BOSS_DEFEATED_FLAG ; 0 = boss not defeated, 1 = boss defeated + bmi @apply_vel_to_player_x_pos ; branch to apply velocity if part of end of level walk off screen animation + jsr sty_level_screen_type ; get screen type #$00 = outdoor, #$01 = indoor/base boss, #$02 = indoor/base + lda SPRITE_X_POS,x ; load player's x position + cmp level_left_edge_x_pos_tbl,y ; compare player position to left edge + bcc @exit ; exit if player too far to the left + +@apply_vel_to_player_x_pos: + lda INDOOR_TRANSITION_X_ACCUM,x ; load the amount of x distance to move for single animation when moving between screens on indoor/base levels + clc ; clear carry in preparation for addition + adc INDOOR_TRANSITION_X_FRACT_VEL,x ; add to the fractional x velocity + sta INDOOR_TRANSITION_X_ACCUM,x ; store result back in accumulator + lda SPRITE_X_POS,x ; load player x position + adc PLAYER_X_VELOCITY,x ; add the player velocity (including any indoor transition velocity adjustment) + sta SPRITE_X_POS,x ; set new player x position + +@exit: + rts + +; table for x position of right edge of screen ($03 bytes) +; #$00 (outdoor) = #$e6 +; #$01 (indoor/base boss) = #$e0 +; #$02 (indoor/base) = #$d0 +level_right_edge_x_pos_tbl: + .byte $e6,$e0,$d0 + +; table for x position of left edge of screen ($03 bytes) +; #$00 (outdoor) = #$1a +; #$01 (indoor/base boss) = #$20 +; #$02 (indoor/base) = #$30 +level_left_edge_x_pos_tbl: + .byte $1a,$20,$30 + +handle_player_state: + lda #$03 ; a = #$03 + sta PLAYER_SPRITE_SEQUENCE,x + lda INDOOR_PLAYER_JUMP_FLAG,x ; see if engine has commanded the player to jump (set when entering new indoor screen) + beq @player_electrocution_check ; branch if no jump command specified + lda #$00 ; reset player jump command and set player jumping velocities + sta INDOOR_PLAYER_JUMP_FLAG,x ; reset player jump command + sta INDOOR_PLAYER_ADV_FLAG,x ; player is no longer walking between screens for indoor level, clear flag + jsr indoor_transition_end ; end player transition animation restore player position + jmp set_jump_status_and_y_velocity ; initialize PLAYER_JUMP_STATUS, animation frame index, and negative y velocity + +@player_electrocution_check: + lda ELECTROCUTED_TIMER,x + beq @player_edge_fall_check + jmp update_indoor_electrocution ; decrement electrocution; if screen is cleared, stops electrocution + +@player_edge_fall_check: + lda EDGE_FALL_CODE,x + beq @player_jump_check + jmp set_x_velocity_for_edge_fall_code + +@player_jump_check: + lda PLAYER_JUMP_STATUS,x ; low nibble 1 = jumping, 0 not jumping; high nibble = facing direction + beq @indoor_transition_anim_check ; branch if the player isn't jumping + jmp handle_jump ; player is jumping, jump + +@indoor_transition_anim_check: + lda INDOOR_PLAYER_ADV_FLAG,x ; load whether player is walking between screens for indoor level + beq @handle_player_input + jmp indoor_transition_set_pos ; player is advancing between indoor screens, update player position for animation + +@handle_player_input: + lda PLAYER_WATER_STATE,x ; see if player in water + bne handle_d_pad ; player not in water, read the controller input for d-pad input + lda CONTROLLER_STATE_DIFF,x ; load controller input + and #$80 ; check for A button pressed + beq handle_d_pad ; branch if a button not pressed to read the controller input + lda CONTROLLER_STATE,x ; load controller input + and #$07 ; keep bits .... .xxx (d-pad down, left, right) + cmp #$04 ; see if pressing the down d-pad button + bne set_jump_status_and_y_velocity ; branch if not only down is pressed + ; to initialize PLAYER_JUMP_STATUS, animation frame index, and negative y velocity + lda #$02 ; only down button pressed,a = #$02 (sprite sequence to crouching) + sta PLAYER_SPRITE_SEQUENCE,x ; set sprite sequence to crouching + jsr can_player_drop_down ; determines if player can drop down (d-pad down and A) + bcs player_sprite_animation_exit ; branch if player cannot drop down (nothing below player to land on and not vertical level) + lda #$81 ; a = #$81 + bne set_edge_fall_code ; always branch + +; called when walked off ledge (not jumped) +; determine collision code and leave result in A +; collision code 0 - Empty +; collision code 1 - Floor +; collision code 2 - Water +; collision code 3 - Solid +walk_off_ledge: + lda PLAYER_AIM_DIR,x + cmp #$05 ; compare to crouched facing right + lda #$21 ; player is falling right off a ledge + bcc set_edge_fall_code ; branch if PLAYER_AIM_DIR,x is less than #$05, i.e. facing right + lda #$41 ; player is falling left off ledge + +set_edge_fall_code: + sta EDGE_FALL_CODE,x + lda SPRITE_Y_POS,x + clc ; clear carry in preparation for addition + adc #$14 ; add #$14 to Y position + bcc @continue ; branch if no overflow + lda #$ff ; if overflow, just set #$ff + +@continue: + sta PLAYER_FALL_X_FREEZE,x ; store updated Y position in $b8 + +player_sprite_animation_exit: + rts + +; sets PLAYER_JUMP_STATUS based on facing direction, initializes animation frame index, and y velocity +; input +; * x - player index +set_jump_status_and_y_velocity: + lda PLAYER_AIM_DIR,x ; player animation frame + cmp #$05 + lda #$91 ; a = #$91 (jumping left) + bcs @continue ; branch if facing left + lda #$11 ; a = #$11 (jumping right) + +@continue: + sta PLAYER_JUMP_STATUS,x + lda #$00 ; a = #$00 + sta PLAYER_ANIM_FRAME_TIMER,x ; reset player sprite index + sta PLAYER_ANIMATION_FRAME_INDEX,x + lda LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor + lsr + lda #$fb ; a = #$fb + ldy #$f0 ; y = #$f0 + bcc @set_y_velocity ; branch if outdoor level (use y velocity -5.94) + lda #$fc ; indoor level, (y velocity -4.56), set fast velocity to #$fc (-4) + ldy #$90 ; set fractional y velocity to #$90 (.56) + +@set_y_velocity: + sta PLAYER_Y_FAST_VELOCITY,x ; set y fast velocity to a + tya ; transfer fractional velocity to a + sta PLAYER_Y_FRACT_VELOCITY,x ; set y fractional velocity to a + rts + +; reads the d-pad controller input and updates velocity appropriately +handle_d_pad: + lda CONTROLLER_STATE,x + lsr + bcs set_player_positive_x_velocity ; branch if d-pad right is pressed + lsr + bcs set_player_negative_x_velocity ; branch if d-pad left is pressed + ldy #$02 ; y = #$02 (crouching sprite sequence) + lsr + bcs set_sprite_sequence_to_y ; branch if down button is pressed to set sprite sequence to #$02 + dey ; gun pointing up sprite sequence + lsr + bcs d_pad_up_pressed ; branch if up button is pressed + dey + +set_sprite_sequence_to_y: + tya + +; sets animation to show for the player +; * #$00 standing (no animation) +; * #$01 gun pointing up +; * #$02 crouching +; * #$03 walking or curled jump animation +; * #$04 dead animation +set_sprite_sequence_to_a: + sta PLAYER_SPRITE_SEQUENCE,x + rts + +; d pad up button pressed by itself while not jumping +; on outdoor levels +; on indoor levels, check for electrocution (depends on if screen is cleared) +d_pad_up_pressed: + lda LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor + lsr + bcc set_sprite_sequence_to_y ; branch for outdoor level + jsr set_sprite_sequence_to_y ; indoor level, set sprite sequence to y + lda INDOOR_SCREEN_CLEARED ; indoor level, check indoor screen cleared flag (0 = not cleared; 1 = cleared) + bne @indoor_screen_cleared ; branch if indoor screen is cleared + lda #$30 ; indoor screen not clear (has electric fence), set electrocution timer to #$30 frames + sta ELECTROCUTED_TIMER,x ; set timer for being electrocuted to #$30 + lda #$1c ; a = #$1c (sound_1c - sound of electrocution) + jmp play_sound ; play sound + +@indoor_screen_cleared: + stx $10 ; backup player index to $10 + txa ; transfer player index to a + eor #$01 ; move to other player + tax ; transfer other player index to x + lda P1_GAME_OVER_STATUS,x ; load game over state for player x (1 = game over) + bne @check_players_advancing ; branch if other player is in game over to skip player state check + lda PLAYER_STATE,x ; both players alive, load player state (0 = dropping into level, 1 = normal, 2 = dead, 3 = can't move) + cmp #$01 ; compare to normal state + bne set_player_standing_sprite_sequence ; branch if not in normal state + lda PLAYER_JUMP_STATUS,x ; low nibble 1 = jumping, 0 not jumping; high nibble = facing direction + bne set_player_standing_sprite_sequence ; branch if jumping + +@check_players_advancing: + ldx #$01 ; start loop with player 2 + +@check_player_x_advancing: + lda #$05 ; a = #$05 + sta PLAYER_SPRITE_SEQUENCE,x ; player animation frame + lda INDOOR_PLAYER_ADV_FLAG,x ; load whether player is walking between screens for indoor level + bne @check_next_player ; branch to move to next player if player x isn't walking between screens + lda #$01 ; player is walking between screens, a = #$01 + sta INDOOR_SCROLL ; set indoor scroll to #$01 + sta INDOOR_PLAYER_ADV_FLAG,x ; set flag indicating player is walking between screens for indoor level + lda #$00 ; a = #$00 + sta PLAYER_ANIMATION_FRAME_INDEX,x ; initialize animation for player walking into screen + sta PLAYER_ANIM_FRAME_TIMER,x ; initialize timer delay between frames of animation of walking into screen + jsr set_player_advancing_vel ; set the x and y velocities and other variables to initiate the player advancing into screen + +@check_next_player: + dex ; move to player 1 + bpl @check_player_x_advancing + ldx $10 ; restore current player index to x + rts + +set_player_standing_sprite_sequence: + ldx $10 ; load player index + lda #$00 ; a = #$00, standing (no animation) + beq set_sprite_sequence_to_a ; sets player animation sequence to standing + +; sets the player's X velocity to a +set_player_positive_x_velocity: + lda #$01 ; a = #$01 + bne set_player_x_vel_to_a ; always jump, set X velocity to #$01 + +; facing left +set_player_negative_x_velocity: + lda #$ff ; a = #$ff + +set_player_x_vel_to_a: + ldy PLAYER_WATER_STATE,x ; see if player is in water + beq @continue ; branch if animation is #$00 (not in water) + sta $08 ; player in water, set player X velocity in $08 temporarily + lda CONTROLLER_STATE,x ; see what buttons are being pressed + and #$04 ; see if d-pad has down direction (among others) pressed (down, down right, down left) + bne @exit ; don't set x velocity if down button is pressed (don't allow moving in water while looking down) + lda $08 ; restore desired player X velocity + +@continue: + sta PLAYER_X_VELOCITY,x + +@exit: + rts + +; set y to level screen type +; output +; y - screen type: #$00 = outdoor level, #$01 = indoor boss level screen, #$02 = indoor level +sty_level_screen_type: + ldy #$00 ; y = #$00 + lda LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor/base + beq @exit ; y = #$0 for outdoor level, exit + iny ; indoor level set y = #$01 + asl + bcs @exit ; exit if indoor boss screen with y = #$01 + iny ; indoor non-boss screen set y = #$02 + +@exit: + rts + +set_x_velocity_for_edge_fall_code: + lda SPRITE_Y_POS,x + cmp PLAYER_FALL_X_FREEZE,x ; load #$14 from where player starting falling + bcc @off_ledge_start ; branch if beginning of fall off/through ledge + jsr get_player_bg_collision_code ; get player background collision code + beq @off_ledge_start ; if collision code set was #$00 (empty), branch + jsr set_player_landing_y_offset ; set SPRITE_Y_POS,x + jmp player_land_on_ground + +; can't adjust x velocity for small amount of time after walking off ledge +@off_ledge_start: + jsr apply_gravity_set_y_pos ; increments y fractional velocity by #$23 (applying gravity) and sets y position + lda SPRITE_Y_POS,x ; load player y position + cmp PLAYER_FALL_X_FREEZE,x + bcc @set_x_velocity + jsr get_x_velocity_d_pad_code ; see if left or right d-pad button is pressed + beq @set_x_velocity ; branch if neither were pressed + sta $08 ; store #$20 for left d-pad, #$40 for right d-pad in $08 + lda EDGE_FALL_CODE,x + and #$9f ; keep bits x..x xxxx + ora $08 ; update EDGE_FALL_CODE based on d-pad input + sta EDGE_FALL_CODE,x + +@set_x_velocity: + lda EDGE_FALL_CODE,x + jmp set_x_velocity_from_a_code + +handle_jump: + lda PLAYER_Y_FAST_VELOCITY,x ; load player y fast velocity + bmi @check_collision_above ; branch if player is still ascending + ldy LEVEL_LOCATION_TYPE ; player descending, load location type (0 = outdoor; 1 = indoor) + bne @check_collision ; branch for indoor level + cmp #$01 ; outdoor level, see if fast velocity is #$01 + bcc @apply_gravity ; branch if either at apex of jump, or just beginning descent + lda PLAYER_Y_FAST_VELOCITY,x ; player y fast velocity >= #$01, reload y fast velocity value + ; !(HUH) already in a register, lda instruction not needed + cmp #$04 ; related to ground collision test + bcs @check_collision ; branch if velocity is greater than or equal to #$04 (fast falling) + lda SPRITE_Y_POS,x ; y velocity less than #$04, load player y position on screen + clc ; clear carry in preparation for addition + adc VERTICAL_SCROLL ; add vertical scroll offset + and #$0f ; keep bits .... xxxx + cmp #$08 ; see if result ends in #$08 + bcs @apply_gravity ; branch to check collision every #$08 pixels + +@check_collision: + jsr get_player_bg_collision_code ; get player background collision code + beq @apply_gravity ; if collision code set was #$00 (empty), branch + jsr set_player_landing_y_offset ; set SPRITE_Y_POS,x + jsr @set_jump_status_from_input + jmp player_land_on_ground + +@check_collision_above: + lda BOSS_DEFEATED_FLAG ; 0 = boss not defeated, 1 = boss defeated + bmi @apply_gravity ; branch if boss already defeated + lda SPRITE_Y_POS,x + clc ; clear carry in preparation for addition + adc #$f6 ; subtract #$0a from y position + tay ; set y position for bg collision check + lda SPRITE_X_POS,x ; load x position for bg collision check + jsr get_bg_collision ; determine player background collision code at position (a,y) + bpl @apply_gravity ; branch if not empty collision code (floor, water, solid) + lda #$00 ; a = #$00 + sta PLAYER_Y_FAST_VELOCITY,x + sta PLAYER_Y_FRACT_VELOCITY,x + +@apply_gravity: + jsr apply_gravity ; increments y fractional velocity by #$23 (applying gravity) + lda LEVEL_SCROLLING_TYPE ; 0 = horizontal, indoor/base; 1 = vertical + beq @set_y_pos ; branch if horizontal level to to set the y position based on velocity + jsr set_frame_scroll_if_appropriate ; vertical level, set FRAME_SCROLL and PLAYER_FRAME_SCROLL if player is causing screen to scroll + bcs @set_jump_status_from_input ; branch if vertical FRAME_SCROLL was set + +@set_y_pos: + jsr player_jumping_set_y_pos ; set player y position based on PLAYER_JUMP_COEFFICIENT and velocity + +@set_jump_status_from_input: + jsr get_x_velocity_d_pad_code ; see if left or right d-pad button is pressed + beq @set_x_velocity ; branch if neither were pressed + sta $08 ; store #$20 for left d-pad, #$40 for right d-pad in $08 + lda PLAYER_JUMP_STATUS,x ; low nibble 1 = jumping, 0 not jumping; high nibble = facing direction + and #$9f ; keep bits x..x xxxx + ora $08 ; update EDGE_FALL_CODE based on d-pad input + sta PLAYER_JUMP_STATUS,x + +@set_x_velocity: + lda PLAYER_JUMP_STATUS,x ; low nibble 1 = jumping, 0 not jumping; high nibble = facing direction + +; a is EDGE_FALL_CODE, or PLAYER_JUMP_STATUS +; bit 7 set specifies negative velocity +; bit 6 set specifies positive velocity +set_x_velocity_from_a_code: + asl ; shift a left + bpl @continue ; branch if a is not negative + jmp set_player_negative_x_velocity ; result was negative, player facing left, jump + +@continue: + asl + bpl x_velocity_exit ; branch if a is not negative + jmp set_player_positive_x_velocity ; set player X velocity to modified EDGE_FALL_CODE + +; set a to #$20 (right), #$40 (left), or #$00 (neither) based on d-pad +get_x_velocity_d_pad_code: + ldy #$00 ; y = #$00 + lda CONTROLLER_STATE,x ; load controller state + lsr + bcc @test_left_d_pad ; branch if not pressing right d-pad button + ldy #$20 ; y = #$20 + +@test_left_d_pad: + lsr + bcc @continue + ldy #$40 ; y = #$40 + +@continue: + tya + +x_velocity_exit: + rts + +; decrements player electrocution +; if screen is cleared, stops electrocution +update_indoor_electrocution: + lda #$01 ; a = #$01 + sta PLAYER_SPRITE_SEQUENCE,x ; player animation frame + dec ELECTROCUTED_TIMER,x ; counter for electrocution + lda INDOOR_SCREEN_CLEARED ; indoor screen cleared flag (0 = not cleared; 1 = cleared) + beq @exit ; exit if indoor screen is not cleared + lda #$00 ; screen cleared; stop electrocution + sta ELECTROCUTED_TIMER,x ; counter for electrocution + +@exit: + rts + +; indoor transition, update player position +indoor_transition_set_pos: + lda #$05 ; a = #$05 + sta PLAYER_SPRITE_SEQUENCE,x ; player animation frame + lda PLAYER_JUMP_COEFFICIENT,x ; load player's jump modifier (alters height of jump) + ; used when animating walking into screen for indoor levels to keep track of overflows + ; to adjust y position + clc ; clear carry in preparation for addition + adc INDOOR_TRANSITION_Y_FRACT_VEL,x ; add fractional y velocity for animation to 'accumulator' + sta PLAYER_JUMP_COEFFICIENT,x ; set new 'accumulator' value + lda SPRITE_Y_POS,x ; player y position on screen + adc INDOOR_TRANSITION_Y_FAST_VEL,x ; add (or subtract) y position fast velocity to y position for advancing animation + ; including any overflow from the fractional velocity + sta SPRITE_Y_POS,x ; set new y position + lda INDOOR_SCROLL ; see if scrolling (0 = not scrolling; 1 = scrolling, 2 = finished scrolling) + cmp #$02 + bcc indoor_transition_exit + +; end player transition animation restore player position +indoor_transition_end: + lda #$00 ; a = #$00 + sta INDOOR_PLAYER_ADV_FLAG,x ; player is no longer walking between screens for indoor level, clear flag + lda PLAYER_INDOOR_ANIM_Y,x ; load y position when player started advancing into screen (#$a8) + sta SPRITE_Y_POS,x ; restore y position from beginning of advancing animation + lda PLAYER_INDOOR_ANIM_X,x ; load x position when player started advancing into screen + sta SPRITE_X_POS,x ; restore x position from beginning of advancing animation + +indoor_transition_exit: + rts + +player_land_on_ground: + lda PLAYER_JUMP_STATUS,x ; low nibble 1 = jumping, 0 not jumping; high nibble = facing direction + ora EDGE_FALL_CODE,x + beq @check_aim_dir ; brach if both are #$00 + lda #$00 ; a = #$00 + sta PLAYER_ANIMATION_FRAME_INDEX,x + sta PLAYER_ANIM_FRAME_TIMER,x + sta PLAYER_FALL_X_FREEZE,x + lda #$03 ; a = #$03 (sound_03) + jsr play_sound ; play player landing sound + +@check_aim_dir: + lda PLAYER_AIM_DIR,x + cmp #$05 ; see if player is facing right + lda PLAYER_SPRITE_FLIP,x ; load player sprite horizontal and vertical flip flags + and #$3f ; reset sprite flip data (clear bits 6 and 7) + bcc @continue ; branch if player is facing right + ora #$40 ; player facing left, flip sprite horizontally + +@continue: + sta PLAYER_SPRITE_FLIP,x ; store whether sprite is flipped horizontally and/or vertically + +init_player_data: + lda #$00 ; a = #$00 + sta PLAYER_JUMP_STATUS,x + sta EDGE_FALL_CODE,x + sta PLAYER_SPRITE_SEQUENCE,x + lda #$00 ; a = #$00 + sta PLAYER_Y_FRACT_VELOCITY,x + sta PLAYER_Y_FAST_VELOCITY,x + sta INDOOR_TRANSITION_Y_FRACT_VEL,x + sta INDOOR_TRANSITION_Y_FAST_VEL,x + rts + +; set the x and y velocities and a few other variables to initiate the player advancing into screen +set_player_advancing_vel: + lda #$00 ; a = #$00 + sta $12 ; negate the resulting velocities + lda #$58 ; a = #$58 (speed code - affects speed when walking up) + jsr set_vel_for_speed_code_a ; set fast ($0f) and fractional ($0e) y velocities for speed code a + lda $0f ; load fast y velocity + sta INDOOR_TRANSITION_Y_FAST_VEL,x ; set indoor transition y fast velocity + lda $0e ; load fractional y velocity + sta INDOOR_TRANSITION_Y_FRACT_VEL,x ; set indoor transition y fractional velocity + lda SPRITE_Y_POS,x ; load player y position + sta PLAYER_INDOOR_ANIM_Y,x ; set y position the player was at when started walking into screen + ; pretty sure always #$a8 since y pos is hard-coded for indoor levels + lda SPRITE_X_POS,x ; load player x position + sta PLAYER_INDOOR_ANIM_X,x ; set x position the player was at when started walking into screen + sec ; set carry flag in preparation for subtraction + sbc #$80 ; subtract #$80 from PLAYER_INDOOR_ANIM_X,x + sta $12 ; store whether to have negative velocity (walk left) based on x position + ; when player on right half of screen, player will advance inward towards left + ; when player on left half of screen, player will advance inward towards right + bcs @continue ; branch if on right half of the screen + eor #$ff ; player on left half of the screen, take negative x position and make positive + adc #$01 ; flip all bits and add #$01 + +@continue: + jsr set_vel_for_speed_code_a ; set fast ($0f) and fractional ($0e) x velocities for speed code a + lda $0f ; load fast x velocity + sta PLAYER_X_VELOCITY,x ; set x fast velocity + lda $0e ; load fractional x velocity + sta INDOOR_TRANSITION_X_FRACT_VEL,x ; set indoor transition x fractional velocity + rts + +; for a given value a, set fast ($0f) and fractional ($0e) velocities +; (a is rotated #$07 times into $0e) +; negate final results if $12 is non-negative +; * a - sort-of speed code, this value is split into fast and fractional velocity +; * $12 - when greater than or equal to #$00, specifies to negate the resulting velocities +; output +; * $0e - x or y fractional velocity +; * $0f - x or y fast velocity +set_vel_for_speed_code_a: + sta $0f ; store speed code in $0f + lda #$00 ; a = #$00 + sta $0e ; set $0e to #$00 + ldy #$07 ; set number of bits to rotate speed code to #$07 + + +; for a given value $0f, set fast ($0f) and fractional ($0e) velocities based on y +; negate final results if $12 is greater than or equal to #$00 +; also used directly for indoor bullets +; * $0f - a sort-of speed code, this value is split into fast and fractional velocity based on y +; * y - number of bits to rotate $0f into fractional velocity $0e (#$05, #$06, or #$07) +; * $12 - when greater than or equal to #$00, specifies to negate the resulting velocities +; output +; * $0e - x or y fractional velocity +; * $0f - x or y fast velocity +set_vel_for_speed_vars: + lsr $0f ; shift bit 0 to carry + ror $0e ; push bit 0 of $0f into bit 7 + dey ; decrement y + bne set_vel_for_speed_vars ; continue to shift the next bit into the fractional velocity + lda $12 ; load whether to negate calculated velocity + bpl @negate_bullet_velocities ; branch when $12 is greater than or equal to #$00 to negative velocity + rts + +@negate_bullet_velocities: + lda #$00 ; a = #$00 + sec ; set carry flag in preparation for subtraction + sbc $0e ; subtract the fractional velocity from #$00 (negate) + sta $0e ; set negated fractional x or y bullet velocity + lda #$00 ; a = #$00 + sbc $0f ; subtract the fast velocity from #$00 (negate) + sta $0f ; set negated fast x or y bullet velocity + rts + +; when landing, sets the player sprite Y position +set_player_landing_y_offset: + jsr sty_level_screen_type ; determine if screen needs to account for vertical offset + lda landing_y_position_tbl,y ; load vertical scroll offset + bne @set_sprite_y ; branch if hard-coded y landing position (indoor/base level, indoor/base boss screen) + lda VERTICAL_SCROLL ; load vertical scroll offset + and #$0f ; only care about low nibble (vertical offset within the nametable) + ora #$f0 ; set bits xxxx .... + sta $00 + clc ; clear carry in preparation for addition + adc SPRITE_Y_POS,x ; add offset to sprite position + and #$f0 ; keep bits xxxx .... + sec ; set carry flag in preparation for subtraction + sbc $00 + clc ; clear carry in preparation for addition + adc #$04 ; move player position down by #$04 since landing from fall/jump + +@set_sprite_y: + sta SPRITE_Y_POS,x ; set sprite position + rts + +; table for landing y position (#$03 bytes) +; when #$00 vertical offset and current position are taken into consideration +landing_y_position_tbl: + .byte $00,$c9,$a8 + +; calculates the player aim direction based on d-pad input, facing direction, and jump status +set_player_aim_for_input: + ldy #$20 ; y = #$20 (row 2 of d_pad_player_aim_tbl) + lda PLAYER_JUMP_STATUS ; low nibble 1 = jumping, 0 not jumping; high nibble = facing direction + beq @continue ; branch if not jumping + ldy #$30 ; jumping, set y = #$30 (row 3 of d_pad_player_aim_tbl) + +@continue: + lda $08 ; load level_spawn_position_index value for level + cmp #$03 ; see if indoor boss screen + beq @set_player_aim ; branch if indoor boss screen + ldy #$00 ; assume player facing facing right + lda PLAYER_AIM_PREV_FRAME,x ; see which direction was the last frame + cmp #$05 ; compare to player crouching facing left + bcc @set_player_aim ; branch if facing left + ldy #$10 ; y = #$10 (row 1 of d_pad_player_aim_tbl) + +@set_player_aim: + sty $08 ; set $08 to #$00, #$10, #$20, or #$30 depending on facing direction and jump status + lda CONTROLLER_STATE,x ; read controller state + and #$0f ; keep bits .... xxxx (d pad input) + clc ; clear carry in preparation for addition + adc $08 ; add d-pad input to $08 + tay ; transfer + lda d_pad_player_aim_tbl,y ; load the aim direction based on the d-pad input + sta PLAYER_AIM_DIR,x ; set new player aim direction + rts + +; table for player aim direction code (#$40 bytes) +; each row is a type of aiming depending on player state and level type +; each byte offset represents a d-pad direction +; below is an example for 0th row (facing right) +; * d-pad value #$00 - no input - #$02 - facing right aiming up +; * d-pad value #$01 - r - #$02 - facing right +; * d-pad value #$02 - l - #$07 - facing left +; * d-pad value #$03 - l and r - #$02 - facing right +; * d-pad value #$04 - d - #$05 - crouch facing right +; * d-pad value #$05 - d and r - #$03 - aim down right +; * d-pad value #$06 - d and l - #$06 - aim down left +; * d-pad value #$07 - d l and r - #$02 - facing right +; * d-pad value #$08 - u - #$00 - facing right aiming up +; * d-pad value #$09 - u and r - #$01 - aiming up and right +; * d-pad value #$0a - u and l - #$08 - aiming up and left +; * d-pad value #$0b - l r and u - #$02 - facing right +; * d-pad value #$0c - u and d - #$07 - facing left +; * d-pad value #$0d - u d and r - #$02 - facing right +; * d-pad value #$0e - u d and l - #$07 - facing left +; * d-pad value #$0f - u d l and r - #$02 - facing right +d_pad_player_aim_tbl: + .byte $02,$02,$07,$02,$04,$03,$06,$02,$00,$01,$08,$02,$07,$02,$07,$02 ; standing facing right + .byte $07,$02,$07,$02,$05,$03,$06,$02,$09,$01,$08,$02,$07,$02,$07,$02 ; facing left + .byte $00,$02,$07,$00,$00,$02,$07,$02,$00,$01,$08,$02,$07,$02,$07,$02 + .byte $00,$02,$07,$00,$0a,$03,$06,$02,$00,$01,$08,$02,$07,$02,$07,$02 ; jumping and indoor boss + +; see if player should check for walking off ledge and if so, walk off it +check_player_ledge: + lda PLAYER_ON_ENEMY,x ; see if player is on non-dangerous enemy, e.g. (#$14, #$15 - mining cart, #$10 - floating rock platform) + bne @clear_edge_code_exit ; branch if player is on top non-dangerous enemy + lda PLAYER_WATER_STATE,x ; player not on enemy, load player water state + ora PLAYER_JUMP_STATUS,x ; merge with player jump status + ora EDGE_FALL_CODE,x ; merge with edge fall code + ora INDOOR_PLAYER_ADV_FLAG,x ; merge with whether or not the player is walking between screens for indoor level + bne @exit ; exit if any of the previous variables were non-zero + lda PLAYER_BG_FLAG_EDGE_DETECT,x ; see if should detect falling off ledge or skip + lsr + bcs @clear_edge_code_exit + jsr get_player_bg_collision_code ; get player background collision code + beq jmp_walk_off_ledge ; collision code is #$00 (empty), player is not on ground, fall + cmp #$02 ; see if player is in water (collision code #$02) + beq init_PLAYER_WATER_STATE ; if in water, initialize player in water animation + +@clear_edge_code_exit: + lda #$00 + sta EDGE_FALL_CODE,x + +@exit: + rts + +init_PLAYER_WATER_STATE: + lda #$01 + sta PLAYER_WATER_STATE,x ; initialize player in water animation + rts + +jmp_walk_off_ledge: + jmp walk_off_ledge + +; retrieves the collision code of the player and sets it in register a +; output +; * carry flag set when on floor +; #$00 - empty +; #$01 - floor +; #$02 - water +; #$80 - solid (not 3 like normal) +get_player_bg_collision_code: + lda LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor; #$80 if indoor boss screen + bmi @indoor_boss_level ; branch if indoor boss level + bne @indoor_floor ; non-indoor boss level, determine max Y position + lda SPRITE_Y_POS,x ; outdoor level, load player y position on screen + clc ; clear carry in preparation for addition + adc #$10 ; add #$10 to player Y position + bcs @exit_code_0 ; branch if overflow + tay ; transfer player y position to the y register + lda SPRITE_X_POS,x ; player x position on screen + jmp get_bg_collision ; not indoor nor indoor boss level, use get_bg_collision to get collision code + +@indoor_floor: + lda #$a0 ; indoor non-boss levels have a hard-coded max Y value of #$a0 + bne @indoor_continue ; always branch + +@indoor_boss_level: + lda #$c8 ; lowest Y value for indoor boss level is hard-coded #$c8 + +@indoor_continue: + cmp SPRITE_Y_POS,x ; compare max Y to y position on screen + lda #$01 ; prep collision code to #$01 (floor) if object is not at bottom of screen (walking to next screen) + bcc @exit ; branch if not at bottom of screen + +@exit_code_0: + lda #$00 ; collision code #$00 (not colliding with anything, or bottom of screen) + +@exit: + rts + + +; increments y fractional velocity by #$23 (applying gravity) and then sets y position +; based on y velocity and PLAYER_JUMP_COEFFICIENT +apply_gravity_set_y_pos: + jsr apply_gravity ; increments y fractional velocity by #$23 (applying gravity) + +; player is jumping, set player y position based on PLAYER_JUMP_COEFFICIENT and velocity +player_jumping_set_y_pos: + lda PLAYER_Y_FAST_VELOCITY,x ; load player's fast y velocity + asl ; shift negative bit to carry flag + lda #$00 ; player falling down, or not falling (0 velocity) + bcc @continue ; branch if player is moving down (down or #$00 y velocity) + lda #$ff ; player moving up + +@continue: + sta $08 ; store either #$00 or #$ff in $08 + lda PLAYER_JUMP_COEFFICIENT,x ; load player's jump modifier (alters height of jump) + clc ; clear carry in preparation for addition + adc PLAYER_Y_FRACT_VELOCITY,x ; add to player's fractional y velocity + sta PLAYER_JUMP_COEFFICIENT,x ; update player's jump modifier (alters height of jump) + lda SPRITE_Y_POS,x ; load player's y position + adc PLAYER_Y_FAST_VELOCITY,x ; add (subtract) the fast y velocity (with jump coefficient overflow) + sta SPRITE_Y_POS,x ; set player's new y position + lda PLAYER_HIDDEN,x ; 0 - visible; #$01/#$ff = invisible (any non-zero) + adc $08 ; add or subtract #$01 to specify whether player is visible (with any overflow) + sta PLAYER_HIDDEN,x ; set whether to draw player sprite, not sure how this is really supposed to be used + rts + +; increments y fractional velocity by #$23 (applying gravity) +apply_gravity: + clc + lda PLAYER_Y_FRACT_VELOCITY,x ; load player's y fractional velocity + adc #$23 ; add #$23 to y fractional velocity + sta PLAYER_Y_FRACT_VELOCITY,x ; update player's y fractional velocity + lda PLAYER_Y_FAST_VELOCITY,x ; load player's fast y velocity + adc #$00 ; add carry into high byte (any overflow when adding to fractional y velocity) + sta PLAYER_Y_FAST_VELOCITY,x ; update player's fast y velocity + rts + +; player death +init_player_dec_num_lives: + jsr init_player_and_weapon + sta PLAYER_STATE,x ; set player state to #$00 (falling into level) + lda P1_NUM_LIVES,x ; load player number of lives + beq @set_game_over ; branch if no more lives + dec P1_NUM_LIVES,x ; decrement player number of lives + rts + +@set_game_over: + lda #$01 ; a = #$01 + sta P1_GAME_OVER_STATUS,x ; game over state of player (1 = game over) + rts + +; sets FRAME_SCROLL and PLAYER_FRAME_SCROLL if player is causing screen to scroll +set_frame_scroll_if_appropriate: + lda LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor + ora AUTO_SCROLL_TIMER_00 ; merge with AUTO_SCROLL_TIMER_00 + ora AUTO_SCROLL_TIMER_01 ; merge with AUTO_SCROLL_TIMER_01 + bne @exit ; if indoor, or any auto scroll timer running, exit + lda PLAYER_STATE,x ; load player state (0 = dropping into level, 1 = normal, 2 = dead, 3 = can't move) + cmp #$01 ; see if player in normal state + bne @exit ; exit if in normal state + lda LEVEL_SCROLLING_TYPE ; 0 = horizontal, indoor/base; 1 = vertical + bne set_vertical_level_frame_scroll ; branch if vertical scrolling + lda LEVEL_STOP_SCROLL ; horizontal, indoor/base level, load screen to stop scrolling at + bmi @exit ; exit if boss auto scroll set (LEVEL_STOP_SCROLL is #$ff when boss auto scroll started) + ldy PLAYER_GAME_OVER_BIT_FIELD ; #$00 = p1 not game over, p2 game over (or not playing) + ; #$01 = p1 game over, p2 not game over, #$02 = p1 nor p2 are in game over + lda SPRITE_X_POS,x ; load player x position + cmp horizontal_scroll_point_tbl,y ; load horizontal scroll point position + bcc @exit ; if not yet reached point to initiate horizontal scroll, exit + cpy #$02 ; see if both players are active + bne @set_horizontal_level_frame_scroll ; if both players aren't active, begin setting scroll + txa ; both players active, see if other player is preventing screen scroll, transfer player index to a + eor #$01 ; swap to other player + tay ; transfer player index to y + lda SPRITE_X_POS,y ; load other player's x position + cmp #$21 ; compare other player to left edge of screen + bcc @stop_player_x_velocity ; don't scroll if other player is preventing it by being too far to the left + +; sets FRAME_SCROLL if needed on horizontal levels +; sees if player is causing scroll +; if so sets FRAME_SCROLL to the right value based on Y velocity and PLAYER_JUMP_COEFFICIENT +; output +; * carry flag - #$01 set when scroll initiated +@set_horizontal_level_frame_scroll: + jsr set_boss_auto_scroll ; starts the auto scroll to reveal boss if at right screen, otherwise do nothing + beq @exit ; branch if boss auto scroll started + lda INDOOR_TRANSITION_X_FRACT_VEL,x ; load player's indoor x velocity (#$00 unless on indoor/base level) + clc ; clear carry in preparation for addition + adc INDOOR_TRANSITION_X_ACCUM,x + sta INDOOR_TRANSITION_X_ACCUM,x ; update INDOOR_TRANSITION_X_ACCUM with increased velocity + lda PLAYER_X_VELOCITY,x ; load player X velocity + adc #$00 ; incorporate any velocity overflow from INDOOR_TRANSITION_X_ACCUM + sta FRAME_SCROLL ; set screen scroll amount + lda #$01 ; a = #$01 + sta PLAYER_FRAME_SCROLL,x ; set player scroll amount for player causing scroll + lda #$00 ; a = #$00 + sta INDOOR_TRANSITION_X_FRACT_VEL,x ; clear player indoor velocity + sta PLAYER_X_VELOCITY,x ; clear player x velocity + sec ; set carry flag + rts + +@stop_player_x_velocity: + lda #$00 ; a = #$00 + sta INDOOR_TRANSITION_X_FRACT_VEL,x + sta PLAYER_X_VELOCITY,x + +@exit: + clc + rts + +; load the x point on horizontal levels to start scrolling the screen +; when 1 player mode, or only 1 player alive, it's the middle of the screen (#$80) +; for 2 active players, it's 70% of the screen (#$b0) +; byte 0 = p1 not game over, p2 game over (or not playing) +; byte 1 = p1 game over, p2 not game over +; byte 2 = p1 nor p2 are in game over +horizontal_scroll_point_tbl: + .byte $80,$80,$b0 + +; sets FRAME_SCROLL if needed on vertical level +; sees if player is jumping up and high enough to cause scrolling +; if so sets FRAME_SCROLL to the right value based on Y velocity and PLAYER_JUMP_COEFFICIENT +; output +; * carry flag - #$01 set when scroll initiated, #$00 when scroll not initiated +set_vertical_level_frame_scroll: + lda SPRITE_Y_POS,x ; load player y position + cmp #$50 ; compare to #$50 + bcs @exit ; exit if player y position > #$50, i.e. far down the screen + ; once player is above #$50, scrolling up is initiated + lda PLAYER_Y_FAST_VELOCITY,x ; load player's y fast velocity byte + bpl @exit ; exit if player falling down + jsr set_boss_auto_scroll ; starts the auto scroll to reveal boss if at right screen, otherwise do nothing + beq @exit ; branch if boss auto scroll started + lda PLAYER_JUMP_COEFFICIENT,x ; player is jumping up, load player's jump modifier (alters height of jump) + clc ; clear carry in preparation for addition + adc PLAYER_Y_FRACT_VELOCITY,x ; add the player's fractional velocity to the jump modifier + sta PLAYER_JUMP_COEFFICIENT,x ; update player's jump modifier (alters height of jump) + lda SPRITE_Y_POS,x ; re-load player y position + adc PLAYER_Y_FAST_VELOCITY,x ; add player y fast velocity + sta $08 ; store sum in $08 + lda SPRITE_Y_POS,x ; re-load player y position + sec ; set carry flag in preparation for subtraction + sbc $08 ; subtract one frame of distance of the jump + sta FRAME_SCROLL ; store scroll amount + lda #$01 ; a = #$01 + sta PLAYER_FRAME_SCROLL,x ; set player scroll amount for player causing scroll + sec ; set carry flag + rts + +@exit: + clc + rts + +; determines if player can drop down (d-pad down and A) +; player can drop down when solid, water, or floor bg collision below the current player +; vertical levels always allow drop down regardless if collision below player +; input +; * x - player index +; output +; * carry flag - clear when player can "drop down" (d-pad down + A) +; i.e. there is a solid, water, or floor collision below the player +; set when player cannot "drop down" (d-pad down + A) +can_player_drop_down: + lda LEVEL_STOP_SCROLL ; load the screen to stop scrolling on, set to #$ff when boss auto scroll starts + cmp #$ff ; see if boss auto scroll has started + beq @continue ; branch if auto scroll has started + lda LEVEL_SCROLLING_TYPE ; 0 = horizontal, indoor/base; 1 = vertical + bne collision_below_player ; clear carry and exit for vertical level, always can drop down + +@continue: + lda SPRITE_Y_POS,x ; load player y position + clc ; clear carry in preparation for addition + adc #$10 ; prepare to move player down by #$10 pixels + +; determines if solid, water, or floor bg collision below the player (all the way to the bottom of screen) +; input +; * a - y position to test +; * x - player offset +; output +; * a - collision code +; * carry flag - set when only empty collision codes below player, i.e. there is something to land on +; set when solid, water, or ground beneath player +check_collision_below: + tay ; transfer y position to y register + lsr + lsr + lsr + lsr + sta $08 ; store high nibble of player y position in $08 + lda SPRITE_X_POS,x ; load player x position + jsr get_bg_collision_far ; determine player background collision code for (a, y) position + bmi set_carry_exit ; exit if collision with solid bg element, can't drop down + +; loops down to bottom of screen looking for a bg collision +@loop: + inc $08 ; increment y position high byte, i.e. bg collision row + lda $08 ; load y position high byte + cmp #$0e ; compare to last bg collision row + bcs can_player_drop_down_exit ; exit if checked all rows below player + lda $13 ; load BG_COLLISION_DATA offset + and #$c0 ; keep bits xx.. .... + sta $17 + lda $13 ; re-load BG_COLLISION_DATA offset + clc ; clear carry in preparation for addition + adc #$04 ; move down one row + and #$3f ; keep bits ..xx xxxx + ora $17 ; determine final BG_COLLISION_DATA + tay ; move to BG_COLLISION_DATA + jsr read_bg_collision_byte_unsafe ; get collision code from BG_COLLISION_DATA byte + beq @loop ; loop if no collision, collision code #$00 (empty) + +collision_below_player: + clc ; clear when found non-empty collision + +can_player_drop_down_exit: + rts + +set_carry_exit: + sec ; set carry flag + rts + +init_player_and_weapon: + lda #$00 ; a = #$00 + sta P1_CURRENT_WEAPON,x ; reset current player's weapon + +init_player_attributes: + lda #$00 ; a = #$00 + sta CPU_SPRITE_BUFFER,x + sta SPRITE_Y_POS,x + sta SPRITE_X_POS,x + sta SPRITE_ATTR,x + sta INDOOR_TRANSITION_X_ACCUM,x + sta PLAYER_JUMP_COEFFICIENT,x + sta INDOOR_TRANSITION_X_FRACT_VEL,x + sta PLAYER_X_VELOCITY,x + sta INDOOR_TRANSITION_Y_FRACT_VEL,x + sta INDOOR_TRANSITION_Y_FAST_VEL,x + sta PLAYER_ANIM_FRAME_TIMER,x + sta PLAYER_JUMP_STATUS,x + sta PLAYER_FRAME_SCROLL,x + sta EDGE_FALL_CODE,x + sta PLAYER_ANIMATION_FRAME_INDEX,x + sta PLAYER_INDOOR_ANIM_Y,x + sta PLAYER_M_WEAPON_FIRE_TIME,x + sta NEW_LIFE_INVINCIBILITY_TIMER,x + sta INVINCIBILITY_TIMER,x + sta PLAYER_WATER_STATE,x + sta PLAYER_DEATH_FLAG,x + sta PLAYER_ON_ENEMY,x + sta PLAYER_FALL_X_FREEZE,x + sta PLAYER_HIDDEN,x + sta PLAYER_SPRITE_SEQUENCE,x + sta PLAYER_INDOOR_ANIM_X,x + sta PLAYER_AIM_PREV_FRAME,x + sta PLAYER_AIM_DIR,x + sta PLAYER_Y_FRACT_VELOCITY,x + sta PLAYER_Y_FAST_VELOCITY,x + sta ELECTROCUTED_TIMER,x + sta INDOOR_PLAYER_JUMP_FLAG,x + sta PLAYER_WATER_TIMER,x + sta PLAYER_RECOIL_TIMER,x + sta INDOOR_PLAYER_ADV_FLAG,x + sta PLAYER_SPRITE_CODE,x + sta PLAYER_SPRITE_FLIP,x + sta PLAYER_BG_FLAG_EDGE_DETECT,x + rts + +; on player x position change +; input +; * a - amount to add to player x position +; * x - player offset +; output +; * carry flag - set when collided with solid object +check_player_solid_bg_collision: + clc ; clear carry in preparation for addition + adc SPRITE_X_POS,x ; add a to player x position + sta $0a ; store x position in $0a + ldy #$0b ; default amount to subtract from player y position + lda PLAYER_SPRITE_CODE,x ; load player sprite + cmp #$17 ; compare to player prone sprite + bne @continue ; branch if player isn't prone + ldy #$00 ; player is prone, don't subtract from player y position + +@continue: + sty $08 ; set amount to subtract from player y position + lda PLAYER_HIDDEN,x ; 0 = visible; #$01/#$ff = invisible (any non-zero) + bne @continue_2 ; branch if invisible + lda SPRITE_Y_POS,x ; load player y position + sec ; set carry flag in preparation for subtraction + sbc $08 ; subtract either #$0b or #$00 from player y position + bcc @continue_2 ; branch if underflow + cmp #$10 ; compare calculated y position to #$10 + bcs @continue_3 ; branch if result is grater than #$10 + +@continue_2: + lda #$0a ; a = #$0a + +@continue_3: + sta $09 ; set calculated y position (position above player) + jsr get_player_bg_collision_code ; get player background collision code, set result in a + bne @player_bg_collision ; branch if collision code is not #$00 (empty) + lda PLAYER_JUMP_STATUS,x ; collision code #$00 (empty) load jump status + ; low nibble 1 = jumping, 0 not jumping; high nibble = facing direction + lsr ; shift jump bit to carry flag + lda #$1b ; a = #$1b + bcs @find_bg_collision ; branch if jumping + +@player_bg_collision: + lda #$0a ; a = #$0a + clc ; clear carry in preparation for addition + adc $08 ; add #$0a to the amount subtracted from player y position + +@find_bg_collision: + sta $08 ; store new y position distance + lda $09 ; load calculated y position (position above player) + clc ; clear carry in preparation for addition + adc VERTICAL_SCROLL ; vertical scroll offset + and #$0f ; keep bits .... xxxx + sta $0b + lda #$10 ; a = #$10 + sec ; set carry flag in preparation for subtraction + sbc $0b + sta $0b + lda $0a ; load x position + ldy $09 ; load y position + jsr get_bg_collision_far ; determine player background collision code at position (a,y) + bmi @set_carry_exit ; branch if collision with solid bg element + +; look at background collision tiles in front of player +@loop: + lda $0b + cmp $08 ; compare to y position + bcs @clear_carry_exit + adc #$10 + sta $0b + lda $13 + and #$c0 ; keep bits xx.. .... + sta $17 + lda $13 + clc ; clear carry in preparation for addition + adc #$04 + and #$3f ; keep bits ..xx xxxx + ora $17 + tay ; set BG_COLLISION_DATA offset + jsr find_floor_collision ; get collision code at BG_COLLISION_DATA,y and if not floor + ; look down one collision row and get that collision code + bpl @loop ; loop if collision code is non-empty + +@set_carry_exit: + sec ; set carry flag + rts + +@clear_carry_exit: + clc + rts + +; initializes PPU scroll offset, PPU write offsets +; then calls load_current_supertiles_screen_indexes to decompress super-tiles +; of the current level's current screen to load into CPU memory at LEVEL_SCREEN_SUPERTILES +init_ppu_write_screen_supertiles: + lda LEVEL_SCROLLING_TYPE ; load level scroll (horizontal or vertical) + bne config_vertical_scrolling ; set-up level for vertical scrolling + lda LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor + bne continue_init_level ; skip scrolling setup for indoor levels + lda #$30 + +; initializes scrolling offsets and sets PPU write address to top left of nametable +; sets up the attribute table write address to the first attribute table +config_horizontal_scrolling: + sta LEVEL_TRANSITION_TIMER ; set to a (either #$30 for indoor level or #$20 for outdoor level) + lda #$00 + sta LEVEL_SCREEN_NUMBER + sta LEVEL_SCREEN_SCROLL_OFFSET ; scrolling offset in pixels within screen + sta SUPERTILE_NAMETABLE_OFFSET ; set to nametable 0 (#$00) + sta PPU_WRITE_TILE_OFFSET + sta PPU_WRITE_ADDRESS_LOW_BYTE + lda #$20 + sta PPU_WRITE_ADDRESS_HIGH_BYTE ; set PPU write address to $2000 + lda #$c0 + sta ATTRIBUTE_TBL_WRITE_LOW_BYTE ; since all attribute tables have #$c0 in their low byte, this is actually never read, just stored + lda #$23 + sta ATTRIBUTE_TBL_WRITE_HIGH_BYTE ; $23c0 is the first nametable attribute table + jmp load_current_supertiles_screen_indexes ; load the super tile indexes for the screen into memory at LEVEL_SCREEN_SUPERTILES + +; initializes scrolling offsets and sets PPU write address to bottom left of nametable +config_vertical_scrolling: + lda #$00 + sta LEVEL_SCREEN_SCROLL_OFFSET ; scrolling offset in pixels within screen + sta LEVEL_SCREEN_NUMBER + sta SUPERTILE_NAMETABLE_OFFSET ; set to nametable 0 (#$00) + lda #$1d + sta PPU_WRITE_TILE_OFFSET ; vertical levels start with #$1d and are decremented as level scrolls up + lda #$a0 + sta PPU_WRITE_ADDRESS_LOW_BYTE + lda #$23 + sta PPU_WRITE_ADDRESS_HIGH_BYTE ; sets write address begin to #$23a0, which is the bottom left of the vertical level's nametable + jmp load_current_supertiles_screen_indexes ; load the super tile indexes for the screen into memory at LEVEL_SCREEN_SUPERTILES + +continue_init_level: + lda #$10 + sta BG_PALETTE_ADJ_TIMER + jsr load_palettes_color_to_cpu ; load #$10 palette colors into PALETTE_CPU_BUFFER based on LEVEL_PALETTE_INDEX + lda #$20 + bne config_horizontal_scrolling ; always jump since lda #$20 clears zero flag + +; animate initial level nametable drawing +; output +; * zero flag - set when LEVEL_TRANSITION_TIMER has elapsed, clear otherwise +init_lvl_nametable_animation: + lda LEVEL_SCROLLING_TYPE ; 0 = horizontal, indoor/base; 1 = vertical + bne @vertical_level ; branch for vertical level + lda LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor + bne @indoor_level ; branch for indoor level + jsr load_column_of_tiles_to_cpu_buffer ; outdoor horizontal level, load the next column of tiles to CPU for drawing + ; set bg collision data in CPU memory + jsr write_col_attribute_to_cpu_memory ; write a column of attribute palette data (#$07 bytes) to the CPU graphics buffer + inc PPU_WRITE_ADDRESS_LOW_BYTE ; increment PPU write address low byte (move to next nametable column) + inc PPU_WRITE_TILE_OFFSET + dec LEVEL_TRANSITION_TIMER + beq @exit ; exit with zero flag set when LEVEL_TRANSITION_TIMER is #$00 + lda PPU_WRITE_TILE_OFFSET ; transition timer hasn't elapsed + cmp #$20 ; see if finished writing entire nametable with pattern table tiles + bcc @set_a_exit ; branch if not finished with nametable + lda #$00 ; finished writing entire nametable, reset PPU write address low byte + sta PPU_WRITE_ADDRESS_LOW_BYTE ; set PPU write address low byte to #$00 + sta PPU_WRITE_TILE_OFFSET ; reset ppu write tile column offset + lda #$24 ; set to top-right nametable + sta PPU_WRITE_ADDRESS_HIGH_BYTE ; move PPU write address to top right nametable + lda #$27 ; load attribute table to write to (top-right [$27c0-$27f8]) + sta ATTRIBUTE_TBL_WRITE_HIGH_BYTE ; set attribute table write address to $27c0 (2nd attribute table) + lda #$40 ; load 2nd nametable offset into $0600 (LEVEL_SCREEN_SUPERTILES) for super-tile indexes + sta SUPERTILE_NAMETABLE_OFFSET ; set to nametable 1 (#$40) + jsr load_next_supertiles_screen_indexes ; load the super tile indexes for the upcoming screen into memory at LEVEL_SCREEN_SUPERTILES + +@set_a_exit: + lda #$ff ; exit with zero flag clear + +@exit: + rts + +@vertical_level: + jsr set_vert_lvl_super_tiles + jsr write_row_attribute_to_cpu_memory ; write a row of attribute palette data (#$08 bytes) to the CPU graphics buffer + lda PPU_WRITE_ADDRESS_LOW_BYTE + sec ; set carry flag in preparation for subtraction + sbc #$20 + sta PPU_WRITE_ADDRESS_LOW_BYTE + lda PPU_WRITE_ADDRESS_HIGH_BYTE + sbc #$00 + sta PPU_WRITE_ADDRESS_HIGH_BYTE + dec PPU_WRITE_TILE_OFFSET + bpl @set_a_exit + jsr config_vertical_scrolling + lda #$40 ; a = #$40 + sta SUPERTILE_NAMETABLE_OFFSET ; set to nametable 1 (#$40) + jsr load_next_supertiles_screen_indexes ; load the super tile indexes for the upcoming screen into memory at LEVEL_SCREEN_SUPERTILES + lda #$00 ; a = #$00 + rts + +; init_lvl_nametable_animation - indoor level +@indoor_level: + jsr load_column_of_tiles_to_cpu_buffer + jsr write_col_attribute_to_cpu_memory ; write a column of attribute palette data (#$07 bytes) to the CPU graphics buffer + inc PPU_WRITE_ADDRESS_LOW_BYTE + inc PPU_WRITE_TILE_OFFSET + dec LEVEL_TRANSITION_TIMER + rts + +; handles scrolling for the level if currently scrolling +; including writing tiles to nametable, writing to the attribute table, and loading alternate graphics +; includes handling auto scroll from boss reveal or tank +handle_scroll: + lda LEVEL_SCROLLING_TYPE ; 0 = horizontal, indoor/base; 1 = vertical + beq @handle_horizontal_level ; handle horizontal level + jmp handle_vertical_scroll ; vertical level, jump + +@handle_horizontal_level: + lda LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor + beq @handle_outdoor_level ; branch for outdoor level + jmp handle_indoor_scroll ; indoor level, handle scrolling while advancing to next screen + ; when not scrolling, this method animates the electric fence + +@handle_outdoor_level: + lda AUTO_SCROLL_TIMER_01 ; see if auto scrolling enabled to reveal boss + beq @check_scroll_timer_00 ; branch if auto scrolling 01 not set, check boss reveal auto scroller + dec AUTO_SCROLL_TIMER_01 ; decrement auto scrolling timer + bne @set_scroll_frame ; branch if timer still hasn't elapsed + +@check_scroll_timer_00: + lda AUTO_SCROLL_TIMER_00 ; see if auto scrolling enabled to reveal boss + beq @include_tank_auto_scroll ; branch if auto scrolling not set + dec AUTO_SCROLL_TIMER_00 ; decrement auto scrolling timer + bne @set_scroll_frame ; branch if timer still hasn't elapsed, to continue screen scroll + inc BOSS_AUTO_SCROLL_COMPLETE ; set boss reveal auto-scroll completed + +; scroll screen for this video frame +@set_scroll_frame: + lda #$01 ; a = #$01 + sta FRAME_SCROLL ; how much to scroll the screen (#00 - no scroll) + +@include_tank_auto_scroll: + lda FRAME_SCROLL ; how much to scroll the screen (#00 - no scroll) + clc ; clear carry in preparation for addition + adc TANK_AUTO_SCROLL ; auto scroll additional amount, always add this value to scroll + beq @exit ; exit if no scrolling is required + sta $17 ; set in memory frame scroll value (including tank auto scroll) + +; sets alternative graphics loading flag if on correct screen +; loads alternate graphics if necessary +; then checks if need to move to next screen +@set_scroll_graphics_data: + inc LEVEL_SCREEN_SCROLL_OFFSET ; scrolling offset in pixels within screen + bne @inc_nametable_data ; branch if camera has scrolled scrolled within frame + inc LEVEL_SCREEN_NUMBER ; new screen, increment screen number + lda LEVEL_SCREEN_NUMBER ; load current screen number within the level + cmp LEVEL_ALT_GRAPHICS_POS ; compare to screen where alternate graphics should start loading + bne @change_screen ; skip loading alternate graphics if not at location to load them + ; start preparing the new nametable data + lda #$01 ; at position to load alternate graphics + sta ALT_GRAPHIC_DATA_LOADING_FLAG ; set the alternate graphics loading flag so alternate graphics will be loaded + jsr load_alternate_graphics ; load alternate graphics + +; initialize the new nametable data: enemy screen read offset, PPUCTRL, PPU write address, attribute write address +@change_screen: + lda #$00 ; a = #$00 + sta ENEMY_SCREEN_READ_OFFSET ; set offset into level_xx_enemy_screen_xx table + lda PPUCTRL_SETTINGS ; load current PPU controller settings + eor #$01 ; swap to other nametable ($2000 or $2400) + sta PPUCTRL_SETTINGS ; update base nametable address + +; handle increment graphics data write offsets and if necessary load new data to cpu buffer +@inc_nametable_data: + lda LEVEL_SCREEN_SCROLL_OFFSET ; scrolling offset within screen in pixels + and #$07 ; keep bits .... .xxx + bne @check_attr_update ; branch if not a new nametable tile column + ; to check if need to update attribute table or just exit + jsr load_column_of_tiles_to_cpu_buffer ; new nametable column + inc PPU_WRITE_ADDRESS_LOW_BYTE + inc PPU_WRITE_TILE_OFFSET + lda PPU_WRITE_TILE_OFFSET + cmp #$20 ; super-tiles are #$20 (32 decimal) pixels wide + bcc @inc_scroll_exit ; branch if PPU_WRITE_TILE_OFFSET < 20 + lda SUPERTILE_NAMETABLE_OFFSET ; load offset into CPU graphics buffer where super-tile indexes are stored for current screen + eor #$40 ; flip bits .x.. .... + sta SUPERTILE_NAMETABLE_OFFSET ; move to other nametable (#$00 = nametable 0, #$40 = nametable 1) + jsr load_next_next_supertiles_screen_indexes ; load the super tile indexes for the screen 2 screens ahead into memory at LEVEL_SCREEN_SUPERTILES + lda #$00 ; a = #$00 + sta PPU_WRITE_ADDRESS_LOW_BYTE + sta PPU_WRITE_TILE_OFFSET ; $60 was #$1f, set back to #$00 + lda PPU_WRITE_ADDRESS_HIGH_BYTE + eor #$04 ; flip bits .... .x.. + sta PPU_WRITE_ADDRESS_HIGH_BYTE + lda ATTRIBUTE_TBL_WRITE_HIGH_BYTE ; load current attribute table write address + eor #$04 ; flip bits .... .x.. + sta ATTRIBUTE_TBL_WRITE_HIGH_BYTE ; switch attribute table write address between $27c0 and $23c0, or $2bc0 and $2fc0 + +@inc_scroll_exit: + inc HORIZONTAL_SCROLL ; increment horizontal component of the PPUSCROLL [#$0 - #$ff] + dec $17 ; decrement in memory frame scroll value (including tank auto scroll) + bne @set_scroll_graphics_data ; branch if still has scroll, this will only happen when for tank auto scroll + ; otherwise done handling scroll, exit + +@exit: + rts + +@check_attr_update: + lda LEVEL_SCREEN_SCROLL_OFFSET ; load the number of pixels into LEVEL_SCREEN_NUMBER the level has scrolled + and #$0f ; keep low nibble + cmp #$03 + bne @inc_scroll_exit ; branch if scroll offset doesn't end in #$03, only update attribute table every #$f pixels + jsr write_col_attribute_to_cpu_memory ; 16 frames have scrolled, write next column of attribute palette data (#$08 bytes) to the CPU graphics buffer + jmp @inc_scroll_exit + +; vertical level +handle_vertical_scroll: + lda AUTO_SCROLL_TIMER_00 ; load scroll to reveal boss timer + beq @init_loop ; branch if scroll complete + lda #$10 ; a = #$10 + sta BG_PALETTE_ADJ_TIMER + lda #$01 ; a = #$01 + sta FRAME_SCROLL ; how much to scroll the screen (#00 - no scroll) + dec AUTO_SCROLL_TIMER_00 ; decrement boss reveal scroll + bne @init_loop + inc BOSS_AUTO_SCROLL_COMPLETE ; set boss reveal auto-scroll completed + +@init_loop: + lda FRAME_SCROLL ; how much to scroll the screen (#00 - no scroll) + beq @exit ; exit + sta $17 ; store frame scroll in $17 + +@frame_scroll_loop: + inc LEVEL_SCREEN_SCROLL_OFFSET ; increment scrolling offset in pixels within screen + lda LEVEL_SCREEN_SCROLL_OFFSET + cmp #$f0 + bcc @continue ; branch if a < #$f0 + lda #$00 ; a = #$00, scroll is >= #$f0, move to next screen + sta LEVEL_SCREEN_SCROLL_OFFSET ; reset scrolling offset in pixels within screen + sta ENEMY_SCREEN_READ_OFFSET ; offset for enemy data + inc LEVEL_SCREEN_NUMBER + lda LEVEL_SCREEN_NUMBER ; load current screen number within the level + cmp LEVEL_ALT_GRAPHICS_POS ; compare to screen where alternate graphics should start loading + bne @continue ; branch if not on the screen where alternate graphics should load + lda #$01 + sta ALT_GRAPHIC_DATA_LOADING_FLAG ; reached screen where alternate graphics should start loading, set flag + lda #$80 ; a = #$80 + sta BG_PALETTE_ADJ_TIMER + jsr load_alternate_graphics + +@continue: + lda LEVEL_SCREEN_SCROLL_OFFSET ; scrolling offset in pixels within screen + and #$07 ; keep bits .... .xxx + bne @write_attribute_continue + jsr set_vert_lvl_super_tiles + lda PPU_WRITE_ADDRESS_LOW_BYTE + sec ; set carry flag in preparation for subtraction + sbc #$20 + sta PPU_WRITE_ADDRESS_LOW_BYTE + lda PPU_WRITE_ADDRESS_HIGH_BYTE + sbc #$00 + sta PPU_WRITE_ADDRESS_HIGH_BYTE + dec PPU_WRITE_TILE_OFFSET + bpl @dec_scroll_continue ; decrement vertical scroll and continue loop + lda SUPERTILE_NAMETABLE_OFFSET ; load offset into CPU graphics buffer where super-tile indexes are stored for current screen + eor #$40 ; move to other nametable of super-tile index data + sta SUPERTILE_NAMETABLE_OFFSET ; move to other nametable (#$00 = nametable 0, #$40 = nametable 1) + jsr load_next_supertiles_screen_indexes ; load the super tile indexes for the upcoming screen into memory at LEVEL_SCREEN_SUPERTILES + lda #$1d ; a = #$1d + sta PPU_WRITE_TILE_OFFSET ; $60 decremented to #$00, reset back to #$1d + lda #$a0 ; a = #$a0 + sta PPU_WRITE_ADDRESS_LOW_BYTE + lda #$23 ; a = #$23 + sta PPU_WRITE_ADDRESS_HIGH_BYTE + +@dec_scroll_continue: + dec VERTICAL_SCROLL ; vertical scroll offset + lda VERTICAL_SCROLL ; vertical scroll offset + cmp #$ff + bne @continue_loop + lda #$ef ; a = #$ef + sta VERTICAL_SCROLL ; vertical scroll offset + +@continue_loop: + dec $17 ; decrement FRAME_SCROLL + bne @frame_scroll_loop + +@exit: + rts + +@write_attribute_continue: + lda LEVEL_SCREEN_SCROLL_OFFSET ; scrolling offset in pixels within screen + and #$0f ; keep bits .... xxxx + cmp #$07 + bne @dec_scroll_continue + jsr write_row_attribute_to_cpu_memory ; write a row of attribute palette data (#$08 bytes) to the CPU graphics buffer + jmp @dec_scroll_continue + +; handle_scroll - indoor level +; handle scrolling, including animating the electric fence +handle_indoor_scroll: + lda INDOOR_SCREEN_CLEARED ; indoor screen cleared flag (0 = not cleared; 1 = cleared, #$80 = cleared and fence removed) + bpl @animate_indoor_fence ; branch if flag is #$80, indicating that the electric fence needs to be animated/removed + lda LEVEL_TRANSITION_TIMER ; load remaining animation timer for player advancing to next screen + bmi @indoor_screen_transition ; jump if LEVEL_TRANSITION_TIMER is negative (couldn't get this to happen) + bne @write_column_tiles_exit ; branch if the player is advancing and the background needs to update + lda INDOOR_SCROLL ; player isn't advancing, see if scrolling (0 = not scrolling; 1 = scrolling, 2 = finished scrolling) + beq @exit ; exit if not scrolling + lda #$00 ; player has pressed up and screen is now 'scrolling' as player advances + ; begin advancing background animation + sta PPU_WRITE_TILE_OFFSET ; initialize PPU_WRITE_TILE_OFFSET to #$00 + sta PPU_WRITE_ADDRESS_LOW_BYTE ; initialize PPU_WRITE_ADDRESS_LOW_BYTE to #$00 + sta $66 ; !(UNUSED) not sure of use, only ever set to #$00 or #$c0, never read + lda #$20 + sta LEVEL_TRANSITION_TIMER ; set initial advancing animation timer to #$20 + lda PPU_WRITE_ADDRESS_HIGH_BYTE + eor #$04 ; flip bits .... .x.. + sta PPU_WRITE_ADDRESS_HIGH_BYTE + lda ATTRIBUTE_TBL_WRITE_HIGH_BYTE ; load current attribute table write address + eor #$04 ; flip bits .... .x.. + sta ATTRIBUTE_TBL_WRITE_HIGH_BYTE ; switch attribute table write address between $27c0 and $23c0, or $2bc0 and $2fc0 + lda LEVEL_SCREEN_NUMBER ; load current screen number within the level + cmp LEVEL_ALT_GRAPHICS_POS ; compare to screen where alternate graphics should start loading + bne @load_supertiles_screen_indexes_indoor + lda LEVEL_SCREEN_SCROLL_OFFSET ; on screen where alternate graphics should load, load scrolling offset in pixels within screen + cmp #$03 + bne @load_supertiles_screen_indexes_indoor + ldy #$00 ; y = #$00 + +; load the graphics data for the indoor boss screen +@loop: + lda level_2_4_boss_graphics_data,y + sta LEVEL_SCREEN_SUPERTILES_PTR,y ; depending on Y offset actually offsets one of the 3 + ; LEVEL_SCREEN_SUPERTILES_PTR, LEVEL_SUPERTILE_DATA_PTR, LEVEL_SUPERTILE_PALETTE_DATA + iny + cpy #$06 + bne @loop + lda CURRENT_LEVEL ; current level + lsr + bcs @load_screen_a_supertile_indexes ; branch if odd level, i.e. indoor/base, energy zone, or alien's lair + ; I believe this is only called in indoor/base boss context, so this always branches + +@load_supertiles_screen_indexes_indoor: + lda LEVEL_SCREEN_NUMBER ; load current screen number within the level + asl + asl + sec ; set carry flag + adc LEVEL_SCREEN_SCROLL_OFFSET ; scrolling offset in pixels within screen + +; input +; * a - index into the screen_supertile_ptr_table table +@load_screen_a_supertile_indexes: + jsr load_supertiles_screen_indexes ; decompress and load super-tile indexes into LEVEL_SCREEN_SUPERTILES + +@write_column_tiles_exit: + jsr load_column_of_tiles_to_cpu_buffer + jsr write_col_attribute_to_cpu_memory ; write a column of attribute palette data (#$07 bytes) to the CPU graphics buffer + inc PPU_WRITE_TILE_OFFSET + inc PPU_WRITE_ADDRESS_LOW_BYTE + dec LEVEL_TRANSITION_TIMER ; subtract 1 + bne @exit ; exit if not #$00 + lda #$80 + sta LEVEL_TRANSITION_TIMER ; reset LEVEL_TRANSITION_TIMER to #$80 + +@exit: + rts + +; update pattern table tiles to animate electric fence +@animate_indoor_fence: + jmp animate_indoor_fence + +; player has cleared the screen and finished advancing, swap active nametable +@indoor_screen_transition: + lda #$00 ; a = #$00 + sta LEVEL_TRANSITION_TIMER ; init to #$00 + inc INDOOR_SCROLL ; set INDOOR_SCROLL to #$02 + inc LEVEL_SCREEN_SCROLL_OFFSET ; increment which of the #$04 screens are showing while advancing + lda LEVEL_SCREEN_SCROLL_OFFSET ; load which animation screen is being shown [#$00-#$03] + cmp #$04 ; see if have shown all #$04 of the backgrounds while advancing to next indoor screen + bne @swap_base_nametable ; swap to next screen for advancing animation + inc INDOOR_PLAYER_JUMP_FLAG ; finished advancing into next indoor screen, set player to jump + inc INDOOR_PLAYER_JUMP_FLAG+1 ; set player 2 to jump (if no player 2, this isn't used) + lda #$00 ; a = #$00 + sta INDOOR_SCREEN_CLEARED ; indoor screen cleared flag (0 = not cleared; 1 = cleared) + sta LEVEL_SCREEN_SCROLL_OFFSET ; scrolling offset in pixels within screen + sta ENEMY_SCREEN_READ_OFFSET + inc LEVEL_SCREEN_NUMBER + lda LEVEL_SCREEN_NUMBER ; load current screen number within the level + cmp LEVEL_STOP_SCROLL ; compare to the screen to stop scrolling on + bne @continue + lda #$80 ; a = #$80 + sta LEVEL_LOCATION_TYPE ; overwrite level location type with #$80 (no longer specifies indoor vs outdoor) + jsr load_alternate_graphics + jsr init_APU_channels + lda CURRENT_LEVEL ; current level + lsr + ora #$08 ; set bits .... x... + jsr load_A_offset_graphic_data ; load graphic data code 08 or 09 + lda #$42 ; a = #$42 (sound_42) + jsr play_sound ; play indoor/base boss screen music + lda #$b1 ; a = #$b1 + sta PPUCTRL_SETTINGS + lda #$e0 ; a = #$e0 + sta VERTICAL_SCROLL ; set vertical scroll offset to match outdoor levels (#$e0) + +@continue: + lda #$0c ; a = #$0c + sta BG_PALETTE_ADJ_TIMER ; set fade-in effect timer for boss screen + lda #$20 ; a = #$20 + jsr load_palettes_color_to_cpu ; load #$20 palette colors into PALETTE_CPU_BUFFER based on LEVEL_PALETTE_INDEX + +@swap_base_nametable: + lda PPUCTRL_SETTINGS + eor #$01 ; flip bits .... ...x + sta PPUCTRL_SETTINGS + rts + +; pointer table for indoor level boss header changes ($03 * $02 = $06 bytes) +; bank 2 and 3 labels +level_2_4_boss_graphics_data: + .addr level_2_4_boss_supertiles_screen_ptr_table ; bank 2 - super-tiles per screen (LEVEL_SCREEN_SUPERTILES_PTR) - CPU address $9013 + .addr level_2_4_boss_supertile_data ; bank 3 - super-tile pattern table tiles (LEVEL_SUPERTILE_DATA_PTR) - CPU address $b57a + .addr level_2_4_boss_palette_data ; bank 3 - super-tile palette data (LEVEL_SUPERTILE_PALETTE_DATA) - CPU address $bd7a + +; populates the CPU_GRAPHICS_BUFFER with #$1c pattern table tiles (one column) from the super-tiles +; since vram_address_increment is 1, this represents a single column of the nametable +; it takes multiple frames to write the entire nametable to the GPU +load_column_of_tiles_to_cpu_buffer: + ldx GRAPHICS_BUFFER_OFFSET ; load the offset into CPU_GRAPHICS_BUFFER + lda #$02 ; + sta CPU_GRAPHICS_BUFFER,x ; set vram_address_increment offset to #$02 (value #$04) (increment by #$20 (write nametable tiles in a column fashion top to bottom)) + lda #$1c ; #$1c (28 decimal) pattern table tiles (a full column of tiles for the screen) + inx + sta CPU_GRAPHICS_BUFFER,x ; set the size of graphics that will be written to PPU to #$1c (28 in decimal, one column of tiles) + lda #$01 ; a = #$01 + inx + sta CPU_GRAPHICS_BUFFER,x ; set the number of #1c-sized blocks that will be written to #$01 (only writing one column) + lda PPU_WRITE_ADDRESS_HIGH_BYTE ; load high byte of PPU write address + inx + sta CPU_GRAPHICS_BUFFER,x ; set high byte of PPU write address + sta $12 ; store high byte into $12 + lda PPU_WRITE_ADDRESS_LOW_BYTE ; load low byte of PPU write address + inx + sta CPU_GRAPHICS_BUFFER,x ; set low byte of PPU write address + inx + ldy #$ff ; y = #$ff + lsr ; shift right the low byte of the PPU write address moving lsb to carry flag + bcs @odd_nametable_column ; branch if low byte of PPU write address is odd (collision is only set on every other column) + ; setting up $12 and $13 to for later when configuring collision, which only looks at every other pattern tile column + sta $13 ; low byte was even, store shifted (halved) byte address in $13 directly + lsr $12 ; shift high byte right + lsr $12 ; shift high byte right + lsr $12 ; shift high byte right + ror ; shift a register high byte right, pulling in carry if set + lsr ; shift a register high byte right + sta $12 ; store new high byte back into $12, this is now the BG_COLLISION_DATA write offset + lda $13 ; load PPU write address low byte + and #$03 ; keep bits .... ..xx (0 to 3) + sta $13 ; numbering every other column (0 to 3 in a loop) since PPU write address was shifted to the right + ldy #$00 ; y = #$00 + +@odd_nametable_column: + sty $11 ; #$ff for odd nametable columns, #$00 for even, used to determine if collision setting is necessary + lda PPU_WRITE_TILE_OFFSET + and #$03 ; keep bits .... ..xx + sta $02 + lda PPU_WRITE_TILE_OFFSET + lsr + lsr + ora SUPERTILE_NAMETABLE_OFFSET ; merge with nametable offset (#$00 = nametable 0, #$40 = nametable 1) where super-tile indexes are stored for current screen + sta $10 ; score LEVEL_SCREEN_SUPERTILES offset into $10 + tay ; y is the LEVEL_SCREEN_SUPERTILES offset + +; * loads level super-tiles from PRG ROM bank 2 by CPU address LEVEL_SCREEN_SUPERTILES +; - looped through until all tiles for a single column have been written +; - ultimately is called 4 times for each super-tile because each time renders only 1 column of pattern table tiles for the super-tile +; * updates the in-memory background collision (BG_COLLISION_DATA) information (set_tile_collision) +; Y is the LEVEL_SCREEN_SUPERTILES offset (level_X_supertiles_screen_XX) +; $03 is the column offset of the super-tile to write to CPU_GRAPHICS_BUFFER block (currently drawing column) +; #$37 super-tiles per screen for horizontal levels +; #$40 super-tiles per screen for vertical levels +; CPU address #$df48 +load_level_supertile_data: + lda #$00 ; initialize LEVEL_SUPERTILE_DATA_PTR read offset + sta $08 ; reset offset into level super-tile pointer table + lda LEVEL_SCREEN_SUPERTILES,y ; read byte specifying which super-tile to load (level_X_supertiles_screen_XX) + asl ; each super-tile is #$10 in size so need to double offset 4 times + asl + rol $08 ; keep track of how many times A overflows + asl + rol $08 + asl + rol $08 + adc LEVEL_SUPERTILE_DATA_PTR ; add read address into level_X_SUPERTILE_data to get to correct super-tile + sta $00 ; store the low byte to level super-tile data + lda $08 ; load read offset + adc LEVEL_SUPERTILE_DATA_PTR+1 ; add number of overflows to the high byte of the pointer table address + sta $01 ; store high byte to level super-tile data + ldy $02 ; level the super-tile data byte offset of the tiles that will be drawn (which column of super-tile will be drawn) + lda ($00),y ; load the level's super-tile y-th pattern table tile byte (level_X_SUPERTILE_data) + sta CPU_GRAPHICS_BUFFER,x ; write first pattern table tile from super-tile data to CPU memory + jsr set_tile_collision ; set the tile collision for the top portion of the entire super-tile in BG_COLLISION_DATA + inx ; increment CPU write offset + iny + iny + iny + iny ; increment CPU read offset by 4 total + lda ($00),y ; read next pattern table tile of the super-tile one row down (level_X_SUPERTILE_data) + sta CPU_GRAPHICS_BUFFER,x ; write first byte of second quadrant of super-tile to CPU memory + inx ; increment CPU write offset + iny + iny + iny + iny + lda ($00),y ; read next pattern table tile of the super-tile one row down (level_X_SUPERTILE_data) + sta CPU_GRAPHICS_BUFFER,x ; write to CPU memory + jsr set_tile_collision ; set tile collision data for middle row of super-tile + inx ; increment CPU write offset + iny + iny + iny + iny + lda ($00),y ; read one tile of last row of the super-tile (level_X_SUPERTILE_data) + sta CPU_GRAPHICS_BUFFER,x ; write first byte of last quadrant of super-tile to CPU memory + inx ; increment CPU write offset + lda $10 ; load the number of horizontal pixels for the row that have been loaded + clc ; clear carry in preparation for addition + adc #$08 ; add #$08 (each pattern table entry is #$08 pixels wide and tall) + sta $10 ; add updated + tay ; A is nth super-tile to load for the column for the screen + and #$3f ; keep bits ..xx xxxx + cmp #$38 + bcc load_level_supertile_data ; load next tile to CPU_GRAPHICS_BUFFER if < #$38 (horizontal levels have #$37 super-tiles) + stx GRAPHICS_BUFFER_OFFSET ; keep track of where in CPU_GRAPHICS_BUFFER buffer to write pattern table tiles + rts ; go back to load_column_of_tiles_to_cpu_buffer + +; vertical level - writing pattern tiles for super-tiles +set_vert_lvl_super_tiles: + ldx GRAPHICS_BUFFER_OFFSET ; index for PPU background tile + lda #$01 ; a = #$01 + sta CPU_GRAPHICS_BUFFER,x ; set vram_address_increment (write across) + sta CPU_GRAPHICS_BUFFER+2,x ; set length of data to #$01 byte + lda #$20 ; a = #$20 + inx ; increment graphics buffer write offset + sta CPU_GRAPHICS_BUFFER,x ; store #$20 #$01-byte groups + inx ; increment graphics buffer write offset + lda PPU_WRITE_ADDRESS_HIGH_BYTE ; load write address high byte + inx ; increment graphics buffer write offset + sta CPU_GRAPHICS_BUFFER,x ; write the PPU write address high byte + lda PPU_WRITE_ADDRESS_LOW_BYTE ; load write address low byte + inx ; increment graphics buffer write offset + sta CPU_GRAPHICS_BUFFER,x ; write the next byte of the PPU write low address + inx ; increment graphics buffer write offset + lda #$00 ; a = #$00 + sta $11 + sta $13 + lda PPU_WRITE_TILE_OFFSET ; load current super-tile data write offset + ; starts with #$1d goes down to #$00 before looping + and #$03 ; keep bits .... ..xx + asl + asl + sta $02 + lda PPU_WRITE_TILE_OFFSET + and #$1c ; keep bits ...x xx.. + asl + ora SUPERTILE_NAMETABLE_OFFSET ; merge with nametable offset (#$00 = nametable 0, #$40 = nametable 1) where super-tile indexes are stored for current screen + sta $10 + lda PPU_WRITE_TILE_OFFSET + lsr + ror $11 + asl + asl + sta $12 ; store PPU write address high byte into $12 + ldy $10 + +@set_supertile_tiles: + lda #$00 ; a = #$00 + sta $08 + lda LEVEL_SCREEN_SUPERTILES,y ; read byte specifying which super-tile to load (level_X_supertiles_screen_XX) + asl + asl + rol $08 + asl + rol $08 + asl + rol $08 + adc LEVEL_SUPERTILE_DATA_PTR ; (bank 3 pointer) + sta $00 + lda $08 + adc LEVEL_SUPERTILE_DATA_PTR+1 ; add the high byte of the pointer address + sta $01 + ldy $02 + lda ($00),y + sta CPU_GRAPHICS_BUFFER,x + jsr set_tile_collision ; set BG_COLLISION_DATA to tile collision code (0-3) for the pattern table tile + inx ; increment graphics buffer write offset + iny ; increment graphics data read offset + lda ($00),y + sta CPU_GRAPHICS_BUFFER,x + inx ; increment graphics buffer write offset + iny ; increment graphics data read offset + lda ($00),y + sta CPU_GRAPHICS_BUFFER,x + jsr set_tile_collision ; set BG_COLLISION_DATA to tile collision code (0-3) for the pattern table tile + inx ; increment graphics buffer write offset + iny ; increment graphics data read offset + lda ($00),y + sta CPU_GRAPHICS_BUFFER,x + inx ; increment graphics buffer write offset + inc $10 + lda $10 + tay + and #$07 ; keep bits .... .xxx + bne @set_supertile_tiles + stx GRAPHICS_BUFFER_OFFSET + rts + +; write a column of attribute palette data (#$07 bytes) to the CPU graphics buffer +write_col_attribute_to_cpu_memory: + ldx GRAPHICS_BUFFER_OFFSET ; load the current tile to draw to the PPU + lda #$01 ; a = #$01 + sta CPU_GRAPHICS_BUFFER,x ; set vram_address_increment (write across) + inx ; increment CPU_GRAPHICS_BUFFER write offset + sta CPU_GRAPHICS_BUFFER,x ; set length of data to #$01 byte + inx ; increment CPU_GRAPHICS_BUFFER write offset + lda #$07 ; a = #$07 + sta CPU_GRAPHICS_BUFFER,x ; specifying to store #$07 #$01-byte groups (the column of super-tile palette data) + inx ; increment CPU_GRAPHICS_BUFFER write offset + lda PPU_WRITE_TILE_OFFSET ; load the current PPU tile offset being written to CPU memory + lsr + lsr ; only care about which column of super-tile is active + ora #$c0 ; set bits xx.. .... + ; e.g. #$08 -> #$c2, which means the 3rd attribute table column + sta $00 ; store current attribute table low byte to $00 in range from #$c0 up to #$ff inclusively + and #$0f ; keep low nibble + ora SUPERTILE_NAMETABLE_OFFSET ; merge with nametable offset (#$00 = nametable 0, #$40 = nametable 1) where super-tile indexes are stored for current screen + ; e.g. #$c2 -> #$42 + sta $10 ; set super-tile address into cpu graphics buffer that contains the attribute data to write (offset from $0600 (LEVEL_SCREEN_SUPERTILES)) + tay ; transfer super-tile index to render to y (level_X_supertiles_screen_XX) + +; write palette data to CPU_GRAPHICS_BUFFER for use to write in attribute table +; writes an entire columned of super-tiles' palette attribute data (#$08 bytes) +@set_supertile_attribute_byte: + lda ATTRIBUTE_TBL_WRITE_HIGH_BYTE ; load current attribute table write address high byte + sta CPU_GRAPHICS_BUFFER,x ; set PPU high write address to correct attribute table + inx ; increment CPU_GRAPHICS_BUFFER write offset + lda $00 ; load the current attribute table low write byte (#$c0 up to #$ff inclusively) + sta CPU_GRAPHICS_BUFFER,x ; store low byte of PPU address + inx ; increment CPU_GRAPHICS_BUFFER write offset + lda LEVEL_SCREEN_SUPERTILES,y ; read byte specifying which super-tile to load (level_X_supertiles_screen_XX) + tay ; transfer super-tile index into y register + lda (LEVEL_SUPERTILE_PALETTE_DATA),y ; load the palette for the super-tile (one entry in attribute table) + sta CPU_GRAPHICS_BUFFER,x ; write the palette byte to graphics buffer, this will be written to the attribute table + inx ; increment CPU_GRAPHICS_BUFFER write offset + lda $00 ; load the current attribute table low write byte (#$c0 up to #$ff inclusively) + clc ; clear carry in preparation for addition + adc #$08 ; add #$08 to the current attribute table write low byte + ; this moves to the next super-tile in the column (move down one row) + sta $00 ; set new attribute table write address low byte + cmp #$f8 ; see if on last attribute table entry + bcs @exit ; branch if attribute entry >= #$f8 + ; contra doesn't write palette data for the last half super-tile attribute row (for non-vertical levels) + ; this area isn't usually rendered on CRTs and when emulated + lda $10 ; load current super-tile to render + adc #$08 ; add #$08 to render the next supertile down + sta $10 ; store next super-tile to render + tay + bcc @set_supertile_attribute_byte ; loop if more palette data in column to write to attribute table + +@exit: + stx GRAPHICS_BUFFER_OFFSET ; restore x to the graphics buffer write offset + rts + +; write a row of attribute palette data (#$08 bytes) to the CPU graphics buffer +; for vertical level (waterfall) only +write_row_attribute_to_cpu_memory: + ldx GRAPHICS_BUFFER_OFFSET ; load the current tile to draw to the PPU + lda #$01 ; a = #$01 + sta CPU_GRAPHICS_BUFFER,x ; set vram_address_increment to #$01 + sta CPU_GRAPHICS_BUFFER+2,x ; set number of length of graphics blocks to #$01 + lda #$08 ; a = #$08 + inx ; increment CPU_GRAPHICS_BUFFER write offset + sta CPU_GRAPHICS_BUFFER,x ; set number of #01-byte-long groups to #$08 + inx ; increment CPU_GRAPHICS_BUFFER write offset + lda #$23 ; a = #$23 + inx ; increment CPU_GRAPHICS_BUFFER write offset + sta CPU_GRAPHICS_BUFFER,x ; set high byte of PPU write address to #$23 + lda PPU_WRITE_TILE_OFFSET + asl + and #$38 ; keep bits ..xx x... + ora SUPERTILE_NAMETABLE_OFFSET ; merge with nametable offset (#$00 = nametable 0, #$40 = nametable 1) where super-tile indexes are stored for current screen + sta $10 ; set super-tile address into cpu graphics buffer that contains the attribute data to write (offset from $0600 (LEVEL_SCREEN_SUPERTILES)) + and #$bf ; strip bit 6 + clc ; clear carry in preparation for addition + adc #$c0 ; attribute table low byte starts at #$c0, add #$c0 to get actual initial attribute address for row + inx ; increment CPU_GRAPHICS_BUFFER write offset + sta CPU_GRAPHICS_BUFFER,x ; set low byte of PPU write address + inx ; increment CPU_GRAPHICS_BUFFER write offset + +@set_supertile_attribute_byte: + lda PPU_WRITE_TILE_OFFSET ; load the current row being written + and #$03 ; keep bits .... ..xx + cmp #$03 ; every #$04 rows take the palette from the bottom super-tile and + ; merge with top palette from other nametable super-tile + beq @merge_supertiles_across_nametable + lda $10 ; load super-tile address into cpu graphics buffer that contains the attribute data to write (offset from $0600 (LEVEL_SCREEN_SUPERTILES)) + tay ; transfer to offset register + lda LEVEL_SCREEN_SUPERTILES,y ; read byte specifying which super-tile to load (level_X_supertiles_screen_XX) + tay ; transfer super-tile index to offset register + lda (LEVEL_SUPERTILE_PALETTE_DATA),y ; load the palette for the super-tile + +@write_supertile_palette_to_cpu: + sta CPU_GRAPHICS_BUFFER,x ; set palette in attribute table for nametable + inx ; increment CPU_GRAPHICS_BUFFER write offset + inc $10 ; move to next super-tile index + lda $10 + and #$07 ; strip to low nibble to see how many super-tile palette attribute bytes have been written + bne @set_supertile_attribute_byte ; loop until all palette attribute table entries are written for the row + stx GRAPHICS_BUFFER_OFFSET ; finished writing attributes, restore x to the graphics buffer write offset + rts + +; used to get correct palette for supertile when it spreads across a nametable +@merge_supertiles_across_nametable: + lda $10 ; load super-tile address into cpu graphics buffer that contains the attribute data to write (offset from $0600 (LEVEL_SCREEN_SUPERTILES)) + tay ; transfer to offset register + lda LEVEL_SCREEN_SUPERTILES,y ; read byte specifying which super-tile to load (level_X_supertiles_screen_XX) + tay ; transfer super-tile index to offset register + lda (LEVEL_SUPERTILE_PALETTE_DATA),y ; load the palette data for the super-tile + and #$f0 ; keep lower half palette data for the super-tile + sta $11 ; store lower half palette data of the super-tile + lda $10 ; load super-tile address into cpu graphics buffer that contains the attribute data to write (offset from $0600 (LEVEL_SCREEN_SUPERTILES)) + eor #$40 ; move to other nametable + tay ; transfer super-tile index to offset register + lda LEVEL_SCREEN_SUPERTILES,y ; read byte specifying which super-tile to load (level_X_supertiles_screen_XX) + tay ; transfer super-tile index to offset register + lda (LEVEL_SUPERTILE_PALETTE_DATA),y ; load the palette data for the super-tile + and #$0f ; keep upper half palette data for the super-tile + ora $11 ; merge with lower half of super-tile palette data to get full super-tile palette data + jmp @write_supertile_palette_to_cpu + +; determine tile collision code (0-3) for the pattern table tile updates BG_COLLISION_DATA +; input +; * a - namespace tile code from the super-tile +; * y - level namespace tile offset (level_X_SUPERTILE_data offset) +; tile code 0 is always set to collision code 0 +set_tile_collision: + sty $14 ; store super-tile tile data read offset in $14 (LEVEL_SUPERTILE_DATA_PTR offset) + ldy $11 ; if $11 is set, then odd number nametable column, and collision isn't considered + bne tile_collision_exit ; exit if $11 is set + tay ; move the pattern table tile code to Y + beq set_collision_code_0 ; tile index is #$00, set to collision code 0 (empty) + cmp COLLISION_CODE_1_TILE_INDEX ; compare against collision code 1 tile index + bcs collision_code_0_check ; pattern table tile is not collision code 1, check to see if collision code 0 + lda #$01 ; set collision code to 1 (floor) + bne collision_continue ; continue + +; check if empty collision code +collision_code_0_check: + cmp COLLISION_CODE_0_TILE_INDEX + bcs collision_code_2_check ; tile index is greater than collision code 2 limit, check if collision code 03 + +set_collision_code_0: + lda #$00 ; set collision code to 0 (empty) + beq collision_continue + +; check if water collision code +collision_code_2_check: + cmp COLLISION_CODE_2_TILE_INDEX + bcs set_collision_code_03 ; tile index offset is greater than collision code 2 limit + lda #$02 ; set collision code to 2 (water) + bne collision_continue + +; check if solid collision code +set_collision_code_03: + lda #$03 ; set collision code to 03 (solid) + +; register a contains the collision code +; handles storing the collision 2-bits for the 1/4 of the super-tile in the correct memory address +collision_continue: + ldy $13 ; load low byte of PPU write address (masked to 0 to 3) + bne set_collision_tile_col_2 ; jump if odd column of screen write address + asl + asl + asl + asl + asl + asl ; shift the tile code (2 bits) all the way to the left 2 bits + sta $15 ; store modified collision code for super-tile into $15 + ldy $12 ; load BG_COLLISION_DATA write offset + lda BG_COLLISION_DATA,y ; load existing collision byte (each byte contains collision data for 2 super-tiles) + and #$3f ; keep bits ..xx xxxx, which will be merged with the modified collision code in $15 + jmp set_collision_tile ; save the updated collision information in CPU memory + +set_collision_tile_col_2: + dey ; see if second column by subtracting stored write offset + bne set_collision_tile_col_3 ; if not the second column, branch to see if 3rd or 4th + asl + asl + asl + asl ; shift the tile code (2 bits) all the way to the bits 5 and 4 (..xx ....) + sta $15 ; store shifted collision code for super-tile into $15 + ldy $12 ; load BG_COLLISION_DATA write offset + lda BG_COLLISION_DATA,y ; load existing collision byte (each byte contains collision data for 2 super-tiles) + and #$cf ; keep bits xx.. xxxx, which will be merged with the modified collision code in $15 + jmp set_collision_tile ; save the updated collision information in CPU memory + +set_collision_tile_col_3: + dey ; see if second column by subtracting stored write offset + bne set_collision_tile_col_4 ; if not the second column, branch to see if 4th + asl + asl ; shift the tile code (2 bits) to the bits 2 and 2 (.... xx..) + sta $15 ; store collision code for super-tile into $15 + ldy $12 ; load BG_COLLISION_DATA write offset + lda BG_COLLISION_DATA,y ; load existing collision byte (each byte contains collision data for 2 super-tiles) + and #$f3 ; keep bits xxxx ..xx, which will be merged with the modified collision code in $15 + jmp set_collision_tile ; save the updated collision information in CPU memory + +set_collision_tile_col_4: + sta $15 ; store collision code for super-tile into $15 + ldy $12 ; load BG_COLLISION_DATA write offset + lda BG_COLLISION_DATA,y ; load collision code for super-tile + and #$fc ; keep bits xxxx xx.., which will be merged with the modified collision code in $15 + +; sets the already-shifted collision code in a with the masked collision code in $15 and updates the CPU memory accordingly +set_collision_tile: + ora $15 ; combine the BG_COLLISION_DATA,y with the shifted collision code for the super-tile + sta BG_COLLISION_DATA,y ; save back into CPU memory + lda LEVEL_SCROLLING_TYPE ; 0 = horizontal, indoor/base; 1 = vertical + bne vert_lvl_tile_collision_exit ; jump if vertical level + tya + clc ; clear carry in preparation for addition + adc #$04 ; move forward 4 bytes for next super-tile + sta $12 ; update BG_COLLISION_DATA write offset to next tile down + +tile_collision_exit: + ldy $14 + rts ; go back to load_level_supertile_data + +vert_lvl_tile_collision_exit: + inc $13 ; increment low byte of PPU write address + lda $13 + cmp #$04 ; see if finished writing all four pattern table tiles in super-tile + bcc tile_collision_exit + lda #$00 ; a = #$00 + sta $13 + inc $12 ; update BG_COLLISION_DATA write offset + bne tile_collision_exit ; should always jump since inc $12 is non-zero + +; gets the collision code for (a,y) and if collision code is the floor, +; look one row (half supertile) see if collision code one row below (half supertile) is solid, +; if so, use that collision code. +; input +; * a - x pos +; * y - y pos +; output +; * a collision code #$00 (empty), #$01 (floor), #$02 (water), or #$80 (solid) +; * carry set when collision is with floor (#$01) +; * negative flag set when solid collision (#$80) +get_bg_collision_far: + jsr get_bg_collision ; determine player background collision code at position (a,y) + +; determines the next half supertile row's collision code below the $13 offset +; if current collision code is a floor collision, otherwise, do nothing +; input +; * a - collision code +; * $13 - BG_COLLISION_DATA offset +; output +; * a - collision code of half row down +; * carry flag - set when collision code #$80 (solid) +floor_get_next_row_bg_collision: + pha ; push collision code on to stack + bcc @exit ; exit if current collision code is not a floor collision + lda $13 ; load current BG_COLLISION_DATA offset + sta $16 ; store in $16 + and #$c0 ; keep bits 6 and 7 + sta $17 ; save result in $17 + lda $16 ; re-load BG_COLLISION_DATA offset + clc ; clear carry in preparation for addition + adc #$04 ; move down to next supertile half-row + and #$3f ; keep bits ..xx xxxx + ora $17 ; merge original bits 6 and 7 back + tay ; transfer BG_COLLISION_DATA offset to y + jsr read_bg_collision_byte_unsafe ; get collision code from BG_COLLISION_DATA byte + asl + lda $16 ; load previous BG_COLLISION_DATA offset + sta $13 ; store value in $13 + bcc @exit ; exit if not a solid collision + pla ; solid collision, set collision code to #$80 + ; pop old collision code from stack + lda #$80 ; a = #$80 + rts + +@exit: + pla ; pop collision code from stack + rts + +; get collision code at BG_COLLISION_DATA,y and if not floor, look down one collision row and get that collision code +; input +; * y - BG_COLLISION_DATA offset +; output +; * a - collision code of half row down +; * carry flag - set when collision code #$80 (solid) +find_floor_collision: + jsr read_bg_collision_byte_unsafe ; get collision code from BG_COLLISION_DATA byte + jmp floor_get_next_row_bg_collision ; if floor collision, get next half supertile row's collision code + ; otherwise exit + +; reads the specific bits of the BG_COLLISION_DATA byte and determines the collision code +; unsafe because it hard-codes a bypass of the bg collision row check, y must be correct here +; example usage is when checking below ground player is standing on to see if player can fall through (drop down) +; input +; * y - BG_COLLISION_DATA offset +; * $12 - specifies which 2 bits of the BG_COLLISION_DATA byte interested in (0, 1, 2, or 3) +; each super-tile has 4 bg collision points, 2 per bg collision row +; one byte contains 4 bg collision points on a single row of 2 super-tiles +; output +; * $13 - BG_COLLISION_DATA offset +; * $14 - collision code +; * a - collision code +; * zero flag - set when collision code #$00 (empty) +; * negative flag - set when collision code #$03 (solid) +; * carry flag - set when collision code #$01 (floor) +read_bg_collision_byte_unsafe: + lda #$00 ; a = #$00 + sta $15 ; used by read_bg_collision_byte as a quick way to ensure not reading past last bg collision row + ; this method is confident y offset (BG_COLLISION_DATA offset) is correct + beq read_bg_collision_byte ; always branch, get collision code from BG_COLLISION_DATA byte + +; input +; * a is x pos +; * y is y pos +; output +; * a collision code #$00 (empty), #$01 (floor), #$02 (water), or #$80 (solid) +; * carry set when collision is with floor (#$01) +; * negative flag set when solid collision (#$80) +get_bg_collision: + sta $13 ; store x position in $13 + +; input +; * $13 - x position +; * y - y position +; output +; * a collision code #$00 (empty), #$01 (floor), #$02 (water), or #$80 (solid) +; * carry set when collision is with floor (#$01) +; * negative flag set when solid collision (#$80) +get_enemy_bg_collision: + lda #$00 ; a = #$00 + sta $10 + beq bg_collision_logic ; always jump because a is #$00 + +; used for the hangar mine cart +get_cart_bg_collision: + sta $13 ; store sprite x position in $13 + +; input +; * $13 - x position +; * y - y pos +; output +; * a collision code #$00 (empty), #$01 (floor), #$02 (water), or #$80 (solid) +; * carry set when collision is with floor (#$01) +; * negative flag set when solid collision (#$80) +bg_collision_logic: + tya ; transfer y pos to a + sta $15 ; store y pos in $15 + clc ; clear carry in preparation for addition + adc VERTICAL_SCROLL ; add vertical scroll offset + bcs @vert_overflow ; branch if overflow + cmp #$f0 ; didn't overflow, compare VERTICAL_SCROLL + Y to #$f0 + bcc @continue ; branch if VERTICAL_SCROLL + Y < #$f0 + +@vert_overflow: + adc #$0f ; add #$0f to vertical scroll + +@continue: + sta $11 ; store VERTICAL_SCROLL + Y in $11 + lda $13 ; load sprite x position + clc ; clear carry in preparation for addition + adc HORIZONTAL_SCROLL ; horizontal scroll offset + sta $12 ; store HORIZONTAL_SCROLL + X in $12 + lda PPUCTRL_SETTINGS ; load PPUCTRL, used to get nametable value + eor $10 ; A XOR $10 (almost always #$00) + ; hangar moving carts (enemy type #$14) will use $10 + ; when moving right and checking for bg collision in opposite nametable + and #$01 ; see if base nametable address is $2400 + bcc @bg_collision_data ; jump if no carry when adding HORIZONTAL_SCROLL to X + eor #$01 ; if carry occurred, flip bit 0 of a + +; does math to determine correct offset into BG_COLLISION_DATA +@bg_collision_data: + tay ; set level_screen_mem_offset_tbl_01 index + lda $11 ; load VERTICAL_SCROLL + Y + lsr + lsr + and #$3c ; keep bits ..xx xx.. + sta $11 ; update VERTICAL_SCROLL + Y to include nametable offset + lda $12 ; load HORIZONTAL_SCROLL + X + lsr + lsr + lsr + lsr + sta $12 ; update HORIZONTAL_SCROLL + X to include nametable offset + lsr + lsr + ora $11 ; merge with adjusted VERTICAL_SCROLL + Y + ora level_screen_mem_offset_tbl_01,y + tay + lda $12 ; load HORIZONTAL_SCROLL + X nametable offset + and #$03 ; keep bits .... ..xx + sta $12 ; set HORIZONTAL_SCROLL + X nametable offset + +; reads the specific bits of the BG_COLLISION_DATA byte and determines the collision code +; input +; * y - BG_COLLISION_DATA offset +; * $15 - bg collision row +; * $12 - specifies which 2 bits of the BG_COLLISION_DATA byte interested in (0, 1, 2, or 3) +; each super-tile has 4 bg collision points, 2 per bg collision row +; one byte contains 4 bg collision points on a single row of 2 super-tiles +; output +; * $13 - BG_COLLISION_DATA offset +; * $14 - collision code +; * a - collision code +; * negative flag - set when solid collision code +; * carry flag - set when collision code #$01 (floor) +read_bg_collision_byte: + sty $13 ; store BG_COLLISION_DATA offset in $13 + lda $15 ; load bg collision row + cmp #$e0 ; see if past last row + lda #$00 ; a = #$00 + bcs @set_code_exit ; set collision code to #$00 (empty) and exit if past last bg collision row + lda BG_COLLISION_DATA,y ; load background collision code + ldy $12 ; load column offset (0, 1, 2, or 3) + beq @shift_6_bits ; collision code stored in bits 6 and 7, shift right to 2 least significant bits + dey + beq @shift_4_bits ; collision code stored in bits 4 and 5, shift right to 2 least significant bits + dey + beq @shift_2_bits ; collision code stored in bits 2 and 3, shift right to 2 least significant bits + bne @no_shift ; collision code already in least 2 significant bits, no shift required + +@shift_6_bits: + lsr + lsr + +@shift_4_bits: + lsr + lsr + +@shift_2_bits: + lsr + lsr + +@no_shift: + and #$03 ; store the collision code in a (it's been shifted to right most bits) + tay ; transfer code offset to y + lda collision_code_lookup_tbl,y ; load collision code + +@set_code_exit: + sta $14 ; store collision code in $14 + lsr ; set carry if collision code is floor (#$01) + lda $14 ; set a register to collision code + rts + +; the base offset into cpu graphics buffer where super-tile indexes are loaded (LEVEL_SCREEN_SUPERTILES) +; $0600 or $0640 +level_screen_mem_offset_tbl_01: + .byte $00,$40 + +collision_code_lookup_tbl: + .byte $00,$01,$02,$80 + +; starts the auto scroll to reveal heart if at right screen, otherwise do nothing +; output +; * a - LEVEL_SCREEN_NUMBER when boss auto scroll not set +; #$00 when boss auto scroll already set, or just set +; #$01 when boss already defeated +set_boss_auto_scroll: + lda BOSS_DEFEATED_FLAG ; 0 = boss not defeated, 1 = boss defeated + bne @exit ; exit if boss is already defeated + lda LEVEL_STOP_SCROLL ; load the screen to stop scrolling on, set to #$ff when boss auto scroll starts + bmi @exit_mark_scroll_enabled ; exit if auto scroll has already started + cmp LEVEL_SCREEN_NUMBER ; screen number + bne @exit ; exit if not on the appropriate screen to start auto scroll + ldy LEVEL_SCROLLING_TYPE ; 0 = horizontal, indoor/base; 1 = vertical + lda LEVEL_SCREEN_SCROLL_OFFSET ; load the number of pixels into LEVEL_SCREEN_NUMBER the level has scrolled + cmp scroll_trigger_tbl,y ; compare to scroll trigger point + bcc @exit ; exit if not yet at spot to trigger auto scroll + lda auto_scroll_timer_tbl,y ; load the appropriate auto scroll timer for the level + sta AUTO_SCROLL_TIMER_00 ; start auto scroll to reveal the boss + lda #$ff ; LEVEL_STOP_SCROLL is #$ff when auto scroll has started + sta LEVEL_STOP_SCROLL ; mark that boss auto scroll has started (LEVEL_STOP_SCROLL = #$ff) + +@exit_mark_scroll_enabled: + lda #$00 ; a = #$00 + +@exit: + rts + +; table for offset into screen before initiating auto scroll to show boss ($04 bytes) +; byte 0 - horizontal level +; byte 1 - vertical level +scroll_trigger_tbl: + .byte $a0,$c0 + +; the amount of time to auto scroll for the end of level +; byte 0 - horizontal level +; byte 1 - vertical level +auto_scroll_timer_tbl: + .byte $60,$40 + +; screen load +load_next_next_supertiles_screen_indexes: + lda LEVEL_SCREEN_NUMBER ; load current screen number within the level + clc ; clear carry bit + adc #$02 ; add #$02 to load screen in the future + bne load_supertiles_screen_indexes ; decompress and load super-tile indexes into LEVEL_SCREEN_SUPERTILES + +load_next_supertiles_screen_indexes: + lda LEVEL_SCREEN_NUMBER ; load current screen number within the level + clc ; clear carry in preparation for addition + adc #$01 ; add #$01 to load screen in the future + bne load_supertiles_screen_indexes ; decompress and load super-tile indexes into LEVEL_SCREEN_SUPERTILES + +; decompresses super-tiles of the current level's current screen to load into CPU memory at LEVEL_SCREEN_SUPERTILES +load_current_supertiles_screen_indexes: + lda LEVEL_SCREEN_NUMBER ; load current screen number within the level + ; to know which super-tiles to load + +; CPU address $e16b +; decompresses and loads super-tile indexes into LEVEL_SCREEN_SUPERTILES (level_x_supertiles_screen_ptr_table) +; read +; input +; * a - index into the screen_supertile_ptr_table table, i.e. the screen number to load +load_supertiles_screen_indexes: + asl ; double since each entry is a 2-byte address + tax ; save the index before jump to load_bank_number subroutine + ldy #$02 ; tell load_bank_number to load bank 2 + jsr load_bank_number ; load bank 2 + txa ; restore screen_supertile_ptr_table offset + tay ; move screen_supertile_ptr_table offset to Y + lda (LEVEL_SCREEN_SUPERTILES_PTR),y ; grab low-byte of pointer to level screen super-tiles (from bank 2) + ; level_x_supertiles_screen_ptr_table + sta $00 ; store in $00 + iny ; increment LEVEL_SCREEN_SUPERTILES_PTR read offset + lda (LEVEL_SCREEN_SUPERTILES_PTR),y ; grab high-byte of level graphic data location + sta $01 ; store in $01 + ldy #$00 ; clear y + ldx SUPERTILE_NAMETABLE_OFFSET ; CPU graphic data write offset (LEVEL_SCREEN_SUPERTILES offset) + +; decompresses encoded data specifying the super-tiles to display for nametable +read_supertiles_screen_ptr_table: + lda ($00),y ; grab next graphic byte (encoded) (level_x_supertiles_screen_xx) + iny ; increment level super tile graphic data read offset + cmp #$80 ; checking if most significant bit is a 1 + bcs load_rle_repeat_command ; if A has msb set, then RLE command + sta LEVEL_SCREEN_SUPERTILES,x ; bit 7 is not set, regular super-tile index. store index in CPU memory + inx ; increment CPU write address offset + +; input +; * ($00) should point to the correct level_x_supertiles_screen_xx +; * x - total number of tiles written to LEVEL_SCREEN_SUPERTILES memory location +; * y - offset into specific level_x_supertiles_screen_xx to read +load_supertile_indexes_starting_at_y: + lda LEVEL_SCROLLING_TYPE ; load the current level scrolling type (horizontal or vertical) + bne @vertical_level_section_end ; if level is a vertical level, then jump + cpx #$38 ; screen_supertile_ptr_table data is #$38 bytes for horizontal levels + beq @exit ; exit if read all #$38 super-tiles (horizontal level) + cpx #$78 ; 2 screens worth of super-tiles are loaded, so if started with second screen at offset #$40, stop at #$78 + bcc read_supertiles_screen_ptr_table ; jump if read less than #$78 tiles + jmp load_previous_bank ; read #$78 super-tiles, exit + +@vertical_level_section_end: + cpx #$40 ; exit if read all #$40 super-tiles (horizontal level) + beq @exit + cpx #$80 ; 2 screens worth of super-tiles are loaded, so if started with second screen at offset #$40, stop at #$80 + bcc read_supertiles_screen_ptr_table ; if not finished reading all super-tiles, move to next super-tile + +@exit: + jmp load_previous_bank + +; read +load_rle_repeat_command: + cmp #$f0 + bcs @set_nametable_supertile_indexes ; branch if >= #$f0 + and #$7f ; clear first bit + sta $02 ; store number of times to repeat next byte + lda ($00),y ; load the byte that will be repeated + iny ; increment read offset + +@repeat_level_data_byte: + sta LEVEL_SCREEN_SUPERTILES,x ; write level graphic byte to CPU memory + inx ; increment CPU memory write offset + dec $02 ; decrement counter for number of times to repeat byte + bne @repeat_level_data_byte ; repeat while $02 > 0 + beq load_supertile_indexes_starting_at_y + +@set_nametable_supertile_indexes: + and #$0f ; grab least significant 4 bits + asl + asl + asl + ora SUPERTILE_NAMETABLE_OFFSET ; merge with nametable offset (#$00 = nametable 0, #$40 = nametable 1) where super-tile indexes are stored for current screen + sty $03 + tay + lda #$08 ; load #$08 super-tiles indexes + sta $02 ; set super-tile index counter to $02 + +@supertile_index_loop: + lda LEVEL_SCREEN_SUPERTILES,y ; read byte specifying which super-tile to load (level_X_supertiles_screen_XX) + sta LEVEL_SCREEN_SUPERTILES,x + inx ; increment write offset + iny ; increment read offset + dec $02 ; decrement number of remaining super-tiles indexes to load + bne @supertile_index_loop + ldy $03 + bne load_supertile_indexes_starting_at_y + +; checks if a player is colliding with the current enemy +check_players_collision: + ldx #$01 ; x = #$01 + +; input +; * x - current player to test +; loop through players and see if colliding with any enemy (including enemy bullets) +@check_player_x_collision: + ldy ENEMY_CURRENT_SLOT ; set y = #$enemy slot + lda PLAYER_STATE,x ; load player state (0 = dropping into level, 1 = normal, 2 = dead, 3 = can't move) + cmp #$01 ; compare to normal state + bne @next_player ; move to next player if current player not in normal state (falling, dead, stuck) + lda LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor; #$80 = indoor/base boss screen + lsr ; shift least significant bit into carry flag + bcc @handle_outdoor_level ; branch for outdoor level or indoor/base boss screen + lda ENEMY_TYPE,y ; indoor level, load current enemy type + cmp #$01 ; see if the enemy is a bullet + bne @check_in_water_crouched ; branch if not a bullet + lda PLAYER_SPRITE_SEQUENCE,x ; enemy is a bullet, load player animation frame + cmp #$02 ; see if player is crouching + bne @check_in_water_crouched ; branch if player isn't crouched on indoor level + beq @next_player ; player crouching go to next player + ; can't get hit by bullet when crouching on indoor level + +@handle_outdoor_level: + lda ENEMY_STATE_WIDTH,y ; load enemy state width + asl ; shift bit 7 to carry flag + bpl @check_in_water_crouched ; branch if ENEMY_STATE_WIDTH,x bit 6 is clear (player can't land on enemy) + asl ; shift bit 6 to carry flag + bpl @check_player_jumping ; branch if ENEMY_STATE_WIDTH,x bit 5 is clear (collision box code bit) + lda ENEMY_Y_POS,y ; ENEMY_STATE_WIDTH,x bit 5 set, load enemy y position on screen + sec ; set carry flag in preparation for subtracting + sbc SPRITE_Y_POS,x ; player sprite y position on screen + bcc @set_collision_box_code ; branch if player below enemy + cmp #$08 ; player above enemy, see how close to enemy + lda ENEMY_STATE_WIDTH,y ; load enemy state width + and #$ef ; clear bit 4 (collision box type) + bcs @set_state_width ; branch if farther than #$08 from enemy, to use cleared bit 4 collision code + +@set_collision_box_code: + lda ENEMY_STATE_WIDTH,y + ora #$10 ; set bit 4 (collision box type) + +@set_state_width: + sta ENEMY_STATE_WIDTH,y + bne @check_in_water_crouched + +@check_player_jumping: + lda PLAYER_JUMP_STATUS,x ; low nibble 1 = jumping, 0 not jumping; high nibble = facing direction + beq @check_in_water_crouched ; branch if player is not jumping + lda PLAYER_Y_FAST_VELOCITY,x ; player is jumping, load y fast velocity + bmi @next_player ; move to next player if player ascending in jump for enemies with bit 5 clear of ENEMY_STATE_WIDTH + cmp #$01 ; player not ascending, compare y fast velocity to #$01 + bcc @next_player ; move to next player if player y fast velocity #$00 for enemies with bit 5 clear of ENEMY_STATE_WIDTH + ; !(OBS) not sure why didn't use beq here + bcs @check_in_water_crouched ; !(OBS) conditionally branches to next line + ; no matter value of condition @check_in_water_crouched will execute + +@check_in_water_crouched: + ldy #$00 ; default collision box offset collision_box_codes_tbl (in water) + lda PLAYER_WATER_STATE,x ; load player in water state + beq @set_collision_code_offset ; branch if player not in water + lda CONTROLLER_STATE,x ; player in water, load controller state to see if crouching + and #$04 ; bits .... .x.. (down button pressed) + bne @next_player ; player is invisible when crouching in water, don't test for collision + beq @check_if_enemy_collision ; player is in water, but not crouching, test collision + +; player not in water +@set_collision_code_offset: + iny ; y = #$1, player is jumping collision_box_codes_tbl + lda PLAYER_JUMP_STATUS,x ; low nibble 1 = jumping, 0 not jumping; high nibble = facing direction + bne @check_if_enemy_collision ; branch if player is jumping + iny ; player not jumping, increment collision box offset + lda PLAYER_SPRITE_CODE,x ; load player sprite + cmp #$17 ; compare to crouching + beq @check_if_enemy_collision ; player animation frame is #$17 (crouching) + iny ; increment if player animation frame isn't #$17 + +; test against correct enemy collision box depending on player state (register y) +@check_if_enemy_collision: + jsr set_enemy_collision_box ; set collision box in [$08-$0b] based off y register + lda SPRITE_Y_POS,x ; load current sprite Y position + sec ; set the carry flag in preparation for subtraction + sbc $08 ; subtract from collision box top left y coordinate + cmp $0a ; compare to height of collision box + bcs @next_player ; branch if player sprite is vertically outside of collision box [(SPRITE_Y_POX,x - $08) < $0a] + ; this is a neat trick + ; * y is above collision box - subtraction result is negative and the carry is set since cmp thinks the value is positive + ; * y is below collision box - subtraction result is greater than height $0a and the carry is set + lda SPRITE_X_POS,x ; player sprite is in collision box vertically, now check horizontally + sec ; set the carry flag in preparation for subtraction + sbc $09 ; subtract the top-left x coordinate of the collision box from the x position + cmp $0b ; compare to the width of the collision box + ; same neat trick applies here + ; * x is to the left of the collision box - subtraction result is negative and carry is set since cmp thinks value is positive + ; * x is to the right of the collision box - subtraction result is greater than width ($08) so carry is set + bcc @inside_enemy_collision_box ; branch if inside of collision box (both horizontally and vertically) [(SPRITE_X_POX,x - $09) < $0b] + +@next_player: + dex ; decrement player index + bmi @exit_00 ; branch if finished looping through sprites + jmp @check_player_x_collision ; loop to next player + +@exit_00: + ldx ENEMY_CURRENT_SLOT + rts + +; player landed on non-dangerous enemy, e.g. moving cart or floating rock in vertical level +; #$14 - mining cart, #$15 - stationary mining cart, #$10 - floating rock platform +; move the player as the enemy moves +@land_on_enemy: + lda SPRITE_Y_POS,x ; load the player's Y position + cmp PLAYER_FALL_X_FREEZE,x + bcc @next_player + lda #$01 ; a = #$01 + sta ENEMY_FRAME,y ; enemy animation frame number to #$01, lets mining cart know to start moving + lda ENEMY_X_VELOCITY_FRACT,y + clc ; clear carry in preparation for addition + adc ENEMY_X_VEL_ACCUM,y + lda ENEMY_X_VELOCITY_FAST,y ; load enemy fast velocity + adc #$00 ; add any fractional overflow + clc ; clear carry in preparation for addition + adc PLAYER_FAST_X_VEL_BOOST,x ; add any existing boost + ; can support being on a moving enemy that is on a moving enemy + sta PLAYER_FAST_X_VEL_BOOST,x ; set any boost to player's x velocity by being on a moving enemy + lda ENEMY_STATE_WIDTH,y + asl + asl + asl + lda #$e4 ; a = #$e4 (-28) + bcc @set_landing_pos ; branch if ENEMY_STATE_WIDTH,y bit 5 is 0 + lda #$e8 ; a = #$e8 (-24) + +@set_landing_pos: + clc ; clear carry in preparation for addition + adc ENEMY_Y_POS,y ; subtract 24 or 28 from enemy y position on screen + sta SPRITE_Y_POS,x ; set player y position + lda #$01 ; a = #$01 + sta PLAYER_ON_ENEMY,x ; set player on enemy flag + jsr player_land_on_ground + jmp @next_player + +; player sprite collides with enemy, or enemy bullet +@inside_enemy_collision_box: + ldy ENEMY_CURRENT_SLOT ; load slot index of enemy that is being collided with + lda ENEMY_X_POS,y ; load enemy x position on screen + sec ; set the carry flag in preparation for subtraction + sbc SPRITE_X_POS,x ; subtract the x position of player sprite + bcs @check_can_land_on ; branch if positive (player to left of enemy) + eor #$ff ; player to right of enemy - flip all bits + adc #$01 ; add 1 for correction + +@check_can_land_on: + cmp #$80 + bcs @next_player + lda ENEMY_STATE_WIDTH,y ; load ENEMY_STATE_WIDTH + asl ; shift bit 7 to carry + bpl @collide_with_enemy ; branch to collide with enemy if can't land on them + and #$20 ; player can land on enemy, keep bit 5 (collision box code) + beq @land_on_enemy ; player can land on enemy (floating rock and moving cart) + +@collide_with_enemy: + lda ENEMY_TYPE,y ; load current enemy type + beq pick_up_weapon_item ; branch if weapon item enemy + lda NEW_LIFE_INVINCIBILITY_TIMER,x ; timer for invincibility (after dying) + bne @next_player ; when still invincibility after dying, player walks through enemy, skip + lda INVINCIBILITY_TIMER,x + bne @invincible_collision ; branch if player is invincible (barrier weapon) to set enemy HP to #$00 + jsr kill_player ; player collided with enemy sprite, kill player + lda ENEMY_TYPE,y ; load current enemy type + cmp #$01 ; compare to bullet + beq remove_current_enemy ; remove bullet after collision + ldx ENEMY_CURRENT_SLOT + rts + +@invincible_collision: + stx $17 ; store current player number in $17 + ldx ENEMY_CURRENT_SLOT ; load current enemy slot number + lda ENEMY_HP,x ; load enemy hp + beq @exit_01 + cmp #$f0 + bcs @exit_01 + lda #$00 ; a = #$00 + sta ENEMY_HP,x ; set enemy hp + jsr add_enemy_score_set_enemy_routine ; adds score amount to player score, sets enemy destroyed routine + +@exit_01: + rts + +; player has collided with a weapon item, pick it up +pick_up_weapon_item: + stx $10 + lda #$0a ; a = #$0a + sta $00 ; set score to add to player as #$0a (1,000 points) + txa + tay + jsr add_player_low_score ; add points to player score, check if new high score and extra life + ldx $10 + ldy ENEMY_CURRENT_SLOT + lda #$1f ; a = #$1f (sound_1f) + jsr play_sound ; play weapon item taken sound + lda ENEMY_ATTRIBUTES,y ; get weapon item attributes + and #$07 ; keep bits 0-3 (attributes) + beq @set_rapid_flag ; set rapid flag for weapon if attribute is #$00 + cmp #$05 ; check for b weapon (barrier). Gives invincibility + bcc @compare_and_set_weapon ; branch if less than #$05 (MFSL) + beq @set_invincibility_timer ; branch for barrier weapon + jsr destroy_all_enemies ; falcon weapon effect - destroy all enemies + lda #$20 ; a = #$20 + sta FALCON_FLASH_TIMER ; set falcon weapon flash timer to #$20 frames + bne remove_current_enemy + +; b weapon effect (barrier). Gives invincibility +; decreases every 8 frames +; NTSC is about #3c frames per second +; PAL is close to #$32 frames per second +; NTSC: #$80 * #$8 = #$400 / #$3c = 17.06667 (decimal) seconds +; NTSC: #$90 * #$8 = #$480 / #$3c = 19.2 (decimal) seconds +@set_invincibility_timer: + lda #$80 ; set duration of the b weapon effect + ldy CURRENT_LEVEL ; current level + cpy #$06 ; check if level 7 (hangar) + bne @continue + lda #$90 ; set duration for level 7 + +@continue: + sta INVINCIBILITY_TIMER,x + jmp remove_current_enemy + +; r weapon +@set_rapid_flag: + lda #$10 ; a = #$10 + sta $08 + ldy #$ff ; y = #$ff + bne @set_player_weapon + +; default for MFSL Weapons +; compare weapon being picked up with current weapon +; if the same, rapid fire flag is kept; otherwise it is dropped +@compare_and_set_weapon: + ldy #$f0 ; y = #$f0 (keep rapid fire flag) + sta $08 ; store weapon item attributes in $08 + eor P1_CURRENT_WEAPON,x ; test to see if current weapon matches weapon item (XOR) + and #$0f ; compare to weapon regardless of rapid fire flag + beq @set_player_weapon ; keep rapid fire flag (if set) when picking up same weapon + ldy #$e0 ; remove rapid fire flag since picking up different weapon + +@set_player_weapon: + tya ; y = #$f0 or #$e0 depending if same weapon was picked up + and P1_CURRENT_WEAPON,x ; strip/set rapid fire flag + ora $08 ; merge in rapid fire flag with weapon being picked up + sta P1_CURRENT_WEAPON,x ; set current player's weapon + +remove_current_enemy: + ldx ENEMY_CURRENT_SLOT + jmp remove_enemy ; remove enemy + +; loop through player bullets and see if they collide with current enemy +bullet_enemy_collision_test: + lda ENEMY_STATE_WIDTH,x ; load current enemy ENEMY_STATE_WIDTH + and #$30 ; keep bits 4 and 5 (collision box code) + asl + asl + sta $10 ; store enemy's original collision box code in $10 + ldy #$04 ; override enemy collision box to box code #$04 + jsr set_enemy_collision_box ; set collision box in [$08-$0b] for box code #$04 (bullet collision box code) + ldx #$0f ; loop through player bullets + +@loop: + lda PLAYER_BULLET_SPRITE_CODE,x ; load current player bullet sprite + beq @next_bullet ; move to next bullet if no bullet at current position + lda PLAYER_BULLET_ROUTINE,x ; load player bullet routine + cmp #$01 ; compare to #$01 + bne @next_bullet ; move to next bullet if not routine 01 + lda LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor + lsr + bcc @test_collision ; branch for outdoor level + ldy $10 ; indoor level, load collision box code + bpl @bullet_delay ; branch if collision box is positive, i.e. non-zero + lda PLAYER_BULLET_SLOT,x ; load bullet type + 1 + bmi @test_collision + bpl @next_bullet ; branch if bullet exists (PLAYER_BULLET_SLOT is non-zero) + +@bullet_delay: + lda PLAYER_BULLET_TIMER,x + cmp #$02 + bcs @next_bullet + +@test_collision: + lda PLAYER_BULLET_Y_POS,x + sbc $08 + cmp $0a + bcs @next_bullet + lda PLAYER_BULLET_X_POS,x + sbc $09 + cmp $0b + bcc @bullet_enemy_collision + +@next_bullet: + dex + bpl @loop + ldx ENEMY_CURRENT_SLOT + rts + +@bullet_enemy_collision: + ldy ENEMY_CURRENT_SLOT ; load the current enemy slot index + lda ENEMY_X_POS,y ; load enemy x position on screen + sec ; set carry flag in preparation for subtraction + sbc PLAYER_BULLET_X_POS,x ; ENEMY_X_POS - PLAYER_BULLET_X_POS + bcs @continue ; branch if bullet hits enemy from left + eor #$ff ; flip all bits + adc #$01 ; add #$01 (convert to positive) + +@continue: + cmp #$80 + bcs @next_bullet + lda PLAYER_BULLET_OWNER,x + jsr bullet_collision_logic + lda PLAYER_BULLET_SLOT,x + cmp #$05 + bne @set_routine_exit + stx $08 + txa + ldx #$00 ; x = #$00 + cmp #$0a + bcc @continue_2 + ldx #$0a ; x = #$0a + +@continue_2: + lda PLAYER_BULLET_ROUTINE,x + cmp #$01 + beq @set_routine_exit + inx + cpx $08 + bne @continue_2 + +@set_routine_exit: + jsr set_bullet_routine_to_2 ; move to bullet routine 2 and reset PLAYER_BULLET_TIMER to #$06 + ldx ENEMY_CURRENT_SLOT + rts + +; set PLAYER_BULLET_ROUTINE,x to #$02, which destroys the bullet (player_bullet_collision_routine) +; set bullet delay to #$06 +set_bullet_routine_to_2: + lda #$02 ; used to specify the bullet routine, see player_bullet_routine_0X_ptr_tbl and player_bullet_routine_indoor_0X_ptr_tbl + sta PLAYER_BULLET_ROUTINE,x ; move to last bullet routine for bullet (player_bullet_collision_routine), this handles destroying the bullet + lda #$06 ; a = #$06 + sta PLAYER_BULLET_TIMER,x + rts + +; subtract enemy HP, play collision sound (if appropriate), award points +bullet_collision_logic: + sta $17 ; store PLAYER_BULLET_OWNER in $17 0 = p1, 1 = p2 + stx $11 ; backup bullet index in $11 for logic + ldy ENEMY_CURRENT_SLOT ; load current enemy slot + lda ENEMY_HP,y ; load enemy hp + beq @exit ; exit if enemy HP already #$00 + cmp #$f0 + bcs @exit ; exit if enemy HP is negative + sbc #$00 ; subtract #$01 from enemy HP (carry is clear) + bcs @continue + lda #$00 ; a = #$00 + +@continue: + sta ENEMY_HP,y ; update enemy hp + bne @play_collision_sound ; play collision sound if appropriate + ldx ENEMY_CURRENT_SLOT + jsr add_enemy_score_set_enemy_routine ; adds score amount to player score, sets enemy destroyed routine + ldx $11 ; restore bullet index back in x + rts + +@play_collision_sound: + lda ENEMY_STATE_WIDTH,y + and #$04 ; keep bit 2 (play bullet collision sound bit) + beq @exit ; exit if shouldn't play sound + lda ENEMY_VAR_A,y ; load appropriate sound index for current enemy + tay + lda bullet_hit_sound_tbl,y ; load sound code + jsr play_sound ; play bullet collision sound + +@exit: + rts + +; table for bullet hit sound codes ($05 bytes) +; #$16 = Normal Hit (sound_16) +; #$18 = Heart Hit (sound_18) +; #$14 = Core Plating Hit (sound_14) +bullet_hit_sound_tbl: + .byte $16,$16,$16,$18,$14 + +; determine enemy (including enemy bullets) collision box +; input +; * y - the collision box table to use, depends on player state (is player crouching, etc) +; * 0 - player is in water +; * 1 - player is jumping +; * 2 - player is crouching +; * 3 - normal +; * 4 - bullet collision box code +; stores collision corners for enemy in $08 (top left Y), $09 (top left X), $0a (width), $0b (height) +set_enemy_collision_box: + stx $11 ; temporarily save x into $11 for duration of method + ldx ENEMY_CURRENT_SLOT ; load current enemy slot index + tya ; move y (desired collision box table) to a so it can be doubled with asl + asl ; double since table is 2 bytes each + tay ; move value back to y + lda collision_box_codes_tbl,y ; load low byte of desired collision box table + sta $0e ; store low byte in $0e + lda collision_box_codes_tbl+1,y ; load high byte of desired collision box table + sta $0f ; store high byte in $0f + lda ENEMY_SCORE_COLLISION,x ; load specified score and collision codes for enemy + and #$0f ; filter to only the collision code part of byte (low 4 bits) + cmp #$0f ; compare to all ones + beq @collision_code_f ; branch if collision code f (fire beams and rising spiked walls) + asl ; double collision code + asl ; double again, since each collision box is 4 bytes + tay ; transfer to offset register + lda ENEMY_Y_POS,x ; load enemy y position on screen + adc ($0e),y ; add collision_box_codes_XX,y to y position (frequently this is actually subtraction) + sta $08 ; store y position on screen of top-left of collision box + iny ; move to next byte to read + lda ENEMY_X_POS,x ; load enemy x position on screen + clc ; clear carry in preparation for addition + adc ($0e),y ; add (collision_box_codes_XX,y) from x position (frequently this is actually subtraction) + sta $09 ; store x position on screen of top-left of collision box + iny ; move to next byte to read + lda ($0e),y ; read height of collision box + sta $0a ; store height in $0a + iny ; move to next byte to read + lda ($0e),y ; read width of collision box + sta $0b ; store width in $0b + ldx $11 ; restore x back to value before set_enemy_collision_box was called + rts + +; used for fire beams and rising spiked walls, i.e. things with variable sized collision boxes +@collision_code_f: + lda ENEMY_ATTRIBUTES,x ; load the enemy's attributes + asl + asl ; shift bit 6 of ENEMY_ATTRIBUTES into carry + lda ENEMY_VAR_1,x ; load collision_code_f_base_tbl offset stored in ENEMY_VAR_1 + bcc @continue ; branch if bit 6 of ENEMY_ATTRIBUTES,x is 0 + eor #$ff ; bit 6 set, negate the , flip all bits + adc #$00 ; add 1 + +@continue: + clc ; clear carry in preparation for addition + adc #$08 ; add #$08 to ENEMY_VAR_1,x + sta $0c ; store result in $0c + tya ; transfer which collision box table to use to y [#$00-#$04] + asl ; double (already doubled y) since each entry is #$04 bytes + tay ; transfer back to offset register + lda collision_code_f_base_tbl,y ; load initial y coordinate of top left of collision box + sta $08 ; store y coordinate of top left of collision box in $08 + lda collision_code_f_base_tbl+1,y ; load initial x coordinate of top left of collision box + sta $09 ; store x coordinate of top left of collision box in $09 + lda collision_code_f_base_tbl+2,y ; load initial height of top left of collision box + sta $0a ; store height of collision box in $0a + lda collision_code_f_base_tbl+3,y ; load initial width of top left of collision box + sta $0b ; store width of collision box in $0b + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + lsr + lsr + lsr + lsr ; move high nibble to low nibble + and #$0c ; keep bits .... xx.. (0, 4, or 8, or 12) + tay ; transfer to offset register + lda collision_code_f_adj_tbl,y ; load top-left y coordinate offset + jsr @replace_placeholder ; if placeholder value, replace it with $c0 (or its negative) + clc ; clear carry in preparation for addition + adc $08 ; add to top-left y coordinate + clc ; clear carry in preparation for addition + adc ENEMY_Y_POS,x ; add to enemy y position on screen + sta $08 ; set top-left y coordinate of collision box based + lda collision_code_f_adj_tbl+1,y ; load top-left x coordinate offset + jsr @replace_placeholder ; if placeholder value, replace it with $c0 (or its negative) + clc ; clear carry in preparation for addition + adc $09 ; add to top-left x coordinate + clc ; clear carry in preparation for addition + adc ENEMY_X_POS,x ; add to enemy x position on screen + sta $09 ; set top-left x coordinate of collision box based + lda collision_code_f_adj_tbl+2,y ; load collision box height adjustment + jsr @replace_placeholder ; if placeholder value, replace it with $c0 (or its negative) + clc ; clear carry in preparation for addition + adc $0a ; add to base height + sta $0a ; set new collision box height + lda collision_code_f_adj_tbl+3,y ; load collision box width + jsr @replace_placeholder ; if placeholder value, replace it with $c0 (or its negative) + clc ; clear carry in preparation for addition + adc $0b ; add to base collision box width + sta $0b ; set new collision box width + ldx $11 ; restore x back to value before set_enemy_collision_box was called + rts + +; replaces the variable placeholder (#$fe, #$ff) with adjustment value $0c (or its negative) +; input +; * a - value from collision_code_f_adj_tbl +; output +; * a +; * if input a < #$fe, a (not a placeholder) +; * if input a == #$fe, negative value of $0c +; * if input a == #$ff, $0c +; note: $0c is ENEMY_VAR_1 or negative ENEMY_VAR_1 depending on bit 6 of ENEMY_ATTRIBUTES +@replace_placeholder: + cmp #$fe ; compare collision box adjustment value to to #$fe + bcc @exit ; exit if value less than #$fe. it is not a placeholder + lsr ; shift bit 0 of collision_code_f_adj_tbl value to the carry flag + lda $0c ; load adjustment control variable + bcs @exit ; exit to $0c if placeholder is #$ff (odd) + eor #$ff ; placeholder is #$fe (event), return negative $0c, flip all bits + adc #$01 ; add one + +@exit: + rts + +; table for f collision code initial collision box rectangles depending on collision code ($14 bytes) +; byte 0 - initial top left y coordinate +; byte 1 - initial top left x coordinate +; byte 2 - initial collision box height +; byte 3 - initial collision box width +collision_code_f_base_tbl: + .byte $00,$fb,$0a,$0a ; (-5, 0 ) - player is in water + .byte $f8,$fc,$10,$08 ; (-4, -8 ) - player is jumping + .byte $f4,$f5,$04,$16 ; (-11, -12) - player is crouching + .byte $f1,$fc,$1d,$08 ; (-14, -15) - normal + .byte $fe,$fe,$04,$04 ; (-2 , -2 ) - bullet collision box code + +; table for adjustment of initial f code collision box based (indirectly) on ENEMY_VAR_1 ($10 bytes) +; these values will be overwritten if value is #$fe or #$ff +; byte 0 - top left y coordinate adjustment +; byte 1 - top left x coordinate adjustment +; byte 2 - collision box height adjustment +; byte 3 - collision box width adjustment +collision_code_f_adj_tbl: + .byte $fa,$f8,$0c,$ff ; variable width fixed x (growing right) + .byte $fa,$fe,$0c,$ff ; variable width variable x (growing left) + .byte $f8,$fa,$ff,$0c ; variable height fixed y (growing downward) + .byte $fe,$f6,$ff,$14 ; variable height variable y (growing upward) + +; pointer table to list of collision box codes for each player state ($05 * $02 = $0a bytes) +; CPU address $e4e8 +collision_box_codes_tbl: + .addr collision_box_codes_00 ; CPU address $e4f2 - player in water + .addr collision_box_codes_01 ; CPU address $e52e - player jumping + .addr collision_box_codes_02 ; CPU address $e56a - player standing + .addr collision_box_codes_03 ; CPU address $e5a6 - player crouching + .addr collision_box_codes_04 ; CPU address $e5e2 + +; each 4 bytes is a a collision box code +; #$e different collision box codes +; player is in water +collision_box_codes_00: + .byte $f1,$f7,$28,$12 + .byte $fe,$f9,$14,$14 + .byte $f8,$f3,$1a,$1a + .byte $f2,$ed,$26,$26 + .byte $e0,$f0,$08,$20 + .byte $fd,$f8,$10,$10 + .byte $f5,$f7,$20,$12 + .byte $e7,$e2,$3c,$3c + .byte $f1,$e2,$28,$3c + .byte $e4,$d3,$48,$5a + .byte $00,$f6,$16,$16 + .byte $08,$f1,$12,$1e + .byte $f5,$f1,$20,$1e + .byte $ea,$ec,$37,$28 + .byte $f3,$f3,$11,$1a + +; each 4 bytes is a a collision box code +; player is jumping +collision_box_codes_01: + .byte $ea,$f8,$1e,$10 + .byte $f6,$fa,$0c,$0c + .byte $f1,$f5,$12,$16 + .byte $ea,$ee,$24,$24 + .byte $e0,$f0,$08,$20 + .byte $f5,$f9,$0e,$0e + .byte $ed,$f8,$1e,$10 + .byte $df,$e3,$3a,$3a + .byte $e9,$e3,$26,$3a + .byte $dc,$d4,$46,$58 + .byte $f8,$f7,$14,$14 + .byte $00,$f2,$10,$1c + .byte $ed,$f2,$1e,$1c + .byte $e2,$ed,$35,$26 + .byte $f3,$f1,$12,$1e + +; each 4 bytes is a a collision box code +; player is crouching +collision_box_codes_02: + .byte $f4,$f1,$0d,$1e + .byte $f3,$f4,$04,$18 + .byte $ec,$ed,$10,$26 + .byte $e6,$e7,$1c,$32 + .byte $e0,$f0,$08,$20 + .byte $f1,$f2,$06,$1c + .byte $e9,$f1,$16,$1e + .byte $db,$dc,$32,$48 + .byte $e5,$dc,$1e,$48 + .byte $d8,$cd,$3e,$66 + .byte $f4,$f0,$19,$22 + .byte $fc,$eb,$08,$2a + .byte $e3,$f2,$1a,$1c + .byte $de,$e6,$2d,$34 + .byte $e9,$ea,$0d,$2c + +; each 4 bytes is a a collision box code +; player is standing on ground +collision_box_codes_03: + .byte $f3,$f8,$24,$10 + .byte $f0,$fb,$1f,$0a + .byte $ea,$f5,$2b,$16 + .byte $e3,$ee,$39,$24 + .byte $e0,$f0,$08,$20 + .byte $ee,$f9,$23,$0e + .byte $e6,$f8,$33,$10 ; #$33, #$10 is the start of sound dpcm sound sample (length 385) !(BUG?) + .byte $d8,$e3,$4f,$3a ; used on level 5 (snow field) after defeating the boss + .byte $e2,$e3,$3b,$3a ; this DPCM sample does not occur in the Japanese version + .byte $d5,$d4,$5b,$58 + .byte $f1,$f7,$29,$14 + .byte $f9,$f2,$25,$1c + .byte $e4,$f2,$35,$1c + .byte $da,$ed,$4a,$26 + .byte $e6,$f1,$2a,$1e + +; each 4 bytes is a a collision box code +; bullet collision code +collision_box_codes_04: + .byte $ee,$f5,$24,$16 + .byte $fc,$fc,$08,$08 + .byte $f5,$f5,$16,$16 + .byte $ef,$ef,$22,$22 + .byte $e0,$f0,$08,$20 + .byte $fa,$fa,$0c,$0c + .byte $f3,$fa,$16,$0c + .byte $e4,$e4,$38,$38 + .byte $ee,$e4,$24,$38 + .byte $e1,$d5,$44,$56 + .byte $fd,$f8,$12,$12 + .byte $05,$f3,$0e,$1a + .byte $f3,$f3,$1a,$1a + .byte $e7,$ee,$33,$24 + .byte $f2,$f2,$13,$1c + +; execute all enemy routines +exe_all_enemy_routine: + lda #$00 ; a = #$00 + sta PLAYER_ON_ENEMY ; clear player on non-dangerous enemy flag + sta $b7 + ldx #$0f ; x = #$0f + +exe_enemy_routine_loop: + lda ENEMY_ROUTINE,x ; enemy routine index + beq advance_enemy ; no enemy loaded in slot, continue to next enemy + stx ENEMY_CURRENT_SLOT ; store current enemy slot offset into $83 + asl ; double enemy routine index since each entry in enemy routine table is 2 bytes + sta $04 ; save enemy routine index (enemy_routine_ptr_tbl or level_enemy_routine_ptr_tbl) + jsr exe_enemy_routine ; execute the xth (current) enemy routine's current sub-routine + lda ENEMY_SPRITES,x ; enemy sprite data: sprite code, attribute, etc. + beq advance_enemy ; no enemy tiles specified, continue to next enemy + lda LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor + lsr + bcc @handle_outdoor ; branch for outdoor level + lda ENEMY_Y_POS,x ; indoor level, load enemy y position on screen + cmp #$9c + bcc @continue + +@handle_outdoor: + lda ENEMY_STATE_WIDTH,x + lsr ; shift bit 0 into the carry flag + bcs @continue + jsr check_players_collision ; check if players are colliding with current enemy + +@continue: + lda ENEMY_STATE_WIDTH,x ; see if enemy is active by checking bit 7 + bmi advance_enemy ; branch if bit 7 is set + jsr bullet_enemy_collision_test ; enemy is active, check collision + +; decrement x and if it's greater than #$00 jump back to execute enemy routine +advance_enemy: + dex + bpl exe_enemy_routine_loop + rts + +; executes the xth enemy routine +; if the enemy type (ENEMY_TYPE,x) is >= #$10, then it is a level-specific enemy routine, i.e. an enemy unique for the level +; each level uses different enemies for these values, they executed from this label as well +; input +; * $04 - the enemy routine index (which sub-routine inside the enemy routine to execute) +; x has the current enemy slot number +exe_enemy_routine: + lda ENEMY_TYPE,x ; load current enemy type + asl ; double since enemy type pointer is 2 bytes + tay ; store offset into pointer table into y + lsr ; undo the doubling to see the offset number + cmp #$10 ; compare offset to #$10 + bcs exec_level_enemy_routine ; offset is greater than or equal to #$10 (level-specific enemy) + lda enemy_routine_ptr_tbl,y ; load low byte of the routine pointer + sta $02 ; store in $02 + lda enemy_routine_ptr_tbl+1,y ; load high byte of the enemy pointer + +; $04 stores the enemy routine index (which sub-routine inside the enemy routine to execute) +exe_enemy_routine_subroutine: + sta $03 ; store enemy routine high byte into $03 + ldy $04 ; load which sub-routine to execute + lda ($02),y ; load the low byte of the enemy sub-routine to execute + sta $04 ; store in $04 + iny ; increment read offset + lda ($02),y ; load the high byte of the enemy sub-routine to execute + sta $05 ; store in $05 + jmp ($04) ; jump to that sub-routine + +; enemy type >= 10 (level specific) +exec_level_enemy_routine: + tya ; move current (doubled) enemy type into a + sbc #$20 ; subtract #$20 (since we are indexing from level specific enemy types) + tay ; store offset into y + lda (ENEMY_LEVEL_ROUTINES),y ; load current level-specific enemy routines low byte into a + sta $02 ; store into $02 + iny ; increment read offset + lda (ENEMY_LEVEL_ROUTINES),y ; load current level-specific enemy routines high byte into a + jmp exe_enemy_routine_subroutine ; loads the current sub-routine for the current enemy + +; stores the enemy routines table 2-byte address for the current level into ENEMY_LEVEL_ROUTINES ($80) +load_level_enemies_to_mem: + lda CURRENT_LEVEL ; load the current level + asl ; double the number since each address is 2 bytes + tay ; transfer offset to y + lda level_enemy_routine_ptr_tbl,y ; load low byte of level-specific enemy table to y + sta ENEMY_LEVEL_ROUTINES ; store low byte into $80 + lda level_enemy_routine_ptr_tbl+1,y ; load high byte of level-specific enemy table to y + sta ENEMY_LEVEL_ROUTINES+1 ; store high byte into $81 + rts + +; pointer table for enemy routines by level ($08 * $02 = $10 bytes) +; each entry is an address pointing to another table listing the tables of +; routines for each enemy specific to the level +level_enemy_routine_ptr_tbl: + .addr enemy_routine_level_1 ; CPU address $e6c8 + .addr enemy_routine_level_2_4 ; CPU address $e6ce + .addr enemy_routine_level_3 ; CPU address $e6f0 + .addr enemy_routine_level_2_4 ; CPU address $e6ce + .addr enemy_routine_level_5 ; CPU address $e6fc + .addr enemy_routine_level_6 ; CPU address $e70a + .addr enemy_routine_level_7 ; CPU address $e714 + .addr enemy_routine_level_8 ; CPU address $e726 + +; pointer table for common enemies routines - codes 00 to 0f +; bank 0 and bank 7 labels +; every entry is 2 bytes less than the actual label +enemy_routine_ptr_tbl: + .addr weapon_item_routine_ptr_tbl-2 ; weapon item (00) - CPU address $8001 bank 0 + .addr enemy_bullet_routine_ptr_tbl-2 ; enemy bullet (01) - CPU address $8147 bank 0 + .addr weapon_box_routine_ptr_tbl-2 ; pill box sensor (02) - CPU address $8205 bank 0 + .addr flying_capsule_routine_ptr_tbl-2 ; flying capsule (03) - CPU address $8305 bank 0 + .addr rotating_gun_routine_ptr_tbl-2 ; rotating gun (04) - CPU address $8379 bank 0 + .addr soldier_routine_ptr_tbl-2 ; soldier (05) - CPU address $8608 bank 0 + .addr sniper_routine_ptr_tbl-2 ; sniper (06) - CPU address $8946 bank 0 + .addr red_turret_routine_ptr_tbl-2 ; red turret (07) - CPU address $84b8 bank 0 + .addr wall_cannon_routine_ptr_tbl-2 ; wall cannon (08) - CPU address $efb7 + .addr enemy_routine_do_nothing_ptr_tbl-2 ; unused (09) - CPU address $e734 + .addr wall_plating_routine_ptr_tbl-2 ; wall plating (0a) - CPU address $f077 + .addr mortar_shot_routine_ptr_tbl-2 ; mortar shot (0b) - CPU address $f1c4 + .addr scuba_soldier_routine_ptr_tbl-2 ; scuba diver (0c) - CPU address $f13b + .addr enemy_routine_do_nothing_ptr_tbl-2 ; unused (0d) - CPU address $e734 + .addr turret_man_routine_ptr_tbl-2 ; turret man (0e) - CPU address $f0bd + .addr turret_man_bullet_routine_ptr_tbl-2 ; turret man bullet (0f) - CPU address $f119 + +; pointer table for level 1 enemy routines (#$2 * #$3 = #$6 bytes) +; every entry is 2 bytes less than the actual label +enemy_routine_level_1: + .addr bomb_turret_routine_ptr_tbl-2 ; boss bomb turret (10) - CPU address $8b47 + .addr boss_wall_plated_door_routine_ptr_tbl-2 ; door plate with siren (11) - CPU address $8bc7 + .addr exploding_bridge_routine_ptr_tbl-2 ; exploding bridge (12) - CPU address $8c50 + +; pointer table for level 2/4 enemy routines (#$11 * #$2 = #$22 bytes) +; every entry is 2 bytes less than the actual label +enemy_routine_level_2_4: + .addr boss_eye_routine_ptr_tbl-2 ; Boss Eye (10) - CPU address $8e65 + .addr roller_routine_ptr_tbl-2 ; Rollers (11) - CPU address $8f80 + .addr grenade_routine_ptr_tbl-2 ; Grenades (12) - CPU address $8fc7 + .addr wall_turret_routine_ptr_tbl-2 ; Wall Turret (13) - CPU address $908a + .addr wall_core_routine_ptr_tbl-2 ; Core (14) - CPU address $910e + .addr indoor_soldier_routine_ptr_tbl-2 ; Running Guy (15) - CPU address $92b8 + .addr jumping_soldier_routine_ptr_tbl-2 ; Jumping Guy (16) - CPU address $936e + .addr grenade_launcher_routine_ptr_tbl-2 ; Seeking Guy (17) - CPU address $9458 + .addr four_soldiers_routine_ptr_tbl-2 ; Group of 4 (18) - CPU address $952f + .addr indoor_soldier_gen_routine_ptr_tbl-2 ; Indoor Soldier Generator (19) - CPU address $8d17 + .addr indoor_roller_gen_routine_ptr_tbl-2 ; Rollers Generator (1A) - CPU address $95c0 + .addr eye_projectile_routine_ptr_tbl-2 ; Sphere Projectile (1B) - CPU address $8f33 + .addr boss_gemini_routine_ptr_tbl-2 ; Boss Gemini (1C) - CPU address $9ef3 + .addr spinning_bubbles_routine_ptr_tbl-2 ; Spinning Bubbles (1D) - CPU address $a04f + .addr blue_soldier_routine_ptr_tbl-2 ; Blue Jumping Guy (1E) - CPU address $a147 + .addr red_soldier_routine_ptr_tbl-2 ; Red Shooting Guy (1F) - CPU address $a258 + .addr red_blue_soldier_gen_routine_ptr_tbl-2 ; Red/Blue Guys Generator (20) - CPU address $a2fc + +; pointer table for level 3 enemy routines (#$6 * #$2 = #$c bytes) +; every entry is 2 bytes less than the actual label +enemy_routine_level_3: + .addr floating_rock_routine_ptr_tbl-2 ; Rock Platform (10) - CPU address $97e3 + .addr moving_flame_routine_ptr_tbl-2 ; Moving Flame (11) - CPU address $983a + .addr rock_cave_routine_ptr_tbl-2 ; Falling Rock Generator (12) - CPU address $9855 + .addr falling_rock_routine_ptr_tbl-2 ; Falling Rock (13) - CPU address $987b + .addr boss_mouth_routine_ptr_tbl-2 ; Level 3 Boss Mouth (14) - CPU address $9916 + .addr dragon_arm_orb_routine_ptr_tbl-2 ; Level 3 Dragon Arm Orb (15) - CPU address $9a8a + +; pointer table for level 5 enemy routines (#$7 * #$2 = #$e bytes) +; every entry is 2 bytes less than the actual label +enemy_routine_level_5: + .addr ice_grenade_generator_routine_ptr_tbl-2 ; Grenade Generator (10) - CPU address $a382 + .addr ice_grenade_routine_ptr_tbl-2 ; Grenade (11) - CPU address $a3a9 + .addr tank_routine_ptr_tbl-2 ; Tank (12) - CPU address $a40c + .addr ice_separator_routine_ptr_tbl-2 ; Pipe Joint (13) - CPU address $a981 + .addr boss_ufo_routine_ptr_tbl-2 ; Alien Carrier (Guldaf) (14) - CPU address $a698 + .addr mini_ufo_routine_ptr_tbl-2 ; Flying Saucer (15) - CPU address $a8ea + .addr boss_ufo_bomb_routine_ptr_tbl-2 ; Drop Bomb (16) - CPU address $a96a + +; pointer table for level 6 enemy routines (#$5 * #$2 = #$a bytes) +; every entry is 2 bytes less than the actual label +enemy_routine_level_6: + .addr fire_beam_down_routine_ptr_tbl-2 ; Energy Beam - Down (10) - CPU address $a997 + .addr fire_beam_left_routine_ptr_tbl-2 ; Energy Beam - Left (11) - CPU address $aa4b + .addr fire_beam_right_routine_ptr_tbl-2 ; Energy Beam - Right (12) - CPU address $aa9a + .addr boss_giant_soldier_routine_ptr_tbl-2 ; Giant Boss Robot (13) - CPU address $ab7d + .addr boss_giant_projectile_routine_ptr_tbl-2 ; Spiked Disk Projectile (14) - CPU address $ae40 + +; pointer table for level 7 enemy routines (#$9 * #$2 = #$12 bytes) +; every entry is 2 bytes less than the actual label +enemy_routine_level_7: + .addr claw_routine_ptr_tbl-2 ; Mechanical Claw (10) - CPU address $aeb9 + .addr rising_spiked_wall_routine_ptr_tbl-2 ; Raising Spiked Wall (11) - CPU address $afc8 + .addr spiked_wall_routine_ptr_tbl-2 ; Spiked Wall (12) - CPU address $b0f9 + .addr mine_cart_generator_routine_ptr_tbl-2 ; Cart Generator (13) - CPU address $b11c + .addr moving_cart_routine_ptr_tbl-2 ; Cart - Moving (14) - CPU address $b178 + .addr immobile_cart_generator_routine_ptr_tbl-2 ; Cart - Immobile (15) - CPU address $b1d7 + .addr boss_door_routine_ptr_tbl-2 ; Armored Door with Siren (16) - CPU address $b201 + .addr boss_mortar_routine_ptr_tbl-2 ; Mortar Launcher (17) - CPU address $b272 + .addr boss_soldier_generator_routine_ptr_tbl-2 ; Boss Screen Soldier Generator (18) - CPU address $b32c + +; pointer table for level 8 enemy routines (#$6 * #$2 = #$c bytes) +; every entry is 2 bytes less than the actual label +enemy_routine_level_8: + .addr alien_guardian_routine_ptr_tbl-2 ; Alien Guardian (10) - CPU address $b422 + .addr alien_fetus_routine_ptr_tbl-2 ; Alien Fetus (11) - CPU address $b6e0 + .addr alien_mouth_routine_ptr_tbl-2 ; Alien Mouth (12) - CPU address $b7f4 + .addr white_blob_routine_ptr_tbl-2 ; White Sentient Blob (13) - CPU address $b866 + .addr alien_spider_routine_ptr_tbl-2 ; Alien Spider (14) - CPU address $ba29 + .addr alien_spider_spawn_routine_ptr_tbl-2 ; Spider Spawn (15) - CPU address $bbb5 + .addr boss_heart_routine_ptr_tbl-2 ; Heart (16) - CPU address $bc80 + +; enemy #$09 and #$0d, unused +enemy_routine_do_nothing_ptr_tbl: + .addr enemy_routine_do_nothing_00 ; Do Nothing - CPU address $e736 + +enemy_routine_do_nothing_00: + rts + +wall_core_routine_05: + jsr enemy_routine_init_explosion ; initialize explosion and play sound if specified in ENEMY_STATE_WIDTH + lda #$00 ; a = #$00 + sta ENEMY_FRAME,x ; set enemy animation frame number + rts + +; door plate defeated +; various enemies use this routine, advanced to by enemy_destroyed_routine_ptr_tbl +; plays boss destroyed sound, destroys all enemies, destroys boss and advances enemy routine +boss_defeated_routine: + jsr init_APU_channels + lda #$57 ; a = #$57 (sound_57) - boss destroyed + jsr level_boss_defeated ; play sound and initiate auto-move + jsr destroy_all_enemies ; boss defeated, destroy all enemies + +; initialize explosion and play sound if specified in ENEMY_STATE_WIDTH +; hide enemy and advance enemy routine +enemy_routine_init_explosion: + lda ENEMY_STATE_WIDTH,x + ora #$81 ; set boss destroyed bits x... ...x + bne explosion_sound_hide_enemy ; always branch + +; also used by ice grenades (11) +; split mortar collide with ground routine +; play explosion sound, update collision, hide sprite +mortar_shot_routine_03: + lda #$0d ; a = #$0d (score code 0, collision code d) + sta ENEMY_SCORE_COLLISION,x ; set collision code for enemy + lda ENEMY_STATE_WIDTH,x ; load enemy state width + and #$be ; strip bits 0 and 6 (player-enemy collision) + ora #$80 ; set bit 7 (allow bullets to travel through enemy) + +explosion_sound_hide_enemy: + sta ENEMY_STATE_WIDTH,x ; set updated ENEMY_STATE_WIDTH to specify explosion triggered + and #$02 ; keep bit 1 .... ..x. + beq @skip_explosion_sound ; skip explosion sound if bit 1 is set + lda #$19 ; a = #$19 (sound_19) + jsr play_sound ; play enemy destroyed sound + +@skip_explosion_sound: + lda ENEMY_SPRITE_ATTR,x ; load enemy sprite attributes + and #$fc ; strip sprite palette + ora #$06 ; override sprite code palette with palette 2 + sta ENEMY_SPRITE_ATTR,x ; update sprite attribute with new palette + lda ENEMY_SPRITES,x ; read enemy sprite code from CPU buffer + bne @continue ; if enemy sprite present, don't remove enemy + jmp remove_enemy ; remove enemy + +; hide enemy (show invisible sprite) before advancing to next routine +@continue: + lda #$ff ; a = #$ff + sta ENEMY_FRAME,x ; set enemy animation frame number to #$ff + lda #$01 ; a = #$01 + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer with invisible sprite (hide enemy) + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda #$01 ; a = #$01 + +; set ENEMY_ANIMATION_DELAY counter and advance to next routine +set_enemy_delay_adv_routine: + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + +; advance to next routine +; input +; * x - enemy slot index of the enemy routine to advance +advance_enemy_routine: + lda ENEMY_ROUTINE,x ; enemy routine index + beq set_sprite_0 ; if routine not set, exit + inc ENEMY_ROUTINE,x ; increment enemy routine index + rts + +; dead code, never called !(UNUSED) +bank_7_unused_label_01: + lda ENEMY_ROUTINE,x ; enemy routine index + beq set_sprite_0 + inc ENEMY_ROUTINE,x ; enemy routine index + lda #$24 ; a = #$24 (sound of explosion) + jmp play_sound ; play sound + +roller_routine_04: + lda #$03 ; explosion_type_03 + ldy #$02 ; show #$02 of the sprites of the explosion_type_03 sequence + bne show_explosion_a + +; generated indoor soldiers: indoor soldier, jumping soldier, grenade launcher, four soldiers +shared_enemy_routine_03: + lda #$02 ; explosion_type_02 + ldy #$03 ; show #$02 of the sprites of the explosion_type_02 sequence + bne show_explosion_a + +enemy_routine_explosion: + lda ENEMY_STATE_WIDTH,x ; load enemy state and width + ldy #$03 ; y = #$03 + and #$08 ; kit bit 3 + beq @continue ; branch if bit 3 wasn't set + iny ; increment y to #$04 + +@continue: + lda #$00 ; a = #$00 + +; enemy explosion +; input +; * a - explosion type, if not specified #$00, grab explosion type from ENEMY_STATE_WIDTH bit 3 +; * y - the number of animations in the explosion to animate, e.g. number of sprites to draw in sequence +show_explosion_a: + sty $08 ; y is either #$03 or #$04 + sta $09 ; a is #$00 for the first time through + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda ENEMY_ROUTINE,x ; load current enemy routine index + beq enemy_routine_explosion_exit ; exit if still on first enemy routine is #$00 + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne enemy_routine_explosion_exit ; timer hasn't elapsed, wait another frame + inc ENEMY_FRAME,x ; increment explosion animation sprite + ldy ENEMY_FRAME,x ; load explosion animation sprite + cpy $08 ; compare ENEMY_FRAME,x to $08 (max number of sprites) + bcs advance_enemy_routine ; advance to next enemy-specific routine if shown all sprites + iny ; haven't shown all sprites, increment to next sprite animation + cpy $08 ; re-compare ENEMY_FRAME,x to $08 (max number of sprites) + bcc @continue ; branch if not on last sprite + jsr disable_enemy_collision ; showing last sprite, prevent player enemy collision + +@continue: + lda #$0a ; a = #$0a (delay between explosion frames) + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + ldy $09 ; load explosion type, if not specified #$00, grab explosion type from ENEMY_STATE_WIDTH bit 3 + bne @continue2 + lda ENEMY_STATE_WIDTH,x ; load ENEMY_STATE_WIDTH to determine explosion type (bit 3) + and #$08 ; keep bit 3 (explosion type) + beq @continue2 ; set ENEMY_FRAME if explosion type is #$00 + iny ; if explosion type is #$01 increment y to match + +@continue2: + tya + asl + tay + lda explosion_type_ptr_tbl,y + sta $0a + lda explosion_type_ptr_tbl+1,y + sta $0b + ldy ENEMY_FRAME,x ; enemy animation frame number + lda ($0a),y + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + +enemy_routine_explosion_exit: + rts + +enemy_routine_remove_enemy: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + +; remove enemy +; CPU memory address $e809 +remove_enemy: + lda #$00 ; a = #$00 + sta ENEMY_ROUTINE,x ; enemy routine index + +set_sprite_0: + lda #$00 ; a = #$00 + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + rts + +; set tile sprite code to #$00 and advance routine +; level 1 boss wall plated door +; level 2 boss eye +; level 3 boss mouth +; level 4 boss gemini +shared_enemy_routine_clear_sprite: + jsr set_sprite_0 ; set tile sprite code to 0 + jmp advance_enemy_routine ; advance to next routine + +; set enemy routine index to a, unless index is #$0 +; remember enemy routines are off by one, so setting ENEMY_ROUTINE to #$03, results in the 2nd routine being run +; ex: for exploding bridge, setting ENEMY_ROUTINE to #$02 causes exploding_bridge_routine_01 to run the next frame +set_enemy_routine_to_a: + ldy ENEMY_ROUTINE,x ; enemy routine index + beq set_sprite_0 + sta ENEMY_ROUTINE,x ; enemy routine index + rts + +; pointer table for explosion type sprites (#$4 * #$2 = #$8 bytes) +explosion_type_ptr_tbl: + .addr explosion_type_00 ; CPU address $e82b + .addr explosion_type_01 ; CPU address $e82e + .addr explosion_type_02 ; CPU address $e832 + .addr explosion_type_03 ; CPU address $e835 + +; tables for explosion sprite codes (#$4 * #$3 = #$c bytes) +; larger circular ring explosion +; sprite_38, sprite_39, sprite_3a +explosion_type_00: + .byte $38,$39,$3a + +; cloudy explosion +; sprite_37, sprite_35, sprite_36, sprite_37 +explosion_type_01: + .byte $37,$35,$36,$37 + +; small ring explosion - +; used for generated indoor soldiers: indoor soldier, jumping soldier, grenade launcher, four soldiers +; sprite_9d, sprite_9e, sprite_9f +explosion_type_02: + .byte $9d,$9e,$9f + +; short cloudy explosion (used for rollers) +; sprite_36, sprite_37 +explosion_type_03: + .byte $36,$37 + +; apply velocities and scrolling adjust +; update enemy position +; remove if off screen +update_enemy_pos: + lda LEVEL_SCROLLING_TYPE ; 0 = horizontal, indoor/base; 1 = vertical + beq update_enemy_x_pos_rem_if_off_screen ; branch for non-vertical levels + jsr update_enemy_y_pos_with_scroll ; vertical level; apply y velocity and update y position + cmp #$e8 ; compare enemy Y position to #$e8 + bcs jmp_remove_enemy ; remove enemy if Y position is >= #$e8 (fallen off bottom of screen) + +; apply X velocity to enemy X velocity +; remove enemy if enemy's resulting X position is less than 8 (off screen to left) +update_enemy_x_pos_rem_off_screen: + jsr update_enemy_x_pos ; apply velocity to X position + cmp #$08 + bcc jmp_remove_enemy ; remove enemy if resulting X position is less than #$08 + +apply_vel_exit: + rts + +; horizontal level, or indoor/base level +update_enemy_x_pos_rem_if_off_screen: + jsr update_enemy_x_pos_with_scroll + cmp #$08 ; compare enemy X position to #$08 + bcc jmp_remove_enemy ; if X position < #$08, remove enemy (fallen off from left of screen) + +; apply Y velocity to enemy Y position +; remove enemy if enemy's resulting Y position is greater than or equal to #$e8 (off screen to bottom) +set_enemy_y_vel_rem_off_screen: + jsr update_enemy_y_pos ; apply velocity to Y position + cmp #$e8 + bcc apply_vel_exit ; remove enemy if resulting X position is greater than or equal to #$e8 (off screen to bottom) + +jmp_remove_enemy: + jmp remove_enemy ; remove enemy + +; sets the weapon item velocity for outdoor levels +set_outdoor_weapon_item_vel: + lda LEVEL_SCROLLING_TYPE ; 0 = horizontal, indoor/base; 1 = vertical + beq @set_weapon_item_velocity ; branch if horizontal or indoor/base level + ldy #$00 ; vertical level + lda ENEMY_Y_VELOCITY_FAST,x ; load Y velocity fast byte + clc ; clear carry in preparation for addition + adc FRAME_SCROLL ; add FRAME_SCROLL to ENEMY_Y_VELOCITY_FAST + jsr set_weapon_item_y_vel_enemy_frame ; apply y velocity to y position, + beq jmp_remove_enemy ; remove weapon item if ENEMY_FRAME is #$01 + jmp update_enemy_x_pos_rem_off_screen ; add velocity to enemy X pos; remove enemy if X position < #$08 (off screen to left) + +; horizontal levels +@set_weapon_item_velocity: + jsr update_enemy_x_pos_with_scroll ; update x position accounting for whether frame is scrolling + cmp #$08 ; compare the x position to the left side of the screen + bcc remove_enemy_far ; remove weapon item if too far to the left (scrolled off screen) + ldy #$00 ; y = #$00 + lda ENEMY_Y_VELOCITY_FAST,x ; load fast velocity so it is applied in next line + jsr set_weapon_item_y_vel_enemy_frame ; apply y velocity to y position, don't adjust ENEMY_FRAME + bne scroll_enemy_pos_exit ; branch if weapon item, isn't off screen to bottom, otherwise remove + +remove_enemy_far: + jmp remove_enemy ; remove enemy + +; adds y to ENEMY_FRAME and adjusts Y position for weapon item by a plus +; ENEMY_FRAME: #$ff is explosion, #$00 is weapon item +; input +; * x - current enemy offset +; * a - how much to move current enemy's Y position +; * y - number of frames to advance ENEMY_FRAME (set to -1 when picked up by player) +; output +; * compare ENEMY_FRAME to #$01, determines if y position overflow (off screen) +set_weapon_item_y_vel_enemy_frame: + bpl @set_y_vel_enemy_frame + dey ; subtract 1 from ENEMY_FRAME advance since enemy is moving in positive Y (accommodate overflow) + +@set_y_vel_enemy_frame: + sty $01 + sta $00 + lda ENEMY_Y_VEL_ACCUM,x ; load current accumulated ENEMY_Y_VELOCITY_FRACT total + clc ; clear carry in preparation for addition + adc ENEMY_Y_VELOCITY_FRACT,x ; a = ENEMY_Y_VELOCITY_FRACT + ENEMY_Y_VEL_ACCUM + sta ENEMY_Y_VEL_ACCUM,x ; add another ENEMY_X_VELOCITY_FRACT to accumulator + lda ENEMY_Y_POS,x ; load enemy y position on screen + adc $00 ; add $00 units to Y position + ; along with an additional 1 unit if ENEMY_Y_VEL_ACCUM rolled over + sta ENEMY_Y_POS,x ; set new Y position + lda ENEMY_FRAME,x ; load enemy animation frame number + adc $01 ; add ENEMY_FRAME to $01, carry could be set from adc $00 above + sta ENEMY_FRAME,x ; set new value + cmp #$01 ; if any carry from previous addition, then y position is > #$ff, so remove weapon item, it's off screen + rts + +; if the screen is scrolling add that amount to the enemy position +add_scroll_to_enemy_pos: + lda LEVEL_SCROLLING_TYPE ; 0 = horizontal, indoor/base; 1 = vertical + beq add_horizontal_scroll + lda ENEMY_Y_POS,x ; vertical level, load enemy y position on screen + clc ; clear carry in preparation for addition + adc FRAME_SCROLL ; how much to scroll the screen (#00 - no scroll) + sta ENEMY_Y_POS,x ; enemy y position on screen + cmp #$e8 + bcs remove_enemy_far ; remove enemy if far above screen for vertical level + +scroll_enemy_pos_exit: + rts + +; add X scrolling to enemy X position +; horizontal scrolling level +add_horizontal_scroll: + lda ENEMY_X_POS,x ; load enemy x position on screen + sec ; set carry flag in preparation for subtraction + sbc FRAME_SCROLL ; subtract the frame scroll amount + sta ENEMY_X_POS,x ; set new enemy x position + cmp #$08 ; remove enemy if too far to the left + bcc remove_enemy_far ; remove enemy if leaving the left of the screen + rts + +; dead code, never called !(UNUSED) +bank_7_unused_label_02: + jsr update_enemy_x_pos_rem_off_screen ; add velocity to enemy X position; remove enemy if X position < #$08 (off screen to left) + jmp set_enemy_y_vel_rem_off_screen ; add velocity to enemy Y position; remove enemy if Y position >= #$e8 (off screen to bottom) + +; set x/y velocities to zero +set_enemy_velocity_to_0: + jsr set_enemy_x_velocity_to_0 + +; set y velocity to zero +set_enemy_y_velocity_to_0: + lda #$00 ; a = #$00 + sta ENEMY_Y_VELOCITY_FRACT,x + sta ENEMY_Y_VELOCITY_FAST,x + rts + +; set x velocity to zero +set_enemy_x_velocity_to_0: + lda #$00 ; a = #$00 + sta ENEMY_X_VELOCITY_FRACT,x + sta ENEMY_X_VELOCITY_FAST,x + rts + +update_enemy_y_pos_with_scroll: + jsr update_enemy_y_pos ; apply velocity to y position + clc ; clear carry in preparation for addition + adc FRAME_SCROLL ; how much to scroll the screen (#00 - no scroll) + sta ENEMY_Y_POS,x ; enemy y position on screen + rts + +; apply Y velocity and update enemy's Y position +; output +; * a - ENEMY_Y_POS +update_enemy_y_pos: + lda ENEMY_Y_VEL_ACCUM,x ; load current accumulated ENEMY_Y_VELOCITY_FRACT total + clc ; clear carry in preparation for addition + adc ENEMY_Y_VELOCITY_FRACT,x ; a = ENEMY_Y_VELOCITY_FRACT + ENEMY_Y_VEL_ACCUM + sta ENEMY_Y_VEL_ACCUM,x ; add another ENEMY_X_VELOCITY_FRACT to accumulator + lda ENEMY_Y_POS,x + adc ENEMY_Y_VELOCITY_FAST,x ; add ENEMY_Y_VELOCITY_FAST units to Y position + ; along with an additional 1 unit if ENEMY_Y_VEL_ACCUM rolled over + sta ENEMY_Y_POS,x ; set new Y position + rts + +; updates enemy position based on velocity and adjusts when frame is scrolling +; input +; * x - enemy to adjust x position +; output +; * a - updated enemy x position +update_enemy_x_pos_with_scroll: + jsr update_enemy_x_pos ; apply velocity to x position + sec ; set carry flag in preparation for subtraction + sbc FRAME_SCROLL ; how much to scroll the screen (#00 - no scroll) + sta ENEMY_X_POS,x ; set enemy x position on screen + rts + +; apply X velocity and update enemy's X position +; output +; * a - ENEMY_X_POS +update_enemy_x_pos: + lda ENEMY_X_VEL_ACCUM,x ; load current accumulated ENEMY_X_VELOCITY_FRACT total + clc ; clear carry in preparation for addition + adc ENEMY_X_VELOCITY_FRACT,x ; a = ENEMY_X_VELOCITY_FRACT + ENEMY_X_VEL_ACCUM + sta ENEMY_X_VEL_ACCUM,x ; add another ENEMY_X_VELOCITY_FRACT to accumulator + lda ENEMY_X_POS,x ; load current enemy x position + adc ENEMY_X_VELOCITY_FAST,x ; add ENEMY_X_VELOCITY_FAST units to X position + ; along with an additional 1 unit if ENEMY_X_VEL_ACCUM rolled over + sta ENEMY_X_POS,x ; set new X position + rts + +; reverse x direction +reverse_enemy_x_direction: + lda #$00 ; a = #$00 + sec ; set carry flag in preparation for subtraction + sbc ENEMY_X_VELOCITY_FRACT,x ; #$00 - ENEMY_X_VELOCITY_FRACT,x + sta ENEMY_X_VELOCITY_FRACT,x + lda #$00 ; a = #$00 + sbc ENEMY_X_VELOCITY_FAST,x ; #$00 - ENEMY_X_VELOCITY_FAST,x + sta ENEMY_X_VELOCITY_FAST,x + rts + +; reverse y direction +; dead code, never called !(UNUSED) +bank_7_unused_label_03: + lda #$00 ; a = #$00 + sec ; set carry flag in preparation for subtraction + sbc ENEMY_Y_VELOCITY_FRACT,x ; #$00 - ENEMY_Y_VELOCITY_FRACT,x + sta ENEMY_Y_VELOCITY_FRACT,x + lda #$00 ; a = #$00 + sbc ENEMY_Y_VELOCITY_FAST,x ; #$00 - ENEMY_Y_VELOCITY_FAST,x + sta ENEMY_Y_VELOCITY_FAST,x + rts + +; get score of current enemy according to score code +; adds score amount to player score +; sets enemy destroyed routine +add_enemy_score_set_enemy_routine: + lda ENEMY_SCORE_COLLISION,x ; pull score bits from byte + lsr + lsr + lsr + lsr + tay + cpy #$0a ; score type a (500,000 points), not in score_codes_tbl since 2 bytes + bne @add_y_code_to_player_score + lda #$88 ; custom score code #$0a - set a = #$88 + sta $00 ; store in score to add low byte + lda #$13 ; a = #$13 (#$1388 = 5000 decimal = 500,000 points) + sta $01 ; store in score to add high byte + ldy $17 ; load current player number (0 or 1) + jsr add_player_score ; add $00 and $01 to player score, get extra life, check if high score + jmp @continue + +@add_y_code_to_player_score: + lda score_codes_tbl,y + beq @continue + sta $00 + ldy $17 ; load current player number (0 or 1) + jsr add_player_low_score ; add enemy points ($00) to player score in memory, see if extra life and high score + +@continue: + ldx ENEMY_CURRENT_SLOT + lda ENEMY_SCORE_COLLISION,x ; load to remove score code + and #$0f ; keep bits .... xxxx (collision code) + sta ENEMY_SCORE_COLLISION,x ; set score component to #$0 + +; set enemy routine to their appropriate destroyed routine +set_destroyed_enemy_routine: + lda ENEMY_TYPE,x ; load current enemy type + cmp #$10 ; see if common enemy type + ldy #$10 ; y = #$10 (common enemies) + bcc @set_destroyed_routine ; if current enemy type is < #$10, use common enemy enemy logic + lda CURRENT_LEVEL ; level-specific enemy type, load current level + asl ; double since each entry in enemy_destroyed_routine_ptr_tbl is #$02 bytes + tay ; transfer offset to y + +@set_destroyed_routine: + lda enemy_destroyed_routine_ptr_tbl,y ; load low byte of the routine pointer + sta $08 ; store in $08 + lda enemy_destroyed_routine_ptr_tbl+1,y ; load high byte of the routine pointer + sta $09 ; store in $09 + lda ENEMY_TYPE,x ; load current enemy type + lsr ; push enemy lsb to the carry flag (odd or even) + ; and half the value since each byte is #$02 enemy types + tay ; transfer enemy type to y + lda ($08),y ; load the byte specified by the table (enemy_destroyed_routine_XX) + bcs @set_routine ; if enemy type loaded is odd, bits 0-3 is the routine number to set + lsr ; enemy type loaded was even, look at high 4 nibble + lsr + lsr + lsr + +@set_routine: + and #$0f ; keep bits .... xxxx + cmp ENEMY_ROUTINE,x ; compare against current enemy routine being executed for the enemy + bcc @exit ; enemy destroyed routine is less than current enemy routine index, exit + sta ENEMY_ROUTINE,x ; update enemy routine to new destroyed index + +@exit: + rts + +; table for score codes (#$a bytes) +; type #$0a is hard-coded and gives 500,000 points +score_codes_tbl: + .byte $00 ; type 0: 0 points + .byte $01 ; type 1: 100 points + .byte $03 ; type 2: 300 points + .byte $05 ; type 3: 500 points + .byte $0a ; type 4: 1,000 points + .byte $14 ; type 5: 2,000 points + .byte $1e ; type 6: 3,000 points + .byte $32 ; type 7: 5,000 points + .byte $64 ; type 8: 10,000 points + .byte $96 ; type 9: 15,000 points + +; pointer table for which enemy routine to execute when destroyed (#$9 * #$2 = #$12 bytes) +enemy_destroyed_routine_ptr_tbl: + .addr enemy_destroyed_routine_00 ; CPU address $e9bf - Level 1 + .addr enemy_destroyed_routine_01 ; CPU address $e9c1 - Level 2 + .addr enemy_destroyed_routine_02 ; CPU address $e9ca - Level 3 + .addr enemy_destroyed_routine_01 ; CPU address $e9c1 - Level 4 + .addr enemy_destroyed_routine_03 ; CPU address $e9cd - Level 5 + .addr enemy_destroyed_routine_04 ; CPU address $e9d1 - Level 6 + .addr enemy_destroyed_routine_05 ; CPU address $e9d4 - Level 7 + .addr enemy_destroyed_routine_06 ; CPU address $e9d9 - Level 8 + .addr enemy_destroyed_routine_00 ; CPU address $e9bf - common enemies (enemy type < #$10) + +; table for enemy routine index when destroyed +; also used for falcon item or when boss is destroyed to destroy all enemies +; #$4 bits per enemy, 2 enemies per byte +; if enemy type is odd, then smaller nibble is used +; if enemy type byte is even, then high nibble is used +; keep in mind that all routines are offset by -2 +; * e.g. #$03 for the boss bomb turret would be routine boss_bomb_turret_routine_02 +enemy_destroyed_routine_00: + .byte $04 ; weapon item (00) / enemy bullet (01) + .byte $53 ; pill box sensor (02) / weapon zeppelin (03) + +enemy_destroyed_routine_01: + .byte $75 ; rotating gun (04) / running man (05) + .byte $56 ; rifle man (06) / red turret (07) + .byte $50 ; wall cannon (08) / unused + .byte $44 ; wall plating (0a) / mortar shot (0b) + .byte $44 ; scuba diver (0c) / unused + .byte $43 ; turret man (0e) / turret man bullet + .byte $33 ; boss bomb turret (10) / door plate with siren (11) + .byte $20 ; exploding bridge (12) + .byte $43 ; boss eye (10) / rollers (11) + +enemy_destroyed_routine_02: + .byte $45 ; grenades (12) / wall turret (13) + .byte $53 ; core (14) / indoor soldier (15) + .byte $33 ; jumping guy (16) / seeking guy (17) + +enemy_destroyed_routine_03: + .byte $43 ; group of 4 (18) / indoor soldier generator (19) + .byte $33 ; rollers generator (1a) / sphere projectile (1b) + .byte $43 ; boss gemini (1c) / spinning bubbles projectile (1d) + .byte $54 ; blue jumping guy (1e) / red shooting guy (1f) + +enemy_destroyed_routine_04: + .byte $30 ; red/blue guys generator (20) + .byte $22 ; rock platform (10) / moving flame (11) + .byte $24 ; falling rock generator (rock cave) (12) / falling rock (13) + +enemy_destroyed_routine_05: + .byte $65 ; level 3 boss mouth (14) / level 3 dragon arm orb (15) + .byte $33 ; ice grenade generator (10) / ice grenade (11) + .byte $50 ; tank (12) / pipe joint (13) + .byte $a5 ; boss ufo (14) / flying saucer (15) + .byte $20 ; bomb drop (16) + +enemy_destroyed_routine_06: + .byte $00 ; fire beam down (10) / fire beam left (11) + .byte $07 ; fire beam right (12) / giant boss robot (13) + .byte $30 ; spiked disk projectile (14) + .byte $05 ; mechanical claw (10) / raising spiked wall (11) + .byte $30 ; spiked wall (12) / cart generator (13) + .byte $44 ; cart moving (14) / cart immobile (15) + .byte $35 ; armored door (16) / mortar launcher (17) + .byte $50 ; enemy generator (18) + .byte $43 ; alien guardian (10) / alien fetus (11) + .byte $34 ; alien mouth (12) / white sentient blob (13) + .byte $63 ; alien spider (14) / spider spawn (15) + .byte $40 ; heart (16) + +; falcon weapon - Destroy All Enemies (with exceptions like for pill box sensor, weapon zeppelin) +; also used when boss is defeated to remove all enemies +destroy_all_enemies: + stx $10 ; store value of x register in $10 temporarily + ldx #$0f ; x = #$0f + +@enemy_loop: + lda ENEMY_ROUTINE,x ; load the current enemy routine pointer + beq @continue ; skip to next enemy when no routine set for enemy + lda ENEMY_SPRITES,x ; load enemy tile sprite code + beq @continue ; skip to next enemy when no sprite set for enemy + lda ENEMY_TYPE,x ; load current enemy type + cmp #$02 ; see if pill box sensor + beq @continue ; skip to next enemy when enemy is pill box sensor + cmp #$03 ; see if flying capsule (weapon zeppelin) + beq @continue ; skip to next enemy when enemy is flying capsule + lda ENEMY_HP,x ; load enemy hp + cmp #$f0 ; f0 = no hit + beq @continue ; skip to next enemy when enemy hp is #$f0 + jsr set_destroyed_enemy_routine ; regular enemy, set it to use its destroy routine + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + ora #$80 ; set bits bit 7 to flag enemy as destroyed + sta ENEMY_ATTRIBUTES,x + +@continue: + dex ; go to next enemy (enemy logic starts high and goes to #$00) + bpl @enemy_loop + ldx $10 ; restore x attribute from before destroy_all_enemies call + rts + +remove_all_enemies: + stx $10 ; store value of x register in $10 temporarily + ldx #$0f ; x = #$0f (total number of possible enemies) + +@loop: + jsr remove_enemy ; remove enemy + dex + bpl @loop + ldx $10 ; restore x attribute from before remove_all_enemies call + rts + +; sets background collision code to #$00 (empty) for a single super-tile, or #$10 pattern table tiles (4 2x2 tiles) +; at PPU address $12 (low) $13 (high) +; input +; * PPU nametable collision address: $12 (low) and $13 (high) +clear_supertile_bg_collision: + lda #$00 ; a = #$00 + +; updates background collision code for a single super-tile, or #$10 pattern table tiles (4 2x2 tiles) +; at PPU address $12 (low) $13 (high) +; input +; * a - the bg collision code for the entire super-tile (4 2x2 tiles) [#$00-#$0f] +; * PPU nametable collision address: $12 (low) and $13 (high) +set_supertile_bg_collision: + tay + +; updates background collision codes for a single super-tile, or #$10 pattern table tiles (4 2x2 tiles) +; at PPU address $12 (low) $13 (high) +; input +; * a - left two collision tiles [#$00-#$0f] +; * bits 0 and 1 - the top-left collision code (1 2x2 nametable tile) +; * bits 2 and 3 - the bottom-left collision code (1 2x2 nametable tile) +; * a - right two collision tiles [#$00-#$0f] +; * bits 0 and 1 - the top-right collision code (1 2x2 nametable tile) +; * bits 2 and 3 - the bottom-right collision code (1 2x2 nametable tile) +; * PPU nametable collision address: $12 (low) and $13 (high) +set_supertile_bg_collisions: + sta $11 ; save first 2x2 bg collision code to $11 + sty $14 ; save second 2x2 bg collision code to $11 + ; start calculation BG_COLLISION_DATA offset from PPU address, e.g. $2190 goes to #$1a + lda $12 ; load low byte of PPU nametable address + lsr ; shift value to the right + and #$03 ; get bits 1 and 2 of $12 before shifting + sta $00 ; store bitmask offset in $00 [#$00-#$03] + lda $13 ; load high byte of PPU nametable address + and #$07 ; strip leading #$02 from nametable address high byte + ; not used when calculating BG_COLLISION_DATA offset + asl $12 + rol + asl $12 + rol + asl $12 ; ignore bit 5 of address low byte + ; this ensures each every 2nd nametable row has same bg collision tile offset as nametable row above + asl $12 + rol + asl $12 + rol + sta $04 ; set the in memory bg collision byte offset (BG_COLLISION_DATA) + lda #$02 ; set number of times to call @set_half_supertile_bg_collisions to #$02 + ; each call @set_half_supertile_bg_collisions will update 2 bg collision tiles (2 2x2 nametable tiles) + sta $01 ; store @set_half_supertile_bg_collisions call counter + +@loop: + lda $00 ; load calculated bitmask index + sta $02 ; set bitmask index (0 = 0011 1111, 1 = 1100 1111, 2 = 1111 0011, 3 = 1111 1100) + jsr @set_half_supertile_bg_collisions ; set the bg collision for the #$08 tiles on one (horizontal) half of the super-tile + lsr $11 + lsr $11 ; shift the bottom-left collision code into the lower bits for use + lsr $14 + lsr $14 ; shift the bottom right collision code into the lower bits for use + lda $04 ; load in memory bg collision byte offset (BG_COLLISION_DATA) + clc ; clear carry in preparation for addition + adc #$04 ; add #$04 to the current calculated bg collision byte offset (BG_COLLISION_DATA) + sta $04 ; move one bg collision row down to next BG_COLLISION_DATA collision byte offset + dec $01 ; decrement @set_half_supertile_bg_collisions counter + bne @loop + rts + +; sets #$08 nametable tile collision code (#$02 bg collision tiles) +; for either the top or bottom horizontal half of the super-tile +@set_half_supertile_bg_collisions: + lda $04 ; load in memory bg collision byte offset (BG_COLLISION_DATA) + sta $07 ; set in memory bg collision byte offset (BG_COLLISION_DATA) + lda #$01 ; a = #$01 + sta $06 ; set to update #$02 bg collision tiles (2 2x2 nametable tiles) + +; input +; * $02 - bitmask index (0 = 0011 1111, 1 = 1100 1111, 2 = 1111 0011, 3 = 1111 1100) +@set_quadrant_bg_collision: + ldy $02 ; load bitmask index (0 = 0011 1111, 1 = 1100 1111, 2 = 1111 0011, 3 = 1111 1100) + lda bg_collision_bit_mask_tbl,y ; load bitmask value, these are the bits to remain unchanged + sta $05 ; store background collision mask in $05 + lda $06 ; load one less than the number of bg collision tiles to update, #$01 or #$00 + lsr ; shift bit + lda $11 ; updating + bcs @continue + lda $14 + +@continue: + and #$03 ; keep bits .... ..xx + +; determine location of the #$02 collision bits within the collision byte +; by counting up to #$04 from the bitmask index shifting a each time twice +@find_bit_offset: + iny ; increment bitmask index + cpy #$04 ; see if last index + bcs @set_collision ; branch if last index to continue + asl + asl ; shift #$02 collision bits to the next portion of the bg collision byte + jmp @find_bit_offset ; jump to see if new bit position is correct + +@set_collision: + sta $03 ; store the background section's collision code, all other bits are 0 + ldy $07 ; load in memory bg collision byte offset (BG_COLLISION_DATA) + lda BG_COLLISION_DATA,y ; load the current byte (a byte specifies bg collision for #$08 pattern table tiles) + and $05 ; keep all other background collision values except the #$02 bits to update + ora $03 ; merge in the new background collision value + sta BG_COLLISION_DATA,y ; update background collision for the #$02 pattern table tiles + inc $02 ; increment bitmask index + lda $02 ; load new bitmask index (0 = 0011 1111, 1 = 1100 1111, 2 = 1111 0011, 3 = 1111 1100) + and #$03 ; keep bits .... ..xx + sta $02 ; set new value + bne @check_next_loop ; branch if still have a bg collision code to update + inc $07 ; increment bg collision byte offset (BG_COLLISION_DATA) + +@check_next_loop: + dec $06 ; decrement number of bg collision tiles to update + bpl @set_quadrant_bg_collision ; branch if more bg collision tiles to update the next quadrant of the super-tile + rts + +; table for bit masks (#$4 bytes) +; each 2 bits encode 2 pattern table tiles (1/4 of a super-tile's collision information) +; 0011 1111 +; 1100 1111 +; 1111 0011 +; 1111 1100 +bg_collision_bit_mask_tbl: + .byte $3f,$cf,$f3,$fc + +; create explosion #$89 at location ($09, $08) +create_explosion_89: + lda #$89 ; a = #$89 + sta $0a ; set explosion type to #$89 + lda #$09 ; set ENEMY_ROUTINE to #$09 (enemy_routine_init_explosion) + bne create_explosion_sequence ; always branch to create explosion animation, by using the weapon box's enemy routines + +; creates 2 sets of explosion #$89 at location ($09, $08) +; input +; * $09 - x location +; * $08 - y location +create_two_explosion_89: + lda #$89 ; set ENEMY_STATE_WIDTH to #$89 (explosion type) + bne create_explosion_a ; always jump + +; create new pill box sensor set to routine enemy_routine_init_explosion +; pill box sensor isn't important, it's just an enemy that has the enemy_routine_init_explosion routine sequence +; input +; * $09 - x position to create enemy at +; * $08 - y position to create enemy at +create_enemy_for_explosion: + lda #$08 ; set ENEMY_STATE_WIDTH to #$08 (explosion type) + +; input +; * $09 - x position to create enemy at +; * $08 - y position to create enemy at +create_explosion_a: + sta $0a ; set explosion type + lda #$06 ; a = #$06 (enemy_routine_init_explosion, 2 rounds of explosions) + +; input +; * $09 - x position to create enemy at +; * $08 - y position to create enemy at +create_explosion_sequence: + sta $0b ; set weapon box enemy routine + stx $10 ; save x to be restored after function call + jsr find_next_enemy_slot ; find next available enemy slot, put result in x register + bne @exit ; branch if no enemy slot was found + lda #$02 ; a = #$02, pill box sensor (weapon box) + ; used for enemy_routine_init_explosion, enemy_routine_explosion, enemy_routine_remove_enemy sequence + sta ENEMY_TYPE,x ; set current enemy type to pill box sensor (weapon box) + jsr initialize_enemy ; initialize enemy attributes + lda $0b ; load enemy routine (#$06 for 2 explosions or #$09 for one explosion) + sta ENEMY_ROUTINE,x ; enemy routine index + lda #$01 ; a = #$01 (blank sprite) + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + lda $0a ; load explosion type + sta ENEMY_STATE_WIDTH,x ; set explosion type + lda $08 ; load y position of explosions + sta ENEMY_Y_POS,x ; set explosion y position on screen + lda $09 ; load x position of explosions + sta ENEMY_X_POS,x ; set explosion x position on screen + +@exit: + ldx $10 ; restore x from before create_explosion_sequence call + rts + +; level-boss defeated +; * play sound code a +; * set auto-move delay to #$ff +; * set BOSS_DEFEATED_FLAG level boss defeated flag +; input +; * a - play sound code +level_boss_defeated: + jsr play_sound ; play sound a + lda #$ff + sta DELAY_TIME_LOW_BYTE ; set auto-move delay to #$ff + lda #$01 + sta BOSS_DEFEATED_FLAG ; set BOSS_DEFEATED_FLAG to true + rts + +; set delay to a and remove enemy +set_delay_remove_enemy: + sta DELAY_TIME_LOW_BYTE ; various delays (low byte) + lda #$00 ; a = #$00 + sta DELAY_TIME_HIGH_BYTE ; various delays (high byte) + jmp remove_enemy ; remove enemy + lda #$01 ; dead code !(UNUSED) + bne enemy_state_width_or_a ; dead code !(UNUSED) + +; set bit 7 of ENEMY_STATE_WIDTH,x +; bit 7 set to allow bullets to travel through enemy, e.g. boss mouth +disable_bullet_enemy_collision: + lda #$80 ; a = #$80 + bne enemy_state_width_or_a + +; prevent player enemy collision check and allow bullets to pass through enemy +; set bits 0 and 7 of ENEMY_STATE_WIDTH,x +; bit 7 set to allow bullets to travel through enemy, e.g. weapon item +; bit 0 - #$00 test player-enemy collision, #$01 means to skip player-enemy collision test +disable_enemy_collision: + lda #$81 ; x... ...x (msb and lsb set) + +enemy_state_width_or_a: + ora ENEMY_STATE_WIDTH,x ; set msb and lsb bits to 1 + bne set_enemy_state_width_to_a + +; enable enemy-player collision checking, e.g. fire beam collision with player +; bit 0 - #$00 test player-enemy collision, #$01 means to skip player-enemy collision test +enable_enemy_player_collision_check: + lda #$fe ; a = #$fe + bne enemy_state_width_and_a ; always branch + +; allow bullets to collide with enemy, some enemies have bullets pass through, e.g. weapon item +enable_bullet_enemy_collision: + lda #$7f ; a = #$7f + bne enemy_state_width_and_a + +; enable bullet-enemy collision and player-enemy collision checks +enable_enemy_collision: + lda #$7e ; a = #$7e + +enemy_state_width_and_a: + and ENEMY_STATE_WIDTH,x + +set_enemy_state_width_to_a: + sta ENEMY_STATE_WIDTH,x + rts + +; add a to enemy y position on screen +add_a_to_enemy_y_pos: + clc ; clear carry in preparation for addition + adc ENEMY_Y_POS,x + sta ENEMY_Y_POS,x ; enemy y position on screen + rts + +; add a to enemy x position on screen +add_a_to_enemy_x_pos: + clc ; clear carry in preparation for addition + adc ENEMY_X_POS,x ; add to enemy x position on screen + sta ENEMY_X_POS,x ; set enemy x position on screen + rts + +; set memory $08 and $09 to enemy X's Y and X position respectively +; output +; * $08 - enemy y position +; * $09 - enemy x position +; * a - enemy y position +; * y - enemy y position +set_08_09_to_enemy_pos: + lda #$00 ; a = #$00 + tay + +; adds register a to the enemy x position, stores result in $09 +; adds register y to the enemy y position, stores result in $08 +; input +; * a - distance to add to x position +; * y - distance to add to y position +; output +; * $08 - y to the enemy y position +; * $09 - a to the enemy x position +add_with_enemy_pos: + clc ; clear carry in preparation for addition + adc ENEMY_X_POS,x ; add a to enemy x position on screen + sta $09 ; store result in $09 + tya + clc ; clear carry in preparation for addition + adc ENEMY_Y_POS,x ; add a to enemy y position on screen + sta $08 ; store value in $08 + rts + +; add .06 to y velocity +add_10_to_enemy_y_fract_vel: + lda #$10 ; a = #$10 + +; add a to enemy y fractional velocity, incorporating carry into fast y velocity +add_a_to_enemy_y_fract_vel: + clc ; clear carry in preparation for addition + adc ENEMY_Y_VELOCITY_FRACT,x ; add a to enemy y fractional velocity + sta ENEMY_Y_VELOCITY_FRACT,x ; store updated result in enemy y fractional velocity + lda ENEMY_Y_VELOCITY_FAST,x ; load the y fast velocity + adc #$00 ; add any carry from adding to fractional velocity + sta ENEMY_Y_VELOCITY_FAST,x ; store updated fast velocity if any carry occurred + rts + +; generate enemy at relative position 0,0 to current enemy +; input +; * a - enemy type +; output +; * a - #$01 when no enemy created, #$00 when enemy created +; * y - created enemy slot number +generate_enemy_a: + sta $0a + lda #$00 ; a = #$00 + tay + +; generate enemy type $0a at relative position a,y +; input +; * $0a - enemy type +; * a - x position +; * y - y position +; output +; * a - #$01 when no enemy created, #$00 when enemy created +; * y - created enemy slot number +generate_enemy_at_pos: + sty $08 + sta $09 + txa + tay + jsr find_next_enemy_slot ; find next available enemy slot, put result in x register + bne @exit ; no enemy slot, exit (find_next_enemy_slot sets zero flag when found, clears when not found) + lda $0a ; load enemy type + sta ENEMY_TYPE,x ; set enemy type + jsr initialize_enemy + lda ENEMY_Y_POS,y ; enemy y position on screen + clc ; clear carry in preparation for addition + adc $08 + sta ENEMY_Y_POS,x + lda ENEMY_X_POS,y ; load enemy x position on screen + clc ; clear carry in preparation for addition + adc $09 + sta ENEMY_X_POS,x ; set enemy x position on screen + txa + tay + ldx ENEMY_CURRENT_SLOT + lda #$00 ; a = #$00 + rts + +@exit: + ldx ENEMY_CURRENT_SLOT + lda #$01 ; a = #$01 + rts + +; add #$04 to the enemy y position accounting for VERTICAL_SCROLL overflow on vertical levels +add_4_to_enemy_y_pos: + lda #$04 + +; add a to the enemy y position accounting for VERTICAL_SCROLL overflow on vertical levels +add_a_with_vert_scroll_to_enemy_y_pos: + sta $01 + lda VERTICAL_SCROLL ; vertical scroll offset + and #$0f ; keep bits .... xxxx + ora #$f0 ; set bits xxxx .... + sta $00 + clc ; clear carry in preparation for addition + adc ENEMY_Y_POS,x ; add vertical scroll to enemy Y position + and #$f0 ; keep bits xxxx .... + sec ; set carry flag in preparation for subtraction + sbc $00 + clc ; clear carry in preparation for addition + adc $01 + sta ENEMY_Y_POS,x ; enemy y position on screen + rts + +; draw the nametable tiles from level_xx_tile_animation (a) at the enemy position +; sets animation delay for enemy to #$01 if successful +; input +; * a - offset into level-specific tile_animation table, e.g. level_2_4_tile_animation +; does not update palette, i.e. leave existing palette +; output +; * carry flag - clear when successful, set when CPU_GRAPHICS_BUFFER is full +update_nametable_tiles_set_delay: + jsr update_enemy_nametable_tiles_no_palette ; draw the nametable tiles from level_2_4_tile_animation (a) at the enemy position + jmp update_nametable_set_anim_delay_exit + +; draw super-tile a at position (ENEMY_X_POS, ENEMY_Y_POS) +; input +; * a - super-tile code (offset into level_xx_nametable_update_supertile_data) +; output +; * carry flag - clear when successful, set when CPU_GRAPHICS_BUFFER is full +draw_enemy_supertile_a_set_delay: + jsr draw_enemy_supertile_a ; draw super-tile a at position (ENEMY_X_POS, ENEMY_Y_POS) + +update_nametable_set_anim_delay_exit: + bcc @exit ; exit if updated nametable tiles + lda #$01 ; a = #$01, animation delay + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + +@exit: + rts + +; draw super-tile a (offset into level nametable update table) at position (ENEMY_X_POS, ENEMY_Y_POS) +; input +; * a - nametable update super-tile code (offset into level_xx_nametable_update_supertile_data) +; output +; * carry flag - clear when successful, set when CPU_GRAPHICS_BUFFER is full +draw_enemy_supertile_a: + sta $10 + +; draw super-tile $10 (offset into level nametable update table) at position (ENEMY_X_POS, ENEMY_Y_POS) +; input +; * $10 - is the super-tile or palette index to draw (level_x_nametable_update_supertile_data/level_x_nametable_update_palette_data offset) +; If bit 7 clear, then update palette, if bit 7 set do not update palette +; output +; * carry flag - clear when successful, set when CPU_GRAPHICS_BUFFER is full +draw_enemy_supertile_10: + lda ENEMY_Y_POS,x ; load enemy y position on screen + sec ; set carry flag in preparation for subtraction + sbc #$0c ; subtract #$0c from enemy y position + bcc nametable_update_exit ; exit if negative (off screen to the top) + tay ; transfer y position to y + lda ENEMY_X_POS,x ; load enemy x position on screen + sec ; set carry flag in preparation for subtraction + sbc #$0c ; subtract #$0c from enemy x position + bcc nametable_update_exit ; exit if negative (off screen to the left) + jsr load_bank_3_update_nametable_supertile ; draw super-tile $10 at position (a,y) + ldx ENEMY_CURRENT_SLOT + rts + +; draw two super-tiles, one on top of the other +; bit 7 of $10 and a control whether ot update palette: +; * if bit 7 clear, then update palette, if bit 7 set do not update palette +; input +; * $10 - first nametable update super-tile code (offset into level_xx_nametable_update_supertile_data) +; * a - second nametable update super-tile code (offset into level_xx_nametable_update_supertile_data) +; * y - whether or not to assign collision (0 - yes, 1 - skip) +; output +; * carry flag - clear when successful, set when CPU_GRAPHICS_BUFFER is full +update_2_enemy_supertiles: + sty $07 ; store whether or not to assign collision in $07 + pha ; save a copy of a on the stack + jsr draw_enemy_supertile_10 ; draw nametable update super-tile index specified + pla ; restore a from before the draw_enemy_supertile_10 call + bcs @exit ; exit if CPU_GRAPHICS_BUFFER is full + sta $10 + lda $07 ; load whether or not to set collision + bne @continue ; skip collision setting if $07 is set + lda #$00 ; left side of super-tile bg collision (#$00 = empty collision codes) + ldy #$0f ; right side of super-tile bg collision (#$0f = solid collision codes) + jsr set_supertile_bg_collisions ; update bg collision codes for a single super-tile at PPU address $12 (low) $13 (high) + +@continue: + lda ENEMY_Y_POS,x ; load enemy y position on screen + clc ; clear carry in preparation for addition + adc #$14 ; add #$14 to enemy y position + bcs nametable_update_exit ; exit if off screen to the bottom + tay ; set update super-tile y position + lda ENEMY_X_POS,x ; load enemy x position on screen + sec ; set carry flag in preparation for subtraction + sbc #$0c ; subtract #$0c from enemy x position + bcc nametable_update_exit ; exit if off screen to the left + jsr load_bank_3_update_nametable_supertile ; draw super-tile $10 at position (a,y) + bcs @set_slot_exit ; exit if CPU_GRAPHICS_BUFFER is full + lda $07 ; load whether or not to set collision + bne @set_slot_exit ; exit if collision shouldn't be set + lda #$01 ; left side of super-tile bg collision (#$01 = empty then solid collision codes) + ldy #$01 ; right side of super-tile bg collision (#$01 = empty then solid collision codes) + ; sets a horizontal ground collision area to walk on under a row of empty collision tiles + jsr set_supertile_bg_collisions ; update bg collision codes for a single super-tile at PPU address $12 (low) $13 (high) + +@set_slot_exit: + ldx ENEMY_CURRENT_SLOT + +@exit: + rts + +; draws the nametable pattern table tiles specified by a at the enemy position +; ultimately, a (multiplied by #$05) is an offset into the level-specific tile_animation table +; input +; * a - offset into level-specific tile_animation table, e.g. level_2_4_tile_animation +; does not update palette, i.e. leave existing palette +; output +; * carry - clear when successful, set when CPU_GRAPHICS_BUFFER is full +update_enemy_nametable_tiles_no_palette: + ora #$80 ; do not update palette on nametable tile update + +; draws the nametable pattern table tiles specified by a at the enemy position +; only called by bank 0 for wall turret and wall core +; ultimately, a (multiplied by #$05) is an offset into the level-specific tile_animation table +; input +; a - offset into level-specific tile_animation table, e.g. level_2_4_tile_animation +; if bit 7 clear, then update palette, if bit 7 set do not update palette +; output +; * carry - clear when successful, set when CPU_GRAPHICS_BUFFER is full +update_enemy_nametable_tiles: + sta $10 ; store offset in $10 + lda ENEMY_Y_POS,x ; load enemy y position on screen + sec ; set carry flag in preparation for subtraction + sbc #$04 ; subtract #$04 from y position (top) + bcc nametable_update_exit + tay ; set enemy y position for load_bank_3_update_nametable_tiles + lda ENEMY_X_POS,x ; load enemy x position on screen + sec ; set carry flag in preparation for subtraction + sbc #$04 ; subtract #$04 from x position (left) + bcc nametable_update_exit + jsr load_bank_3_update_nametable_tiles ; draw tile code $10 to nametable at (a, y) + ldx ENEMY_CURRENT_SLOT ; restore x to point to current enemy + rts + +nametable_update_exit: + clc + ldx ENEMY_CURRENT_SLOT + rts + +; gets enemy's bg collision code and look for solid collision +; if collision with floor, load collision code one row down (half supertile) +; to see if it's a floor collision on top of a solid object +; output +; * a - collision code of half row down if floor collision, otherwise, current collision code +; * carry flag - set when collision code #$80 (solid) +check_enemy_collision_solid_bg: + ldy #$00 ; y = #$00 + lda #$00 ; a = #$00 + jsr add_a_y_to_enemy_pos_get_bg_collision ; add a to X position and y to Y position; get bg collision code + jmp floor_get_next_row_bg_collision ; if floor collision, get next half supertile row's collision code + ; below the $13 BG_COLLISION_DATA offset + +; initializes $13 to the enemy X position, and y to enemy Y position, then calls get_enemy_bg_collision +; output +; * a collision code #$00 (empty), #$01 (floor), #$02 (water), or #$80 (solid) +; * carry set when collision is with floor (#$01) +; * negative flag set when solid collision (#$80) +init_vars_get_enemy_bg_collision: + ldy #$00 ; y = #$00 + +; adds y to enemy y position and gets bg collision code +; input +; * y - added to enemy Y position +; output +; * a collision code #$00 (empty), #$01 (floor), #$02 (water), or #$80 (solid) +; * carry set when collision is with floor (#$01) +; * negative flag set when solid collision (#$80) +add_y_to_y_pos_get_bg_collision: + lda #$00 ; a = #$00 + +; adds a to X position and y to Y position for use in determining background collision +; ENEMY_X_POS and ENEMY_Y_POS are unaffected +; returns the bg collision code for current enemy +; input +; * a - added to enemy X position +; * y - added to enemy Y position +; output +; * a collision code #$00 (empty), #$01 (floor), #$02 (water), or #$80 (solid) +; * carry set when collision is with floor (#$01) +; * negative flag set when solid collision (#$80) +add_a_y_to_enemy_pos_get_bg_collision: + clc ; clear carry in preparation for addition + adc ENEMY_X_POS,x ; add to enemy x position on screen + sta $13 ; store enemy x position in $13 for get_enemy_bg_collision + tya ; transfer amount to add to y enemy y position into a + clc ; clear carry in preparation for addition + adc ENEMY_Y_POS,x ; add to enemy y position on screen + bcs @exit ; exit if overflow, i.e. enemy y position is off screen towards bottom + tay ; set enemy y position in y for bg collision detection + jmp get_enemy_bg_collision ; get bg collision code for position ($13, y) + +@exit: + lda #$00 ; a = #$00 + rts + +; dead code, never called !(UNUSED) +bank_7_unused_label_04: + ldy #$00 ; y = #$00 + +; flying_capsule_routine_01 horizontal level +set_flying_capsule_y_vel: + lda ENEMY_Y_POS,x ; load enemy y position on screen + sta $01 + lda ENEMY_VAR_1,x + sta $03 + lda ENEMY_Y_VELOCITY_FAST,x + sta $04 + lda ENEMY_Y_VELOCITY_FRACT,x + sta $05 + jsr set_flying_capsule_path + lda $00 + sta ENEMY_Y_VELOCITY_FAST,x + lda $01 + sta ENEMY_Y_VELOCITY_FRACT,x + rts + +; dead code, never called !(UNUSED) +bank_7_unused_label_05: + ldy #$00 ; y = #$00 + +; flying_capsule_routine_01 vertical level +; set various local variables to x velocity +; output +; * $01 - ENEMY_X_POS +; * $03 - ENEMY_VAR_2 +; * $04 - ENEMY_X_VELOCITY_FAST +; * $05 - ENEMY_X_VELOCITY_FRACT +set_flying_capsule_x_vel: + lda ENEMY_X_POS,x ; load enemy x position on screen + sta $01 + lda ENEMY_VAR_2,x + sta $03 + lda ENEMY_X_VELOCITY_FAST,x + sta $04 + lda ENEMY_X_VELOCITY_FRACT,x + sta $05 + jsr set_flying_capsule_path + lda $00 + sta ENEMY_X_VELOCITY_FAST,x + lda $01 ; load new fractional velocity + sta ENEMY_X_VELOCITY_FRACT,x ; save new fractional velocity + rts + +; dead code, never called !(UNUSED) +bank_7_unused_label_06: + ldy #$00 ; y = #$00 + +; creates flight pattern for flying capsule +; input +; * $01 - ENEMY_X_POS or ENEMY_Y_POS +; * $03 - ENEMY_VAR_2 or ENEMY_VAR_1 (amount to subtract from $01) +; * $04 - ENEMY_X_VELOCITY_FAST or ENEMY_Y_VELOCITY_FAST +; * $05 - ENEMY_X_VELOCITY_FRACT or ENEMY_Y_VELOCITY_FRACT +set_flying_capsule_path: + lda $01 ; load position point (x or y position) + sec ; set carry flag in preparation for subtraction + sbc $03 ; subtract ENEMY_VAR_1 or ENEMY_VAR_2 from x or y position + sta $01 ; store new x position back in $01 + lda #$00 ; a = #$00 + sbc #$00 ; subtract #$00 and any overflow + sta $00 ; new fractional velocity in $00 + sta $07 ; store overflow in $07 + tya ; move overflow to a + beq @set_vars_exit + bmi @loop2 + +@loop: + asl $01 ; shift left the x or y position + rol $00 ; rotate fractional velocity left, bringing in any bit 7 from $01 + dey + bne @loop + beq @set_vars_exit + +@loop2: + lsr $07 + ror $00 + ror $01 + iny + bne @loop2 + +@set_vars_exit: + lda $05 + sec ; set carry flag in preparation for subtraction + sbc $01 + sta $01 + lda $04 + sbc $00 + sta $00 + rts + +; red turret +red_turret_find_target_player: + jsr player_enemy_x_dist ; a = closest x distance to enemy from players, y = closest player (#$00 or #$01) + tya ; set the closest player to a #$00 for p1, #$01 for p2 + eor #$01 ; flip to other player + sta $0c ; store farther player in $0c + lda $08 ; load player 1 x distance to enemy + sta $0a ; store player 1 x distance to enemy in $0a + lda $09 ; load player 2 x distance to enemy + sta $0b ; store player 2 x distance to enemy in $0b + jsr player_enemy_y_dist ; a = closest y distance to enemy from players, y = closest player (#$00 or #$01) + tya ; set the closest player to a #$00 for p1, #$01 for p2 + eor #$01 ; flip to other player + cmp $0c ; compare farther y player to farther x player + bne @continue ; branch if not the same player + tya ; one player is farther in both x and y axises, transfer farther player to a + sta $0c ; transfer farther player to $0c + +@continue: + tax ; transfer farther y player to x + tay ; transfer farther y player to y + lda $08,x ; load the farther y player's distance + ldx $0c ; load the farther player's y distance + cmp $0a,x ; compare farther player's x distance + bcc @exit ; branch if + ldy $0c ; set y to the player to target + lda $0a,x ; load + +@exit: + ldx ENEMY_CURRENT_SLOT + rts + +; calculates x distance between p1 and the enemy and p2 and the enemy +; stores shortest distance in a, player number in y +; input +; * x - the current enemy offset +; output +; * a - the shortest x distance to current enemy (either p1 or p2) +; * y - the player closest, #$00 for p1, #$01 for p2 +; * $08 - p1 x distance +; * $09 - p2 x distance +; when player state is not #$01, #$fe is stored in $08 or #$ff in $09 +player_enemy_x_dist: + lda SPRITE_X_POS ; load player 1 x position + sec ; prepare for subtraction + sbc ENEMY_X_POS,x ; enemy x position on screen + bcs @continue_to_p2 ; branch if no overflow occurred + eor #$ff ; overflow occurred, flip bits and add one (two's compliment) + adc #$01 + +@continue_to_p2: + sta $08 ; store distance between player and enemy in $08 + lda SPRITE_X_POS+1 ; load player 2 x position + sec ; prepare for subtraction + sbc ENEMY_X_POS,x ; enemy x position on screen + jmp lda_closer_distance ; jump to determine smallest of $08 (p1) and $09 (p2) store in a + +; calculates y distance between p1 and p2 with the current enemy (x register) +; input +; * x - the current enemy offset +; output +; * a - the shortest y distance to current enemy (either p1 or p2) +; if both players are in non-normal state, a is set to #$fe +; * y - the player closest, #$00 for p1, #$01 for p2 +; * $08 - p1 y distance +; * $09 - p2 y distance +; when player state is not #$01, #$fe is stored in $08 or #$ff in $09 +player_enemy_y_dist: + lda SPRITE_Y_POS ; load player 1 y position + sec ; prepare for subtraction + sbc ENEMY_Y_POS,x ; enemy y position on screen + bcs @continue_to_p2 ; branch if no overflow occurred + eor #$ff ; overflow occurred, flip bits and add one (two's compliment) + adc #$01 + +@continue_to_p2: + sta $08 ; store distance between player and enemy in $08 + lda SPRITE_Y_POS+1 ; load player 2 y position + sec ; prepare for subtraction + sbc ENEMY_Y_POS,x ; enemy y position on screen + +; take the smallest of $08 (p1) and $09 (p2) and store in a accounting for overflow +; ignoring non-normal player state +lda_closer_distance: + bcs @continue + eor #$ff ; overflow occurred, flip bits and add one (two's compliment) + adc #$01 + +@continue: + sta $09 ; store player 2 x or y distance from current enemy in $09 + ldy #$fe ; y = #$fe + lda PLAYER_STATE ; load player state (#$00 dropping into level, #$01 normal, #$02 dead, #$03 can't move) + cmp #$01 + beq @continue_p1 ; branch if player state is #$01 (normal) + sty $08 ; player 1 state not normal, store #$fe in $08 + +@continue_p1: + ldy #$ff ; y = #$ff + lda PLAYER_STATE+1 ; load 2nd player state (#$00 dropping into level, #$01 normal, #$02 dead, #$03 can't move) + cmp #$01 + beq @set_closest ; branch if p2 state is normal + sty $09 ; player 2 state not normal, set to #$ff + +@set_closest: + lda $09 ; load player 2 distance (or #$ff if not normal) + ldy #$01 + cmp $08 ; compare player 1 distance to player 2 distance + bcc @exit ; branch if $09 < $08 (p2 is closer) + dey ; p1 is closer, ensure player specified is p1 + lda $08 ; load the closest distance + +@exit: + rts + +; gets a number from #$06 to #$00 indicating how far the enemy at position $09 is from the left of the screen +; starting from #$06 for farthest left, down to #$00 for farthest right +; very similar to find_close_segment in bank 0 +; usually used together to compare player and enemy x positions on indoor levels +; input +; * $09 - current enemy X position +; output +; * a velocity code +find_far_segment_for_x_pos: + lda $09 ; load enemy X position + +; gets a number from #$06 to #$00 indicating how far the enemy is from the left of the screen +; starting from #$06 for farthest left, down to #$00 for farthest right +; very similar to find_close_segment in bank 0 +; usually used together to compare player and enemy x positions on indoor levels +find_far_segment_for_a: + ldy #$06 ; y = #$06 + +@loop: + cmp far_segment_code_tbl,y ; compare a to far_segment_code_tbl,y + bcc @exit ; branch if a < far_segment_code_tbl,y + dey ; a >= far_segment_code_tbl,y move to next larger velocity value + bmi @use_code_0 ; if y became negative (shouldn't happen), use largest velocity code + bcs @loop + +@use_code_0: + lda #$00 ; safety code, use #$00 velocity code + rts + +@exit: + tya ; move far_segment_code_tbl into a + rts + +; table for ?? (#$7 bytes) +far_segment_code_tbl: + .byte $ff,$94,$8c,$84,$7c,$74,$6c + +; grenade_routine_01 +; weapon_item_routine_01 (indoor/base level only) +; creates a falling arc pattern by using X and Y velocities to update X and Y positions +; used for grenades and for weapon items in indoor/base levels +set_enemy_falling_arc_pos: + lda ENEMY_VAR_2,x ; load ENEMY_VAR_2,x + clc ; clear carry in preparation for addition + adc ENEMY_VAR_4,x ; add ENEMY_VAR_2,x and ENEMY_VAR_4,x + sta ENEMY_VAR_2,x ; store result back in ENEMY_VAR_2,x + lda ENEMY_VAR_3,x ; load ENEMY_VAR_3,x + adc ENEMY_VAR_B,x ; add ENEMY_VAR_3,x and ENEMY_VAR_B,x along with any overflow carry + sta ENEMY_VAR_3,x ; store result back in ENEMY_VAR_3,x + lda ENEMY_Y_VEL_ACCUM,x ; load ENEMY_Y_VEL_ACCUM,x + clc ; clear carry in preparation for addition + adc ENEMY_Y_VELOCITY_FRACT,x ; ENEMY_Y_VEL_ACCUM,x + ENEMY_Y_VELOCITY_FRACT,x + sta ENEMY_Y_VEL_ACCUM,x ; update ENEMY_Y_VEL_ACCUM,x to sum + lda ENEMY_VAR_1,x ; load ENEMY_VAR_1,x + adc ENEMY_Y_VELOCITY_FAST,x ; ENEMY_Y_VELOCITY_FAST,x + ENEMY_VAR_1,x along with any overflow carry + sta ENEMY_VAR_1,x ; update ENEMY_VAR_1,x with the result + cmp #$f0 ; if the enemy has fallen below the screen, remove it + bcs @remove_enemy + clc ; clear carry in preparation for addition + adc ENEMY_VAR_3,x + sta ENEMY_Y_POS,x ; enemy y position on screen + jmp update_enemy_x_pos_rem_off_screen ; add velocity to enemy X position; remove enemy if X position < #$08 (off screen to left) + +@remove_enemy: + jmp remove_enemy ; remove enemy + +; weapon_item_routine_00 indoor level +; sets initial velocities for indoor level based on X position +; sets Y velocity to #$01 for the high byte and #$00 for the low byte +; doesn't use weapon_item_init_vel_tbl like outdoor levels +set_weapon_item_indoor_velocity: + lda ENEMY_X_POS,x ; load enemy x position on screen + jsr find_far_segment_for_a ; find the appropriate velocity code, given the X position + asl ; double since each entry is #$02 bytes + tay + lda weapon_item_indoor_vel_tbl,y ; load X fractional velocity byte for velocity code + sta ENEMY_X_VELOCITY_FRACT,x ; set X fractional velocity byte + lda weapon_item_indoor_vel_tbl+1,y ; load X velocity fast value for velocity code + sta ENEMY_X_VELOCITY_FAST,x ; set X velocity fast value (number of units to move in the X direction per frame) + lda #$00 ; a = #$00 + sta ENEMY_Y_VELOCITY_FRACT,x ; set Y fractional velocity byte to #$00 + lda #$01 ; a = #$01 + sta ENEMY_Y_VELOCITY_FAST,x ; set Y velocity fast value to #$01 + rts + +; two-byte values for weapon item X velocity in indoor levels (#$e bytes) +; byte 0 - X fractional velocity value +; byte 1 - X velocity fast value +weapon_item_indoor_vel_tbl: + .byte $aa,$00 ; aa 170 170 / 256 = 0.66 + .byte $71,$00 ; 71 113 113 / 256 = 0.44 + .byte $38,$00 ; 38 56 56 / 256 = 0.22 + .byte $00,$00 ; 00 0 + .byte $c8,$ff ; -38 -56 -56 / 256 = -0.22 + .byte $8f,$ff ; -71 -113 -113 / 256 = -0.44 + .byte $56,$ff ; -aa -170 -170 / 256 = -0.66 + +; find next available enemy slot (between slots 0 and 6) +; slots 0 to 6 are reserved for soldiers +; slot number is stored in x register +; zero flag set when found, not set when no slots available +find_next_enemy_slot_6_to_0: + ldx #$06 ; x = #$06 + bne find_next_enemy_slot_x_to_0 + +; find next available enemy slot (all slots 0-f) +; slot number is stored in x register +; zero flag set when found, not set when no slots available +find_next_enemy_slot: + ldx #$0f ; x = #$0f + +; slot number is stored in x register +find_next_enemy_slot_x_to_0: + lda ENEMY_ROUTINE,x + beq find_enemy_routine_slot_exit + dex ; decrement offset + bpl find_next_enemy_slot_x_to_0 ; if not zero loop to see if a lower index is available + +find_enemy_routine_slot_exit: + rts + +find_bullet_slot: + ldx #$0f ; x = #$0f + +@loop: + lda ENEMY_TYPE,x ; load current enemy type + cmp #$01 ; is enemy type a bullet + beq find_enemy_routine_slot_exit + dex + bpl @loop + ldx #$00 ; no bullet found, return slot 0 + rts + +; dead code, never called !(UNUSED) +; runs clear_enemy on all enemies +bank_7_unused_label_07: + ldx #$0f ; set loop counter for enemies + +@clear_next_enemy: + jsr clear_enemy ; clear current enemy vars + dex ; decrement enemy offset + bpl @clear_next_enemy ; branch if more enemies to clear + rts + +clear_sprite_clear_enemy_pt_3: + lda #$00 ; a = #$00 + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + beq clear_enemy_pt_3 + +clear_enemy_custom_vars: + lda #$00 ; a = #$00 + beq clear_enemy_pt_4 ; always jump + +; clear many of the enemy variables +clear_enemy: + lda #$00 ; a = #$00 + sta ENEMY_ROUTINE,x + sta ENEMY_HP,x + sta ENEMY_TYPE,x + sta ENEMY_SPRITES,x + +clear_enemy_pt_2: + sta ENEMY_ATTRIBUTES,x + sta ENEMY_Y_POS,x + sta ENEMY_X_POS,x + sta ENEMY_Y_VEL_ACCUM,x + sta ENEMY_X_VEL_ACCUM,x + +clear_enemy_pt_3: + sta ENEMY_SPRITE_ATTR,x + sta ENEMY_Y_VELOCITY_FRACT,x + sta ENEMY_X_VELOCITY_FRACT,x + sta ENEMY_Y_VELOCITY_FAST,x + sta ENEMY_X_VELOCITY_FAST,x + sta ENEMY_ANIMATION_DELAY,x + sta ENEMY_VAR_A,x + sta ENEMY_ATTACK_DELAY,x + sta ENEMY_FRAME,x + sta ENEMY_STATE_WIDTH,x + sta ENEMY_SCORE_COLLISION,x + +clear_enemy_pt_4: + sta ENEMY_VAR_1,x + sta ENEMY_VAR_2,x + sta ENEMY_VAR_3,x + sta ENEMY_VAR_4,x + rts + +; initialize enemy attributes +; initialize enemy_routine index +; initialize enemy tiles +; initialize enemy hp +; initialize enemy position +initialize_enemy: + lda #$01 ; a = #$01 + sta ENEMY_ROUTINE,x ; initialize enemy routine index to ..._routine_00 (always off by one) + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + lda #$00 ; a = #$00 + jsr clear_enemy_pt_2 ; set many variables to a + tya ; save existing value of y to stack so it isn't overwritten + pha ; push on stack + lda ENEMY_TYPE,x ; load current enemy type + cmp #$10 ; see if shared enemy (used across levels) + ldy #$10 + bcc @continue ; set to use enemy_prop_00 when for common/shared enemies (ENEMY_TYPE < #$10) + lda CURRENT_LEVEL ; not shared enemy type, load current level to figure out offset + asl ; double since each entry in enemy_prop_ptr_tbl is #$02 bytes + tay ; set offset based on current level + +; y is offset enemy_prop_ptr_tbl +@continue: + lda enemy_prop_ptr_tbl,y ; load low byte of enemy_prop_xx address + sta $8c ; store in $8c + lda enemy_prop_ptr_tbl+1,y ; load high byte of enemy_prop_xx address + sta $8d ; store in $8d + lda ENEMY_TYPE,x ; load current enemy type + asl + asl ; quadruple since each entry is #$04 bytes + tay + lda ($8c),y ; load first byte of enemy_prop_XX + sta ENEMY_STATE_WIDTH,x ; set ENEMY_STATE_WIDTH + iny + lda ($8c),y ; read next byte of enemy_prop_XX + sta ENEMY_SCORE_COLLISION,x ; set ENEMY_SCORE_COLLISION + iny + lda ($8c),y ; read next byte of enemy_prop_XX + sta ENEMY_HP,x ; set ENEMY_HP + iny + lda ($8c),y ; read next byte of enemy_prop_XX + sta ENEMY_VAR_A,x ; set ENEMY_VAR_A + pla ; pull saved value of y from stack + tay ; restore previous value of y from stack + rts + +; pointer table for enemy properties (#$9 * #$2 = #$12 bytes) +; enemy width, enemy score code, enemy collision box code, enemy HP, and enemy hit sound +enemy_prop_ptr_tbl: + .addr enemy_prop_00 ; Level 1 - CPU address $ee9f + .addr enemy_prop_01 ; Level 2 - CPU address $eeab + .addr enemy_prop_02 ; Level 3 - CPU address $eeef + .addr enemy_prop_01 ; Level 4 - CPU address $eeab + .addr enemy_prop_04 ; Level 5 - CPU address $ef07 + .addr enemy_prop_05 ; Level 6 - CPU address $ef23 + .addr enemy_prop_06 ; Level 7 - CPU address $ef37 + .addr enemy_prop_07 ; Level 8 - CPU address $ef5b + .addr enemy_prop_00 ; shared enemies (ENEMY_TYPE < #$10) + +; (#$46 * #$4 = #$118 bytes) +; byte 0: ENEMY_STATE_WIDTH - related to facing direction and/or enemy width +; byte 1: ENEMY_SCORE_COLLISION - score code (bits 4-7), explosion type (bit 3), collision box code +; byte 2: ENEMY_HP - enemy hp +; byte 3: ENEMY_VAR_A +; shared enemies and level 1 enemies +enemy_prop_00: + .byte $82,$22,$01,$00 ; weapon item (00) + .byte $80,$00,$01,$00 ; enemy bullet (01) + .byte $0f,$32,$f0,$00 ; weapon box (02) + +; indoor/base level enemies +enemy_prop_01: + .byte $0b,$32,$01,$00 ; weapon zeppelin (03) + .byte $8f,$22,$08,$00 ; rotating gun (04) + .byte $83,$10,$01,$00 ; running man (05) + .byte $83,$30,$01,$00 ; rifle man (06) + .byte $8f,$30,$08,$00 ; red turret (07) + .byte $0f,$52,$f1,$00 ; triple cannon (08) + .byte $00,$00,$01,$00 ; ? (09) + .byte $0f,$42,$f0,$00 ; wall plating (0a) + .byte $8a,$05,$01,$00 ; mortar shot (0b) + .byte $83,$42,$01,$00 ; scuba diver (0c) + .byte $00,$00,$01,$00 ; ? (0d) + .byte $0e,$33,$0a,$00 ; turret man (0e) + .byte $80,$01,$01,$00 ; turret man bullet (0f) + +; level 1 specific enemies + .byte $0f,$42,$10,$00 ; boss bomb turret (10) + .byte $0c,$82,$20,$00 ; door plate with siren (11) + .byte $89,$00,$01,$00 ; exploding bridge (12) + +; level 2/4 enemies + .byte $8d,$02,$01,$00 ; boss eye (10) + +enemy_prop_02: + .byte $2f,$22,$05,$00 ; rollers (11) + .byte $81,$03,$01,$00 ; grenades (12) + .byte $9f,$35,$04,$00 ; wall cannon (13) + .byte $9f,$05,$01,$00 ; core (14) + .byte $13,$16,$01,$00 ; running guy (15) + .byte $13,$16,$01,$00 ; jumping guy (16) + +enemy_prop_04: + .byte $13,$36,$01,$00 ; seeking guy (17) + .byte $13,$16,$01,$00 ; group of 4 (18) + .byte $89,$00,$f1,$00 ; green guys generator (19) + .byte $81,$00,$f1,$00 ; rollers generator (1a) + .byte $8f,$13,$02,$01 ; sphere projectile (1b) + .byte $8f,$02,$01,$00 ; boss gemini (1c) + .byte $0a,$15,$01,$00 ; spinning bubbles projectile (1d) + +enemy_prop_05: + .byte $03,$30,$01,$00 ; blue jumping guy (1e) + .byte $03,$30,$01,$00 ; red shooting guy (1f) + .byte $81,$00,$f1,$00 ; red/blue guys generator (20) + +; level 3 enemies + .byte $c0,$04,$f0,$00 ; floating rock platform (10) + .byte $80,$02,$f0,$00 ; moving flame (11) + +enemy_prop_06: + .byte $81,$00,$f0,$00 ; falling rock generator (12) + .byte $8f,$31,$05,$00 ; falling rock (13) + .byte $8d,$83,$f1,$02 ; level 3 boss mouth (14) + .byte $0e,$52,$f1,$00 ; level 3 dragon arm orb (15) + +; level 5 enemies + .byte $81,$00,$f0,$00 ; grenade generator (10) + .byte $81,$02,$f1,$00 ; grenade (11) + .byte $85,$79,$f0,$00 ; tank (12) + .byte $81,$00,$f0,$00 ; pipe joint (13) + .byte $8d,$93,$20,$00 ; alien carrier (14) + +enemy_prop_07: + .byte $02,$20,$01,$00 ; flying saucer (15) + .byte $0a,$12,$01,$00 ; bomb drop (16) + +; level 6 enemies + .byte $81,$0f,$f0,$00 ; fire beam - down (10) + .byte $81,$0f,$f0,$00 ; fire beam - left (11) + .byte $81,$0f,$f0,$00 ; fire beam - right (12) + .byte $04,$9d,$01,$02 ; boss robot (13) + .byte $80,$05,$01,$00 ; spiked disk projectile (14) + +; level 7 enemies + .byte $80,$0a,$f0,$00 ; mechanical claw (10) + .byte $8d,$0f,$10,$00 ; raising spiked wall (11) + .byte $0c,$0f,$10,$00 ; spiked wall (12) + .byte $81,$00,$f0,$00 ; cart generator (13) + .byte $6e,$0c,$03,$00 ; cart - moving (14) + .byte $6e,$0c,$03,$00 ; cart - immobile (15) + .byte $0c,$93,$20,$00 ; armored door (16) + .byte $8f,$72,$08,$00 ; mortar launcher (17) + .byte $89,$00,$01,$00 ; enemy generator (18) + +; level 8 enemies + .byte $04,$78,$01,$02 ; alien guardian (10) + .byte $06,$22,$01,$01 ; alien fetus (11) + .byte $06,$42,$01,$01 ; alien mouth (12) + .byte $02,$22,$01,$00 ; white sentient blob (13) + .byte $06,$33,$01,$01 ; alien spider (14) + .byte $06,$62,$10,$01 ; spider spawn (15) + .byte $04,$a7,$01,$03 ; heart (16) + +; pointer table for triple cannon (#$9 * #$2 = #$12 bytes) +wall_cannon_routine_ptr_tbl: + .addr wall_cannon_routine_00 ; CPU address $efc7 - set hp to #$08, animation delay to #$50, advance routine + .addr wall_cannon_routine_01 ; CPU address $efd4 + .addr wall_cannon_routine_02 ; CPU address $f007 + .addr wall_cannon_routine_03 ; CPU address $f048 + .addr wall_cannon_routine_04 ; CPU address $f06d + .addr enemy_routine_init_explosion ; CPU address $e74b + .addr enemy_routine_explosion ; CPU address $e7b0 + .addr enemy_routine_remove_enemy ; CPU address $e806 + +; set hp to #$08, animation delay to #$50, advance routine +wall_cannon_routine_00: + lda #$08 ; a = #$08 (hp for triple cannon) + sta ENEMY_VAR_1,x ; set hp + lda #$50 ; a = #$50 + +; set the animation delay to a and advanced the ENEMY_ROUTINE +; input +; * a - the ENEMY_ANIMATION_DELAY +; this label is identical to two other labels +; * bank 0 - set_anim_delay_adv_enemy_routine_00 +; * bank 0 - set_anim_delay_adv_enemy_routine_01 +set_anim_delay_adv_enemy_routine: + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + jmp advance_enemy_routine ; advance to next routine + +wall_cannon_routine_01: + lda ENEMY_ATTACK_FLAG ; see if enemies should attack + beq wall_cannon_exit ; exit if enemies shouldn't attack + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne wall_cannon_exit ; exit if animation delay hasn't elapsed + jsr animate_wall_cannon ; animation delay elapsed, animate wall cannon + bcs wall_cannon_exit ; exit if unable to update the nametable to try again next frame + lda ENEMY_FRAME,x ; load enemy animation frame number + cmp #$02 + bcs wall_cannon_set_delays + inc ENEMY_FRAME,x ; increment enemy animation frame number + +wall_cannon_exit: + rts + +; used only by wall_cannon_routine_01 +wall_cannon_set_delays: + lda ENEMY_VAR_1,x ; load enemy hp + sta ENEMY_HP,x ; store hp temporarily + lda #$04 ; a = #$04 + sta ENEMY_ATTACK_DELAY,x ; set delay between attacks + lda #$40 ; a = #$40 (delay between attack and closing) + bne set_anim_delay_adv_enemy_routine ; set ENEMY_ANIMATION_DELAY to #$40 and advance enemy routine + +animate_wall_cannon: + lda #$06 ; a = #$06 (delay between frames when open/close) + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + lda ENEMY_FRAME,x ; load enemy animation frame number + jmp draw_enemy_supertile_a_set_delay ; draw pattern table tile specified in a at enemy position + +wall_cannon_routine_02: + lda ENEMY_ATTACK_DELAY,x ; load delay between attacks + beq @continue ; skip creating bullet if delay is #$00 + dec ENEMY_ATTACK_DELAY,x ; decrement delay between attacks + bne @continue ; skip creating bullet if delay not #$00 + lda #$02 ; delay has elapsed, a = #$02 (number of bullets to fire) + sta $16 ; set number of bullets to fire to #$02 (#$03 bullets) + +@create_bullet_loop: + ldy $16 ; load remaining number of bullets to fire + lda wall_cannon_bullet_x_offset,y ; set horizontal offset from enemy position (param for add_with_enemy_pos) + ldy #$08 ; set vertical offset from enemy position (param for add_with_enemy_pos) + jsr add_with_enemy_pos ; stores absolute screen x position in $09, and y position in $08 + ldy $16 ; load remaining number of bullets to fire + lda wall_cannon_bullet_type_and_angle,y ; load bullet type (xxx. ....) and angle index (...x xxxx) + ldy #$07 ; set bullet speed to #$07 + jsr create_enemy_bullet_angle_a ; create a bullet with speed y, bullet type a, angle a at position ($09, $08) + dec $16 ; decrement number of bullets to fire + bpl @create_bullet_loop + +@continue: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne wall_cannon_exit + lda ENEMY_HP,x ; load enemy hp + sta ENEMY_VAR_1,x ; store enemy hp while invulnerable + lda #$f1 ; a = #$f1 (f1 = hittable, no damage) + sta ENEMY_HP,x ; set enemy hp + lda #$06 ; a = #$06 + jmp set_anim_delay_adv_enemy_routine ; set ENEMY_ANIMATION_DELAY to #$06 and advance enemy routine + +; table for bullets starting x positions (#$3 bytes) +wall_cannon_bullet_x_offset: + .byte $f8,$00,$08 + +; table for bullets type and angle (#$3 bytes) +wall_cannon_bullet_type_and_angle: + .byte $48,$46,$44 + +wall_cannon_routine_03: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne wall_cannon_exit + jsr animate_wall_cannon + bcs wall_cannon_exit_01 + lda ENEMY_FRAME,x ; load enemy animation frame number + beq wall_cannon_calc_delay_set_routine_01 + dec ENEMY_FRAME,x ; decrement enemy animation frame number + +wall_cannon_exit_01: + rts + +wall_cannon_calc_delay_set_routine_01: + lda PLAYER_WEAPON_STRENGTH + cmp #$02 ; if weapon strength < 2 + lda #$c0 ; a = #$c0 (delay for weapon strength 0-1) + bcc @set_delay_set_routine_01 + lda #$60 ; a = #$60 (delay for weapon strength 2-3) + +@set_delay_set_routine_01: + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + lda #$02 ; a = #$02 + jmp set_enemy_routine_to_a ; set enemy routine index to wall_cannon_routine_01 + +wall_cannon_routine_04: + lda #$05 ; a = #$05 (tile code after destruction) + jsr draw_enemy_supertile_a ; draw super-tile a at position (ENEMY_X_POS, ENEMY_Y_POS) + bcs wall_cannon_exit_01 + jmp advance_enemy_routine ; advance to next routine + +; pointer table for wall plating (#$7 * #$2 = #$e bytes) +; level 2/4 boss screen targets +; * 4 exist on level 2 boss screen +; * 3 exist on level 4 boss screen +wall_plating_routine_ptr_tbl: + .addr wall_plating_routine_00 ; CPU address $f085 + .addr wall_plating_routine_01 ; CPU address $f08a + .addr wall_plating_routine_02 ; CPU address $f0b0 + .addr wall_plating_routine_03 ; CPU address $f0b1 + .addr enemy_routine_init_explosion ; CPU address $e74b + .addr enemy_routine_explosion ; CPU address $e7b0 + .addr enemy_routine_remove_enemy ; CPU address $e806 + +; wall plating - pointer 0 +wall_plating_routine_00: + lda #$80 ; a = #$80 (delay before deployment) + jmp set_anim_delay_adv_enemy_routine ; set ENEMY_ANIMATION_DELAY to #$80 and advance enemy routine + +; wall plating - pointer 1 +wall_plating_routine_01: + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne wall_plating_routine_02 + lda #$04 ; a = #$04 (delay between frames when deploying) + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + lda ENEMY_FRAME,x ; load enemy animation frame number (#$00 to #$03) + clc ; clear carry in preparation for addition + adc #$03 ; draw the appropriate frame of the animation (level_2_4_nametable_update_supertile_data offset) + jsr draw_enemy_supertile_a_set_delay ; draw pattern table tile specified in a at enemy position + bcs wall_plating_routine_02 + inc ENEMY_FRAME,x ; increment enemy animation frame number + lda ENEMY_FRAME,x ; load enemy animation frame number + cmp #$02 ; see if wall plating is now open + bcc wall_plating_routine_02 ; exit if wall cannon is open + lda #$0a ; a = #$0a (hp for wall plating) + sta ENEMY_HP,x ; set enemy hp + bcs wall_plating_adv_enemy_routine + +; wall plating - pointer 2 +wall_plating_routine_02: + rts + +; wall plating - pointer 3 +; called when enemy is destroyed +wall_plating_routine_03: + lda #$05 ; a = #$05 (level_2_4_nametable_update_supertile_data offset) + jsr draw_enemy_supertile_a ; draw destroyed wall plating super-tile + bcs wall_plating_routine_02 ; exit + inc WALL_PLATING_DESTROYED_COUNT ; increment number of boss platings destroyed + +wall_plating_adv_enemy_routine: + jmp advance_enemy_routine ; advance to next routine + +; pointer table for turret man (#$6 * #$2 = #$c bytes) +turret_man_routine_ptr_tbl: + .addr turret_man_routine_00 ; CPU address $f0c9 + .addr turret_man_routine_01 ; CPU address $f0db + .addr turret_man_routine_02 ; CPU address $f0ec + .addr enemy_routine_init_explosion ; CPU address $e74b + .addr enemy_routine_explosion ; CPU address $e7b0 + .addr enemy_routine_remove_enemy ; CPU address $e806 + +turret_man_routine_00: + lda #$bd ; a = #$bd + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + asl + asl + asl + asl ; shift low nibble to high nibble + clc ; clear carry in preparation for addition + adc #$01 ; add one + jmp set_anim_delay_adv_enemy_routine ; set ENEMY_ANIMATION_DELAY to a and advance enemy routine + +turret_man_routine_01: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne turret_man_exit + inc ENEMY_SPRITES,x ; increment enemy sprite code to CPU buffer + lda #$05 ; a = #$05 (recoil delay) + jmp set_anim_delay_adv_enemy_routine ; set ENEMY_ANIMATION_DELAY to #$05 and advance enemy routine + +turret_man_exit: + rts + +turret_man_routine_02: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne turret_man_exit + lda #$0c ; a = #$0c (sound_0c) + jsr play_sound ; play machine gun (M weapon) sound + lda #$0f ; a = #$0f (enemy code of projectile) + sta $0a + lda #$f0 ; a = #$f0 + ldy #$fc ; y = #$fc + jsr generate_enemy_at_pos ; generate enemy type $0a at relative position a,y + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + asl + asl + asl + asl + clc ; clear carry in preparation for addition + adc #$30 ; delay between shots (attr. * 10 + 30) + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + dec ENEMY_SPRITES,x ; decrement enemy sprite code to CPU buffer + lda #$02 ; a = #$02 + jmp set_enemy_routine_to_a ; set enemy routine index to a + +; pointer table for turret man bullet (#$3 * #$2 = #$6 bytes) +turret_man_bullet_routine_ptr_tbl: + .addr turret_man_bullet_routine_00 ; CPU address $f11f + .addr turret_man_bullet_routine_01 ; CPU address $f131 + .addr remove_enemy ; Remove Enemy - CPU address $e809 + +; turret man bullet - pointer 1 +turret_man_bullet_routine_00: + lda #$fd ; a = #$fd + sta ENEMY_X_VELOCITY_FAST,x + lda #$80 ; a = #$80 + sta ENEMY_X_VELOCITY_FRACT,x + lda #$1f ; a = #$1f (sprite_1f) + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + +turret_man_adv_routine: + jmp advance_enemy_routine ; advance to next routine + +; turret man bullet - pointer 2 +turret_man_bullet_routine_01: + lda ENEMY_X_POS,x ; load enemy x position on screen + cmp #$f0 + bcs turret_man_adv_routine + jmp update_enemy_pos ; apply velocities and scrolling adjust + +; pointer table for scuba diver (#$6 * #$2 = #$c bytes) +scuba_soldier_routine_ptr_tbl: + .addr scuba_soldier_routine_00 ; CPU address $f147 + .addr scuba_soldier_routine_01 ; CPU address $f14c + .addr scuba_soldier_routine_02 ; CPU address $f183 + .addr enemy_routine_init_explosion ; CPU address $e74b + .addr enemy_routine_explosion ; CPU address $e7b0 + .addr enemy_routine_remove_enemy ; CPU address $e806 + +scuba_soldier_routine_00: + lda #$80 ; a = #$80 (delay before first attack) + jmp set_anim_delay_adv_enemy_routine ; set ENEMY_ANIMATION_DELAY counter to #$80; advance enemy routine + +; hides in water, bumping up every #$08 frames until activated +; * for vertical levels, scuba soldiers aren't activated until towards bottom 72% of screen +; * snow field level has scuba soldiers at bottom of screen already so no scroll activation delay. +; only timer delay +; if already activated from scuba_soldier_routine_02, simply wait for delay timer to elapse +scuba_soldier_routine_01: + lda #$4b ; a = #$4b (sprite_4b scuba soldier hiding) + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + lda ENEMY_ANIMATION_DELAY,x ; load enemy animation frame delay counter + asl ; first execution is #$00, but can be set from + asl ; scuba_soldier_routine_02 after firing mortar + asl + asl + lda #$08 ; a = #$08 + bcc @continue ; #$07 out of every #$08 frames use #$08, otherwise use #$00 + lda #$00 ; a = #$00 + +@continue: + sta ENEMY_SPRITE_ATTR,x ; set gun recoil flag value + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + bne @exit ; exit if animation delay hasn't elapsed + lda ENEMY_Y_POS,x ; load enemy y position on screen + cmp #$b8 ; if vertical, don't shoot until this height + bcs @activate_scuba_soldier ; scuba soldier is at heigh #$b8 or higher (lower on screen), 'activate' enemy + lda #$10 ; wait another #$10 frames before checking again + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + +@exit: + rts + +; enable enemy collisions, set attack delay to #$10, set animation delay to #$30, +; advance routine to scuba_soldier_routine_02 +@activate_scuba_soldier: + jsr enable_enemy_collision ; enable bullet-enemy collision and player-enemy collision checks + lda #$10 ; a = #$10 (delay between aim and fire) + sta ENEMY_ATTACK_DELAY,x ; set delay between attacks + lda #$30 ; a = #$30 (total delay of vulnerability) + jmp set_anim_delay_adv_enemy_routine ; set ENEMY_ANIMATION_DELAY to #$30 and advance enemy routine + +; scuba soldier activated, set sprite, and fire mortar shot, then go back to scuba_soldier_routine_01 +scuba_soldier_routine_02: + lda #$4c ; a = #$4c (sprite_4c scuba soldier out of water shooting up) + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + lda #$00 ; a = #$00 + ldy ENEMY_VAR_1,x ; load gun recoil delay timer + beq @continue ; continue to firing logic, if elapsed + dec ENEMY_VAR_1,x ; recoil delay timer elapsed, set gun recoil + lda #$08 ; a = #$08 (gun recoil flag) + +; create mortar shot (#$07) if timers have elapsed +@continue: + sta ENEMY_SPRITE_ATTR,x ; set/clear gun recoil flag + dec ENEMY_ANIMATION_DELAY,x ; decrement enemy animation frame delay counter + beq @disable_and_dec_enemy_routine ; branch if animation delay has elapsed + dec ENEMY_ATTACK_DELAY,x ; decrement delay between attacks + bne @add_scroll_exit ; exit if attack delay hasn't elapsed + lda #$07 ; a = #$07 (recoil delay) + sta ENEMY_VAR_1,x ; store recoil timer + lda #$0b ; a = #$0b (mortar shot) + sta $0a ; set enemy type to #$0b (mortar shot) + ldy #$e8 ; y = #$e8 (mortar initial relative y position) + lda #$05 ; a = #$05 (mortar initial relative x position) + jsr generate_enemy_at_pos ; generate enemy type #$0b at relative position (#$05,#$e8) + +@add_scroll_exit: + jmp add_scroll_to_enemy_pos ; add scrolling to enemy position + +@disable_and_dec_enemy_routine: + jsr add_scroll_to_enemy_pos ; add scrolling to enemy position + lda #$c0 ; a = #$c0 (delay hidden in water) + sta ENEMY_ANIMATION_DELAY,x ; set enemy animation frame delay counter + jsr disable_enemy_collision ; prevent player enemy collision check and allow bullets to pass through enemy + lda #$02 ; a = #$02 + jmp set_enemy_routine_to_a ; set enemy routine index to scuba_soldier_routine_01 + +; pointer table for mortar shot (#$9 * #$2 = #$12 bytes) +mortar_shot_routine_ptr_tbl: + .addr mortar_shot_routine_00 ; CPU address $f1d6 - set explosion sound, sprite, palette, and velocities + .addr mortar_shot_routine_01 ; CPU address $f237 + .addr mortar_shot_routine_02 ; CPU address $f26e - mortar shot falling down, create 3 split mortar rounds + .addr enemy_routine_init_explosion ; CPU address $e74b - mortar collide with player (enemy destroyed routine) + .addr enemy_routine_explosion ; CPU address $e7b0 + .addr enemy_routine_remove_enemy ; CPU address $e806 + .addr mortar_shot_routine_03 ; CPU address $e752 - split mortar collide with ground routine, play explosion sound, update collision, hide sprite + .addr enemy_routine_explosion ; CPU address $e7b0 + .addr enemy_routine_remove_enemy ; CPU address $e806 + +; set explosion sound, sprite, palette, and velocities +mortar_shot_routine_00: + lda #$8a ; a = #$8a (type of explosion for main shot) + ; explosion_type_01, with explosion noise 1 + ldy ENEMY_ATTRIBUTES,x ; load mortar shot enemy attributes + beq @continue ; branch if no attributes, using custom explosion + lda #$80 ; default 0 explosion + +@continue: + sta ENEMY_STATE_WIDTH,x ; set explosion and specify player bullets travel through enemy + lda #$20 ; a = #$20 (sprite_20) S bullet, mortar + sta ENEMY_SPRITES,x ; write enemy sprite code to CPU buffer + lda #$06 ; a = #$06 (use palette #$02) + sta ENEMY_SPRITE_ATTR,x ; set palette + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + bne @set_mortar_velocities ; branch if enemy attributes specified (hangar zone boss screen) + lda ENEMY_VAR_1,x ; load mortar_shot_velocity_tbl offset + beq @set_mortar_velocities ; skip offset if using default mortar shot + clc ; hangar zone boss screen specified initial aim direction, + ; use ENEMY_VAR_1 to get velocities + ; clear carry in preparation for addition + adc #$03 ; aimed enemy mortar shots start at offset 4 + ; e.g. ENEMY_VAR_1 would be $00,$fb,$c0,$ff + +@set_mortar_velocities: + asl + asl ; quadruple since each entry is #$04 bytes + tay ; transfer to offset register + lda mortar_shot_velocity_tbl,y ; load mortar shot fractional y velocity + sta ENEMY_Y_VELOCITY_FRACT,x ; set mortar shot fractional y velocity + lda mortar_shot_velocity_tbl+1,y ; load mortar shot fast y velocity + sta ENEMY_Y_VELOCITY_FAST,x ; set mortar shot fast y velocity + lda mortar_shot_velocity_tbl+2,y ; load mortar shot fractional x velocity + sta ENEMY_X_VELOCITY_FRACT,x ; set mortar shot fractional x velocity + lda mortar_shot_velocity_tbl+3,y ; load mortar shot fast x velocity + sta ENEMY_X_VELOCITY_FAST,x ; set mortar shot fast x velocity + +mortar_shot_adv_routine: + jmp advance_enemy_routine ; advance to next routine + +; table for mortar velocities (#$20 bytes) +; byte 0 - mortar shot fractional y velocity +; byte 1 - mortar shot fast y velocity +; byte 2 - mortar shot fractional x velocity +; byte 3 - mortar shot fast x velocity +mortar_shot_velocity_tbl: + .byte $00,$fb,$00,$00 ; ( -5 , 0 ) default initial mortar shot (straight up fast) + .byte $00,$fe,$00,$00 ; ( -2 , 0 ) one of 3 split mortar shot (straight up slow) + .byte $40,$fe,$90,$00 ; ( -1.75 , 0.5625 ) right of 3 split mortar shot + .byte $40,$fe,$70,$ff ; ( -1.75 , -0.5625 ) left of 3 split mortar shot + +; values for initial launch velocity on hangar zone + .byte $00,$fb,$c0,$ff ; ( -5 , -0.25 ) - ENEMY_VAR_1 - 1 (aim slight left) + .byte $00,$fb,$80,$ff ; ( -5 , -0.5 ) - ENEMY_VAR_1 - 2 (aim farther left) + .byte $00,$fb,$40,$ff ; ( -5 , -0.75 ) - ENEMY_VAR_1 - 3 (aim even farther left) + .byte $00,$fb,$00,$ff ; ( -5 , -1 ) - ENEMY_VAR_1 - 4 (aim farthest left) + +; apply gravity, apply velocity, advance routine if reached apex of initial mortar shot +mortar_shot_routine_01: + jsr add_10_to_enemy_y_fract_vel ; add #$10 (gravity) to y fractional velocity (.06 faster) + jsr update_enemy_pos ; apply velocities and scrolling adjust + lda ENEMY_ATTRIBUTES,x ; load enemy attributes + bne @split_mortar ; branch if not initial mortar shot + lda ENEMY_Y_VELOCITY_FAST,x ; initial mortar shot, load y fast velocity + bpl mortar_shot_adv_routine ; advance to next routine if mortar falling down + lda ENEMY_Y_POS,x ; load enemy y position on screen + cmp #$30 ; height for mortar to divide + bcc mortar_shot_adv_routine ; branch if mortar shot has reached its apex to advance the routine + +@mortar_shot_routine_01_exit: + rts + +; split mortar shot, e.g. ENEMY_ATTRIBUTES > 0, check collision if necessary if so +@split_mortar: + lda ENEMY_Y_VELOCITY_FAST,x ; load fast y velocity + bmi @mortar_shot_routine_01_exit ; exit if y split mortar is still shooting up + jsr player_enemy_x_dist ; split mortar falling, a = closest x distance to enemy from players, y = closest player (#$00 or #$01) + lda ENEMY_Y_POS,x ; load enemy y position on screen + cmp SPRITE_Y_POS,y ; compare closest player by x distance's y position to current enemy y position + bcc @mortar_shot_routine_01_exit ; exit if mortar shot y position is higher than closest player is + jsr init_vars_get_enemy_bg_collision ; initialize required memory and call get_enemy_bg_collision to determine bg collision + beq @mortar_shot_routine_01_exit ; exit if no bg collision + lda #$24 ; bg collision, a = #$24 (sound_24) + jsr play_sound ; play explosion sound + lda #$07 ; a = #$07 + jmp set_enemy_routine_to_a ; set enemy routine index to mortar_shot_routine_03 + +; mortar shot falling down, create 3 split mortar rounds +mortar_shot_routine_02: + jsr update_enemy_pos ; apply velocities and scrolling adjust + lda #$03 ; a = #$03 (number of projectiles generated) + sta $08 ; store enemy attributes for mortars to create + txa ; transfer enemy slot offset to a + tay ; transfer enemy slot offset to y + +@generate_mortar_shot: + jsr find_next_enemy_slot ; find next available enemy slot, put result in x register + bne @advance_enemy_routine ; branch if no enemy slot was found + lda #$0b ; a = #$0b (#$0b = mortar) + sta ENEMY_TYPE,x ; set current enemy type to mortar + jsr initialize_enemy ; initialize enemy attributes + lda ENEMY_X_POS,y ; load created enemy x position on screen + sta ENEMY_X_POS,x ; set current mortar x position on screen to match + lda ENEMY_Y_POS,y ; load created enemy y position on screen + sta ENEMY_Y_POS,x ; set current mortar y position on screen to match + lda $08 ; load enemy attributes + sta ENEMY_ATTRIBUTES,x ; load appropriate enemy attribute (mortar velocities) + ; (see mortar_shot_velocity_tbl starting at 3rd entry) + dec $08 ; decrement mortar shot creation count + bne @generate_mortar_shot ; if haven't created 3 mortar shots loop to create next one + +@advance_enemy_routine: + ldx ENEMY_CURRENT_SLOT ; restore enemy slot + jmp advance_enemy_routine ; advance to next routine + +; determines firing direction based on enemy position ($08, $09) and player position ($0b, $0a) +; and creates bullet if appropriate +; input +; * a - bullet type +; * y - bullet speed code +; * $08 - enemy x position +; * $09 - enemy y position +; * $0b - player x position +; * $0a - player y position +aim_and_create_enemy_bullet: + sty $06 ; store bullet speed code in $06 + sta $00 ; store bullet type temporarily + lda #$01 ; a = #$01, use quadrant_aim_dir_01 + sta $0f ; quadrant_aim_dir_lookup_tbl offset (quadrant_aim_dir_01) + lda $0a ; load player y position + bpl @continue ; branch if >= #$00 + lda $0c ; load player y position + sta $0a ; set target y position + jsr get_quadrant_aim_dir ; get aim direction code for target ($0b, $0a) from location ($08, $09) using table code $0f + jmp @create_bullet_if_appropriate + +@continue: + jsr get_quadrant_aim_dir_for_player ; set a to the aim direction within a quadrant + ; based on source position ($09, $08) targeting player index $0a + +@create_bullet_if_appropriate: + ora $00 ; merge enemy bullet quadrant aim dir with bullet type code + sta $0a ; store bullet type and bullet velocity in $0a + jmp create_enemy_bullet_if_attack_enabled ; create enemy bullet (type $0a) at ($09, $08) in quadrant $07 and speed $06 + ; if ENEMY_ATTACK_FLAG is set or if level 1 boss cannonball + +; create enemy bullet (ENEMY_TYPE #$02) of type a (and angle) with speed y at ($09, $08) +; input +; * a = bullet type and angle +; * y = bullet speed (enemy attributes) +; * $08 = y position +; * $09 = x position +bullet_generation: + asl + +; creates a bullet if attack enabled with speed y, bullet type a, angle a at position ($09, $08) +; input +; * y - enemy bullet speed +; * a - (xxx. ....) specifies enemy bullet type: +; * #$00 - regular bullet +; * #$01 - level 1 boss large cannonball +; * #$02 - indoor large cannonball (boss screen) +; * #$03 - indoor regular bullet +; * #$04 - level 3 dragon boss fire ball (dragon arm orb projectile) +; * a - (...x xxxx) specifies bullet angle value (see bullet_fract_vel_dir_lookup_tbl) +; [#$00-#$17] is pointing right as value increments direction goes clockwise +; * $08 - y position +; * $09 - x position +; output +; zero flag - set when bullet created, clear when unable to create +create_enemy_bullet_angle_a: + sty $06 ; store enemy bullet speed + sta $0a ; store bullet type and angle: regular, cannonball, indoor bullet, etc. (see create_enemy_bullet) + and #$1f ; keep bits ...x xxxx (keep angle value) + ldy #$00 ; y = #$00 + cmp #$07 ; compare to left of straight down + bcc @continue ; branch if creating bullet that is firing right or down and to the right + cmp #$12 ; compare to firing straight up + bcs @continue ; branch if firing up and to the right + ldy #$02 ; firing left, set y to #$02 + +@continue: + cmp #$0d ; compare to firing straight left + bcc @set_dir_create_bullet ; branch if firing down (between 3 o'clock and 9 o'clock) + iny ; #$01 or #$03 + +@set_dir_create_bullet: + sty $07 ; set aim quadrant + +; create enemy bullet (type $0a) at ($09, $08) in quadrant $07 with quadrant aim dir in $0a and speed $06 +; if ENEMY_ATTACK_FLAG is set or if level 1 boss cannonball +; input +; * $08 - y position +; * $09 - x position +; * $0a - (xxx. ....) specifies enemy bullet type: +; * #$00 - regular bullet +; * #$01 - level 1 boss large cannonball +; * #$02 - indoor large cannonball (boss screen) +; * #$03 - indoor regular bullet +; * #$04 - level 3 dragon boss fire ball (dragon arm orb projectile) +; * $0a - (...x xxxx) specifies bullet quadrant aim value (see bullet_fract_vel_dir_lookup_tbl) +; [#$00-#$17] is pointing right as value increments direction goes clockwise +; * $06 - enemy bullet speed code (see bullet_velocity_adjust_ptr_tbl) +; * $07 - specifies quadrant to aim in (0 = quadrant IV, 1 = quadrant I, 2 = quadrant III, 3 = quadrant II) +; * bit 0 - 0 = bottom half of plane (quadrants III and IV), 1 = top half of plane (quadrants I and II) +; * bit 1 - 0 = right half of the plan (quadrants I and IV), 1 = left half of plane (quadrants II and III) +create_enemy_bullet_if_attack_enabled: + lda $0a ; load bullet type and bullet angle + and #$e0 ; keep bits xxx. .... (keep type value) + cmp #$20 ; compare to level 1 large cannonball (boss screen) bullet type + beq create_enemy_bullet ; always create bullet if bullet type #$01 (level 1 boss large cannonball) + lda ENEMY_ATTACK_FLAG ; see if enemies should attack + beq bullet_gen_exit ; don't shoot/create enemy bullet if ENEMY_ATTACK_FLAG is set + +; create enemy bullet +; input +; * $08 - y position +; * $09 - x position +; * $0a - (xxx. ....) specifies enemy bullet type: +; * #$00 - regular bullet +; * #$01 - level 1 boss large cannonball +; * #$02 - indoor large cannonball (boss screen) +; * #$03 - indoor regular bullet +; * #$04 - level 3 dragon boss fire ball (dragon arm orb projectile) +; * $0a - (...x xxxx) specifies bullet angle value (see bullet_fract_vel_dir_lookup_tbl) +; * $06 - enemy bullet speed code (see bullet_velocity_adjust_ptr_tbl) +; * $07 - specifies quadrant to aim in (0 = quadrant IV, 1 = quadrant I, 2 = quadrant III, 3 = quadrant II) +; * bit 0 - 0 = bottom half of plane (quadrants III and IV), 1 = top half of plane (quadrants I and II) +; * bit 1 - 0 = right half of the plan (quadrants I and IV), 1 = left half of plane (quadrants II and III) +; output +; zero flag - set when bullet created, clear when unable to create +create_enemy_bullet: + jsr find_next_enemy_slot ; find next available enemy slot for bullet, put result in x register + bne bullet_gen_exit ; branch if no enemy slot was found + lda #$01 ; a = #$01 (bullet) + sta ENEMY_TYPE,x ; set current enemy type to bullet + jsr initialize_enemy ; initialize enemy attributes + lda $0a ; load enemy bullet type + lsr + lsr + lsr + lsr + lsr + sta ENEMY_VAR_1,x ; store enemy bullet type in ENEMY_VAR_1 + lda $06 ; load enemy bullet speed code (see bullet_velocity_adjust_ptr_tbl) + cmp #$07 ; see if speed code is >= #$07 + bcc @continue ; continue if not too high + lda #$07 ; can't have more than speed code #$07, set to #$07 + +@continue: + sta $06 ; store speed code in $06 + lda $08 ; load enemy bullet y position + sta ENEMY_Y_POS,x ; store enemy bullet y position on screen + lda $09 ; load enemy bullet x position + sta ENEMY_X_POS,x ; store enemy bullet x position on screen + lda $0a + and #$1f ; keep bits ...x xxxx (quadrant aim dir) + +; sets the bullet/projectile X and Y velocities (both high and low) based on register a and $07 +; used by bullets, eye projectile, and spinning bubbles +; input +; * a - bullet angle value (bullet_fract_vel_dir_lookup_tbl offset) +; * $07 - specifies quadrant to aim in (0 = quadrant IV, 1 = quadrant I, 2 = quadrant III, 3 = quadrant II) +; * bit 0 - 0 = bottom half of plane (quadrants III and IV), 1 = top half of plane (quadrants I and II) +; * bit 1 - 0 = right half of the plan (quadrants I and IV), 1 = left half of plane (quadrants II and III) +; output +; * a - #$00 +set_bullet_velocities: + jsr calc_bullet_velocities ; determine the bullet velocities based on quadrant aim dir (a) and quadrant ($07) + lda $05 ; load enemy bullet y velocity fast + sta ENEMY_Y_VELOCITY_FAST,x ; set enemy bullet y velocity fast + lda $04 ; load enemy bullet y fractional velocity + sta ENEMY_Y_VELOCITY_FRACT,x ; set enemy bullet y fractional velocity + lda $0b ; load enemy bullet x velocity fast + sta ENEMY_X_VELOCITY_FAST,x ; set enemy bullet x velocity fast + lda $0a ; load enemy bullet x fractional velocity + sta ENEMY_X_VELOCITY_FRACT,x ; set enemy bullet x fractional velocity + ldx ENEMY_CURRENT_SLOT ; load enemy current slot (doesn't seem necessary) + lda #$00 ; clear a + rts + +; no enemy slot available +bullet_gen_exit: + ldx ENEMY_CURRENT_SLOT + lda #$01 ; a = #$01 + rts + +; determine the bullet velocities based on quadrant aim dir (a) and quadrant ($07) +; input +; * a - quadrant aim dir (see bullet_fract_vel_dir_lookup_tbl) +; * $07 - specifies quadrant to aim in (0 = quadrant IV, 1 = quadrant I, 2 = quadrant III, 3 = quadrant II) +; * bit 0 - 0 = bottom half of plane (quadrants III and IV), 1 = top half of plane (quadrants I and II) +; * bit 1 - 0 = right half of the plan (quadrants I and IV), 1 = left half of plane (quadrants II and III) +; output +; * $04 - bullet y fractional velocity value +; * $05 - bullet y velocity fast value +; * $0a - bullet x fractional velocity value +; * $0b - bullet x velocity fast value +calc_bullet_velocities: + tay ; store quadrant aim direction in y + lda bullet_fract_vel_dir_lookup_tbl,y + tay + lda bullet_fract_vel_tbl+1,y ; load the bullet fractional x velocity + sta $04 ; store bullet x fractional velocity byte + lda #$00 ; set x velocity fast value to #$00 + sta $05 ; store bullet x velocity fast value + jsr adjust_bullet_velocity ; adjust bullet x velocity based on speed code ($06) + lda $04 ; load bullet x fractional velocity byte + sta $0a ; set bullet x fractional velocity byte + lda $05 ; load bullet x velocity fast value + sta $0b ; set bullet x velocity fast value + lda bullet_fract_vel_tbl,y ; load bullet y fractional velocity + sta $04 ; set bullet y fractional velocity + lda #$00 ; load bullet y fast velocity + sta $05 ; set bullet y fast velocity + jsr adjust_bullet_velocity ; adjust bullet y velocity based on speed code ($06) + lda $07 ; load bullet direction + lsr ; puts bit 0 to carry (up down bit) + bcc @set_x_vel ; branch if firing down + lda #$00 ; bullet firing up, flip y velocity so bullet travels up instead of down + sec ; set carry flag in preparation for subtraction + sbc $04 ; #$00 - $04 + sta $04 ; update y fractional velocity to be negative + lda #$00 ; a = #$00 + sbc $05 ; #$00 - $05 + sta $05 ; update y velocity fast to be negative + +@set_x_vel: + lda $07 ; load bullet direction + lsr + lsr + bcc @exit ; exit if firing right + lda #$00 ; firing left, a = #$00 + sec ; set carry flag in preparation for subtraction + sbc $0a ; negate bullet x fractional velocity + sta $0a ; set bullet x fractional velocity value + lda #$00 ; a = #$00 + sbc $0b ; negate bullet x fast velocity + sta $0b ; set bullet x velocity fast value + +@exit: + rts + +; table of enemy bullet fractional velocity indexes based on quadrant aim direction (#$18 bytes) +; offsets into bullet_fract_vel_tbl +; [#$00-#$17] #$00 is pointing right as value increments direction goes clockwise +; #$00 right, #$03 is down, #$06 left, #$09 is straight up +bullet_fract_vel_dir_lookup_tbl: + .byte $00,$02,$04,$06,$08,$0a ; quadrant IV + .byte $0c,$0a,$08,$06,$04,$02 ; quadrant III + .byte $00,$02,$04,$06,$08,$0a ; quadrant II + .byte $0c,$0a,$08,$06,$04,$02 ; quadrant I + +; table for bullet x and y fractional velocities, based on index specified from bullet_fract_vel_dir_lookup_tbl (#$d bytes) +; byte 0 - y fractional velocity +; byte 1 - x fractional velocity +bullet_fract_vel_tbl: + .byte $00,$ff ; x velocity + .byte $42,$f7 + .byte $80,$dd + .byte $b5,$b5 + .byte $dd,$80 + .byte $f7,$42 + .byte $ff,$00 ; shooting horizontally + +; adjusts bullet x or y velocity based on speed code (#$0-#$07) +; e.g. bullet speed code #$01 is .75 speed +; assumes fast velocity will always be #$00, otherwise, math won't work in all cases +; input +; * $04 - bullet fractional velocity value (either x dir or y dir) +; * $05 - bullet velocity fast value (either x dir or y dir) +; * $06 - bullet speed code +; output +; * $04 - bullet fractional velocity value (either x dir or y dir) +; * $05 - bullet velocity fast value (either x dir or y dir) +adjust_bullet_velocity: + lda $06 ; bullet speed (0-7) + and #$07 ; keep bits .... .xxx + jsr run_routine_from_tbl_below ; run routine a in the following table (bullet_velocity_adjust_ptr_tbl) + +; pointer table for bullet speeds (#$9 * #$2 = #$12 bytes) +bullet_velocity_adjust_ptr_tbl: + .addr bullet_velocity_adjust_00 ; CPU address $f3be (.5x speed) + .addr bullet_velocity_adjust_01 ; CPU address $f3c3 (.75x speed) + .addr bullet_velocity_adjust_02 ; CPU address $f3e4 (normal speed) + .addr bullet_velocity_adjust_03 ; CPU address $f3ca (1.25x speed) + .addr bullet_velocity_adjust_04 ; CPU address $f3d3 (1.5x speed) + .addr bullet_velocity_adjust_05 ; CPU address $f3e5 (1.62x speed) + .addr bullet_velocity_adjust_06 ; CPU address $f3f0 (1.75x speed) + .addr bullet_velocity_adjust_07 ; CPU address $f3ff (1.87x speed) + .addr bullet_velocity_adjust_08 ; CPU address $f415 (2x speed) impossible? !(HUH) + +; bullet speed 0 (.5x speed) +; halves fast and slow velocity, e.g. #$03 #80 (3.5) becomes #$01 #$c0 (1.75) +bullet_velocity_adjust_00: + lsr $05 ; half fast velocity + ror $04 ; half fractional value, including carry from fast velocity + rts + +; bullet speed 1 (.75x speed) +; first half value, then half that again to add to the originally halved value +bullet_velocity_adjust_01: + lsr $05 ; half fast velocity + ror $04 ; half fractional value, including carry from fast velocity + jmp bullet_velocity_adjust_04 + +; bullet speed 3 (1.25x speed) +; halves fast and fractional velocity, halves fractional again and adds it to original velocity +bullet_velocity_adjust_03: + lda $05 ; load fast velocity + lsr ; half fast velocity + lda $04 ; load fractional velocity + ror ; half fractional velocity, including carry from fast velocity + lsr ; half fractional velocity again + bpl bullet_velocity_adjust_add_a ; add .25 * to original velocity + +; bullet speed 4 (1.5x speed) +bullet_velocity_adjust_04: + lda $05 ; load fast velocity + lsr ; half fast velocity + lda $04 ; load original fractional velocity + ror ; half fractional velocity, including carry from fast velocity + +bullet_velocity_adjust_add_a: + clc ; clear carry in preparation for addition + adc $04 ; add to original value of $04 + sta $04 ; store value back in $04 + lda $05 ; re-load $05 + adc #$00 ; add any carry + sta $05 ; update $05 + +; bullet speed 2 (normal speed) +bullet_velocity_adjust_02: + rts + +; bullet speed 5 (1.62x speed) +bullet_velocity_adjust_05: + lda $05 + lsr + lda $04 + ror + sta $00 + lsr + bpl bullet_dir_half_a_add_to_vel + +; bullet speed 6 (1.75x speed) (for any value less than 1.14) +; doesn't work correctly when carry from fractional velocity, e.g. 1.5 becomes 1.62 and not 2.62 +; however, 1.1 (#$01 #1a) correctly goes to (#$01 #$ed) (1.92) +bullet_velocity_adjust_06: + lda $05 + lsr + lda $04 + ror + sta $00 + +bullet_dir_half_a_add_to_vel: + lsr + clc ; clear carry in preparation for addition + adc $00 + jmp bullet_velocity_adjust_add_a + +; bullet speed 7 (1.87x speed) +; doesn't work correctly when carry from fractional velocity, e.g. 1.5 becomes 1.81 and not 2.81 +; however, 1.05 (#$01 #0d) correctly goes to (#$01 #$f7) (1.96) +bullet_velocity_adjust_07: + lda $05 + lsr + lda $04 + ror + sta $00 + lsr + sta $01 + clc ; clear carry in preparation for addition + adc $00 + lsr $01 + clc ; clear carry in preparation for addition + adc $01 + jmp bullet_velocity_adjust_add_a + +; bullet speed 8 (2x speed) (impossible ?) +bullet_velocity_adjust_08: + asl $04 ; double fast velocity + rol $05 + rts + +; either increments or decrements ENEMY_VAR_1 by 1 to aim towards the player using quadrant_aim_dir_01 +; used by spinning bubbles (enemy type = #$1d), tank (enemy type = #$12), and white blob (enemy type = #$13) +; input +; * $0a - player index +; output +; * carry flag - set when enemy already aiming at player, clear when rotation happened +; * minus flag +; * zero flag - clear when clockwise direction, set when counterclockwise +aim_var_1_for_quadrant_aim_dir_01: + jsr get_rotate_01 ; get enemy aim direction and rotation direction using quadrant_aim_dir_01 + jmp rotate_enemy_var_1 ; rotate the enemy's aim by one in a clockwise or counterclockwise direction if needed + +; either increments or decrements ENEMY_VAR_1 by 1 to aim towards the player using quadrant_aim_dir_00 +; used by rotating gun (enemy type = #$04) and alien fetus (enemy type = #$11) +; output +; * ENEMY_VAR_1,x - enemy aim direction [#$00-#$0b] #$00 when facing right incrementing clockwise +; * carry flag - set when enemy already aiming at player, clear when rotation happened +aim_var_1_for_quadrant_aim_dir_00: + jsr get_rotate_00 ; get enemy aim direction and rotation direction using quadrant_aim_dir_00 + +; rotate the enemy's aim by one in a clockwise or counterclockwise direction +; input +; * minus flag - set when enemy is already aiming at player and no rotation is required +; * carry flag - set when enemy already aiming at player, clear when rotation happened +; * zero flag - clear when clockwise direction, set when counterclockwise +rotate_enemy_var_1: + bmi @set_carry_exit ; exit if enemy is already aiming at the player + bne @rotate_1_counterclockwise ; if a = #$01, then a counterclockwise rotation + inc ENEMY_VAR_1,x ; move enemy aim direction clockwise + lda ENEMY_VAR_1,x ; load enemy aim direction + cmp $06 ; compare maximum supported enemy aim dir, e.g. rotating gun is #$0b + bcc @continue ; continue if not past last position + lda #$00 ; wrapped around, set to first aim direction #$00 (horizontal left) + beq @set_var_1_continue + +; rotate counter-clockwise +@rotate_1_counterclockwise: + dec ENEMY_VAR_1,x ; update enemy aim direction + ; moves direction counter clockwise + bpl @continue ; branch if counter-clockwise doesn't cause underflow + lda $06 ; load maximum number of the supported enemy aim dir, e.g. rotating gun is #$0c (left and slightly down) + sec ; set carry flag in preparation for subtraction + sbc #$01 ; subtract one + +@set_var_1_continue: + sta ENEMY_VAR_1,x ; update enemy aim direction + +@continue: + lda ENEMY_VAR_1,x ; load enemy aim direction + cmp $0c ; compare to new enemy position as determined by get_rotate_00 + beq @set_carry_exit ; set carry and exit + clc ; not yet at desired direction, clear carry and exit + rts + +; rotating gun is at desired position, set carry and exit +@set_carry_exit: + sec ; set carry flag + rts + +; determines which direction to rotate based on quadrant_aim_dir_00 +; targetting player index ($0a) +; input +; * $0a - player index to target, 0 = player 1, 1 = player 2 +; * $08 - source y position +; * $09 - source x position +; output +; * negative flag - set when enemy is already aiming at player and no rotation is needed +; * a - rotation direction, #$00 clockwise, #$01 counterclockwise, #$80 no rotation needed +; * $0c - new enemy aim direction +get_rotate_00: + lda #$00 ; a = #$00 (use quadrant_aim_dir_00) + beq get_rotate_dir_for_index ; always jump, get enemy aim direction and rotation direction using quadrant_aim_dir_00 + +; determines which direction to rotate based on quadrant_aim_dir_01 +; targetting player index ($0a) +; input +; * $0a - player index to target, 0 = player 1, 1 = player 2 +; * $08 - source y position +; * $09 - source x position +; output +; * negative flag - set when enemy is already aiming at player and no rotation is needed +; * a - rotation direction, #$00 clockwise, #$01 counterclockwise, #$80 no rotation needed +; * $0c - new enemy aim direction +get_rotate_01: + lda #$01 ; a = #$01 (use quadrant_aim_dir_01) + +; determines which direction to rotate based on quadrant_aim_dir_lookup_tbl index offset (a) +; targetting player index ($0a) +; input +; * a - quadrant_aim_dir_lookup_tbl offset table +; * $0a - player index to target, 0 = player 1, 1 = player 2 +; * $08 - source y position +; * $09 - source x position +; output +; * negative flag - set when enemy is already aiming at player and no rotation is needed +; * a - rotation direction, #$00 clockwise, #$01 counterclockwise, #$80 no rotation needed +; * $0c - new enemy aim direction +get_rotate_dir_for_index: + sta $0f ; set quadrant_aim_dir_lookup_tbl index offset + lda $0a ; load player index + bpl @get_quadrant_aim_dir ; branch if closest player has been determined + lda $0c ; no player to target, not sure when this happens (see player_enemy_x_dist) + ; even when both player states are not normal, still targets player 1 + sta $0a ; set $0c as player index + jsr get_quadrant_aim_dir ; get aim direction code for target ($0b, $0a) from location ($08, $09) using table code $0f + ; depending on quadrant_aim_dir_xx, dir code will be [#$00-#$03], [#$00-#$06], or [#$00-#$0f] + jmp get_rotate_dir ; determine which direction to rotate + ; based on a (quadrant aim dir) and quadrant ($07) + +@get_quadrant_aim_dir: + jsr get_quadrant_aim_dir_for_player ; set a to the aim direction within a quadrant + ; based on source position ($09, $08) targeting player index $0a + +; determines which direction to rotate +; based on a (quadrant aim dir) and quadrant ($07) +; input +; * a - quadrant aim direction (quadrant_aim_dir_xx value) +; * $07 - specifies quadrant to aim in (0 = quadrant IV, 1 = quadrant I, 2 = quadrant III, 3 = quadrant II) +; * bit 0 - 0 = bottom half of plane (quadrants III and IV), 1 = top half of plane (quadrants I and II) +; * bit 1 - 0 = right half of the plan (quadrants I and IV), 1 = left half of plane (quadrants II and III) +; * $0f - quadrant_aim_dir_lookup_tbl offset +; output +; * negative flag - set when enemy is already aiming at player and no rotation is needed +; * a - rotation direction, #$00 clockwise, #$01 counterclockwise, #$80 no rotation needed +; * $0c - new enemy aim direction +get_rotate_dir: + sta $0c ; store quadrant aim direction code in $0c + lda $0f ; load quadrant_aim_dir_lookup_tbl offset (which quadrant_aim_dir_xx to use) + lsr ; move bit 0 to the carry + lda #$06 ; using either quadrant_aim_dir_00, or quadrant_aim_dir_02 + ; midway direction, i.e. 9 o'clock + ldy #$0c ; maximum aim direction (same as #$00 aim dir), i.e. 3 o'clock + bcc @continue ; branch if aim type quadrant_aim_dir_00 or quadrant_aim_dir_02 + lda #$0c ; using quadrant_aim_dir_01 + ; midway direction, i.e. 9 o'clock + ldy #$18 ; maximum aim direction (same as #$00 aim dir), i.e. 3 o'clock + +@continue: + sta $05 ; store either #$06 or #$0c into $06, which is the midway aim direction, i.e. 9 o'clock + sty $06 ; store either #$0c or #$18 into $06, which is the maximum aim direction, i.e. 3 o'clock + lda $07 ; load player position relative to enemy (left/right and above/below) + and #$02 ; keep bit 1 (set when player to the left) + beq @check_player_pos ; branch if player to the right of the enemy (#$00 or #$01) + lda $05 ; player to left of enemy enemy, load mid way direction (either #$06 or #$0c) + sec ; set carry flag in preparation for subtraction + sbc $0c ; subtract quadrant aim direction code from from midway direction point ($05 - $0c) + sta $0c ; store result back into $0c + +@check_player_pos: + lsr $07 ; shift right the position relative to enemy (left/right and above/below) + bcc @calc_reflected_dir ; branch if player below enemy + lda $06 ; player is above enemy, need to reflect aim dir ($0c) across x-axis + ; load max direction value (#$0c or #$18) + sec ; set carry flag in preparation for subtraction + sbc $0c ; subtract either the quadrant aim direction code directly from max aim direction (when player to right) + ; or subtract the offset amount from half-way (when player to left) from max aim direction (reflect across x-axis) + ; ($06 - $0c) + cmp $06 ; compare to max direction value + bcc @continue2 ; branch if aim direction wasn't #$00 + lda #$00 ; direction result was #$00, set value to #$00 (right 3 o'clock) + +@continue2: + sta $0c ; set new aim direction + +; calculates the aim direction reflected along the vertical axis +; e.g. if original aim direction #$00 is right and increment clockwise +; reflected aim direction #$00 is left increment clockwise +@calc_reflected_dir: + lda #$00 ; a = #$00 + sta $0e ; initialize value of $0e to #$00 + lda ENEMY_VAR_1,x ; load current enemy aim direction (#$00 facing right) + clc ; clear carry in preparation for addition + adc $05 ; add either #$06 or #$0c to current enemy aim dir (halfway point) + cmp $06 ; see if current enemy aim direction is in first half or second half of aim directions + ; used for calculating 'reflected' aim direction + bcc @determine_rotation ; branch if no wraparound, i.e. first half of aim directions + inc $0e ; current aim direction is greater than half way point + ; set $0e to #$01 + sbc $06 ; subtract max aim direction value to get correct reflected dir (modulus) + +; check to see if need to rotate, and if so, which direction +; all examples assume quadrant_aim_dir_00 or quadrant_aim_dir_02 +; $0c - new aim direction. Example: right is #$00, increment clockwise +; $0d - reflected new aim direction. Example: left is #$00, increment clockwise +; $0e - whether or not new direction has gone through the maximum direction, e.g. old value #$0a, new value #$03 +; ENEMY_VAR_1 - previous enemy aim direction - (right is #$00, increment clockwise) +@determine_rotation: + sta $0d ; store 'reflected' aim direction in $0d + lda $0c ; load new enemy aim direction + cmp ENEMY_VAR_1,x ; compare to current enemy aim dir (#$00 is right) + beq @no_dir_change ; if the calculated enemy aim dir is the same, no need to move, set minus flag and exit + ldy $0e ; need to rotate to new enemy aim dir, load prefered direction + bne @wrapped_rotation_check_dir ; branch if new direction caused a 'wrap around' + bcc @rotate_counterclockwise ; no wrap occurred and new direction is less than current, rotate counterclockwise + cmp $0d ; compare 'reflected' dir to current enemy aim dir, to find shortest direction + bcs @rotate_counterclockwise ; rotate counterclockwise if reflected enemy aim dir is > new aim dir + ; e.g. no wrap occurred, $0c is #$09 (up) (this means $0d is #$03) + ; and suppose ENEMY_VAR_1 is #$0a (up-right), rotate counter clockwise + +; clockwise rotation +@rotate_clockwise: + lda #$00 ; a = #$00 + beq @exit + +; when determining the new aim direction for the enemy, the direction 'wrapped around' the max dir, e.g. #$0b +; so while the enemy aim direction may have gotten smaller, it doesn't necessarily imply a counterclockwise rotation +@wrapped_rotation_check_dir: + bcs @rotate_clockwise ; rotate clockwise if new aim direction is greater than old aim direction + cmp $0d ; compare 'reflected' dir to current enemy aim dir, to find shortest direction + bcc @rotate_clockwise ; rotate clockwise if reflected enemy aim dir is < new aim dir + ; e.g. wrap occurred, $0c is #$03 (down) (this means $0d is #$09) + ; and suppose ENEMY_VAR_1 is #$0a (up-right), rotate clockwise + +@rotate_counterclockwise: + lda #$01 ; a = #$01 + +@exit: + rts + +@no_dir_change: + lda #$80 ; a = #$80 + bne @exit ; always exit + +; determines whether dragon arm orb should move, and if so, in which direction +; dragon_arm_orb_routine_03 - related to dragon arm orb seeking/following the players (ENEMY_FRAME #$04) +; input +; * x - current enemy slot index for for the dragon arm orb +; output +; * minus flag - set when orb doesn't need to move, clear otherwise +; * a - #$00, #$01 or #$80 +dragon_arm_orb_seek_should_move: + jsr set_08_09_to_enemy_pos ; set $08 and $09 to enemy x's X and Y position + lda #$02 ; dragon arm orb is only enemy that uses quadrant_aim_dir_02 + sta $0f ; set quadrant_aim_dir_lookup_tbl offset to use quadrant_aim_dir_02 + jsr get_quadrant_aim_dir_for_player ; set a to the aim direction within a quadrant + ; based on source position ($09, $08) targeting player index $0a + sta $0c ; store enemy aim direction in $0c + ldy ENEMY_VAR_3,x ; load next dragon arm orb (farther from body) enemy index + lda $07 ; load player position relative to enemy + lsr + lsr + bcc @check_player_vert_pos ; branch if player on right side enemy (and below) + lda #$20 ; player to the left, load a = #$20 + sec ; set carry flag in preparation for subtraction + sbc $0c ; subtract enemy aim direction from #$20 (#$20 - $0c) + sta $0c ; update enemy aim direction to now point + +@check_player_vert_pos: + lsr $07 ; move bit 0 to carry flag + bcc @player_below_enemy ; branch if player below enemy + lda #$40 ; player is above enemy, set a = #$40 + ; this doesn't seem possible for dragon arm orb enemy + sec ; set carry flag in preparation for subtraction + sbc $0c ; subtract enemy aim direction from #$40 (#$40 - $0c) + and #$3f ; keep bits ..xx xxxx + sta $0c ; update enemy aim direction + +@player_below_enemy: + lda #$00 ; a = #$00 + sta $0e + lda ENEMY_X_VELOCITY_FAST,y ; load next orb's enemy position index (see dragon_arm_orb_pos_tbl) + clc ; clear carry in preparation for addition + adc #$20 ; add #$20 to next orb's position index (2 rows) + cmp #$40 ; see if position index is on the last row + bcc @b3 ; branch if not on last row of dragon_arm_orb_pos_tbl + inc $0e ; on last row of dragon_arm_orb_pos_tbl, increment $0e + sbc #$40 ; set index into 0th row of dragon_arm_orb_pos_tbl + +@b3: + sta $0d ; store adjusted enemy position index in $0d + lda $0c ; load enemy aim direction + cmp ENEMY_X_VELOCITY_FAST,y ; compare enemy aim direction to next dragon arm orb's fast x velocity + beq @set_negative_exit + ldy $0e ; load whether or not the position index was on the last row + bne @continue + bcc @clear_zero_exit + cmp $0d + bcs @clear_zero_exit + +@loop: + lda #$00 ; a = #$00 + beq @exit ; always exit with zero flag set + +@continue: + bcs @loop + cmp $0d + bcc @loop + +; exit with the zero flag clear +@clear_zero_exit: + lda #$01 ; a = #$01 + +@exit: + rts + +; set negative flag, clear zero flag, exit +@set_negative_exit: + lda #$80 ; a = #$80 + bne @exit ; always branch + +; determines the aim direction within a quadrant based on source position ($09, $08) targeting player index $0a +; input +; * $0f - quadrant_aim_dir_lookup_tbl offset [#$00-#$02] +; * $0a - player index of player to target (#$00 for p1 or #$01 for p2) +; * $08 - source y position +; * $09 - source x position +; output +; * a - player aim direction (for most things this is an offset into bullet_fract_vel_dir_lookup_tbl) +; when called for dragon boss arm orbs, it is a reference to dragon_arm_orb_pos_tbl) +; * $07 - player position relative to enemy (left/right and above/below) +; * #$00 = player below enemy (or equal) and to the right +; * #$01 = player above enemy and to the right +; * #$02 = player to left of enemy and player below enemy (or equal) +; * #$03 = player to left of enemy and player above enemy +get_quadrant_aim_dir_for_player: + lda $0a ; load the player player index + and #$01 ; should only be #$00 or #$01 (p1 or p2) + tay ; transfer to y + lda PLAYER_STATE,y ; load the closest player's PLAYER_STATE + cmp #$01 ; see if normal state + beq @get_y_pos ; branch if normal state + tya ; not normal state, either falling, dead or can't move + eor #$01 ; flip to other player + tay + lda PLAYER_STATE,y ; load other player's PLAYER_STATE + cmp #$01 ; see if normal state + beq @get_y_pos ; branch if normal state + lda #$ff ; other player also not in normal state + sta $0a ; set player y position to #$ff (bottom of screen) + lda #$80 ; a = #$80 + sta $0b ; set player x position to #$80 (center of screen) + bne get_quadrant_aim_dir ; always branch, get aim direction code for target ($0b, $0a) from location ($08, $09) using table code $0f + +@get_y_pos: + lda LEVEL_LOCATION_TYPE ; 0 = outdoor; 1 = indoor + lsr + lda #$b0 ; player y position is set at #$a8 on indoor levels + ; note this means that enemies won't aim correctly at a player who is jumping on an indoor level + bcs @get_x_pos ; branch for indoor level + lda SPRITE_Y_POS,y ; outdoor level, load y position from memory + +@get_x_pos: + sta $0a ; store player Y location in $0a + lda SPRITE_X_POS,y ; load x position from memory + sta $0b ; store player X position in $0b + +; determines the aim direction within a quadrant based on source position ($09, $08) targeting player location ($0b, $0a) +; input +; * $08 - source y position +; * $09 - source x position +; * $0a - closest player y position +; * $0b - closest player x position +; * $0f - which of the #$03 tables from quadrant_aim_dir_lookup_tbl to use +; output +; * a - quadrant aim direction (quadrant_aim_dir_xx value) +; * $07 - specifies quadrant to aim in (0 = quadrant IV, 1 = quadrant I, 2 = quadrant III, 3 = quadrant II) +; * bit 0 - 0 = bottom half of plane (quadrants III and IV), 1 = top half of plane (quadrants I and II) +; * bit 1 - 0 = right half of the plan (quadrants I and IV), 1 = left half of plane (quadrants II and III) +get_quadrant_aim_dir: + ldy #$00 ; default assume player is to the right and equal to or below enemy + lda $0a ; load closest player y position + sec ; set carry flag in preparation for subtraction + sbc $08 ; subract enemy y position from player y position + bcs @shift_get_x_diff ; branch if no overflow occurred (enemy above player or same vertical position) + eor #$ff ; enemy below player, handle overflow, flip all bits and add one + adc #$01 + iny ; y used to keep track of where player is in relation to enemy, e.g. $07 + ; mark that enemy was below player + +@shift_get_x_diff: + lsr ; shift the difference between player and enemy y difference 5 bits + lsr ; (every #$20 pixels difference is a new horizontal direction) + lsr + lsr + lsr + sta $0a ; store result in $0a (row offset for quadrant_aim_dir_xx) + lda $0b ; load player x position + sec ; set carry flag in preparation for subtraction + sbc $09 ; subtract enemy x position from player x position + bcs @continue ; branch if no overflow (player to right of enemy) + eor #$ff ; enemy to left of player, handle overflow, flip all bits and add one + adc #$01 + iny ; player to left of enemy, increment y by two to set correct relative position + iny ; if y was 0, now is 2, if y was 1, now is 3 + +@continue: + lsr ; shift the difference between player and enemy x difference 6 bits + lsr ; (every #$40 pixels difference is a new horizontal direction) + lsr + lsr + lsr + sty $07 ; store position of player relative to enemy in $07 (above/below, left/right) + lsr ; push bit 5 to the carry flag for use after plp instruction below + sta $0b ; overwrite player x position with shifted bits 6 and 7 + ; (values [#$00-#$03]) of horizontal distance + php ; backup CPU status flags on stack + lda $0f ; load which of the #$03 tables from quadrant_aim_dir_lookup_tbl to use + asl ; double since each entry is #$2 bytes + tay ; transfer to offset register + lda quadrant_aim_dir_lookup_tbl,y ; get low byte of quadrant_aim_dir_xx address + sta $0c ; store low byte of pointer address in $0c + lda quadrant_aim_dir_lookup_tbl+1,y ; get high byte of quadrant_aim_dir_xx address + sta $0d ; store high byte of pointer address in $0d + lda $0a ; load y difference to determine row offset + asl + asl ; quadruple since each entry is #$04 bytes to get correct row + adc $0b ; add the x distance between player and enemy as offset into the entry to load + ; this gets the column of the aim direction + tay ; transfer to offset register + lda ($0c),y ; load specific byte + plp ; restore CPU status flags from stack + bcs @set_and_exit ; branch if bit 5 of difference between player and enemy was set + lsr ; this segments screen into bands for which nibble to use + lsr + lsr + lsr + +@set_and_exit: + and #$0f ; keep low nibble + rts + +; pointer table for set of quadran aim directions (#$3 * #$2 = #$6 bytes) +quadrant_aim_dir_lookup_tbl: + .addr quadrant_aim_dir_00 ; CPU address $f5b2 (soldiers, weapon boxes, red turrets, wall core) + .addr quadrant_aim_dir_01 ; CPU address $f5d2 (rotating gun, wall turrets, sniper, eye projectile, spinning bubbles, jumping soldier, white blob) + .addr quadrant_aim_dir_02 ; CPU address $f5f2 (dragon arm seeking) + +; table for where to aim within a quadrant that is split into 3 parts [#$00-#$03] (#$20 bytes) +; * used by soldiers, weapon boxes, red turrets, wall turrets, +; wall core, and dragon arm orb projectiles +; (not arm seeking, that's #$02) +; * which nibble used from byte depends on bit 5 of difference between player and enemy distance +; * each subsequent row is player farther away from enemy with respect to y (height) +; * each subsequent column is player farther away from enemy with respect to x (distance) +quadrant_aim_dir_00: + .byte $00,$00,$00,$00 ; player at same height + .byte $32,$11,$00,$00 + .byte $32,$11,$11,$11 + .byte $32,$22,$11,$11 + .byte $33,$22,$11,$11 + .byte $33,$22,$22,$11 + .byte $33,$22,$22,$11 + .byte $33,$22,$22,$22 + +; table for where to aim within a quadrant that is split into 6 parts [#$00-#$06] (#$20 bytes) +; when used indoors only one 'quadrant' so the quadrant aim dir is the same as the aim dir +; * indoor levels exclusively use this table: wall turret, eye projectile, spinning bubbles +; jumping soldier, and wall core +; * also used by rotating gun, sniper, white blob, spinning bubbles, and tank +; * which nibble used from byte depends on bit 5 of difference between player and enemy distance +; * each subsequent row is player farther away from enemy with respect to y (height) +; * each subsequent column is player farther away from enemy with respect to x (distance) +quadrant_aim_dir_01: + .byte $00,$00,$00,$00 ; player at same height + .byte $63,$21,$11,$11 + .byte $64,$32,$21,$11 + .byte $65,$43,$22,$22 + .byte $65,$44,$33,$22 + .byte $65,$54,$33,$32 + .byte $65,$54,$43,$33 + .byte $65,$54,$44,$33 + +; table for where to aim within a quadrant that is split into #$0f parts [#$00-#$0f] (#$20 bytes) +; * used by dragon arm seeking (not projectile firing that's #$00) +; * which nibble used from byte depends on bit 5 of difference between player and enemy distance +; * each subsequent row is player farther away from enemy with respect to y (height) +; * each subsequent column is player farther away from enemy with respect to x (distance) +quadrant_aim_dir_02: + .byte $80,$00,$00,$00 + .byte $f8,$53,$32,$21 + .byte $fb,$86,$54,$33 + .byte $fd,$a8,$75,$54 + .byte $fe,$b9,$87,$65 + .byte $fe,$cb,$98,$76 + .byte $fe,$db,$a9,$87 + .byte $ff,$dc,$ba,$98 + +; unused space #$5ee bytes +bank_7_unused_space: + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + +; DPCM (differential pulse code modulation) audio sample used by DMC (delta modulation channel) +; CPU address $fc00 (#$51 bytes) See dpcm_sample_data_tbl +dpcm_sample_00: + .incbin "assets/audio_data/dpcm_sample_00.bin" + +; possibly unused DPCM sample, #$5f bytes (excluding #$ff) +; CPU address $fc51 +; exists in Japanese version of game as well (I don't think it's used there either) +unknown_00: + .byte $6b,$56,$ce,$b5,$5b,$5d,$59,$b6,$d5,$ab,$d6,$b5,$d7,$6b,$6d,$ad + .byte $ae,$b6,$d6,$b5,$b5,$aa,$d6,$aa,$ac,$aa,$a9,$54,$a9,$94,$a4,$a9 + .byte $4a,$4a,$8a,$92,$54,$a5,$49,$29,$4a,$54,$a5,$29,$52,$a5,$4a,$a5 + .byte $54,$a9,$54,$aa,$95,$2a,$aa,$94,$95,$2a,$aa,$55,$55,$2a,$56,$66 + .byte $aa,$9a,$aa,$b5,$5a,$ad,$ab,$5a,$b5,$6b,$6b,$6b,$5a,$b5,$aa,$b5 + .byte $56,$aa,$d5,$55,$56,$aa,$aa,$aa,$aa,$aa,$aa,$aa,$95,$55,$55,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + +; DPCM (differential pulse code modulation) audio sample used by DMC (delta modulation channel) +; CPU address $fcc0 (#$251 bytes). See dpcm_sample_data_tbl +dpcm_sample_01: + .incbin "assets/audio_data/dpcm_sample_01.bin" + +; possibly unused DPCM sample, #$a8 bytes (excluding #$ff) +; CPU address $ff0b +; exists in Japanese version of game as well (I don't think it's used there either) +unknown_01: + .byte $4c,$aa,$ca,$aa,$a5,$a6,$56,$55,$54,$d3,$2a,$c6,$aa,$6a,$96,$a6 + .byte $66,$aa,$aa,$b2,$b4,$d5,$55,$66,$9a,$aa,$aa,$aa,$aa,$aa,$aa,$aa + .byte $aa,$aa,$aa,$9a,$a6,$56,$55,$52,$b2,$aa,$aa,$aa,$96,$aa,$65,$aa + .byte $aa,$aa,$b5,$55,$56,$6a,$6a,$aa,$aa,$aa,$6a,$72,$9a,$aa,$9a,$9a + .byte $a9,$96,$59,$55,$55,$55,$52,$d2,$b2,$aa,$a9,$aa,$aa,$aa,$ac,$b2 + .byte $cc,$d5,$55,$55,$65,$96,$59,$aa,$aa,$9a,$9a,$9a,$aa,$6a,$56,$a5 + .byte $65,$95,$55,$55,$55,$54,$b5,$32,$aa,$aa,$b2,$aa,$b2,$aa,$ab,$55 + .byte $55,$55,$55,$55,$69,$aa,$aa,$96,$9a,$99,$5a,$59,$5a,$55,$65,$96 + .byte $59,$55,$55,$55,$55,$55,$52,$b4,$aa,$aa,$aa,$aa,$b2,$d3,$55,$55 + .byte $55,$55,$55,$56,$55,$66,$aa,$aa,$aa,$aa,$a6,$9a,$aa,$65,$55,$55 + .byte $55,$55,$55,$32,$cc,$aa,$aa,$aa,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + .byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff + +; a byte for each bank written when switching PRG ROM banks +; CPU address $ffd0 +prg_rom_banks: + .byte $00,$01,$02,$03,$04,$05,$06,$07 + +; Konami production code RD008 +; RD008 would be later used as the name for Bill's replacement in Probotector (European Contra release) +; https://tcrf.net/User:Revenant/Konami_catalog_numbers +; > Incidentally, the NES version of Contra had the ID RD008 within its code. +; > RD008 would later be used as the codename for one of the robot protagonists +; > (alongside RC011) in the European version of the game titled Probotector, +; > released in 1990. 4/4 +; -- https://twitter.com/Arc_Hound/status/1161732318740041729 +konami_catalog_number: + .byte $52,$44,$30,$30,$38,$ff,$ff,$ff ; RD008 in ASCII + +; NES undocumented footer +; https://forums.nesdev.org/viewtopic.php?p=56921 +; game name left-padded with zeros +nes_footer_rom_name: + .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00 + .byte $43,$4f,$4e,$54,$52,$41 ; CONTRA in ASCII + +; checksum of game bytes +; * add all bytes together (setting checksum bytes to #$00) +; * take smallest 2 bytes bytes of result +; actual sum of all bytes excluding checksum bytes is 14,256,747 (#$d98a6b) +nes_footer_checksum: + .byte $8a,$6b + +; CHR ROM checksum - the sum of all bytes in the CHR ROM +; no CHR ROM for game so #$00 #$00 +nes_footer_chr_checksum: + .byte $00,$00 + +; PRG and CHR size +; #$03 PRG ROM size - 128 KiB (8 banks each bank 16 KiB) +; #$08 CHR RAM size - 8 KiB +nes_footer_size: + .byte $38 + +; #$02 vertical mirroring +; * #$02 - vertical +; * #$81 or #$82 - horizontal +; * #$04 - mapper controlled +nes_footer_mirroring: + .byte $02 + +; byte 0 - country code - #$01 - North America +; byte 1 - unknown, almost always #$01 +; byte 2 - company code - #$a4 - Konami +; byte 3 - unknown (matches Japanese Contra ROM) +nes_footer_maker_code: + .byte $01,$05,$a4,$1c \ No newline at end of file diff --git a/src/constants.asm b/src/constants.asm new file mode 100644 index 0000000..2219737 --- /dev/null +++ b/src/constants.asm @@ -0,0 +1,513 @@ +; Contra US Disassembly - v1.0 +; https://github.com/vermiceli/nes-contra-us +; constants.asm contains the list of constants with meaningful names for the +; memory addresses used by the game. It also contains constants for the various +; palette colors. + +BANK_NUMBER = $8000 +GAME_MODE = $18 ; 0 for normal, 1 for demo, 3 for intro +GAME_ROUTINE_INDEX = $18 ; which part of the game routine to execute (see game_routine_pointer_table) +GAME_END_ROUTINE_INDEX = $19 ; which part of the ending sequence to execute (see game_end_routine_tbl) +GAME_ROUTINE_INIT_FLAG = $19 ; used to determine if the current game_routine has initialized, used in game_routine_02 and game_routine_03 +FRAME_COUNTER = $1a ; the frame counter loops from #$00 to #$ff increments once per frame +NMI_CHECK = $1b ; set to #$01 at start of nmi and #$00 at end + ; used to track if nmi occurred during game loop + ; bit 7 is set when inside play_sound, i.e. init_sound_code_vars +DEMO_MODE = $1c ; #$00 not in demo mode, #$01 demo mode on +PLAYER_MODE_1D = $1d ; #$01 for 1 player, #$07 for 2 player. Not sure why developer just didn't use PLAYER_MODE instead +DEMO_LEVEL_END_FLAG = $1f ; whether or not demo for the level is complete and new demo level should play +PPU_READY = $20 ; #$00 when at least 5 executions of nmi_start have happened since last configure_PPU call +GRAPHICS_BUFFER_OFFSET = $21 ; current write offset into CPU_GRAPHICS_BUFFER (CPU_GRAPHICS_BUFFER contains pattern table tiles that are written to PPU) +PLAYER_MODE = $22 ; #$00 = single player, #$01 = 2 player +GRAPHICS_BUFFER_MODE = $23 ; defines the format of the CPU_GRAPHICS_BUFFER. #$ff is for super-tile data, #$00 is for text strings and palette data +KONAMI_CODE_STATUS = $24 ; #$00 not entered, #$01 entered, (30 lives code) +PAUSE_STATE = $25 ; #$00 when not paused, #$01 when paused +DEMO_LEVEL = $27 ; the current level when in DEMO mode + ; only ever 0, 1 or 2 as those are the only levels demoed +INTRO_THEME_DELAY = $28 ; timer to prevent starting a level until the intro theme is complete (including explosion sound). + ; initialized to #a4, decrements every other frame for ~5 seconds for NTSC +GAME_OVER_DELAY_TIMER = $29 ; goes from #$60 to #$00, timer after dying before showing score +DELAY_TIME_LOW_BYTE = $2a ; the low byte of the delay +DELAY_TIME_HIGH_BYTE = $2b ; the high byte of the delay +LEVEL_ROUTINE_INDEX = $2c ; the index into level_routine_ptr_tbl of the routine to run +END_LEVEL_ROUTINE_INDEX = $2d ; offset into either end_level_sequence_ptr_tbl or end_game_sequence_ptr_tbl +DEMO_FIRE_DELAY_TIMER = $2e ; the number of frames to delay before starting to fire when demoing +PLAYER_WEAPON_STRENGTH = $2f ; the damage strength of the player's current weapon (see weapon_strength) bits 0-2 of P1_CURRENT_WEAPON,x +CURRENT_LEVEL = $30 ; #$00-#$09, #$00 to #$07 represent levels 1 through 8. #$9 is interpreted as game over sequence +GAME_COMPLETION_COUNT = $31 ; the number of times the game has been completed (final boss defeated) +P1_NUM_LIVES = $32 ; P1 number of lives, #$00 is last life, on game over stays #$00, but P1_GAME_OVER_STATUS becomes #$01 +P2_NUM_LIVES = $33 ; P2 number of lives, #$00 is last life, on game over stays #$00, but P1_GAME_OVER_STATUS becomes #$01 +RANDOM_NUM = $34 ; random number increased in forever_loop +NUM_PALETTES_TO_LOAD = $36 ; the number of palettes to load into CPU memory +INDOOR_SCREEN_CLEARED = $37 ; whether indoor screen has had all cores destroyed (0 = not cleared, 1 = cleared, #$80 = cleared and fence removed) +P1_GAME_OVER_STATUS = $38 ; #$00 not game over, #$01 game over +P2_GAME_OVER_STATUS = $39 ; #$00 not game over, #$01 game over or player 2 not playing (1 player game) +NUM_CONTINUES = $3a ; the number of continues remaining +BOSS_DEFEATED_FLAG = $3b ; whether or not the level boss has been defeated (0 = no, 1 = yes) + ; after set to 1, end level sequence logic uses this value as well using values #$81 and #$02 +EXTRA_LIFE_SCORE_LOW = $3c ; the low byte of the score required for the next extra life +EXTRA_LIFE_SCORE_HIGH = $3d ; the high byte of the score required for the next extra life + ; $3e is the EXTRA_LIFE_SCORE_LOW for player 2 +KONAMI_CODE_NUM_CORRECT = $3f ; the number of successful inputs of the Konami code sequence #$0a for all correct + ; also used as player 2's EXTRA_LIFE_SCORE_HIGH byte during game play + +; level header data +LEVEL_LOCATION_TYPE = $40 ; current level type #$00 outdoor, #$01 indoor (base level); #$80 on indoor/base boss screen and indoor/base levels when players advancing to next screen +LEVEL_SCROLLING_TYPE = $41 ; current level scrolling type #$00 horizontal (and indoor/base level) #$01 vertical +LEVEL_SCREEN_SUPERTILES_PTR = $42 ; $42,$43 stores 2-byte address to bank 2 containing which super-tiles to use for each screen of the level (level_x_supertiles_screen_ptr_table) +LEVEL_SUPERTILE_DATA_PTR = $44 ; current level 2-byte pointer to super-tile data, which defines pattern table tiles of the super-tiles that are used to make level blocks +LEVEL_SUPERTILE_PALETTE_DATA = $46 ; current level 2-byte pointer address to the palettes used for each super-tile, each byte describes the 4 palettes for a single super-tile +LEVEL_ALT_GRAPHICS_POS = $48 ; how far into level (in number of screens) before loading alternate graphic data +COLLISION_CODE_1_TILE_INDEX = $49 ; pattern table tiles below this tile index (but not #$00) are considered Collision Code 1 (floor) +COLLISION_CODE_0_TILE_INDEX = $4a ; pattern table tiles >= $49 and less than this tile index are considered Collision Code 0 (empty) +COLLISION_CODE_2_TILE_INDEX = $4b ; pattern table tiles >= $4a and less than this tile index are considered Collision Code 2 (water) + +LEVEL_PALETTE_CYCLE_INDEXES = $4c ; palette indexes into game_palettes to cycle through for the level [$4c-4f] +LEVEL_PALETTE_INDEX = $50 ; the level's initial background palettes [$50 to $54) and sprite palettes [$54 to $58). Offsets into game_palettes table + +LEVEL_STOP_SCROLL = $58 ; the screen of the level to stop scrolling, set to #$ff when boss auto scroll starts +LEVEL_SOLID_BG_COLLISION_CHECK = $59 ; used to determine whether to check for bullet and weapon item solid bg collisions + ; 1. When non-zero, specifies weapon item should check for solid bg collisions (weapon_item_check_bg_collision) + ; 2. When negative, used to let bullet (player and enemy) collision detection code to know to look for bullet-solid background collisions + ; This is for levels 6 - energy zone and 7 - hangar. (see check_bullet_solid_bg_collision and enemy_bullet_routine_01) + +DEMO_INPUT_NUM_FRAMES = $5a ; used to determine how many even-numbered frames to continue pressing the button specified in $5c for demo + ; $5b the DEMO_INPUT_NUM_FRAMES for player 2 +DEMO_INPUT_VAL = $5c ; the current controller input pressed during a demo + ; $5d is DEMO_INPUT_VAL for player 2 +DEMO_INPUT_TBL_INDEX = $5e ; when in demo, this stores the offset into specific demo_input_tbl_lX_pX table + ; $5f is for player 2 +PPU_WRITE_TILE_OFFSET = $60 ; the current write offset of the super-tile data, number of tiles outside the current view + ; horizontal levels loops #$00 to #$1f, vert starts with #$1d goes down to #$00 before looping +LEVEL_TRANSITION_TIMER = $61 ; used in vertical levels to time animation between sections for every 'up' input + ; used in indoor levels between screens to animate moving forward +PPU_WRITE_ADDRESS_LOW_BYTE = $62 ; used to populate the PPU write address in the CPU_GRAPHICS_BUFFER +PPU_WRITE_ADDRESS_HIGH_BYTE = $63 ; used to populate the PPU write address in the CPU_GRAPHICS_BUFFER +LEVEL_SCREEN_NUMBER = $64 ; the screen number of the current level (how many screens into the level) +LEVEL_SCREEN_SCROLL_OFFSET = $65 ; the number of pixels into LEVEL_SCREEN_NUMBER the level has scrolled. Goes from $00-$ff for each screen (256 pixels) + ; for horizontal levels, this is how many pixels scrolled to the right, for vertical levels, this is how many pixels up scrolled + ; for indoor levels, after defeating a wall, increases from #$00 to #03 +ATTRIBUTE_TBL_WRITE_LOW_BYTE = $66 ; the low byte of the attribute table write address to write to (always #$c0, never read) +ATTRIBUTE_TBL_WRITE_HIGH_BYTE = $67 ; the high byte of the attribute table write address to write to +FRAME_SCROLL = $68 ; how much to scroll the screen this frame based on player velocity (usually #$00 or #$01), for vertical levels, up to #$03 + ; note that this is not the scroll distance within the screen +SUPERTILE_NAMETABLE_OFFSET = $69 ; base nametable offset into memory address into CPU graphics buffer starting at $0600 (LEVEL_SCREEN_SUPERTILES) + ; always either #$00 (nametable 0) or #$40 (nametable 1), points to area that contains the super-tile indexes for screen +SPRITE_LOAD_TYPE = $6a ; which sprites to load #$0 for normal sprites, #$1 for HUD sprites +CONT_END_SELECTION = $6b ; #$00 when "CONTINUE" is selected, #$01 when 'END" is selected, used only in game over screen (level_routine_06) +ALT_GRAPHIC_DATA_LOADING_FLAG = $71 ; #$00 means that the alternate graphics data should not be loaded, #$01 means it should be #$02 means it currently is being loaded +LEVEL_PALETTE_CYCLE = $72 ; the current iteration of the palette animation loop #$00 up to entry for level in lvl_palette_animation_count +INDOOR_SCROLL = $73 ; scrolling on indoor level changes (0 = not scrolling; 1 = scrolling, 2 = finished scrolling) +BG_PALETTE_ADJ_TIMER = $74 ; timer used for adjusting background palette colors (not sprite palettes). Used for fade-in effect of dragon and boss ufo as well as indoor transitions +AUTO_SCROLL_TIMER_00 = $75 ; used when completing scroll to show a boss, e.g. vertical level dragon screen +AUTO_SCROLL_TIMER_01 = $76 ; used when completing scroll to show a boss, e.g. alien guardian +TANK_AUTO_SCROLL = $77 ; amount to scroll every frame, regardless of AUTO_SCROLL_TIMER_xx, used for snow field tanks (dogras), breaks levels if used on other levels +PAUSE_PALETTE_CYCLE = $78 ; #$00 - nametable palettes #$03 and #$04 will cycle through colors like normal, non zero pauses palette color cycling (ice field tank pauses palette cycle) +SOLDIER_GENERATION_ROUTINE = $79 ; which routine is currently in use for generating soldiers (index into soldier_generation_ptr_tbl) +SOLDIER_GENERATION_TIMER = $7a ; a timer between soldier generation. #$00 means no generation. see level_soldier_generation_timer. When used in a level, every frame decrements by 2 (unless scrolling, then only by 1) +SOLDIER_GENERATION_X_POS = $7b ; the initial x position of the generated soldier +SOLDIER_GENERATION_Y_POS = $7c ; the initial y position of the generated soldier +FALCON_FLASH_TIMER = $7d ; the number of frames to flash the screen for falcon weapon item +TANK_ICE_JOINT_SCROLL_FLAG = $7f ; whether or not to have the ice joint enemy move left while player walks right to simulate being on the background +ENEMY_LEVEL_ROUTINES = $80 ; two byte address to the correct enemy_routine_level_XX for the current level, used to retrieve enemy routines for the level +ENEMY_SCREEN_READ_OFFSET = $82 ; read offset into level_xx_enemy_screen_xx table, which specifies the enemies on each screen of a level +ENEMY_CURRENT_SLOT = $83 ; when in use, specifies the current enemy slot that is being executed, used to be able to restore x register after method has used it +BOSS_AUTO_SCROLL_COMPLETE = $84 ; set when boss reveal auto-scrolling has completed, see AUTO_SCROLL_TIMER_00 and AUTO_SCROLL_TIMER_01 +BOSS_SCREEN_ENEMIES_DESTROYED = $85 ; used on level 3 and level 7 boss screens to keep track of how many dragon arm orbs or mortar launchers have been destroyed respectively +WALL_CORE_REMAINING = $86 ; remaining wall cores/wall platings to destroy until can advance to next screen. For level 4 boss, used to count remaining boss gemini +WALL_PLATING_DESTROYED_COUNT = $87 ; used in indoor/base boss levels to keep track of how many wall platings (ENEMY_TYPE #$0a) have been destroyed +INDOOR_ENEMY_ATTACK_COUNT = $88 ; used in indoor/base levels to specify how many 'rounds' of attack have happened per screen, max #$07 before certain enemies no longer generate + ; indoor soldiers, jumping soldiers, indoor rollers, and wall core check this value +INDOOR_RED_SOLDIER_CREATED = $89 ; used in indoor/base levels to indicate if a red jumping soldier has been created, to prevent creation of another +GRENADE_LAUNCHER_FLAG = $8a ; used in indoor/base levels to indicate that a grenade launcher enemy (ENEMY_TYPE #$17) is on the screen. Prevents other indoor enemies from being generated +ALIEN_FETUS_AIM_TIMER_INDEX = $8b ; used to keep track of the index into alien_fetus_aim_timer_tbl to set the delay between re-aiming towards the player +ENEMY_ATTACK_FLAG = $8e ; whether or not enemies will fire at player, also whether or not random enemies are generated, bosses ignore this +PLAYER_STATE = $90 ; #$00 falling into level (only run once to init fall), #$01 normal state, #$02 when dead, #$03 can't move + ; $91 is for p2, if p2 not playing, set to #$00 +INDOOR_TRANSITION_X_ACCUM = $92 ; a variable to store INDOOR_TRANSITION_X_FRACT_VEL being added to itself to account for overflow before adding to player x velocity when moving between screens on indoor/base levels +PLAYER_JUMP_COEFFICIENT = $94 ; related to jump height (used by speed runners to jump higher) (https://www.youtube.com/watch?v=K7MjxHvWof8 and https://www.youtube.com/watch?v=yrnW9yQXa9I) + ; also used when walking into screen for indoor screen changes to keep track of overflow of animation y fractional velocity + ; $95 is for player 2 +INDOOR_TRANSITION_X_FRACT_VEL = $96 ; indoor animation transition when walking into screen x fractional velocity + ; $98 is for player 2 +INDOOR_TRANSITION_Y_FRACT_VEL = $9a ; indoor animation transition when walking into screen y fractional velocity + ; $9b is for player 2 +INDOOR_TRANSITION_Y_FAST_VEL = $9c ; indoor animation transition when walking into screen y fast velocity + ; $9d is for player 2 +PLAYER_ANIM_FRAME_TIMER = $9e ; value that is incremented every frame when player is walking, used to wait #$08 frames before incrementing PLAYER_ANIMATION_FRAME_INDEX for animating player walking + ; $9f is for player 2 +PLAYER_X_VELOCITY = $98 ; the player's fast x velocity (#$00, #$01, or #$ff) + ; $99 is for p2 +PLAYER_JUMP_STATUS = $a0 ; the status of the player jump (facing direction); similar to EDGE_FALL_CODE + ; high nibble is for facing direction + ; bit 7 - set when jumping left + ; low nibble is #$01 when jumping, #$00 when not + ; $a1 is for player 2 +PLAYER_FRAME_SCROLL = $a2 ; how much player 1 is causing the frame to scroll by, see FRAME_SCROLL + ; $a3 is for player 2, larger of the 2 is set to FRAME_SCROLL +EDGE_FALL_CODE = $a4 ; similar to PLAYER_JUMP_STATUS. Used to initiate gravity pulling player down + ; if bit 7 set, then falling through platform + ; if bit 6 is set, then walking left off edge + ; if bit 5 is set, then walking right off ledge + ; can change if change direction during fall, bit 0 always set when EDGE_FALL_CODE non-zero +PLAYER_ANIMATION_FRAME_INDEX = $a6 ; which frame of the player animation. Depends on player state. For example, if player is running, this cycles from #$00 to #$05 +PLAYER_INDOOR_ANIM_Y = $a8 ; the y position the player was at when they started walking into screen after clearing an indoor level. I believe it's always #$a8 since y pos is hard-coded for indoor levels + ; $a9 is player 2 +P1_CURRENT_WEAPON = $aa ; low nibble is what weapon P1 has, high nibble 1 is rapid fire flag, commonly abbreviated MFSL + ; #$00 - Regular, #$01 - Machine Gun, #$02 - Flame Thrower, #$03 - Spray, #$04 - Laser, bit 4 set for rapid fire +P2_CURRENT_WEAPON = $ab ; byte 0 is what weapon P2 has, byte 1 is rapid fire flag +PLAYER_M_WEAPON_FIRE_TIME = $ac ; used when holding down the B button wit the m weapon. High nibble is number of bullets generated (up to #$06), low nibble is counter before next bullet is generated (up to #$07) + ; $ad is for player 2 +NEW_LIFE_INVINCIBILITY_TIMER = $ae ; timer for invincibility after dying + ; $af is for player 2 +INVINCIBILITY_TIMER = $b0 ; timer for player invincibility (b (barrier) weapon) (decreases every 8 frames), usually set to #$80 except level 7 when set to #$90 + ; $b1 is for player 2 +PLAYER_WATER_STATE = $b2 ; bit 1 - horizontal sprite flip flag + ; bit 2 - set when player in water, or exiting water + ; bit 3 - player is walking out of water + ; bit 4 - finished initialization for entering water + ; bit 7 - player is walking out of water + ; $b3 is for player 2 +PLAYER_DEATH_FLAG = $b4 ; bit 0 specifies whether player has died, bit 1 specifies player was facing left when hit, used so player dies lying in appropriate direction +PLAYER_ON_ENEMY = $b6 ; whether or not the player is on top of another enemy (#$14 - mining cart, #$15 - stationary mining cart, #$10 - floating rock platform) + ; $b7 is for player 2 +PLAYER_FALL_X_FREEZE = $b8 ; used to prevent changing X velocity shortly after walking off/falling through ledge, set to Y post of ledge + #$14 +PLAYER_HIDDEN = $ba ; #$00 player visible, #$01/#$ff player invisible (any non-zero). I believe it is meant to track distance off screen the player is + ; $bb is for player 2 +PLAYER_SPRITE_SEQUENCE = $bc ; which animation to show for the player + ; outdoor - #$00 standing (no animation), #$01 gun pointing up, #$02 crouching, #$03 walking or curled jump animation, #$04 dead animation + ; indoor - (see indoor_player_sprite_tbl), #$00 standing facing back wall, #$01 electrocuted, #$02 crouching, #$03 walking left/right animation, #$05 walking into screen (advancing), #$06 dead animation +PLAYER_INDOOR_ANIM_X = $be ; the x position the player was at when they started walking into screen after clearing an indoor level + ; $bf is player 2 +PLAYER_AIM_PREV_FRAME = $c0 ; backup of PLAYER_AIM_DIR +PLAYER_AIM_DIR = $c2 ; which direction the player is aiming [#$00-#$0a] depends on level and jump status (00 up facing right, 1 up-right, 2 right, 3 right-down, 4 crouching facing right, 5 crouching facing left, etc) + ; there are #$02 up and #$02 down values depending on facing direction + ; $c3 is for player 2 +PLAYER_Y_FRACT_VELOCITY = $c4 ; the low byte of the change in vertical position velocity for a player positive pulls down, negative pulls up + ; $c5 is for player 2 +PLAYER_Y_FAST_VELOCITY = $c6 ; the change in vertical position velocity for a player positive pulls down, negative pulls up + ; $c7 is for player 2 +ELECTROCUTED_TIMER = $c8 ; timer for player being electrocuted, used to freeze player and modify look after touching electricity +INDOOR_PLAYER_JUMP_FLAG = $ca ; used when entering new screen to tell the engine to cause the player to jump + ; $cb is player 2 +PLAYER_WATER_TIMER = $cc ; timer used for getting into and out of water +PLAYER_RECOIL_TIMER = $ce ; how many frames to be pushed back/down from recoil + ; $cf is for player 2 +INDOOR_PLAYER_ADV_FLAG = $d0 ; whether or not the player is walking into screen when advancing between screens on indoor levels, used for animating player + ; $d1 is for player 2 +PLAYER_FAST_X_VEL_BOOST = $d4 ; the x fast velocity boost from landing on a non-dangerous enemy, e.g. moving cart or floating rock in vertical level +PLAYER_SPRITE_CODE = $d6 ; sprite code of the player + ; $d7 is for player 2 +PLAYER_SPRITE_FLIP = $d8 ; stores player sprite horizontal (bit 6) and vertical (bit 7) flip flags before saving into SPRITE_ATTR, other bits are used + ; bit 3 specifies whether the PLAYER_ANIMATION_FRAME_INDEX is even or odd (see @check_anim_frame_and_collision) +PLAYER_BG_FLAG_EDGE_DETECT = $da ; bit 7 specifies the player's sprite attribute for background priority, allows player to walk behind opaque background (OAM byte 2 bit 5) + ; 0 (clear) sprite in foreground, 1 (set) sprite is background + ; bit 0 allows the player to keep walking horizontally off a ledge without falling +PLAYER_SPECIAL_SPRITE_TIMER = $d2 ; used to track animation for player death animation + ; outdoor is a timer that increments once player hit, ever #$08 frames updates to next animation frame until #$04 + ; also used to track jumping curl animation (loops from #$00-#$04) +PLAYER_GAME_OVER_BIT_FIELD = $df ; combination of both players game over status + ; #$00 = p1 not game over, p2 game over (or not playing), #$01 = p1 game over, p2 not game over, #$02 = p1 nor p2 are in game over +SOUND_TABLE_PTR = $ec ; low byte of address pointing of index into sound_table_00 offset INIT_SOUND_CODE +CONTROLLER_STATE = $f1 ; stores the currently-pressed buttons for the player 1 + ; $f2 stores the currently-pressed buttons for the player 2 + ; bit 7 - A, bit 6 - B, bit 5 - select, bit 4 - start + ; bit 3 - up, bit 2 - down, bit 1 - left, bit 0 - right +CONTROLLER_STATE_DIFF = $f5 ; stores the difference between the controller input between reads. Useful for events that should only trigger on first button press + ; $f6 is for player 2 +CTRL_KNOWN_GOOD = $f9 ; used in input-reading code to know the last known valid read of controller input (similar to CONTROLLER_STATE) +VERTICAL_SCROLL = $fc ; the number of pixels to vertically scroll down + ; (y component of PPUSCROLL) (see level_vert_scroll_and_song for initial values) + ; outdoor levels are always #$e0 (224 pixels or 28 tiles down), indoor/base are always #$e8 (232 or 29 tiles down) + ; waterfall level starts at #$00 and decrements as players move up screen (wrapping) +HORIZONTAL_SCROLL = $fd ; the horizontal scroll component of the PPUSCROLL, [#$0 - #$ff] +PPUMASK_SETTINGS = $fe ; used to store value of PPUMASK before writing to PPU +PPUCTRL_SETTINGS = $ff ; used to set PPUCTRL value for next frame + +SOUND_CMD_LENGTH = $0100 ; how many video frames the sound count should last for, i.e. the time to wait before reading next sound command + ; #$05 bytes, one for each sound slot +SOUND_CODE = $0106 ; the sound code for the sound slot, #$06 slots +SOUND_PULSE_LENGTH = $010c ; APU_PULSE_LENGTH, #$06 slots +SOUND_CMD_LOW_ADDR = $0112 ; low byte of address to current sound command in sound_xx data. #$06 slots, one per sound slot +SOUND_CMD_HIGH_ADDR = $0118 ; high byte of address to current sound command in sound_xx data. #$06 slots, one per sound slot +SOUND_VOL_ENV = $011e ; either an offset into pulse_volume_ptr_tbl (c.f. LVL_PULSE_VOL_INDEX) which specifies the volume for the frame + ; or a specific volume to use. when bit 7 is set, then the volume will auto decrescendo +INIT_SOUND_CODE = $0122 ; the sound code to load; sound codes greater than #$5a are dmc sounds +SOUND_CHNL_REG_OFFSET = $0123 ; sound channel configuration register offset, i.e. #$00 for first pulse channel, #$04 for second, #$08 for triangle, #$0c for noise +SOUND_FLAGS = $0124 ; sound channel flags + ; bit 0 - 0 = sound_xx command byte >= #$30 (read_low_sound_cmd), 1 = sound_xx command byte 0 < #$30 (read_high_sound_cmd) + ; bit 1 - 1 = DECRESCENDO_END_PAUSE has triggered and decrescendo can resume, 0 = keep volume constant + ; bit 2 - 0 = use lvl_config_pulse to set volume for frame, 1 = automatic decrescendo logic (handling DECRESCENDO_END_PAUSE) + ; bit 3 - used in sound_cmd_routine_03, signifies that a shared (child) sound command (sound_xx_part) is executing, specified by #$fd, or #$fe in sound command + ; used to know, after finishing parsing a sound command, whether or not to done or should return to parent sound command + ; bit 4 - slightly flatten note (see @flatten_note and @flip_flatten_note_adv) + ; bit 5 - 1 = PULSE_VOL_DURATION has counted down and decrescendo should be paused until DECRESCENDO_END_PAUSE + ; set to ignore SOUND_VOL_ENV negative check, i.e. override to decrescendo + ; bit 6 - mute flag (1 = muted, 0 = not muted) + ; bit 7 - sweep flag +LVL_PULSE_VOL_INDEX = $012a ; index into lvl_x_pulse_volume_xx to read +PULSE_VOL_DURATION = $012a ; the number of video frames to decrement the volume for, before stopping decrescendo and keeping final volume +PAUSE_STATE_01 = $012f ; whether or not the game is paused, used for sound logic +SOUND_CURRENT_SLOT = $0120 ; the current sound slot [#$00-#$05] +PERCUSSION_INDEX_BACKUP = $0121 ; backup location for percussion_tbl index to restore after call to play_sound +DECRESCENDO_END_PAUSE = $0130 ; number of video frames before end of sound command in which the decrescendo will resume + ; $0131 is for pulse channel 2 +SOUND_PITCH_ADJ = $0132 ; the amount added to the sound byte low nibble before loading the correct note_period_tbl values +UNKNOWN_SOUND_00 = $0136 +UNKNOWN_SOUND_01 = $013c ; used to adjust volume amount when setting volume +SOUND_CFG_LOW = $0142 ; the value to merge with the high nibble before storing in apu channel config register +SOUND_TRIANGLE_CFG = $0144 ; in memory value for APU_TRIANGLE_CONFIG +SOUND_REPEAT_COUNT = $0148 ; used for #$fe sound commands to specify how many times to repeat a shared sound part, e.g. .byte $fe, $03, .addr sound_xx_part to loop 3 times. #$06 slots +SOUND_CFG_HIGH = $014e ; the value to merge with the volume when saving the pulse config +SOUND_LENGTH_MULTIPLIER = $0154 ; value used when determining how many video frames to wait before reading next sound command, #$05 bytes, one for each sound slot + ; ultimately used when calculating SOUND_CMD_LENGTH, and kept around between sound commands so subsequent notes can be the same length + ; for low sound codes, SOUND_LENGTH_MULTIPLIER is set to SOUND_CMD_LENGTH directly with no multiplication (see @high_nibble_not_1) +SOUND_PERIOD_ROTATE = $015a ; when not #$04, the number of times to shift the high byte of note_period_tbl into the low byte +PULSE_VOLUME = $0160 ; low nibble only, stores the volume for the pulse channels +NEW_SOUND_CODE_LOW_ADDR = $0166 ; sound command return location low byte once sound command specified in move_sound_code_read_addr executes, e.g. jungle boss siren +NEW_SOUND_CODE_HIGH_ADDR = $016c ; sound command return location high byte once sound command specified in move_sound_code_read_addr executes, e.g. jungle boss siren +SOUND_PULSE_PERIOD = $0172 ; APU_PULSE_PERIOD +VIBRATO_CTRL = $0178 ; vibrato control mode [#$00-#$03], #$80 = no vibrato + ; even values cause the note to stay the same, odd values cause vibrato #$03 = pitch up, #$01 = pitch down + ; $0178 is for sound slot #$00 and $0719 is for sound slot #$01 +SOUND_VOL_TIMER = $017a ; sound command counter; increments up to VIBRATO_DELAY, at which vibrato will be checked + ; only increments when VIBRATO_CTRL is non-negative, i.e. not #$80 +PULSE_NOTE = $017c ; the note that is sustained or has the vibrato applied to for pulse channels (in Contra only ever sustained no vibrato) + ; $017c is for sound slot #$00 and $071d is for sound slot #$01 +VIBRATO_DELAY = $017e ; used to delay start of vibrato until SOUND_VOL_TIMER has counted up to this value + ; if a note isn't as long as VIBRATO_DELAY, i.e. SOUND_CMD_LENGTH < VIBRATO_DELAY, then vibrato won't be considered for a note + ; $017e is for sound slot #$00 and $071f is for sound slot #$01 +VIBRATO_AMOUNT = $0180 ; the amount of vibrato to apply +LEVEL_END_DELAY_TIMER = $0190 ; a delay timer before beginning level end animation sequence +LEVEL_END_SQ_1_TIMER = $0191 ; a delay timer specifying the duration of end_level_sequence_01, decremented every other frame +LEVEL_END_LVL_ROUTINE_STATE = $0192 ; used by level end routines (end_of_lvl_routine_...) for managing animation state. + ; for example, indoor level end animations have 4 states: walk to elevator, initialize elevator sprite, ride elevator + ; $0193 is for player 2 +LEVEL_END_PLAYERS_ALIVE = $0194 ; the number of players alive at the end of the level, used to know if should play level end music +SOLDIER_GEN_SCREEN = $0195 ; the current screen that soldiers are being generated for +SCREEN_GEN_SOLDIERS = $0196 ; the total number of soldiers that have been generated for the current screen (exe_soldier_generation) +OAMDMA_CPU_BUFFER = $0200 ; $0200-$02ff OAMDMA (sprite) read data, read once per frame, populated by load_sprite_to_cpu_mem, draw_hud_sprites, or draw_player_hud_sprites + +CPU_SPRITE_BUFFER = $0300 ; sprites on screen, each byte is an entry into sprite_ptr_tbl [$0300-$0387], memory is segmented as defined below +PLAYER_SPRITES = $0300 ; player sprites, p1 and p2 sprite, then player bullets, each byte is an entry into sprite_ptr_tbl (#$0a bytes) +ENEMY_SPRITES = $030a ; enemy sprites to load on screen, each byte is an entry into sprite_ptr_tbl (#$0f bytes) +SPRITE_Y_POS = $031a ; y position on screen of each player sprite. First 2 bytes are for player sprites. Starts at #$00 for top increases downward (#$0a bytes) +ENEMY_Y_POS = $0324 ; y position on screen of each enemy sprite. Starts at #$00 for top increases downward (#$0f bytes) +SPRITE_X_POS = $0334 ; x position of screen of each player sprite. First 2 bytes are for player sprites (#$0a bytes) +ENEMY_X_POS = $033e ; x position on screen of each enemy sprite (#$0f bytes) +SPRITE_ATTR = $034e ; sprite attribute, specifies palette, vertical flip, horizontal flip (#$0a bytes) + ; and whether to adjust y position + ; bit 0 and 1 - sprite palette + ; bit 2 - 0 to use default palette as specified in sprite code + ; - 1 to use palette specified in bits 0 and 1 + ; bit 3 - whether to add #$01 to sprite y position, used for recoil effect firing weapon + ; bit 5 - bg priority + ; bit 6 - whether to flip the sprite horizontally + ; bit 7 - whether to flip the sprite vertically + ; bytes 0 and 1 are p1 and p2 sprite attributes, then each byte is the player bullet sprite attributes + ; examples: player being electrocuted or invincible (flashes various colors) +ENEMY_SPRITE_ATTR = $0358 ; enemy sprite attribute. See specification above (#$0f bytes) + +PLAYER_BULLET_SPRITE_CODE = $0368 ; The sprite codes to load for the bullet, eventually copied into CPU_SPRITE_BUFFER starting at offset 2 +PLAYER_BULLET_SPRITE_ATTR = $0378 ; The sprite attributes for the bullet (see SPRITE_ATTR for specification) + ; used for L bullets for flipping the angled sprites depending on direction +PLAYER_BULLET_SLOT = $0388 ; #$00 when no bullet, otherwise stores bullet type + 1, i.e. #$01 basic, #$02 M, #$03 F bullet, #$04 S, #$05 L, can be negative sometimes +PLAYER_BULLET_VEL_Y_ACCUM = $0398 ; an accumulator to keep track of PLAYER_BULLET_X_VEL_FRACT being added to itself have elapsed before adding 1 to PLAYER_BULLET_X_POS +PLAYER_BULLET_VEL_X_ACCUM = $03a8 ; an accumulator to keep track of PLAYER_BULLET_Y_VEL_FRACT being added to itself have elapsed before adding 1 to PLAYER_BULLET_Y_POS +PLAYER_BULLET_Y_POS = $03b8 ; the bullet's sprite y position +PLAYER_BULLET_X_POS = $03c8 ; the bullet's sprite x position + ; for F bullets, PLAYER_BULLET_FS_X and PLAYER_BULLET_X_POS together determine x position +PLAYER_BULLET_Y_VEL_FRACT = $03d8 ; percentage out of 0-255 set number of frames until Y position is incremented by and additional 1 unit +PLAYER_BULLET_X_VEL_FRACT = $03e8 ; percentage out of 0-255 set number of frames until X position is incremented by and additional 1 unit +PLAYER_BULLET_Y_VEL_FAST = $03f8 ; player bullet velocity y high byte +PLAYER_BULLET_VEL_X_FAST = $0408 ; player bullet velocity x high byte +PLAYER_BULLET_TIMER = $0418 ; 'timer' starts at #$00. Used by F, S (indoor only) and L + ; for indoor S, used to specify size of bullet + ; For F, used to set x and y pos when traveling to create swirl (see f_bullet_outdoor_x_swirl_amt_tbl, and f_bullet_outdoor_y_swirl_amt_tbl) + ; increments or decrements every frame depending on firing direction (left decrement, right increment) + ; For L used to spread out 4 lasers for one shot +PLAYER_BULLET_AIM_DIR = $0428 ; the direction of the bullet #$00 for up facing right, incrementing clockwise up to #09 for up facing left +PLAYER_BULLET_ROUTINE = $0438 ; #$00, #$01, or #$03, offset into player_bullet_routine_XX_(indoor_)ptr_tbl +PLAYER_BULLET_OWNER = $0448 ; #$00 player 1 bullet, #$01 player 2 bullet, each byte is for a bullet +PLAYER_BULLET_F_RAPID = $0458 ; #$01 for player indoor bullets for F weapon when rapid fire is enabled +PLAYER_BULLET_S_INDOOR_ADJ = $0458 ; (same address as previous) for indoor S bullets, specifies whether to adjust PLAYER_BULLET_X_POS by an additional -1 (#$ff) every frame (see s_bullet_pos_mod_tbl) +PLAYER_BULLET_DIST = $0468 ; represents how far a bullet has traveled + ; For S outdoor bullets, used to determine the size (scale) of the bullet + ; For F on indoor levels, used to determine spiraling position based on distance from player +PLAYER_BULLET_S_ADJ_ACCUM = $0468 ; (same address as previous) for indoor S weapons, stores accumulated fractional velocity where overflow affects PLAYER_BULLET_S_INDOOR_ADJ (see update_s_bullet_indoor_pos) +PLAYER_BULLET_FS_X = $0478 ; Used to offset from general x direction of bullet for swirl effect in F bullet and spread effect in S bullet (indoor) + ; Specifies center x position on screen f bullet swirls around. Used when firing f bullet either left, right, or at an angle +PLAYER_BULLET_F_Y = $0488 ; Specifies center y position on screen f bullet swirls around. Used when firing f bullet either up, down, or at an angle. +PLAYER_BULLET_S_RAPID = $0488 ; (same address as previous) for S weapon in indoor levels, specifies whether weapon is rapid fire or not, not sure why $09 wasn't used like other bullet routines +PLAYER_BULLET_VEL_FS_X_ACCUM = $0498 ; (for F weapon only) an accumulator to keep track of PLAYER_BULLET_X_VEL_FRACT being added to itself have elapsed before adding 1 to PLAYER_BULLET_X_POS +PLAYER_BULLET_VEL_F_Y_ACCUM = $04a8 ; (for F weapon only) an accumulator to keep track of PLAYER_BULLET_Y_VEL_FRACT being added to itself have elapsed before adding 1 to PLAYER_BULLET_Y_POS +PLAYER_BULLET_S_BULLET_NUM = $04a8 ; (same address as previous) for S weapon only, specifies the number the bullet it in the current 'spray' for the shot + ; per shot of S weapon, #$05 bullets are generated. If no other bullets exist then + ; $04a8 would have #$00, $04a9 would have #$01, $04a9 would have #$02, etc. + +; each enemy property is #$10 bytes, one byte per enemy +ENEMY_ROUTINE = $04b8 ; enemy routine indexes starting at offset #$f ($04c7) going to #$0 ($04b8) + ; subtract 1 to get real routine, since all offsets are off by 1 (...routine_ptr_tbl -2) + ; ex: for exploding bridge, setting ENEMY_ROUTINE to #$02 causes exploding_bridge_routine_01 to run the next frame + +; the following 6 address ranges control the change in position of the enemy +; every frame the position is moved by VELOCITY_FAST units +; VELOCITY_FRACT can enable only moving by 1 unit every n frames +; for example, if ENEMY_Y_VELOCITY_FAST is #$00 and ENEMY_Y_VELOCITY_FRACT is #$c0, (#$c0/#$ff = 75%), +; then the enemy will move one position to the right 3 out of every 4 frames +ENEMY_Y_VEL_ACCUM = $04c8 ; an accumulator to keep track of ENEMY_Y_VELOCITY_FRACT being added to itself have elapsed before adding 1 to ENEMY_Y_POS +ENEMY_X_VEL_ACCUM = $04d8 ; an accumulator to keep track of ENEMY_X_VELOCITY_FRACT being added to itself have elapsed before adding 1 to ENEMY_X_POS +ENEMY_Y_VELOCITY_FAST = $04e8 ; the number of units to add to ENEMY_Y_POS every frame +ENEMY_Y_VELOCITY_FRACT = $04f8 ; percentage out of 0-255 of a unit to add, e.g. if #$80 (#$80/#$ff = 50%), then every other frame will cause Y pos to increment by 1 +ENEMY_X_VELOCITY_FAST = $0508 ; the number of units to add to ENEMY_X_POS every frame +ENEMY_X_VELOCITY_FRACT = $0518 ; percentage out of 0-255 of a unit to add, e.g. if #$80 (#$80/#$ff = 50%), then every other frame will cause X pos to increment by 1 + +ENEMY_TYPE = $0528 ; a list of current enemy types for the level, used when executing the enemy routines for the level +ENEMY_ANIMATION_DELAY = $0538 ; the delay before the enemy starts moving +ENEMY_VAR_A = $0548 ; the sound code to play when enemy hit by player bullet, also used for other logic + ; dragon arm orb uses it for adjusting enemy position, fire beam uses it for animation delay +ENEMY_ATTACK_DELAY = $0558 ; the delay before an enemy attacks, for weapon items and grenades this is used for helping calculate falling arc trajectory instead of enemy delay +ENEMY_VAR_B = $0558 ; for weapon items and grenades this is used for helping calculate falling arc trajectory instead of enemy delay +ENEMY_FRAME = $0568 ; a list of numbers which each represent which animation frame the enemy is in, for example offset into soldier_sprite_codes +ENEMY_SCORE_COLLISION = $0588 ; a list of bytes, each byte represents 3 things for an enemy + ; SSSS CCCC - score code (see `score_codes_tbl`), and collision type (entry in collision_box_codes_XX) + ; also explosion type +ENEMY_HP = $0578 ; a list of enemy hp for each enemy in ENEMY_TYPE +ENEMY_STATE_WIDTH = $0598 ; loaded from enemy_prop_ptr_tbl + ; bit 7 set to allow bullets to travel through enemy, e.g. weapon item + ; bit 6 specifies whether player can land on enemy (floating rock and moving cart), bit 4 also has to be 0 (see `beq @land_on_enemy`) + ; bit 4 and 5 specify the collision box type (see collision_box_codes_tbl) + ; bit 3 determines the explosion type (explosion_type_ptr_tbl), either explosion_type_00 or explosion_type_01 + ; bit 2 for bullets specifies whether to play sound on collision + ; bit 1 specifies whether to play explosion noise; also specifies width of enemy + ; bit 0 - #$00 test player-enemy collision, #$01 means to skip player-enemy collision test +ENEMY_ATTRIBUTES = $05a8 ; a list of enemy attributes that define how an enemy behaves and/or looks [$05a8 to 05b7] +ENEMY_VAR_1 = $05b8 ; a byte available to each enemy for whatever they want to use it for (#$f bytes, 1 per enemy) +ENEMY_VAR_2 = $05c8 ; a byte available to each enemy for whatever they want to use it for (#$f bytes, 1 per enemy) +ENEMY_VAR_3 = $05d8 ; a byte available to each enemy for whatever they want to use it for (#$f bytes, 1 per enemy) +ENEMY_VAR_4 = $05e8 ; a byte available to each enemy for whatever they want to use it for (#$f bytes, 1 per enemy) +LEVEL_SCREEN_SUPERTILES = $0600 ; cpu memory address where super tiles indexes for the screens of the level are loaded (level_X_supertiles_screen_XX data) + ; 2 screens are stored in the cpu buffer. The second screen loaded at $0640. Indexes are into level_x_supertile_data + ; This data specifies the super-tiles (indexes) to load for the screens +BG_COLLISION_DATA = $0680 ; map of collision types for each of the super-tiles for both nametables, each 2 bits encode 1/4 of a super-tile's collision information + ; first 8 nibbles are a row of the top of super-tile, the next 8 are the middle middle. Not used on base (indoor) levels +CPU_GRAPHICS_BUFFER = $0700 ; used to store data that will be then moved to the PPU later on. $700 to $750, repeating structure + ; * byte $700 is multifaceted + ; * if $700 is #$0, then done writing graphics buffer to PPU + ; * if $700 is greater than #$0, then there is data to write, this byte is the offset into vram_address_increment + ; * both #$01, and #$03 signify VRAM address increment to 0, meaning to add #$1 every write to PPU (write across) + ; * #$02 signifies VRAM address increment is 1, meaning add #$20 (32 in decimal) every write to PPU (write down) + ; if GRAPHICS_BUFFER_MODE is #$ff + ; * byte $701 is length of the tiles being written per group + ; * byte $702 is the number of $701-sized blocks to write to the PPU + ; * for each block, the block prefixed with 2 bytes specifying PPU address (high byte, then low byte) + ; if GRAPHICS_BUFFER_MODE is #$00 + ; * if byte #$00 is #$00, then no drawing takes place for frame + ; * blocks of text/palette data prefixed with 2 bytes specifying PPU address (high byte, then low byte) + ; the block of text is ended with a #$ff, if the byte after #$ff is the vram_address_increment offset + ; then the the process continues, i.e. read #$02 PPU address bytes, read next text + +PALETTE_CPU_BUFFER = $07c0 ; [$07c0-$07df] the cpu memory address of the palettes eventually loaded into the PPU $3f00 to $3f1f + +HIGH_SCORE_LOW = $07e0 ; the low byte of the high score score +HIGH_SCORE_HIGH = $07e1 ; the high byte of the high score score +PLAYER_1_SCORE_LOW = $07e2 ; the low byte of player 1 high score +PLAYER_1_SCORE_HIGH = $07e3 ; the high byte of player 1 high score +PLAYER_2_SCORE_LOW = $07e4 ; the low byte of player 1 high score +PLAYER_2_SCORE_HIGH = $07e5 ; the high byte of player 1 high score +PREVIOUS_ROM_BANK = $07ec ; the previously-loaded PRG BANK ($8000-$bfff) +PREVIOUS_ROM_BANK_1 = $07ed ; the previously-loaded PRG BANK, but used only for load_bank_1 (from play_sound) + +; PPU (picture processing unit) +PPUCTRL = $2000 +PPUMASK = $2001 +PPUSTATUS = $2002 +OAMADDR = $2003 +PPUSCROLL = $2005 +PPUADDR = $2006 +PPUDATA = $2007 + +; APU (audio processing unit) +APU_PULSE_CONFIG = $4000 ; config - DDLC VVVV duty (D), envelope loop / length counter halt (L), constant volume (C), volume/envelope (V) +APU_PULSE_SWEEP = $4001 ; sweep - EPPP NSSS enabled (E), period (P), negate (N), shift (S) +APU_PULSE_PERIOD = $4002 ; timer - TTTT TTTT timer low (T). Controls note frequency +APU_PULSE_LENGTH = $4003 ; length - LLLL LTTT length counter load (L), timer high (T) +APU_PULSE2_CONFIG = $4004 ; config - DDLC VVVV duty (D), envelope loop / length counter halt (L), constant volume (C), volume/envelope (V) +APU_PULSE2_SWEEP = $4005 ; config - DDLC VVVV duty (D), envelope loop / length counter halt (L), constant volume (C), volume/envelope (V) +APU_TRIANGLE_CONFIG = $4008 ; config - CRRR RRRR length counter halt / linear counter control (C), linear counter load (R) +APU_NOISE_CONFIG = $400c ; config - --LC VVVV envelope loop / length counter halt (L), constant volume (C), volume/envelope (V) +APU_DMC = $4010 ; APU delta modulation channel +APU_DMC_COUNTER = $4011 ; APU delta modulation channel load counter +APU_DMC_SAMPLE_ADDR = $4012 ; APU delta modulation channel sample address (location of sample) +APU_DMC_SAMPLE_LEN = $4013 ; APU delta modulation channel sample length +OAMDMA = $4014 +APU_STATUS = $4015 ; ---D NT21 - enable DMC (D), noise (N), triangle (T), and pulse channels (2/1) +APU_FRAME_COUNT = $4017 + +; controller input addresses +CONTROLLER_1 = $4016 +CONTROLLER_2 = $4017 + +; colors +COLOR_DARK_GRAY_00 = $00 +COLOR_DARK_BLUE_01 = $01 +COLOR_DARK_VIOLET_02 = $02 +COLOR_DARK_PURPLE_03 = $03 +COLOR_DARK_MAGENTA_04 = $04 +COLOR_DARK_PINK_05 = $05 +COLOR_DARK_RED_06 = $06 +COLOR_DARK_ORANGE_07 = $07 +COLOR_DARK_OLIVE_08 = $08 +COLOR_DARK_FOREST_GREEN_09 = $09 +COLOR_DARK_GREEN_0a = $0a +COLOR_DARK_BLUE_GREEN_0b = $0b +COLOR_DARK_TEAL_0c = $0c +COLOR_BLACK_0f = $0f +COLOR_LT_GRAY_10 = $10 +COLOR_MED_BLUE_11 = $11 +COLOR_MED_VIOLET_12 = $12 +COLOR_MED_PURPLE_13 = $13 +COLOR_MED_MAGENTA_14 = $14 +COLOR_MED_PINK_15 = $15 +COLOR_MED_RED_16 = $16 +COLOR_MED_ORANGE_17 = $17 +COLOR_MED_OLIVE_18 = $18 +COLOR_MED_FOREST_GREEN_19 = $19 +COLOR_MED_GREEN_1a = $1a +COLOR_MED_BLUE_GREEN_1b = $1b +COLOR_MED_TEAL_1c = $1c +COLOR_BLACK_1d = $1d ; not used +COLOR_MED_1e = $1e ; not used +COLOR_BLACK_1f = $1f ; not used +COLOR_WHITE_20 = $20 +COLOR_LT_BLUE_21 = $21 +COLOR_LT_VIOLET_22 = $22 +COLOR_LT_PURPLE_23 = $23 ; not used +COLOR_LT_MAGENTA_24 = $24 +COLOR_LT_PINK_25 = $25 +COLOR_LT_RED_26 = $26 +COLOR_LT_ORANGE_27 = $27 +COLOR_LT_OLIVE_28 = $28 +COLOR_LT_FOREST_GREEN_29 = $29 +COLOR_LT_GREEN_2a = $2a ; not used +COLOR_LT_BLUE_GREEN_2b = $2b +COLOR_LT_TEAL_2c = $2c +COLOR_GRAY_2D = $2d ; not used +COLOR_BLACK_2e = $2e ; not used +COLOR_BLACK_2f = $2f ; not used +COLOR_WHITE_30 = $30 +COLOR_PALE_BLUE_31 = $31 ; not used +COLOR_PALE_VIOLET_32 = $32 +COLOR_PALE_PURPLE_33 = $33 ; not used +COLOR_PALE_MAGENTA_34 = $34 ; not used +COLOR_PALE_PINK_35 = $35 +COLOR_PALE_RED_36 = $36 +COLOR_PALE_ORANGE_37 = $37 +COLOR_PALE_OLIVE_38 = $38 +COLOR_PALE_FOREST_GREEN_39 = $39 ; not used +COLOR_PALE_GREEN_3a = $3a ; not used +COLOR_PALE_BLUE_GREEN_3b = $3b +COLOR_PALE_TEAL_3c = $3c ; not used +COLOR_PALE_GRAY_3d = $3d ; not used +COLOR_BLACK_3e = $3e ; not used +COLOR_BLACK_3f = $3f ; not used \ No newline at end of file diff --git a/src/ines_header.asm b/src/ines_header.asm new file mode 100644 index 0000000..fbade84 --- /dev/null +++ b/src/ines_header.asm @@ -0,0 +1,38 @@ +; Contra US Disassembly - v1.0 +; https://github.com/vermiceli/nes-contra-us + +.segment "HEADER" + +; +--------+------+------------------------------------------+ +; | Offset | Size | Content(s) | +; +--------+------+------------------------------------------+ +; | 0 | 3 | 'NES' | +; | 3 | 1 | $1A | +; | 4 | 1 | 16K PRG-ROM page count | +; | 5 | 1 | 8K CHR-ROM page count | +; | 6 | 1 | ROM Control Byte #1 | +; | | | %####vTsM | +; | | | | ||||+- 0=Horizontal mirroring | +; | | | | |||| 1=Vertical mirroring | +; | | | | |||+-- 1=SRAM enabled | +; | | | | ||+--- 1=512-byte trainer present | +; | | | | |+---- 1=Four-screen mirroring | +; | | | | | | +; | | | ++++----- Mapper # (lower 4-bits) | +; | 7 | 1 | ROM Control Byte #2 | +; | | | %####0000 | +; | | | | | | +; | | | ++++----- Mapper # (upper 4-bits) | +; | 8-15 | 8 | $00 | +; | 16-.. | | Actual 16K PRG-ROM pages (in linear | +; | ... | | order). If a trainer exists, it precedes | +; | ... | | the first PRG-ROM page. | +; | ..-EOF | | CHR-ROM pages (in ascending order). | +; +--------+------+------------------------------------------+ + +.byte $4e,$45,$53,$1a ; "NES"^Z +.byte $08 ; Specifies the number of 16k prg banks. +.byte $00 ; Specifies the number of 8k chr banks. +.byte $21 ; Mapper 002 (UxROM), vertical mirroring, no battery +.byte $00 ; Nintendo Entertainment System (NES) console, iNES header (not NES 2.0) +.byte 00,00,00,00,00,00,00,00 ; 8 zeroes \ No newline at end of file