; Title:	ZX Spectrum Stack Based Scroller
; Author:	Dean Belfield
; Created:	30/01/2020
; Last Updated:	03/02/2020	
; Requires:
; Modinfo:
; 30/01/2020	Initial proof-of-concept - missing mapping code
; 03/02/2020	Tilesets for each row are now pulled from the map data

; Initialise
; DE = Map Array (12 bytes long)
Initialise_Scroll:	LD HL, (Vertical_Scroll_Offset)	; Work out the map offset
			LD A,L				; Mask out lower nibble
			AND 0xF0			; to give us the index into
			LD L,A				; the 16 byte wide map table
			ADD HL, DE 			; Add to map address
			EX DE, HL  			; Swap into DE

; Write out the self-modding code for the scroll routine
			LD HL, Scroll_Write_Row_00	; Self-mod the map code
			LD BC, Scroll_Mod_00 + 1
			CALL Initialise_Scroll_0
			LD HL, Scroll_Write_Row_01
			LD BC, Scroll_Mod_01 + 1
			CALL Initialise_Scroll_0
			LD HL, Scroll_Write_Row_02
			LD BC, Scroll_Mod_02 + 1			
			CALL Initialise_Scroll_0
			LD HL, Scroll_Write_Row_03
			LD BC, Scroll_Mod_03 + 1
			CALL Initialise_Scroll_0
			LD HL, Scroll_Write_Row_04
			LD BC, Scroll_Mod_04 + 1
			CALL Initialise_Scroll_0
			LD HL, Scroll_Write_Row_05
			LD BC, Scroll_Mod_05 + 1
			CALL Initialise_Scroll_0
			LD HL, Scroll_Write_Row_06
			LD BC, Scroll_Mod_06 + 1
			CALL Initialise_Scroll_0
			LD HL, Scroll_Write_Row_07
			LD BC, Scroll_Mod_07 + 1
			CALL Initialise_Scroll_0
			LD HL, Scroll_Write_Row_08
			LD BC, Scroll_Mod_08 + 1
			CALL Initialise_Scroll_0
			LD HL, Scroll_Write_Row_09
			LD BC, Scroll_Mod_09 + 1
			CALL Initialise_Scroll_0
			LD HL, Scroll_Write_Row_10
			LD BC, Scroll_Mod_10 + 1
			CALL Initialise_Scroll_0
			LD HL, Scroll_Write_Row_11
			LD BC, Scroll_Mod_11 + 1
			CALL Initialise_Scroll_0
			LD HL, Scroll_Write_Row_12
			LD BC, Scroll_Mod_12 + 1

; Read a map row in and write out the self-modding code
; Parameters:
;  DE - Map
;  HL - Buffer
;  BC - Address to store the tileset address in
; The self-modding code is essentially a series of PUSH instructions
; where each PUSH instruction corresponds to the tile that we want to 
; display in that column. There are only 5 possible graphics, with
; HL being reserved for a blank tile. The code is terminated with a JP
; instruction back to the scroll routine
;       C5 - Push BC = 00
;       D5 - Push DE = 10
;       E5 - Push HL = 20
;       F5 - Push AF = 30
;    DD E5 - Push IX = 40 (note IX and IY require two bytes written out)
;    FD E5 - Push IY = 50
; C3 LL HH - JP nn - where nn is the address of Scroll_Rentry_Point
; Need a buffer size of 12 x 2 + 3
Initialise_Scroll_0:	LD A, (DE)			; Get the tileset low byte
			LD (BC), A			; Store in self mod code
			LD A, (DE)
			LD (BC), A
			INC DE				; Skip the spare two bytes
			LD B, 12			; Numbe of tile columns to write out
			LD C, 0x40			; Use this to check for IX and IY
Initialise_Scroll_1:	LD A,(DE)			; Get first map tile
			CP C				; Are we writing out AF, BC, DE or HL
			JR C, Initialise_Scroll_1B	; Yes, so jump to write out a single byte PUSH
			JR Z, Initialise_Scroll_IX	; If exactly 0x40 then jump to write out PUSH IX
; Write out IY (FD E5)					; So we must be writing out IY at this point
			LD (HL), 0xFD
			LD (HL), 0xE5
			JR Initialise_Scroll_Ret
; Write out IX (DD E5)
Initialise_Scroll_IX:	LD (HL), 0xDD
			LD (HL), 0xE5
			JR Initialise_Scroll_Ret
; Write out AF, BC, DE, HL (C5, D5, E5 and F5)
Initialise_Scroll_1B:	ADD A, 0xC5			; Quickly add 0xC5 to the tile # to get the PUSH opcode
			LD (HL), A			; Write out the PUSH instruction here!
; Write out final JP instruction (C3 LL HH)
Initialise_Scroll_Ret:	INC HL				; Loop to next byte of memory to write out
			INC DE				; And the next tile address
			DJNZ Initialise_Scroll_1	; Jump to next tile column
			LD (HL), 0xC3			; Here we're writing out a JP instruction
			LD (HL),low Scroll_Rentry_Point ; These are self-modding with the correct value at
			INC HL				; top of function Initialise_Scroll with the 
			LD (HL),high Scroll_Rentry_Point

