shithub: pokered

ref: 5d9957293c28a4d1483a02d76020a895b23d36db
dir: /engine/battle/animations.asm/

View raw version
; Draws a "frame block". Frame blocks are blocks of tiles that are put
; together to form frames in battle animations.
DrawFrameBlock:
	ld l, c
	ld h, b
	ld a, [hli]
	ld [wNumFBTiles], a
	ld a, [wFBDestAddr + 1]
	ld e, a
	ld a, [wFBDestAddr]
	ld d, a
	xor a
	ld [wFBTileCounter], a ; loop counter
.loop
	ld a, [wFBTileCounter]
	inc a
	ld [wFBTileCounter], a
	ld a, [wSubAnimTransform]
	dec a
	jr z, .flipHorizontalAndVertical   ; SUBANIMTYPE_HVFLIP
	dec a
	jp z, .flipHorizontalTranslateDown ; SUBANIMTYPE_HFLIP
	dec a
	jr z, .flipBaseCoords              ; SUBANIMTYPE_COORDFLIP
.noTransformation
	ld a, [wBaseCoordY]
	add [hl]
	ld [de], a ; store Y
	inc hl
	inc de
	ld a, [wBaseCoordX]
	jr .finishCopying
.flipBaseCoords
	ld a, [wBaseCoordY]
	ld b, a
	ld a, 136
	sub b ; flip Y base coordinate
	add [hl] ; Y offset
	ld [de], a ; store Y
	inc hl
	inc de
	ld a, [wBaseCoordX]
	ld b, a
	ld a, 168
	sub b ; flip X base coordinate
.finishCopying ; finish copying values to OAM (when subanimation not transformed)
	add [hl] ; X offset
	ld [de], a ; store X
	inc hl
	inc de
	ld a, [hli]
	add $31 ; base tile ID for battle animations
	ld [de], a ; store tile ID
	inc de
	ld a, [hli]
	ld [de], a ; store flags
	inc de
	jp .nextTile
.flipHorizontalAndVertical
	ld a, [wBaseCoordY]
	add [hl] ; Y offset
	ld b, a
	ld a, 136
	sub b ; flip Y coordinate
	ld [de], a ; store Y
	inc hl
	inc de
	ld a, [wBaseCoordX]
	add [hl] ; X offset
	ld b, a
	ld a, 168
	sub b ; flip X coordinate
	ld [de], a ; store X
	inc hl
	inc de
	ld a, [hli]
	add $31 ; base tile ID for battle animations
	ld [de], a ; store tile ID
	inc de
; toggle horizontal and vertical flip
	ld a, [hli] ; flags
	and a
	ld b, OAM_VFLIP | OAM_HFLIP
	jr z, .storeFlags1
	cp OAM_HFLIP
	ld b, OAM_VFLIP
	jr z, .storeFlags1
	cp OAM_VFLIP
	ld b, OAM_HFLIP
	jr z, .storeFlags1
	ld b, 0
.storeFlags1
	ld a, b
	ld [de], a
	inc de
	jp .nextTile
.flipHorizontalTranslateDown
	ld a, [wBaseCoordY]
	add [hl]
	add 40 ; translate Y coordinate downwards
	ld [de], a ; store Y
	inc hl
	inc de
	ld a, [wBaseCoordX]
	add [hl]
	ld b, a
	ld a, 168
	sub b ; flip X coordinate
	ld [de], a ; store X
	inc hl
	inc de
	ld a, [hli]
	add $31 ; base tile ID for battle animations
	ld [de], a ; store tile ID
	inc de
	ld a, [hli]
	bit 5, a ; is horizontal flip enabled?
	jr nz, .disableHorizontalFlip
.enableHorizontalFlip
	set 5, a
	jr .storeFlags2
.disableHorizontalFlip
	res 5, a
.storeFlags2
	ld [de], a
	inc de
.nextTile
	ld a, [wFBTileCounter]
	ld c, a
	ld a, [wNumFBTiles]
	cp c
	jp nz, .loop ; go back up if there are more tiles to draw
.afterDrawingTiles
	ld a, [wFBMode]
	cp FRAMEBLOCKMODE_02
	jr z, .advanceFrameBlockDestAddr ; skip delay and don't clean OAM buffer
	ld a, [wSubAnimFrameDelay]
	ld c, a
	call DelayFrames
	ld a, [wFBMode]
	cp FRAMEBLOCKMODE_03
	jr z, .advanceFrameBlockDestAddr ; skip cleaning OAM buffer
	cp FRAMEBLOCKMODE_04
	jr z, .done ; skip cleaning OAM buffer and don't advance the frame block destination address
	ld a, [wAnimationID]
	cp GROWL
	jr z, .resetFrameBlockDestAddr
	call AnimationCleanOAM
.resetFrameBlockDestAddr
	ld hl, wOAMBuffer ; OAM buffer
	ld a, l
	ld [wFBDestAddr + 1], a
	ld a, h
	ld [wFBDestAddr], a ; set destination address to beginning of OAM buffer
	ret
.advanceFrameBlockDestAddr
	ld a, e
	ld [wFBDestAddr + 1], a
	ld a, d
	ld [wFBDestAddr], a
.done
	ret

PlayAnimation:
	xor a
	ldh [hROMBankTemp], a ; it looks like nothing reads this
	ld [wSubAnimTransform], a
	ld a, [wAnimationID] ; get animation number
	dec a
	ld l, a
	ld h, 0
	add hl, hl
	ld de, AttackAnimationPointers  ; animation command stream pointers
	add hl, de
	ld a, [hli]
	ld h, [hl]
	ld l, a
.animationLoop
	ld a, [hli]
	cp -1
	jr z, .AnimationOver
	cp FIRST_SE_ID ; is this subanimation or a special effect?
	jr c, .playSubanimation
.doSpecialEffect
	ld c, a
	ld de, SpecialEffectPointers
.searchSpecialEffectTableLoop
	ld a, [de]
	cp c
	jr z, .foundMatch
	inc de
	inc de
	inc de
	jr .searchSpecialEffectTableLoop
.foundMatch
	ld a, [hli]
	cp NO_MOVE - 1 ; is there a sound to play?
	jr z, .skipPlayingSound
	ld [wAnimSoundID], a ; store sound
	push hl
	push de
	call GetMoveSound
	call PlaySound
	pop de
	pop hl
.skipPlayingSound
	push hl
	inc de
	ld a, [de]
	ld l, a
	inc de
	ld a, [de]
	ld h, a
	ld de, .nextAnimationCommand
	push de
	jp hl ; jump to special effect function
.playSubanimation
	ld c, a
	and %00111111
	ld [wSubAnimFrameDelay], a
	xor a
	sla c
	rla
	sla c
	rla
	ld [wWhichBattleAnimTileset], a
	ld a, [hli] ; sound
	ld [wAnimSoundID], a ; store sound
	ld a, [hli] ; subanimation ID
	ld c, l
	ld b, h
	ld l, a
	ld h, 0
	add hl, hl
	ld de, SubanimationPointers
	add hl, de
	ld a, l
	ld [wSubAnimAddrPtr], a
	ld a, h
	ld [wSubAnimAddrPtr + 1], a
	ld l, c
	ld h, b
	push hl
	ldh a, [rOBP0]
	push af
	ld a, [wAnimPalette]
	ldh [rOBP0], a
	call LoadAnimationTileset
	call LoadSubanimation
	call PlaySubanimation
	pop af
	ldh [rOBP0], a
.nextAnimationCommand
	pop hl
	jr .animationLoop
.AnimationOver
	ret

LoadSubanimation:
	ld a, [wSubAnimAddrPtr + 1]
	ld h, a
	ld a, [wSubAnimAddrPtr]
	ld l, a
	ld a, [hli]
	ld e, a
	ld a, [hl]
	ld d, a ; de = address of subanimation
	ld a, [de]
	ld b, a
	and %00011111
	ld [wSubAnimCounter], a ; number of frame blocks
	ld a, b
	and %11100000
	cp SUBANIMTYPE_ENEMY << 5
	jr nz, .isNotType5
.isType5
	call GetSubanimationTransform2
	jr .saveTransformation
.isNotType5
	call GetSubanimationTransform1
.saveTransformation
; place the upper 3 bits of a into bits 0-2 of a before storing
	srl a
	swap a
	ld [wSubAnimTransform], a
	cp SUBANIMTYPE_REVERSE
	ld hl, 0
	jr nz, .storeSubentryAddr
; if the animation is reversed, then place the initial subentry address at the end of the list of subentries
	ld a, [wSubAnimCounter]
	dec a
	ld bc, 3
.loop
	add hl, bc
	dec a
	jr nz, .loop
.storeSubentryAddr
	inc de
	add hl, de
	ld a, l
	ld [wSubAnimSubEntryAddr], a
	ld a, h
	ld [wSubAnimSubEntryAddr + 1], a
	ret

; called if the subanimation type is not SUBANIMTYPE_ENEMY
; sets the transform to SUBANIMTYPE_NORMAL if it's the player's turn
; sets the transform to the subanimation type if it's the enemy's turn
GetSubanimationTransform1:
	ld b, a
	ldh a, [hWhoseTurn]
	and a
	ld a, b
	ret nz
	xor a ; SUBANIMTYPE_NORMAL << 5
	ret

