INCLUDE "game.inc" INCLUDE "hardware.inc" INCLUDE "util.inc" SECTION "Map Data", WRAM0 DEF ROW_BUFFER_SIZE EQUS "SCRN_X_B + 4" DEF COL_BUFFER_SIZE EQUS "SCRN_Y_B + 4" PENDING_ROW_PTR:: DW ; Where to write pending row data (0 = no write) PENDING_ROW_DATA:: DS ROW_BUFFER_SIZE ; Row to be written PENDING_COL_PTR:: DW ; Where to write pending column data (0 = no write) PENDING_COL_DATA: DS COL_BUFFER_SIZE ; 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 ; Store metadata ld bc, CURRENT_DATA_START ld d, CURRENT_DATA_END - CURRENT_DATA_START call memcpy ; Move player to spawn point ld a, [CURRENT_CAMERA_X] ld b, a ld a, [CURRENT_SPAWN_X] sub b sla a sla a sla a ld [PLAYER_X], a ld a, [CURRENT_CAMERA_Y] ld b, a ld a, [CURRENT_SPAWN_Y] sub b sla a sla a sla a ld [PLAYER_Y], a ; 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 + TILE_INDEX_BACKGROUND * TILE_SIZE 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 ; Scroll the map upwards ; @param d The amount to scroll by (0 < d < 8) ; ; SCY_T = SCY % 8; ; if CAMERA_Y = 0 { ; if SCY_T != 0 { ; SCY -= MIN(D, SCY_T); ; } ; return; ; } ; SCY -= D; ; if SCY_T - D < 0 { ; CAMERA_Y -= 1; ; Map_ScrollRow(CAMERA_X - 2, CAMERA_Y - 2); ; } Map_ScrollUp:: ld a, [rSCY] and %111 add sp, -1 ld hl, sp + 0 ld [hl], a ld a, [CURRENT_CAMERA_Y] or a jr nz, .scroll ld hl, sp + 0 ld a, [hl] or a jr z, .done ld b, d call min ld d, a ld a, [rSCY] sub d ld [rSCY], a jr .done .scroll: ld a, [rSCY] sub d ld [rSCY], a ; Check SCY_T - D < 0 ld hl, sp + 0 ld a, [hl] sub d jr nc, .done ; CAMERA_Y -= 1 ld a, [CURRENT_CAMERA_Y] dec a ld [CURRENT_CAMERA_Y], a ; B = CAMERA_X - 2 ld a, [CURRENT_CAMERA_X] sub 2 ld b, a ; C = CAMERA_Y - 2 ld a, [CURRENT_CAMERA_Y] sub 2 ld c, a ; MAP Y = SCY/8 - 2 ld a, [rSCY] srln a, 3 sub 2 add sp, 1 jr Map_ScrollRow .done: add sp, 1 ret ; Map_ScrollDown(D) ; ; SCY_T = SCY % 8; ; if CAMERA_Y + 18 = MAP_HEIGHT { ; if SCY_T != 0 { ; SCY += MIN(D, 8 - SCY_T); ; } ; return; ; } ; SCY += D; ; if SCY_T + D >= 8 { ; CAMERA_Y += 1; ; Map_ScrollRow(CAMERA_X - 2, CAMERA_Y + 18 + 1); ; } Map_ScrollDown:: ld a, [rSCY] and %111 add sp, -1 ld hl, sp + 0 ld [hl], a ld a, [CURRENT_CAMERA_Y] add SCRN_Y_B ld hl, CURRENT_MAP_HEIGHT cp [hl] jr nz, .scroll ld hl, sp + 0 ld a, [hl] or a jr z, .done sub 8 cpl ld b, d call min ld d, a ld a, [rSCY] add d ld [rSCY], a jr .done .scroll: ld a, [rSCY] add d ld [rSCY], a ; Check SCY_T + D < 8 ld hl, sp + 0 ld a, [hl] add d cp 8 jr c, .done ; CAMERA_Y += 1 ld a, [CURRENT_CAMERA_Y] inc a ld [CURRENT_CAMERA_Y], a ; B = CAMERA_X - 2 ld a, [CURRENT_CAMERA_X] sub 2 ld b, a ; C = CAMERA_Y + 18 + 1 ld a, [CURRENT_CAMERA_Y] add SCRN_Y_B + 1 ld c, a ; MAP Y = SCY/8 + SCRN_Y_B + 1 ld a, [rSCY] srln a, 3 add SCRN_Y_B + 1 add sp, 1 jr Map_ScrollRow .done: add sp, 1 ret ; Scroll in a new row ; @param a Map VRAM Y-coordinate ; @param b Data X-coordinate ; @param c Data Y-coordinate Map_ScrollRow: and %11111 call get_row_ptr ld a, [rSCX] sub 16 srl a srl a srl a ld e, a ld d, 0 add hl, de call enqueue_row_write ret Map_ScrollLeft:: ld a, [rSCX] and %111 add sp, -1 ld hl, sp + 0 ld [hl], a ld a, [CURRENT_CAMERA_X] or a jr nz, .scroll ld hl, sp + 0 ld a, [hl] or a jr z, .done ld b, d call min ld d, a ld a, [rSCX] sub d ld [rSCX], a jr .done .scroll: ld a, [rSCX] sub d ld [rSCX], a ; Check SCX_T - D < 0 ld hl, sp + 0 ld a, [hl] sub d jr nc, .done ld a, [CURRENT_CAMERA_X] dec a ld [CURRENT_CAMERA_X], a ld a, [rSCY] sub 16 srl a srl a srl a call get_row_ptr ; B = CAMERA_X - 2 ld a, [CURRENT_CAMERA_X] sub 2 ld b, a ; E = VRAM + (SCX/8 - 2) ld a, [rSCX] srln a, 3 sub 2 ADD16 hl add sp, 1 jr Map_ScrollColumn .done: add sp, 1 ret Map_ScrollRight:: ld a, [rSCX] and %111 add sp, -1 ld hl, sp + 0 ld [hl], a ld a, [CURRENT_CAMERA_X] add SCRN_X_B ld hl, CURRENT_MAP_WIDTH cp [hl] jr nz, .scroll ld hl, sp + 0 ld a, [hl] or a jr z, .done sub 8 cpl ld b, d call min ld d, a ld a, [rSCX] add d ld [rSCX], a jr .done .scroll: ld a, [rSCX] add d ld [rSCX], a ; Check SCX_T + D < 8 ld hl, sp + 0 ld a, [hl] add d cp 8 jr c, .done ; CAMERA_X += 1 ld a, [CURRENT_CAMERA_X] inc a ld [CURRENT_CAMERA_X], a ld a, [rSCY] sub 16 srl a srl a srl a call get_row_ptr ; B = CAMERA_X + 20 + 1 ld a, [CURRENT_CAMERA_X] add SCRN_X_B + 1 ld b, a ; E = VRAM + SCX/8 + SCRN_X_B + 1 ld a, [rSCX] srl a srl a srl a add SCRN_X_B + 1 and %11111 ADD16 hl add sp, 1 jr Map_ScrollColumn .done: add sp, 1 ret Map_ScrollColumn: ; C = CAMERA_Y - 2 ld a, [CURRENT_CAMERA_Y] sub 2 ld c, a call enqueue_col_write 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, ROW_BUFFER_SIZE .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 ; Reset PENDING_ROW_PTR xor a ld [PENDING_ROW_PTR], a ld [PENDING_ROW_PTR + 1], a .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, COL_BUFFER_SIZE 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 ; Reset PENDING_COL_PTR xor a ld [PENDING_COL_PTR], a ld [PENDING_COL_PTR + 1], a 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, ROW_BUFFER_SIZE 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 .pad_left: ; Check X < 0 bit 7, b jr z, .copy_middle ; Check BYTES_LEFT > 0 ld a, c or a ret z ; *ROW++ = 0 ld a, TILE_INDEX_BACKGROUND 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+] add TILE_INDEX_BACKGROUND 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 ld a, TILE_INDEX_BACKGROUND 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, ROW_BUFFER_SIZE .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 init_row_write 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 b jr c, .zero_row ; HL = CURRENT_MAP_PTR + X ld a, [CURRENT_MAP_PTR] ld l, a ld a, [CURRENT_MAP_PTR + 1] ld h, a ld a, b ADD16 hl ; B = BYTES_LEFT ld b, COL_BUFFER_SIZE ld de, PENDING_COL_DATA .pad_left: ; Check Y < 0 bit 7, c jr z, .adjust_hl ; Check BYTES_LEFT > 0 ld a, b or a ret z ; *ROW++ = 0 ld a, TILE_INDEX_BACKGROUND ld [de], a inc de ; Y++, BYTES_LEFT-- inc c dec b jr .pad_left .adjust_hl: push de ; HL += Y * MAP_WIDTH ld d, 0 ld a, [CURRENT_MAP_WIDTH] ld e, a ld a, c .get_map_col_ptr: or a jr z, .done_get_col_map_ptr add hl, de dec a jr .get_map_col_ptr .done_get_col_map_ptr: pop de .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] add TILE_INDEX_BACKGROUND 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 ld a, TILE_INDEX_BACKGROUND 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, COL_BUFFER_SIZE .zero_row_loop: ld [hl+], a dec c jr nz, .zero_row_loop ret ; Basically enqueue_col_write but for entire row, used during map load ; TODO: Deduplicate ; @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 init_row_write: ; 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 ; DE = HL ld d, h ld e, l ; HL = CURRENT_MAP_PTR ld a, [CURRENT_MAP_PTR] ld l, a ld a, [CURRENT_MAP_PTR + 1] ld h, a push de ; 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: pop de ; C = BYTES_LEFT ld c, SCRN_VX_B ; If X > 0, increment map pointer by X ; TODO: Remove this branch by adding X before copy_middle bit 7, b jr nz, .pad_left ld a, b ADD16 hl jr .copy_middle .pad_left: ; Check X < 0 bit 7, b jr z, .copy_middle ; Check BYTES_LEFT > 0 ld a, c or a ret z ; *ROW++ = 0 ld a, TILE_INDEX_BACKGROUND 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+] add TILE_INDEX_BACKGROUND 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 ld a, TILE_INDEX_BACKGROUND ld [de], a inc de ; X++, BYTES_LEFT-- inc b dec c jr .pad_right .zero_row: xor a ld c, SCRN_VX_B .zero_row_loop: ld [hl+], a dec c jr nz, .zero_row_loop ret