ClearSpriteAnims:
	ld hl, wSpriteAnimData
	ld bc, wSpriteAnimDataEnd - wSpriteAnimData
.loop
	ld [hl], 0
	inc hl
	dec bc
	ld a, c
	or b
	jr nz, .loop
	ret

PlaySpriteAnimationsAndDelayFrame:
	call PlaySpriteAnimations
	call DelayFrame
	ret

PlaySpriteAnimations:
	push hl
	push de
	push bc
	push af

	ld a, LOW(wVirtualOAM)
	ld [wCurSpriteOAMAddr], a
	call DoNextFrameForAllSprites

	pop af
	pop bc
	pop de
	pop hl
	ret

DoNextFrameForAllSprites:
	ld hl, wSpriteAnimationStructs
	ld e, NUM_SPRITE_ANIM_STRUCTS

.loop
	ld a, [hl]
	and a
	jr z, .next ; This struct is deinitialized.
	ld c, l
	ld b, h
	push hl
	push de
	call DoAnimFrame ; Uses a massive dw
	call UpdateAnimFrame
	pop de
	pop hl
	jr c, .done

.next
	ld bc, SPRITEANIMSTRUCT_LENGTH
	add hl, bc
	dec e
	jr nz, .loop

	ld a, [wCurSpriteOAMAddr]
	ld l, a
	ld h, HIGH(wVirtualOAM)

.loop2 ; Clear (wVirtualOAM + [wCurSpriteOAMAddr] --> wVirtualOAMEnd)
	ld a, l
	cp LOW(wVirtualOAMEnd)
	jr nc, .done
	xor a
	ld [hli], a
	jr .loop2

.done
	ret

DoNextFrameForFirst16Sprites:
	ld hl, wSpriteAnimationStructs
	ld e, NUM_SPRITE_ANIM_STRUCTS

.loop
	ld a, [hl]
	and a
	jr z, .next
	ld c, l
	ld b, h
	push hl
	push de
	call DoAnimFrame ; Uses a massive dw
	call UpdateAnimFrame
	pop de
	pop hl
	jr c, .done

.next
	ld bc, SPRITEANIMSTRUCT_LENGTH
	add hl, bc
	dec e
	jr nz, .loop

	ld a, [wCurSpriteOAMAddr]
	ld l, a
	ld h, HIGH(wVirtualOAMSprite16)

.loop2 ; Clear (wVirtualOAM + [wCurSpriteOAMAddr] --> Sprites + $40)
	ld a, l
	cp LOW(wVirtualOAMSprite16)
	jr nc, .done
	xor a
	ld [hli], a
	jr .loop2

.done
	ret

_InitSpriteAnimStruct::
; Initialize animation a at pixel x=e, y=d
; Find if there's any room in the wSpriteAnimationStructs array, which is 10x16
	push de
	push af
	ld hl, wSpriteAnimationStructs
	ld e, NUM_SPRITE_ANIM_STRUCTS
.loop
	ld a, [hl]
	and a
	jr z, .found
	ld bc, SPRITEANIMSTRUCT_LENGTH
	add hl, bc
	dec e
	jr nz, .loop
; We've reached the end.  There is no more room here.
; Return carry.
	pop af
	pop de
	scf
	ret

.found
; Back up the structure address to bc.
	ld c, l
	ld b, h

; Increment [wSpriteAnimCount], skipping a 0 value.
	ld hl, wSpriteAnimCount
	inc [hl]
	ld a, [hl]
	and a
	jr nz, .nonzero
	inc [hl]
.nonzero

; Get row a of SpriteAnimSeqData, copy the pointer into de
	pop af
	ld e, a
	ld d, 0
	ld hl, SpriteAnimSeqData
	add hl, de
	add hl, de
	add hl, de
	ld e, l
	ld d, h
; Set hl to the first field (field 0) in the current structure.
	ld hl, SPRITEANIMSTRUCT_INDEX
	add hl, bc
; Load the index.
	ld a, [wSpriteAnimCount]
	ld [hli], a ; SPRITEANIMSTRUCT_INDEX