; called if the subanimation type is SUBANIMTYPE_ENEMY
; sets the transform to SUBANIMTYPE_HFLIP if it's the player's turn
; sets the transform to SUBANIMTYPE_NORMAL if it's the enemy's turn
GetSubanimationTransform2:
	ldh a, [hWhoseTurn]
	and a
	ld a, SUBANIMTYPE_HFLIP << 5
	ret z
	xor a ; SUBANIMTYPE_NORMAL << 5
	ret

; loads tile patterns for battle animations
LoadAnimationTileset:
	ld a, [wWhichBattleAnimTileset]
	add a
	add a
	ld hl, AnimationTilesetPointers
	ld e, a
	ld d, 0
	add hl, de
	ld a, [hli]
	ld [wTempTilesetNumTiles], a ; number of tiles
	ld a, [hli]
	ld e, a
	ld a, [hl]
	ld d, a ; de = address of tileset
	ld hl, vSprites tile $31
	ld b, BANK(AnimationTileset1) ; ROM bank
	ld a, [wTempTilesetNumTiles]
	ld c, a ; number of tiles
	jp CopyVideoData ; load tileset

anim_tileset: MACRO
	db \1
	dw \2
	db -1 ; padding
ENDM

AnimationTilesetPointers:
	; number of tiles, gfx pointer
	anim_tileset 79, AnimationTileset1
	anim_tileset 79, AnimationTileset2
	anim_tileset 64, AnimationTileset1

AnimationTileset1:
	INCBIN "gfx/battle/attack_anim_1.2bpp"

AnimationTileset2:
	INCBIN "gfx/battle/attack_anim_2.2bpp"

SlotMachineTiles2:
IF DEF(_RED)
	INCBIN "gfx/slots/red_slots_2.2bpp"
ENDC
IF DEF(_BLUE)
	INCBIN "gfx/slots/blue_slots_2.2bpp"
ENDC
SlotMachineTiles2End:

MoveAnimation:
	push hl
	push de
	push bc
	push af
	call WaitForSoundToFinish
	call SetAnimationPalette
	ld a, [wAnimationID]
	and a
	jr z, .animationFinished

	; if throwing a Poké Ball, skip the regular animation code
	cp TOSS_ANIM
	jr nz, .moveAnimation
	ld de, .animationFinished
	push de
	jp TossBallAnimation

.moveAnimation
	; check if battle animations are disabled in the options
	ld a, [wOptions]
	bit 7, a
	jr nz, .animationsDisabled
	call ShareMoveAnimations
	call PlayAnimation
	jr .next4
.animationsDisabled
	ld c, 30
	call DelayFrames
.next4
	call PlayApplyingAttackAnimation ; shake the screen or flash the pic in and out (to show damage)
.animationFinished
	call WaitForSoundToFinish
	xor a
	ld [wSubAnimSubEntryAddr], a
	ld [wUnusedD09B], a
	ld [wSubAnimTransform], a
	dec a ; NO_MOVE - 1
	ld [wAnimSoundID], a
	pop af
	pop bc
	pop de
	pop hl
	ret

ShareMoveAnimations:
; some moves just reuse animations from status conditions
	ldh a, [hWhoseTurn]
	and a
	ret z

	; opponent's turn

	ld a, [wAnimationID]

	cp AMNESIA
	ld b, CONF_ANIM
	jr z, .replaceAnim

	cp REST
	ld b, SLP_ANIM
	ret nz

.replaceAnim
	ld a, b
	ld [wAnimationID], a
	ret

PlayApplyingAttackAnimation:
; Generic animation that shows after the move's individual animation
; Different animation depending on whether the move has an additional effect and on whose turn it is
	ld a, [wAnimationType]
	and a
	ret z
	dec a
	add a
	ld c, a
	ld b, 0
	ld hl, AnimationTypePointerTable
	add hl, bc
	ld a, [hli]
	ld h, [hl]
	ld l, a
	jp hl

AnimationTypePointerTable:
	dw ShakeScreenVertically        ; enemy mon has used a damaging move without a side effect
	dw ShakeScreenHorizontallyHeavy ; enemy mon has used a damaging move with a side effect
	dw ShakeScreenHorizontallySlow  ; enemy mon has used a non-damaging move
	dw BlinkEnemyMonSprite          ; player mon has used a damaging move without a side effect
	dw ShakeScreenHorizontallyLight ; player mon has used a damaging move with a side effect
	dw ShakeScreenHorizontallySlow2 ; player mon has used a non-damaging move

ShakeScreenVertically:
	call PlayApplyingAttackSound
	ld b, 8
	jp AnimationShakeScreenVertically

ShakeScreenHorizontallyHeavy:
	call PlayApplyingAttackSound
	ld b, 8
	jp AnimationShakeScreenHorizontallyFast

ShakeScreenHorizontallySlow:
	lb bc, 6, 2
	jr AnimationShakeScreenHorizontallySlow

BlinkEnemyMonSprite:
	call PlayApplyingAttackSound
	jp AnimationBlinkEnemyMon

ShakeScreenHorizontallyLight:
	call PlayApplyingAttackSound
	ld b, 2
	jp AnimationShakeScreenHorizontallyFast

ShakeScreenHorizontallySlow2:
	lb bc, 3, 2

AnimationShakeScreenHorizontallySlow:
	push bc
	push bc
.loop1
	ldh a, [rWX]
	inc a
	ldh [rWX], a
	ld c, 2
	call DelayFrames
	dec b
	jr nz, .loop1
	pop bc
.loop2
	ldh a, [rWX]
	dec a
	ldh [rWX], a
	ld c, 2
	call DelayFrames
	dec b
	jr nz, .loop2
	pop bc
	dec c
	jr nz, AnimationShakeScreenHorizontallySlow
	ret

SetAnimationPalette:
	ld a, [wOnSGB]
	and a
	ld a, $e4
	jr z, .notSGB
	ld a, $f0
	ld [wAnimPalette], a
	ld b, $e4
	ld a, [wAnimationID]
	cp TRADE_BALL_DROP_ANIM
	jr c, .next
	cp TRADE_BALL_POOF_ANIM + 1
	jr nc, .next
	ld b, $f0
.next
	ld a, b
	ldh [rOBP0], a
	ld a, $6c
	ldh [rOBP1], a
	ret
.notSGB
	ld a, $e4
	ld [wAnimPalette], a
	ldh [rOBP0], a
	ld a, $6c
	ldh [rOBP1], a
	ret

PlaySubanimation:
	ld a, [wAnimSoundID]
	cp NO_MOVE - 1
	jr z, .skipPlayingSound
	call GetMoveSound
	call PlaySound
.skipPlayingSound
	ld hl, wOAMBuffer ; base address of OAM buffer
	ld a, l
	ld [wFBDestAddr + 1], a
	ld a, h
	ld [wFBDestAddr], a
	ld a, [wSubAnimSubEntryAddr + 1]
	ld h, a
	ld a, [wSubAnimSubEntryAddr]
	ld l, a
.loop
	push hl
	ld c, [hl] ; frame block ID
	ld b, 0
	ld hl, FrameBlockPointers
	add hl, bc
	add hl, bc
	ld a, [hli]
	ld c, a
	ld a, [hli]
	ld b, a
	pop hl
	inc hl
	push hl
	ld e, [hl] ; base coordinate ID
	ld d, 0
	ld hl, FrameBlockBaseCoords  ; base coordinate table
	add hl, de
	add hl, de
	ld a, [hli]
	ld [wBaseCoordY], a
	ld a, [hl]
	ld [wBaseCoordX], a
	pop hl
	inc hl
	ld a, [hl] ; frame block mode
	ld [wFBMode], a
	call DrawFrameBlock
	call DoSpecialEffectByAnimationId ; run animation-specific function (if there is one)
	ld a, [wSubAnimCounter]
	dec a
	ld [wSubAnimCounter], a
	ret z
	ld a, [wSubAnimSubEntryAddr + 1]
	ld h, a
	ld a, [wSubAnimSubEntryAddr]
	ld l, a
	ld a, [wSubAnimTransform]
	cp SUBANIMTYPE_REVERSE
	ld bc, 3
	jr nz, .nextSubanimationSubentry
	ld bc, -3
.nextSubanimationSubentry
	add hl, bc
	ld a, h
	ld [wSubAnimSubEntryAddr + 1], a
	ld a, l
	ld [wSubAnimSubEntryAddr], a
	jp .loop

AnimationCleanOAM:
	push hl
	push de
	push bc
	push af
	call DelayFrame
	call ClearSprites
	pop af
	pop bc
	pop de
	pop hl
	ret

; this runs after each frame block is drawn in a subanimation
; it runs a particular special effect based on the animation ID
DoSpecialEffectByAnimationId:
	push hl
	push de
	push bc
	ld a, [wAnimationID]
	ld hl, AnimationIdSpecialEffects
	ld de, 3
	call IsInArray
	jr nc, .done
	inc hl
	ld a, [hli]
	ld h, [hl]
	ld l, a
	ld de, .done
	push de
	jp hl
.done
	pop bc
	pop de
	pop hl
	ret

