;; This example demonstrates how to draw a compressed sprite on the screen.
;;
;; The compressed sprite data uses control codes to determine the following:
;; 1. end of line
;; 2. draw x bytes containing only opaque pixels
;; 3. skip x bytes containing only transparent pixels
;; 4. draw x bytes which have mask and data
;;
;; Compressed sprites are a good way of storing large sprites that are not rectangular
;; in shape and which have a lot of transparent pixels around them.
;; 
;; The idea is to save both space, and to save time drawing them - no need to mask every byte
;; as is done with a standard sprite drawing routine.
;;
;; Clipping a compressed sprite is more difficult because it's not so easy to determine which area to draw and
;; what not to draw.
;;
;; There are 2 drawing functions. One which shows the sprite, the other shows the control codes.
;;
;; When control codes are drawn:
;; - flashing pink/blue shows the fully opaque regions
;; - white shows fully transparent regions
;; - grey shows the masked regions
;;
;; You can see that we don't draw anything on the right side. In a normal sprite routine we would be masking the
;; pixels in this area although nothing would be drawn and we would waste time for a large sprite.
;;
;; Other ways to draw a large sprite would be to split it into smaller sprites and draw them together.

org &8000
;; turn off listing; remove for pasmo assembler
nolist

scr_set_mode equ &bc0e
txt_output equ  &bb5a
mc_wait_flyback equ &bd19
km_test_key equ &bb1e
scr_set_ink equ &bc32

sprite_height equ 25							;; sprite height in lines
sprite_width_pixels equ 24						;; sprite width in mode 0 pixels
sprite_width_bytes equ sprite_width_pixels/2			;; sprite width in bytes

;;-----------------------------------------------------------------------------------------------------

start:
;; set mode to 0
ld a,0
call scr_set_mode


;; put something on the screen so we can see the sprite masking working
ld d,'A'
ld b,24
l2:
ld c,20
l1:
ld a,d
call txt_output
dec c
jr nz,l1
inc d
dec b
jr nz,l2

;; make table of screen addresses for the start of each scanline
call make_scr_table

;; draw sprite
call draw_sprite_cur_coords

;;---------------------------------------------------
main_loop:
;; wait for VSYNC
call mc_wait_flyback

;; get current coords and store them in prev coords
;; we compare new coords to previous coords to determine
;; if we should erase and then redraw the sprite.
;; this avoids continuous flicker if the sprite is not moving
;; however, the sprite can still disapear when moving around
;; the screen because there is a time when the sprite has been
;; deleted, the monitor has drawn it when it has been deleted
;; and we then draw the new version too late.
ld hl,(sprite_coords)
ld (prev_coords),hl

;; check keys
call do_keys

;; wait for next interrupt
;; this ensures our code takes longer than the vsync time
;; so we can always wait just for the start of the vsync
halt

;; draw the sprite if it has moved
call redraw_sprite

jp main_loop

;;-----------------------------------------------------------------------------------------------------

redraw_sprite:
;; coords changed?
ld hl,(prev_coords)
ld bc,(sprite_coords)
or a
sbc hl,bc
ld a,h
or l
jr nz,rs1

;; no change in x or y, so no update
ret
;;-----------------------------------------------------------------------------------------------------

rs1:

;; restore background (where sprite used to be)
ld hl,(prev_coords)
ld de,sprite_background
ld b,sprite_height
ld c,sprite_width_bytes
call sprite_background_restore

draw_sprite_cur_coords:

;; store background pixels where sprite is now located
ld hl,(sprite_coords)
ld de,sprite_background
ld b,sprite_height
ld c,sprite_width_bytes
call sprite_background_store

ld hl,(sprite_coords)

;; uncomment this line to draw data to show sprite codes
;;call draw_sprite_code

;; uncomment this line to draw sprite for real
call draw_sprite
ret
;;-----------------------------------------------------------------------------------------------------
;; initialise a table 

make_scr_table:
ld hl,&c000
ld b,200
ld ix,scr_table
mst1:
ld (ix+0),l
ld (ix+1),h
call scr_next_line
inc ix
inc ix
djnz mst1
ret

;;-----------------------------------------------------------------------------------------------------

scr_table:
defs 200*2

;;-----------------------------------------------------------------------------------------------------

;; input conditions:
;; H = x byte coordinate (0-((scr_width_chars*2)-1))
;; L = y coordinate (0-((scr_height_chars*char_height_lines)-1))
;; output conditions:
;; HL = screen address

get_scr_addr:
push bc
push de
ld d,0
ld e,h    ;; DE = x coordinate

ld h,0  ;; HL = y coordinate
add hl,hl
;; x2 for offset into table (2 bytes per y line)
ld bc,scr_table
add hl,bc
ld a,(hl)
inc hl
ld h,(hl)
ld l,a
;; HL = screen address for start of line