; Copy the table entry to the next two fields.
	ld a, [de]
	ld [hli], a ; SPRITEANIMSTRUCT_FRAMESET_ID
	inc de
	ld a, [de]
	ld [hli], a ; SPRITEANIMSTRUCT_ANIM_SEQ_ID
	inc de
; Look up the third field in the wSpriteAnimDict mapping.
; Take the mapped value and load it in.
	ld a, [de]
	call GetSpriteAnimVTile
	ld [hli], a ; SPRITEANIMSTRUCT_TILE_ID
	pop de
; Set hl to field 4 (X coordinate).  Kinda pointless, because we're presumably already here.
	ld hl, SPRITEANIMSTRUCT_XCOORD
	add hl, bc
; Load the original value of de into here.
	ld a, e
	ld [hli], a ; SPRITEANIMSTRUCT_XCOORD
	ld a, d
	ld [hli], a ; SPRITEANIMSTRUCT_YCOORD
; load 0 into the next four fields
	xor a
	ld [hli], a ; SPRITEANIMSTRUCT_XOFFSET
	ld [hli], a ; SPRITEANIMSTRUCT_YOFFSET
	xor a
	ld [hli], a ; SPRITEANIMSTRUCT_DURATION
	ld [hli], a ; SPRITEANIMSTRUCT_DURATIONOFFSET
; load -1 into the next field
	dec a
	ld [hli], a ; SPRITEANIMSTRUCT_FRAME
; load 0 into the last five fields
	xor a
	ld [hli], a ; SPRITEANIMSTRUCT_JUMPTABLE_INDEX
	ld [hli], a ; SPRITEANIMSTRUCT_VAR1
	ld [hli], a ; SPRITEANIMSTRUCT_VAR2
	ld [hli], a ; SPRITEANIMSTRUCT_VAR3
	ld [hl], a  ; SPRITEANIMSTRUCT_VAR4
; back up the address of the first field to wSpriteAnimAddrBackup
	ld a, c
	ld [wSpriteAnimAddrBackup], a
	ld a, b
	ld [wSpriteAnimAddrBackup + 1], a
	ret

DeinitializeSprite:
; Clear the index field of the struct in bc.
	ld hl, SPRITEANIMSTRUCT_INDEX
	add hl, bc
	ld [hl], 0
	ret

DeinitializeAllSprites:
; Clear the index field of every struct in the wSpriteAnimationStructs array.
	ld hl, wSpriteAnimationStructs
	ld bc, SPRITEANIMSTRUCT_LENGTH
	ld e, NUM_SPRITE_ANIM_STRUCTS
	xor a
.loop
	ld [hl], a
	add hl, bc
	dec e
	jr nz, .loop
	ret

UpdateAnimFrame:
	call InitSpriteAnimBuffer ; init WRAM
	call GetSpriteAnimFrame ; read from a memory array
	cp dowait_command
	jr z, .done
	cp delanim_command
	jr z, .delete
	call GetFrameOAMPointer
	; add byte to [wCurAnimVTile]
	ld a, [wCurAnimVTile]
	add [hl]
	ld [wCurAnimVTile], a
	inc hl
	; load pointer into hl
	ld a, [hli]
	ld h, [hl]
	ld l, a
	push bc
	ld a, [wCurSpriteOAMAddr]
	ld e, a
	ld d, HIGH(wVirtualOAM)
	ld a, [hli]
	ld c, a ; number of objects
.loop
	; first byte: y (px)
	; [de] = [wCurAnimYCoord] + [wCurAnimYOffset] + [wGlobalAnimYOffset] + AddOrSubtractY([hl])
	ld a, [wCurAnimYCoord]
	ld b, a
	ld a, [wCurAnimYOffset]
	add b
	ld b, a
	ld a, [wGlobalAnimYOffset]
	add b
	ld b, a
	call AddOrSubtractY
	add b
	ld [de], a
	inc hl
	inc de
	; second byte: x (px)
	; [de] = [wCurAnimXCoord] + [wCurAnimXOffset] + [wGlobalAnimXOffset] + AddOrSubtractX([hl])
	ld a, [wCurAnimXCoord]
	ld b, a
	ld a, [wCurAnimXOffset]
	add b
	ld b, a
	ld a, [wGlobalAnimXOffset]
	add b
	ld b, a
	call AddOrSubtractX
	add b
	ld [de], a
	inc hl
	inc de
	; third byte: vtile
	; [de] = [wCurAnimVTile] + [hl]
	ld a, [wCurAnimVTile]
	add [hl]
	ld [de], a
	inc hl
	inc de
	; fourth byte: attributes
	; [de] = GetSpriteOAMAttr([hl])
	call GetSpriteOAMAttr
	ld [de], a
	inc hl
	inc de
	ld a, e
	ld [wCurSpriteOAMAddr], a
	cp LOW(wVirtualOAMEnd)
	jr nc, .reached_the_end
	dec c
	jr nz, .loop
	pop bc
	jr .done