INCLUDE "data/battle_anims/special_effects.asm"

DoBallTossSpecialEffects:
	ld a, [wcf91]
	cp 3 ; is it a Master Ball or Ultra Ball?
	jr nc, .skipFlashingEffect
.flashingEffect ; do a flashing effect if it's Master Ball or Ultra Ball
	ldh a, [rOBP0]
	xor %00111100 ; complement colors 1 and 2
	ldh [rOBP0], a
.skipFlashingEffect
	ld a, [wSubAnimCounter]
	cp 11 ; is it the beginning of the subanimation?
	jr nz, .skipPlayingSound
; if it is the beginning of the subanimation, play a sound
	ld a, SFX_BALL_TOSS
	call PlaySound
.skipPlayingSound
	ld a, [wIsInBattle]
	cp 02 ; is it a trainer battle?
	jr z, .isTrainerBattle
	ld a, [wd11e]
	cp $10 ; is the enemy pokemon the Ghost Marowak?
	ret nz
; if the enemy pokemon is the Ghost Marowak, make it dodge during the last 3 frames
	ld a, [wSubAnimCounter]
	cp 3
	jr z, .moveGhostMarowakLeft
	cp 2
	jr z, .moveGhostMarowakLeft
	cp 1
	ret nz
.moveGhostMarowakLeft
	hlcoord 17, 0
	ld de, 20
	lb bc, 7, 7
.loop
	push hl
	push bc
	call AnimCopyRowRight ; move row of tiles left
	pop bc
	pop hl
	add hl, de
	dec b
	jr nz, .loop
	ld a, %00001000
	ldh [rNR10], a ; Channel 1 sweep register
	ret
.isTrainerBattle ; if it's a trainer battle, shorten the animation by one frame
	ld a, [wSubAnimCounter]
	cp 3
	ret nz
	dec a
	ld [wSubAnimCounter], a
	ret

DoBallShakeSpecialEffects:
	ld a, [wSubAnimCounter]
	cp 4 ; is it the beginning of a shake?
	jr nz, .skipPlayingSound
; if it is the beginning of a shake, play a sound and wait 2/3 of a second
	ld a, SFX_TINK
	call PlaySound
	ld c, 40
	call DelayFrames
.skipPlayingSound
	ld a, [wSubAnimCounter]
	dec a
	ret nz
; if it's the end of the ball shaking subanimation, check if more shakes are left and restart the subanimation
	ld a, [wNumShakes] ; number of shakes
	dec a ; decrement number of shakes
	ld [wNumShakes], a
	ret z
; if there are shakes left, restart the subanimation
	ld a, [wSubAnimSubEntryAddr]
	ld l, a
	ld a, [wSubAnimSubEntryAddr + 1]
	ld h, a
	ld de, -(4 * 3) ; 4 subentries and 3 bytes per subentry
	add hl, de
	ld a, l
	ld [wSubAnimSubEntryAddr], a
	ld a, h
	ld [wSubAnimSubEntryAddr + 1], a
	ld a, 5 ; number of subentries in the ball shaking subanimation plus one
	ld [wSubAnimCounter], a
	ret

; plays a sound after the second frame of the poof animation
DoPoofSpecialEffects:
	ld a, [wSubAnimCounter]
	cp 5
	ret nz
	ld a, SFX_BALL_POOF
	jp PlaySound

DoRockSlideSpecialEffects:
	ld a, [wSubAnimCounter]
	cp 12
	ret nc
	cp 8
	jr nc, .shakeScreen
	cp 1
	jp z, AnimationFlashScreen ; if it's the end of the subanimation, flash the screen
	ret
; if the subanimation counter is between 8 and 11, shake the screen horizontally and vertically
.shakeScreen
	ld b, 1
	predef PredefShakeScreenHorizontally ; shake horizontally
	ld b, 1
	predef_jump PredefShakeScreenVertically ; shake vertically

FlashScreenEveryEightFrameBlocks:
	ld a, [wSubAnimCounter]
	and 7 ; is the subanimation counter exactly 8?
	call z, AnimationFlashScreen ; if so, flash the screen
	ret

; flashes the screen if the subanimation counter is divisible by 4
FlashScreenEveryFourFrameBlocks:
	ld a, [wSubAnimCounter]
	and 3
	call z, AnimationFlashScreen
	ret

; used for Explosion and Selfdestruct
DoExplodeSpecialEffects:
	ld a, [wSubAnimCounter]
	cp 1 ; is it the end of the subanimation?
	jr nz, FlashScreenEveryFourFrameBlocks
; if it's the end of the subanimation, make the attacking pokemon disappear
	hlcoord 1, 5
	jp AnimationHideMonPic ; make pokemon disappear

; flashes the screen when subanimation counter is 1 modulo 4
DoBlizzardSpecialEffects:
	ld a, [wSubAnimCounter]
	cp 13
	jp z, AnimationFlashScreen
	cp 9
	jp z, AnimationFlashScreen
	cp 5
	jp z, AnimationFlashScreen
	cp 1
	jp z, AnimationFlashScreen
	ret

; flashes the screen at 3 points in the subanimation
; unused
FlashScreenUnused:
	ld a, [wSubAnimCounter]
	cp 14
	jp z, AnimationFlashScreen
	cp 9
	jp z, AnimationFlashScreen
	cp 2
	jp z, AnimationFlashScreen
	ret

; function to make the pokemon disappear at the beginning of the animation
TradeHidePokemon:
	ld a, [wSubAnimCounter]
	cp 6
	ret nz
	ld a, 2 * SCREEN_WIDTH + 7
	jp ClearMonPicFromTileMap ; make pokemon disappear

; function to make a shaking pokeball jump up at the end of the animation
TradeShakePokeball:
	ld a, [wSubAnimCounter]
	cp 1
	ret nz
; if it's the end of the animation, make the ball jump up
	ld de, BallMoveDistances1
.loop
	ld hl, wOAMBuffer ; OAM buffer
	ld bc, 4
.innerLoop
	ld a, [de]
	cp $ff
	jr z, .done
	add [hl] ; add to Y value of OAM entry
	ld [hl], a
	add hl, bc
	ld a, l
	cp 4 * 4 ; there are 4 entries, each 4 bytes
	jr nz, .innerLoop
	inc de
	push bc
	call Delay3
	pop bc
	jr .loop
.done
	call AnimationCleanOAM
	ld a, SFX_TRADE_MACHINE
	jp PlaySound

BallMoveDistances1:
	db -12, -12, -8
	db -1 ; end

; function to make the pokeball jump up
TradeJumpPokeball:
	ld de, BallMoveDistances2
.loop
	ld hl, wOAMBuffer ; OAM buffer
	ld bc, 4
.innerLoop
	ld a, [de]
	cp $ff
	jp z, ClearScreen
	add [hl]
	ld [hl], a
	add hl, bc
	ld a, l
	cp 4 * 4 ; there are 4 entries, each 4 bytes
	jr nz, .innerLoop
	inc de
	push de
	ld a, [de]
	cp 12
	jr z, .playSound
	cp $ff
	jr nz, .skipPlayingSound
.playSound ; play sound if next move distance is 12 or this is the last one
	ld a, SFX_BATTLE_18
	call PlaySound
.skipPlayingSound
	push bc
	ld c, 5
	call DelayFrames
	pop bc
	ldh a, [hSCX] ; background scroll X
	sub 8 ; scroll to the left
	ldh [hSCX], a
	pop de
	jr .loop

BallMoveDistances2:
	db 11, 12, -12, -7, 7, 12, -8, 8
	db -1 ; end

; this function copies the current musical note graphic
; so that there are two musical notes flying towards the defending pokemon
DoGrowlSpecialEffects:
	ld hl, wOAMBuffer ; OAM buffer
	ld de, wOAMBuffer + $10
	ld bc, $10
	call CopyData ; copy the musical note graphic
	ld a, [wSubAnimCounter]
	dec a
	call z, AnimationCleanOAM ; clean up at the end of the subanimation
	ret

; this is associated with Tail Whip, but Tail Whip doesn't use any subanimations
TailWhipAnimationUnused:
	ld a, 1
	ld [wSubAnimCounter], a
	ld c, 20
	jp DelayFrames

INCLUDE "data/battle_anims/special_effect_pointers.asm"

AnimationDelay10:
	ld c, 10
	jp DelayFrames

; calls a function with the turn flipped from player to enemy or vice versa
; input - hl - address of function to call
CallWithTurnFlipped:
	ldh a, [hWhoseTurn]
	push af
	xor 1
	ldh [hWhoseTurn], a
	ld de, .returnAddress
	push de
	jp hl
.returnAddress
	pop af
	ldh [hWhoseTurn], a
	ret

; flashes the screen for an extended period (48 frames)
AnimationFlashScreenLong:
	ld a, 3 ; cycle through the palettes 3 times
	ld [wFlashScreenLongCounter], a
	ld a, [wOnSGB] ; running on SGB?
	and a
	ld hl, FlashScreenLongMonochrome
	jr z, .loop
	ld hl, FlashScreenLongSGB
.loop
	push hl
.innerLoop
	ld a, [hli]
	cp $01 ; is it the end of the palettes?
	jr z, .endOfPalettes
	ldh [rBGP], a
	call FlashScreenLongDelay
	jr .innerLoop
