;;;
;; This is a simple example of sprite cycling, so sprites don't disappear on a
;; scanline overflow. This example is extremely simple, and on a real game you'd
;; take into account lots of things that would make this process less tedious
;; and less cycle consuming.

.segment "HEADER"
    .byte 'N', 'E', 'S', $1A

    .byte $02, $01

    .byte $00
    .byte $00

.segment "VECTORS"
    .addr nmi, reset, irq

.segment "CHARS"
.incbin "../assets/diskun.chr"

.segment "CODE"

.include "../shared/diskun.s"
.include "../shared/clear.s"

.proc main
    CLEAR_SCREEN

    jsr Diskun::init_palettes
    jsr init_sprites

    cli
    lda #%10001000
    sta $2000                   ; PPUCTRL
    lda #%00011110
    sta $2001                   ; PPUMASK

@main_game_loop:
    ;; NOTE: the logic is pretty simple: read the pad, move the player
    ;; accordingly, and apply the flickering effect.
    READ_JOYPAD1
    jsr Diskun::update

    ;; NOTE: comment this `jsr` out if you want to see what happens if no
    ;; flickering effect is applied (spoiler alert: the last character will
    ;; suddenly disappear :P).
    ;;
    ;; NOTE: this function is called mindlessly. In a real game you'd try to
    ;; detect scanline overflows, or you will try to be more careful with sprite
    ;; priorities and apply the flickering more carefully. Here it's applied for
    ;; (almost) every sprite on each frame and we roll with it.
    jsr apply_flicker

    lda #%10000000
    ora $20
    sta $20
@wait_for_render:
    bit $20
    bmi @wait_for_render

    jmp @main_game_loop
.endproc

;;;
;; NOTE: this is the actual meat of the example :D
.proc apply_flicker
    ;; We will store a 16-bit pointer to the first sprite that can be flickered.
    ;; In this case, we will point to the first sprite from the first NPC. That
    ;; is, we suppose that, for whatever reason, we don't want to apply the
    ;; flickering effect for the player.
    lda #$10
    sta $40
    lda #$02
    sta $41

    ;;;
    ;; The algorithm is really straight-forward here: we will simply cycle the
    ;; sprites starting from the one being pointed at $40-$41 until the last
    ;; sprite. The main idea is that the OAM will show sprites on a scanline in
    ;; order. Hence, those sprites that are left beyond the eigth on a scanline
    ;; will suddenly disappear. Flickering is a technique by which sprites are
    ;; rotated from OAM priority, and hence they won't disappear always, but
    ;; just for a small amount of time: just enough to see them, creating a
    ;; flickering effect.
    ;;
    ;; For this, we will reserve four bytes (1 sprite) for auxiliary temporary
    ;; values ($42-$45) which will then be used in order to carry values along
    ;; the cycle.

    ;; We initialize the auxiliary bytes with the data from the last sprite to
    ;; be iterated since these are the values to be stored for the first sprite
    ;; now (that is, the last element is now the first one).
    lda $24C
    sta $42
    lda $24D
    sta $43
    lda $24E
    sta $44
    lda $24F
    sta $45

@loop:
    ;; For each of the four bytes from a sprite, pick the value stored on its
    ;; auxiliary byte and save the old value into the auxiliary byte afterwards.
    ;; This way the current byte holds the value from the previous sprite, but
    ;; its value will be carry on into the next sprite.
    .repeat 4, I
        ldy #I
        lda $42, y
        pha
        lda ($40), y
        sta $42, y
        pla
        sta ($40), y
    .endrepeat

    ;; And move the sprite pointer 4 bytes (1 sprite). Note that we only need to
    ;; move the low byte since we know beforehand that everything will be inside
    ;; of the $2xx range.
    lda $40
    clc
    adc #4
    sta $40

    ;; We know that the last byte from the last sprite is held at $24F. Thus, if
    ;; the sprite pointer is already passed this point, we can break the loop.
    ;; Otherwise just carry on.
    cmp #$50
    bne @loop

    rts
.endproc

;;;
;; NOTE: and from here on stuff that is not relevant for sprite flickering.

.proc nmi
    bit $20
    bpl @next

    pha
    txa
    pha
    tya
    pha

    jsr Diskun::nmi_update

    lda #$00
    sta $2003                   ; OAMADDR
    lda #$02
    sta $4014                   ; OAMDMA

    bit $2002                   ; PPUSTATUS
    lda #$00
    sta $2005                   ; PPUSCROLL
    sta $2005                   ; PPUSCROLL

    lda #%01111111
    and $20
    sta $20

    pla
    tay
    pla
    tax
    pla
@next:
    rti
.endproc

.proc reset
    sei
    cld

    ldx #$40
    stx $4017                   ; APU Frame Counter

    ldx #$FF
    txs

    inx
    stx $2000                   ; PPUCTRL
    stx $2001                   ; PPUMASK
    stx $4010                   ; APU DMC

    bit $2002                   ; PPUSTATUS
@vblankwait1:
    bit $2002                   ; PPUSTATUS
    bpl @vblankwait1

    ldx #0
    lda #0
@ram_reset_loop:
    sta $000, x
    sta $100, x
    sta $300, x
    sta $400, x
    sta $500, x
    sta $600, x
    sta $700, x
    inx
    bne @ram_reset_loop         ; if x overflows back to #00, then we are done.

    lda #$EF
@sprite_reset_loop:
    sta $200, x
    inx
    bne @sprite_reset_loop

    lda #$00
    sta $2003                   ; OAMADDR
    lda #$02
    sta $4014                   ; OAMDMA

@vblankwait2:
    bit $2002                   ; PPUSTATUS
    bpl @vblankwait2

    lda #$3F
    sta $2006                   ; PPUADDR
    lda #$00
    sta $2006                   ; PPUADDR

    lda #$0F
    ldx #$20
@palettes_reset_loop:
    sta $2007                   ; PPUDATA
    dex
    bne @palettes_reset_loop

    jmp main
.endproc

.proc irq
    rti
.endproc

.proc init_sprites
    NUM_SPRITES = 20

    lda #$40
    sta Diskun::m_screen_y
    lda #$46
    sta Diskun::m_screen_x

    ldx #$00
@load_sprites_loop:
    lda initial_sprite_data, x
    sta $0200, x
    inx
    cpx #(4 * NUM_SPRITES)
    bne @load_sprites_loop
    rts
initial_sprite_data:
    ;; $200-$20F
    .byte $40, $01, %00000000, $46
    .byte $40, $01, %01000000, $4E
    .byte $48, $11, %00000000, $46
    .byte $48, $11, %01000000, $4E

    ;; $210-$21F
    .byte $60, $01, %00000000, $68
    .byte $60, $01, %01000000, $70
    .byte $68, $11, %00000000, $68
    .byte $68, $11, %01000000, $70

    ;; $220-$22F
    .byte $60, $01, %00000000, $7A
    .byte $60, $01, %01000000, $82
    .byte $68, $11, %00000000, $7A
    .byte $68, $11, %01000000, $82

    ;; $230-$23F
    .byte $60, $01, %00000000, $8C
    .byte $60, $01, %01000000, $94
    .byte $68, $11, %00000000, $8C
    .byte $68, $11, %01000000, $94

    ;; $240-$24F
    .byte $60, $01, %00000000, $9E
    .byte $60, $01, %01000000, $A6
    .byte $68, $11, %00000000, $9E
    .byte $68, $11, %01000000, $A6
.endproc