.delete
	call DeinitializeSprite
.done
	and a
	ret

.reached_the_end
	pop bc
	scf
	ret

AddOrSubtractY:
	push hl
	ld a, [hl]
	ld hl, wCurSpriteOAMFlags
	bit OAM_Y_FLIP, [hl]
	jr z, .ok
	; -8 - a
	add 8
	xor $ff
	inc a

.ok
	pop hl
	ret

AddOrSubtractX:
	push hl
	ld a, [hl]
	ld hl, wCurSpriteOAMFlags
	bit OAM_X_FLIP, [hl]
	jr z, .ok
	; -8 - a
	add 8
	xor $ff
	inc a

.ok
	pop hl
	ret

GetSpriteOAMAttr:
	ld a, [wCurSpriteOAMFlags]
	ld b, a
	ld a, [hl]
	xor b
	and PRIORITY | Y_FLIP | X_FLIP
	ld b, a
	ld a, [hl]
	and ~(PRIORITY | Y_FLIP | X_FLIP)
	or b
	ret

InitSpriteAnimBuffer:
	xor a
	ld [wCurSpriteOAMFlags], a
	ld hl, SPRITEANIMSTRUCT_TILE_ID
	add hl, bc
	ld a, [hli]
	ld [wCurAnimVTile], a
	ld a, [hli]
	ld [wCurAnimXCoord], a
	ld a, [hli]
	ld [wCurAnimYCoord], a
	ld a, [hli]
	ld [wCurAnimXOffset], a
	ld a, [hli]
	ld [wCurAnimYOffset], a
	ret

GetSpriteAnimVTile:
; a = wSpriteAnimDict[a] if a in wSpriteAnimDict else vtile offset $00
	push hl
	push bc
	ld hl, wSpriteAnimDict
	ld b, a
	ld c, NUM_SPRITEANIMDICT_ENTRIES
.loop
	ld a, [hli]
	cp b
	jr z, .ok
	inc hl
	dec c
	jr nz, .loop
	xor a
	jr .done

.ok
	ld a, [hl]

.done
	pop bc
	pop hl
	ret

_ReinitSpriteAnimFrame::
	ld hl, SPRITEANIMSTRUCT_FRAMESET_ID
	add hl, bc
	ld [hl], a
	ld hl, SPRITEANIMSTRUCT_DURATION
	add hl, bc
	ld [hl], 0
	ld hl, SPRITEANIMSTRUCT_FRAME
	add hl, bc
	ld [hl], -1
	ret

GetSpriteAnimFrame:
.loop
	ld hl, SPRITEANIMSTRUCT_DURATION
	add hl, bc
	ld a, [hl]
	and a
	jr z, .next_frame
	dec [hl]
	call .GetPointer
	ld a, [hli]
	push af
	jr .okay

.next_frame
	ld hl, SPRITEANIMSTRUCT_FRAME
	add hl, bc
	inc [hl]
	call .GetPointer
	ld a, [hli]
	cp dorestart_command
	jr z, .restart
	cp endanim_command
	jr z, .repeat_last

	push af
	ld a, [hl]
	push hl
	and ~(Y_FLIP << 1 | X_FLIP << 1)
	ld hl, SPRITEANIMSTRUCT_DURATIONOFFSET
	add hl, bc
	add [hl]
	ld hl, SPRITEANIMSTRUCT_DURATION
	add hl, bc
	ld [hl], a
	pop hl
.okay
	ld a, [hl]
	and Y_FLIP << 1 | X_FLIP << 1 ; The << 1 is compensated in the "frame" macro
	srl a
	ld [wCurSpriteOAMFlags], a
	pop af
	ret