.endOfPalettes
	ld a, [wFlashScreenLongCounter]
	dec a
	ld [wFlashScreenLongCounter], a
	pop hl
	jr nz, .loop
	ret

; BG palettes
FlashScreenLongMonochrome:
	db %11111001 ; 3, 3, 2, 1
	db %11111110 ; 3, 3, 3, 2
	db %11111111 ; 3, 3, 3, 3
	db %11111110 ; 3, 3, 3, 2
	db %11111001 ; 3, 3, 2, 1
	db %11100100 ; 3, 2, 1, 0
	db %10010000 ; 2, 1, 0, 0
	db %01000000 ; 1, 0, 0, 0
	db %00000000 ; 0, 0, 0, 0
	db %01000000 ; 1, 0, 0, 0
	db %10010000 ; 2, 1, 0, 0
	db %11100100 ; 3, 2, 1, 0
	db $01 ; terminator

; BG palettes
FlashScreenLongSGB:
	db %11111000 ; 3, 3, 2, 0
	db %11111100 ; 3, 3, 3, 0
	db %11111111 ; 3, 3, 3, 3
	db %11111100 ; 3, 3, 3, 0
	db %11111000 ; 3, 3, 2, 0
	db %11100100 ; 3, 2, 1, 0
	db %10010000 ; 2, 1, 0, 0
	db %01000000 ; 1, 0, 0, 0
	db %00000000 ; 0, 0, 0, 0
	db %01000000 ; 1, 0, 0, 0
	db %10010000 ; 2, 1, 0, 0
	db %11100100 ; 3, 2, 1, 0
	db $01 ; terminator

; causes a delay of 2 frames for the first cycle
; causes a delay of 1 frame for the second and third cycles
FlashScreenLongDelay:
	ld a, [wFlashScreenLongCounter]
	cp 4 ; never true since [wFlashScreenLongCounter] starts at 3
	ld c, 4
	jr z, .delayFrames
	cp 3
	ld c, 2
	jr z, .delayFrames
	cp 2 ; nothing is done with this
	ld c, 1
.delayFrames
	jp DelayFrames

AnimationFlashScreen:
	ldh a, [rBGP]
	push af ; save initial palette
	ld a, %00011011 ; 0, 1, 2, 3 (inverted colors)
	ldh [rBGP], a
	ld c, 2
	call DelayFrames
	xor a ; white out background
	ldh [rBGP], a
	ld c, 2
	call DelayFrames
	pop af
	ldh [rBGP], a ; restore initial palette
	ret

AnimationDarkScreenPalette:
; Changes the screen's palette to a dark palette.
	lb bc, $6f, $6f
	jr SetAnimationBGPalette

AnimationDarkenMonPalette:
; Darkens the mon sprite's palette.
	lb bc, $f9, $f4
	jr SetAnimationBGPalette

AnimationUnusedPalette1:
	lb bc, $fe, $f8
	jr SetAnimationBGPalette

AnimationUnusedPalette2:
	lb bc, $ff, $ff
	jr SetAnimationBGPalette

AnimationResetScreenPalette:
; Restores the screen's palette to the normal palette.
	lb bc, $e4, $e4
	jr SetAnimationBGPalette

AnimationUnusedPalette3:
	lb bc, $00, $00
	jr SetAnimationBGPalette

AnimationLightScreenPalette:
; Changes the screen to use a palette with light colors.
	lb bc, $90, $90
	jr SetAnimationBGPalette

AnimationUnusedPalette4:
	lb bc, $40, $40

SetAnimationBGPalette:
	ld a, [wOnSGB]
	and a
	ld a, b
	jr z, .next
	ld a, c
.next
	ldh [rBGP], a
	ret

	ld b, $5

AnimationShakeScreenVertically:
	predef_jump PredefShakeScreenVertically

AnimationShakeScreen:
; Shakes the screen for a while. Used in Earthquake/Fissure/etc. animations.
	ld b, $8

AnimationShakeScreenHorizontallyFast:
	predef_jump PredefShakeScreenHorizontally

AnimationWaterDropletsEverywhere:
; Draws water droplets all over the screen and makes them
; scroll. It's hard to describe, but it's the main animation
; in Surf/Mist/Toxic.
	xor a
	ld [wWhichBattleAnimTileset], a
	call LoadAnimationTileset
	ld d, 32
	ld a, -16
	ld [wBaseCoordX], a
	ld a, $71
	ld [wDropletTile], a
.loop
	ld a, 16
	ld [wBaseCoordY], a
	ld a, 0
	ld [wUnusedD08A], a
	call _AnimationWaterDroplets
	ld a, 24
	ld [wBaseCoordY], a
	ld a, 32
	ld [wUnusedD08A], a
	call _AnimationWaterDroplets
	dec d
	jr nz, .loop
	ret

_AnimationWaterDroplets:
	ld hl, wOAMBuffer
.loop
	ld a, [wBaseCoordY]
	ld [hli], a ; Y
	ld a, [wBaseCoordX]
	add 27
	ld [wBaseCoordX], a
	ld [hli], a ; X
	ld a, [wDropletTile]
	ld [hli], a ; tile
	xor a
	ld [hli], a ; attribute
	ld a, [wBaseCoordX]
	cp 144
	jr c, .loop
	sub 168
	ld [wBaseCoordX], a
	ld a, [wBaseCoordY]
	add 16
	ld [wBaseCoordY], a
	cp 112
	jr c, .loop
	call AnimationCleanOAM
	jp DelayFrame

AnimationSlideMonUp:
; Slides the mon's sprite upwards.
	ld c, 7
	ldh a, [hWhoseTurn]
	and a
	hlcoord 1, 6
	decoord 1, 5
	ld a, $30
	jr z, .next
	hlcoord 12, 1
	decoord 12, 0
	ld a, $ff
.next
	ld [wSlideMonUpBottomRowLeftTile], a
	jp _AnimationSlideMonUp

AnimationSlideMonDown:
; Slides the mon's sprite down out of the screen.
	xor a ; TILEMAP_MON_PIC
	call GetTileIDList
.loop
	call GetMonSpriteTileMapPointerFromRowCount
	push bc
	push de
	call CopyPicTiles
	call Delay3
	call AnimationHideMonPic
	pop de
	pop bc
	dec b
	jr nz, .loop
	ret

AnimationSlideMonOff:
; Slides the mon's sprite off the screen horizontally.
	ld e, 8
	ld a, 3
	ld [wSlideMonDelay], a
	jp _AnimationSlideMonOff

AnimationSlideEnemyMonOff:
; Slides the enemy mon off the screen horizontally.
	ld hl, AnimationSlideMonOff
	jp CallWithTurnFlipped

_AnimationSlideMonUp:
	push de
	push hl
	push bc

; In each iteration, slide up all rows but the top one (which is overwritten).
	ld b, 6
.slideLoop
	push bc
	push de
	push hl
	ld bc, 7
	call CopyData
; Note that de and hl are popped in the same order they are pushed, swapping
; their values. When CopyData is called, hl points to a tile 1 row below
; the one de points to. To maintain this relationship, after swapping, we add 2
; rows to hl so that it is 1 row below again.
	pop de
	pop hl
	ld bc, SCREEN_WIDTH * 2
	add hl, bc
	pop bc
	dec b
	jr nz, .slideLoop

; Fill in the bottom row of the mon pic with the next row's tile IDs.
	ldh a, [hWhoseTurn]
	and a
	hlcoord 1, 11
	jr z, .next
	hlcoord 12, 6
.next
	ld a, [wSlideMonUpBottomRowLeftTile]
	inc a
	ld [wSlideMonUpBottomRowLeftTile], a
	ld c, 7
.fillBottomRowLoop
	ld [hli], a
	add 7
	dec c
	jr nz, .fillBottomRowLoop

	ld c, 2
	call DelayFrames
	pop bc
	pop hl
	pop de
	dec c
	jr nz, _AnimationSlideMonUp
	ret

ShakeEnemyHUD_WritePlayerMonPicOAM:
; Writes the OAM entries for a copy of the player mon's pic in OAM.
; The top 5 rows are reproduced in OAM, although only 2 are actually needed.
	ld a, $10
	ld [wBaseCoordX], a
	ld a, $30
	ld [wBaseCoordY], a
	ld hl, wOAMBuffer
	ld d, 0
	ld c, 7
.loop
	ld a, [wBaseCoordY]
	ld e, a
	ld b, 5
.innerLoop
	call BattleAnimWriteOAMEntry
	inc d
	dec b
	jr nz, .innerLoop
	dec c
	ret z
	inc d
	inc d
	ld a, [wBaseCoordX]
	add 8
	ld [wBaseCoordX], a
	jr .loop

BattleAnimWriteOAMEntry:
; Y coordinate = e (increased by 8 each call, before the write to OAM)
; X coordinate = [wBaseCoordX]
; tile = d
; attributes = 0
	ld a, e
	add 8
	ld e, a
	ld [hli], a
	ld a, [wBaseCoordX]
	ld [hli], a
	ld a, d
	ld [hli], a
	xor a
	ld [hli], a
	ret

AdjustOAMBlockXPos:
	ld l, e
	ld h, d

