; Simple routines to control sprites.
; They're not written for high performance, but for simplicity.
; That's why they control 1 sprite at a time. The exception is pos_batch().
; which is quite efficient to update sprite positions of multiple sprites in one go.

; note: sprites z-order will be in front of all layers.
; note: collision mask is not supported here yet.

sprites {
    const ubyte SIZE_8  = 0
    const ubyte SIZE_16 = 1
    const ubyte SIZE_32 = 2
    const ubyte SIZE_64 = 3
    const ubyte COLORS_16 = 0
    const ubyte COLORS_256 = 128
    const uword VERA_SPRITEREGS = $fc00     ; $1fc00
    uword @zp sprite_reg

    sub init(ubyte spritenum,
             ubyte databank, uword dataaddr,
             ubyte width_flag, ubyte height_flag,
             ubyte colors_flag, ubyte palette_offset_idx) {
        hide(spritenum)
        cx16.VERA_DC_VIDEO |= %01000000             ; enable sprites globally
        dataaddr >>= 5
        dataaddr |= (databank as uword)<<11
        sprite_reg = VERA_SPRITEREGS + spritenum*$0008
        cx16.vpoke(1, sprite_reg, lsb(dataaddr))                    ; address 12:5
        cx16.vpoke(1, sprite_reg+1, colors_flag | msb(dataaddr))    ; 4 bpp + address 16:13
        cx16.vpoke(1, sprite_reg+6, %00001100)                      ; z depth %11 = in front of both layers, no flips
        cx16.vpoke(1, sprite_reg+7, height_flag<<6 | width_flag<<4 | palette_offset_idx>>4) ; 64x64 pixels, palette offset
    }

    sub data(ubyte spritenum, ubyte bank, uword addr) {
        addr >>= 5
        addr |= (bank as uword)<<11
        sprite_reg = VERA_SPRITEREGS + spritenum*$0008
        cx16.vpoke(1, sprite_reg, lsb(addr))                    ; address 12:5
        cx16.vpoke_mask(1, sprite_reg+1, %11110000, msb(addr))    ; address 16:13
    }

    sub pos(ubyte spritenum, word xpos, word ypos) {
        sprite_reg = VERA_SPRITEREGS + 2 + spritenum*$0008
        cx16.vpoke(1, sprite_reg, lsb(xpos))
        cx16.vpoke(1, sprite_reg+1, msb(xpos))
        cx16.vpoke(1, sprite_reg+2, lsb(ypos))
        cx16.vpoke(1, sprite_reg+3, msb(ypos))
    }

    sub pos_batch(ubyte first_spritenum, ubyte num_sprites, uword xpositions_ptr, uword ypositions_ptr) {
        ; -- note: the x and y positions word arrays must be regular arrays, they cannot be @split arrays!
        sprite_reg = VERA_SPRITEREGS + 2 + first_spritenum*$0008
        cx16.vaddr_autoincr(1, sprite_reg, 0, 8)
        cx16.vaddr_autoincr(1, sprite_reg+1, 1, 8)
        repeat num_sprites {
            cx16.VERA_DATA0 = @(xpositions_ptr)
            xpositions_ptr ++
            cx16.VERA_DATA1 = @(xpositions_ptr)
            xpositions_ptr ++
        }
        sprite_reg += 2
        cx16.vaddr_autoincr(1, sprite_reg, 0, 8)
        cx16.vaddr_autoincr(1, sprite_reg+1, 1, 8)
        repeat num_sprites {
            cx16.VERA_DATA0 = @(ypositions_ptr)
            ypositions_ptr ++
            cx16.VERA_DATA1 = @(ypositions_ptr)
            ypositions_ptr ++
        }
    }

    sub setx(ubyte spritenum, word xpos) {
        sprite_reg = VERA_SPRITEREGS + 2 + spritenum*$0008
        cx16.vpoke(1, sprite_reg, lsb(xpos))
        cx16.vpoke(1, sprite_reg+1, msb(xpos))
    }

    sub sety(ubyte spritenum, word ypos) {
        sprite_reg = VERA_SPRITEREGS + 4 + spritenum*$0008
        cx16.vpoke(1, sprite_reg, lsb(ypos))
        cx16.vpoke(1, sprite_reg+1, msb(ypos))
    }

    sub move(ubyte spritenum, word dx, word dy) {
        ; move a sprite based on its current position
        sprite_reg = VERA_SPRITEREGS + 2 + spritenum*$0008
        cx16.r1 = mkword(cx16.vpeek(1, sprite_reg+1), cx16.vpeek(1, sprite_reg)) as word + dx
        cx16.r2 = mkword(cx16.vpeek(1, sprite_reg+3), cx16.vpeek(1, sprite_reg+2)) as word + dy
        cx16.vpoke(1, sprite_reg, cx16.r1L)
        cx16.vpoke(1, sprite_reg+1, cx16.r1H)
        cx16.vpoke(1, sprite_reg+2, cx16.r2L)
        cx16.vpoke(1, sprite_reg+3, cx16.r2H)
    }

    sub movex(ubyte spritenum, word dx) {
        ; move a sprite horizontally based on its current position
        sprite_reg = VERA_SPRITEREGS + 2 + spritenum*$0008
        cx16.r1 = mkword(cx16.vpeek(1, sprite_reg+1), cx16.vpeek(1, sprite_reg)) as word + dx
        cx16.vpoke(1, sprite_reg, cx16.r1L)
        cx16.vpoke(1, sprite_reg+1, cx16.r1H)
    }

    sub movey(ubyte spritenum, word dy) {
        ; move a sprite vertically based on its current position
        sprite_reg = VERA_SPRITEREGS + 4 + spritenum*$0008
        cx16.r1 = mkword(cx16.vpeek(1, sprite_reg+1), cx16.vpeek(1, sprite_reg)) as word + dy
        cx16.vpoke(1, sprite_reg, cx16.r1L)
        cx16.vpoke(1, sprite_reg+1, cx16.r1H)
    }

    sub getx(ubyte spritenum) -> word {
        sprite_reg = VERA_SPRITEREGS + 2 + spritenum*$0008
        return mkword(cx16.vpeek(1, sprite_reg+1), cx16.vpeek(1, sprite_reg)) as word
    }

    sub gety(ubyte spritenum) -> word {
        sprite_reg = VERA_SPRITEREGS + 4 + spritenum*$0008
        return mkword(cx16.vpeek(1, sprite_reg+1), cx16.vpeek(1, sprite_reg)) as word
    }

    sub hide(ubyte spritenum) {
        pos(spritenum, -64, -64)
    }

    sub flipx(ubyte spritenum, bool flipped) {
        cx16.vpoke_mask(1, VERA_SPRITEREGS + 6 + spritenum*$0008, %11111110, flipped)
    }

    sub flipy(ubyte spritenum, bool flipped) {
        cx16.vpoke_mask(1, VERA_SPRITEREGS + 6 + spritenum*$0008, %11111101, flipped<<1)
    }

    sub set_palette_offset(ubyte spritenum, ubyte offset) {
        cx16.vpoke_mask(1, VERA_SPRITEREGS + 7 + spritenum*$0008, %11110000, offset>>4)
    }
}