.repeat_last
	xor a
	ld hl, SPRITEANIMSTRUCT_DURATION
	add hl, bc
	ld [hl], a

	ld hl, SPRITEANIMSTRUCT_FRAME
	add hl, bc
	dec [hl]
	dec [hl]
	jr .loop

.restart
	xor a
	ld hl, SPRITEANIMSTRUCT_DURATION
	add hl, bc
	ld [hl], a

	dec a
	ld hl, SPRITEANIMSTRUCT_FRAME
	add hl, bc
	ld [hl], a
	jr .loop

.GetPointer:
	ld hl, SPRITEANIMSTRUCT_FRAMESET_ID
	add hl, bc
	ld e, [hl]
	ld d, 0
	ld hl, SpriteAnimFrameData
	add hl, de
	add hl, de
	ld e, [hl]
	inc hl
	ld d, [hl]
	ld hl, SPRITEANIMSTRUCT_FRAME
	add hl, bc
	ld l, [hl]
	ld h, 0
	add hl, hl
	add hl, de
	ret

GetFrameOAMPointer:
	ld e, a
	ld d, 0
	ld hl, SpriteAnimOAMData
	add hl, de
	add hl, de
	add hl, de
	ret

UnusedLoadSpriteAnimGFX: ; unreferenced
	push hl
	ld l, a
	ld h, 0
	add hl, hl
	add hl, hl
	ld de, UnusedSpriteAnimGFX
	add hl, de
	ld c, [hl]
	inc hl
	ld b, [hl]
	inc hl
	ld e, [hl]
	inc hl
	ld d, [hl]
	pop hl
	push bc
	call Request2bpp
	pop bc
	ret

INCLUDE "data/sprite_anims/sequences.asm"

INCLUDE "engine/gfx/sprite_anims.asm"

INCLUDE "data/sprite_anims/framesets.asm"

INCLUDE "data/sprite_anims/oam.asm"

INCLUDE "data/sprite_anims/unused_gfx.asm"

Sprites_Cosine:
; a = d * cos(a * pi/32)
	add %010000 ; cos(x) = sin(x + pi/2)
	; fallthrough
Sprites_Sine:
; a = d * sin(a * pi/32)
	calc_sine_wave

AnimateEndOfExpBar:
	ldh a, [hSGB]
	ld de, EndOfExpBarGFX
	and a
	jr z, .load
	ld de, SGBEndOfExpBarGFX

.load
	ld hl, vTiles0 tile $00
	lb bc, BANK(EndOfExpBarGFX), 1
	call Request2bpp
	ld c, 8
	ld d, 0
.loop
	push bc
	call .AnimateFrame
	call DelayFrame
	pop bc
	inc d
	inc d
	dec c
	jr nz, .loop
	call ClearSprites
	ret

.AnimateFrame:
	ld hl, wVirtualOAMSprite00
	ld c, 8 ; number of animated circles
.anim_loop
	ld a, c
	and a
	ret z
	dec c
	ld a, c
; multiply by 8
	sla a
	sla a
	sla a
	push af

	push de
	push hl
	call Sprites_Sine
	pop hl
	pop de
	add 13 * TILE_WIDTH
	ld [hli], a ; y

	pop af
	push de
	push hl
	call Sprites_Cosine
	pop hl
	pop de
	add 10 * TILE_WIDTH + 4
	ld [hli], a ; x

	ld a, $0
	ld [hli], a ; tile id
	ld a, PAL_BATTLE_OB_BLUE
	ld [hli], a ; attributes
	jr .anim_loop

EndOfExpBarGFX:
INCBIN "gfx/battle/expbarend.2bpp"
SGBEndOfExpBarGFX:
INCBIN "gfx/battle/expbarend_sgb.2bpp"

ClearSpriteAnims2:
	push hl
	push de
	push bc
	push af
	ld hl, wSpriteAnimData
	ld bc, wSpriteAnimDataEnd - wSpriteAnimData
.loop
	ld [hl], 0
	inc hl
	dec bc
	ld a, c
	or b
	jr nz, .loop
	pop af
	pop bc
	pop de
	pop hl
	ret