AdjustOAMBlockXPos2:
	ld de, 4
.loop
	ld a, [wCoordAdjustmentAmount]
	ld b, a
	ld a, [hl]
	add b
	cp 168
	jr c, .skipPuttingEntryOffScreen
; put off-screen if X >= 168
	dec hl
	ld a, 160
	ld [hli], a
.skipPuttingEntryOffScreen
	ld [hl], a
	add hl, de
	dec c
	jr nz, .loop
	ret

AdjustOAMBlockYPos:
	ld l, e
	ld h, d

AdjustOAMBlockYPos2:
	ld de, 4
.loop
	ld a, [wCoordAdjustmentAmount]
	ld b, a
	ld a, [hl]
	add b
	cp 112
	jr c, .skipSettingPreviousEntrysAttribute
	dec hl
	ld a, 160 ; bug, sets previous OAM entry's attribute
	ld [hli], a
.skipSettingPreviousEntrysAttribute
	ld [hl], a
	add hl, de
	dec c
	jr nz, .loop
	ret

AnimationBlinkEnemyMon:
; Make the enemy mon's sprite blink on and off for a second or two
	ld hl, AnimationBlinkMon
	jp CallWithTurnFlipped

AnimationBlinkMon:
; Make the mon's sprite blink on and off for a second or two.
	push af
	ld c, 6
.loop
	push bc
	call AnimationHideMonPic
	ld c, 5
	call DelayFrames
	call AnimationShowMonPic
	ld c, 5
	call DelayFrames
	pop bc
	dec c
	jr nz, .loop
	pop af
	ret

AnimationFlashMonPic:
; Flashes the mon's sprite on and off
	ld a, [wBattleMonSpecies]
	ld [wChangeMonPicPlayerTurnSpecies], a
	ld a, [wEnemyMonSpecies]
	ld [wChangeMonPicEnemyTurnSpecies], a
	jp ChangeMonPic

AnimationFlashEnemyMonPic:
; Flashes the enemy mon's sprite on and off
	ld hl, AnimationFlashMonPic
	jp CallWithTurnFlipped

AnimationShowMonPic:
	xor a ; TILEMAP_MON_PIC
	call GetTileIDList
	call GetMonSpriteTileMapPointerFromRowCount
	call CopyPicTiles
	jp Delay3

AnimationShowEnemyMonPic:
; Shows the enemy mon's front sprite. Used in animations like Seismic Toss
; to make the mon's sprite reappear after disappears offscreen.
	ld hl, AnimationShowMonPic
	jp CallWithTurnFlipped

AnimationShakeBackAndForth:
; Shakes the mon's sprite back and forth rapidly. This is used in Double Team.
; The mon's sprite disappears after this animation.
	ldh a, [hWhoseTurn]
	and a
	hlcoord 0, 5
	decoord 2, 5
	jr z, .next
	hlcoord 11, 0
	decoord 13, 0

.next
	xor a ; TILEMAP_MON_PIC
	ld c, $10
.loop
	push af
	push bc
	push de
	push hl
	push hl
	push de
	push af
	push hl
	push hl
	call GetTileIDList
	pop hl
	call CopyPicTiles
	call Delay3
	pop hl
	lb bc, 7, 9
	call ClearScreenArea
	pop af
	call GetTileIDList
	pop hl
	call CopyPicTiles
	call Delay3
	pop hl
	lb bc, 7, 9
	call ClearScreenArea
	pop hl
	pop de
	pop bc
	pop af
	dec c
	jr nz, .loop
	ret

AnimationMoveMonHorizontally:
; Shifts the mon's sprite horizontally to a fixed location. Used by lots of
; animations like Tackle/Body Slam.
	call AnimationHideMonPic
	ldh a, [hWhoseTurn]
	and a
	hlcoord 2, 5
	jr z, .next
	hlcoord 11, 0
.next
	xor a ; TILEMAP_MON_PIC
	push hl
	call GetTileIDList
	pop hl
	call CopyPicTiles
	ld c, 3
	jp DelayFrames

AnimationResetMonPosition:
; Resets the mon's sprites to be located at the normal coordinates.
	ldh a, [hWhoseTurn]
	and a
	ld a, 5 * SCREEN_WIDTH + 2
	jr z, .next
	ld a, 11
.next
	call ClearMonPicFromTileMap
	jp AnimationShowMonPic

AnimationSpiralBallsInward:
; Creates an effect that looks like energy balls spiralling into the
; player mon's sprite.  Used in Focus Energy, for example.
	ldh a, [hWhoseTurn]
	and a
	jr z, .playerTurn
	ld a, -40
	ld [wSpiralBallsBaseY], a
	ld a, 80
	ld [wSpiralBallsBaseX], a
	jr .next
.playerTurn
	xor a
	ld [wSpiralBallsBaseY], a
	ld [wSpiralBallsBaseX], a
.next
	ld d, $7a ; ball tile
	ld c, 3 ; number of balls
	xor a
	call InitMultipleObjectsOAM
	ld hl, SpiralBallAnimationCoordinates
.loop
	push hl
	ld c, 3
	ld de, wOAMBuffer
.innerLoop
	ld a, [hl]
	cp $ff
	jr z, .done
	ld a, [wSpiralBallsBaseY]
	add [hl]
	ld [de], a ; Y
	inc de
	inc hl
	ld a, [wSpiralBallsBaseX]
	add [hl]
	ld [de], a ; X
	inc hl
	inc de
	inc de
	inc de
	dec c
	jr nz, .innerLoop
	ld c, 5
	call DelayFrames
	pop hl
	inc hl
	inc hl
	jr .loop
.done
	pop hl
	call AnimationCleanOAM
	jp AnimationFlashScreen

SpiralBallAnimationCoordinates:
; y, x pairs
; This is the sequence of screen coordinates that the spiralling
; balls are positioned at.
	db $38, $28
	db $40, $18
	db $50, $10
	db $60, $18
	db $68, $28
	db $60, $38
	db $50, $40
	db $40, $38
	db $40, $28
	db $46, $1E
	db $50, $18
	db $5B, $1E
	db $60, $28
	db $5B, $32
	db $50, $38
	db $46, $32
	db $48, $28
	db $50, $20
	db $58, $28
	db $50, $30
	db $50, $28
	db -1 ; end

AnimationSquishMonPic:
; Squishes the mon's sprite horizontally making it
; disappear. Used by Teleport/Sky Attack animations.
	ld c, 4
.loop
	push bc
	ldh a, [hWhoseTurn]
	and a
	jr z, .playerTurn
	hlcoord 16, 0
	decoord 14, 0
	jr .next
.playerTurn
	hlcoord 5, 5
	decoord 3, 5
.next
	push de
	xor a ; left
	ld [wSquishMonCurrentDirection], a
	call _AnimationSquishMonPic
	pop hl
	ld a, 1 ; right
	ld [wSquishMonCurrentDirection], a
	call _AnimationSquishMonPic
	pop bc
	dec c
	jr nz, .loop
	call AnimationHideMonPic
	ld c, 2
	jp DelayFrame

_AnimationSquishMonPic:
	ld c, 7
.loop
	push bc
	push hl
	ld c, 3
	ld a, [wSquishMonCurrentDirection]
	cp 0
	jr nz, .right
	call AnimCopyRowLeft
	dec hl
	jr .next
.right
	call AnimCopyRowRight
	inc hl
.next
	ld [hl], " "
	pop hl
	ld de, SCREEN_WIDTH
	add hl, de
	pop bc
	dec c
	jr nz, .loop
	jp Delay3

AnimationShootBallsUpward:
; Shoots one pillar of "energy" balls upwards. Used in Teleport/Sky Attack
; animations.
	ldh a, [hWhoseTurn]
	and a
	jr z, .playerTurn
	lb bc, 0, 16 * 8
	jr .next
.playerTurn
	lb bc, 6 * 8, 5 * 8
.next
	ld a, b
	ld [wBaseCoordY], a
	ld a, c
	ld [wBaseCoordX], a
	lb bc, 5, 1
	call _AnimationShootBallsUpward
	jp AnimationCleanOAM

_AnimationShootBallsUpward:
	push bc
	xor a
	ld [wWhichBattleAnimTileset], a
	call LoadAnimationTileset
	pop bc
	ld d, $7a ; ball tile
	ld hl, wOAMBuffer
	push bc
	ld a, [wBaseCoordY]
	ld e, a
.initOAMLoop
	call BattleAnimWriteOAMEntry
	dec b
	jr nz, .initOAMLoop
	call DelayFrame
	pop bc
	ld a, b
	ld [wNumShootingBalls], a
.loop
	push bc
	ld hl, wOAMBuffer
.innerLoop
	ld a, [wBaseCoordY]
	add 8
	ld e, a
	ld a, [hl]
	cp e ; has the ball reached the top?
	jr z, .reachedTop
	add -4 ; ball hasn't reached the top. move it up 4 pixels
	ld [hl], a
	jr .next
.reachedTop
; remove the ball once it has reached the top
	ld [hl], 0 ; put it off-screen
	ld a, [wNumShootingBalls]
	dec a
	ld [wNumShootingBalls], a