; Stack-based scroll routine
; Scrolls a 24x24 character block vertically - each tile is 16x16 pixels
Scroll:			LD DE, 0x4018			; Point to the screen
Scroll_Mod_00:		LD HL, 0			; Point to tileset
			LD IX, Scroll_Write_Row_00
			LD A, (Vertical_Scroll_Offset)	; Get offset for first row into tileset
			AND 15
			LD C, A
			SLA A				; Multiply by 10 (5 possible tiles in the set
			SLA A				; each of them a word wide
			ADD A, C
			SLA A
			LD B, 0				; Load into BC
			LD C, A	
			ADD HL, BC			; And add to the tile offset
			LD A, (Vertical_Scroll_Offset)	; Get the # of lines to output for first tile
			AND 15
			LD B, A
			LD A, 16
			SUB B
			LD B, A				; Partial number of rows for first tile
			CALL Scroll_Tile_Part		; Draw the first partial row
Scroll_Mod_01:		LD HL, 0			; Address of the tileset for this row
			LD IX, Scroll_Write_Row_01
			CALL Scroll_Tile_Full		; Draw 11 more full rows of tiles
Scroll_Mod_02:		LD HL, 0
			LD IX, Scroll_Write_Row_02
			CALL Scroll_Tile_Full
Scroll_Mod_03:		LD HL, 0
			LD IX, Scroll_Write_Row_03
			CALL Scroll_Tile_Full
Scroll_Mod_04:		LD HL, 0
			LD IX, Scroll_Write_Row_04
			CALL Scroll_Tile_Full
Scroll_Mod_05:		LD HL, 0
			LD IX, Scroll_Write_Row_05
			CALL Scroll_Tile_Full
Scroll_Mod_06:		LD HL, 0
			LD IX, Scroll_Write_Row_06
			CALL Scroll_Tile_Full
Scroll_Mod_07:		LD HL, 0
			LD IX, Scroll_Write_Row_07
			CALL Scroll_Tile_Full
Scroll_Mod_08:		LD HL, 0
			LD IX, Scroll_Write_Row_08
			CALL Scroll_Tile_Full
Scroll_Mod_09:		LD HL, 0
			LD IX, Scroll_Write_Row_09
			CALL Scroll_Tile_Full
Scroll_Mod_10:		LD HL, 0
			LD IX, Scroll_Write_Row_10
			CALL Scroll_Tile_Full
Scroll_Mod_11:		LD HL, 0
			LD IX, Scroll_Write_Row_11
			CALL Scroll_Tile_Full
Scroll_Mod_12:		LD HL, 0			; And finally, draw the end part if required
			LD IX, Scroll_Write_Row_12
			LD A, (Vertical_Scroll_Offset)
			AND 15
			RET Z
			LD B, A
			JR Scroll_Tile_Part

; Write out a single row of tiles
; HL - Address of the tileset for this row of tiles
; DE - Screen Address
; IX - Address of the routine to push out a pixel row of the tiles
Scroll_Tile_Full:	LD B, 16			; Set the tile height in B
Scroll_Tile_Part:	LD (Scroll_03 + 1), SP		; Save the stack pointer
			LD (Scroll_02 + 1), IX		; Save the draw line routine
Scroll_01:		LD SP,HL			; Point the stack at the tileset
			LD HL, 10			; Go to the next line of the tileset
			EXX				; Switch to alternate registers
			POP AF				; Pop the tileset into the AF, BC', DE', IX and IY
			POP BC				; ..
			POP DE				; ..
			POP IX				; ..
			POP IY				; ..
			LD HL, 0			; Set HL' to zero pixels
			EXX				; Switch back to normal registers
			EX DE, HL			; Swap the screen address (in DE) into HL
			LD SP, HL			; And load into the stack pointer
			EXX				; Switch back to the alternate registers
Scroll_02:		JP 0				; Write out a row of pixels
Scroll_Rentry_Point:	EXX				; Switch back to the normal registers
			EX DE, HL			; Swap DE and HL back again
			INC D				; Drop down to the next pixel line of the screen
			LD A, D
			AND 0x07
			JR NZ, Scroll_04		; If we've gone over a character boundary, then
			LD A, E				; Drop down one character in screen memory
			ADD A, 32
			LD E, A
			JR C, Scroll_04			; If we've gone over a screen third boundary
			LD A, D				; Drop down to the next third
			SUB 8
			LD D,A
Scroll_04:		DJNZ Scroll_01			; Loop
Scroll_03:		LD SP, 0			; Restore the stack pointer

Vertical_Scroll_Offset:	DW 0				; Vertical scroll offset

; Buffer for all the PUSH instructions for a single scroll - self modded code written
; by Initialise_Scroll is written in here
; These are called in sequence in the function Scroll
Scroll_Write_Row_00:	DEFS 27, 0
Scroll_Write_Row_01:	DEFS 27, 0
Scroll_Write_Row_02:	DEFS 27, 0
Scroll_Write_Row_03:	DEFS 27, 0
Scroll_Write_Row_04:	DEFS 27, 0
Scroll_Write_Row_05:	DEFS 27, 0
Scroll_Write_Row_06:	DEFS 27, 0
Scroll_Write_Row_07:	DEFS 27, 0
Scroll_Write_Row_08:	DEFS 27, 0
Scroll_Write_Row_09:	DEFS 27, 0
Scroll_Write_Row_10:	DEFS 27, 0
Scroll_Write_Row_11:	DEFS 27, 0
Scroll_Write_Row_12:	DEFS 27, 0