/
main.s
400 lines (339 loc) · 8.37 KB
/
main.s
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
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
.include "globals.inc"
.import read_gamepad
.import prepare_blank_sprites, prepare_game_sprites, ppu_set_sprites
.import ppu_set_title_bg, ppu_set_game_bg , ppu_set_ending_bg
.import ppu_set_results_bg, ppu_set_timer_display
.import move_player, reset_player_position
.import move_enemies
.import load_level
.import ppu_set_palette
.import FamiToneInit
.import FamiToneSfxInit
.import FamiToneMusicPlay
.import FamiToneUpdate
.import FamiToneSfxPlay
.importzp FT_SFX_CH0, FT_SFX_CH1
.import ralph4_music_music_data
.import sounds
.export main, nmi_handler, irq_handler
.segment "ZEROPAGE"
nmi_counter: .res 1 ; Spin on this until vblank.
.segment "CODE"
; X should be set to 0.
; Carry should be set.
; Guarantees that carry will be set if not jumping to doneLabel.
.macro increment_digit var, modulo, doneLabel
lda var
adc #0
sta var
cmp #modulo
bcc doneLabel
stx var
.endmacro
.proc nmi_handler
; Push registers to the stack.
php
pha
txa
pha
tya
pha
; Rememeber: none of the functions called in the nmi handler
; can clobber zero page locals.
lda do_draw
cmp #1
bcc dontDraw
lda using_timer
cmp #1
bcc dontDisplayTimer
jsr ppu_set_timer_display
dontDisplayTimer:
jsr ppu_set_palette
jsr ppu_set_sprites
; Update PPU registers.
lda PPUSTATUS ; Clear the vblank flag before writing PPUCTRL.
lda #PPUCTRL_BG_PT_1000 | PPUCTRL_8X16_SPR | PPUCTRL_NMI_ON
sta PPUCTRL
lda #$00
sta PPUSCROLL
sta PPUSCROLL
lda #PPUMASK_SPR_ON | PPUMASK_BG_ON | PPUMASK_NO_BG_CLIP
sta PPUMASK
dontDraw:
jsr FamiToneUpdate
inc nmi_counter
lda using_timer
cmp #1
bcc doneUpdatingGameTimer
inc frame_counter
lda framerate_60
bne fps60
lda #50
jmp doCmp
fps60:
lda #60
doCmp:
cmp frame_counter
bne doneUpdatingGameTimer
; Increment the timer by 1 second.
ldx #0 ; X has to be 0 before using increment_digit.
stx frame_counter
sec ; Carry has to be set before using increment_digit.
increment_digit seconds_digits+1, 10, doneUpdatingGameTimer
increment_digit seconds_digits+0, 6, doneUpdatingGameTimer
increment_digit minutes_digits+1, 10, doneUpdatingGameTimer
increment_digit minutes_digits+0, 6, doneUpdatingGameTimer
last_i = ::NUM_HOURS_DIGITS-1
.repeat ::NUM_HOURS_DIGITS, i
increment_digit hours_digits+last_i-i, 10, doneUpdatingGameTimer
.endrepeat
doneUpdatingGameTimer:
; Restore registers and return.
pla
tay
pla
tax
pla
plp
rti
.endproc
.proc irq_handler
rti
.endproc
.macro wait_for_nmi frames_to_wait
.local nmiLoop
lda nmi_counter
.if frames_to_wait > 1
clc
adc #frames_to_wait-1
nmiLoop:
cmp nmi_counter
bne nmiLoop
.else
nmiLoop:
cmp nmi_counter
beq nmiLoop
.endif
.endmacro
.proc main
titleScreen:
lda #0
sta do_draw
sta using_timer
sta buttons_pressed
sta buttons_held
sta palette_dim
; Set the palette while we're still in the safe part of vblank.
lda #INTRO_BG_COLOR
sta palette_bg_color
jsr ppu_set_palette
; Clear sprites.
jsr prepare_blank_sprites
jsr ppu_set_sprites
; Load the title screen and enable NMI.
jsr ppu_set_title_bg
lda PPUSTATUS ; Clear the vblank flag before writing PPUCTRL.
lda #PPUCTRL_SPR_PT_1000 | PPUCTRL_8X16_SPR | PPUCTRL_NMI_ON
sta PPUCTRL
; Init FamiTone2 while waiting for the next frame.
lda framerate_60 ; A = 0 for PAL. A > 0 for NTSC.
ldx #<ralph4_music_music_data
ldy #>ralph4_music_music_data
jsr FamiToneInit
ldx #<sounds
ldy #>sounds
jsr FamiToneSfxInit
lda #1
sta do_draw
titleScreenLoop:
jsr read_gamepad ; Read the gamepad so that we can check for the
lda buttons_pressed ; start button, signifying the start of the game.
and #BUTTON_START
bne startGame
wait_for_nmi
jmp titleScreenLoop
startGame:
lda #0
ldx #FT_SFX_CH0
jsr FamiToneSfxPlay
; Flash the screen.
lda #$06
sta palette_bg_color
wait_for_nmi 3
lda #$27
sta palette_bg_color
wait_for_nmi 10
lda #$0D
sta palette_bg_color
wait_for_nmi 5
lda #$06
sta palette_bg_color
wait_for_nmi 10
jsr fade_out
wait_for_nmi 10
lda #1
jsr FamiToneMusicPlay
lda #1
sta using_timer
lda #GAME_BG_COLOR
sta palette_bg_color
lda #0
sta animation_ticks
; Set digit counters to 0
.repeat ::NUM_DEATHS_DIGITS, i
sta deaths_digits+i
.endrepeat
.repeat ::NUM_SECONDS_DIGITS, i
sta seconds_digits+i
.endrepeat
.repeat ::NUM_MINUTES_DIGITS, i
sta minutes_digits+i
.endrepeat
.repeat ::NUM_HOURS_DIGITS, i
sta hours_digits+i
.endrepeat
; Initialize 'current_level' and load the first level.
; A should be 0.
sta current_level
jsr load_level
jsr ppu_set_game_bg
jsr fade_in
gameLoop:
; Reset 'game_event_flags' at the start of every frame.
lda #0
sta game_event_flags
inc movement_ticks
jsr read_gamepad
jsr move_player
jsr move_enemies
jsr prepare_game_sprites
; Check 'game_event_flags'.
lda game_event_flags
and #EVENT_KILL_PLAYER
beq notKillingPlayer
jsr do_kill_player
jmp gameLoop
notKillingPlayer:
lda game_event_flags
and #EVENT_ADVANCE_LEVEL
beq notAdvancingLevel
; Check if we've completed the last level.
lda current_level
cmp #num_levels - 1
beq endingScreen ; Go to the ending screen if we did.
jsr do_advance_level ; Otherwise go to the next level.
jmp gameLoop
notAdvancingLevel:
; No events, so nothing fancy is going on with this frame.
wait_for_nmi
jmp gameLoop
endingScreen:
wait_for_nmi
jsr fade_out
lda #0
jsr FamiToneMusicPlay
lda #0 ; Disable the timer so it doesn't increment.
sta using_timer
lda #ENDING_BG_COLOR
sta palette_bg_color
jsr prepare_blank_sprites
jsr ppu_set_sprites
jsr ppu_set_ending_bg
jsr fade_in
endingScreenLoop:
jsr read_gamepad
lda buttons_pressed
and #BUTTON_START
bne resultsScreen
wait_for_nmi
jmp endingScreenLoop
resultsScreen:
wait_for_nmi
jsr fade_out
jsr ppu_set_results_bg
jsr fade_in
resultsScreenLoop:
jsr read_gamepad
lda buttons_pressed
and #BUTTON_START
bne restartGame
wait_for_nmi
jmp resultsScreenLoop
restartGame:
wait_for_nmi
jmp startGame
.endproc
; Call at the start of vblank. Returns in vblank.
; Clobbers A. Probably clobbers X, Y.
.proc fade_out
.repeat 4, i
lda #$10*(i+1)
sta palette_dim
wait_for_nmi 4
.endrepeat
lda #0
sta PPUMASK ; Disable rendering; force vblank.
sta do_draw
rts
.endproc
; Call at the start of vblank. Returns in vblank.
; Clobbers A, X. Probably Y.
.proc fade_in
lda #1
sta do_draw
.repeat 5, i
lda #$10*(4-i)
sta palette_dim
wait_for_nmi 3
.endrepeat
rts
.endproc
.proc do_kill_player
lda #2
ldx #FT_SFX_CH0
jsr FamiToneSfxPlay
wait_for_nmi
lda #GAME_DEATH_BG_COLOR
sta palette_bg_color
; Increment the deaths counter.
; If it gets to 10000 then it overflows to 0. Hurrah!
ldx #0 ; X has to be 0 before using increment_digit.
sec ; Carry has to be set before using increment_digit.
last_i = ::NUM_DEATHS_DIGITS-1
.repeat ::NUM_DEATHS_DIGITS, i
increment_digit deaths_digits+last_i-i, 10, doneIncrementDeaths
.endrepeat
doneIncrementDeaths:
; Reset the player and gem positions now, but don't update their sprites.
jsr reset_player_position
lda gem_starting_visible
sta gem_visible
wait_for_nmi 20
lda #GAME_BG_COLOR
sta palette_bg_color
rts
.endproc
.proc do_advance_level
lda #3
ldx #FT_SFX_CH0
jsr FamiToneSfxPlay
wait_for_nmi
jsr fade_out
lda #0
sta PPUMASK ; Disable rendering; force vblank.
sta do_draw
inc current_level
lda current_level
jsr load_level
lda #0
sta movement_ticks
jsr move_enemies
jsr ppu_set_game_bg
jsr prepare_game_sprites
wait_for_nmi 8
jsr fade_in
rts
.endproc
.segment "CHR"
.incbin "obj/nes/sprites16.chr"
.incbin "obj/nes/bg.chr"