aboutsummaryrefslogtreecommitdiff
path: root/src/jetpac.s
blob: d2ed90bf78f4c98655a07852215adcf28d80d81b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
;; 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 "player.s"
.include "enemies.s"
.include "bullets.s"
.include "items.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

    ;; 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
    sta Globals::zp_tmp0

    ;; 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?
    lda Globals::zp_tmp0
    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