.next
	ld de, 4
	add hl, de ; next OAM entry
	dec b
	jr nz, .innerLoop
	call DelayFrames
	pop bc
	ld a, [wNumShootingBalls]
	and a
	jr nz, .loop
	ret

AnimationShootManyBallsUpward:
; Shoots several pillars of "energy" balls upward.
	ldh a, [hWhoseTurn]
	and a
	ld hl, UpwardBallsAnimXCoordinatesPlayerTurn
	ld a, $50 ; y coordinate for "energy" ball pillar
	jr z, .player
	ld hl, UpwardBallsAnimXCoordinatesEnemyTurn
	ld a, $28 ; y coordinate for "energy" ball pillar
.player
	ld [wSavedY], a
.loop
	ld a, [wSavedY]
	ld [wBaseCoordY], a
	ld a, [hli]
	cp $ff
	jp z, AnimationCleanOAM
	ld [wBaseCoordX], a
	lb bc, 4, 1
	push hl
	call _AnimationShootBallsUpward
	pop hl
	jr .loop

UpwardBallsAnimXCoordinatesPlayerTurn:
; List of x coordinates for each pillar of "energy" balls in the
; AnimationShootManyBallsUpward animation. It's unused in the game.
	db $10, $40, $28, $18, $38, $30
	db -1 ; end

UpwardBallsAnimXCoordinatesEnemyTurn:
; List of x coordinates for each pillar of "energy" balls in the
; AnimationShootManyBallsUpward animation. It's unused in the game.
	db $60, $90, $78, $68, $88, $80
	db -1 ; end

AnimationMinimizeMon:
; Changes the mon's sprite to a mini black sprite. Used by the
; Minimize animation.
	ld hl, wTempPic
	push hl
	xor a
	ld bc, 7 * 7 * $10
	call FillMemory
	pop hl
	ld de, $194
	add hl, de
	ld de, MinimizedMonSprite
	ld c, MinimizedMonSpriteEnd - MinimizedMonSprite
.loop
	ld a, [de]
	ld [hli], a
	ld [hli], a
	inc de
	dec c
	jr nz, .loop
	call CopyTempPicToMonPic
	call Delay3
	jp AnimationShowMonPic

MinimizedMonSprite:
	INCBIN "gfx/battle/minimize.1bpp"
MinimizedMonSpriteEnd:

AnimationSlideMonDownAndHide:
; Slides the mon's sprite down and disappears. Used in Acid Armor.
	ld a, TILEMAP_SLIDE_DOWN_MON_PIC_7X5
	ld c, 2
.loop
	push bc
	push af
	call AnimationHideMonPic
	pop af
	push af
	call GetTileIDList
	call GetMonSpriteTileMapPointerFromRowCount
	call CopyPicTiles
	ld c, 8
	call DelayFrames
	pop af
	inc a
	pop bc
	dec c
	jr nz, .loop
	call AnimationHideMonPic
	ld hl, wTempPic
	ld bc, 7 * 7 tiles
	xor a
	call FillMemory
	jp CopyTempPicToMonPic

_AnimationSlideMonOff:
; Slides the mon's sprite off the screen horizontally by e tiles and waits
; [wSlideMonDelay] V-blanks each time the pic is slid by one tile.
	ldh a, [hWhoseTurn]
	and a
	jr z, .playerTurn
	hlcoord 12, 0
	jr .next
.playerTurn
	hlcoord 0, 5
.next
	ld d, 8 ; d's value is unused
.slideLoop ; iterates once for each time the pic slides by one tile
	push hl
	ld b, 7
.rowLoop ; iterates once for each row
	ld c, 8
.tileLoop ; iterates once for each tile in the row
	ldh a, [hWhoseTurn]
	and a
	jr z, .playerTurn2
	call .EnemyNextTile
	jr .next2
.playerTurn2
	call .PlayerNextTile
.next2
	ld [hli], a
	dec c
	jr nz, .tileLoop
	push de
	ld de, SCREEN_WIDTH - 8
	add hl, de
	pop de
	dec b
	jr nz, .rowLoop
	ld a, [wSlideMonDelay]
	ld c, a
	call DelayFrames
	pop hl
	dec d
	dec e
	jr nz, .slideLoop
	ret

; Since mon pic tile numbers go from top to bottom, left to right in order,
; adding the height of the mon pic in tiles to a tile number gives the tile
; number of the tile one column to the right (and thus subtracting the height
; gives the reverse). If the next tile would be past the edge of the pic, the 2
; functions below catch it by checking if the tile number is within the valid
; range and if not, replacing it with a blank tile.

.PlayerNextTile
	ld a, [hl]
	add 7
; This is a bug. The lower right corner tile of the mon back pic is blanked
; while the mon is sliding off the screen. It should compare with the max tile
; plus one instead.
	cp $61
	ret c
	ld a, " "
	ret

.EnemyNextTile
	ld a, [hl]
	sub 7
; This has the same problem as above, but it has no visible effect because
; the lower right tile is in the first column to slide off the screen.
	cp $30
	ret c
	ld a, " "
	ret

AnimationSlideMonHalfOff:
; Slides the mon's sprite halfway off the screen. It's used in Softboiled.
	ld e, 4
	ld a, 4
	ld [wSlideMonDelay], a
	call _AnimationSlideMonOff
	jp Delay3

CopyTempPicToMonPic:
	ldh a, [hWhoseTurn]
	and a
	ld hl, vBackPic ; player turn
	jr z, .next
	ld hl, vFrontPic ; enemy turn
.next
	ld de, wTempPic
	ld bc, 7 * 7
	jp CopyVideoData

AnimationWavyScreen:
; used in Psywave/Psychic etc.
	ld hl, vBGMap0
	call BattleAnimCopyTileMapToVRAM
	call Delay3
	xor a
	ldh [hAutoBGTransferEnabled], a
	ld a, SCREEN_HEIGHT_PX
	ldh [hWY], a
	ld d, $80 ; terminator
	ld e, SCREEN_HEIGHT_PX - 1
	ld c, $ff
	ld hl, WavyScreenLineOffsets
.loop
	push hl
.innerLoop
	call WavyScreen_SetSCX
	ldh a, [rLY]
	cp e ; is it the last visible line in the frame?
	jr nz, .innerLoop ; keep going if not
	pop hl
	inc hl
	ld a, [hl]
	cp d ; have we reached the end?
	jr nz, .next
	ld hl, WavyScreenLineOffsets ; go back to the beginning if so
.next
	dec c
	jr nz, .loop
	xor a
	ldh [hWY], a
	call SaveScreenTilesToBuffer2
	call ClearScreen
	ld a, 1
	ldh [hAutoBGTransferEnabled], a
	call Delay3
	call LoadScreenTilesFromBuffer2
	ld hl, vBGMap1
	call BattleAnimCopyTileMapToVRAM
	ret

WavyScreen_SetSCX:
	ldh a, [rSTAT]
	and $3 ; is it H-blank?
	jr nz, WavyScreen_SetSCX ; wait until it's H-blank
	ld a, [hl]
	ldh [rSCX], a
	inc hl
	ld a, [hl]
	cp d ; have we reached the end?
	ret nz
	ld hl, WavyScreenLineOffsets ; go back to the beginning if so
	ret

WavyScreenLineOffsets:
; Sequence of horizontal line pixel offsets for the wavy screen animation.
; This sequence vaguely resembles a sine wave.
	db 0, 0, 0, 0, 0,  1,  1,  1,  2,  2,  2,  2,  2,  1,  1,  1
	db 0, 0, 0, 0, 0, -1, -1, -1, -2, -2, -2, -2, -2, -1, -1, -1
	db $80 ; terminator

AnimationSubstitute:
; Changes the pokemon's sprite to the mini sprite
	ld hl, wTempPic
	xor a
	ld bc, $310
	call FillMemory
	ldh a, [hWhoseTurn]
	and a
	jr z, .playerTurn
	ld hl, MonsterSprite tile 0 ; facing down sprite
	ld de, wTempPic + $120
	call CopyMonsterSpriteData
	ld hl, MonsterSprite tile 1
	ld de, wTempPic + $120 + $70
	call CopyMonsterSpriteData
	ld hl, MonsterSprite tile 2
	ld de, wTempPic + $120 + $10
	call CopyMonsterSpriteData
	ld hl, MonsterSprite tile 3
	ld de, wTempPic + $120 + $10 + $70
	call CopyMonsterSpriteData
	jr .next
.playerTurn
	ld hl, MonsterSprite tile 4 ; facing up sprite
	ld de, wTempPic + $120 + $70
	call CopyMonsterSpriteData
	ld hl, MonsterSprite tile 5
	ld de, wTempPic + $120 + $e0
	call CopyMonsterSpriteData
	ld hl, MonsterSprite tile 6
	ld de, wTempPic + $120 + $80
	call CopyMonsterSpriteData
	ld hl, MonsterSprite tile 7
	ld de, wTempPic + $120 + $f0
	call CopyMonsterSpriteData
.next
	call CopyTempPicToMonPic
	jp AnimationShowMonPic

CopyMonsterSpriteData:
	ld bc, 1 tiles
	ld a, BANK(MonsterSprite)
	jp FarCopyData2

