You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

662 lines
11 KiB

INCLUDE "hardware.inc"
INCLUDE "util.inc"
SECTION "Map Data", WRAM0
PAGEX:: DB ; X coordinate to enqueue map column at
PAGEY:: DB ; Y coordinate to enqueue map row at
PENDING_ROW_PTR:: DW ; Where to write pending row data (0 = no write)
PENDING_ROW_DATA:: DS SCRN_VX_B ; Row to be written
PENDING_COL_PTR:: DW ; Where to write pending column data (0 = no write)
PENDING_COL_DATA: DS SCRN_VY_B ; Column to be written
CURRENT_DATA_START::
CURRENT_TILE_PTR:: DW ; Location of tile data
CURRENT_TILE_SIZE:: DB ; Length of tile data (num_tiles * 8)
CURRENT_MAP_PTR:: DW ; Location of map data
CURRENT_MAP_COLLISION:: DW ; Location of map collision data
CURRENT_MAP_WIDTH:: DB ; Width of map in tiles
CURRENT_MAP_HEIGHT:: DB ; Height of map in tiles
CURRENT_SPAWN_X:: DB ; X coordinate to spawn player at
CURRENT_SPAWN_Y:: DB ; Y coordinate to spawn player at
CURRENT_CAMERA_X:: DB ; X coordinate of camera (top left of viewport)
CURRENT_CAMERA_Y:: DB ; Y coordinate of camera (top left of viewport)
CURRENT_DATA_END::
SECTION "Map Code", ROM0
DEF INIT_SCX EQUS "((SCRN_VX - SCRN_X) / 2)"
DEF INIT_SCY EQUS "((SCRN_VY - SCRN_Y) / 2)"
; Loads a map
; @param hl Pointer to map metadata
Map_Load::
; Initialize scroll state
ld a, INIT_SCX
ld [rSCX], a
ld a, INIT_SCY
ld [rSCY], a
ld a, 8
ld [PAGEX], a
ld [PAGEY], a
; Store metadata
ld bc, CURRENT_DATA_START
ld d, CURRENT_DATA_END - CURRENT_DATA_START
call memcpy
; Write tiles to VRAM
ld hl, CURRENT_TILE_PTR
ld a, [hl+]
ld c, a
ld a, [hl+]
ld b, a
ld a, [CURRENT_TILE_SIZE]
ld d, a
ld hl, _VRAM
MEMCPY hl, bc, d
; Write initial map data
ld hl, _SCRN0
ld a, [CURRENT_CAMERA_X]
sub INIT_SCX / 8
ld b, a
ld a, [CURRENT_CAMERA_Y]
sub INIT_SCY / 8
ld c, a
ld d, SCRN_VY_B
.write_rows:
call write_map_row
inc c
ld a, SCRN_VX_B
ADD16 hl
dec d
jr nz, .write_rows
ret
; Loads the map X-coordinate into B
MACRO LOAD_MAPX
; if SCX <= 0x7f
ld a, [rSCX]
cp $80
jr nc, .wrap\@
; B = CAMERA_X - SCX/8
ld a, [rSCX]
ld b, a
ld a, [CURRENT_CAMERA_X]
srln b, 3
sub b
ld b, a
jr .done\@
; else SCX > 0x7f
.wrap\@:
; B = CAMERA_X - PAGE_X/8
ld a, [PAGEX]
ld b, a
ld a, [CURRENT_CAMERA_X]
srln b, 3
sub b
ld b, a
; HL += PAGE_X/8
ld a, [PAGEX]
srln a, 3
ADD16 hl
.done\@:
ENDM
; Loads the map Y-coordinate into C
MACRO LOAD_MAPY
; if SCY <= 0x7f
ld a, [rSCY]
cp $80
jr nc, .wrap\@
; C = CAMERA_Y - SCY/8
ld a, [rSCY]
ld c, a
ld a, [CURRENT_CAMERA_Y]
srln c, 3
sub c
ld c, a
jr .done\@
; else SCY > 0x7f
.wrap\@:
; C = CAMERA_Y - PAGE_Y/8
ld a, [PAGEY]
ld c, a
ld a, [CURRENT_CAMERA_Y]
srln c, 3
sub c
ld c, a
; HL += 32 * PAGE_Y/8
ld a, [PAGEY]
srln a, 3
ld d, a
ld e, 32
.adjust_hl\@:
ld a, d
ADD16 hl
dec e
jr nz, .adjust_hl\@
.done\@:
ENDM
; Update map state based on SCX/SCY
Map_Scroll::
; If SCY = PAGEY, write map row
ld hl, PAGEY
ld a, [rSCY]
cp [hl]
jr nz, .scroll_down_check
LOAD_MAPX
; C = CAMERA_Y - 2
ld a, [CURRENT_CAMERA_Y]
sub 2
ld c, a
; HL = _SCRN0 + 32 * (SCY/8 - 2)
ld a, [rSCY]
sub 16
srl a
srl a
srl a
call get_row_ptr
call enqueue_row_write
ld a, [PAGEY]
sub 8
ld [PAGEY], a
ret
.scroll_down_check:
; Check SCY + (SCRN_Y + 16) = PAGEY
ld a, [PAGEY]
sub SCRN_Y + 16
ld hl, rSCY
cp [hl]
jr nz, .scroll_left_check
ld a, [PAGEY]
srln a, 3
dec a
call get_row_ptr
LOAD_MAPX
; C = CAMERA_Y + 18 + 1
ld a, [CURRENT_CAMERA_Y]
add SCRN_Y_B + 1
ld c, a
call enqueue_row_write
ld a, [PAGEY]
add 8
ld [PAGEY], a
ret
.scroll_left_check:
; If SCX = PAGEX, write map col
ld a, [PAGEX]
ld hl, rSCX
cp [hl]
jr nz, .scroll_right_check
; HL = VRAM + PAGEX/8
ld hl, _SCRN0
ld a, [PAGEX]
srln a, 3
dec a
ADD16 hl
; B = CAMERA_X - 1
ld a, [CURRENT_CAMERA_X]
dec a
ld b, a
; C = CAMERA_Y - SCY/8
LOAD_MAPY
call enqueue_col_write
ld a, [PAGEX]
sub 8
ld [PAGEX], a
.scroll_right_check:
; If SCX = SCRN_X - PAGEX, write map col
; Check SCX + (SCRN_X + 16) = PAGEX
ld a, [PAGEX]
sub SCRN_X + 16
ld hl, rSCX
cp [hl]
ret nz
; HL = VRAM + PAGEX/8
ld hl, _SCRN0
ld a, [PAGEX]
srl a
srl a
srl a
dec a
ADD16 hl
; B = CAMERA_X + 20
ld a, [CURRENT_CAMERA_X]
add SCRN_X_B + 1
ld b, a
; C = CAMERA_Y - SCY/8
LOAD_MAPY
call enqueue_col_write
ld a, [PAGEX]
add 8
ld [PAGEX], a
ret
Map_Update::
; Skip row update if PENDING_ROW_PTR is 0
ld a, [PENDING_ROW_PTR]
ld c, a
ld a, [PENDING_ROW_PTR + 1]
ld b, a
or c
jr z, .update_col
ld hl, PENDING_ROW_DATA
ld d, SCRN_VX_B
.copy_row:
ld a, [hl+]
ld [bc], a
inc bc
; if BC % 32 == 0 (we just crossed a row boundary)
ld a, c
and %11111
jr nz, .copy_row1
; BC -= 32 (reset back to beginning of row)
ld a, c
sub 32
ld c, a
ld a, b
sbc 0
ld b, a
.copy_row1:
dec d
jr nz, .copy_row
.update_col:
; Skip column update if PENDING_COL_PTR is 0
ld a, [PENDING_COL_PTR]
ld c, a
ld a, [PENDING_COL_PTR + 1]
ld b, a
or c
ret z
ld d, SCRN_VY_B
ld hl, PENDING_COL_DATA
.update_col_loop:
ld a, [hl+]
ld [bc], a
ld a, SCRN_VX_B
ADD16 bc
; If BC = 9c00, set BC = 9800
CP16 bc, _SCRN0 + SCRN_VY_B * SCRN_VX_B
jr nz, .update_col_loop_next
ld b, HIGH(_SCRN0)
.update_col_loop_next:
dec d
jr nz, .update_col_loop
ret
; Computes the offset into map RAM
; @param a The map RAM y-coordinate
; @return hl Pointer into map RAM
get_row_ptr:
push de
ld d, 0
ld e, a
REPT 5
SLA16 de
ENDR
ld hl, _SCRN0
add hl, de
pop de
ret
; Write a row of map data into row buffer
; @param b Map X coordinate (signed)
; @param c Map Y coordinate (signed)
; @param hl Where to write the row in map VRAM
; @destroy All registers
enqueue_row_write:
; PENDING_ROW_PTR = HL
ld a, l
ld [PENDING_ROW_PTR], a
ld a, h
ld [PENDING_ROW_PTR + 1], a
; If Y < 0, write 0s
bit 7, c
jr nz, .zero_row
; If Y >= MAP_HEIGHT, write 0s
ld a, [CURRENT_MAP_HEIGHT]
dec a
cp c
jr c, .zero_row
; HL = CURRENT_MAP_PTR
ld a, [CURRENT_MAP_PTR]
ld l, a
ld a, [CURRENT_MAP_PTR + 1]
ld h, a
; HL = CURRENT_MAP_PTR + Y * MAP_WIDTH
ld d, 0
ld a, [CURRENT_MAP_WIDTH]
ld e, a
ld a, c
.get_map_row_ptr:
or a
jr z, .copy_map_row
add hl, de
dec a
jr .get_map_row_ptr
.copy_map_row:
; C = BYTES_LEFT
ld c, SCRN_VX_B
ld de, PENDING_ROW_DATA
; If X > 0, increment map pointer by X
bit 7, b
jr nz, .pad_left
ld a, b
ADD16 hl
jr .copy_middle
; Note: Can skip checking BYTES_LEFT > 0 in this loop. If there were
; SCRN_VX_B zeros to write, then Y would be 1 greater and we would have
; jumped into .zero_row before reaching this code
.pad_left:
; Check X < 0
bit 7, b
jr z, .copy_middle
; *ROW++ = 0
ld [de], a
inc de
; X++, BYTES_LEFT--
inc b
dec c
jr .pad_left
.copy_middle:
; Check X < MAP_WIDTH
ld a, [CURRENT_MAP_WIDTH]
dec a
cp b
jr c, .pad_right
; Check BYTES_LEFT > 0
ld a, c
or a
ret z
; *ROW++ = *MAP++
ld a, [hl+]
ld [de], a
inc de
; X++, BYTES_LEFT--
inc b
dec c
jr .copy_middle
.pad_right:
; Check BYTES_LEFT > 0
ld a, c
or a
ret z
; *ROW++ = 0
xor a
ld [de], a
inc de
; X++, BYTES_LEFT--
inc b
dec c
jr .pad_right
.zero_row:
ld hl, PENDING_ROW_DATA
xor a
ld c, SCRN_VX_B
.zero_row_loop:
ld [hl+], a
dec c
jr nz, .zero_row_loop
ret
; NOTE: This works by enqueueing a row to write, and then immediately flushing
; the queue. The subroutine could be sped up by writing to map RAM directly, but
; this is simpler to write and saves on code size.
;
; Write a row of map data into map RAM.
; @param b Map X coordinate (signed)
; @param c Map Y coordinate (signed)
; @param hl Pointer into map VRAM
write_map_row:
push bc
push de
push hl
call enqueue_row_write
ld a, [PENDING_ROW_PTR]
ld c, a
ld a, [PENDING_ROW_PTR + 1]
ld b, a
; TODO: Fix
ld hl, PENDING_ROW_DATA
ld d, SCRN_VX_B
MEMCPY bc, hl, d
pop hl
pop de
pop bc
ret
; Write a column of map data into column buffer
; @param b Map X coordinate (signed)
; @param c Map Y coordinate (signed)
; @param hl Where to write the column in map VRAM
; @destroy All registers
enqueue_col_write:
; PENDING_COL_PTR = HL
ld a, l
ld [PENDING_COL_PTR], a
ld a, h
ld [PENDING_COL_PTR + 1], a
; If X < 0, write 0s
bit 7, b
jr nz, .zero_row
; If X >= MAP_WIDTH, write 0s
ld a, [CURRENT_MAP_WIDTH]
dec a
cp c
jr c, .zero_row
; HL = CURRENT_MAP_PTR
ld a, [CURRENT_MAP_PTR]
ld l, a
ld a, [CURRENT_MAP_PTR + 1]
ld h, a
; HL = CURRENT_MAP_PTR + Y * MAP_WIDTH + X
ld d, 0
ld a, [CURRENT_MAP_WIDTH]
ld e, a
ld a, c
.get_map_col_ptr:
or a
jr z, .copy_map_column
add hl, de
dec a
jr .get_map_col_ptr
.copy_map_column:
ld a, b
ADD16 hl
; B = BYTES_LEFT
ld b, SCRN_VY_B
ld de, PENDING_COL_DATA
; Note: Can skip checking BYTES_LEFT > 0 in this loop. If there were
; SCRN_VY_B zeros to write, then X would be 1 greater and we would have
; jumped into .zero_row before reaching this code
.pad_left:
; Check Y < 0
bit 7, c
jr z, .copy_middle
; *ROW++ = 0
xor a
ld [de], a
inc de
; Y++, BYTES_LEFT--
inc c
dec b
jr .pad_left
.copy_middle:
; Check Y < MAP_HEIGHT
ld a, [CURRENT_MAP_HEIGHT]
dec a
cp c
jr c, .pad_right
; Check BYTES_LEFT > 0
ld a, b
or a
ret z
; *ROW++ = *MAP
ld a, [hl]
ld [de], a
inc de
; MAP += MAP_WIDTH
ld a, [CURRENT_MAP_WIDTH]
ADD16 hl
; Y++, BYTES_LEFT--
inc c
dec b
jr .copy_middle
.pad_right:
; Check BYTES_LEFT > 0
ld a, b
or a
ret z
; *ROW++ = 0
xor a
ld [de], a
inc de
; X++, BYTES_LEFT--
inc c
dec b
jr .pad_right
.zero_row:
ld hl, PENDING_COL_DATA
xor a
ld c, SCRN_VY_B
.zero_row_loop:
ld [hl+], a
dec c
jr nz, .zero_row_loop
ret
; Compute the distance (modulo 32) between two values
; @param b Value 1
; @param c Value 2
mmd2:
push bc
ld a, b
sub c
cpl
ld b, a
cpl
bit 7, b
jr z, .0
ld b, a
.0:
ld a, 32
sub b
cp b
jr nc, .1
pop bc
ret
.1:
ld a, b
pop bc
ret