;; now add on X
add hl,de
pop de
pop bc
ret

;;-----------------------------------------------------------------------------------------------------
;; input conditions:
;; HL = screen address
;; output conditions:
;; HL = screen address (next scanline down)
;;

scr_next_line:
;; go down next scan line
ld a,h
add a,8
ld h,a
ret nc
;; add on amount to go to next char line
ld a,l
add a,&50
ld l,a
ld a,h
adc a,&c0
ld h,a
ret

;;-----------------------------------------------------------------------------------------------------

code_eol equ 0
code_transparent equ &40
code_mask equ &80
code_opaque equ &c0

;; this function draws a special byte for each of the codes. It is used
;; to show how the sprite is described.
draw_sprite_code:
call get_scr_addr
ld de,sprite_pixels
ld b,sprite_height

dsc_next_line:
push bc
push hl

dsc_next_code:

ld a,(de)     ;; get code/count
and &c0
jr z,dsc_spr_eol    ;; end of line?
cp code_transparent
jr z,dsc_spr_trans
cp code_mask
jr z,dsc_spr_mask

;; drop through to opaque

;; draw opaque pixels
ld a,(de)
and &3f
ld b,a
inc de

dsc_op1:
ld (hl),&ff
inc hl
inc de
djnz dsc_op1

jr dsc_next_code

;;------------------------------------------------
;; skip fully transparent pixels
dsc_spr_trans:

ld a,(de)  ;; get count
and &3f
ld b,a
inc de

;; skip bytes on screen
dsc_st1:
ld (hl),&30
;; increment screen address
inc hl
djnz dsc_st1
jr dsc_next_code

;;--------------------------------------------
;; mask pixels
dsc_spr_mask:
;; get count
ld a,(de)
and &3f
ld b,a

inc de

dsc_mask1:
ld (hl),&3
inc hl
inc de
inc de

djnz dsc_mask1

jr dsc_next_code

;;--------------------------------------------

dsc_spr_eol:
inc de
pop hl
call scr_next_line
pop bc
djnz dsc_next_line
ret







;; this is the real sprite drawing code

draw_sprite:
call get_scr_addr
ld de,sprite_pixels
ld b,sprite_height


ds_next_line:
push bc
push hl

ds_next_code:

ld a,(de)     ;; get code/count
and &c0
jr z,ds_spr_eol    ;; end of line?
cp code_transparent
jr z,ds_spr_trans
cp code_mask
jr z,ds_spr_mask

;; drop through to opaque

;; draw opaque pixels
ld a,(de)
and &3f
ld b,a
inc de

ds_op1:
;; read pixels
ld a,(de)
;; write directly to screen
ld (hl),a
inc hl
inc de
djnz ds_op1

jr ds_next_code

;;------------------------------------------------
;; skip fully transparent pixels
ds_spr_trans:

ld a,(de)  ;; get count
and &3f
ld b,a
inc de

;; skip bytes on screen
ds_st1:
;; increment screen address
inc hl
djnz ds_st1
jr ds_next_code

;;--------------------------------------------
;; mask pixels
ds_spr_mask:
;; get count
ld a,(de)
and &3f
ld b,a

inc de

ds_mask1:

;; read mask
ld a,(de)
;; mask with screen
and (hl)
;; store this temporarily
ld c,a

inc de

;; get pixels
ld a,(de)
;; combine with masked screen pixels
or c
;; write back to screen
ld (hl),a
;; increment screen address
inc hl
inc de

djnz ds_mask1

jr ds_next_code

;;--------------------------------------------

ds_spr_eol:
inc de
pop hl
call scr_next_line
pop bc
djnz ds_next_line
ret

;;-----------------------------------------------------------------------------------------------------

;; the compiled sprite pixel data
;; - normal pixel data (uncompressed) would be 300 bytes long.
;; but we should also consider how long it would take to draw.
sprite_pixels:
 ;;288 bytes