HideSubstituteShowMonAnim:
	ldh a, [hWhoseTurn]
	and a
	ld hl, wPlayerMonMinimized
	ld a, [wPlayerBattleStatus2]
	jr z, .next1
	ld hl, wEnemyMonMinimized
	ld a, [wEnemyBattleStatus2]
.next1
	push hl
; if the substitute broke, slide it down, else slide it offscreen horizontally
	bit HAS_SUBSTITUTE_UP, a
	jr nz, .substituteStillUp
	call AnimationSlideMonDown
	jr .next2
.substituteStillUp
	call AnimationSlideMonOff
.next2
	pop hl
	ld a, [hl]
	and a
	jp nz, AnimationMinimizeMon
	call AnimationFlashMonPic
	jp AnimationShowMonPic

ReshowSubstituteAnim:
	call AnimationSlideMonOff
	call AnimationSubstitute
	jp AnimationShowMonPic

AnimationBoundUpAndDown:
; Bounces the mon's sprite up and down several times. It is used
; by Splash's animation.
	ld c, 5
.loop
	push bc
	call AnimationSlideMonDown
	pop bc
	dec c
	jr nz, .loop
	jp AnimationShowMonPic

AnimationTransformMon:
; Redraws this mon's sprite as the back/front sprite of the opposing mon.
; Used in Transform.
	ld a, [wEnemyMonSpecies]
	ld [wChangeMonPicPlayerTurnSpecies], a
	ld a, [wBattleMonSpecies]
	ld [wChangeMonPicEnemyTurnSpecies], a

ChangeMonPic:
	ldh a, [hWhoseTurn]
	and a
	jr z, .playerTurn
	ld a, [wChangeMonPicEnemyTurnSpecies]
	ld [wcf91], a
	ld [wd0b5], a
	xor a
	ld [wSpriteFlipped], a
	call GetMonHeader
	hlcoord 12, 0
	call LoadFrontSpriteByMonIndex
	jr .done
.playerTurn
	ld a, [wBattleMonSpecies2]
	push af
	ld a, [wChangeMonPicPlayerTurnSpecies]
	ld [wBattleMonSpecies2], a
	ld [wd0b5], a
	call GetMonHeader
	predef LoadMonBackPic
	xor a ; TILEMAP_MON_PIC
	call GetTileIDList
	call GetMonSpriteTileMapPointerFromRowCount
	call CopyPicTiles
	pop af
	ld [wBattleMonSpecies2], a
.done
	ld b, SET_PAL_BATTLE
	jp RunPaletteCommand

AnimationHideEnemyMonPic:
; Hides the enemy mon's sprite
	xor a
	ldh [hAutoBGTransferEnabled], a
	ld hl, AnimationHideMonPic
	call CallWithTurnFlipped
	ld a, $1
	ldh [hAutoBGTransferEnabled], a
	jp Delay3

InitMultipleObjectsOAM:
; Writes c OAM entries with tile d.
; Sets their Y coordinates to sequential multiples of 8, starting from 0.
; Sets their X coordinates to 0.
; Loads animation tileset a.
	push bc
	push de
	ld [wWhichBattleAnimTileset], a
	call LoadAnimationTileset
	pop de
	pop bc
	xor a
	ld e, a
	ld [wBaseCoordX], a
	ld hl, wOAMBuffer
.loop
	call BattleAnimWriteOAMEntry
	dec c
	jr nz, .loop
	ret

AnimationHideMonPic:
; Hides the mon's sprite.
	ldh a, [hWhoseTurn]
	and a
	jr z, .playerTurn
	ld a, 12
	jr ClearMonPicFromTileMap
.playerTurn
	ld a, 5 * SCREEN_WIDTH + 1

ClearMonPicFromTileMap:
	push hl
	push de
	push bc
	ld e, a
	ld d, 0
	hlcoord 0, 0
	add hl, de
	lb bc, 7, 7
	call ClearScreenArea
	pop bc
	pop de
	pop hl
	ret

; puts the tile map destination address of a mon sprite in hl, given the row count in b
; The usual row count is 7, but it may be smaller when sliding a mon sprite in/out,
; in order to show only a portion of the mon sprite.
GetMonSpriteTileMapPointerFromRowCount:
	push de
	ldh a, [hWhoseTurn]
	and a
	jr nz, .enemyTurn
	ld a, 20 * 5 + 1
	jr .next
.enemyTurn
	ld a, 12
.next
	hlcoord 0, 0
	ld e, a
	ld d, 0
	add hl, de
	ld a, 7
	sub b
	and a
	jr z, .done
	ld de, 20
.loop
	add hl, de
	dec a
	jr nz, .loop
.done
	pop de
	ret

; Input:
; a = tile ID list index
; Output:
; de = tile ID list pointer
; b = number of rows
; c = number of columns
GetTileIDList:
	ld hl, TileIDListPointerTable
	ld e, a
	ld d, 0
	add hl, de
	add hl, de
	add hl, de
	ld a, [hli]
	ld e, a
	ld a, [hli]
	ld d, a
	ld a, [hli]
	ld b, a
	and $f
	ld c, a
	ld a, b
	swap a
	and $f
	ld b, a
	ret

AnimCopyRowLeft:
; copy a row of c tiles 1 tile left
	ld a, [hld]
	ld [hli], a
	inc hl
	dec c
	jr nz, AnimCopyRowLeft
	ret

AnimCopyRowRight:
; copy a row of c tiles 1 tile right
	ld a, [hli]
	ld [hld], a
	dec hl
	dec c
	jr nz, AnimCopyRowRight
	ret

; get the sound of the move id in b
GetMoveSoundB:
	ld a, b
	call GetMoveSound
	ld b, a
	ret

GetMoveSound:
	ld hl, MoveSoundTable
	ld e, a
	ld d, 0
	add hl, de
	add hl, de
	add hl, de
	ld a, [hli]
	ld b, a
	call IsCryMove
	jr nc, .NotCryMove
	ldh a, [hWhoseTurn]
	and a
	jr nz, .next
	ld a, [wBattleMonSpecies] ; get number of current monster
	jr .Continue
.next
	ld a, [wEnemyMonSpecies]
.Continue
	push hl
	call GetCryData
	ld b, a
	pop hl
	ld a, [wFrequencyModifier]
	add [hl]
	ld [wFrequencyModifier], a
	inc hl
	ld a, [wTempoModifier]
	add [hl]
	ld [wTempoModifier], a
	jr .done
.NotCryMove
	ld a, [hli]
	ld [wFrequencyModifier], a
	ld a, [hli]
	ld [wTempoModifier], a
.done
	ld a, b
	ret

IsCryMove:
; set carry if the move animation involves playing a monster cry
	ld a, [wAnimationID]
	cp GROWL
	jr z, .CryMove
	cp ROAR
	jr z, .CryMove
	and a ; clear carry
	ret
.CryMove
	scf
	ret

INCLUDE "data/moves/sfx.asm"

CopyPicTiles:
	ldh a, [hWhoseTurn]
	and a
	ld a, $31 ; base tile ID of player mon sprite
	jr z, .next
; enemy turn
	xor a ; base tile ID of enemy mon sprite
.next
	ldh [hBaseTileID], a
	jr CopyTileIDs_NoBGTransfer

; copy the tiles used when a mon is being sent out of or into a pokeball
CopyDownscaledMonTiles:
	call GetPredefRegisters
	ld a, [wDownscaledMonSize]
	and a
	jr nz, .smallerSize
	ld de, DownscaledMonTiles_5x5
	jr CopyTileIDs_NoBGTransfer
.smallerSize
	ld de, DownscaledMonTiles_3x3
; fall through

CopyTileIDs_NoBGTransfer:
	xor a
	ldh [hAutoBGTransferEnabled], a
; fall through

; b = number of rows
; c = number of columns
CopyTileIDs:
	push hl
.rowLoop
	push bc
	push hl
	ldh a, [hBaseTileID]
	ld b, a
.columnLoop
	ld a, [de]
	add b
	inc de
	ld [hli], a
	dec c
	jr nz, .columnLoop
	pop hl
	ld bc, 20
	add hl, bc
	pop bc
	dec b
	jr nz, .rowLoop
	ld a, $1
	ldh [hAutoBGTransferEnabled], a
	pop hl
	ret

INCLUDE "data/tilemaps.asm"

AnimationLeavesFalling:
; Makes leaves float down from the top of the screen. This is used
; in Razor Leaf's animation.
	ldh a, [rOBP0]
	push af
	ld a, [wAnimPalette]
	ldh [rOBP0], a
	ld d, $37 ; leaf tile
	ld a, 3 ; number of leaves
	ld [wNumFallingObjects], a
	call AnimationFallingObjects
	pop af
	ldh [rOBP0], a
	ret

AnimationPetalsFalling:
; Makes lots of petals fall down from the top of the screen. It's used in
; the animation for Petal Dance.
	ld d, $71 ; petal tile
	ld a, 20 ; number of petals
	ld [wNumFallingObjects], a
	call AnimationFallingObjects
	jp ClearSprites

AnimationFallingObjects:
	ld c, a
	ld a, 1
	call InitMultipleObjectsOAM
	call FallingObjects_InitXCoords
	call FallingObjects_InitMovementData
	ld hl, wOAMBuffer
	ld [hl], 0
