-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.asm
658 lines (587 loc) · 15.5 KB
/
main.asm
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
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
include "lib/gbhw.inc" ; hardware descriptions
include "lib/debug.inc" ; debug instructions for bgb
include "src/optim.inc" ; optimized instruction aliases
include "src/interrupts.asm"
section "Org $100",ROM0[$100]
nop
jp begin
ROM_HEADER ROM_MBC1_RAM_BAT, ROM_SIZE_32KBYTE, RAM_SIZE_8KBYTE
include "lib/memory.asm"
include "src/music.asm"
;------------------------,
; Configurable constants ;
;________________________'
TWO_TILE_ALIGN equ 0 ; whether to align tiles so each new one starts on even-numbered tiles (useful for 8x16 sprites)
LEVEL_WIDTH equ 40
LEVEL_HEIGHT equ 18
CHARACTER_HEIGHT equ 4
COLLISION_DETECTION equ 1 ; whether to enable collision detection with the environment (bounds checking is always performed)
;-------,
; Tiles ;
;_______'
TILE_NUM set 0
; label name, number of frames, tiles per frame
registertiles: macro
\1Frames equ (\2)
\1TilesPerFrame equ (\3)
\1BeginIndex equ TILE_NUM
TILE_NUM set TILE_NUM + \1Frames * \1TilesPerFrame
if TWO_TILE_ALIGN
TILE_NUM set TILE_NUM + (TILE_NUM % 2)
endc
endm
Tileset: incbin "obj/tileset.2bpp"
registertiles Tileset,$a1,1
; the tile indices of all the tiles you should be able to walk on
nonlava:
db $89 ; kitchen tile
db $4e, $4f, $5e, $5f ; wood
db $11, $9f, $9e, $9d, $85, $73, $60, $61, $62 ; carpet
nonlava_end:
Tilemap: incbin "obj/tileset.tilemap"
TilemapEnd:
Blacktile:
rept SCRN_TILE_B
db $ff
endr
registertiles Blacktile,1,1
Star: incbin "obj/star.2bpp"
registertiles Star,8,1
Sadcat: incbin "obj/sadcat.2bpp"
registertiles Sadcat,8,8
;-------------------,
; Sprite Meta-Table ;
;___________________'
include "src/smt.inc"
; arguments:
; \1: name of sprite, an index of which will be defined for you
; \2: motion behavior: `SMTF_SCREEN_FIXED` or `SMTF_WORLD_FIXED`
; \3: first animation frame tile index
; \4: x position
; \5: y position
; \6: flags
; \7: number of animation frames
; \8: animation speed (in number of vblank interrupts)
; \9: animation frame index to start on
SMT_ROM:
Sprite lstar_sprite,SMTF_ACTIVE|SMTF_ANIMATED|SMTF_WORLD_FIXED,StarBeginIndex,$5d,$2e,0,8,2,0
Sprite rstar_sprite,SMTF_ACTIVE|SMTF_ANIMATED|SMTF_WORLD_FIXED,StarBeginIndex,$6d,$2e,OAMF_XFLIP,8,2,4
Sprite playerHL_sprite,SMTF_ACTIVE|SMTF_SCREEN_FIXED,SadcatBeginIndex+0,$50,$4e,OAMF_PAL1,0,0,0 ; head
Sprite playerHR_sprite,SMTF_ACTIVE|SMTF_SCREEN_FIXED,SadcatBeginIndex+1,$58,$4e,OAMF_PAL1,0,0,0
Sprite playerSL_sprite,SMTF_ACTIVE|SMTF_SCREEN_FIXED,SadcatBeginIndex+2,$50,$56,OAMF_PAL1,0,0,0 ; head/shoulders
Sprite playerSR_sprite,SMTF_ACTIVE|SMTF_SCREEN_FIXED,SadcatBeginIndex+2,$58,$56,OAMF_XFLIP|OAMF_PAL1,0,0,0
Sprite playerTL_sprite,SMTF_ACTIVE|SMTF_SCREEN_FIXED,SadcatBeginIndex+4,$50,$5e,OAMF_PAL1,0,0,0 ; torso
Sprite playerTR_sprite,SMTF_ACTIVE|SMTF_SCREEN_FIXED,SadcatBeginIndex+5,$58,$5e,OAMF_PAL1,0,0,0
Sprite playerCL_sprite,SMTF_ACTIVE|SMTF_SCREEN_FIXED,SadcatBeginIndex+6,$50,$66,OAMF_PAL1,0,0,0 ; core
Sprite playerCR_sprite,SMTF_ACTIVE|SMTF_SCREEN_FIXED,SadcatBeginIndex+7,$58,$66,OAMF_PAL1,0,0,0
;---------------,
; Allocated RAM ;
;_______________'
rsset _HIRAM
buttons rb 1 ; bitmask of which buttons are being held, $10 right, $20 left, $40 up, $80 down
song_repeated rb 1 ; when the song repeats for the first time, start scrolling
spr_index rb 1 ; used to loop through animating/moving sprites
note_dur rb 1 ; counter for frames within note
note_swindex rb 1 ; index into the swing table
note_index rb 1 ; index of note in song
duration rb 1 ; how many vblank frames of the current directive are left
dx rb 1
dy rb 1
lfootx rb 1 ; x position of left foot wrt the map, 0 is the furthest left you can go without hitting the wall
lfooty rb 1 ; y position of left foot wrt the map, 0 is the furthest up you can go without hitting your head
tmp1 rb 1
tmp2 rb 1
_HIRAM_END rb 0
if _HIRAM_END > $fffe
fail "Allocated HIRAM exceeds available HIRAM space!"
endc
rsset _RAM
SMT_RAM rb SPRITE_NUM * SMT_RAM_BYTES ; sprite meta-table, holding sprite animation and movement metadata
_RAM_END rb 0
if _RAM_END > $dfff
fail "Allocated RAM exceeds available RAM space!"
endc
;-------------,
; Entry point ;
;_____________'
begin::
di
ld sp,$ffff
call StopLCD
ld a,%11100100 ; Window palette colors, from darkest to lightest
ld [rBGP],a ; Setup the default background palette
ld a,%11100100
; ^^ not used, always transparent
ldh [rOBP0],a ; set sprite pallette 0
ld a,%00011100
; ^^ not used, always transparent
ldh [rOBP1],a ; set sprite pallette 1
; clear screen RAM
ld a,$20
ld hl,_SCRN0
ld bc,32*32
call mem_Set
; zero out OAM (sprite RAM)
ldz
ld hl,_OAMRAM
ld bc,160
call mem_Set
; zero out allocated HRAM
ldz
ld hl,_HIRAM
ld bc,_HIRAM_END-_HIRAM
call mem_Set
; enable sound registers
ld a,%10000000 ; enable sound
ld [rAUDENA],a
ld a,%01110111 ; left and right channel volume
ld [rAUDVOL],a
ld a,%00010010 ; hard-pan PU1 (left) and PU2 (right) outputs
ld [rAUDTERM],a
; enable PU1 and PU2
ld a,%10000011
ld [rNR52],a
vram_addr set $8000
LoadTiles: macro
ld hl,\1
ld de,vram_addr
size set (\2) * (\3) * SCRN_TILE_B
ld bc,size
call mem_Copy
vram_addr set vram_addr+size
if TWO_TILE_ALIGN
vram_addr set vram_addr + vram_addr % (2 * SCRN_TILE_B)
endc
endm
; write tiles from ROM into tile memory
LoadTiles Tileset,TilesetFrames,TilesetTilesPerFrame
LoadTiles Blacktile,BlacktileFrames,BlacktileTilesPerFrame
LoadTiles Star,StarFrames,StarTilesPerFrame
LoadTiles Sadcat,SadcatFrames,SadcatTilesPerFrame
; blit tilemap
if TilesetBeginIndex != 0
fail "the first tiles in tile memory must be the tileset used by the tilemap!"
endc
ld de,_SCRN0
ld hl,Tilemap
.loop
nop
rept $20
ld a,[hl+]
ld [de],a
inc de
endr
ld bc,8 ; add 8 to tilemap addr
add hl,bc
ld a,h ; check if we're at the end
cp high(TilemapEnd)
jr nz,.loop
ld a,l
cp low(TilemapEnd)
jr nz,.loop
ld h,d
ld l,e
.surroundings
ld a,BlacktileBeginIndex
ld [hl+],a
ld a,h
cp high(_SCRN1)
jr nz,.surroundings
ld a,l
cp low(_SCRN1)
jr nz,.surroundings
; we start at 7,9 in lfoot coordinates
ld a,9
ld [lfootx],a
ld a,7
ld [lfooty],a
; copy ROM SMT to RAM SMT and set OAM where needed
ld a,SPRITE_NUM
ld [spr_index],a
ld hl,SMT_ROM
ld bc,SMT_RAM
ld de,_OAMRAM
.smt_row
rept 2 ; bytes 0 (SMT flags) and 1 (animation speed/counter) go to SMT RAM
ld a,[hl+]
ld [bc],a
inc bc
endr
rept 4 ; bytes 2-5 (y, x, tile, flags) go to OAM RAM
ld a,[hl+]
ld [de],a
inc de
endr
rept 2 ; bytes 6 (overrun tile index) and 7 (initial tile index) go to SMT RAM
ld a,[hl+]
ld [bc],a
inc bc
endr
ld a,[spr_index]
dec a
ld [spr_index],a
jr nz,.smt_row ; ...and loop if there's more sprites to process
ld a,LCDCF_ON | LCDCF_BG8000 | LCDCF_BG9800 | LCDCF_BGON | LCDCF_OBJ8 | LCDCF_OBJON
; turn LCD on
; use 8000-8FFF for bg and window tile data
; use 9800-9BFF for tiles
; enable background
; use 8x8 sprites
; enable sprites
ld [rLCDC],a
; set up interrupt
ld a,IEF_VBLANK
ld [rIE],a
ei
.wait
; wait for vblank
halt
nop
jr .wait
StopLCD:
ld a,[rLCDC]
rlca ; Put the high bit of LCDC into the Carry flag
ret nc ; Screen is off already. Exit.
.wait
; wait for vblank scan line 145
ld a,[rLY]
cp 145
jr nz,.wait
; turn off the LCD
ld a,[rLCDC]
res 7,a
ld [rLCDC],a
ret
; directly from GB CPU manual
ReadJoypad:
ld a,P1F_5 ; bit 5 = $20
ld [rP1],A ; select P14 by setting it low
ld A,[rP1]
ld A,[rP1] ; wait a few cycles
cpl ; complement A
and $0F ; get only first 4 bits
swap A ; swap it
ld B,A ; store A in B
ld A,P1F_4
ld [rP1],A ; select P15 by setting it low
rept 6
ld A,[rP1] ; Wait a few MORE cycles
endr
cpl ; complement (invert)
and $0F ; get first 4 bits
or B ; put A and B together
;ld B,A ; store A in D
;ld A,[buttons_old] ; read old joy data from ram
;xor B ; toggle w/current button bit
;and B ; get current button bit back
ld [buttons],A ; save in new Joydata storage
;ld A,B ; put original value in A
;ld [buttons_old],A ; store it as old joy data
ld A,P1F_5|P1F_4 ; deselect P14 and P15
ld [rP1],A ; RESET Joypad
ret ; Return from Subroutine
VBlank::
ld a,[duration]
cpz
jp nz,.scroll_screen ; if the walk cycle isn't over, don't read buttons, just scroll
;walkcycleover
call ReadJoypad
ld a,[buttons]
and $f0 ; is a movement button held?
jr nz,.parsemovement ; if so, process it
.haltmovement
ldz ; clear out current movement command
ld [dx],a
ld [dy],a
jp .animate_sprites
.parsemovement ; also does bounds checks
ld a,[buttons]
and PADF_UP | PADF_DOWN ; moving up or down?
cpz
jr z,.xmovement
;ymovement
and PADF_UP ; moving up?
cpz
jr nz,.up
;down
ld a,[lfooty] ;if you're at the bottom edge, you can't move down
cp LEVEL_HEIGHT - CHARACTER_HEIGHT - 1
jr z,.haltmovement
ld a,1 ; move down
ld [dy],a
ldz
ld [dx],a
jr .collision
.up
ld a,[lfooty] ; if you're at the top edge, you can't move up
cpz
jr z,.haltmovement
ld a,$ff ; move up
ld [dy],a
ldz
ld [dx],a
jr .collision
.xmovement
ld a,[buttons]
and PADF_LEFT ; moving left?
cpz
jr nz,.left
;right
ld a,[lfootx] ; if you're at the rightmost edge, you can't move right
cp LEVEL_WIDTH - 2 ; (subtract one to account for your RIGHT foot and one because you need to check it earlier)
jr z,.haltmovement
ld a,1 ; move right
ld [dx],a
ldz
ld [dy],a
jr .collision
.left
ld a,[lfootx] ; if you're at the leftmost edge, you can't move left
cpz
jr z,.haltmovement
ld a,$ff ; move left
ld [dx],a
ldz
ld [dy],a
;jr .collision
.collision
if !COLLISION_DETECTION
jr .collision_end
endc
; at (lfootx,lfooty) we test tilemap entry = firsttile + (40 * lfooty) + lfootx
; where firsttile represents the tile (with respect to the "real" tile map)
; that your left foot is standing on when you're at the lfoot origin
ld hl,Tilemap ; Tilemap[] -> hl
ld b,0 ; firsttile -> bc
ld c,CHARACTER_HEIGHT * LEVEL_WIDTH
add hl,bc ; Tilemap[firsttile] -> hl
ld a,[dx] ; dx -> c
ld c,a
ld a,[lfootx] ; lfootx -> a
add c ; lfootx + dx -> a
ld b,0 ; lfootx + dx -> bc
ld c,a
add hl,bc ; hl = Tilemap[firsttile + lfootx + dx]
ld a,[dy] ; dy -> c
ld c,a
ld a,[lfooty] ; lfooty -> a
add c ; lfooty + dy -> a
ld b,0 ; 40 -> bc
ld c,40
cpz
jr .mulcheck
.muladd
add hl,bc
dec a
.mulcheck
jr nz,.muladd
;mulend ; hl = Tilemap[firsttile + (lfootx + dx) + ((lfooty + dy) * 40)]
rept 2 ; once for each foot
ld a,[hl+] ; left foot tile -> e
ld e,a
ld d,nonlava_end-nonlava ; number of entries in nonlava table
ld bc,nonlava ; nonlava[] -> bc
.nexttile\@
ld a,[bc] ; nonlava[i] -> a
cp e ; if this tile is a nonlava tile, move onto the next foot
jp z,.nextfoot\@
inc bc ; otherwise, i++
dec d
cpz
jp nz,.nexttile\@
jp .haltmovement ; if we run out of tiles without jumping to the next foot, it's lava!
.nextfoot\@
endr
.collision_end
; update lfootx and lfooty to new coordinates
ld a,[dx] ; dx -> b
ld b,a
ld a,[lfootx] ; lfootx -> a
add b ; lfootx + dx -> a
ld [lfootx],a ; lfootx += dx
ld a,[dy] ; dy -> b
ld b,a
ld a,[lfooty] ; lfooty -> a
add b ; lfooty + dy -> a
ld [lfooty],a ; lfooty += dy
; restart animation counter
ld a,8
ld [duration],a
.scroll_screen
ld a,[duration]
dec a
ld [duration],a
; scroll bg viewport rightward by dx
ld a,[dx] ; dx -> b
ld b,a
ld a,[rSCX] ; rSCX -> a
add b ; rSCX + dx -> a
ld [rSCX],a ; rSCX += dx
; scroll bg viewport downward by dy
ld a,[dy] ; dy -> b
ld b,a
ld a,[rSCY] ; rSCY -> a
add b ; rSCY + dy -> a
ld [rSCY],a ; rSCY += dy
.animate_sprites
ld a,SPRITE_NUM
ld [spr_index],a
ld hl,SMT_RAM
ld bc,_OAMRAM
jr .first_sprite
.next_sprite
ld a,[spr_index]
dec a
jr z,.anim_end
ld [spr_index],a
.first_sprite
ld a,[hl] ; (byte 0 bit 0) check if this row is active
and SMTF_ACTIVE
jr z,.skip_sprite ; ...if not, skip it
;position_update
ld a,[hl] ; (byte 0 bit 1) do we need to move with the screen?
and SMTF_WORLD_FIXED
jr z,.no_position_update ; ...if not, don't do position updating
ld a,[dy] ; dy -> d
ld d,a
ld a,[bc] ; OAM y pos -> a
sub d ; OAM y pos - dy -> a
ld [bc],a ; OAM y pos -= dy
inc bc
ld a,[dx] ; dx -> d
ld d,a
ld a,[bc] ; OAM x pos -> a
sub d ; OAM x pos - dx -> a
ld [bc],a ; OAM x pos -= dx
inc bc
jr .advance_animation
.no_position_update
rept 2
inc bc
endr
;jr .advance_animation
.advance_animation
ld a,[hl+] ; (byte 0 bit 2) should we animate?
and SMTF_ANIMATED
jr z,.skip_animation
ld a,[hl] ; (byte 1 low nybble) animation stall amount -> d
and $0f
ld d,a
ld a,[hl] ; (byte 1 high nybble) animation stall counter -> a
swap a
and $0f
jr nz,.decrease_stall ; we animate only when the counter reaches zero. if it's not zero, just decrease it this frame
ld a,d ; reset the stall counter to the stall amount
swap a ; combine the two nybbles
or d
ld [hl+],a ; stall counter and stall amount -> (byte 1)
ld a,[hl+] ; (byte 2) tile-after-last -> d
ld d,a
ld a,[bc] ; OAM current tile -> a
inc a ; current tile + 1 -> a
cp d ; if current tile + 1 == tile-after-last...
jp nz,.no_reset_animation
ld a,[hl] ; (byte 3) first animation frame tile index -> a
.no_reset_animation ; (assuming hl is at byte 3 of the RAM SMT entry)
ld [bc],a ; new tile index -> OAM current tile
jr .after_animation
.decrease_stall
dec a ; decrease the animation stall counter
swap a ; combine the two nybbles
or d
ld [hl+],a ; write back to the RAM SMT
jr .after_decrease_stall
; skipping through the remainder of the row. different entry points for different points to skip from
.skip_sprite
inc hl
rept 2
inc bc
endr
.skip_animation
inc hl
.after_decrease_stall
inc hl
.after_animation
inc hl
rept 2
inc bc
endr
jr .next_sprite
.anim_end
HandleNotes:
ld a,[note_dur] ; if duration of previous note is expired, continue
cpz
jr z,.next_note
dec a ; otherwise decrement and return
ld [note_dur],a
reti
.next_note
ld a,[note_swindex]
ld hl,NoteDuration
add a,l ; add index into note duration table
ld l,a
adc a,h
sub l
ld h,a
ld a,[hl] ; set next note duration
ld [note_dur],a
ld a,[note_swindex] ; increase note swing index
inc a
cp NoteDurationEnd-NoteDuration ; wrap if necessary
jr c,.dont_wrap
ldz
.dont_wrap
ld [note_swindex],a
ld a,[note_index] ; get note index
cp a,SongLength-1 ; if hPU1NoteIndex isn't zero, fine...
jr nz,.sound_registers
ld a,1 ; ...but if it is, the song has repeated and we need to mark that
ld [song_repeated],a
pulsenote: macro
; index the notes-in-song table with the note song-index to get the actual note value
ld b,0
ld a,[note_index]
ld c,a
ld hl,\1
add hl,bc
ld c,[hl]
ld a,c ; if it's a rest, don't set any registers for this note
cp REST
jr z,.end\@
cp KILL ; if it's a kill command, stop the note
jr nz,.nocut\@
ldz
ld [\6],a
ld a,$80
ld [\8],a
jr .end\@
.nocut\@
; index the note frequency table with the actual note value to get the note frequency (16-bit)
ld b,0
sla c ; double the index (16-bit), sla+rl together represents a 16-bit left shift
rl b
ld hl,NoteFreqs ; now index the damn table
add hl,bc
ldz ; disable sweep
ld [\4],a
ld a,\2 ; duty cycle (top two) and length (the rest)
ld [\5],a
ld a,\3 ; envelope, precisely like LSDj
ld [\6],a
ld a,[hl+] ; freq LSB
ld [\7],a
ld a,[hl] ; freq MSB
and %00000111 ; truncate to bits of MSB that are actually used
or %10000000 ; reset envelope (not legato)
ld [\8],a ; set frequency MSB and flags
.end\@
endm
.sound_registers
pulsenote NotesPU1,%00111111,$F1,rAUD1SWEEP,rAUD1LEN,rAUD1ENV,rAUD1LOW,rAUD1HIGH
pulsenote NotesPU2,%10111111,$C3,rAUD2LOW,rAUD2LEN,rAUD2ENV,rAUD2LOW,rAUD2HIGH ; TODO: skip sweep appropriately
ld a,[note_index] ; increment index of note in song
inc a
and SongLength-1
ld [note_index],a
reti
; vim: se ft=rgbds: