From 8db7384d89ad3021448337db777c44e1ac3ce19b Mon Sep 17 00:00:00 2001 From: Michael Miceli Date: Wed, 26 Apr 2023 20:04:47 -0400 Subject: [PATCH] v1.0 --- .gitignore | 6 + README.md | 232 + assets.txt | 347 + build.bat | 54 + build.ps1 | 97 + build.sh | 88 + contra.cfg | 37 + docs/Aim Documentation.md | 189 + docs/Bugs.md | 68 + docs/Contra Control Flow.md | 253 + docs/Enemy Glossary.md | 1178 ++ docs/Enemy Routines.md | 210 + docs/Graphics Documentation.md | 517 + docs/How Contra Prints High Score.md | 178 + docs/Level Headers.md | 178 + docs/Sound Documentation.md | 306 + docs/attachments/pw_0.png | Bin 0 -> 2405 bytes docs/attachments/pw_1.png | Bin 0 -> 2247 bytes docs/attachments/pw_2.png | Bin 0 -> 2248 bytes docs/attachments/pw_3.png | Bin 0 -> 2282 bytes docs/attachments/sound_ff.mp3 | Bin 0 -> 10752 bytes docs/diagrams/game_flow.mmd | 15 + docs/diagrams/graphics_flow.mmd | 42 + docs/diagrams/level_routine_flow.mmd | 18 + .../read_sound_command_00 simplified.mmd | 25 + docs/diagrams/read_sound_command_00.mmd | 49 + docs/lua_scripts/fceux/Always Invincible.lua | 9 + docs/lua_scripts/fceux/Show HUD Info.lua | 24 + docs/lua_scripts/mesen/Always Invincible.lua | 9 + docs/lua_scripts/mesen/Enemy Debug.lua | 37 + .../mesen/Show Background Collisions.lua | 72 + .../mesen/Show Enemy Positions.lua | 106 + docs/lua_scripts/mesen/Show HUD Info.lua | 27 + docs/sprite_library/README.md | 45 + docs/sprite_library/player_1_game_over.png | Bin 0 -> 211 bytes docs/sprite_library/player_1_lives_medal.png | Bin 0 -> 136 bytes docs/sprite_library/player_2_game_over.png | Bin 0 -> 211 bytes docs/sprite_library/player_2_lives_medal.png | Bin 0 -> 136 bytes docs/sprite_library/sprite_02.png | Bin 0 -> 272 bytes docs/sprite_library/sprite_02_p2.png | Bin 0 -> 263 bytes docs/sprite_library/sprite_03.png | Bin 0 -> 251 bytes docs/sprite_library/sprite_03_p2.png | Bin 0 -> 237 bytes docs/sprite_library/sprite_04.png | Bin 0 -> 243 bytes docs/sprite_library/sprite_04_p2.png | Bin 0 -> 235 bytes docs/sprite_library/sprite_05.png | Bin 0 -> 254 bytes docs/sprite_library/sprite_05_p2.png | Bin 0 -> 252 bytes docs/sprite_library/sprite_06.png | Bin 0 -> 253 bytes docs/sprite_library/sprite_06_p2.png | Bin 0 -> 245 bytes docs/sprite_library/sprite_07.png | Bin 0 -> 89 bytes docs/sprite_library/sprite_08.png | Bin 0 -> 209 bytes docs/sprite_library/sprite_08_p2.png | Bin 0 -> 196 bytes docs/sprite_library/sprite_09.png | Bin 0 -> 211 bytes docs/sprite_library/sprite_09_p2.png | Bin 0 -> 193 bytes docs/sprite_library/sprite_0a.png | Bin 0 -> 218 bytes docs/sprite_library/sprite_0a_p2.png | Bin 0 -> 207 bytes docs/sprite_library/sprite_0b.png | Bin 0 -> 218 bytes docs/sprite_library/sprite_0b_p2.png | Bin 0 -> 204 bytes docs/sprite_library/sprite_0c.png | Bin 0 -> 202 bytes docs/sprite_library/sprite_0c_p2.png | Bin 0 -> 183 bytes docs/sprite_library/sprite_0d.png | Bin 0 -> 267 bytes docs/sprite_library/sprite_0d_p2.png | Bin 0 -> 264 bytes docs/sprite_library/sprite_0e.png | Bin 0 -> 259 bytes docs/sprite_library/sprite_0e_p2.png | Bin 0 -> 249 bytes docs/sprite_library/sprite_0f.png | Bin 0 -> 257 bytes docs/sprite_library/sprite_0f_p2.png | Bin 0 -> 250 bytes docs/sprite_library/sprite_10.png | Bin 0 -> 268 bytes docs/sprite_library/sprite_10_p2.png | Bin 0 -> 259 bytes docs/sprite_library/sprite_11.png | Bin 0 -> 262 bytes docs/sprite_library/sprite_11_p2.png | Bin 0 -> 248 bytes docs/sprite_library/sprite_12.png | Bin 0 -> 253 bytes docs/sprite_library/sprite_12_p2.png | Bin 0 -> 246 bytes docs/sprite_library/sprite_13.png | Bin 0 -> 272 bytes docs/sprite_library/sprite_13_p2.png | Bin 0 -> 264 bytes docs/sprite_library/sprite_14.png | Bin 0 -> 268 bytes docs/sprite_library/sprite_14_p2.png | Bin 0 -> 256 bytes docs/sprite_library/sprite_15.png | Bin 0 -> 266 bytes docs/sprite_library/sprite_15_p2.png | Bin 0 -> 257 bytes docs/sprite_library/sprite_16.png | Bin 0 -> 256 bytes docs/sprite_library/sprite_16_p2.png | Bin 0 -> 258 bytes docs/sprite_library/sprite_17.png | Bin 0 -> 248 bytes docs/sprite_library/sprite_17_p2.png | Bin 0 -> 227 bytes docs/sprite_library/sprite_18.png | Bin 0 -> 137 bytes docs/sprite_library/sprite_19.png | Bin 0 -> 168 bytes docs/sprite_library/sprite_1a.png | Bin 0 -> 178 bytes docs/sprite_library/sprite_1b.png | Bin 0 -> 213 bytes docs/sprite_library/sprite_1c.png | Bin 0 -> 206 bytes docs/sprite_library/sprite_1d.png | Bin 0 -> 191 bytes docs/sprite_library/sprite_1e.png | Bin 0 -> 89 bytes docs/sprite_library/sprite_1f.png | Bin 0 -> 115 bytes docs/sprite_library/sprite_20.png | Bin 0 -> 119 bytes docs/sprite_library/sprite_21.png | Bin 0 -> 123 bytes docs/sprite_library/sprite_22.png | Bin 0 -> 126 bytes docs/sprite_library/sprite_23.png | Bin 0 -> 143 bytes docs/sprite_library/sprite_24.png | Bin 0 -> 129 bytes docs/sprite_library/sprite_25.png | Bin 0 -> 139 bytes docs/sprite_library/sprite_26.png | Bin 0 -> 218 bytes docs/sprite_library/sprite_27.png | Bin 0 -> 225 bytes docs/sprite_library/sprite_28.png | Bin 0 -> 224 bytes docs/sprite_library/sprite_29.png | Bin 0 -> 238 bytes docs/sprite_library/sprite_2a.png | Bin 0 -> 245 bytes docs/sprite_library/sprite_2b.png | Bin 0 -> 245 bytes docs/sprite_library/sprite_2c.png | Bin 0 -> 215 bytes docs/sprite_library/sprite_2d.png | Bin 0 -> 216 bytes docs/sprite_library/sprite_2f.png | Bin 0 -> 191 bytes docs/sprite_library/sprite_30.png | Bin 0 -> 189 bytes docs/sprite_library/sprite_31.png | Bin 0 -> 187 bytes docs/sprite_library/sprite_32.png | Bin 0 -> 188 bytes docs/sprite_library/sprite_33.png | Bin 0 -> 189 bytes docs/sprite_library/sprite_34.png | Bin 0 -> 189 bytes docs/sprite_library/sprite_35.png | Bin 0 -> 324 bytes docs/sprite_library/sprite_36.png | Bin 0 -> 256 bytes docs/sprite_library/sprite_37.png | Bin 0 -> 184 bytes docs/sprite_library/sprite_38.png | Bin 0 -> 208 bytes docs/sprite_library/sprite_39.png | Bin 0 -> 228 bytes docs/sprite_library/sprite_3a.png | Bin 0 -> 158 bytes docs/sprite_library/sprite_3b.png | Bin 0 -> 234 bytes docs/sprite_library/sprite_3c.png | Bin 0 -> 224 bytes docs/sprite_library/sprite_3d.png | Bin 0 -> 226 bytes docs/sprite_library/sprite_3e.png | Bin 0 -> 222 bytes docs/sprite_library/sprite_3f.png | Bin 0 -> 236 bytes docs/sprite_library/sprite_40.png | Bin 0 -> 236 bytes docs/sprite_library/sprite_41.png | Bin 0 -> 245 bytes docs/sprite_library/sprite_42.png | Bin 0 -> 254 bytes docs/sprite_library/sprite_43.png | Bin 0 -> 255 bytes docs/sprite_library/sprite_44.png | Bin 0 -> 120 bytes docs/sprite_library/sprite_45.png | Bin 0 -> 171 bytes docs/sprite_library/sprite_46.png | Bin 0 -> 193 bytes docs/sprite_library/sprite_47.png | Bin 0 -> 116 bytes docs/sprite_library/sprite_48.png | Bin 0 -> 321 bytes docs/sprite_library/sprite_49.png | Bin 0 -> 177 bytes docs/sprite_library/sprite_4a.png | Bin 0 -> 272 bytes docs/sprite_library/sprite_4b.png | Bin 0 -> 163 bytes docs/sprite_library/sprite_4c.png | Bin 0 -> 228 bytes docs/sprite_library/sprite_4d.png | Bin 0 -> 178 bytes docs/sprite_library/sprite_4e 1.png | Bin 0 -> 193 bytes docs/sprite_library/sprite_4e 2.png | Bin 0 -> 193 bytes docs/sprite_library/sprite_4e 3.png | Bin 0 -> 193 bytes docs/sprite_library/sprite_4e 4.png | Bin 0 -> 193 bytes docs/sprite_library/sprite_4e.gif | Bin 0 -> 3283 bytes docs/sprite_library/sprite_50.png | Bin 0 -> 283 bytes docs/sprite_library/sprite_50_p2.png | Bin 0 -> 281 bytes docs/sprite_library/sprite_51.png | Bin 0 -> 265 bytes docs/sprite_library/sprite_51_p2.png | Bin 0 -> 266 bytes docs/sprite_library/sprite_52.png | Bin 0 -> 265 bytes docs/sprite_library/sprite_52_p2.png | Bin 0 -> 261 bytes docs/sprite_library/sprite_53.png | Bin 0 -> 260 bytes docs/sprite_library/sprite_53_p2.png | Bin 0 -> 257 bytes docs/sprite_library/sprite_54.png | Bin 0 -> 267 bytes docs/sprite_library/sprite_54_p2.png | Bin 0 -> 261 bytes docs/sprite_library/sprite_55.png | Bin 0 -> 300 bytes docs/sprite_library/sprite_55_p2.png | Bin 0 -> 300 bytes docs/sprite_library/sprite_56.png | Bin 0 -> 247 bytes docs/sprite_library/sprite_56_p2.png | Bin 0 -> 239 bytes docs/sprite_library/sprite_57.png | Bin 0 -> 278 bytes docs/sprite_library/sprite_57_p2.png | Bin 0 -> 257 bytes docs/sprite_library/sprite_58.png | Bin 0 -> 275 bytes docs/sprite_library/sprite_58_p2.png | Bin 0 -> 255 bytes docs/sprite_library/sprite_5d.png | Bin 0 -> 237 bytes docs/sprite_library/sprite_5e.png | Bin 0 -> 242 bytes docs/sprite_library/sprite_5f.png | Bin 0 -> 250 bytes docs/sprite_library/sprite_60.png | Bin 0 -> 241 bytes docs/sprite_library/sprite_61.png | Bin 0 -> 258 bytes docs/sprite_library/sprite_62.png | Bin 0 -> 263 bytes docs/sprite_library/sprite_63.png | Bin 0 -> 171 bytes docs/sprite_library/sprite_64.png | Bin 0 -> 295 bytes docs/sprite_library/sprite_68.png | Bin 0 -> 240 bytes docs/sprite_library/sprite_69.png | Bin 0 -> 256 bytes docs/sprite_library/sprite_6a.png | Bin 0 -> 267 bytes docs/sprite_library/sprite_6b.png | Bin 0 -> 270 bytes docs/sprite_library/sprite_6c.png | Bin 0 -> 281 bytes docs/sprite_library/sprite_6d.png | Bin 0 -> 152 bytes docs/sprite_library/sprite_6e.png | Bin 0 -> 153 bytes docs/sprite_library/sprite_6f.png | Bin 0 -> 125 bytes docs/sprite_library/sprite_70.png | Bin 0 -> 153 bytes docs/sprite_library/sprite_71.png | Bin 0 -> 153 bytes docs/sprite_library/sprite_72.png | Bin 0 -> 125 bytes docs/sprite_library/sprite_73.png | Bin 0 -> 154 bytes docs/sprite_library/sprite_74.png | Bin 0 -> 150 bytes docs/sprite_library/sprite_75.png | Bin 0 -> 110 bytes docs/sprite_library/sprite_76.png | Bin 0 -> 150 bytes docs/sprite_library/sprite_77.png | Bin 0 -> 129 bytes docs/sprite_library/sprite_78.png | Bin 0 -> 151 bytes docs/sprite_library/sprite_79.png | Bin 0 -> 182 bytes docs/sprite_library/sprite_7a.png | Bin 0 -> 185 bytes docs/sprite_library/sprite_7b.png | Bin 0 -> 158 bytes docs/sprite_library/sprite_7c.png | Bin 0 -> 189 bytes docs/sprite_library/sprite_7d.png | Bin 0 -> 184 bytes docs/sprite_library/sprite_7e.png | Bin 0 -> 178 bytes docs/sprite_library/sprite_82.png | Bin 0 -> 141 bytes docs/sprite_library/sprite_83.png | Bin 0 -> 138 bytes docs/sprite_library/sprite_84.png | Bin 0 -> 141 bytes docs/sprite_library/sprite_85.png | Bin 0 -> 199 bytes docs/sprite_library/sprite_86.png | Bin 0 -> 193 bytes docs/sprite_library/sprite_87.png | Bin 0 -> 177 bytes docs/sprite_library/sprite_88.png | Bin 0 -> 191 bytes docs/sprite_library/sprite_89.png | Bin 0 -> 198 bytes docs/sprite_library/sprite_8a.png | Bin 0 -> 233 bytes docs/sprite_library/sprite_8b.png | Bin 0 -> 316 bytes docs/sprite_library/sprite_8c.png | Bin 0 -> 206 bytes docs/sprite_library/sprite_8d.png | Bin 0 -> 197 bytes docs/sprite_library/sprite_8e.png | Bin 0 -> 176 bytes docs/sprite_library/sprite_8f.png | Bin 0 -> 196 bytes docs/sprite_library/sprite_90.png | Bin 0 -> 199 bytes docs/sprite_library/sprite_91.png | Bin 0 -> 270 bytes docs/sprite_library/sprite_91_p2.png | Bin 0 -> 258 bytes docs/sprite_library/sprite_92.png | Bin 0 -> 138 bytes docs/sprite_library/sprite_93.png | Bin 0 -> 212 bytes docs/sprite_library/sprite_94.png | Bin 0 -> 195 bytes docs/sprite_library/sprite_95.png | Bin 0 -> 191 bytes docs/sprite_library/sprite_96.png | Bin 0 -> 201 bytes docs/sprite_library/sprite_97.png | Bin 0 -> 182 bytes docs/sprite_library/sprite_98.png | Bin 0 -> 177 bytes docs/sprite_library/sprite_99.png | Bin 0 -> 115 bytes docs/sprite_library/sprite_9a.png | Bin 0 -> 133 bytes docs/sprite_library/sprite_9b.png | Bin 0 -> 149 bytes docs/sprite_library/sprite_9c.png | Bin 0 -> 163 bytes docs/sprite_library/sprite_9d.png | Bin 0 -> 117 bytes docs/sprite_library/sprite_9e.png | Bin 0 -> 135 bytes docs/sprite_library/sprite_9f.png | Bin 0 -> 146 bytes docs/sprite_library/sprite_a0.png | Bin 0 -> 149 bytes docs/sprite_library/sprite_a1.png | Bin 0 -> 129 bytes docs/sprite_library/sprite_a2.png | Bin 0 -> 120 bytes docs/sprite_library/sprite_a3.png | Bin 0 -> 129 bytes docs/sprite_library/sprite_a4.png | Bin 0 -> 129 bytes docs/sprite_library/sprite_a5.png | Bin 0 -> 123 bytes docs/sprite_library/sprite_a6.png | Bin 0 -> 116 bytes docs/sprite_library/sprite_a7.png | Bin 0 -> 123 bytes docs/sprite_library/sprite_a8.png | Bin 0 -> 126 bytes docs/sprite_library/sprite_a9.png | Bin 0 -> 123 bytes docs/sprite_library/sprite_aa.png | Bin 0 -> 146 bytes docs/sprite_library/sprite_ab.png | Bin 0 -> 637 bytes docs/sprite_library/sprite_ac.png | Bin 0 -> 248 bytes docs/sprite_library/sprite_ad.png | Bin 0 -> 249 bytes docs/sprite_library/sprite_ae.png | Bin 0 -> 247 bytes docs/sprite_library/sprite_af.png | Bin 0 -> 246 bytes docs/sprite_library/sprite_b0.png | Bin 0 -> 181 bytes docs/sprite_library/sprite_b1.png | Bin 0 -> 185 bytes docs/sprite_library/sprite_b2.png | Bin 0 -> 155 bytes docs/sprite_library/sprite_b3.png | Bin 0 -> 248 bytes docs/sprite_library/sprite_b4.png | Bin 0 -> 287 bytes docs/sprite_library/sprite_b5.png | Bin 0 -> 274 bytes docs/sprite_library/sprite_b6.png | Bin 0 -> 184 bytes docs/sprite_library/sprite_b7.png | Bin 0 -> 615 bytes docs/sprite_library/sprite_b8.png | Bin 0 -> 615 bytes docs/sprite_library/sprite_b9.png | Bin 0 -> 560 bytes docs/sprite_library/sprite_ba.png | Bin 0 -> 644 bytes docs/sprite_library/sprite_bb.png | Bin 0 -> 155 bytes docs/sprite_library/sprite_bc.png | Bin 0 -> 156 bytes docs/sprite_library/sprite_bd.png | Bin 0 -> 361 bytes docs/sprite_library/sprite_be.png | Bin 0 -> 357 bytes docs/sprite_library/sprite_bf.png | Bin 0 -> 117 bytes docs/sprite_library/sprite_c0.png | Bin 0 -> 126 bytes docs/sprite_library/sprite_c1.png | Bin 0 -> 107 bytes docs/sprite_library/sprite_c2.png | Bin 0 -> 119 bytes docs/sprite_library/sprite_c3.png | Bin 0 -> 544 bytes docs/sprite_library/sprite_c4.png | Bin 0 -> 116 bytes docs/sprite_library/sprite_c5.png | Bin 0 -> 149 bytes docs/sprite_library/sprite_c6.png | Bin 0 -> 152 bytes docs/sprite_library/sprite_c7.png | Bin 0 -> 149 bytes docs/sprite_library/sprite_c8.png | Bin 0 -> 171 bytes docs/sprite_library/sprite_c9.png | Bin 0 -> 176 bytes docs/sprite_library/sprite_ca.png | Bin 0 -> 200 bytes docs/sprite_library/sprite_cb.png | Bin 0 -> 215 bytes docs/sprite_library/sprite_cc.png | Bin 0 -> 253 bytes docs/sprite_library/sprite_cd.png | Bin 0 -> 252 bytes docs/sprite_library/sprite_ce.png | Bin 0 -> 249 bytes docs/sprite_library/sprite_cf.png | Bin 0 -> 156 bytes set_bytes.vbs | 22 + src/assets/README.md | 1 + src/assets/audio_data/README.md | 1 + src/assets/graphic_data/README.md | 1 + src/bank0.asm | 10427 +++++++++++++++ src/bank1.asm | 6206 +++++++++ src/bank2.asm | 3092 +++++ src/bank3.asm | 1572 +++ src/bank4.asm | 814 ++ src/bank5.asm | 420 + src/bank6.asm | 1885 +++ src/bank7.asm | 10647 ++++++++++++++++ src/constants.asm | 513 + src/ines_header.asm | 38 + 281 files changed, 40055 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 assets.txt create mode 100644 build.bat create mode 100644 build.ps1 create mode 100644 build.sh create mode 100644 contra.cfg create mode 100644 docs/Aim Documentation.md create mode 100644 docs/Bugs.md create mode 100644 docs/Contra Control Flow.md create mode 100644 docs/Enemy Glossary.md create mode 100644 docs/Enemy Routines.md create mode 100644 docs/Graphics Documentation.md create mode 100644 docs/How Contra Prints High Score.md create mode 100644 docs/Level Headers.md create mode 100644 docs/Sound Documentation.md create mode 100644 docs/attachments/pw_0.png create mode 100644 docs/attachments/pw_1.png create mode 100644 docs/attachments/pw_2.png create mode 100644 docs/attachments/pw_3.png create mode 100644 docs/attachments/sound_ff.mp3 create mode 100644 docs/diagrams/game_flow.mmd create mode 100644 docs/diagrams/graphics_flow.mmd create mode 100644 docs/diagrams/level_routine_flow.mmd create mode 100644 docs/diagrams/read_sound_command_00 simplified.mmd create mode 100644 docs/diagrams/read_sound_command_00.mmd create mode 100644 docs/lua_scripts/fceux/Always Invincible.lua create mode 100644 docs/lua_scripts/fceux/Show HUD Info.lua create mode 100644 docs/lua_scripts/mesen/Always Invincible.lua create mode 100644 docs/lua_scripts/mesen/Enemy Debug.lua create mode 100644 docs/lua_scripts/mesen/Show Background Collisions.lua create mode 100644 docs/lua_scripts/mesen/Show Enemy Positions.lua create mode 100644 docs/lua_scripts/mesen/Show HUD Info.lua create mode 100644 docs/sprite_library/README.md create mode 100644 docs/sprite_library/player_1_game_over.png create mode 100644 docs/sprite_library/player_1_lives_medal.png create mode 100644 docs/sprite_library/player_2_game_over.png create mode 100644 docs/sprite_library/player_2_lives_medal.png create mode 100644 docs/sprite_library/sprite_02.png create mode 100644 docs/sprite_library/sprite_02_p2.png create mode 100644 docs/sprite_library/sprite_03.png create mode 100644 docs/sprite_library/sprite_03_p2.png create mode 100644 docs/sprite_library/sprite_04.png create mode 100644 docs/sprite_library/sprite_04_p2.png create mode 100644 docs/sprite_library/sprite_05.png create mode 100644 docs/sprite_library/sprite_05_p2.png create mode 100644 docs/sprite_library/sprite_06.png create mode 100644 docs/sprite_library/sprite_06_p2.png create mode 100644 docs/sprite_library/sprite_07.png create mode 100644 docs/sprite_library/sprite_08.png create mode 100644 docs/sprite_library/sprite_08_p2.png create mode 100644 docs/sprite_library/sprite_09.png create mode 100644 docs/sprite_library/sprite_09_p2.png create mode 100644 docs/sprite_library/sprite_0a.png create mode 100644 docs/sprite_library/sprite_0a_p2.png create mode 100644 docs/sprite_library/sprite_0b.png create mode 100644 docs/sprite_library/sprite_0b_p2.png create mode 100644 docs/sprite_library/sprite_0c.png create mode 100644 docs/sprite_library/sprite_0c_p2.png create mode 100644 docs/sprite_library/sprite_0d.png create mode 100644 docs/sprite_library/sprite_0d_p2.png create mode 100644 docs/sprite_library/sprite_0e.png create mode 100644 docs/sprite_library/sprite_0e_p2.png create mode 100644 docs/sprite_library/sprite_0f.png create mode 100644 docs/sprite_library/sprite_0f_p2.png create mode 100644 docs/sprite_library/sprite_10.png create mode 100644 docs/sprite_library/sprite_10_p2.png create mode 100644 docs/sprite_library/sprite_11.png create mode 100644 docs/sprite_library/sprite_11_p2.png create mode 100644 docs/sprite_library/sprite_12.png create mode 100644 docs/sprite_library/sprite_12_p2.png create mode 100644 docs/sprite_library/sprite_13.png create mode 100644 docs/sprite_library/sprite_13_p2.png create mode 100644 docs/sprite_library/sprite_14.png create mode 100644 docs/sprite_library/sprite_14_p2.png create mode 100644 docs/sprite_library/sprite_15.png create mode 100644 docs/sprite_library/sprite_15_p2.png create mode 100644 docs/sprite_library/sprite_16.png create mode 100644 docs/sprite_library/sprite_16_p2.png create mode 100644 docs/sprite_library/sprite_17.png create mode 100644 docs/sprite_library/sprite_17_p2.png create mode 100644 docs/sprite_library/sprite_18.png create mode 100644 docs/sprite_library/sprite_19.png create mode 100644 docs/sprite_library/sprite_1a.png create mode 100644 docs/sprite_library/sprite_1b.png create mode 100644 docs/sprite_library/sprite_1c.png create mode 100644 docs/sprite_library/sprite_1d.png create mode 100644 docs/sprite_library/sprite_1e.png create mode 100644 docs/sprite_library/sprite_1f.png create mode 100644 docs/sprite_library/sprite_20.png create mode 100644 docs/sprite_library/sprite_21.png create mode 100644 docs/sprite_library/sprite_22.png create mode 100644 docs/sprite_library/sprite_23.png create mode 100644 docs/sprite_library/sprite_24.png create mode 100644 docs/sprite_library/sprite_25.png create mode 100644 docs/sprite_library/sprite_26.png create mode 100644 docs/sprite_library/sprite_27.png create mode 100644 docs/sprite_library/sprite_28.png create mode 100644 docs/sprite_library/sprite_29.png create mode 100644 docs/sprite_library/sprite_2a.png create mode 100644 docs/sprite_library/sprite_2b.png create mode 100644 docs/sprite_library/sprite_2c.png create mode 100644 docs/sprite_library/sprite_2d.png create mode 100644 docs/sprite_library/sprite_2f.png create mode 100644 docs/sprite_library/sprite_30.png create mode 100644 docs/sprite_library/sprite_31.png create mode 100644 docs/sprite_library/sprite_32.png create mode 100644 docs/sprite_library/sprite_33.png create mode 100644 docs/sprite_library/sprite_34.png create mode 100644 docs/sprite_library/sprite_35.png create mode 100644 docs/sprite_library/sprite_36.png create mode 100644 docs/sprite_library/sprite_37.png create mode 100644 docs/sprite_library/sprite_38.png create mode 100644 docs/sprite_library/sprite_39.png create mode 100644 docs/sprite_library/sprite_3a.png create mode 100644 docs/sprite_library/sprite_3b.png create mode 100644 docs/sprite_library/sprite_3c.png create mode 100644 docs/sprite_library/sprite_3d.png create mode 100644 docs/sprite_library/sprite_3e.png create mode 100644 docs/sprite_library/sprite_3f.png create mode 100644 docs/sprite_library/sprite_40.png create mode 100644 docs/sprite_library/sprite_41.png create mode 100644 docs/sprite_library/sprite_42.png create mode 100644 docs/sprite_library/sprite_43.png create mode 100644 docs/sprite_library/sprite_44.png create mode 100644 docs/sprite_library/sprite_45.png create mode 100644 docs/sprite_library/sprite_46.png create mode 100644 docs/sprite_library/sprite_47.png create mode 100644 docs/sprite_library/sprite_48.png create mode 100644 docs/sprite_library/sprite_49.png create mode 100644 docs/sprite_library/sprite_4a.png create mode 100644 docs/sprite_library/sprite_4b.png create mode 100644 docs/sprite_library/sprite_4c.png create mode 100644 docs/sprite_library/sprite_4d.png create mode 100644 docs/sprite_library/sprite_4e 1.png create mode 100644 docs/sprite_library/sprite_4e 2.png create mode 100644 docs/sprite_library/sprite_4e 3.png create mode 100644 docs/sprite_library/sprite_4e 4.png create mode 100644 docs/sprite_library/sprite_4e.gif create mode 100644 docs/sprite_library/sprite_50.png create mode 100644 docs/sprite_library/sprite_50_p2.png create mode 100644 docs/sprite_library/sprite_51.png create mode 100644 docs/sprite_library/sprite_51_p2.png create mode 100644 docs/sprite_library/sprite_52.png create mode 100644 docs/sprite_library/sprite_52_p2.png create mode 100644 docs/sprite_library/sprite_53.png create mode 100644 docs/sprite_library/sprite_53_p2.png create mode 100644 docs/sprite_library/sprite_54.png create mode 100644 docs/sprite_library/sprite_54_p2.png create mode 100644 docs/sprite_library/sprite_55.png create mode 100644 docs/sprite_library/sprite_55_p2.png create mode 100644 docs/sprite_library/sprite_56.png create mode 100644 docs/sprite_library/sprite_56_p2.png create mode 100644 docs/sprite_library/sprite_57.png create mode 100644 docs/sprite_library/sprite_57_p2.png create mode 100644 docs/sprite_library/sprite_58.png create mode 100644 docs/sprite_library/sprite_58_p2.png create mode 100644 docs/sprite_library/sprite_5d.png create mode 100644 docs/sprite_library/sprite_5e.png create mode 100644 docs/sprite_library/sprite_5f.png create mode 100644 docs/sprite_library/sprite_60.png create mode 100644 docs/sprite_library/sprite_61.png create mode 100644 docs/sprite_library/sprite_62.png create mode 100644 docs/sprite_library/sprite_63.png create mode 100644 docs/sprite_library/sprite_64.png create mode 100644 docs/sprite_library/sprite_68.png create mode 100644 docs/sprite_library/sprite_69.png create mode 100644 docs/sprite_library/sprite_6a.png create mode 100644 docs/sprite_library/sprite_6b.png create mode 100644 docs/sprite_library/sprite_6c.png create mode 100644 docs/sprite_library/sprite_6d.png create mode 100644 docs/sprite_library/sprite_6e.png create mode 100644 docs/sprite_library/sprite_6f.png create mode 100644 docs/sprite_library/sprite_70.png create mode 100644 docs/sprite_library/sprite_71.png create mode 100644 docs/sprite_library/sprite_72.png create mode 100644 docs/sprite_library/sprite_73.png create mode 100644 docs/sprite_library/sprite_74.png create mode 100644 docs/sprite_library/sprite_75.png create mode 100644 docs/sprite_library/sprite_76.png create mode 100644 docs/sprite_library/sprite_77.png create mode 100644 docs/sprite_library/sprite_78.png create mode 100644 docs/sprite_library/sprite_79.png create mode 100644 docs/sprite_library/sprite_7a.png create mode 100644 docs/sprite_library/sprite_7b.png create mode 100644 docs/sprite_library/sprite_7c.png create mode 100644 docs/sprite_library/sprite_7d.png create mode 100644 docs/sprite_library/sprite_7e.png create mode 100644 docs/sprite_library/sprite_82.png create mode 100644 docs/sprite_library/sprite_83.png create mode 100644 docs/sprite_library/sprite_84.png create mode 100644 docs/sprite_library/sprite_85.png create mode 100644 docs/sprite_library/sprite_86.png create mode 100644 docs/sprite_library/sprite_87.png create mode 100644 docs/sprite_library/sprite_88.png create mode 100644 docs/sprite_library/sprite_89.png create mode 100644 docs/sprite_library/sprite_8a.png create mode 100644 docs/sprite_library/sprite_8b.png create mode 100644 docs/sprite_library/sprite_8c.png create mode 100644 docs/sprite_library/sprite_8d.png create mode 100644 docs/sprite_library/sprite_8e.png create mode 100644 docs/sprite_library/sprite_8f.png create mode 100644 docs/sprite_library/sprite_90.png create mode 100644 docs/sprite_library/sprite_91.png create mode 100644 docs/sprite_library/sprite_91_p2.png create mode 100644 docs/sprite_library/sprite_92.png create mode 100644 docs/sprite_library/sprite_93.png create mode 100644 docs/sprite_library/sprite_94.png create mode 100644 docs/sprite_library/sprite_95.png create mode 100644 docs/sprite_library/sprite_96.png create mode 100644 docs/sprite_library/sprite_97.png create mode 100644 docs/sprite_library/sprite_98.png create mode 100644 docs/sprite_library/sprite_99.png create mode 100644 docs/sprite_library/sprite_9a.png create mode 100644 docs/sprite_library/sprite_9b.png create mode 100644 docs/sprite_library/sprite_9c.png create mode 100644 docs/sprite_library/sprite_9d.png create mode 100644 docs/sprite_library/sprite_9e.png create mode 100644 docs/sprite_library/sprite_9f.png create mode 100644 docs/sprite_library/sprite_a0.png create mode 100644 docs/sprite_library/sprite_a1.png create mode 100644 docs/sprite_library/sprite_a2.png create mode 100644 docs/sprite_library/sprite_a3.png create mode 100644 docs/sprite_library/sprite_a4.png create mode 100644 docs/sprite_library/sprite_a5.png create mode 100644 docs/sprite_library/sprite_a6.png create mode 100644 docs/sprite_library/sprite_a7.png create mode 100644 docs/sprite_library/sprite_a8.png create mode 100644 docs/sprite_library/sprite_a9.png create mode 100644 docs/sprite_library/sprite_aa.png create mode 100644 docs/sprite_library/sprite_ab.png create mode 100644 docs/sprite_library/sprite_ac.png create mode 100644 docs/sprite_library/sprite_ad.png create mode 100644 docs/sprite_library/sprite_ae.png create mode 100644 docs/sprite_library/sprite_af.png create mode 100644 docs/sprite_library/sprite_b0.png create mode 100644 docs/sprite_library/sprite_b1.png create mode 100644 docs/sprite_library/sprite_b2.png create mode 100644 docs/sprite_library/sprite_b3.png create mode 100644 docs/sprite_library/sprite_b4.png create mode 100644 docs/sprite_library/sprite_b5.png create mode 100644 docs/sprite_library/sprite_b6.png create mode 100644 docs/sprite_library/sprite_b7.png create mode 100644 docs/sprite_library/sprite_b8.png create mode 100644 docs/sprite_library/sprite_b9.png create mode 100644 docs/sprite_library/sprite_ba.png create mode 100644 docs/sprite_library/sprite_bb.png create mode 100644 docs/sprite_library/sprite_bc.png create mode 100644 docs/sprite_library/sprite_bd.png create mode 100644 docs/sprite_library/sprite_be.png create mode 100644 docs/sprite_library/sprite_bf.png create mode 100644 docs/sprite_library/sprite_c0.png create mode 100644 docs/sprite_library/sprite_c1.png create mode 100644 docs/sprite_library/sprite_c2.png create mode 100644 docs/sprite_library/sprite_c3.png create mode 100644 docs/sprite_library/sprite_c4.png create mode 100644 docs/sprite_library/sprite_c5.png create mode 100644 docs/sprite_library/sprite_c6.png create mode 100644 docs/sprite_library/sprite_c7.png create mode 100644 docs/sprite_library/sprite_c8.png create mode 100644 docs/sprite_library/sprite_c9.png create mode 100644 docs/sprite_library/sprite_ca.png create mode 100644 docs/sprite_library/sprite_cb.png create mode 100644 docs/sprite_library/sprite_cc.png create mode 100644 docs/sprite_library/sprite_cd.png create mode 100644 docs/sprite_library/sprite_ce.png create mode 100644 docs/sprite_library/sprite_cf.png create mode 100644 set_bytes.vbs create mode 100644 src/assets/README.md create mode 100644 src/assets/audio_data/README.md create mode 100644 src/assets/graphic_data/README.md create mode 100644 src/bank0.asm create mode 100644 src/bank1.asm create mode 100644 src/bank2.asm create mode 100644 src/bank3.asm create mode 100644 src/bank4.asm create mode 100644 src/bank5.asm create mode 100644 src/bank6.asm create mode 100644 src/bank7.asm create mode 100644 src/constants.asm create mode 100644 src/ines_header.asm 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 0000000000000000000000000000000000000000..90ae43ad63e7eb43a2120e958054e8d8d0051bad GIT binary patch literal 2405 zcmbVO`#aNrAO38{MuZ}V)JTO8qoY%skm*1w`Z|uG91=MuvpJ54@K7o8Aar1}+Lt*j zAr^^58^tEcSC%0*TDE6TuIu>&p6hyFuh;#$uls&o_x1Yeo#k@ogo2!=8~^|cXh%C& z002oKumF*g)E%^%RY}|Da_YRjq^tUlnjZv?5CL1HIDjk;y#i#ifV#RmDnh%J4hUF} zL~mb0R=i`02%+n#BC?)$za6l91bnyJ?@yUC`KHzx7!N+ZB2X4aE zZIefC+WxjFANkTgC#c-!`a9O5^i0i%+30336XvJYfztNAnY9Jcu_md3$TqDh!E%Ci zH*faGy1JM`jWgk0m1xYkVr-JLIPn3qQ2=jTG$_M^dvsT2;2qWrEVxrvkH|3^!8eIt z>zun(@ybvhXCYEEXu)vvD!DRw>wab}a@``+(Q)Z>c-BYvn8Ta~o?WCD$E2Ng*!2)S z{aL-m{>b^~YloA9P(~rm7NFBOHXc@|+GZOYyGJ?)cfV>5oBXzV6NcMTOKDjsxfFBm zyiOjj@R?{e4R%Ta@0%Nmecv3PSAe$}? zBOc@{{OcshFB66}ve!Sr7oN2b1er0EDSNbnxR!%G^g|xZ-6zvJ&#{*wR@K0EmcuGB_GU_9 z*{_O?b##eSlCyHOZ5{mna@9J(w^m6GLocx&cI)(*qus2ErrxLsj+7=*EM?Rw5R{Ta z(LbRtj-pt0K2Lw!d5dejA5|x0m<|dTqZ;k4P0|p(4+IU&;1`Z}k57AvyXJ7}g${W- zQS`SvIL6JL7#>4XDpk;j)W2(1lxrq4rgw)R1 z!FZqN*I7l8vxbT^-!7;Vr!@%6H=G)B-#IAEfXcUD@6X_pS_O}oLZl(6qW`Md_c9u2 zee8}8Pq;V(SqQw6ROpvv71fDmP28Cnwi&9FbMsFsRGuC9tVv)Adv%9x=$JO@Oh-VM z+FlZzEGRlri&G)L8vAuWCv1D8jCLb^*^!!8GA(s^?y4EPwwy_hr5krMGKgTUYf~LQ3@_oifSnYuI zoWcv=A)@9^K1^WWIzYPm?Ni)%?@0c{h)vGs;S&G z9bz$IxkclsRdruZl6K}*M;gDuZs?wU$YCyU9d3w67HaKJ;5QqWVGpi zzHTDqISZZJqJc=Pnx5S0aQt(>`WbjMmITd-Gykof8omtvJWvjj5sIMm)A+9<3YOSJ z(?Y4FlRSE{qsM0n9s{}=#T8(+WeQkkIm9;UpEf)GMV(tt#-}s4_ZvhUT-BvXeGvz< zebv9L72`k%@+O6{@c!u(ZJesB&G^zZ1q=-S(Q4&^q_L zs4v6+ZH*_)r9RLx<6$@=tY_i(_5wzYXMSIO{{Hfm8czgsCC1bxr^XYz|5LKU?!3lp zXqVdfd6yu1{flE3yOuY@+&ZSAO#xNLa?DeZ!S! zZ1B;ePs`^!?{2IO+^!FK?1H{>@EqR1=d`W`{1O_XbDpq=HMe z8y#uo9s8??y|<+LO&b^Q2*PADt-CHOJGXqlbupo8IQ>JCO>ndT(n{ERVplW1%Zc*3 zoniEyCku@0*~n*3Z6hvOM7Srn=(VQmE9aY92W5k_wguhe%*(R1JGEvyGrcA7m{7X9 z(~0mwRb)f<PfVa2P4!8pInHvlN@Wuz3(Op znl+RPt)x?!B^*@H?bg0S!>)pv5={M@tbOzs2i8DH#xKPq^nq{C7d=1)MT7iCG2*2* zp?qJjOyH8#SdA(WT@u*$o*KRhf;oG&U9OJMk(vlHKmmI$@F80LH@!hZ5oA4z0HBW6 z>S4+ZP?oAUDGhAkTZ4u+a>g}ZCN!}_j5~#H(Jt1W-`7Z%z`!ja+ IwwRRv0R2s}DgXcg literal 0 HcmV?d00001 diff --git a/docs/attachments/pw_1.png b/docs/attachments/pw_1.png new file mode 100644 index 0000000000000000000000000000000000000000..8d919d8c197fa0adc3608027e320d04436684498 GIT binary patch literal 2247 zcmbVO`8(A88vYJr79}HkZDlM|DI`?NGKPjsWF*UZ84P0yMY7A+5*nRkJt(rgA-&m# zl6?|FN~C2*#aNOw!`Pakd6_fSb)7%p{Bl3{{d}JLx$f)v>2uxQ?v#|Ik|Y2CQUq(f z0{}oo6j*|ai*QeC)w-x)>}?$_MRk|wn88utI2EwK34Lh7po>5n6R54N?VtmEW~FdT zSgZbU2LK>E2zYbHXb%pjDZ;qXtSQL$asoB8lIk}l)p`fza1GK{FrSynLV}L9Ab69fs~UU1Fk7U+>?t4%g%9MsN18{qH{2 z+723R$scGZb27_0u*^*_{dUQ|t;1vE66X?_?_DAcCmXoKYct3OPz&0bu8IYBLsNL0R5gEBxA{{KMU{jJ_-rGjzwBK)RUn=yEIOnLA3Fg*p ztq1;f*}jTPHzyT?Vz$~2%a~78;8d^ z<>WEFId1mj?!_pt>^uTQG82SH)Ee9FR3kY$LnthK@w?lO)Amp&ZONM;{99!50ri>` zD9x0oqu>6w7+C`7RuSy$r^Bc^DD?{>?^&l}BuqVBYG8**JSbfBL8eL|s7HBH|FMC1 zr6DNVmYPR+g0q&s5d8s}#=X1!IL0IW^kc3Jj7>_K3+ocpy0vdo~=6Ih?sc2vkB`K(ZeQDwp*3*0)H4W;F z=&X~f7FJ$=I_y5eGnGraKv-dx_Nnz75S&f&W}eIOj~B-`8p9MCq2?&5ystrzPM9Za%o-r+Ye0v=i4Iyp-c_Od~RT&qmAu*HOFHU8@p zJM$wBCh&~SmJc^c@4tF>PM?i>MKrNkC^vYykd}4bh`p)dsU7)R$2fp$zBeNy+0FBZ zcS9!LRY~gJ=;)N`@^xKj!5YULK2um6J=N1pUuopx>R$x=kPI?wvlo7Pvcr$U)0!D|Q5g|jgKzJO8M?-)@8DN51UN0o-JuZuZzZje%?|}0T*2}j zbjkN(e6Cl#NmvhoIZ-h27CTxl>Fgb!D>Fa*Nhy&j7}R)+rIS0G=DK})<@eR2Y5crX z_dz+@(+@w2(l01C!&IB;Yt~Jfg|p)4*&+JOW5N4@U~u7Ye)aNw+J=tb$LOuZ+Io|B zSo@y2Rjyn5O)UrqT=*WpJt~g%vN_~N!{vwy!&bAt0d(QU;zcL7)*s#SFE+Vi<1z-U z&hkJl7teL?R?qn3{p6Q%!jjI;`pyrhGA@ngVpqqF1N~;dZS$<*>6z?7^xGx~Ug?Fg zZAxQVH;>eZeEAqPKKL$s;vF`9$J$TO$tokV(jB zSJaEEn4MCwI{9fx!44fxsfVXW8T_M~9K0s>X}Amm6Kug3XA?eeHBzXWZ5VU8R9z(Z z_89&WzQ*6Snq^`^8dylz9B_XW@UBStYhYi6O1=}^<5k4gSAN}Y?bUGysM$@Dw&dRF!AN;GajUDE}PFAqk} zM+o3N)6FPV7t3@zegc`5%Wy5Y*`?XYaF=HCeOOclj03NY4H1-ZTEBp0*M9fI$EwQ~ zdv5(+>#M8@tt9>C5o0)Y+`LDWsc6xh6WyyX7J%)3x zJg`-zSf>ou24Cr-?>`8s$g&vu)xWEWAf!NRgP(vzq%S6Wmr{5eSCk+TgSjAk_YEit zCYOSkick#r-Ip638EA4=`~GXFkMT6josj0t7e&ypkmUSHn6&%JbF(WtE@;Nr{}RS4 zNoe%?@^1(u9$!oP|6%oi-tQs?6nBV#@Poaumw?utU+8?Dz-G$AVhgZXGmrXV276|c zG3QoQNg?8dk15w$WO{IN#o@Y>()rQ?d&(yj<68>`!P zm|T-*+1M*eYu_np%i`L9mBFTBO;{8?E^^I^)Cw@s$1O7a^Ut$n(`e^BV3U_ksG2?# zvJ=SrK+5uTv|p%5V3ET8h}-;>kYs?7#}OpXi~U5)#AIVHbYhY<5A4FI7J+CQf|doM z?U5LO{tRU(z>?d$0w>^(V%W{Ct+Z4QE(xCH>VpJ5NBjzpiYX#h6o<-qpm~vh9+1O& z(zoa5LnRPrrvn9k(punhK|8(d6|p#xZ;iZ3*ZsP>;@g&3ym7)67Y5-L4+oq^&NXO5 zC1M5-&EUJwgh5)Kzy7@M4vaxP4IIiYy`-Z42-Wwraa=`1!s3(45uTWq)GnO%4Aix> z3*}m>fEoGGtD5wFKjP$+g~H2ANj8%^TcC%M&@`1gFhiE`T=XLW2$pvEIt%jE{{l~+ BT44YH literal 0 HcmV?d00001 diff --git a/docs/attachments/pw_2.png b/docs/attachments/pw_2.png new file mode 100644 index 0000000000000000000000000000000000000000..32f5e3b3556abd310f7772315ab733a1b026230a GIT binary patch literal 2248 zcmbVO_dnYU7yiZ$TB~aBx+t}4kJwdeUFz0~*1WY{MG-<{@3t;ViMB2^VpAe{jZ4(! z+N-6h(m_JZM4CokZ9kv)4|soh&U2n~&hvSGJlReTHs?4+IROATXM4@k1pq*26xd^7 zK0}G#x|6eV*~#A3>Z}SvSG2AIdYOPZgyG-H2nhqSmVxHx<}MBCek?#)u05R?VVp9k zeB}fH;J9ULY33U5LtJvaD`v)Pz0bswQ^aD9kBlMuA=4=K#_Vw$Byf&Zo$T$p;vGNP(WlfH%0FF#?rPxPE#dHOW|3dB8tnY>Ivwi^m$ zanxUL=l9HmaW$YD1!Ps)HI*RiZ6qY>UZ$-qB6s|81RZSZSvKFpx zg(xWkx^Y;mKwUSW34kp7;Ww9;-nX)vBX-(I(hUt-gAha;XywcZ;x_kGrKS~FZym*^ zMk`5k^WDy?8ZiH6JV z^8TBV;znc%amz796}y}Vqu5N@qARwNv^l-a_+MV<)I}SWe~_%3bBx6S7Jo`}Jgeb{ zHsMr5J z6BS9YgQ}2=uh~5xboDUkS1{Qhxm#Uj{KeO+LqIL*WYOJ+Eb-;uN`79*XmR}dDsB89 zlNmF~+u2EzV?w)$(66eLsAm#`(ooHHcQy+4pkWFU5(G}bUFEy)B~pq#D53=q44)UB z3en*~YpTLN%20AoAmGZS(23*#udn0G3HUuYNUvq3h+aa$9wkTx%R@S;H>}sRbdgZn zECFVES_xpACL=T_3WLJ+z;9`@6>n@3TnDdDL}<#=%RQNS6QTDJZ*W3QJ!G%4ypy5L zV`eh;fTOFBN#{4ult;y38;ii^VyWayvU5ig5Jtbj_I7lKWuUpm_&E8hq@d1!Mnc7= zq*{+l>fW&5JxMx+<+er1!!a$GcZKT(qzWoIOyef1;OG|$PMB(;g~v4CRd$D--05X_ zBMt9-LdUs=eca}Ps!;yn`7E4QPQrKeP`cb{V|)$?vq9KIo5?0G*m~}QNk$#>df8S4 zUOzA#w)HCsUHs{IGKq%r>@}E0zsa0Q{hC$M67k2z_=5X8uN}rI^^h-FcQ}RGy1|et zO4gOj#Kn|r>;}4Se7?i2JB1c8jgXRi{^}smx*%(U&&S4kbuvrVaf-uHODs++8d2zb z(eQPYu3Sc8r_t(ga28r*c(=)Ywq>pebHh*UgwFeL>~3o2OF(j7ZM!)!;RnQ9lwkcZ1UNOTVWimJ9@<_ zj~c@T8t>wMy*GCyV@J4(0pX{{C`Yecon>|1i&6g6hds}hvmX;O5n$Mwy19Q7MJrOh z_F-!gb|Mx!#^>0xYjKX4HWPXkkFD!n3^3eNF@L_~&9h87f@KvS%8q`y=gv$vp6vQF z>~AkY(dKZ*0Je^CezPHKX6uxPn0VkpXy+5QBKHwMv??Nv_krcOPn@wY{*m!gKhM0= zPhB-pa2_Vy@x!(=lsO&wxbY`f6}!fwxJE#+pBeRUn{bUZKY|*N5vj>` zBq>tM5{__uN-Hevaj?Tx!B4|*dCp>NX^I8~YDnlSUZS>sDkL3MbHOvbg6q|ozN=_b z+Nyc2S$cO_H0hymnvm=7nj_j@r3XopaxB%QcdkqMW{b#kmeT1D+gB8(#iG`^-&5fN z{wYDWvDFYn(18?4k3`Y{m&1k(L9B3fpOv?_L^Wv$$03G5`h>~)TVNpI5?V^#8(Fl* z^BbV26Ke`lzf`=&QwI2V(Lh`P*9slZD*e~OE_)0RXXG@j`GgMVM3%QnS6((7_veYN z8|j=rprM4a-eQnD^U8oZ2k_}W3Xcq>q;*q70sB{Y$zbJK*6&jTIGjzP|2!|~`-}ig z|J$Qp6NnZ%qQ4QCc$sL1&s0kb`P!%pr?6%W3Q1SN)COt~S$Soa90%H~3?1QRiv>w) z>JOCxmssZd#38D#$_yB}3s!*lDR7|H?IQ~e2SAa4IKV!@W9p&A)VuE7arTk`wpI?7 JD0APm{{jaIXe$5! literal 0 HcmV?d00001 diff --git a/docs/attachments/pw_3.png b/docs/attachments/pw_3.png new file mode 100644 index 0000000000000000000000000000000000000000..b070bdae72d3f797b16c6b61c46f96565b415ce8 GIT binary patch literal 2282 zcmbVO`#;ltAO3E}Mkz|SQ#GfGkYf?1S;&$@h&yNIa62@~SvF@QB0O{_G1lAw|{Cc9bYKz~_+Fo-o|3` z%DthtyglBw_#-+1!0w_=O&p@#85FBP7ZWkFIj~R5tX06O)%4t>ad&XE*7+C26y1Ap zDOBRbJ=9O*vWfXlI;Z?MyPuH;CDv6Rmc!dzH7HYCBPE@KOWSLEms$iyLW##0IILJi zA9MNpt`x7aDN2JG!q;dsco@75FRqx<%z-zrtCYrr$%F0lrw~KcD=OQMf&zMC#MDN?)$Unl~ zK|TkU|7ve5@UP5gD)nR%p)nh0<`?M?8l!S-;j~+JzE1BY`Z2VmcdLxvAW+*`S-uEb zF#QN=ioB6mK=o$0*iO5Zh;Z+Kx9>G@EZhryx6a>0Ya!->yX)H(?2$uR0oUSRu zJp`D6U^TN3Qu|1$NVUA~Ai;lvjM*pZ#WNW$w^R5D>H#rop7-^XiUyjE2u7^`bh-eB z)>Wu3uSi(nU+b(K%G~Rw-EmzuLLxC3y+M7_Cr{QvI~o-%{3F59FTpn&3Q09WOvFSB zz6Je#(S&O1Uj5V5jaTJX`7Aa~bCkOt)@+8*NYNT7<}^?O{ZIf%XNmnr$5LrpzkVrs71b4Hw++{ zoXXBlcJbKpZpblpmJxk6Ha3S?&(Lt>vKS`Ng`$$^xt?ayMk5nhH;DCd)yeKXur%Z3 z&dxZr0kixPWKOzG3J9OC2%L-@ zjdp(>LoEnhRz2M6(F?sg+u?^{t1gT=ogC$+K@WDu45u;bIyjXSE>acrbT~wNrL+~a z`{|)Ole@kIS@XSx&-cO`h4rARGY@CRjmOGG9KG@R;>#nSWfG{|0mX4+k|(igsoSSl z@^l@%o>Oq;IZmR!>(kETtYFz@A-QG}%c3c#Xi?w~dWbgl0`?4m#eILlsa}6xzpd^! z6}^{GQ)e_`Y}>Q+gXxlWUlqi_ec!|%jEbWTHHTz2+=-~rYrWLh4=>tYy>-K-b*Ec$ zaF+?57T2K>%L7%NJhr@BJ@EQu&!IS8DUn!5{B$Mz_E^60k7>g|zr~dU9Xpk}lEk#c%nWbEaHXW*7U=2RG4;t_Gti#LnoGpQnnxE+wmi;N zjDx(UqO;qjwc;uk=T4em{yZ#Y4G+iEL9?QCe##|dS>VqjWgsE$9&~jv{>xq?hS1V_ z+(aU^&UpYcd3ia(c|Y(v!IFxsL@~iQ)L)B1yX~3gR>F#Rmwdk6W+$;CrD6I z3O@Su1CAd|A_X=VAsw*KON@>7H#y30{u1?3y29Mh>5d$J1Vxkx<0g96`oB0ox1u9h z8DH~jFx*Il!?)J=OPGy5EAjs?KmO<45- z{%$;R#=VY1izng2oekI!R*0cr`jn=a>HE-3wPOsZfWpO!GwD7Lf}*>+D~U*C?Htn5 z*u*9mWhtccdrQ=dvu0O2`aitT?ajflb=zm@RmEd^`hGbN6bYL#PdZ|4$|UDIPbNSC z>vXyfxq@~yeJ%5iY{KqNvAxn}>6$eX5R;Dj=%1ZEezzMs$NXZkf|#?H@0>H=!SUZL z_12*Z8dM3qX{)a7UqyrZNGgXPUFLu?I;R|9z1D9I_d`|&!@gSZ#Y&?il%-;OzDNmN zTUr$un)nzGmxD8a*rf8NY*hy{Yd3i zBE+u+jP!xYUJ*C4Zeb|A;wDan;c>*>T`joXU$Af*rmTb8g?x4e&iB0^sZ rrpASINw6W9A;AlYR-Q+x5Ipd$L(HrC^I!aL2mqkXtW9fCo=N`!iyU^x literal 0 HcmV?d00001 diff --git a/docs/attachments/sound_ff.mp3 b/docs/attachments/sound_ff.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..2fbc9698139ce6e5c2fb1df4d3cd842a52890737 GIT binary patch literal 10752 zcmeI2cT7}Y|DcCH^kL||4$>Kl2nafKhTc0m3`M$B1yPxy_ui!pz4s2*Y^h{6|; zCQa!m3bT;+_r8B@vYTwOn`}1wBscfuoaCOH=bn7d=bUqKf6@j4|2=R|r>8e{)HfF^ z03ZYakdl&uZ(zFt0)dF%AbW$dvah?WG>b-^k z-SGd&9rs`U@k;0b0G@z)mkVJSJ}n-q*UsxL1u+3045Np+E5AyuhB>@94WwvrK9uIM zp!f?tR1obA{20LU4eJF z2v9D-;eLa-w$wu=xPHj3=bKx~=vVc+J^OO-wEdv1^Q+@W`{I4Y0g?(sBQ9Wf7NCyl zI5am)3(24_osqW3gurw%3H23sda1nVpzIn-I4LVl?SS!rii~irk63!2lI`wE&?u@< zkCH;`N=WFuDJ|%6GVuPV*DdXvm%SK;cf3r3$3&&*nuJ-z*}4{;6pZ+UlE_$)iWZid z$~sDZWNTIHlwZJhu)+a~(}Q5uIO1FwC>Tkx!t`-2EbUJJ+`axMIMV+j&YC(iFvPAe zmbWkzpEMjm8uASt9+KbugA;e_-w6QmD6s6f#()EPH&i-%@oEVyvBni=6ia#{$)_0j$o;{65nqMLS<)R z35pxR1wZFVzW!+y=C){w!p4@3XTp6GaGpSg{mbDEkCH;~G}Hwh2u>Cb(P*_S6L^q^t{lF2o?>(a?iRiqB`3al2f{qX{q7=))^LaF~g4C6+ z1G2gy1D6TtVQ8+|@6RcYs7>c0{fy+le(qJ!#lVm&8usIUZ*Ew z&9spoB*v=SSV{&`7WtLv^o1Hi82G?sw&{+7`+lW4oH_FD#UCz8d*li)ST0ueIy^3F zqr4O<`|}h(#LHg2Y_efh(RR@sw#gY2o2@639%K7#-NPNfWByb2*@cyBK(idQVa z1k}b4WY}(o_@J@|q@Ek~qjPWlJEG|F(4w_`pZYVPCFyNcvRoqMdSS`GC?w4{H)r6T zn@>Cvi1C#=!jJe=H%W%1v^d}%GueoWXJ73h` z=+oNs#2%T|S3>y4PT?jRHZFHy2sWM!mq?48++o?yl97HUGpV>!9rdtK$wvH19=N0k z5+2XvlJGtHU>$RZh&P!7+B~`xYZE&wa#>Zx`xA1ff2!eHIOCm% z*x~zPTzq9phi@6b+9$)T(vBKIyo!{tW)SJmZ_LpyLCq=HXuLO__7!AAg8?ir=vB1D zPTOlJS7;M^-6e(j?WB=5+*x5}XMn@rFrG!Vg%_F{h6H1zAOP}F(RXBr`PduAPqpS6 zI-|(|`((B8OkGYMWpu!s8hs&;rWA3uwsOb@tIo!OkAYq}N)eQmGn5vns&$4j&Ce z$-HPC#|P<*Pt0q)Ac|N#NtPa0j#9EqP7c>pL88VXIW*yey9x{xoYJKCn{=F`WXC ziaZ>j09FWCHQxa<#f~fD^2T{jLUn)FX$sdV&V{8i(Kwq#t-`R{A+zFU-lyi0 zhC;G=9txpUXlX=Ukuyj1QwO2%{BSGoS3c2>@>g zymc=6=wnkfKQOZx_AwSB-nJJEA9y>Y4s0!dn3!9_y>OyQdx5+anT^A89;OKYu(C{H1uG z|99o{h#1@JpdPn&`_|lXEouZ%!I%bSHO%J|3y7k`yNOMFBP2R-+$okE~Vdg^%UyKkkFDsNwnt&8G zkS!7a%u!6d{gZWW_4F@wE}udRp+tFnItF>hmp%rQEpvMrA_{3EE8ZrCHo^LU;cB7UM@&r>1U=_53LRTvZGi%q1BB11QkM;2?s+Wc0 z7D>fhvm{PPX|FR~UH(G8|9x=q#L~Ik=Y4Q%LWYy)q|VpwR^c4K&N?vyT~mPtD>a{r z#!T--O@RSbP-GZkNVy2rV}r1?mow}Z6TcE^9HO3lv?q8j47c}QBV^#19jp$S`5&SC6Y64@dT^Ceqf#ohqmBDf7-RQlzjwagMUqg?<&%4bTP_ zvCYlSmW0^!s1>Dh1p<6YLvyWq;c(3qW8D0Z*{pKrH zc2veMLR&~{q#@cCRFuA<`i;b6Y`NUDsy~n_sN~GN*r^aiz!FdL-aAf&kjzz!8_xtG zkZY0Uwd3H6j}K<6-Q`FU5Cz;doo7zz-PL_>olJ6l*`h+Zc#bacXG`ol9v(iGj^SO1 z)7U13e`3S4hO`*2;uK!u{8MbGK5j13XSa2CWp_I{# z4It435oQ-@3A#4bMkqgO751xSk`f7jMM!n7CRrBM+o4ucUn8YU7p1VcYcAYC8{*FB z9%WI;Oj-it+2aX7-D|4%wkh~>kfcTtO&9`jWRXn7oKeP)Z3rpzeXtZxV2xFy($&B9 zk3%s?pl|Ze1>SqD4(VmAf-|P=q|+@sgC!Q&mgk#~QdHVHtiP=nKEw5}*god9Q|aw9 z?7$NzlY|G~CnhL+z>0@tfl9l5iGS6hiE@N(%hFIt@E zQEu66A?+$-G66sF7o=3oPoJTz!hwfZXE&;4i+dxcbyZGMt!W!CG)HaN-~h)>iZM>@ ze{9=ZDZmyFbynv|Y3@)#$X>3S6PwYH?2s{>7Qf+r(Tz1rRqrmSg@#uQin|WHv!s;r zddKaui9aUySjA4XxNOTHDcc~dNwv?Ya)pnL1N}PJkQVAaE%EtjNV#V zuu6u}77sa^RDoT_NkpjMILMIPm==0#2UG0fiUy1Do#q=?Rb-@Ci{9xk>ziWGo)}az z=M%DjcqaN5i$zk4iUtLJNqx0kD}m4CQ);!5az*88OCm!WA#~DqDl)N~(-v+GY+xXb z5@2pDHyD*`Q8JKRPX>k8gpX^K@~taxlDF~i4Qllp{P6Nrh{Kra2$gbik`g9ESpH=F z?meS%Vj?73Q&pR>bU>d9E~_ucq%La7ODoiCDhC`)tVbq^c5DCrgQYf;X{6yhbCs4V z`Mw|&aWBg~wahY&okaoO*mdWMG9U3xjD+EcB=gq)HGtU;EvVBY+V53g)*6=MO@Egz z93C$cI2L2ku;y*m!W3l;tR~H|bjFo3B3(gR9u;e429KKn#0o65V`LHF{s$}+kBgk^ zxl`E2i+&!OXc<-2yoK>Z;%i1JuoBon=+FpEki>&KqJ-Qk!h$rUNB5XYHo21|#1w$k zL#De)a~WVrwytr4O!RQfR>@iEG^N~OdYTvZZ^;XM%6Gp2X z&eWPk*zW#kfAAecrw5p>_YunJKdliZCzUy3_aCG#O#QTR-eWGh zFa5sxu}S)`)3V2%cylv|Vu424K?==jX;M{sOtCsf4Y;VntWB>=L@#=h@|WEEQhxGB zf-QG#Z0b;{=a{PC7f-P{ouo{1H|vePnz>UewSF z3g_9hv8jAfJ7tTw6v#-dq5t&V-JHUGe>BUka0vh0i=&vJI*+a7FH&>&AEm4m{44&@ zFW!v*M%s0d_~uzIRMxN{UMi;UY?8|PDnk|vw}y)qn6)QxJP-7IE~wHOUHfNAR6^8E zRcPCFW?2LYNY|W()5WN>9hQ?ybMPG3Cn;&CNN>-FWS({;+M^bdb@fSzRna*tCdEzb zY1_k<<0$Fi!a23pkMwB_He7|0jTEJ4jS@Qzrw1<*N6=H4})8%Tm%|jDsEQzU>U- z_;hlDsP=dXtEkm{`(Y-GP0d2{q*~zolV(>)Q#UV#oy4 zY)nJVu_v9GW~F+!!NB;V^IxkWb}Frh!miV=3W%NmC`i*UvA9HiJ;B$1gh@lwkA@Y! zE%)NF*48lMS4(n~G-^Bjtz&eh=r-q*OU+hd;FPKWrp|hL|M%>8<$bWqJ;lJ0F}ZJD z9l0t#k%e?!WgmV!YHyrK2=#kzR+inmruprVp!vqR;>8d@X)M@YD+|$w*1%(;Da5AL z07a)nrE8DikWK%zdWPM{qT=`QxS^{RdP>rY#GAbMhuyy(f|ZL~caTOe+D1xOk_C@l zP$y#S^wiYgmP$nis&Z;thIEGfspaVh<;lD0BBt^~?uq=`Nzw&sPo`3banCFpJ_qHd zR^y*xKdfBuq<*9ty!F2Zpua-jjfc+LUDUYrgi_U7r1z-gx?TNH-RqfG%qA*p+2r|Q zsd=eN-7gj$V)HdcxV&5f3&ba`kWkgxX%>ctxc5<} z)65qR(x%gVP9_bNNBNXii`!9TWk0CDuh2m>C~rlQ$oXJ+mJQZjr|HN&-G^&8;hXor zNG+`zz$0WCN{JS|Gcwi@tYFNb#q~)#_0xUMXsPMvSa5OH&pAOgGD`r!Gy##Odk8A& zMcNYsDOgyDD0S$>f0qR7jP^W+*E#2?eJ$t zKCD$li$f)>1FS*Y7#YuG`u9=5;PhEuHO3?D*Q}S=zs5g(Jo;WObo$$l_?CAWt4LtR zV)J7&xgTxvxCxT1&1%t8`pA`Na!^N%QsC<{nwJglI4(Z#F1R@J)qr8TYI$GG*SG&^ zGyeP&MgSnaCeEsYh4FE%GHG!EV5R-^lq^IV>bVJ!YkR6DsRiA3$c(NN(?I6L@Oe9V zWz)2s7aeda zlir%4|D~U-+ zY-X@k78I!&;Z|sj300wTm$~#4GVI6jEFlTai{PIim3g65QgNiP(7)pmpe@y~#VAIT zKE+^zB20`<=m8$305dNYkok24@e?}Y+S0`s%>~*XZnx^i_|`V7s{wD@SwK<+CD4$C zmJtTT@bOqew8sp!1puR(LN;bv`BbzSh1V}Pz~mqU^eGpzI0KMs{$nRrLc zE^>mP!C|e9YDWTqdH7ZkrY-fOTk%DoSgVZR9esh%j|%TChqZ z>mWD;;ay7_VA9Af>cZUH$*n{VKM`t7tS7zp3Fs6>Yz#d}8`G$!9v`fzTJBkxkXXV! z^}qY<(D22GneV62DdkmyQG{}T^;>T}0Wmt%DLCQRz>m)%;Z(^#Z1z|+%dIDU9f7ry zQsN&&H&?tcG}IO1pTFN>Pq;l^1DktJzyH&*su^SEOQ?iUm8h!3$15v(3JkI1xi%3I z5VFJQ3))B2W6Yk*o(!#b1^Hg^^Xj}57yHZyd4W-m{YOd?K}_NeIq&fzCB~O$}#8+ zd|w?K`>`c-hx69|4vNt`RHD`n(oZEQ@b{dur+%S6W>!IJ-?k!*S<~gKfGTGY6Y=km zWSVwi?ofg30fwTngk)}6d=SV6(MO)UWQvhd9?hdm+AtBjG^rmUCb0wOG@R@ob?U|C z4K`D1GYrNkZ9ZSNx!;ieJkf)efg{tLR!y=uO-w>o@n?@&?N&|J*-O=GQHSYM+M%JQ zTu0q#!Le}L#`P!*VL8*-13F+qmDqhdx|Xo39r`jA)mo2!rXr4yRADc?$em4eQs4>@l_#~1wB z-gxplkkE?-MQyh59M3Fz@pI0+$?w-_ui3`c$?fq|9PT>=)Jkq9F~95JmM-a0(l;RV zlNq!7p4LXAXq190fP|~O^VHL3VOdBMQ*wMbRd(zD8bHtOFBb=$-@R<$m*|H6#hqQ_ zgth$@&3#idsLl92_)cG ztt=o7|A{#;WP#C+klY;{VAYn80yKOxW6)`uSXN<&gaYumxO(F0C!c0DnRAEaG!O-g zI(kirc!uT+q)apxw#kXcb1BT$ke(}VomOq?vp?ssj35(Bn;hhNJDFsI7=P9g zXT*K9uFjX`ujtOpy^%njHAn6@x0S0z=|Z-d5H&Bj;jsX%`xzXxT)WbJck(qAT%*5v+=F5F#qi1UOwEI#mmw+!p&CB&cWVwh5? z+5K1`ph4*dj&Bb0%t?fOXaQ#fB*cXOK5_f&SR`-MN4*Ah;@})Y8Ru` zI(RSOvp15KNHZNowbA>7){IQUk7Un$+))j9hbV*nc*DqF_|qDm?}ThSekj48?$N^5 z@S!g+A|9z2M11Tt%U}?TE;)TuQ$@tmyg~9#K1(*b^}`qGV6Xu~0e#Iv!cr=y|xC1SgHpm}s^M_AKu~`&0_cQhJ40ffn!8lVZlM z6x=E8&b6&2pA6aJWsW9@IJ+u$U_R;7-^3T=zoY-Uj0pmwlAy7~Mg%_6_4k97iJq57 zI(xLWX{W`51r;l)!LC^8fEeId`9I}i#y(O+joc@EVE`WmI3JN_xi^n&sBupD#h$ys zcmvkox|}saN#VG>SK^F#F(iUh=P4}efKYJv)yCP9(7ng6=C|xq&w6~%aUAvg!F#{A zJN^QIHwR;cH-B%yK-5_RI{zB~tVHNLqCMgX9xh;EdIjv_Oqs~Kf9N1O-+ zg*+z(hm;P8l0uC_Nu@8=P7c)vNB1X(_F4e3PuK!;D1?}vc5H_3nE+|>H}?!zaO-|7g`vPjzCj#_;gh3b|ot3*%3JnEaDYHsPQ z{o=oZ>0Gk=?D4zSmt^_p(8|w$wcvW|Hg5f&0w{YE4bQ?*Vxoh{>iZ112QYn;5 z6|LXOYP_mihRDe!4;LzVYOq-BhXE-z?0EGba&n>CASri4?BQJHJOOt_EHN76d_!V0iMcB)-qU?m{sCLFU9IC5ZCD7$!#L1qE@G3 z7{$V*5pnCk21Uj9p4&DJfVTtX9BavPpwODiAWta@mqjZ{wNhH_X9+`R`X%Iq+3789 zVOH+mKE^h#s*2`dmKRkdbXtkrT>C94@ae<7aK1ddzi)I6Dk^3tqO{`5bOKWcW9X^9a zEha8%HpAyR&a*CMrY`;lRe42@siRGP_Z_R%yV-u(JG58{2+ zPHHjU$piSPqy7>9@8P;Z-25(^TJ2^SIwpGuYV3V`+LBEuhL1ElL_4mLq9F93V z{Wc59zTrLY>c5I8R7mtq@$qg|RWlU9bqj`zi$_UGIV1tM-A_hz12NeR|F`-NBETvM a(-sr@$^rn=sGHg0f28RD*Z*%f@ZSLRyV@oI literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..085ed9522f04776bc6b27eeb35fa49ebfc6cf340 GIT binary patch literal 211 zcmeAS@N?(olHy`uVBq!ia0vp^IzTMI#0(;XN?m{yXMj(LtAm3Bkm=;`e;(74QXq@5 zB*-tA!Qt7BG$5zK)5S5QB2Kq&Bky4Yfg>*t^=4JGpS-+eLs`eR4+5V5kM38@JoRJ( zgHD7@hjQtq*3@NzT1Q_ruV8UW@twUSQ6wH%;50sMjDW#<>}%W!XcZ?lbUeG#c7gV&zgTdXSVjNxz}?^UGnoD$<^Hs g9498&u})!P*#4H|kN)4Q(m-7dp00i_>zopr0QRyhEdT%j literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..070ab0036b407eccf387e2818a09136a85c46216 GIT binary patch literal 211 zcmeAS@N?(olHy`uVBq!ia0vp^IzTMI#0(;XN?m{yXMj(LtAm3Bkh#@Rf$yN(B_NBj zB*-tA!Qt7BG$5zK)5S5QB2Kq&Bky4Yfg>*t^=4JGpS-+eLs`eR4+5V5kM38@JoRJ( zgHD7@hjQtq*3@NzT1Q_ruV8UWbQA(cDD~|_x1~thYja@X&XrlhLm64IBdGvx} zbw3>`XFPY{aE9^GpG-R<@9VStJH7S4!V`^6Q?nfV3m!CjZ=O_q&dGmSYW<#Q8Kbg! S8)pOE&EVfMIAfQG2d4C=S{YaACK@igw8%>S@=d?wtiy{XSNQmy%6Re^VwbwhWJ%6B6W5D!}NeoWbkzLb6Mw<&;$U3wPslW literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_03_p2.png b/docs/sprite_library/sprite_03_p2.png new file mode 100644 index 0000000000000000000000000000000000000000..e0738cdaa009f983460b0c537a5dc5908e96c67e GIT binary patch literal 237 zcmeAS@N?(olHy`uVBq!ia0vp^B0wz9#0(^7DX_)@DV_kI5LX8W2Ox8+p~Am2M>)hV zfjEpML4Lsu4$p3+0XhAiE{-7_vaAOTvknDFFg$qQ_=;7_sXgtz=0D{fd#?IsD9zfr z{e(b<`E+$5ukGvNoIfyZeQ>X7GVA68D>#?PaOR%z%(XH8+Zn#bzGLa_gnzY99v1e8{^NPIkgx5vd%(2otf_lqFUSqcCE literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_04.png b/docs/sprite_library/sprite_04.png new file mode 100644 index 0000000000000000000000000000000000000000..2d7f29cfbf48f41758809b7aad59daab3f44bbf7 GIT binary patch literal 243 zcmeAS@N?(olHy`uVBq!ia0vp^0zj<9!VDyr$3xpp3p+x&uY>zoy9QZ3bQacNvh z=%2dP-}vp_&ohpED`9Zyh`kfF*Ma>>rKQ8H`HkNy{8d~+@8um{yXAt-!y@Zd{7=L_ pyVx)hV zfjEpML4Lsu4$p3+0Xe;%E{-7_(a8x3Jc$Vj3DHa{P6yZMCp1lMG~D7cr}6FeBVKGT z>#wI9dHi29=TN}+nl!G%=P#@bmiYJnbj;}q|LbqYv2EPz?JODLSS1MC3%4Zn~373O90u#5fs&I!KvXWjAp~bCnavHyqQRAZ?1wH3$A_9y- dGE%1)7&6SYe;_Bew0AxBj{NHM*@cqWp ze`k*Fi}@A<6k;q1@(X5gcy=QV$eHEo;uum9=R2X1^N0Zl!)vxIJ7$eo2PZL&{SKTr z>!+Gu{4_Oa-So*0{iY5TN2W}3`BMJbcT4puj?&p_0cS(!KVB!6!nZH@c!7rB<&K`5 zi*FW3?7seL&e{_759P{>c9^|qwSB56%K50- zADkYzKYb>i_+9&?59{6ouIkHDYU}uQB%NQ@X$6Rf%{6~319T;Wr>mdKI;Vst0DWg; AyZ`_I literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_05_p2.png b/docs/sprite_library/sprite_05_p2.png new file mode 100644 index 0000000000000000000000000000000000000000..4176f5850e092afa42b96004f590b581e18c6280 GIT binary patch literal 252 zcmeAS@N?(olHy`uVBq!ia0vp^LO`s<#0(_$ZzW~|DV_kI5LX8W2Ox8+p~Am2M>)hV zfjEpML4Lsu4$p3+0Xg$LT^vI=WLXa=`Y}6-FkINasC!M;?4E~O``KbI^1c)-(-ONK z&C@h#k+}*pCs*?;(Nz}%*x4Bv59%netHmA=FgVh6^XsR!g?e4jer(mA_4u!1OwIdm ziB;Qgw!D1*^Wl=ezi-Ynx&1xQ^*ZaNXoagyKPz2+M@+PvKjB!<(cJz$>rM2-m3?!$ y-yXN|P<>n!dGU3iGVA$^VxOcgY5QFJ!MHO$?7rurWV$ z?oRsZJ;wW5H_q8tbXU4s^0{`zTALfKDy{a{{Upzd->!)SI+DTD)z4*}Q$iB}-B@A} literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_06_p2.png b/docs/sprite_library/sprite_06_p2.png new file mode 100644 index 0000000000000000000000000000000000000000..1813401cb3876a1f25e7fb76f6d6cd0d6cf0adaa GIT binary patch literal 245 zcmeAS@N?(olHy`uVBq!ia0vp^0zjok;OXk;vd$@?2>?wWTiO5s literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_07.png b/docs/sprite_library/sprite_07.png new file mode 100644 index 0000000000000000000000000000000000000000..eaecc196fd40110a5ad2f6bf7931d83a34b655f4 GIT binary patch literal 89 zcmeAS@N?(olHy`uVBq!ia0vp^%plANB6FUp{{d2L0X`wFTMZTdH5SNSX?_b7;q!EH j45^4qE=Z_2@PL7VMU64BoXa2|D8u0C>gTe~DWM4fS*R7> literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_08.png b/docs/sprite_library/sprite_08.png new file mode 100644 index 0000000000000000000000000000000000000000..c87adfdb9ef1ecde8f314585b8b71e67d80389ee GIT binary patch literal 209 zcmeAS@N?(olHy`uVBq!ia0vp^0zfRn!VDzKd=|I>DWL$L5LX8W2O!hQ;r~`ch3_|( z{yTGYU(B}{pb%q8kY6x^!?PP{Ku)fwi(^PdoNo6;PG&Loa_Wteju z`@Q}W$GckFBa=JDeI%4(ge;A>mLI)lvdi81@T)ujI4CWeHa32Y&*NhL{r?vO+t0tn zvJFSBX!4ZaJf9``>iPfwrvvu?xgXcIYvp<;38&Rg(+yJg&MIwm%nIGcJmCb3)TR>6 q3o9;cb!aLyy-xst)t)z4*} HQ$iB}(=t(B literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_09_p2.png b/docs/sprite_library/sprite_09_p2.png new file mode 100644 index 0000000000000000000000000000000000000000..630ce122e996dcbe475052653113487bff722c08 GIT binary patch literal 193 zcmeAS@N?(olHy`uVBq!ia0vp^!ayv*#0(@mSFQO7q<8{+LR=jj9DvNNh6?}A9OV$d z1mZB31o;IsI6S+N2IM4rx;TbZ#EBkJ6moEoaCoSGLwd!zOFR)02&y%my4T7w7Yuc(MoVY7h~B!KS+7 zzt-AT8I6AHgo0+hm-ka#7=mxTzIr&Xe`=z^+Se8W4$S;_|;n|HeAg9dJ#WAEJ&h*SfPG>_7mV|uf^C=DDFBxiEbT06o zI{Y_$CD+$}`FjgH8m7n;>{E~O;)dRoptTuR8 zkt2GnGL7l?n+-aOlM@m}?8052XV;}x9)HXEz$I>r;9qw)maPqEW9${&1vak`wRZu! OfWgz%&t;ucLK6UC;ZW89 literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_0b_p2.png b/docs/sprite_library/sprite_0b_p2.png new file mode 100644 index 0000000000000000000000000000000000000000..7c126583b90fee3a2cf32748690c2fe48cd60361 GIT binary patch literal 204 zcmeAS@N?(olHy`uVBq!ia0vp^{6H+u#0(@?+WyrCQak}ZA+8P%4nXEsLxq25j&g`! z0&y5ig8YIR9G=}s19I{_T^vI=WLX;)@-;ayINaZxXp~`Lb@Z9K0Efg}7LR{((w?~) zZW8SI*D`fuWY4bRN#-8cmCPqds@yd+VSVJX)w?1 yVu<=5$6T|0YSYg%U%&lHTcrEFBJZX7%k2Zd%gz)#=CK)QGlQqApUXO@geCynutwAX literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_0c.png b/docs/sprite_library/sprite_0c.png new file mode 100644 index 0000000000000000000000000000000000000000..5f11fd371938203f39a5afd983e900f1ad084088 GIT binary patch literal 202 zcmeAS@N?(olHy`uVBq!ia0vp^3P8-o!VDzUlO3l6DWL$L5LX8W2O!hQ;r~`ch3_|( z{yTGYU(B}{pb%q8kY6x^!?PP{Ku(&ci(?3fZ0LbT&cg-*%npCqrpfRpOk|at%y@^_ z>&M@4%>a%k9}*6G&k@$&B;CN!l;SM&jZNW5#NtU?p0aW5-FJPJtAC}k-HE8u$tEv7 xC2cpq^7EQ}%P1*z|EjFhTb45#od}#=Z$DLnU)3jI&qAP$44$rjF6*2UngHNgMNwENJ-$~O|IeOfW@_H;n04#l|LfD%%{qEON+PP> d)Wk%DVfkX2-~U!P0_|X6@O1TaS?83{1OUNbKeYe= literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_0d.png b/docs/sprite_library/sprite_0d.png new file mode 100644 index 0000000000000000000000000000000000000000..10e30088118e225f013864fbf8d52ecb399e5df6 GIT binary patch literal 267 zcmeAS@N?(olHy`uVBq!ia0vp^l0dA)!VDxMs`BlClu&?Ah^vEx1CZ(D@PDhJ!uK0X z|D8FyFXmeeP>8W4$S;_|;n|HeAZNL!i(^PdobSYoyv~XouBX`=N*O%F4UVzKSvl|~ z{twm4{Pp12{KpTodipsoDl-?0EPBOoZJnM*V$L4+>At&Ci{rBs6}40DMwvVe*#6M$ z#ge0a(t+RiowZZ6wGc{u(Vmw5xm@w9?~}x)FRC;6gG4YI?@_;hX6Pxf@q&sqYi~^ln9pFY9@ee=l2IHwjI%=bX9l(Z1hLr2KjVYmrba4!kkYqiu(d#f!@n|9py6?p!7 zwT#7&nfa$9#hz`7`@3)RJa5gJPiyKb`xl4)JngYwa*0*L(eE6Ol38EA=3KIo{q$_U zFXf_#PQT<>c#QGUb0ue~s)Cvz$K}kX%X!XQ@?F^OFsor1>wmUFqk|6rZ)O8s&EVnmm+@z9yX{}D^ zQYME@;-U)K^OW?tujVADp4uMe^|n}`lIfQxli~b$cZNSa(XHM((J^eFA8RD=9J|&(pd~B+~EYVZqcetNAzkIQiFCq2!v^a)~ zIseP^8!!A;__BXj47(c}+qdcem$kK78W{ZJUc0`wyrFU7zxa#{jeylZHm}&i>d?Aq z>B9KN7{QfG6Ox0SuQ#1N8#9x0S@^yv-euE|?B!y(yrwdtimUmBh=6TlV=iY)8p~ye u+1nOWH7E*uwJ-X(d6`4zibDYlm>3u~>Gytqdg=$znGBw;elF{r5}E)?EMTYr literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_0f.png b/docs/sprite_library/sprite_0f.png new file mode 100644 index 0000000000000000000000000000000000000000..4b8800d8efd69d8881aef4f253dcfaf86277e483 GIT binary patch literal 257 zcmeAS@N?(olHy`uVBq!ia0vp^;y|p#!VDzcHplk@DWL$L5LX8W2O!hQ;r~`ch3_|( z{yTGYU(B}{pb%q8kY6x^!?PP{K+arG7srr_xX_6YIh_>+n$Po|>15W>Q+ULnE4!fR z&cD@H)c2HM|MN-NP;iNEgIw!H-Uo98o)z^mpV{|l?kuB%XwTAflDtP8HZ9qzDUkY7 zOEy|FD1dR(Gu2Obr{&q~T2y)1BXf@86IP8!jZvp9?>O)mR7*Bw*Kll&?7JDS^=F&a zA4|R&v-3A+iAeKa%{QvIi4bR%S6-)TRj|0?-xKz0g%XLERvq61bSQ(TtDnm{r-UW| DmH}c7 literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_0f_p2.png b/docs/sprite_library/sprite_0f_p2.png new file mode 100644 index 0000000000000000000000000000000000000000..48054bfd6b55c9c6655ba8a78805fd0387f804fd GIT binary patch literal 250 zcmeAS@N?(olHy`uVBq!ia0vp^;y|p##0(@)eqN;uq<8{+LR=jj9DvNNh6?}A9OV$d z1mZB31o;IsI6S+N2IS1~ba4#fh)zyO;7wp)T*;ZBC2}Tt%07l?uFaa)=7u{=`La4f zVCw(s?aVW(c$dwy-+f5oK!ExG)Qk+S=0BG|y#IdBV8j14z vw5^6?DreY14#AWYvzQht+;VnqWMMe9NMGZT=Z;9AI~hD({an^LB{Ts5SrA(T literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_10.png b/docs/sprite_library/sprite_10.png new file mode 100644 index 0000000000000000000000000000000000000000..08c1381495c13f2fd4162337c6b7d9425c235be6 GIT binary patch literal 268 zcmeAS@N?(olHy`uVBq!ia0vp^!a%Id!VDx^Sad)FLIFM@t_}_kK&F$!|E-1!-)}7a zcjoB6m~SyaA;yv*zhDN3XE)M-oE4rfjv*CsuICuOEJ{9r3NPoWbcW&(+NWu|HBvTQucFJ=^V)THIeN$z_(v2?N>ysHK)tr1Y*Xo+hH*CADDlGnb#Xc675U*Q&M}32RKb@UsEIm7|U~=T836uR< zulbnXnyMtNmv!^IIn)2+HUAHJG)+v+>SE<-E_*QVCdYsI`#M{$^DCQ4Iv<~X@HNoQ N44$rjF6*2UngE^+W@G>W literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_10_p2.png b/docs/sprite_library/sprite_10_p2.png new file mode 100644 index 0000000000000000000000000000000000000000..427c4c668517aacc06b39a065727e6c15ca46fdc GIT binary patch literal 259 zcmeAS@N?(olHy`uVBq!ia0vp^!a%Id#0(_4U2c8?Qak}ZA+8P%4nXEsLxq25j&g`! z0&y5ig8YIR9G=}s19Fynx;Tb#$g&>T=y@bSg5kk?{WBbba!t%Tj_f{m>rs=kM^FTp z*(|}N#8a!AK8UPwu{kKaWz*(M#Y`IodK+Y&Yy!TYb=mzx&Z4IBbkh5;Tb66ij+-Of zyLU#odgPhu-y{Fj{uP>U?|-DK?%3nXV};!rdX!6AH8kQCO9Uqn~+!AaYIg%Z?qpq{a4_5%G|3#EDgavt2o3MemzLI92OODdu5fs z(YfG>cLL5mh*CaM)V(fli30oQPM>56J$t7AmlpNv$GI|8F!G!dpX&R~dkWCC44$rj JF6*2UngES)hV zfjEpML4Lsu4$p3+0XefgT^vI=WLXaw>ahe$Fg$p#Z_yZf;@$z(d-oE!^{wWfxSizP z*=Bi2#Cc^^^qvksmQL${ms3vwZdftuFmpsE( tb$e=D$fO<384DVCxFUErWKH;NUp3ocy>R{YwLn)gc)I$ztaD0e0stdTT_gYi literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_12.png b/docs/sprite_library/sprite_12.png new file mode 100644 index 0000000000000000000000000000000000000000..29f748f6adad6739b2c8669b1babd9fe300d3b0f GIT binary patch literal 253 zcmeAS@N?(olHy`uVBq!ia0vp^0zji{y`AqQ!ZN>EXR9}@mSVcmTl#`~ z1^2sy^O&!kRSQX;?+4+e)T}3zDuZ>|varZZl-+FHySx$I056_4=!YbG#1B zt2`GL9W^a6^Z5GM^KE;5o{Ojo%G_vsy}7>NnD~a;clWJbP0l+XkKA;Vz# literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_12_p2.png b/docs/sprite_library/sprite_12_p2.png new file mode 100644 index 0000000000000000000000000000000000000000..c926483b8f346848de626e8a38ff84cdcea26664 GIT binary patch literal 246 zcmeAS@N?(olHy`uVBq!ia0vp^0zjCnI zJ9Bhj%(obz5MxP@UoeBivm0qZ&Kge_$B>FR-}4uFofLW85^pT1*Ji$8(a^HtKrge0 z$M<+wk*-f)SovDo&O3cz4Jl%u_^4;=Sp?X@-#x<7VJ?noaP2I$nlzNuGvUWE2Dn+|6|25T3 z(S38Kr=PLf`)THdGCuX48lUoi@Ap>mwDU_ayAaOt#_ T%=`0z?q=|G^>bP0l+XkKtDNQak}ZA+8P%4nXEsLxq25j&g`! z0&y5ig8YIR9G=}s19Db*x;Tb#$g&<#^mcY+V0iGp>ASGn9qxZqH_s6Nwn3Nc`=(HX zne$T*Ij(G zP5a34vzCwEzmZL>|JbmhUeDmK_1TQqH>F;#)S2+Art4yw<@qD2d#e;{@3nbfx^VDe zX=7fP;5&b|7OCnZ{ef<;tSzk81&FM$Ouh5;#^TuxTbS5|f3V-zHtWh)`d$unHG`+C KpUXO@geCxLfob9Z literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_14.png b/docs/sprite_library/sprite_14.png new file mode 100644 index 0000000000000000000000000000000000000000..3cc52feef7bb506f16572756d6b4009341774453 GIT binary patch literal 268 zcmeAS@N?(olHy`uVBq!ia0vp^qCl+3!VDyjT>2{qq=W)|LR=jj9DqzGhyPm*6~5nC z`tQuqeKFr+fI^HVL4Lsu4$p3+0XZu?T^vIy;&RVj^hBMNqy5^|6{cYivT6-w=o50QuO=3^Dk{G-BIsWn=x$mD*X1Q>te6-2$vr=d4 zHape)-RJaY_iv{e>*EEUZD+cBIMV4}>>A6Mp5w~TfBxLE*yrgJyP4bkUw(^yyDfJ| zAirKTPam7k(XOQd6I(<%gaaCQL>9<9*f-Ak%gp=MIBQ1p1rDHV89ZJ6T-G@yGywpl Cfnj?9 literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_15.png b/docs/sprite_library/sprite_15.png new file mode 100644 index 0000000000000000000000000000000000000000..c25f35b00e65d07922def2f0ceaad0445270f0b6 GIT binary patch literal 266 zcmeAS@N?(olHy`uVBq!ia0vp^B0#Ln!VDzSZkTHWDWL$L5LX8W2O!hQ;r~`ch3_|( z{yTGYU(B}{pb%q8kY6x^!?PP{K+ZBx7srr_IM?$RIS(7~xCVCXJxHI(sAJ{O`jJ61 z(t+jeey_Qw{JxY{#2ENJ)>v-AlpS?#TiUxBPOrrl<@Isg;61QpewJlL)or;=u}jj{ zNxe~D?7nfQnpa>`{|6#mB}$LtM^yhPbgsTboo%A%JSe5|5Pz4hLhWOo&vg< N!PC{xWt~$(699-UXpsN_ literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_15_p2.png b/docs/sprite_library/sprite_15_p2.png new file mode 100644 index 0000000000000000000000000000000000000000..6f8b6aead42a33532dda13cad4a4c75c44c3ff69 GIT binary patch literal 257 zcmeAS@N?(olHy`uVBq!ia0vp^B0#Ln#0(@~&N$Q!q<8{+LR=jj9DvNNh6?}A9OV$d z1mZB31o;IsI6S+N2IMUEba4!+h?70Lk*g^|hUJ6tg$1^bwN3XP{pa9)xnK+1%?p8W4$S;_|;n|HeAZL!Ji(^PdTW-Uobgyej4M(Wh?G%G5H%FEMYWj*{qvB-Zq^U&+hmK-#y(f)xn=$ zx_DbqQs^4>`R1F;tA6IDzc`;eUH7}Y6~mXevGYHQ7TH}EaFmq%bP0l+XkK D&8lT$ literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_16_p2.png b/docs/sprite_library/sprite_16_p2.png new file mode 100644 index 0000000000000000000000000000000000000000..78f1be15ceca37430218e2664e92625ba3f511ac GIT binary patch literal 258 zcmeAS@N?(olHy`uVBq!ia0vp^d_b(r#0(@K%Wb*^q<8{+LR=jj9DvNNh6?}A9OV$d z1mZB31o;IsI6S+N2IMU9ba4!+h%-H_$aly=gylly#iwj4+&7I2TC2`*Ihd?u3E~Nl zjGn|6?CfjWRM2YGbU;wZMYpNtK+l0aXZ~CN8W4$S;_|;n|HeAZLoFi(^PdTx{P(&cg;gE>C%jHrY0`uxA*^DJXJt zEdCq5^3@jkvwu6PZTJKm-`wBMuvhc}n`h*;x4->=Hl;4v5PbO6RRPZ4>ule% zN@wveQc7HY`g@mLXJFz~p@bcb3U)hR?sV{QJF;l)$-=KT&$W-oTwW%1;JC{-1{v84 uMz!w`Ycy~uw$KdJe=d#Wzp$P!4Wnw1) literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_17_p2.png b/docs/sprite_library/sprite_17_p2.png new file mode 100644 index 0000000000000000000000000000000000000000..503ad01bac7ac18d3b439ae75df11bf42c608d5c GIT binary patch literal 227 zcmeAS@N?(olHy`uVBq!ia0vp^3P3Et#0(_4@}`~wQak}ZA+8P%4nXEsLxq25j&g`! z0&y5ig8YIR9G=}s19DnDT^vI=qLUL+8yM8w*fq4@Fom@I=VE5gW(df79mvKOxWMGH z^nx?*nR%3WdZHyI4zF)>+q7XHyH?_?gU8P#9sZpe_5QrH&Y?AmQ9N-;ygrKIj%o6% z(tf=?Fn2@P|5`?Gg~Qct7sB-=XZ%#T_Tg0ShMc5d|9_P?9*`?s@KKRxO?{&u1H&Cn W<*L61kM;o_#Ng@b=d#Wzp$P!@G*JWq literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_18.png b/docs/sprite_library/sprite_18.png new file mode 100644 index 0000000000000000000000000000000000000000..53677e580908cf59086b48fbf73ceff69e5ada63 GIT binary patch literal 137 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~p#0(^B{8TRjDV_kI5LX8W2Ox8+p~Am2M>)hV zfjEpML4Lsu4$p3+0Xf>9E{-7;amfi6HYOw_q}vrVEP#c4%tDnm{r-UW|!Kx+( literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_19.png b/docs/sprite_library/sprite_19.png new file mode 100644 index 0000000000000000000000000000000000000000..ceef92fcb8305f7f6e4f76926a5f22ab1e602e71 GIT binary patch literal 168 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~q#0(@S$S&Ulq<8{+LR=jj9DvNNh6?}A9OV$d z1mZB31o;IsI6S+N2IP2ox;Tb#$R;NwB=95xiD)Jj7e}3q`VL8w6B7RHX4Uwo&){NW zP;lm!eP?52-dC|U-pbAYj5ZkQ%{{X~Au-|Lfjcjr@Lc)W$jFfYPiUHF#^Xq!sSKX3 KelF{r5}E*4zBNVw literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_1a.png b/docs/sprite_library/sprite_1a.png new file mode 100644 index 0000000000000000000000000000000000000000..00ff02409f0538a2d763bfb21bc4e74ea403e4c1 GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!93?!50ihlx9JOMr-t_}_kK;~9Mg@0#`a)@67 zaTrU2{DK)Ap4~_TazZ>^978x{lM@mWI9QVoPE}x=aP(WTKC|<;`+IqMe%Bu}+VC&N zs@1V5{)Q0S%0Cxad2%){_f42E>svYN;WwS{og|jnG)Rf4GnCFtvN&)+A?fe|28PZ) WapnbAnw-p literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_1b.png b/docs/sprite_library/sprite_1b.png new file mode 100644 index 0000000000000000000000000000000000000000..b9a23f658cb232ab69e62e8d17f3c558d71706a2 GIT binary patch literal 213 zcmeAS@N?(olHy`uVBq!ia0vp^f*%s4xNN$s?ZK#EjHiVo|^?bTF!a7RScc_V@O`rW6089e#bU zPfW;|#LJtMU{PGZ(~!Bj=@&O28=Kt80A`>v-&!6XhWi={%y(2PwgR2N;OXk;vd$@? F2>>XiL#+S+ literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_1c.png b/docs/sprite_library/sprite_1c.png new file mode 100644 index 0000000000000000000000000000000000000000..8b992e4c7f8fbe57b829ae370787092592a2de3f GIT binary patch literal 206 zcmeAS@N?(olHy`uVBq!ia0vp^!ayv<#0(^lB=PU$3|G9a1e5+s2 zm5?~M=d?*$Lcp2s2MGxe5(NzyqBgGVZ)9Y+uvSr=@dTq5&~64#S3j3^P6)hV zfjEpML4Lsu4$p3+0Xd1DE{-7_(a8x39H|V<$EFI*5kIpi(|N$ z^S?a5@xpI~FS}R8u)DFbeY^gDU0a)_fyv*scf)_bV>mqX|Nqm@&ax6BU&>sU>Lw&S k5bV|c%Vm@qEbn2>AoN-O?Ho4&W1wXWp00i_>zopr04w7{EdT%j literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_1e.png b/docs/sprite_library/sprite_1e.png new file mode 100644 index 0000000000000000000000000000000000000000..0050f1829e99b057138a78e0ab00f3c3a47d131b GIT binary patch literal 89 zcmeAS@N?(olHy`uVBq!ia0vp^%plANB6FUp{{d2L0X`wFTMZTd|NDPxn-E9|pQnps iNJU(7K|;lW2Mi1>YK)2HTn70-83s>RKbLh*2~7ZXdlqc~ literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_1f.png b/docs/sprite_library/sprite_1f.png new file mode 100644 index 0000000000000000000000000000000000000000..fbf7dd13f0191d8f7a9c7941cc1e53bff20cfd2b GIT binary patch literal 115 zcmeAS@N?(olHy`uVBq!ia0vp^tRT$93?x4=o+|@VJOMr-t_}_kTMZTdH5UB;_g~_w zI#8H_u_VYZn8D%MjWi%f)YHW=q#`a^AT>dwG=ZZy;Y10;CPsl|Ci(kPPvbz!Jzf1= J);T3K0RSan9>M?s literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_20.png b/docs/sprite_library/sprite_20.png new file mode 100644 index 0000000000000000000000000000000000000000..62a421fcb6bb62e2a5092d82f2e728fa9ddd3c7f GIT binary patch literal 119 zcmeAS@N?(olHy`uVBq!ia0vp^Y#_|U3?%0+V!sWfcmjMvTpb)7wi+t@Yb^Nx@4v)V zb)YZ*?YcQW2M&z?&ezo7lmh+R>lF!MezYZKJA#BZJ98mSq>O RwN-$$dAj=N Vw*4O_l&OJqdb;|#taD0e0szv%C0zgj literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_23.png b/docs/sprite_library/sprite_23.png new file mode 100644 index 0000000000000000000000000000000000000000..37bfe9d72969fc217c582e1c6f8b218e752ef432 GIT binary patch literal 143 zcmeAS@N?(olHy`uVBq!ia0vp^Y(Ol)#0(@obf3inDV_kI5LX8WhpmPR{~8Pa|NAd- zRUIhIz*rLG7tG-B>_!@pW9aGP7{VdTdSD@MgMkRkg>=n@E1Nz@!ut-HiT#qWolHVm0lmutteE}Dna8f28GtDnm{r-UW|c}6PR literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_24.png b/docs/sprite_library/sprite_24.png new file mode 100644 index 0000000000000000000000000000000000000000..98a6573b8ed835f08ce0ec7dd17f358eb12d1160 GIT binary patch literal 129 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~h#0(_23wWGF{C0cIiY~T$x$JpV%xi$R&9S`f9{sJ6LU94l0}9^ YhM~}d{c8~Cl4~H%p00i_>zopr0CO!SZvX%Q literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_25.png b/docs/sprite_library/sprite_25.png new file mode 100644 index 0000000000000000000000000000000000000000..f26bb1f8508a3670d1280b9d5b56418f488e0826 GIT binary patch literal 139 zcmeAS@N?(olHy`uVBq!ia0vp^93VCmGmw0_=J;wL#S`EY;_Bewu+>oEUt_`lfBz+} zssn`?7)yfuf*Bm1-ADs+bUj@hLn`7}4;YFv2naA-sBdPvc6vecLE+;%ZH}J0HcTR` jJf?j*quhS%o!(t`J2jSic`NbbAY(jT{an^LB{Ts5DhMgn literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_26.png b/docs/sprite_library/sprite_26.png new file mode 100644 index 0000000000000000000000000000000000000000..15b7b3a381bce391e539acedf70b5bdafe9aadab GIT binary patch literal 218 zcmeAS@N?(olHy`uVBq!ia0vp^3P8-q!VDz$d}3|{Qv3lvA+8P%4nXEsLxq25j{g7m zf7#;1%RoM3NswPKgTu2MX+Tb;r;B3HA%f46aV`+%1 zOqp}f>-4j8&zUTFcU<>yT(W^ZMTU{jU*fopU(3&l><8a|aJO@;i8g$6!&m0b``z(B XYWZJycWylbbPI#0tDnm{r-UW|GSyk( literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_28.png b/docs/sprite_library/sprite_28.png new file mode 100644 index 0000000000000000000000000000000000000000..796441585989b847208a726203d957e7de50d100 GIT binary patch literal 224 zcmeAS@N?(olHy`uVBq!ia0vp^0zfRo!VDy5$t!6BDgFST5LX8W2Ox8+p~Am2NB{r( zzie^hWgwriB*-tA!Qt7BG$5zm)5S4_LzeXbWA70K0oDuVe9xHKUT7w)UcmX!G}eRp zYfi$SzgE3c)|M+K|2?zp+>QF*3d#2S7tBkyQ9S8!?_1hD+xy@8=KPd2M9Vz$iCW`SmDr7wysl04zxzJ!YpLo^t=_0tW?3t*3hb0G*w2vpK>m*e V|Dl;B&p}RR@O1TaS?83{1ONjSS787E literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_29.png b/docs/sprite_library/sprite_29.png new file mode 100644 index 0000000000000000000000000000000000000000..ce2626f71822291d11e7df9b76119a16f6b9edb5 GIT binary patch literal 238 zcmeAS@N?(olHy`uVBq!ia0vp^0zj<5!VDx|E`RzTNC^e_gt$65IBYdk_}5qf6aX>K z9Nn7zbRUqzToU9L45WXR9k~ajIy_w*Ln`8Y&mLUsY{=nqapHyh|Am5>E;+DH;L7=a z-)qKmolP4`)^IaTJDtd);_{pM`R;}_G3u>HCm;0utp2xqM$3#Fwg&TrrGw^qGI?@c z-0hW6^4qqoyY!;|F7J+CM|BIQmmOcn?)os*SNNalgwJtJyaKKAR~lFx4zkuIw&*_W g_u%SYknn|nin?l4-ODA%fo@{(boFyt=akR{04~Q|umAu6 literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_2a.png b/docs/sprite_library/sprite_2a.png new file mode 100644 index 0000000000000000000000000000000000000000..e6fdb5e4d8d59e753410a4603667b4b644dbf4c8 GIT binary patch literal 245 zcmeAS@N?(olHy`uVBq!ia0vp^azHG~!VDxA-*g5Ehz9tCxH>pE0GUn>|F;?{e7~{u zUt_`lfB!YNCCmqkGL{7S1v5B2yO9Ru^m)2ChE&Am_Be7e8S=R9pK^1%(mPRpg&L#k zy-p|A96$QypL@Y1tEJrXPh0)#x~!NI?ycFprXj3s4U^e=iw{j1vW`Z52||XKWG;zJ z4mvX>XYu!XW`W+Rt}i(=N|fE?eoZ`Tf90Ev`r{yTqaN9b{yVh}&zl_cPCr%R+{;+G qojadKTRwlbzxvJeyMY=%KZwiDv3dA@l8*q;WelFKelF{r5}E+Cv|wuh literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_2b.png b/docs/sprite_library/sprite_2b.png new file mode 100644 index 0000000000000000000000000000000000000000..ed5c54363312fb7ff0c430c06c8bad105b8ada3a GIT binary patch literal 245 zcmeAS@N?(olHy`uVBq!ia0vp^azHG~!VDxA-*g5Ehz9tCxH>pE0GUn>|F;?{e7~{u zUt_`lfB!YNCCmqkGL{7S1v5B2yO9Ru^m)2ChE&Am_Be7KHsE2|KI6{YpsM`1fQTv8 z3+)9?S=t`?U+<*6)6lNOKv`^Uj}=qG&9$5J8p6ufFm21R_}G*o>uA)MAY^z+=90+d zpfgi)7Jsj27U-So`jRuFMA=R5*TkdtSH9V(KaMIl>S3Pfzf=40yvZ^5`BNp%rRo*u p<^2rJ+xhdo-0{?M5ih&Hto$}Mb1&VMd)hV zfjEpML4Lsu4$p3+0XdbPE{-7_(a8x6O$-7DcQCkY%1z+l5lQ4p(D)xG{i9KF!@u|U znb_JcU8qrT;T}Y+Byram-)?=Ne9hNq?C-rZ9DTG@d==sDMqK~^ literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_2d.png b/docs/sprite_library/sprite_2d.png new file mode 100644 index 0000000000000000000000000000000000000000..f53437a204c93c748b283110fa9e0a6e9fb2d714 GIT binary patch literal 216 zcmeAS@N?(olHy`uVBq!ia0vp^LO?9f#0(^-+m%WIDV_kI5LX8W2Ox8+p~Am2M>)hV zfjEpML4Lsu4$p3+0XbEkE{-7_(XK}ng%|@R7#_Um_YUnziFqpfkoA1emS~nq4khj3 zUwB2@3*;wlx+t){#(=%Or6TNzvh&Ff@Ai{h7u^ie*(aK7DRlV5LdiP$u-lU|^>fAU ze_dy;`)#k~?|Ys$R>mLS39Rqgao%93O>~V*(m0rZpE`444L46uV5IkSCf*gNqIQX`2sAM^VM*~y f(_j`7l>)m_nEpx3{2hx|&*neLab2#Z~wx1#Fu8%@5;1rxU}JL_nm iy1=Ni;o*aJ7JV%qNvVz5E2MxnF?hQAxvX)cMZ-z?CZdy?G9ZTo1DD zJj621pz{=i(^FG^kF2=JA6nuEZkaqeRyun%-?Dqh7beeHTDttjl9Lm2C)s;S$tUwJ j-X^P|!mjY~G>=>ikL2C8r`gznRxxU~(i!T{XXX0JqbvB0kipH#2l1xUb irjr>vxp-3(7#I>lCC~qUvwZ{5Dh5wiKbLh*2~7a$pgmFm literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_35.png b/docs/sprite_library/sprite_35.png new file mode 100644 index 0000000000000000000000000000000000000000..92cb2647d1f6a1563c9462134df86fc8c9ba550f GIT binary patch literal 324 zcmV-K0lWT*P)0p(oVYNSj$F7kwBP!FNc}f(}B9y+v@dvy)qqlzM}L5bKEmDh~oo;W}PmO7E=~ zWVWCUVz6=LPxgXLvA#*>5cXijw@3NM{sZR7OZo9IzT%X6wmhF*BiwVp+Ah8Lb7t^k zQCeWlDM9gecg|;u`c30e2sJ9YwqIvWkS(37a)WxA@A#e4%Vn1>riWUkAupgqVX%!fS9uO)K1XWVia$tBUI3i6QG6|hk)jcCjkF9 Wo>iiuoLgl800005XLa?NUu^bxs_l)L@ACQl3z6-sy8=GE+7PrM?#qIjZC{hT4(>Hi6L}MS zxu0qFk6WA-Uh`SsXazC7*(|o8QiLIjiQy^p1BUuZp3eT8_s3O&-0tb>=d#Wzp$Pz4 CtzY5* literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_37.png b/docs/sprite_library/sprite_37.png new file mode 100644 index 0000000000000000000000000000000000000000..0a4d99607bcdde11d369d1e39becffd740e0d41f GIT binary patch literal 184 zcmeAS@N?(olHy`uVBq!ia0vp^{6H+k#0(^Ve#n^vq<8{+LR=jj9JU%N{A(=u|L?!V zRdt{+17k^$UoeBivm0qZPL!vMV+e?B7RO)z*J$zt?Y^g*C{X?NAE)z|{a?c+HvNC*&ma`Y$P;PbxNA`u183j@ dJ+`m~iVP0Rr3!DJGjssi>FMg{vd$@?2>@*sL6QIf literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_38.png b/docs/sprite_library/sprite_38.png new file mode 100644 index 0000000000000000000000000000000000000000..9909b365e2e4c1a7c125c0f069e652b3aa8ae67d GIT binary patch literal 208 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaP3?%1DUd;wlJOMr-t_}_kTMZTdH5UB;_g~_w zI#8H_u_VYZn8D%MjWi&q$kW9!ghMttAwhsQk%8&hhLwB{UL~0#9kXX{Hx||i+npP? zmYMnWnyc%lu^Mdnd#f)0FALkp()ac6cO6pL_WS?;@?eQe*Z=_ z-1`6jzdZ}9V2f5`Rzp_16q~73mf)4B8OjU%n5+dD3i!M>E-aP526Bt1tDnm{r-UW| D!fZ{_ literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_39.png b/docs/sprite_library/sprite_39.png new file mode 100644 index 0000000000000000000000000000000000000000..06b460959582323df4d33b37640189b4d0c6e175 GIT binary patch literal 228 zcmeAS@N?(olHy`uVBq!ia0vp^G9b*v3?%uJ&DntzPk>K|tAm5XRzrn;MB(f114;Id6X1;C5$TqE3SmH(;}FUR6eoT=@TPSq^E15})(_vc{bYWm Z&+t3V(tow#b{mlUJYD@<);T3K0RRE{R>J@Q literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_3a.png b/docs/sprite_library/sprite_3a.png new file mode 100644 index 0000000000000000000000000000000000000000..a99af7d70bfd6744237420f281f527b44b6ff69a GIT binary patch literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnL3?x0byx0z;*aCb)Tpb)7wi+sg*R2QxiZGT0 z`2{mLJiCzwIk3t5r)K&j zTw*BDa1fdVGTDR4LWIFb>cR}I)5n#Ws~5!EYqqdGEf9Zx7%0o&>FVdQ&MBb@0O$!T A!~g&Q literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_3b.png b/docs/sprite_library/sprite_3b.png new file mode 100644 index 0000000000000000000000000000000000000000..7c12653320eb61b733ccdbfde688a0b165457a6f GIT binary patch literal 234 zcmeAS@N?(olHy`uVBq!ia0vp^0zj<5!VDx|E`RzTNbv{wgt$65H~^Vj4Hf>KIr{(K z|7D94F9Z3EB|(0{3=Yq3qyagdo-U3d6>+X-Zwnq(;9v=O)4(cyAS7YU0msAMQ+1;qKX^dx%xbR$WIVyUy>FAEY^uuh72O5@cdSGmmIAhEakt5vO~)apf@sj@H0da|gTIjB&F#qtrQ4{?Gik zLh#_>soGAHWooMq9=mQ7^7@B##o0U23~p`_`}yp=W6UIM4_uZvW9XPxY$;Sw|DXA5 zrx^Poofoz%6C8uf7z*?g56#kEnA3f3TJK(Vr)icR=bQIm<*HinebxPaQyuQg&y*GD UIl>Tj0_YS5Pgg&ebxsLQ00=))y#N3J literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_3d.png b/docs/sprite_library/sprite_3d.png new file mode 100644 index 0000000000000000000000000000000000000000..85af71233ae9333163983ef849a97ecd4f44d50a GIT binary patch literal 226 zcmeAS@N?(olHy`uVBq!ia0vp^0zj<5!VDx|E`RzTNbv{wgt$65H~^Vj4Hf>KIr{(K z|7D94F9Z3EB|(0{3=Yq3qyagNo-U3d6>+8KPV+h|^0-`F$lNc_dn4q*4kndf>oxx@ z(pj{@swq3Ap{cg(cH_3`Z%!{d*3-Hm?NnR(F_Bnxo|MhoK3s4%?r^_yRdT_EW9}2? zFcvFK&SIRfWOl-_(+B$At1bPvKv??CC-JXl_cb@&e-V5&uF~+=-(9C>N_|i5xje7- Z9?K@d#g83kuLU}W!PC{xWt~$(69DK>U+Mq= literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_3e.png b/docs/sprite_library/sprite_3e.png new file mode 100644 index 0000000000000000000000000000000000000000..2c156389c23e3d90e2c047ce6f493fb2b93cd342 GIT binary patch literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^{6MV0!VDzsne1nQ1Oj|QTpb)7fXuCi3jfX={r~U( zvc-v)fqcf2AirP+hi5m^fSg)S7srr_xYBbEIgcptI3(_NjC;hiQFzH*hl2kz{etr+Qm&Hwl%Uzjx)i zu5hEPn@EmQ=0paS!YlG6UQK%Sfu4J`UReJw+Zt}>cHcj|a@9ujKIr{(K z|7D94F9Z3EB|(0{3=Yq3qyahIo-U3d6>+X-AM&~=axg#G#po)2z?|_oi`qN;%Z>X^ zf9;RY5N71^yS0V8)~=|}xA!6Ia<>x)dp?LPSL1n-?7y&hSDSEb$*U#JPExxSW-z-c zHD&P`82e6mY*cXUd+@vJmF1#r|1;QQG8SK$wr4?#Zl`Xfc-tMp?@Tjpba%dDy{dnw hV1I^vk9h8S{l8a*k6WHz#0zv5gQu&X%Q~loCIF%ZTATm? literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_40.png b/docs/sprite_library/sprite_40.png new file mode 100644 index 0000000000000000000000000000000000000000..c49d535b264a1d5f57a56970f718572aa07ff7b0 GIT binary patch literal 236 zcmeAS@N?(olHy`uVBq!ia0vp^5gSYDrwiv@pW h4p_AJT{LfKm6zBgI5R!&k{!@l44$rjF6*2UngG6VSd#z% literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_41.png b/docs/sprite_library/sprite_41.png new file mode 100644 index 0000000000000000000000000000000000000000..0bec2ad63e96694d1f9312a95a5ffc95f7745a73 GIT binary patch literal 245 zcmeAS@N?(olHy`uVBq!ia0vp^5SRohxNz1l| zs2PW-vtFAl9(7BFclU1ARUgkDxG`gA_-45q)8~Ge(|*(H*4DJD-_bVm&z@Oc(AM3y zWm@-zLpBedpZenA)9&)p=jYFuiIzGdoz8mkvyba?Uh4WV1L#%;Pgg&ebxsLQ042_6 A)Bpeg literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_43.png b/docs/sprite_library/sprite_43.png new file mode 100644 index 0000000000000000000000000000000000000000..d3fc2d0eb058db5f5f0dbd4f6c6142be044ac773 GIT binary patch literal 255 zcmeAS@N?(olHy`uVBq!ia0vp^5)hV zfjEpML4Lsu4$p3+0XYjjT^vI=qJ57l`Y}6-FkJY4^j(&af$sycAC`ymHU&E}nJTO} zkQ>mV-J@YH;Zn`avwg)usn)Ge*V%mYT_!)d?^iHuPO{gx6Pm~5wO#ZBK1vl@0v*fX>FVdQ&MBb@0JW%L A{{R30 literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_44.png b/docs/sprite_library/sprite_44.png new file mode 100644 index 0000000000000000000000000000000000000000..2a00f1814f5e6c335c651fce1dc22244cd1d82d2 GIT binary patch literal 120 zcmeAS@N?(olHy`uVBq!ia0vp^96-#@#0(@o%%7YEQak}ZA+8P%4nXEsLxq25j&g`! z0&y5ig8YIR9G=}s19GH1T^vI=WRp|2rcKdGKC>=GbP0l+XkKkf}F? literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_46.png b/docs/sprite_library/sprite_46.png new file mode 100644 index 0000000000000000000000000000000000000000..aca98726c3dc39a7ea83644280d44ef4f0259b7f GIT binary patch literal 193 zcmeAS@N?(olHy`uVBq!ia0vp^5h$LD+r5BHou_g6Y}*?!eOA#rTO1rdd#2WFWv nx@?T+We5yf$d{0Hm4!hwLi+RTyuKAc>li#;{an^LB{Ts5TyaBS literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_47.png b/docs/sprite_library/sprite_47.png new file mode 100644 index 0000000000000000000000000000000000000000..08dded5731086bf4bdf0870d83fc95954bce455c GIT binary patch literal 116 zcmeAS@N?(olHy`uVBq!ia0vp^>>$j<3?z5j>~{cCoB=)|t_}_kTMZTdH5PPVNiPHn zGL{7S1v5B2yO9RuNP4 z{6G$4NswPKgTu2MX+X|nPZ!4!2}#xl!>r~2i3WT2{s~j=9a~z>^{>g!V>w6WK^cVy znl)m(rC$`ExT470J6+=mOTOWX;C77+Mj1i=1Drh<+oC&Pf(VuE~R@FK5n8f9X zve^7H75f%HTWBqxetOyb{Oy%Io>wa8SywyNx(3It(@o5Ey|N{7=9Falxkh61uUGBb z_e&=!Q%kcZ=HhR&38Fbyjvs70P`3PPE%%bZg{R~b?@RX zleulCd{z9#I_>9E+w%>s22XCW$7F6@!|?oP^gJ5|Aw~u{R)&IFh6d3O{M^g4Z_k?` RJ_+bu22WQ%mvv4FO#n-=dQt!Y literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_49.png b/docs/sprite_library/sprite_49.png new file mode 100644 index 0000000000000000000000000000000000000000..74afa8dce33aa1911861b6fa875a674a32749a83 GIT binary patch literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!93?!50ihlx9JOMr-t_}_kTMZTdH5UB;_g~_w zI#8H_u_VYZn8D%MjWi%9*we)^ghQ6q!I00vL150@HC~BF%R8pVtp4oXDqkS9_eX13 z(NoTa90&X#PLWZPemloMRx;ILQ9GxNx8bsohR0K)|2{(gh&HP^)hXCXUy}@3o+{@w)jOGKL-KZBgKeSCj4gqzKR7w!jcHY14IdO+( RtOmNC!PC{xWt~$(69CK4Xxab( literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_4b.png b/docs/sprite_library/sprite_4b.png new file mode 100644 index 0000000000000000000000000000000000000000..256c7bdb68ca828238175af8fbe6bda4912c2762 GIT binary patch literal 163 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~q#0(@S$S&Ulq<8{+LR=jj9DvNNh6?}A9OV$d z1mZB31o;IsI6S+N2IRPUx;Tb#$R;NwB&0Afm9eKdi5!_)&Yxfr@oL_o2UVsvlO1hJ zDm9ujZk*fCv#2q0E04Ua%TYKzU=kipZ{&t;uc GLK6TJK`$}@ literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_4c.png b/docs/sprite_library/sprite_4c.png new file mode 100644 index 0000000000000000000000000000000000000000..7c60ae856d287c4a414b0e646eecf02afbc0d8fe GIT binary patch literal 228 zcmeAS@N?(olHy`uVBq!ia0vp^0zfR!!VDy3x&zvQ6n}tEh^vEx1CY7ZP~qR1qyPW? zU$!{$GLX+$666=m;PC858j#cM>Eakt5m(xOkdxVvhb4RoWA78T9P41rcX~; z&Ug4`s`jb={XacQypw9{Hf zI~-1~l8fEs)u9;4bsd8|X(_%LEUOKI0%_L6QefJAJ Z^Di#q``gF0#S`cp22WQ%mvv4FO#ovuTZuUw`fY zd!1Rr<#gEES<;6l{H(pY>i0~CSJ$f)RHOdo|Gz7+U{d_+MlT7k&0)u!wjSnbmf~gT XnyZvxaB0mnp!p1*u6{1-oD!M&0xd&nC)7xnV6G9uAN%S$HNmEsePTvw98GOmvN?$ nnP|?8D?$cSw+i{RGUYNb7{8T%%W}f&Hpp^MS3j3^P67`N3sD48_Ya;9q>-mU*e_({3(nKP-} zD}0hy%o6=uax;qU;+F5L)7lI+tdH5Q^_qz}Ipo@@rF=X*p^@6xnM}Lf^m!R)3Ym%K m%(x9;H=ylw-nWAJqKb6Mw<&;$URUOkNf literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_4e 3.png b/docs/sprite_library/sprite_4e 3.png new file mode 100644 index 0000000000000000000000000000000000000000..5a7abb719a866674cb3192aa0637e3e35585a4ec GIT binary patch literal 193 zcmeAS@N?(olHy`uVBq!ia0vp^5c2F7jm4oW7CwVdf1hj;705q?r`eCA9l z_X?ln6|+SDmfVbDySU~1>a;e44eMjJYrSS-P7b+tYAGKNPiUm}btcm;H+^2lnL=iw nIWw*Z8BEli#;{an^LB{Ts5@H;*v literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_4e 4.png b/docs/sprite_library/sprite_4e 4.png new file mode 100644 index 0000000000000000000000000000000000000000..42d1f0eda760b8630b74c31157486f1ced682581 GIT binary patch literal 193 zcmeAS@N?(olHy`uVBq!ia0vp^5s{ zb{WWFED7=pW^j0RBMrz&_H=O!;fPL7NJ!vGWnkP^@1SJTSj(BNad@}>8{sGA#%Io? za8U|obb90w2r~k)z4*}Q$iB}iu*r9 literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_4e.gif b/docs/sprite_library/sprite_4e.gif new file mode 100644 index 0000000000000000000000000000000000000000..3c76ab1af3afd9624de2571a977afad021c9bf3f GIT binary patch literal 3283 zcmeH}TSyd97{~v+>n+o?veGgb&BDMnFL{kb-F7!2Wl^`*OVI4>u3GN8?XHL=Q6c(L zBnVwhQ&+n@1QKDQzC>vu7!^TTNtC5w^iYOHg-+j$p$6JhFVY^EIX}MhedqGc{LeY+ z3{_$h8wfv4Kq#Hj%=BRY^a(3J&6`Z7^i=-a(B%+*d6qIaN0Fvc#zaQRB;-e?422?F zovx0KijLs{X-g?*w!1|EW{bl{4U8@?pYa@n48HJ3B4Sz2Zq3Y5<%;7h`1<-3Lf?2I z7y4SzWpSgg!D#0I2}3TaoYm$aK0q8_;jjvPo;ZLjW(~yGi6d+@LE<67uAkwL0vCxa zB}9NNH(Nn#3m|5S=COVrbHz5^OitU$Da>HD&;U~13>yTl64+c?PLmUhutXoDMVmu) z7(KyIBLkVxfPo(6C_yP4kW+0Tiyhh2p1w%@dNE!@QF@9h0Y?$LPzEztF&@H4ARj7o z$+eqeV?>k0CmnEq#q(b_1D^H3R2|R%Y~}eWCmN!%UXcMNwU|?*}|FuziPmF zt8dPzKL>Qx0)fx1tc?{1lF?ggG654;0pC1e^&`N$fHFskqaw7U?HQ0u$=dS(xX=J> zDWc8`7g@L$1&SUVKcH0qk*X?aTGJgBSj^qNUqGxs54ZInLT8*A>_N8 zWChJ8LYR*r3P`n$XJozOekaer*}8V6f5`E->~MJ1y|Ili!z)`2s?&GmkuQ|njJ^9S zZzy*+IZw5YygSxE`mp`Octdi#EFmsMo_2ASY}CUZdpLAMd~-umr$=EH{Z20Z%<9Ue zpQWK6Q5&>x&T2!p&#TAhbH_lCM>m(O%VX~PM}&G_>bq7oGSPHamw#|g((6q(**Gpa zFTC@yR@2(kiXh zha-Q(Ge3*2FA(kN7ucT{^Qe{UYtp~#uKQ+n_wQo6^s0?-&BdS;Bj!zR>E=^nPR+P? ze%iS!M{ZWHU%6kes>I@V=eNUQ64zec3z8P}NL~Lp@rz6g%lpaI`8)PaRzK^L-*Zvt z)B+~6%&cyqe4b|^p8^gVW}Na(;-1)(pD4+=ImiELth&cjhJB)e60dEh$h@!M$;_YG eX~Ps#SHmFvTER?{T`U0T1qM%7KbLh*2~7auYH(lx literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_50_p2.png b/docs/sprite_library/sprite_50_p2.png new file mode 100644 index 0000000000000000000000000000000000000000..5818a049e6bf11b320c173c39a371e95fbc25bcf GIT binary patch literal 281 zcmeAS@N?(olHy`uVBq!ia0vp^!a%Ic#0(@2{dP0~DV_kI5LX8W2Ox8+p~Am2M>)hV zfjEpML4Lsu4$p3+0Xe%pT^vIsBv}s_>NzJeuz$G!{rdD9C7FkgX%w6a2G68@2U%6}7EbD0qF*b-)*|cVE+N3zS>!&2@nXr5Z@e4lgW{_t@yA|2`)*QK{@d(SuC|%pY7JqgM^~ob zm=PLytVNwEXq8*eSLcm2@m+q}XAAFN$tVpHi%U5hb*f!iGW2X@NtEUC{T=Dtl{stI b{bx-2WODGy(W3Q0Z!mbe`njxgN@xNA7tw1= literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_51.png b/docs/sprite_library/sprite_51.png new file mode 100644 index 0000000000000000000000000000000000000000..7256bd2e4591aad12b5754102de075f322f2efd8 GIT binary patch literal 265 zcmeAS@N?(olHy`uVBq!ia0vp^LO`s+!VDxC8Mm(o2?Y3rxH>pE0GUn>|F;?{e7~{u z-ec^AWtRE7Yt4kdko(#8`> z|3kaDp9FOu{`^~h<68D;+hdFR>iOi3^Y?lkJoVAn^{-8nm0;-F_UgIr%W{st5lJz> zy(#6H@kR5t)k?kS7)AB=Bs+C zJ7jaa?hD-uDp`+&O?zTL)VGN{X-}Ok&(a&PZOzIB<^>n~6!&u%OU-AHdHNRUUW{Mr2L*|Y4A=dPaie3aDiQmXHnz;#Z;@)907h8rI z$HBkhJ6C7&I{f|mv7+m?>6W7$?8f_-Yzc~76R6M~V%7BXMt&MY;MwwDmfw~uZ0qlt zeeI}CyW|%cEHCU0TxGype;!UT>G=e5aQOF9IFR N;OXk;vd$@?2>{4JYw-X8 literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_52_p2.png b/docs/sprite_library/sprite_52_p2.png new file mode 100644 index 0000000000000000000000000000000000000000..4d81865ed2c939baa1343fe3c69324c75bd9deb1 GIT binary patch literal 261 zcmeAS@N?(olHy`uVBq!ia0vp^B0#Lk#0(@om><3cQak}ZA+8P%4nXEsLxq25j&g`! z0&y5ig8YIR9G=}s19Fyox;Tb#$g&<-=yw<>d7)foF;g=`@Zt4y4u%&TG`!q;hh-I` z>o;!Uzb$?-9DGlHu*rzE&QO>!QDmdgoF=|43z{!DT|MNS2Jua=kF_k8}HeX(xq+*g8W4$S;_|;n|HeAZLN6i(^PdoNZq>=V1dL7u{VA{%*`iQW_I%9K6I0 z+~On6l;go?}lZzSt@)cR@9a zZ`HSQU6bi5D_%vg1zT)|({BaA4v0#cDeqelBAu-!J<2OOItY zUtP8IKedLRXY}%qKV)9{+~%2o(|3_mD{i!%_@rR7y^c|Sp5z4U^=529w=#IT`njxg HN@xNAR$pXL literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_53_p2.png b/docs/sprite_library/sprite_53_p2.png new file mode 100644 index 0000000000000000000000000000000000000000..9b8c0c03c6c68fe79e7157a704c1496cbd557fb3 GIT binary patch literal 257 zcmeAS@N?(olHy`uVBq!ia0vp^0zj3aZ>g=YsDn{*f#jjr`n(YBo8#4|JotyXL2-XPG_ZXx$wS}Xw2Z34Bv8SnZzlkng|`r%kq* zXR93^+jqWMqCGj|@Q$~YTeuBb6Sf=*cPgCpfW6ehVEHkn-d{lHGI+ZBxvX9^ literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_54.png b/docs/sprite_library/sprite_54.png new file mode 100644 index 0000000000000000000000000000000000000000..ca8211af164fb584cdd509b8b528e3c459136797 GIT binary patch literal 267 zcmeAS@N?(olHy`uVBq!ia0vp^5)hV zfjEpML4Lsu4$p3+0XfS(T^vI=WLXawdL0g6V7T!8V2%C)ky!^<|4~l4&>(q7W|pJ6 zxMOka{*(g$2_iNPP30#Rd||p9`|5(C5{G1qWD8FhmkD#r`_(N!K3OgPb5l0p-`j5y zwRdC$&1bVdeE6O7)jytFoQKM07hLM$EX*-_bR})g@w5YPpZ+v`x9X8>ZlCbOjav66 zMJ&INp6b81{NvGA3(g)nS*%v~%Bb+-**SXej6Y_~FVdQ I&MBb@04Voti2wiq literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_55.png b/docs/sprite_library/sprite_55.png new file mode 100644 index 0000000000000000000000000000000000000000..2387b47008d4d9612451d92587c4256c03f20b54 GIT binary patch literal 300 zcmeAS@N?(olHy`uVBq!ia0vp^5=KzSp>Ns$f5A`aZ+ zJJa$nI%()*Pq<$c-uZbVzC+wY{{hoh>90-6e|P3*o5wL9lrK0L@};5Fzv0Z* zo1KRub=9@RC01mezOd7E<0>V+4O#L>uip*|$+&mEm$$Sj-oowvtP{!}>KP%cX0a!| zOlbHo=#s+pjzL+gbIF$f!3_lpj(kG3?h*p|KN=3DJJ-z5Q+c1TKs{1&0;A!X%@4~J weG(KGimP->ZdhmdKI;Vst00+%^RsaA1 literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_55_p2.png b/docs/sprite_library/sprite_55_p2.png new file mode 100644 index 0000000000000000000000000000000000000000..0931024a4cde4b9028c5b4d7a64c008be55a828f GIT binary patch literal 300 zcmeAS@N?(olHy`uVBq!ia0vp^5L%KRkVRZH`MwUH3T$W&QA!U*BV-%C3JBUs8OMq4s)n(-AGD z+{K?v4a}`%IA6-maD1_7=DP!14s9&3;5un-=E*zriyB|{n&g}|msF0VBA3&F4)afP v?D95SarB+!74Ad5EY&Z1jsz@8YWQcL#OB)k`MRt(&}R&uu6{1-oD!MtK~%!H tPr%bbCF$r#A)ec|a;NH|1id(ZFwSw8xV}v0A|KFs44$rjF6*2UngD9fViy1a literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_56_p2.png b/docs/sprite_library/sprite_56_p2.png new file mode 100644 index 0000000000000000000000000000000000000000..a38b6ff638aba3aed27bf4765400a0a9c361eb5c GIT binary patch literal 239 zcmeAS@N?(olHy`uVBq!ia0vp^5-ovZ)3bV6$6BUS&)1&0#TlZrHS4O7!cnuow^$U8M*J7oII8e1|G$X9 z^WU`!;jjL_eZGIX!IkIny#IerU-&QV+Nr1Yl1t{h>GswgIxTUT-BQLi)s4TN k=x?ZJ3|hn2!^p$%@u{wg$Cf5VpyL=kUHx3vIVCg!0P>7iNdN!< literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_57.png b/docs/sprite_library/sprite_57.png new file mode 100644 index 0000000000000000000000000000000000000000..6d894b63f7e55b619684bcee2d27c8564e362033 GIT binary patch literal 278 zcmeAS@N?(olHy`uVBq!ia0vp^!a%IS!VDze9lkRWNC^e_gt$65H~^VW4*$0rDty1O z^xv7I`(nPu0EHM!g8YIR9G=}s19CQbx;TbZ#O0p7$a~m;!!__SXGNhcg9dlRH1>=u ztUBF)!!`2)J_+yZyWqNH#v%2FDgHs(F-;856LM?VEts9&r|JZ4x9iePGcFEGH+;R} z-I8zV(tfXtpUhORo+a&dx;J>H#D=b;jv9Uy0i0$NCy22YSj-Fw71P$M+OYD4(ht_0 zbEf)@TJvpcq(k;q7&I;66q@qIPU**mt6Xmn`exY1v1IW7Qq0quw=F*A&eP8O?oG1x YhMY3ri}r;!0A0`E>FVdQ&MBb@08zMTbpQYW literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_57_p2.png b/docs/sprite_library/sprite_57_p2.png new file mode 100644 index 0000000000000000000000000000000000000000..6b6af9982c72946378e38acdd08f1b18021b7394 GIT binary patch literal 257 zcmeAS@N?(olHy`uVBq!ia0vp^!a%IS#0(@8n>7uA6iT5}_?_+iL0j*SMoks6 zx1EkRHCwi9H%M?S*wCnc(3Nwx)~vJ58`d6DPTVSX_?O?T?Jm25dNO6^Kb;kIsrJq} z=VNzYn=Jjm^Ut)ZJ%uOUJgx2gc2eTW%*DQ3_FMn}00DGTPE!Ct=GbNc006H^L_t(|UTx0NX~i%Mg<&aO zK*w$dkmv%&WPn?`KoHu0mCCXGPX=?=zw$eb5K1*9({XV6Qj4pDt7z+8J#Sxjd>#7Ovqyljlc32{dSCn)u5@ z&9G=dnJlKTO_c9VDZ8rd3Y9}w{Sn?Y@IL!ewn?_6OlHNRg4Vcb;$QuICHC1{zTS|4 Z2ftgu9Q0s^M*;u<002ovPDHLkV1lQDb0h!& literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_58_p2.png b/docs/sprite_library/sprite_58_p2.png new file mode 100644 index 0000000000000000000000000000000000000000..be13001edd3a4b6695c5796e47a81350e088aa6d GIT binary patch literal 255 zcmeAS@N?(olHy`uVBq!ia0vp^!a%If#0(@ST%G*{Nbv;tgt$65H~^Vj4Hf>KIm#h^ z3B+M63GxeOaCmkj4aiyO>Eal|A(@3mCwWtZ1oJMP<@`t|x%XkJ&xx3%Y+Jbyi( zu2w82e|yX4yDHBs??0)?Ik z@uhu_?goi8?~vhtv|gxt;*K|+Pdyw~upBu*uYP)>@sjNOw>p51W$<+Mb6Mw<&;$T7 CU1=Kt literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_5d.png b/docs/sprite_library/sprite_5d.png new file mode 100644 index 0000000000000000000000000000000000000000..f0a1e64c5a2b6ca95b4d45bf66225754f34bb3cb GIT binary patch literal 237 zcmeAS@N?(olHy`uVBq!ia0vp^5CQVGhyfrWoVwT{*?S> z?iEZMR_!+1@b0qheBO)cXNu4I9a;N--&@ZolJ9@-yUr8y{N9>$zD2+J_ubWe#d^={ zHCx3t7KJs;2HRN_mMBaTx%M&eX?h-G=!7I?A#GRYl1-QT`)?GmK4Hk+Y!WejYcKC2 k{>{QGUe~!XhJ+rppZ=ULvYES23Fs~cPgg&ebxsLQ08P|deET0{{R3*#+}%0000FP)t-sKtMng z5~q@o|Ftn7|Nj5|#eZ)A0004WQchCb&lw0II6sAO7JGzxM$?!~g&Q07*qoM6N<$g57m# A9RL6T literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_60.png b/docs/sprite_library/sprite_60.png new file mode 100644 index 0000000000000000000000000000000000000000..0a4b31ffc268b5b55743c9270a8d6594d6eafe0e GIT binary patch literal 241 zcmeAS@N?(olHy`uVBq!ia0vp^5X-rHw6D= z?_x-1n6WrZCn0Y4s{P!H%3l9|GquC}@846iPu$)2{`WaKi|<=!U0kCi|40wzBioMI<<7lnvUSQdoS0m$gZCE+|uho#PbFBQ9<)!26uSHQV7eX9r3{{~elF{r G5}E)PwP~vW literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_62.png b/docs/sprite_library/sprite_62.png new file mode 100644 index 0000000000000000000000000000000000000000..e365248694d0dea4b9a94a8b0d63a0a129840e4b GIT binary patch literal 263 zcmV+i0r>ujP)T0{{R3*#+}%0000IP)t-sKtMng z5~q@o|Ftn7{(*4+{{Q156LA0l00DGTPE!Ct=GbNc005&&L_t&-83n*Wii0o|0MHl3 zeUj3>`QYB+_Xef=RO zk3qk-g%a_FXGB%n4cx8j2f#HvPJs1KpUA{ERIIP}iQ;h<`T3=~{^^ta{E}@b$k#hj zl|g1Ey(+<)c@mXg$)pU}3)BUKK^LI!`mhX8VzNaA+J0P118A)^_y^$16?Bd7?;ijF N002ovPDHLkV1gZwaRC4T literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_63.png b/docs/sprite_library/sprite_63.png new file mode 100644 index 0000000000000000000000000000000000000000..b1e83cec0bcdaf4f4b8b33026d52f41dd5e3fe2f GIT binary patch literal 171 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uk3?!4(jyeG;o&cW^R|f|N5uxRiCj9^Z@4wr* z>k>e5#*!evU(R2Ufrz#&ewLKo3E$fWToHVmFv+2|`Lhnl@tystyN%p`yXq#m_;T4l2Itp>VY>~o=R(#Vr!k$P@ZPjA( literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_68.png b/docs/sprite_library/sprite_68.png new file mode 100644 index 0000000000000000000000000000000000000000..04b8db94d921d84d49ce9ef8cf5d242b8ed0a519 GIT binary patch literal 240 zcmeAS@N?(olHy`uVBq!ia0vp^3P3E!#0(@2Qoig0Qak}ZA+8P%4h%KDlP3KC|L?z~ zpMV`uoUtUxFPOpM*^M+HXOgFjV~B(#>p??4Mn?vQ13OhCcXnTR;c-RDgex$1&p~Gq zxeawiOBJ>(`qWZ;=&IJz6=x1S<(*=(U}4LGEY&#M=Kjp%^Hwd*_qn-2h_iD|h2!#N zjY8+X-gTe~DWM4fDe7AD literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_69.png b/docs/sprite_library/sprite_69.png new file mode 100644 index 0000000000000000000000000000000000000000..174530ebd11a5215c4ce02a4ea77080d6e6dfd7d GIT binary patch literal 256 zcmeAS@N?(olHy`uVBq!ia0vp^3P3E!#0(@2Qoig0Qak}ZA+8P%4h%KDlP3KC|L?z~ zpMV`uoUtUxFPOpM*^M+HXOX9iV@O3@>gCi=LcWKJ5nZeUhp`2pf2ZAy4j|nqf@h! zo*h&@ZEAnW&L;ND;(0uRW%BK^HHTzNVsEb!da*hq_~nDu=d5{5Hq`n&&E?}|zGJqM zhx^Q`o{6hO&g{RLBe+z_*_%D@-T(MG9WOU;cIsE|+ogOmp=7o%(6tPnu6{1-oD!M< D-Ya7s literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_6a.png b/docs/sprite_library/sprite_6a.png new file mode 100644 index 0000000000000000000000000000000000000000..98328efdebeed27857a46dfca47f78c2e38ef6d2 GIT binary patch literal 267 zcmeAS@N?(olHy`uVBq!ia0vp^3P3E!#0(@2Qoig0Qak}ZA+8P%4h%KDlP3KC|L?z~ zpMV`uoUtUxFPOpM*^M+HXRW7;V@O3@>s=uGf7?ErjD_ZMTpVMQ2NGIzx`W<%e!u$zqir& z@`SK;GBfYq(XZ7g`@Ag5ze`_?>`@Z>Ke9W)2S3SHw@rwe` O;S8RxelF{r5}E*=d1s^m literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_6b.png b/docs/sprite_library/sprite_6b.png new file mode 100644 index 0000000000000000000000000000000000000000..85097a641777bb2894a8abafe040b3c4d5c5beb1 GIT binary patch literal 270 zcmV+p0rCEcP)RKgEBp*L%f3Pf{X|qfpURA?Ma|I-0XQEZ12pZu>n5=QE`W=r z>AgNlD^Ihep$P6I8(|1CNoT=@BX$=z64oS=NnkL92E_`n;IUkx_q?tA7BcVm%KsJzU-V2B U+BI4>B>(^b07*qoM6N<$f`gZ5s{jB1 literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_6c.png b/docs/sprite_library/sprite_6c.png new file mode 100644 index 0000000000000000000000000000000000000000..7715cbeac89ec32684a301e2f48fb1d28a972c52 GIT binary patch literal 281 zcmeAS@N?(olHy`uVBq!ia0vp^3P3E!!VDz$t1wCdDWL$L5LX8W2Zoy7NfZ8WHB|W5 zSn&Vf|HsjC>Ofh>k|4ie28U-i(tw<;o-U3d5|XS34D(tH1Q-q^%2ox%?2kIRSJiI9 zp)Ye-|Nr-5nQ`5iG2swP0+X;pfvv|5&prB0ciC_4m}mANdw1ci%bkB8IA=e8zP|UM z`0cfGdkgt~GqO4`KAZF2`Z>!NmJZg8wT8zSzT~i-?O-*C>1Vre`eoIeEJh&?ho^DB zg33i49GH&ieo=kP+0>xYsP{!xSK$@kiM>KA{NmRzt&sl46d?J2b|3?j2Sd*O{p>&& cCs;H5mwzsCJ>MwI6v$=pboFyt=akR{0J{cgX#fBK literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_6d.png b/docs/sprite_library/sprite_6d.png new file mode 100644 index 0000000000000000000000000000000000000000..4cb29c88c2ff8be460ba52e5a2c813372e8898fe GIT binary patch literal 152 zcmeAS@N?(olHy`uVBq!ia0vp^+(691!VDzuib?DSQbGYfA+8P%4h%KDlP3J%YN+t9 zvEcu||Bs{P)Pb^$B|(0{3=Yq3qyaf5o-U3d9I~tj0|grt1XvHae)(_E#Cea$Yy#J} y^@sEpr)BK&i~Z>;8@Z@u$KG=p{_*l|XK!Q&v#1*zxU&KEGkCiCxvXbP0l+XkKwdgQ} literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_6f.png b/docs/sprite_library/sprite_6f.png new file mode 100644 index 0000000000000000000000000000000000000000..1fda18b611211062e54adb106ae2b0a053a84520 GIT binary patch literal 125 zcmeAS@N?(olHy`uVBq!ia0vp^93afZ3?z3ZhDiV^o&cW^R|f|NhML|<6aN4I_g~Ua zzz!(RSQ6wH%;50sMjDVK@9E+gQW2M&z?&eDkXT@M;=z>@4-TA|aNwlERu&Bo23-l} VcgzU|Q-DetJYD@<);T3K0RWYzBt`%L literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_70.png b/docs/sprite_library/sprite_70.png new file mode 100644 index 0000000000000000000000000000000000000000..eafa95ab08349cfafe1081d95a1f24241438abf3 GIT binary patch literal 153 zcmeAS@N?(olHy`uVBq!ia0vp^+(691!VDzuib?DSQbGYfA+8P%4h%KDlP3J%YN+t9 zvEcu||Bs{P)Pb^$B|(0{3=Yq3qyagmo-U3d9I~tpfxHa{0?zT4Ki)5F@O4zxnc4U= z+bP0l+XkKwdgQ} literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_71.png b/docs/sprite_library/sprite_71.png new file mode 100644 index 0000000000000000000000000000000000000000..90fb20c269cf74561060082116bd6441d1c27755 GIT binary patch literal 153 zcmeAS@N?(olHy`uVBq!ia0vp^+(691!VDzuib?DSQbGYfA+8P%4h%KDlP3J%YN+t9 zvEcu||Bs{P)Pb^$B|(0{3=Yq3qyagmo-U3d6>-T424(^x2?+^oPHt>$%-0x#*%;ZF y#n_bvjvh#0SmGlQ!83zJVn*zonIe)4q!{wES=1M{zs?65z~JfX=d#Wzp$Pz!4=H{C literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_72.png b/docs/sprite_library/sprite_72.png new file mode 100644 index 0000000000000000000000000000000000000000..1fda18b611211062e54adb106ae2b0a053a84520 GIT binary patch literal 125 zcmeAS@N?(olHy`uVBq!ia0vp^93afZ3?z3ZhDiV^o&cW^R|f|NhML|<6aN4I_g~Ua zzz!(RSQ6wH%;50sMjDVK@9E+gQW2M&z?&eDkXT@M;=z>@4-TA|aNwlERu&Bo23-l} VcgzU|Q-DetJYD@<);T3K0RWYzBt`%L literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_73.png b/docs/sprite_library/sprite_73.png new file mode 100644 index 0000000000000000000000000000000000000000..c35186fa0ecccfbbff93e19ec0f82ca9354bc6c7 GIT binary patch literal 154 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~q#0(@S$S&Ulq&Ne7LR=jj9DvO4H^)r^LpWqv4;2 literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_74.png b/docs/sprite_library/sprite_74.png new file mode 100644 index 0000000000000000000000000000000000000000..244cad580efdd77f93e9e36595c98d3395ed59c2 GIT binary patch literal 150 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=(3?#Lsx0HZ50X`wF4h{}Lrjx_}?>ClSJ0W%% z$YCrA@(X5gcy=QV$g%KraSY**O-@MQNK8ohbNbAIyt4-szD}E|IB{<0#czDfx3$^X tcFyJT*|=VE#*9*l84}zQ5-ySw3}@DI7_4LtlLnf>;OXk;vd$@?2>_(LEY<)3 literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_75.png b/docs/sprite_library/sprite_75.png new file mode 100644 index 0000000000000000000000000000000000000000..08d6ccd79310a90780b758230a7a82618f6e6c1c GIT binary patch literal 110 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~h#0(_23w-TNDG3FsA1b%rnsD&l-FLPLOlMfV85rh;a!uDtmOKDd$l&Sf=d#Wz Gp$P!2Qy*vm literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_76.png b/docs/sprite_library/sprite_76.png new file mode 100644 index 0000000000000000000000000000000000000000..fada0b374b5f44ebd20778c8a841f901ea05e810 GIT binary patch literal 150 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=(3?#Lsx0HZ50X`wF4h{}Lrjx_}?>ClSJ0W%% z$YCrA@(X5gcy=QV$g%KraSY**O%_N^NLW!0#Jdk12-w@w=$NO@%zRs$o$c6FVF`&( q+YAgm_5zjMKYif95AhX7EDV22xsLp2o_`%^27{-opUXO@geCxrD=%CC literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_77.png b/docs/sprite_library/sprite_77.png new file mode 100644 index 0000000000000000000000000000000000000000..9e024b6959b26dffd98756be664f00afff8faddb GIT binary patch literal 129 zcmeAS@N?(olHy`uVBq!ia0vp^Y(Ol)#0(@obf3inDV_kI5LX8W2O!hQ;s5s=ORt>} zyA0$omIV0)GdMiEkp|=_d%8G=RKz7Gq$W%#Pgt@4fC8)9W;KH+{tS}}*kZVr^h{-B Y(A~!~|316kdZ2O!Pgg&ebxsLQ0OtE7F#rGn literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_78.png b/docs/sprite_library/sprite_78.png new file mode 100644 index 0000000000000000000000000000000000000000..46aedf8fc08cfe00b9b4ad7ce7b2666595c1848c GIT binary patch literal 151 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=(3?#Lsx0HZ50X`wFKpF^~9R7d5v2q&I0|)j^Z*+X6%j|qljP2M}VTl`YmIf!@ rrX;M0J9HqRys>fNy~akzDUFN_Odq(8Y}3ER4l>`<)z4*}Q$iB}&^0hl literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_79.png b/docs/sprite_library/sprite_79.png new file mode 100644 index 0000000000000000000000000000000000000000..fa040985c5fad9a3b5c67989a481ead914ecfa36 GIT binary patch literal 182 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!93?!50ihlx9JOMr-t_}_kTMZTdH5UB;_g~_w zI#8H_u_VYZn8D%MjWi%9!qdeuq#`akL4Y?QA>qudR|gJ!`<=?%{N;aWu*8%9XQK{H z_^)Nc>S1v%|fs+%qPjQ^2sNJAA)iI07nWK3Mb2CHp4VGg9Y}YuO_;`9|YVz># bEaKx~c$Fub<6RSb7i6QStDnm{r-UW|7HU0N literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_7a.png b/docs/sprite_library/sprite_7a.png new file mode 100644 index 0000000000000000000000000000000000000000..96a19e3f5bab0c58a241b5abfedfbb9997a48526 GIT binary patch literal 185 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPE^3h)VW1=0=<4*$*^1v1jo(*FPZ z-{>{@H;}_z666;Qq<@tixd)_zJzX3_IHG<1Zu2%M2x#YB*wCOoK~DINA>$kSZmFk_ z;xC(@bLv#-Q?lilaOVogspCe1SKmC_vry|AhpEP{+dPhUe|`DM9_>|pZ1aRt{}0lt a_oc6 literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_7b.png b/docs/sprite_library/sprite_7b.png new file mode 100644 index 0000000000000000000000000000000000000000..336d9aae6c1a0e1372311ff257ea2584ddaf2b79 GIT binary patch literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!93?!50ihlx9JOMr-t_}_kK;~9Mg@0#`a)@67 zaTrU2{DK)Ap4~_TavVHe978JNk`oe25`bX#fddDm%uE{^ZF63+vRzyKkdbFiVKlRZ zi*~WI#HLI6>sW*n^dgwnu&j|-!<)l!LxaJ5zsSjK-bF<~vlu*G{an^LB{Ts5YIrTR literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_7c.png b/docs/sprite_library/sprite_7c.png new file mode 100644 index 0000000000000000000000000000000000000000..b8605e0dfc8d869fa636937db8f62c4d26fe9365 GIT binary patch literal 189 zcmeAS@N?(olHy`uVBq!ia0vp^Vn8gw#0(_!o71-fDV_kI5LX8WhqSb`t%eH!|NZ}L zBwGm-XDkWw3ubV5b|VeQiT8AI4B?1QPDoh6o4~+yY`vmFSLRhN0oDKJR~TKS8#w;` zfBv7{$Y6u-*Iuriga>giW?%o&(75)hxz`3p4ebL54jm}CckkbyO6IoB%a-li$7I08 lw&mM;Ce4(H?G25L3`-YL~j59 literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_7d.png b/docs/sprite_library/sprite_7d.png new file mode 100644 index 0000000000000000000000000000000000000000..498084019940050b4b5ac497ab970d5d45ba50cc GIT binary patch literal 184 zcmeAS@N?(olHy`uVBq!ia0vp^5IIY{tRLRqmpnB~2`TBn~Z3k~$H@CRK>UNOf`mbgTe~DWM4fD~dgV literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_7e.png b/docs/sprite_library/sprite_7e.png new file mode 100644 index 0000000000000000000000000000000000000000..0c8b9f90abd16264320493041943a001aa944d14 GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^Vn8gw#0(_!o71-fDV_kI5LX8WhqSb`t%eH!|NZ}L zBwGm-XDkWw3ubV5b|VeQ3GsAs4B?1QPDoh6o4~+yY`vla7uS>jZB`AE3X`7yum2su z#x^y2;j)=LJkj^I&U(l6(Z$!e;g*8ex!bqr+vguBxOU~r^>7BagALdIh4Ly?-DP59 ZV|ZdIepjXTV-(PQ22WQ%mvv4FO#qR@J`n%_ literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_82.png b/docs/sprite_library/sprite_82.png new file mode 100644 index 0000000000000000000000000000000000000000..edb8bbdd78c5fd6fb71e6ad4b433313ea0211f2c GIT binary patch literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^93VCmGmw0_=J;wL#S`EY;tHg<8Y=v2EcpNLe}YTj zejuN*B*-tA!Qt7BG$2Rc)5S5QA}%>WASHn_G2zas15(Eoc1%8)%isJZnC&_@PoTKO kD;bF=OcE}V5)l#%GbPwBoAIPn0JSoBy85}Sb4q9e0Eho6KL7v# literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_83.png b/docs/sprite_library/sprite_83.png new file mode 100644 index 0000000000000000000000000000000000000000..f5c9d417440ec38a5aaba9a35a5f1557c399c7f0 GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^96-#�(^rK8WyvH~~H(u0VRLp~An$g8%>iC%E+O z2l5$9g8YIR9G=}s19EgcT^vIy;*t{tQW8WG6M9k-P9_V?J@(++WJjx3#d;;@nas^g gn47ton;DoH5;wE%SDG-t7pRZH)78&qol`;+08JYwi~s-t literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_84.png b/docs/sprite_library/sprite_84.png new file mode 100644 index 0000000000000000000000000000000000000000..edb8bbdd78c5fd6fb71e6ad4b433313ea0211f2c GIT binary patch literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^93VCmGmw0_=J;wL#S`EY;tHg<8Y=v2EcpNLe}YTj zejuN*B*-tA!Qt7BG$2Rc)5S5QA}%>WASHn_G2zas15(Eoc1%8)%isJZnC&_@PoTKO kD;bF=OcE}V5)l#%GbPwBoAIPn0JSoBy85}Sb4q9e0Eho6KL7v# literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_85.png b/docs/sprite_library/sprite_85.png new file mode 100644 index 0000000000000000000000000000000000000000..fc6c9f72db620da8e4e3c2aebcb67663e2eaef30 GIT binary patch literal 199 zcmeAS@N?(olHy`uVBq!ia0vp^B0wz8#0(_MirWo<6i_SNDAcTFT(*>gTe~DWM4f2@ym? literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_86.png b/docs/sprite_library/sprite_86.png new file mode 100644 index 0000000000000000000000000000000000000000..7a288ab17e2c238286af22cdf2cd62e4b8b7eef9 GIT binary patch literal 193 zcmeAS@N?(olHy`uVBq!ia0vp^qChOp#0(^7ZhXTBq<8{+LR=jj92jbPCr$YO|KEQ} zKLI41qjz1hR5DX8h7+S~R21*q|YEf((Ch3d4j;X*?{N7p){27KxRzbuh1Ni(}_o oafw;r6?4mN{)bVH6;eD5mp6;rTo-zz1GJ98)78&qol`;+0M6e$ApigX literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_87.png b/docs/sprite_library/sprite_87.png new file mode 100644 index 0000000000000000000000000000000000000000..0767297172b05c051562b681e8542afb4fd3ac86 GIT binary patch literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^AhsA2GmvcYczO;<@dWsUxH>pEFx2!;n(+VszyFec z0(L-g#*!evUJ!9T*r61P9GLrj(?;(xuhnlrrBJ=A{DJ ziuPU177b+^E&BNOpK#l!cHZ`v%oXX&t|ywVONJ=-Ps-W1Uou1az5Y+9i5VBvyDrO1 X_6p=&68CrjG@ilJ)z4*}Q$iB}02w>B literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_88.png b/docs/sprite_library/sprite_88.png new file mode 100644 index 0000000000000000000000000000000000000000..ce0e22a7e7579d76f190671e73117150fd1ceb18 GIT binary patch literal 191 zcmeAS@N?(olHy`uVBq!ia0vp^{6H+p#0(@So6m^{Qak}ZA+8P%4h%KDlP3KC|L?z~ zpMV`uoUtUxFPOpM*^M+HC(+ZzF@z&JIpIN8LPElYdIl8{(FPXtj@1k)hV$*uA2_i8 z|93_SPVGU)GHnkV0dD{0%ROIaOdvX0|%bpWeX8fVY(v5pzhF6*=T0O leeh}i1G@tYt{?cyz;IDpY^A6t-y)!844$rjF6*2UngGz*KX?ED literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_89.png b/docs/sprite_library/sprite_89.png new file mode 100644 index 0000000000000000000000000000000000000000..9e2c61e9838fad016c74df9e971f20a3a23b611b GIT binary patch literal 198 zcmeAS@N?(olHy`uVBq!ia0vp^{6H+u#0(@?+WyrCQak}ZA+8P%4h%KDlP3KC|L?z~ zpMV`uoUtUxFPOpM*^M+HC&SalF{C1n_rz|_Lkb0J|Ie%@_d|sJ{6L?A@ zIyoX0rEe_TGR0@2op;7>4%X$8yInT#x#{!wxNE7{al_&{C(pZ2SsuRRe^9%1o?xu| v)a@bHMb8#EcwJFVh+WZco15AF;kvxyA`xfbjiR|gI~hD({an^LB{Ts5A7Do1 literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_8a.png b/docs/sprite_library/sprite_8a.png new file mode 100644 index 0000000000000000000000000000000000000000..1997ee34bfbd155ff16f6db012007c0e0dbbc822 GIT binary patch literal 233 zcmeAS@N?(olHy`uVBq!ia0vp^5|uDWM#d9y6|?m~74#$%Jq*$uw!En;{j<@O-?OXzjk9|ezQF(|LAWELrBoFl>c gU;&c_!-amdKI;Vst0B-kLI{*Lx literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_8b.png b/docs/sprite_library/sprite_8b.png new file mode 100644 index 0000000000000000000000000000000000000000..f32284480e5930361e1980c0c78452f9d2a1f2bd GIT binary patch literal 316 zcmV-C0mJ@@P)S9_S4)=Y;$*5INww0hC#lfUTpkKTbjkM-|3ZbF|QpB$bX!6 zu`_u;K&E_og6M3kjOhEgf@rlWAsR1BFkV7)G)nOIxPbe{95Nm7eE6AQv@uE0F0Rke z*4su~AA%UUS8<2zi@1Xap@XVg3Q9moK{W-{6oeFn1VjN?fb1Q}CjJ49wLFHeOo=N1 O0000_!@pQ{d_17{VdTdSD~xAqNqL3)A)46~iNzZs$?5Xz-P)Ze3C! z?S7KwAmsyJffas)wJQKGo{f2h|)e^mVzFT*&&ftZi4c zW2u#c72kwklDFi}6gp;b&AQ|`ja%*G5wQ%eKa3rZ#qwkB%SM7+;_2$=vd$@?2>`G} BMcM!W literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_8d.png b/docs/sprite_library/sprite_8d.png new file mode 100644 index 0000000000000000000000000000000000000000..14d40f045bfd784ab5ef122d5fbb84b0a7686011 GIT binary patch literal 197 zcmeAS@N?(olHy`uVBq!ia0vp^qChOp#0(^7ZhXTBq<8{+LR=jj9JU%N{A(=u|L?!V zRdt{+17k^$UoeBivm0qZPP(UyV@O3@aza8v)By&jJN61!7!8&#WL~z)+Ce1wD>JLW z*WZjYzF%f^F)VOeu~kt<;njAIj+9?#4=^lqEVAjD$mrlT+t7f;BXcefLtwWR^O437 s=k?6&Auky@zA_$q+g`9%!G@QO;puMClnBZ1Am=f7y85}Sb4q9e0GQW61poj5 literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_8e.png b/docs/sprite_library/sprite_8e.png new file mode 100644 index 0000000000000000000000000000000000000000..02e70e1e4c74e03ebdf6f04a7deb4984df139fd2 GIT binary patch literal 176 zcmeAS@N?(olHy`uVBq!ia0vp^+(0bG#0(^}yZSx=DV_kI5LX8WhpmPR{~8Pa|NAd- zRUIhIz*rLG7tG-B>_!@p6XfaQ7*Y|(dSD|blc5O1h3N%XZ+0eWhqVjFsGi`R(x`L9 z>Vf=GMj3_coZ&T6bN}$|&M*F7_{zpCYZc#Ot7!&rcW=>KCTc$I*i+9sL-zD333C79 W<_qx#M&4Tovdh!e&t;ucLK6V+g*%Y| literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_8f.png b/docs/sprite_library/sprite_8f.png new file mode 100644 index 0000000000000000000000000000000000000000..011d16b2b72da7465d46ce81b8dee8729e935043 GIT binary patch literal 196 zcmeAS@N?(olHy`uVBq!ia0vp^LO?9Z#0(@I)2FcmDV_kI5LX8WhpmPR{~8Pa|NAd- zRUIhIz*rLG7tG-B>_!@pljiB-7*Y`zdtxJRqoYK_!{=fa%r_V;bvx&DdsP}<43}sL zJHjxlUQ}9wv0jDi>|}HOv;+! r*KfO9R{iT8`n2o*makHM_v|B^-xCQV_KiKCK=ymO`njxgN@xNAW=TaT literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_90.png b/docs/sprite_library/sprite_90.png new file mode 100644 index 0000000000000000000000000000000000000000..157ed36f541e9d105cdc284e5712984d32aa8ecf GIT binary patch literal 199 zcmeAS@N?(olHy`uVBq!ia0vp^d_XM8#0(^BzJ5;xQak}ZA+8P%4qFWs{xuf-|My?w zsya}Zfw3gWFPOpM*^M+HC)3l#F{C2S^oSwf!2k({2j@SS?V5R~q2=lY6WLz_XER6Xv<*nNLb9{F( vzfL>$Vut5edDB(L8CE3yU^EH4qx*=P(M7VR{zC0UkP|#z{an^LB{Ts5YL!Z5 literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_91.png b/docs/sprite_library/sprite_91.png new file mode 100644 index 0000000000000000000000000000000000000000..447b7eee9ce6b51f5b18efbf4933d88c99f00458 GIT binary patch literal 270 zcmeAS@N?(olHy`uVBq!ia0vp^vOuiN!VDy5ot1eAq$C1-LR=jj9DqzGhyPm*6~5nC z`meFz-oy97*Y|J+vmvTtjObXmiM1a!TLYk z9S`;`4S%su=!9t1-+x-IF2Bn9C6BkT3g}!}Rs3Vl3jX=k?&}{LeAVM!CJz!`X0@Hj77byYZ0hv#lUOo0x06)EqaT%;m1e!{uWRnjY3_c@ zKIO7)!aX*l`*+!I6u&?7d|Uh)<=xdQa-JL5o1H)CwCulGi@gnRe14m^{b~_aN1n|A RCxPx|@O1TaS?83{1OV3*Y*zpP literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_91_p2.png b/docs/sprite_library/sprite_91_p2.png new file mode 100644 index 0000000000000000000000000000000000000000..b115ad0d963c1b76fbcef31638aa834df9b4406e GIT binary patch literal 258 zcmeAS@N?(olHy`uVBq!ia0vp^vOuiN#0(@wdtW*NDV_kI5LX8W2Ox8+p~Am2M>)hV zfjEpML4Lsu4$p3+0Xa)NT^vIsE+;1>ByglQFdUm2z^3ivQlRg{a;#(CYlgtx`7c=w zn&19^ryFq0PUCTW>^f%U2?zgux_{+@!Gfw|^$Wjleb2|x`s#mcTU(Qlz<=?n+ZdKa zZ87_zeZynrRDsEjJS$?847^=8F9=$*THQe+Co+KT@fP&}HxZu{F&|$upX*v0%0gKR zk2k3XOuoGDfP`?uG^Zv1ni-jeB3RiLJYZsGV`KRB$|A_OD)A@Ky$qhNelF{r5}E)A CxLrg5 literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_92.png b/docs/sprite_library/sprite_92.png new file mode 100644 index 0000000000000000000000000000000000000000..f5c9d417440ec38a5aaba9a35a5f1557c399c7f0 GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^96-#�(^rK8WyvH~~H(u0VRLp~An$g8%>iC%E+O z2l5$9g8YIR9G=}s19EgcT^vIy;*t{tQW8WG6M9k-P9_V?J@(++WJjx3#d;;@nas^g gn47ton;DoH5;wE%SDG-t7pRZH)78&qol`;+08JYwi~s-t literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_93.png b/docs/sprite_library/sprite_93.png new file mode 100644 index 0000000000000000000000000000000000000000..1506b82a651dc927c4c6e7ea5c4a0f250f29d4f4 GIT binary patch literal 212 zcmeAS@N?(olHy`uVBq!ia0vp^f| z|Nr}6_xRIGpa^40kY6x^!?PP{Ku&?Di(^PdT%z2fKf!p>oZ{c9vG=+AHKsEsZ+oiL9lFe|{LpJLC&+D{u6{1- HoD!MEakt5f^&qAg8k-4|6~z^Xacl7g<_M8mH|4@1v^0 zxT;%DOv3sz@1Mt|0!wyRJ#~ATT;P*`Q0jAzTftmUF9}n{!-MIZnG literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_95.png b/docs/sprite_library/sprite_95.png new file mode 100644 index 0000000000000000000000000000000000000000..38de2db534a49c702f6a4755766292089fcdb033 GIT binary patch literal 191 zcmeAS@N?(olHy`uVBq!ia0vp^{6H+h!VDy@YwwBz2?Y3rxH>pE0GSN^Mq3RP{{Q>G z_0`f5ju kg$GBOhUAL1KUuP`%nFVdQ&MBb@0HxAOuK)l5 literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_97.png b/docs/sprite_library/sprite_97.png new file mode 100644 index 0000000000000000000000000000000000000000..54195cdfab0106552f514ee46b3f32331899383c GIT binary patch literal 182 zcmeAS@N?(olHy`uVBq!ia0vp^5w|4ue5lI#bW|zsx6AS}}N*Gpw8|-ErmJUF*cB?^NcNF8`S45x)QAEQ!#_>5N(D b1shoEuUzHW!yNZk3}m~ftDnm{r-UW|wf;Rm literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_98.png b/docs/sprite_library/sprite_98.png new file mode 100644 index 0000000000000000000000000000000000000000..be6f20f5d4f2d0e2912e2b2b64bebe3a08e7ecf9 GIT binary patch literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^+(0bM!VDz0u)lf?r1%4TLR=jj9Dq!Qext313jhE8 zU;Mqu3dm(F3GxeOaCmkj4af=bba4#fkYzo<$jhW4z zB+Xb7)hV zfjEpML4Lsu4$p3+0XgcPE{-7;amfk1yu1k-(Oa|h4uAju|GjYzkSXV!nUSI6tRc{l Z%E&s2chiZ;C3Qe844$rjF6*2Ung9{UB=`UT literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_9b.png b/docs/sprite_library/sprite_9b.png new file mode 100644 index 0000000000000000000000000000000000000000..58e95023b8119afac6d29c2b5baa968cf75cf791 GIT binary patch literal 149 zcmeAS@N?(olHy`uVBq!ia0vp^VnEEn#0(@G*q%EBDV_kI5LX8W2Ox8+p~Am2M>)hV zfjEpML4Lsu4$p3+0XgQLE{-7;amfk1tW0cdKi7n=er9cO;`;yp_4gYU<&VrhpuoB$ s&CqyQU(zO7LI`8S`#fJQKQy85}Sb4q9e0JID$<^TWy literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_9c.png b/docs/sprite_library/sprite_9c.png new file mode 100644 index 0000000000000000000000000000000000000000..41eeb5b15e8fa22bb9853147de5e77e06e3809a9 GIT binary patch literal 163 zcmeAS@N?(olHy`uVBq!ia0vp^5Ekq`j>Ztb!~|AOHiM3)6#^Qp3^QKHT4v`}zXTe{;OXk;vd$@? F2>^6kF>(L^ literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_9d.png b/docs/sprite_library/sprite_9d.png new file mode 100644 index 0000000000000000000000000000000000000000..583b048262f36d45a82667d351d49fec60e214aa GIT binary patch literal 117 zcmeAS@N?(olHy`uVBq!ia0vp^Y#_|U3?%0+V!sWfcmjMvTpb)7wi+t@Yb^Nx@4v)V zb)YZLiFha3X;zbOgLrLsMz|E$(i#pi-5Gw ev3-*mm#{HR?_{4PkaF`m$OunYKbLh*2~7Yb2`C@{ literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_9f.png b/docs/sprite_library/sprite_9f.png new file mode 100644 index 0000000000000000000000000000000000000000..21aa8e10e609cfd5a42dbe9e4775988ae33fa064 GIT binary patch literal 146 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uk3?!4(jyeG;&H$efR|f}&t%eH!8VkCwq!$7O z8B2ovf*Bm1-ADs+%spKkLpY+769h^V5)yo79XfE}*ntxV6b>GE!k{7{(=Z{C$(hMW q@qhxuL{=dko;eP=k`B2F%nZL@vK!vEb-E8Ug2B_(&t;ucLK6U!W-0#w literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_a0.png b/docs/sprite_library/sprite_a0.png new file mode 100644 index 0000000000000000000000000000000000000000..598bae7b1613f1cb3b740d9aedf16cdddd97f90a GIT binary patch literal 149 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=(3?#Lsx0HZ50X`wF4h{}L=2k<6e`k(zh+hJ6 z7)yfuf*Bm1-ADs+%spKkLn`8u6B2k55)z^h9Z>i>xlyrNmHDxjLSNLO1K)C)n!lK{ pxBXep!}C>GLgJN-goKl%1cR^w_et>#dwZY}44$rjF6*2UngD}aDGdMs literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_a1.png b/docs/sprite_library/sprite_a1.png new file mode 100644 index 0000000000000000000000000000000000000000..6e3ca42fb9d1b7b684855e9fcba93a138bd950c2 GIT binary patch literal 129 zcmeAS@N?(olHy`uVBq!ia0vp^96-#;#0(^hY+LsNDV_kI5LX8W2Ox8+p~Am2M>)hV zfjEpML4Lsu4$p3+0XfQ^E{-7;amfj#2@_@=nDDnn@wKY+H7~Z6sXRHKB_)a_C0rO1 XjbP0l+XkKs=*`9 literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_a2.png b/docs/sprite_library/sprite_a2.png new file mode 100644 index 0000000000000000000000000000000000000000..c3a3c47fc6991a0b7236b7f83749d5aeb0e95a0f GIT binary patch literal 120 zcmeAS@N?(olHy`uVBq!ia0vp^93afZ3?z3ZhDiV^o&cW^R|f|NAakps!oM>|Im9o4 zIE*Dhe!&b5&u*jvIZ~c3jv*Y;$thpcL{=yFd`dgBj(MSm!St)r3|37nZ`JnexC2!& Nc)I$ztaD0e0su1GAISg! literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_a3.png b/docs/sprite_library/sprite_a3.png new file mode 100644 index 0000000000000000000000000000000000000000..0785da8789185985eff79ab048043443a85f0a53 GIT binary patch literal 129 zcmeAS@N?(olHy`uVBq!ia0vp^96-#;#0(^hY+LsNDV_kI5LX8W2Ox8+p~Am2M>)hV zfjEpML4Lsu4$p3+0XfQ^E{-7;amgHs2|r#Q_@LjASkL8Lr{?^^i*4mpo*Zup31)`; X0QSi(8$NXdl{0v{`njxgN@xNAlm#Pu literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_a4.png b/docs/sprite_library/sprite_a4.png new file mode 100644 index 0000000000000000000000000000000000000000..3752edc0336c6836e2462e9b1bc4ea3b3ea0dd58 GIT binary patch literal 129 zcmeAS@N?(olHy`uVBq!ia0vp^96-#;#0(^hY+LsNDV_kI5LX8W2Ox8+p~Am2M>)hV zfjEpML4Lsu4$p3+0XfQ^E{-7;amfOy2`7pYPRu_rVQ-6LmKJlgE_1RSv$F{^vo?dr XT$T%!d_Dhx${9Re{an^LB{Ts5W6mMT literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_a5.png b/docs/sprite_library/sprite_a5.png new file mode 100644 index 0000000000000000000000000000000000000000..31eb337add8ebe9848b16908e21957b468ed4132 GIT binary patch literal 123 zcmeAS@N?(olHy`uVBq!ia0vp^>>$j<3?z5j>~{cCJOMr-t_}_$=2k<6e`k(zh+hJ6 z7)yfuf*Bm1-ADs+WIbIRLn`8u1yU1E6eXOPe_+Da7RM|u=4ejlWDW)$YZkATg|n^! P6*73b`njxgN@xNAoxva# literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_a6.png b/docs/sprite_library/sprite_a6.png new file mode 100644 index 0000000000000000000000000000000000000000..c2747401f8ed8ec022b243b9cbc353e3866d369e GIT binary patch literal 116 zcmeAS@N?(olHy`uVBq!ia0vp^Y#_|U3?%0+V!sWfcmjMvTpb)7fXuCi3jfX=zopr0I@zC_W%F@ literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_a7.png b/docs/sprite_library/sprite_a7.png new file mode 100644 index 0000000000000000000000000000000000000000..7af1b25c127a9b75a062391fee9b6f99efeeed67 GIT binary patch literal 123 zcmeAS@N?(olHy`uVBq!ia0vp^>>$j<3?z5j>~{cCJOMr-t_}_$=2k<6e`k(zh+hJ6 z7)yfuf*Bm1-ADs+WIbIRLn`8u1yU1E6eXOPbzp+zA%&kUjiubo$s7#YZ&{k+PUSEG P6*73b`njxgN@xNAzIq`Z literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_a8.png b/docs/sprite_library/sprite_a8.png new file mode 100644 index 0000000000000000000000000000000000000000..15a23b18f5e8e9b2d35d6a9fc8fe074a1e68ec01 GIT binary patch literal 126 zcmeAS@N?(olHy`uVBq!ia0vp^93afZ3?z3ZhDiV^o&cW^R|f|NAakps!oM>|Im9o4 zIE*Dhe!&b5&u*jvISQUGjv*Cs$pWbfCyEkI%sQYTdg#EcLkD(DZd9D!=qSRVzlZs3 TM}~kWP%VR}tDnm{r-UW|EGZ*B literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_a9.png b/docs/sprite_library/sprite_a9.png new file mode 100644 index 0000000000000000000000000000000000000000..2c2ab4bb20497636f8284121c7ebec8c5c5188c9 GIT binary patch literal 123 zcmeAS@N?(olHy`uVBq!ia0vp^Y(UJ;#0(_E{#~g9Qak}ZA+8P%4nXEsLxq25j&g`! z0&y5ig8YIR9G=}s19D_NT^vIy;*vQM6HcTgoG41@C`u5Rd*DD>W1}GhgK7ZtxoV~z R^MMK(JYD@<);T3K0RZ#?ADlG+Z$|HQ~l|w=;jT57iyoe r-*??^WKf{X;Ki}Qfg_ErE&n=$lPFKOxK3X%P(OpGtDnm{r-UW|jC3r$ literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_ab.png b/docs/sprite_library/sprite_ab.png new file mode 100644 index 0000000000000000000000000000000000000000..260afecc04bb2ebbe8762d202b38abc56524d20a GIT binary patch literal 637 zcmV-@0)qXCP)d295CzcT!V$LQa4!&2C$M~h6m-9Z24Lw*+%lEu3kc$|cLtFe2V|HL0s(pYpCjAw zb>3IF!EJ!%ta0C$by?%`{uK~G1H^mY*A)3$o>6Hqh(zFQXeLE>CfZ_hpH3EnzcF z<2X*!`E^Juz>qk&lybw>PU;;(WB~~dWi>R$p)*{sKmNH0mvU_k8f!-9aEQV;;j)@? z77>lJ&S~aj%Gt=Oq{5;epk@TE01>TivDR_|Hy90{TuxCMv^<0cp*0A<SEkSIwabXTxCUc?KK#$OteDmL`?!Sr>z|>^ov`mFy?^WJ zm8ig}&Jxe}2fa|vnF?19;(s3xHx>zDBXMD><1ZZF9!zzd=y5zzJ1#C^o{AxmxJ+)f zhb%5bG}rt)A5Nipcn~?_H3KnjqSS0BF5iOa*5t6U6Y)LFb^BE|do0)D; zzco*pU8A8$5SELllzd~0lWI*t_-t-Xvu+l&1N Xe3Ite6KslA00000NkvXXu0mjf+eRy6 literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_ac.png b/docs/sprite_library/sprite_ac.png new file mode 100644 index 0000000000000000000000000000000000000000..00d87f84659e4c7c171ae835b184b3cbf5436e6b GIT binary patch literal 248 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaP3?%1DUd;wlJOMr-t_}_kTMZTV>|^-<@4pZ? z^BSNyV@Z%-FoVOh8)-n!EKe855Dr<^21CAs0Ss;S^?Ob_y^z%Lb-AGx^5&8w{}Z;m zXVnC49hRoEx9(QpwEnTExHfyuQ$s*?v|^-<@4pZ? z^BSNyV@Z%-FoVOh8)-n!Y)==*5RT~NgoK2W1O`Sk15vG529?Ng*Jg{P-(hkLK|%l1 zjA9ZKI{ul?RTTEv%k}Us`;suO4SQ1(I-*V$WOGfJRb+BGlWD@Fszcve6mwQ)Cp;2k zyBNyotEDaJ;(YXqQ;?)fFstxU7l{aGA=bTm1{;j*f?l#^@!VdrQIjDfcmG>w1=Uqc tmok;GdAlv+xDa>jzyyW~#w?903`^TJt(|_x9|Ag)!PC{xWt~$(697#mQeprA literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_ae.png b/docs/sprite_library/sprite_ae.png new file mode 100644 index 0000000000000000000000000000000000000000..9a3d2d283f23577ab13e7612833de53bb4d32216 GIT binary patch literal 247 zcmeAS@N?(olHy`uVBq!ia0vp^;y^6H#0(^Jg!-NVDV_kI5LX8WhpmPRd-gH>|My>r zn|TdToUtUxFPOpM*^M+HXQro%V+codaza7^ZvsQ(3QazTNrite2?%V>`V^vYROHoH zmLtgm*({!2ejAxqUge5WW>``be6yjcC(!(pT0=mdKI;Vst00Wm)KmY&$ literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_af.png b/docs/sprite_library/sprite_af.png new file mode 100644 index 0000000000000000000000000000000000000000..3040a44f9e82e447f28962cd7826a61850be841b GIT binary patch literal 246 zcmeAS@N?(olHy`uVBq!ia0vp^5|My>r zn|TdToUtUxFPOpM*^M+HXNISXV@O3D@9~X%2OSt19`4Vq4(@bhKXY%fZ`F~q=IA3U zn0;@&Ej#j7wrY}@(B{tP32TokOSml8mp`zD`N4E4j^kggo4#JW({Nq!oM=bYMLWmU z5@~W9zC5!1Fo%bW>4?VW7wy`dH_yy|`{H)wqh~+V`1Hk=^IX~1y&~k6w?O=4uht(o uH}?p9Kjl@hPJXde*wj3SY~Ba2IsQ$H)D-U8rLF^XBZH@_pUXO@geCwUM`FML literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_b0.png b/docs/sprite_library/sprite_b0.png new file mode 100644 index 0000000000000000000000000000000000000000..d3b23eca0ef1fb801b3401543b4b6362c9b8083e GIT binary patch literal 181 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!93?!50ihlx9JOMr-t_}_k3#88c%RBS`-~Z%& z#)Uv}#*!evUenU&u)Fl{J4bow{%aQwWONisrmNVCnGXXZJzn@P|Q3#xAsFjXG_jJ dH#3}x!S03h9nbqcKY&&+c)I$ztaD0e0sv!|MT`Id literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_b1.png b/docs/sprite_library/sprite_b1.png new file mode 100644 index 0000000000000000000000000000000000000000..6b60560d6df40040d3dda5d2ed8de76a6c97e691 GIT binary patch literal 185 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!93?!50ihlx9JOMr-t_}_k3#88c%RBS`-~Z%& z#)Uv}#*!evUfqq8KlFz;gC&!l;V-_>*Vi_Ns>N0bl z{WG5#*rfkDbl`BxnSyrBAHL$j|7OSf_(bwD%w8^V@939vO+bSfJYD@<);T3K0RUfe BHr4Qak}ZA+8P%4hy8t{L4G@|KI=Q zea3}AamJD$zhDN3XE)M-oLQbOjv*Cs$qbDFYz7I@FBz7Z9$wb)QnSb3QDic+vuek) zl!OVa7r8jB#9Yh`HiR{09$TTVFe@_WuaLmm@?Y^B6QT}m|C!1#Vcx;nySFfAM9o+p za)V3ZZPv?Y5`P&pR;f0x$l#k`CYVqVxXW4<$S00001bW%=J06^y0W&i*H!AV3xR2Ufrz@bV5VE_Q&@Bd}TE+`8F zVfl(??f)qji@!srm=+ckjeCGg=9_3SDeSVofqMjlk6@6!!Ga(_xB!g)63|_?Am;E5 ziewJ@%t4BA$#tH-{gT>bu|NJ*Cmh z+t)5V>u!?6O10IEACeoxo%%ElG1!DX6FfdyDsbG!g#t$@o~O{T%_4_}l6Dpv4ycMW lrJ|Cj{7{l#}Elg)&m>84m&U~Tqtk!@?YVyL1C_lUU#uo$?l^Y z1vZ41oO|{_{|Wb(wvzG-OXoj&!@gt37ygn!1J)T#GK?+Z4ek5+X5`c}yfaD&Vp!Oq z@nyk6_Ex!7uVX8kcUau%n=r5LJ8Nj=pIhY~V*1l1P0q^~*vMUbA3pP0+V*|(`KL_I zGqaa{5oXENtW|$qJJI0d&Pc9Ele}pu=|Nd9uiUzF>Ex{`D+=ZGR~UAi3NS0M{$Lkc WuUzUa5fuq^KZB>MpUXO@geCymD{6@V literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_b6.png b/docs/sprite_library/sprite_b6.png new file mode 100644 index 0000000000000000000000000000000000000000..7bc1adf96738184278ec1bf1354f643ebedac1b4 GIT binary patch literal 184 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!93?!50ihlx9JOMr-t_}_k3#88c%RBS`-~Z%& z#)Uv}#*!evUwLe0JSk7{(*4+{{I(Icsc+800DGTPE!Ct=GbNc00IL^L_t(|UUkyJ&Z8g{1>lRy z{vHzdK+?U5oxMqMFOIzbs|=T7{ps(JrfK{294`v}Sp7WD6Wy*X>)$)r-nl!{OG*?% zfvu%sBVAMLcT7@c4M&A`#%al+rG-9`j6P5r(ii<%LO%~-8Nv|0Lgkldem^!^T3SMw z=8)glbcB|M@SqdeSsn*e_q9EJCF3W=R$Tp!j# zVIbw5H?E5Eppf#G!Wx!dJ-m00Pcj_Veog6Ehzj8(XLxw^&vfKDXJw-IsEtsbX)d>y zdKoIyJEkLhdz}vgjAsR@i$GT;hjJ1iLLt11r+%J#N!7oTxRK&MQ@tM12%$0Job)9S3d?ygWOn5$l_OI-DQ${w7Kn@o{{VDpmm-#{ z&;yq!blxifV`Q@A;|;NN0oz3Ydt1nHnXe&nAUSGRsVc;{A#fqSrQ1TZh2Nw~itY$P zqvKX|6sp;lIxV4FdRPSzshcU%uE0WK+|u?=G5xC4owY(br=ifsT>Wkdt|cNX_Snf2 zxUt2ofva}H*|3X4?CIR*P6;Aed&{in5?(P{r5QN(r? zj<%&!w2NtP3UPhXXbADM-^kIl5B+n{Mrm))>pyDNS|W-cQYQca002ovPDHLkV1kp- B78(Em literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_b8.png b/docs/sprite_library/sprite_b8.png new file mode 100644 index 0000000000000000000000000000000000000000..69764daa058302901e5d6206d40f2263247547e4 GIT binary patch literal 615 zcmV-t0+{`YP)wLe0JSk7{(*4+{{I(Icsc+800DGTPE!Ct=GbNc00IL^L_t(|UUkyJ&Z8g{1>lRy z{vHzdK+?U5oxMqMFOIzbs|=T7{ps(JrfK{294`v}Sp7WD6Wy*X>)$)r-nl!{OG*?% zfvu%sBVAMLcT7@c4M&A`#%al+rG-9`j6P5r(ii<%LO%~-8Nv|0Lgkldem^!^T3SMw z=8)glbcB|M@SqdeSsn*e_q9EJCF3W=R$Tp!j# zVIbw5H?E5Eppf#G!Wx!dJ-m00Pcj_Veog6Ehzj8(XLxw^&vfKDXJw-IsEtsbX)d>y zdKoIyJEkLhdz}vgjAsR@i$GT;hjJ1iLLt11r+%J#N!7oTxRK&MQ@tM12%$0Job)9S3d?ygWOn5$l_OI-DQ${w7Kn@o{{VDpmm-#{ z&;yq!blxifV`Q@A;|;NN0oz3Ydt1nHnXe&nAUSGRsVc;{A#fqSrQ1TZh2Nw~itY$P zqvKX|6sp;lIxV4FdRPSzshcU%uE0WK+|u?=G5xC4owY(br=ifsT>Wkdt|cNX_Snf2 zxUt2ofva}H*|3X4?CIR*P6;Aed&{in5?(P{r5QN(r? zj<%&!w2NtP3UPhXXbADM-^kIl5B+n{Mrm))>pyDNS|W-cQYQca002ovPDHLkV1kp- B78(Em literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_b9.png b/docs/sprite_library/sprite_b9.png new file mode 100644 index 0000000000000000000000000000000000000000..1ce3fac90ed2e61c0991974cc0ee62e637ee6701 GIT binary patch literal 560 zcmV-00?+-4P)wLe0JSk7{(*4+{{I(Icsc+800DGTPE!Ct=GbNc00GQNL_t(|UUkyJuB0Fk1<+d} z`?@CXg{1c;^z2O@?$we1|CJG2)8i9w3=0ocR1|yz6GG@w%qHXC5=@`E64@ao#CUAG zq@G}psu91EtM=3@?lGYsfN{8jNywwuNOk%}e}-s69fvyB@hWD2^WN5V!f>S_#y-St z9n%`GRL38@H_8e9g-i(fxeL*6nx}vN1PB#p#9wo<~QDSA_lZ!T@v| zeah^tzWd$}kd0R%0GY`OM+|y^1h^AFje8S%ldF#H$lg!^v(a%L&@xjylX@P6BQqO? zu>F8N|9d*AadOam*wn6w+37Z7^`$tR7efe$J7~W$M^B_la1Rw|JoOHwDjK|qZ$w|I z3yF5;~v`=Ko`9ac5mT6`?>r*?nU-$>zwLe0JSk7{(*4+{{I(Icsc+800DGTPE!Ct=GbNc00JOML_t(|UVW3zuHrBZh3z)k z+mYBek=REHv#)gBePEjRf0cWc3q_Euhw6O7)C_thw za4%s6+Ol3aAg^&HZ-86Y09`Fz+`o!<+5zEI`0Prl95P%8v0c+^ar={A%U=3x5%^u* e>vvxlf31JomRFZ@LHXza0000#j+S3j3^P6=P%cPEa$moetEvZhP1Uj46hj&INk``J-Y4B1T=}k)78&qol`;+0DW^a Ao&W#< literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_bd.png b/docs/sprite_library/sprite_bd.png new file mode 100644 index 0000000000000000000000000000000000000000..2c2998961323cfa8ce5d4aba0a15c640b852269e GIT binary patch literal 361 zcmV-v0ha!WP)_8*~a_AyIGue!J#fe z_zQx|)Qdx`5SpIAd4_`hP2eFbOvKrUCNGV!QB95|KcK2Ca67juwG-gdOyUrYwgC-i zuXIY0000OP)t-sKtMnM z002Tj|7K=pwJ{*~*roo=#sB{Q$&Wd@00001bW%=J06^y0W&i*H`$aW3R_dJH28m2m@vIC#qr*sM>p zLhkc>6R(*nDl}^%6OakXN=c{O$t+BOyN5?`eqd&bE$7YGvnMireI@;$XdQ8M0NwUr zwM4tu%g$RwxTSfTn#j8lresxg+<{qQnPuD)3Y(}^I7uo<;z>Y;L7ON`z`sJ}bGV?= zRdKs0aGc>d4Mc<7MPC9f(S<%GWTl5fj!toRDqc>nLx?rr3YkzCcI~k)& z&*h_vP;^e|ul;usITXUNU%31Vbg^=|)(%nu7RvksjaD@C960t>00000NkvXXu0mjf DHa(WE literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_bf.png b/docs/sprite_library/sprite_bf.png new file mode 100644 index 0000000000000000000000000000000000000000..934791ee6a60f2efefebff9b00fdda6876828b84 GIT binary patch literal 117 zcmeAS@N?(olHy`uVBq!ia0vp^tRT$93?x4=o+|@VJOMr-t_}_kTMZTdH5UB;_g~_w zI#8H_u_VYZn8D%MjWi%f+|$J|q#`akAu*vKAt4|oVM0p61E6k}M+~QqccmtPG@iNm?%(?=)s%d!JRN6Ct_E)H#0(@ovUsNgDb4_&5LX8WhpmPR{~8OrucQ|O z1sO|%{DK)Ap4~_Ta%4PR978JNk`odc6g?G+5>6B)PT)x>NKHs6U|wLe0JSk7{(*4+{{PF4=|RqtH!>+jU+PO0DG zc@armJJGn;0*iH>@Q5Y*_-TKYN_*&+^Y*Eqj#7oketpj~@F&KrS=pGG`j^(%eqAtM zcpqutk|(9GF4%xY*%QQ_Y94qE-O2$%i8Pss?a7h%10`S z#1HdnDgCh=Ac4gv128&#=cVi^qRUSM{qT(0_%zm`n*L$&0J{^Z@fP68T0y z0HZQh^*mZXJl0tN1Pshv<#)6ZP_MTiizT}OLn)AV*1Z$adX@h;|06ab}gpFJE4r6>QcMLVN*eiyB!KZ<1#CO z2#Lv1g}>V{)%nPsQFanec13;ve(PX{l7ZCjBy_=)=UFyR*~)Hrn32ly^dwP4`>vP| zKaD8br)RjdRX@=_Xt?m0pKBu%;gXyIk@1c#`CsGvg|B0~_YyLe&0T+OKIhH5{6Fzz iDPy5*oHWt?dcZ#g^hVYamT_MI0000bTbr1%4TLR=jj92opL82XJie?GI- zP=R|}sW4EAu_VYZn8D%MjWi&~+|$J|ghQ6~0Ha`o0SChY`E-T|rbNyw|L)o}a4Rs# xEiloTXb^U|{J6@od2W%D=f@G0s%fDt_}_k4E`Jp{YINVpV?}t zz`dEal|A`i<>ob<>F!ew#V0t&ap( Reg>M#;OXk;vd$@?2>{7?Hktqc literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_c9.png b/docs/sprite_library/sprite_c9.png new file mode 100644 index 0000000000000000000000000000000000000000..3158a71e58c83630e9c224c39dbfefb7b5d174bf GIT binary patch literal 176 zcmeAS@N?(olHy`uVBq!ia0vp^qCm{g!VDyTmduO*aRPioTpb)782mXH`i(Y!KC{(O zfqPr2Fi?uIB*-tA!Qt7BG$6;{)5S4_LzeX*qv&A;0oDx%W;58CURW%=p{YLKn?aUA zoI#C&jUoR)h?8EJHu$=ER}GHyZ(d} W>zR~k%R7NaGkCiCxvX6yr literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_ca.png b/docs/sprite_library/sprite_ca.png new file mode 100644 index 0000000000000000000000000000000000000000..750dfa01cdd5d370889feb78bac7ad946d411c87 GIT binary patch literal 200 zcmeAS@N?(olHy`uVBq!ia0vp^3P3Ez#0(_wn3r7#Qak}ZA+8P%4h;Sr4E;u%KcDG+ zntci=&R7!U7tG-B>_!@pljZ5+7*Y|J+`yzK@tAp+1JjhW)=NB$0$n#nPo;A;T=G`4 zYgTe~DWM4fbj?8` literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_cb.png b/docs/sprite_library/sprite_cb.png new file mode 100644 index 0000000000000000000000000000000000000000..44485c06a27774b513d2dfcb00d7db77ed2dc291 GIT binary patch literal 215 zcmeAS@N?(olHy`uVBq!ia0vp^20$#t!VDzedpL9gDgFST5LX8W2L^u*hJK^XpU-SH zRN&rLDh!ljED7=pW^j0RBMrzY^K@|xsff$HaFO@00f*~F2kVAH@rLKDN}CsU_c-|C!Df>!2fs1J*8gDmR>Spv+Zv&pKo>B0 My85}Sb4q9e0NBz^pa1{> literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_cc.png b/docs/sprite_library/sprite_cc.png new file mode 100644 index 0000000000000000000000000000000000000000..15e126e33237a8a891bab1d87600ea4ba198df1d GIT binary patch literal 253 zcmeAS@N?(olHy`uVBq!ia0vp^20$#�(_wOk4IHNbv;tgt$65I57BgF!UR3{(PqQ zY4$0gIAckWUoeBivm0qZ&U{Z7#}J9j$v+M>uh($ literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_ce.png b/docs/sprite_library/sprite_ce.png new file mode 100644 index 0000000000000000000000000000000000000000..c465ed8dbef1911d457129018aea8c7661b49e0f GIT binary patch literal 249 zcmeAS@N?(olHy`uVBq!ia0vp^20$#�(_wOk4IHNbv;tgt$65I57BgF!UR3{(PqQ zY4$0gIAckWUoeBivm0qZ&TLN?#}J9B$s7p|Olx>g*yyJ49hOSr`^=sqCq8Q%x5u6d zoE|c6DSVr?C*AeoTT>ucy`GtQj`~8*BJnBP^-I|2954B>RQSeOg{0}t*Fttg<_3!x zJ)E;HT=>R=Icxs=3(EZczu!r4&eiqP4%JSax>f55!<4fVC8Z>pCI<@g^70lp6g^RE wX;b>yV0eY`VB-!h?e>%GGq*F(RAx|Py!1<1w5oi5FVL9`p00i_>zopr00+BSMgRZ+ literal 0 HcmV?d00001 diff --git a/docs/sprite_library/sprite_cf.png b/docs/sprite_library/sprite_cf.png new file mode 100644 index 0000000000000000000000000000000000000000..2903b06e9af59c5b228f61e32bf87423a57f4e43 GIT binary patch literal 156 zcmeAS@N?(olHy`uVBq!ia0vp^azM<%!VDy3CI*QEDgFST5LX8!NIc#xQug`njxgN@xNASwu53 literal 0 HcmV?d00001 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