defb &042,&081,&0aa,&044,&0c1,&0cc,&000,&042,&0c2,&0cc,&03c,&081,&055,&028,&000,&042
defb &0c3,&09c,&03c,&09c,&000,&042,&0c2,&09c,&03c,&081,&055,&028,&044,&081,&0aa,&050
defb &0c1,&0f0,&081,&055,&0a0,&000,&042,&0c2,&0cc,&03c,&045,&081,&0aa,&050,&0c1,&0f0
defb &081,&055,&0a0,&000,&041,&081,&0aa,&044,&081,&055,&088,&0c1,&03c,&045,&0c1,&0b4
defb &081,&055,&0a0,&000,&043,&0c1,&00c,&081,&055,&008,&043,&0c2,&00c,&0b4,&000,&041
defb &0c9,&00c,&00c,&00c,&00c,&00c,&00c,&00c,&00c,&0b4,&000,&081,&0aa,&004,&0c7,&00c
defb &00c,&00c,&00c,&00c,&0c0,&00c,&081,&055,&080,&000,&081,&0aa,&004,&0c5,&00c,&00c
defb &00c,&00c,&00c,&000,&081,&0aa,&004,&0c5,&00c,&00c,&00c,&00c,&00c,&000,&081,&0aa
defb &004,&0c4,&084,&00c,&00c,&00c,&081,&055,&008,&000,&0c5,&00c,&084,&00c,&00c,&00c
defb &081,&055,&008,&000,&0c1,&00c,&081,&055,&080,&0c3,&00c,&00c,&00c,&000,&0c1,&00c
defb &081,&055,&080,&0c3,&00c,&00c,&00c,&000,&0c1,&03c,&041,&081,&0aa,&010,&0c1,&030
defb &081,&055,&020,&000,&0c1,&03c,&041,&081,&0aa,&010,&0c1,&030,&081,&055,&020,&000
defb &0c1,&03c,&041,&081,&0aa,&010,&0c1,&030,&081,&055,&020,&000,&081,&055,&028,&041
defb &081,&0aa,&010,&0c1,&030,&081,&055,&020,&000,&042,&081,&0aa,&010,&0c2,&030,&030
defb &000,&042,&081,&0aa,&010,&0c2,&030,&030,&000,&042,&081,&0aa,&010,&0c2,&030,&030
defb &081,&055,&020,&000,&042,&081,&0aa,&010,&081,&055,&020,&081,&0aa,&010,&081,&055
defb &020,&000,&042,&081,&0aa,&010,&0c1,&0cc,&081,&0aa,&010,&0c1,&064,&081,&055,&088
defb &000,&042,&081,&0aa,&044,&0c1,&0cc,&081,&0aa,&044,&0c1,&0cc,&081,&055,&088,&000
;; sprite
;;-----------------------------------------------------------------------------------------------------
;; check keyboard
;;
;; cursor keys

do_keys:
ld a,0*8+0
call km_test_key
jr nz,move_up
ld a,0*8+1
call km_test_key
jr nz,move_right
ld a,0*8+2
call km_test_key
jr nz,move_down
ld a,1*8+0
call km_test_key
jr nz,move_left
ret
;;-----------------------------------------------------------------------------------------------------

move_up:
;; do not move up if we are already on line 0
ld a,(y_coord)
or a
ret z
dec a
ld (y_coord),a
ret
;;-----------------------------------------------------------------------------------------------------

move_down:
;; avoid going off bottom by checking for bottom-most y position

ld a,(y_coord)
sub 200-sprite_height
ret nc      ;; greater than or equal to this pos then don't increment

ld a,(y_coord)
inc a
ld (y_coord),a
ret
;;-----------------------------------------------------------------------------------------------------

move_left:
;; avoiding going off left side
ld a,(x_coord)
or a
ret z
dec a
ld (x_coord),a
ret
;;-----------------------------------------------------------------------------------------------------

right_side equ (40*2)-sprite_width_bytes

move_right:
ld a,(x_coord)
sub right_side
ret nc

ld a,(x_coord)
inc a
ld (x_coord),a
ret
;;-----------------------------------------------------------------------------------------------------


;; H = x byte coord
;; L = y line
;; DE = address to store screen data
;; B = height
;; C = width
;; store a rectangle from the screen into a buffer
sprite_background_store:
call get_scr_addr

sprite_back_height:
push bc
push hl

sprite_back_width:
ld a,(hl)				;; read from screen
ld (de),a				;; store to buffer
inc hl
inc de
dec c
jr nz,sprite_back_width

pop hl
call scr_next_line
pop bc
djnz sprite_back_height
ret

;; H = x byte coord
;; L = y line
;; DE = address to store screen data
;; B = height
;; C = width
;;
;; restore a rectangle to the screen
sprite_background_restore:
call get_scr_addr

sprite_reback_height:
push bc
push hl

sprite_reback_width:
ld a,(de)					;; read from buffer
ld (hl),a					;; write to screen
inc hl
inc de
dec c
jr nz,sprite_reback_width

pop hl
call scr_next_line
pop bc
djnz sprite_reback_height
ret

;; a buffer to store screen behind sprite
sprite_background:
defs sprite_height*sprite_width_bytes


prev_coords:
prev_y_coord:
defb 0
prev_x_coord:
defb 0

sprite_coords:
y_coord:
defb 0
x_coord:
defb 0

;; make this executable for pasmo, uncomment for pasmo assembler
end start