;; NOTE: automatically generated by the build system. .include "../config/generated.s" .segment "HEADER" ;; Bytes 0-3: NES\0 magic header. .byte 'N', 'E', 'S', $1A ;; Standard 32KB + 8KB NROM cartridge. .byte $02, $01 ;; Horizontal mirroring, no battery. .byte $00 ;; We use the NES 2.0 header mainly to differentiate between NTSC vs PAL in ;; a way that emulators won't ignore it. .byte $08 ;; Blanked out stuff. .res $04, $00 ;; NTSC vs PAL .ifdef PAL .byte $01 .else .byte $00 .endif ;; Blanked out stuff. .res $03, $00 .segment "CODE" .include "../include/apu.s" .include "../include/oam.s" .include "../include/ppu.s" .include "../include/asm.s" .include "../include/joypad.s" .include "../include/globals.s" .include "../include/debug.s" .include "assets.s" .include "background.s" .include "prng.s" .include "explosions.s" .include "items.s" .include "player.s" .include "enemies.s" .include "bullets.s" .include "title.s" .include "over.s" .include "driver.s" .include "interrupts.s" ;; Pretty standard reset function, nothing crazy. .proc reset ;; Disable interrupts and decimal mode. sei cld ;; Disable APU frame counter. ldx #$40 stx APU::m_frame_counter ;; Setup the stack. ldx #$FF txs ;; Disable NMIs and the APU's DMC. inx stx PPU::m_control stx PPU::m_mask stx APU::m_dmc ;; First PPU wait. bit PPU::m_status @vblankwait1: bit PPU::m_status bpl @vblankwait1 ;; Initialize the counter for frame drops before any NMIs can come in. .ifdef PARTIAL lda #0 sta Debug::zp_frame_drops .endif ;; Reset all sprites by simply moving the Y coordinate out of screen. lda #$EF ldx #0 @sprite_reset_loop: sta OAM::m_sprites, x inx inx inx inx bne @sprite_reset_loop ;; DMA setup for sprite reset. lda #$00 sta OAM::m_address lda #$02 sta OAM::m_dma ;; Second PPU wait. After that the PPU is stable. @vblankwait2: bit PPU::m_status bpl @vblankwait2 ;; NOTE: palettes are not initialized here as it's going to be one of the ;; first things done on `main` code. __fallthrough__ main .endproc .proc main ;; TODO: high score initialization has to happen here. @init: ;; Disable the PPU and zero out variables which shadow PPU registers. lda #0 sta PPU::m_mask sta PPU::zp_mask sta PPU::zp_control ;; Initialize other global variables which the rest of the game assume to ;; have zero as their initial values. Note that it's important to have these ;; variables defined before sta Globals::zp_flags sta Globals::zp_multiplayer sta Joypad::zp_buttons sta Joypad::zp_prev sta Player::zp_state sta Items::zp_state sta Prng::zp_last_rand ;; Initialize the level. We allow the build system to pass its own value for ;; this in `LEVEL`, just in case we want to debug the enemy of a specific ;; level. .ifdef LEVEL lda #LEVEL sta Globals::zp_level and #%00000111 sta Globals::zp_level_kind .else sta Globals::zp_level sta Globals::zp_level_kind .endif ;; Initialize the assets for the game. If the first sprite is not ;; initialized to zero, then we are not coming from reset() but from ;; starting after a "Game over". In this case skip this initialization. lda OAM::m_address bne @skip_assets jsr Assets::init @skip_assets: ;; Initialize some variables from the "Game Over" side of the game. jsr Over::init ;; Initialize variables from the game's driver that need to be set before ;; NMIs start ticking. jsr Driver::init_before_nmi ;; Initialize some PAL-specific constants. .ifdef PAL lda #0 sta Driver::zp_pal_counter .endif ;; We shadow the PPU control register in memory. Otherwise we go into the ;; title as expected. jsr Title::init lda #%10001000 sta PPU::zp_control sta PPU::m_control cli ;; Enable back the PPU lda #%00011110 sta PPU::zp_mask sta PPU::m_mask @main_game_loop: ;; Select the joypad from the active player and read it. lda Globals::zp_multiplayer and #$01 tax READ_JOYPAD_X lda Globals::zp_flags and #%00000011 beq @title_screen cmp #1 beq @game_screen jmp @over @title_screen: ;; If we are in a transitioning state, avoid the update from the title. lda Globals::zp_flags and #%00000100 bne @do_switch jsr Title::update beq @set_flags @do_switch: ;; Start was pressed by the player, switch to the main game. jsr Driver::switch jmp @set_flags @game_screen: jsr Driver::update __fallthrough__ @set_flags @set_flags: lda #%10000000 ora Globals::zp_flags sta Globals::zp_flags @wait_for_render: bit Globals::zp_flags bmi @wait_for_render ;; Rendering is done, we can perform another iteration of the loop! jmp @main_game_loop @over: ;; Display the "Game over" screen if it hasn't been displayed yet. After ;; that, if we detect that the user wants to go back to the title screen we ;; go back to @init to initialize everything again except for the score ;; which should be preserved. Otherwise we go back to the main game loop, ;; which effectively means to just sit and wait until for the right player ;; input. jsr Over::handle pha ;; Wait for the PPU to render the screen. @set_flags_over: lda #%10000000 ora Globals::zp_flags sta Globals::zp_flags @wait_for_render_over: bit Globals::zp_flags bmi @wait_for_render_over ;; Can the player start over? pla beq @main_game_loop ;; We will skip the initialization of the assets so to avoid writing into ;; the first nametable when it's just fine. That being said, we still need ;; to prepare palettes for the title screen. Do it now before starting over. jsr Assets::prepare_for_title_screen jmp @init .endproc .segment "VECTORS" .addr nmi, reset, irq