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 LAST_SCX:: DB ; Value of SCX last frame LAST_SCY:: DB ; Value of SCY last frame 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 [LAST_SCX], a ld a, INIT_SCY ld [rSCY], a ld [LAST_SCY], 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 ; 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 ; B = CAMERA_X - SCX/8 ld a, [rSCX] srl a srl a srl a ld b, a ld a, [CURRENT_CAMERA_X] sub b ld b, a ; 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 jp .done .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] srl a srl a srl a dec a call get_row_ptr ; B = CAMERA_X - SCX/8 ld a, [rSCX] ld b, a ld a, [CURRENT_CAMERA_X] srl b srl b srl b sub b ld b, a ; 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 jr .done .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] srl a srl a srl a dec a ADD16 hl ; B = CAMERA_X - 1 ld a, [CURRENT_CAMERA_X] dec a ld b, a ; C = CAMERA_Y - SCY/8 ld a, [rSCY] ld c, a ld a, [CURRENT_CAMERA_Y] srl c srl c srl c sub c ld c, a 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] jr nz, .done ; 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 ld a, [rSCY] ld c, a ld a, [CURRENT_CAMERA_Y] srl c srl c srl c sub c ld c, a call enqueue_col_write ld a, [PAGEX] add 8 ld [PAGEX], a .done: ld hl, LAST_SCY ld a, [rSCY] ld [hl], 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 MEMCPY bc, hl, d .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 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 ; 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 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