.loop
	ld hl, wFallingObjectsMovementData
	ld de, 0
	ld a, [wNumFallingObjects]
	ld c, a
.innerLoop
	push bc
	push hl
	push de
	ld a, [hl]
	ld [wFallingObjectMovementByte], a
	call FallingObjects_UpdateMovementByte
	call FallingObjects_UpdateOAMEntry
	pop de
	ld hl, 4
	add hl, de
	ld e, l
	ld d, h
	pop hl
	ld a, [wFallingObjectMovementByte]
	ld [hli], a
	pop bc
	dec c
	jr nz, .innerLoop
	call Delay3
	ld hl, wOAMBuffer
	ld a, [hl] ; Y
	cp 104 ; has the top falling object reached 104 yet?
	jr nz, .loop ; keep moving the falling objects down until it does
	ret

FallingObjects_UpdateOAMEntry:
; Increases Y by 2 pixels and adjusts X and X flip based on the falling object's
; movement byte.
	ld hl, wOAMBuffer
	add hl, de
	ld a, [hl]
	inc a
	inc a
	cp 112
	jr c, .next
	ld a, 160 ; if Y >= 112, put it off-screen
.next
	ld [hli], a ; Y
	ld a, [wFallingObjectMovementByte]
	ld b, a
	ld de, FallingObjects_DeltaXs
	and $7f
	add e
	jr nc, .noCarry
	inc d
.noCarry
	ld e, a
	ld a, b
	and $80
	jr nz, .movingLeft
; moving right
	ld a, [de]
	add [hl]
	ld [hli], a ; X
	inc hl
	xor a ; no horizontal flip
	jr .next2
.movingLeft
	ld a, [de]
	ld b, a
	ld a, [hl]
	sub b
	ld [hli], a ; X
	inc hl
	ld a, (1 << OAM_X_FLIP)
.next2
	ld [hl], a ; attribute
	ret

FallingObjects_DeltaXs:
	db 0, 1, 3, 5, 7, 9, 11, 13, 15

FallingObjects_UpdateMovementByte:
	ld a, [wFallingObjectMovementByte]
	inc a
	ld b, a
	and $7f
	cp 9 ; have we reached the end of the delta-Xs?
	ld a, b
	jr nz, .next
; We've reached the end of the delta-Xs, so wrap to the start and change
; direction from right to left or vice versa.
	and $80
	xor $80
.next
	ld [wFallingObjectMovementByte], a
	ret

FallingObjects_InitXCoords:
	ld hl, wOAMBuffer + $01
	ld de, FallingObjects_InitialXCoords
	ld a, [wNumFallingObjects]
	ld c, a
.loop
	ld a, [de]
	ld [hli], a
	inc hl
	inc hl
	inc hl
	inc de
	dec c
	jr nz, .loop
	ret

FallingObjects_InitialXCoords:
	db $38, $40, $50, $60, $70, $88, $90, $56, $67, $4A, $77, $84, $98, $32, $22, $5C, $6C, $7D, $8E, $99

FallingObjects_InitMovementData:
	ld hl, wFallingObjectsMovementData
	ld de, FallingObjects_InitialMovementData
	ld a, [wNumFallingObjects]
	ld c, a
.loop
	ld a, [de]
	ld [hli], a
	inc de
	dec c
	jr nz, .loop
	ret

FallingObjects_InitialMovementData:
	db $00, $84, $06, $81, $02, $88, $01, $83, $05, $89, $09, $80, $07, $87, $03, $82, $04, $85, $08, $86

AnimationShakeEnemyHUD:
; Shakes the enemy HUD.

; Make a copy of the back pic's tile patterns in sprite tile pattern VRAM.
	ld de, vBackPic
	ld hl, vSprites
	ld bc, 7 * 7
	call CopyVideoData

	xor a
	ldh [hSCX], a

; Copy wTileMap to BG map 0. The regular BG (not the window) is set to use
; map 0 and can be scrolled with SCX, which allows a shaking effect.
	ld hl, vBGMap0
	call BattleAnimCopyTileMapToVRAM

; Now that the regular BG is showing the same thing the window was, move the
; window off the screen so that we can modify its contents below.
	ld a, SCREEN_HEIGHT_PX
	ldh [hWY], a

; Copy wTileMap to VRAM such that the row below the enemy HUD (in wTileMap) is
; lined up with row 0 of the window.
	ld hl, vBGMap1 - $20 * 7
	call BattleAnimCopyTileMapToVRAM

; Move the window so that the row below the enemy HUD (in BG map 0) lines up
; with the top row of the window on the screen. This makes it so that the window
; covers everything below the enemy HD with a copy that looks just like what
; was there before.
	ld a, 7 * 8
	ldh [hWY], a

; Write OAM entries so that the copy of the back pic from the top of this
; function shows up on screen. We need this because the back pic's Y coordinates
; range overlaps with that of the enemy HUD and we don't want to shake the top
; of the back pic when we shake the enemy HUD. The OAM copy won't be affected
; by SCX.
	call ShakeEnemyHUD_WritePlayerMonPicOAM

	ld hl, vBGMap0
	call BattleAnimCopyTileMapToVRAM

; Remove the back pic from the BG map.
	call AnimationHideMonPic
	call Delay3

; Use SCX to shake the regular BG. The window and the back pic OAM copy are
; not affected.
	lb de, 2, 8
	call ShakeEnemyHUD_ShakeBG

; Restore the original graphics.
	call AnimationShowMonPic
	call ClearSprites
	ld a, SCREEN_HEIGHT_PX
	ldh [hWY], a
	ld hl, vBGMap1
	call BattleAnimCopyTileMapToVRAM
	xor a
	ldh [hWY], a
	call SaveScreenTilesToBuffer1
	ld hl, vBGMap0
	call BattleAnimCopyTileMapToVRAM
	call ClearScreen
	call Delay3
	call LoadScreenTilesFromBuffer1
	ld hl, vBGMap1
	jp BattleAnimCopyTileMapToVRAM

; b = tile ID list index
; c = base tile ID
CopyTileIDsFromList:
	call GetPredefRegisters
	ld a, c
	ldh [hBaseTileID], a
	ld a, b
	push hl
	call GetTileIDList
	pop hl
	jp CopyTileIDs

ShakeEnemyHUD_ShakeBG:
	ldh a, [hSCX]
	ld [wTempSCX], a
.loop
	ld a, [wTempSCX]
	add d
	ldh [hSCX], a
	ld c, 2
	call DelayFrames
	ld a, [wTempSCX]
	sub d
	ldh [hSCX], a
	ld c, 2
	call DelayFrames
	dec e
	jr nz, .loop
	ld a, [wTempSCX]
	ldh [hSCX], a
	ret

BattleAnimCopyTileMapToVRAM:
	ld a, h
	ldh [hAutoBGTransferDest + 1], a
	ld a, l
	ldh [hAutoBGTransferDest], a
	jp Delay3

TossBallAnimation:
	ld a, [wIsInBattle]
	cp 2
	jr z, .BlockBall ; if in trainer battle, play different animation
	ld a, [wPokeBallAnimData]
	ld b, a

	; upper nybble: how many animations (from PokeBallAnimations) to play
	; this will be 4 for successful capture, 6 for breakout
	and $F0
	swap a
	ld c, a

	; lower nybble: number of shakes
	; store these for later
	ld a, b
	and $F
	ld [wNumShakes], a

	ld hl, .PokeBallAnimations
	; choose which toss animation to use
	ld a, [wcf91]
	cp POKE_BALL
	ld b, TOSS_ANIM
	jr z, .done
	cp GREAT_BALL
	ld b, GREATTOSS_ANIM
	jr z, .done
	ld b, ULTRATOSS_ANIM
.done
	ld a, b
.PlayNextAnimation
	ld [wAnimationID], a
	push bc
	push hl
	call PlayAnimation
	pop hl
	ld a, [hli]
	pop bc
	dec c
	jr nz, .PlayNextAnimation
	ret

.PokeBallAnimations:
; sequence of animations that make up the Poké Ball toss
	db POOF_ANIM, HIDEPIC_ANIM, SHAKE_ANIM, POOF_ANIM, SHOWPIC_ANIM

.BlockBall
	ld a, TOSS_ANIM
	ld [wAnimationID], a
	call PlayAnimation
	ld a, SFX_FAINT_THUD
	call PlaySound
	ld a, BLOCKBALL_ANIM
	ld [wAnimationID], a
	jp PlayAnimation

PlayApplyingAttackSound:
; play a different sound depending if move is not very effective, neutral, or super-effective
; don't play any sound at all if move is ineffective
	call WaitForSoundToFinish
	ld a, [wDamageMultipliers]
	and $7f
	ret z
	cp 10
	ld a, $20
	ld b, $30
	ld c, SFX_DAMAGE
	jr z, .playSound
	ld a, $e0
	ld b, $ff
	ld c, SFX_SUPER_EFFECTIVE
	jr nc, .playSound
	ld a, $50
	ld b, $1
	ld c, SFX_NOT_VERY_EFFECTIVE
.playSound
	ld [wFrequencyModifier], a
	ld a, b
	ld [wTempoModifier], a
	ld a, c
	jp PlaySound