mirror of
https://github.com/pret/pokered.git
synced 2024-10-23 06:58:24 +00:00
7047 lines
155 KiB
NASM
7047 lines
155 KiB
NASM
BattleCore:
|
|
|
|
INCLUDE "data/battle/residual_effects_1.asm"
|
|
INCLUDE "data/battle/set_damage_effects.asm"
|
|
INCLUDE "data/battle/residual_effects_2.asm"
|
|
INCLUDE "data/battle/always_happen_effects.asm"
|
|
INCLUDE "data/battle/special_effects.asm"
|
|
|
|
SlidePlayerAndEnemySilhouettesOnScreen:
|
|
call LoadPlayerBackPic
|
|
ld a, MESSAGE_BOX ; the usual text box at the bottom of the screen
|
|
ld [wTextBoxID], a
|
|
call DisplayTextBoxID
|
|
hlcoord 1, 5
|
|
lb bc, 3, 7
|
|
call ClearScreenArea
|
|
call DisableLCD
|
|
call LoadFontTilePatterns
|
|
call LoadHudAndHpBarAndStatusTilePatterns
|
|
ld hl, vBGMap0
|
|
ld bc, $400
|
|
.clearBackgroundLoop
|
|
ld a, " "
|
|
ld [hli], a
|
|
dec bc
|
|
ld a, b
|
|
or c
|
|
jr nz, .clearBackgroundLoop
|
|
; copy the work RAM tile map to VRAM
|
|
hlcoord 0, 0
|
|
ld de, vBGMap0
|
|
ld b, 18 ; number of rows
|
|
.copyRowLoop
|
|
ld c, 20 ; number of columns
|
|
.copyColumnLoop
|
|
ld a, [hli]
|
|
ld [de], a
|
|
inc e
|
|
dec c
|
|
jr nz, .copyColumnLoop
|
|
ld a, 12 ; number of off screen tiles to the right of screen in VRAM
|
|
add e ; skip the off screen tiles
|
|
ld e, a
|
|
jr nc, .noCarry
|
|
inc d
|
|
.noCarry
|
|
dec b
|
|
jr nz, .copyRowLoop
|
|
call EnableLCD
|
|
ld a, $90
|
|
ldh [hWY], a
|
|
ldh [rWY], a
|
|
xor a
|
|
ldh [hTileAnimations], a
|
|
ldh [hSCY], a
|
|
dec a
|
|
ld [wUpdateSpritesEnabled], a
|
|
call Delay3
|
|
xor a
|
|
ldh [hAutoBGTransferEnabled], a
|
|
ld b, $70
|
|
ld c, $90
|
|
ld a, c
|
|
ldh [hSCX], a
|
|
call DelayFrame
|
|
ld a, %11100100 ; inverted palette for silhouette effect
|
|
ldh [rBGP], a
|
|
ldh [rOBP0], a
|
|
ldh [rOBP1], a
|
|
.slideSilhouettesLoop ; slide silhouettes of the player's pic and the enemy's pic onto the screen
|
|
ld h, b
|
|
ld l, $40
|
|
call SetScrollXForSlidingPlayerBodyLeft ; begin background scrolling on line $40
|
|
inc b
|
|
inc b
|
|
ld h, $0
|
|
ld l, $60
|
|
call SetScrollXForSlidingPlayerBodyLeft ; end background scrolling on line $60
|
|
call SlidePlayerHeadLeft
|
|
ld a, c
|
|
ldh [hSCX], a
|
|
dec c
|
|
dec c
|
|
jr nz, .slideSilhouettesLoop
|
|
ld a, $1
|
|
ldh [hAutoBGTransferEnabled], a
|
|
ld a, $31
|
|
ldh [hStartTileID], a
|
|
hlcoord 1, 5
|
|
predef CopyUncompressedPicToTilemap
|
|
xor a
|
|
ldh [hWY], a
|
|
ldh [rWY], a
|
|
inc a
|
|
ldh [hAutoBGTransferEnabled], a
|
|
call Delay3
|
|
ld b, SET_PAL_BATTLE
|
|
call RunPaletteCommand
|
|
call HideSprites
|
|
jpfar PrintBeginningBattleText
|
|
|
|
; when a battle is starting, silhouettes of the player's pic and the enemy's pic are slid onto the screen
|
|
; the lower of the player's pic (his body) is part of the background, but his head is a sprite
|
|
; the reason for this is that it shares Y coordinates with the lower part of the enemy pic, so background scrolling wouldn't work for both pics
|
|
; instead, the enemy pic is part of the background and uses the scroll register, while the player's head is a sprite and is slid by changing its X coordinates in a loop
|
|
SlidePlayerHeadLeft:
|
|
push bc
|
|
ld hl, wShadowOAMSprite00XCoord
|
|
ld c, $15 ; number of OAM entries
|
|
ld de, $4 ; size of OAM entry
|
|
.loop
|
|
dec [hl] ; decrement X
|
|
dec [hl] ; decrement X
|
|
add hl, de ; next OAM entry
|
|
dec c
|
|
jr nz, .loop
|
|
pop bc
|
|
ret
|
|
|
|
SetScrollXForSlidingPlayerBodyLeft:
|
|
ldh a, [rLY]
|
|
cp l
|
|
jr nz, SetScrollXForSlidingPlayerBodyLeft
|
|
ld a, h
|
|
ldh [rSCX], a
|
|
.loop
|
|
ldh a, [rLY]
|
|
cp h
|
|
jr z, .loop
|
|
ret
|
|
|
|
StartBattle:
|
|
xor a
|
|
ld [wPartyGainExpFlags], a
|
|
ld [wPartyFoughtCurrentEnemyFlags], a
|
|
ld [wActionResultOrTookBattleTurn], a
|
|
inc a
|
|
ld [wFirstMonsNotOutYet], a
|
|
ld hl, wEnemyMon1HP
|
|
ld bc, wEnemyMon2 - wEnemyMon1 - 1
|
|
ld d, $3
|
|
.findFirstAliveEnemyMonLoop
|
|
inc d
|
|
ld a, [hli]
|
|
or [hl]
|
|
jr nz, .foundFirstAliveEnemyMon
|
|
add hl, bc
|
|
jr .findFirstAliveEnemyMonLoop
|
|
.foundFirstAliveEnemyMon
|
|
ld a, d
|
|
ld [wSerialExchangeNybbleReceiveData], a
|
|
ld a, [wIsInBattle]
|
|
dec a ; is it a trainer battle?
|
|
call nz, EnemySendOutFirstMon ; if it is a trainer battle, send out enemy mon
|
|
ld c, 40
|
|
call DelayFrames
|
|
call SaveScreenTilesToBuffer1
|
|
.checkAnyPartyAlive
|
|
call AnyPartyAlive
|
|
ld a, d
|
|
and a
|
|
jp z, HandlePlayerBlackOut ; jump if no mon is alive
|
|
call LoadScreenTilesFromBuffer1
|
|
ld a, [wBattleType]
|
|
and a ; is it a normal battle?
|
|
jp z, .playerSendOutFirstMon ; if so, send out player mon
|
|
; safari zone battle
|
|
.displaySafariZoneBattleMenu
|
|
call DisplayBattleMenu
|
|
ret c ; return if the player ran from battle
|
|
ld a, [wActionResultOrTookBattleTurn]
|
|
and a ; was the item used successfully?
|
|
jr z, .displaySafariZoneBattleMenu ; if not, display the menu again; XXX does this ever jump?
|
|
ld a, [wNumSafariBalls]
|
|
and a
|
|
jr nz, .notOutOfSafariBalls
|
|
call LoadScreenTilesFromBuffer1
|
|
ld hl, .outOfSafariBallsText
|
|
jp PrintText
|
|
.notOutOfSafariBalls
|
|
callfar PrintSafariZoneBattleText
|
|
ld a, [wEnemyMonSpeed + 1]
|
|
add a
|
|
ld b, a ; init b (which is later compared with random value) to (enemy speed % 256) * 2
|
|
jp c, EnemyRan ; if (enemy speed % 256) > 127, the enemy runs
|
|
ld a, [wSafariBaitFactor]
|
|
and a ; is bait factor 0?
|
|
jr z, .checkEscapeFactor
|
|
; bait factor is not 0
|
|
; divide b by 4 (making the mon less likely to run)
|
|
srl b
|
|
srl b
|
|
.checkEscapeFactor
|
|
ld a, [wSafariEscapeFactor]
|
|
and a ; is escape factor 0?
|
|
jr z, .compareWithRandomValue
|
|
; escape factor is not 0
|
|
; multiply b by 2 (making the mon more likely to run)
|
|
sla b
|
|
jr nc, .compareWithRandomValue
|
|
; cap b at 255
|
|
ld b, $ff
|
|
.compareWithRandomValue
|
|
call Random
|
|
cp b
|
|
jr nc, .checkAnyPartyAlive
|
|
jr EnemyRan ; if b was greater than the random value, the enemy runs
|
|
|
|
.outOfSafariBallsText
|
|
text_far _OutOfSafariBallsText
|
|
text_end
|
|
|
|
.playerSendOutFirstMon
|
|
xor a
|
|
ld [wWhichPokemon], a
|
|
.findFirstAliveMonLoop
|
|
call HasMonFainted
|
|
jr nz, .foundFirstAliveMon
|
|
; fainted, go to the next one
|
|
ld hl, wWhichPokemon
|
|
inc [hl]
|
|
jr .findFirstAliveMonLoop
|
|
.foundFirstAliveMon
|
|
ld a, [wWhichPokemon]
|
|
ld [wPlayerMonNumber], a
|
|
inc a
|
|
ld hl, wPartySpecies - 1
|
|
ld c, a
|
|
ld b, 0
|
|
add hl, bc
|
|
ld a, [hl] ; species
|
|
ld [wcf91], a
|
|
ld [wBattleMonSpecies2], a
|
|
call LoadScreenTilesFromBuffer1
|
|
hlcoord 1, 5
|
|
ld a, $9
|
|
call SlideTrainerPicOffScreen
|
|
call SaveScreenTilesToBuffer1
|
|
ld a, [wWhichPokemon]
|
|
ld c, a
|
|
ld b, FLAG_SET
|
|
push bc
|
|
ld hl, wPartyGainExpFlags
|
|
predef FlagActionPredef
|
|
ld hl, wPartyFoughtCurrentEnemyFlags
|
|
pop bc
|
|
predef FlagActionPredef
|
|
call LoadBattleMonFromParty
|
|
call LoadScreenTilesFromBuffer1
|
|
call SendOutMon
|
|
jr MainInBattleLoop
|
|
|
|
; wild mon or link battle enemy ran from battle
|
|
EnemyRan:
|
|
call LoadScreenTilesFromBuffer1
|
|
ld a, [wLinkState]
|
|
cp LINK_STATE_BATTLING
|
|
ld hl, WildRanText
|
|
jr nz, .printText
|
|
; link battle
|
|
xor a
|
|
ld [wBattleResult], a
|
|
ld hl, EnemyRanText
|
|
.printText
|
|
call PrintText
|
|
ld a, SFX_RUN
|
|
call PlaySoundWaitForCurrent
|
|
xor a
|
|
ldh [hWhoseTurn], a
|
|
jpfar AnimationSlideEnemyMonOff
|
|
|
|
WildRanText:
|
|
text_far _WildRanText
|
|
text_end
|
|
|
|
EnemyRanText:
|
|
text_far _EnemyRanText
|
|
text_end
|
|
|
|
MainInBattleLoop:
|
|
call ReadPlayerMonCurHPAndStatus
|
|
ld hl, wBattleMonHP
|
|
ld a, [hli]
|
|
or [hl] ; is battle mon HP 0?
|
|
jp z, HandlePlayerMonFainted ; if battle mon HP is 0, jump
|
|
ld hl, wEnemyMonHP
|
|
ld a, [hli]
|
|
or [hl] ; is enemy mon HP 0?
|
|
jp z, HandleEnemyMonFainted ; if enemy mon HP is 0, jump
|
|
call SaveScreenTilesToBuffer1
|
|
xor a
|
|
ld [wFirstMonsNotOutYet], a
|
|
ld a, [wPlayerBattleStatus2]
|
|
and (1 << NEEDS_TO_RECHARGE) | (1 << USING_RAGE) ; check if the player is using Rage or needs to recharge
|
|
jr nz, .selectEnemyMove
|
|
; the player is not using Rage and doesn't need to recharge
|
|
ld hl, wEnemyBattleStatus1
|
|
res FLINCHED, [hl] ; reset flinch bit
|
|
ld hl, wPlayerBattleStatus1
|
|
res FLINCHED, [hl] ; reset flinch bit
|
|
ld a, [hl]
|
|
and (1 << THRASHING_ABOUT) | (1 << CHARGING_UP) ; check if the player is thrashing about or charging for an attack
|
|
jr nz, .selectEnemyMove ; if so, jump
|
|
; the player is neither thrashing about nor charging for an attack
|
|
call DisplayBattleMenu ; show battle menu
|
|
ret c ; return if player ran from battle
|
|
ld a, [wEscapedFromBattle]
|
|
and a
|
|
ret nz ; return if pokedoll was used to escape from battle
|
|
ld a, [wBattleMonStatus]
|
|
and (1 << FRZ) | SLP_MASK
|
|
jr nz, .selectEnemyMove ; if so, jump
|
|
ld a, [wPlayerBattleStatus1]
|
|
and (1 << STORING_ENERGY) | (1 << USING_TRAPPING_MOVE) ; check player is using Bide or using a multi-turn attack like wrap
|
|
jr nz, .selectEnemyMove ; if so, jump
|
|
ld a, [wEnemyBattleStatus1]
|
|
bit USING_TRAPPING_MOVE, a ; check if enemy is using a multi-turn attack like wrap
|
|
jr z, .selectPlayerMove ; if not, jump
|
|
; enemy is using a multi-turn attack like wrap, so player is trapped and cannot execute a move
|
|
ld a, $ff
|
|
ld [wPlayerSelectedMove], a
|
|
jr .selectEnemyMove
|
|
.selectPlayerMove
|
|
ld a, [wActionResultOrTookBattleTurn]
|
|
and a ; has the player already used the turn (e.g. by using an item, trying to run or switching pokemon)
|
|
jr nz, .selectEnemyMove
|
|
ld [wMoveMenuType], a
|
|
inc a
|
|
ld [wAnimationID], a
|
|
xor a
|
|
ld [wMenuItemToSwap], a
|
|
call MoveSelectionMenu
|
|
push af
|
|
call LoadScreenTilesFromBuffer1
|
|
call DrawHUDsAndHPBars
|
|
pop af
|
|
jr nz, MainInBattleLoop ; if the player didn't select a move, jump
|
|
.selectEnemyMove
|
|
call SelectEnemyMove
|
|
ld a, [wLinkState]
|
|
cp LINK_STATE_BATTLING
|
|
jr nz, .noLinkBattle
|
|
; link battle
|
|
ld a, [wSerialExchangeNybbleReceiveData]
|
|
cp LINKBATTLE_RUN
|
|
jp z, EnemyRan
|
|
cp LINKBATTLE_STRUGGLE
|
|
jr z, .noLinkBattle
|
|
cp LINKBATTLE_NO_ACTION
|
|
jr z, .noLinkBattle
|
|
sub 4
|
|
jr c, .noLinkBattle
|
|
; the link battle enemy has switched mons
|
|
ld a, [wPlayerBattleStatus1]
|
|
bit USING_TRAPPING_MOVE, a ; check if using multi-turn move like Wrap
|
|
jr z, .specialMoveNotUsed
|
|
ld a, [wPlayerMoveListIndex]
|
|
ld hl, wBattleMonMoves
|
|
ld c, a
|
|
ld b, 0
|
|
add hl, bc
|
|
ld a, [hl]
|
|
cp METRONOME ; a MIRROR MOVE check is missing, might lead to a desync in link battles
|
|
; when combined with multi-turn moves
|
|
jr nz, .specialMoveNotUsed
|
|
ld [wPlayerSelectedMove], a
|
|
.specialMoveNotUsed
|
|
callfar SwitchEnemyMon
|
|
.noLinkBattle
|
|
ld a, [wPlayerSelectedMove]
|
|
cp QUICK_ATTACK
|
|
jr nz, .playerDidNotUseQuickAttack
|
|
ld a, [wEnemySelectedMove]
|
|
cp QUICK_ATTACK
|
|
jr z, .compareSpeed ; if both used Quick Attack
|
|
jp .playerMovesFirst ; if player used Quick Attack and enemy didn't
|
|
.playerDidNotUseQuickAttack
|
|
ld a, [wEnemySelectedMove]
|
|
cp QUICK_ATTACK
|
|
jr z, .enemyMovesFirst ; if enemy used Quick Attack and player didn't
|
|
ld a, [wPlayerSelectedMove]
|
|
cp COUNTER
|
|
jr nz, .playerDidNotUseCounter
|
|
ld a, [wEnemySelectedMove]
|
|
cp COUNTER
|
|
jr z, .compareSpeed ; if both used Counter
|
|
jr .enemyMovesFirst ; if player used Counter and enemy didn't
|
|
.playerDidNotUseCounter
|
|
ld a, [wEnemySelectedMove]
|
|
cp COUNTER
|
|
jr z, .playerMovesFirst ; if enemy used Counter and player didn't
|
|
.compareSpeed
|
|
ld de, wBattleMonSpeed ; player speed value
|
|
ld hl, wEnemyMonSpeed ; enemy speed value
|
|
ld c, $2
|
|
call StringCmp ; compare speed values
|
|
jr z, .speedEqual
|
|
jr nc, .playerMovesFirst ; if player is faster
|
|
jr .enemyMovesFirst ; if enemy is faster
|
|
.speedEqual ; 50/50 chance for both players
|
|
ldh a, [hSerialConnectionStatus]
|
|
cp USING_INTERNAL_CLOCK
|
|
jr z, .invertOutcome
|
|
call BattleRandom
|
|
cp 50 percent + 1
|
|
jr c, .playerMovesFirst
|
|
jr .enemyMovesFirst
|
|
.invertOutcome
|
|
call BattleRandom
|
|
cp 50 percent + 1
|
|
jr c, .enemyMovesFirst
|
|
jr .playerMovesFirst
|
|
.enemyMovesFirst
|
|
ld a, $1
|
|
ldh [hWhoseTurn], a
|
|
callfar TrainerAI
|
|
jr c, .AIActionUsedEnemyFirst
|
|
call ExecuteEnemyMove
|
|
ld a, [wEscapedFromBattle]
|
|
and a ; was Teleport, Road, or Whirlwind used to escape from battle?
|
|
ret nz ; if so, return
|
|
ld a, b
|
|
and a
|
|
jp z, HandlePlayerMonFainted
|
|
.AIActionUsedEnemyFirst
|
|
call HandlePoisonBurnLeechSeed
|
|
jp z, HandleEnemyMonFainted
|
|
call DrawHUDsAndHPBars
|
|
call ExecutePlayerMove
|
|
ld a, [wEscapedFromBattle]
|
|
and a ; was Teleport, Road, or Whirlwind used to escape from battle?
|
|
ret nz ; if so, return
|
|
ld a, b
|
|
and a
|
|
jp z, HandleEnemyMonFainted
|
|
call HandlePoisonBurnLeechSeed
|
|
jp z, HandlePlayerMonFainted
|
|
call DrawHUDsAndHPBars
|
|
call CheckNumAttacksLeft
|
|
jp MainInBattleLoop
|
|
.playerMovesFirst
|
|
call ExecutePlayerMove
|
|
ld a, [wEscapedFromBattle]
|
|
and a ; was Teleport, Road, or Whirlwind used to escape from battle?
|
|
ret nz ; if so, return
|
|
ld a, b
|
|
and a
|
|
jp z, HandleEnemyMonFainted
|
|
call HandlePoisonBurnLeechSeed
|
|
jp z, HandlePlayerMonFainted
|
|
call DrawHUDsAndHPBars
|
|
ld a, $1
|
|
ldh [hWhoseTurn], a
|
|
callfar TrainerAI
|
|
jr c, .AIActionUsedPlayerFirst
|
|
call ExecuteEnemyMove
|
|
ld a, [wEscapedFromBattle]
|
|
and a ; was Teleport, Road, or Whirlwind used to escape from battle?
|
|
ret nz ; if so, return
|
|
ld a, b
|
|
and a
|
|
jp z, HandlePlayerMonFainted
|
|
.AIActionUsedPlayerFirst
|
|
call HandlePoisonBurnLeechSeed
|
|
jp z, HandleEnemyMonFainted
|
|
call DrawHUDsAndHPBars
|
|
call CheckNumAttacksLeft
|
|
jp MainInBattleLoop
|
|
|
|
HandlePoisonBurnLeechSeed:
|
|
ld hl, wBattleMonHP
|
|
ld de, wBattleMonStatus
|
|
ldh a, [hWhoseTurn]
|
|
and a
|
|
jr z, .playersTurn
|
|
ld hl, wEnemyMonHP
|
|
ld de, wEnemyMonStatus
|
|
.playersTurn
|
|
ld a, [de]
|
|
and (1 << BRN) | (1 << PSN)
|
|
jr z, .notBurnedOrPoisoned
|
|
push hl
|
|
ld hl, HurtByPoisonText
|
|
ld a, [de]
|
|
and 1 << BRN
|
|
jr z, .poisoned
|
|
ld hl, HurtByBurnText
|
|
.poisoned
|
|
call PrintText
|
|
xor a
|
|
ld [wAnimationType], a
|
|
ld a, BURN_PSN_ANIM
|
|
call PlayMoveAnimation ; play burn/poison animation
|
|
pop hl
|
|
call HandlePoisonBurnLeechSeed_DecreaseOwnHP
|
|
.notBurnedOrPoisoned
|
|
ld de, wPlayerBattleStatus2
|
|
ldh a, [hWhoseTurn]
|
|
and a
|
|
jr z, .playersTurn2
|
|
ld de, wEnemyBattleStatus2
|
|
.playersTurn2
|
|
ld a, [de]
|
|
add a
|
|
jr nc, .notLeechSeeded
|
|
push hl
|
|
ldh a, [hWhoseTurn]
|
|
push af
|
|
xor $1
|
|
ldh [hWhoseTurn], a
|
|
xor a
|
|
ld [wAnimationType], a
|
|
ld a, ABSORB
|
|
call PlayMoveAnimation ; play leech seed animation (from opposing mon)
|
|
pop af
|
|
ldh [hWhoseTurn], a
|
|
pop hl
|
|
call HandlePoisonBurnLeechSeed_DecreaseOwnHP
|
|
call HandlePoisonBurnLeechSeed_IncreaseEnemyHP
|
|
push hl
|
|
ld hl, HurtByLeechSeedText
|
|
call PrintText
|
|
pop hl
|
|
.notLeechSeeded
|
|
ld a, [hli]
|
|
or [hl]
|
|
ret nz ; test if fainted
|
|
call DrawHUDsAndHPBars
|
|
ld c, 20
|
|
call DelayFrames
|
|
xor a
|
|
ret
|
|
|
|
HurtByPoisonText:
|
|
text_far _HurtByPoisonText
|
|
text_end
|
|
|
|
HurtByBurnText:
|
|
text_far _HurtByBurnText
|
|
text_end
|
|
|
|
HurtByLeechSeedText:
|
|
text_far _HurtByLeechSeedText
|
|
text_end
|
|
|
|
; decreases the mon's current HP by 1/16 of the Max HP (multiplied by number of toxic ticks if active)
|
|
; note that the toxic ticks are considered even if the damage is not poison (hence the Leech Seed glitch)
|
|
; hl: HP pointer
|
|
; bc (out): total damage
|
|
HandlePoisonBurnLeechSeed_DecreaseOwnHP:
|
|
push hl
|
|
push hl
|
|
ld bc, $e ; skip to max HP
|
|
add hl, bc
|
|
ld a, [hli] ; load max HP
|
|
ld [wHPBarMaxHP+1], a
|
|
ld b, a
|
|
ld a, [hl]
|
|
ld [wHPBarMaxHP], a
|
|
ld c, a
|
|
srl b
|
|
rr c
|
|
srl b
|
|
rr c
|
|
srl c
|
|
srl c ; c = max HP/16 (assumption: HP < 1024)
|
|
ld a, c
|
|
and a
|
|
jr nz, .nonZeroDamage
|
|
inc c ; damage is at least 1
|
|
.nonZeroDamage
|
|
ld hl, wPlayerBattleStatus3
|
|
ld de, wPlayerToxicCounter
|
|
ldh a, [hWhoseTurn]
|
|
and a
|
|
jr z, .playersTurn
|
|
ld hl, wEnemyBattleStatus3
|
|
ld de, wEnemyToxicCounter
|
|
.playersTurn
|
|
bit BADLY_POISONED, [hl]
|
|
jr z, .noToxic
|
|
ld a, [de] ; increment toxic counter
|
|
inc a
|
|
ld [de], a
|
|
ld hl, 0
|
|
.toxicTicksLoop
|
|
add hl, bc
|
|
dec a
|
|
jr nz, .toxicTicksLoop
|
|
ld b, h ; bc = damage * toxic counter
|
|
ld c, l
|
|
.noToxic
|
|
pop hl
|
|
inc hl
|
|
ld a, [hl] ; subtract total damage from current HP
|
|
ld [wHPBarOldHP], a
|
|
sub c
|
|
ld [hld], a
|
|
ld [wHPBarNewHP], a
|
|
ld a, [hl]
|
|
ld [wHPBarOldHP+1], a
|
|
sbc b
|
|
ld [hl], a
|
|
ld [wHPBarNewHP+1], a
|
|
jr nc, .noOverkill
|
|
xor a ; overkill: zero HP
|
|
ld [hli], a
|
|
ld [hl], a
|
|
ld [wHPBarNewHP], a
|
|
ld [wHPBarNewHP+1], a
|
|
.noOverkill
|
|
call UpdateCurMonHPBar
|
|
pop hl
|
|
ret
|
|
|
|
; adds bc to enemy HP
|
|
; bc isn't updated if HP subtracted was capped to prevent overkill
|
|
HandlePoisonBurnLeechSeed_IncreaseEnemyHP:
|
|
push hl
|
|
ld hl, wEnemyMonMaxHP
|
|
ldh a, [hWhoseTurn]
|
|
and a
|
|
jr z, .playersTurn
|
|
ld hl, wBattleMonMaxHP
|
|
.playersTurn
|
|
ld a, [hli]
|
|
ld [wHPBarMaxHP+1], a
|
|
ld a, [hl]
|
|
ld [wHPBarMaxHP], a
|
|
ld de, wBattleMonHP - wBattleMonMaxHP
|
|
add hl, de ; skip back from max hp to current hp
|
|
ld a, [hl]
|
|
ld [wHPBarOldHP], a ; add bc to current HP
|
|
add c
|
|
ld [hld], a
|
|
ld [wHPBarNewHP], a
|
|
ld a, [hl]
|
|
ld [wHPBarOldHP+1], a
|
|
adc b
|
|
ld [hli], a
|
|
ld [wHPBarNewHP+1], a
|
|
ld a, [wHPBarMaxHP]
|
|
ld c, a
|
|
ld a, [hld]
|
|
sub c
|
|
ld a, [wHPBarMaxHP+1]
|
|
ld b, a
|
|
ld a, [hl]
|
|
sbc b
|
|
jr c, .noOverfullHeal
|
|
ld a, b ; overfull heal, set HP to max HP
|
|
ld [hli], a
|
|
ld [wHPBarNewHP+1], a
|
|
ld a, c
|
|
ld [hl], a
|
|
ld [wHPBarNewHP], a
|
|
.noOverfullHeal
|
|
ldh a, [hWhoseTurn]
|
|
xor $1
|
|
ldh [hWhoseTurn], a
|
|
call UpdateCurMonHPBar
|
|
ldh a, [hWhoseTurn]
|
|
xor $1
|
|
ldh [hWhoseTurn], a
|
|
pop hl
|
|
ret
|
|
|
|
UpdateCurMonHPBar:
|
|
hlcoord 10, 9 ; tile pointer to player HP bar
|
|
ldh a, [hWhoseTurn]
|
|
and a
|
|
ld a, $1
|
|
jr z, .playersTurn
|
|
hlcoord 2, 2 ; tile pointer to enemy HP bar
|
|
xor a
|
|
.playersTurn
|
|
push bc
|
|
ld [wHPBarType], a
|
|
predef UpdateHPBar2
|
|
pop bc
|
|
ret
|
|
|
|
CheckNumAttacksLeft:
|
|
ld a, [wPlayerNumAttacksLeft]
|
|
and a
|
|
jr nz, .checkEnemy
|
|
; player has 0 attacks left
|
|
ld hl, wPlayerBattleStatus1
|
|
res USING_TRAPPING_MOVE, [hl] ; player not using multi-turn attack like wrap any more
|
|
.checkEnemy
|
|
ld a, [wEnemyNumAttacksLeft]
|
|
and a
|
|
ret nz
|
|
; enemy has 0 attacks left
|
|
ld hl, wEnemyBattleStatus1
|
|
res USING_TRAPPING_MOVE, [hl] ; enemy not using multi-turn attack like wrap any more
|
|
ret
|
|
|
|
HandleEnemyMonFainted:
|
|
xor a
|
|
ld [wInHandlePlayerMonFainted], a
|
|
call FaintEnemyPokemon
|
|
call AnyPartyAlive
|
|
ld a, d
|
|
and a
|
|
jp z, HandlePlayerBlackOut ; if no party mons are alive, the player blacks out
|
|
ld hl, wBattleMonHP
|
|
ld a, [hli]
|
|
or [hl] ; is battle mon HP zero?
|
|
call nz, DrawPlayerHUDAndHPBar ; if battle mon HP is not zero, draw player HD and HP bar
|
|
ld a, [wIsInBattle]
|
|
dec a
|
|
ret z ; return if it's a wild battle
|
|
call AnyEnemyPokemonAliveCheck
|
|
jp z, TrainerBattleVictory
|
|
ld hl, wBattleMonHP
|
|
ld a, [hli]
|
|
or [hl] ; does battle mon have 0 HP?
|
|
jr nz, .skipReplacingBattleMon ; if not, skip replacing battle mon
|
|
call DoUseNextMonDialogue ; this call is useless in a trainer battle. it shouldn't be here
|
|
ret c
|
|
call ChooseNextMon
|
|
.skipReplacingBattleMon
|
|
ld a, $1
|
|
ld [wActionResultOrTookBattleTurn], a
|
|
call ReplaceFaintedEnemyMon
|
|
jp z, EnemyRan
|
|
xor a
|
|
ld [wActionResultOrTookBattleTurn], a
|
|
jp MainInBattleLoop
|
|
|
|
FaintEnemyPokemon:
|
|
call ReadPlayerMonCurHPAndStatus
|
|
ld a, [wIsInBattle]
|
|
dec a
|
|
jr z, .wild
|
|
ld a, [wEnemyMonPartyPos]
|
|
ld hl, wEnemyMon1HP
|
|
ld bc, wEnemyMon2 - wEnemyMon1
|
|
call AddNTimes
|
|
xor a
|
|
ld [hli], a
|
|
ld [hl], a
|
|
.wild
|
|
ld hl, wPlayerBattleStatus1
|
|
res ATTACKING_MULTIPLE_TIMES, [hl]
|
|
; Bug. This only zeroes the high byte of the player's accumulated damage,
|
|
; setting the accumulated damage to itself mod 256 instead of 0 as was probably
|
|
; intended. That alone is problematic, but this mistake has another more severe
|
|
; effect. This function's counterpart for when the player mon faints,
|
|
; RemoveFaintedPlayerMon, zeroes both the high byte and the low byte. In a link
|
|
; battle, the other player's Game Boy will call that function in response to
|
|
; the enemy mon (the player mon from the other side's perspective) fainting,
|
|
; and the states of the two Game Boys will go out of sync unless the damage
|
|
; was congruent to 0 modulo 256.
|
|
xor a
|
|
ld [wPlayerBideAccumulatedDamage], a
|
|
ld hl, wEnemyStatsToDouble ; clear enemy statuses
|
|
ld [hli], a
|
|
ld [hli], a
|
|
ld [hli], a
|
|
ld [hli], a
|
|
ld [hl], a
|
|
ld [wEnemyDisabledMove], a
|
|
ld [wEnemyDisabledMoveNumber], a
|
|
ld [wEnemyMonMinimized], a
|
|
ld hl, wPlayerUsedMove
|
|
ld [hli], a
|
|
ld [hl], a
|
|
hlcoord 12, 5
|
|
decoord 12, 6
|
|
call SlideDownFaintedMonPic
|
|
hlcoord 0, 0
|
|
lb bc, 4, 11
|
|
call ClearScreenArea
|
|
ld a, [wIsInBattle]
|
|
dec a
|
|
jr z, .wild_win
|
|
xor a
|
|
ld [wFrequencyModifier], a
|
|
ld [wTempoModifier], a
|
|
ld a, SFX_FAINT_FALL
|
|
call PlaySoundWaitForCurrent
|
|
.sfxwait
|
|
ld a, [wChannelSoundIDs + CHAN5]
|
|
cp SFX_FAINT_FALL
|
|
jr z, .sfxwait
|
|
ld a, SFX_FAINT_THUD
|
|
call PlaySound
|
|
call WaitForSoundToFinish
|
|
jr .sfxplayed
|
|
.wild_win
|
|
call EndLowHealthAlarm
|
|
ld a, MUSIC_DEFEATED_WILD_MON
|
|
call PlayBattleVictoryMusic
|
|
.sfxplayed
|
|
; bug: win sfx is played for wild battles before checking for player mon HP
|
|
; this can lead to odd scenarios where both player and enemy faint, as the win sfx plays yet the player never won the battle
|
|
ld hl, wBattleMonHP
|
|
ld a, [hli]
|
|
or [hl]
|
|
jr nz, .playermonnotfaint
|
|
ld a, [wInHandlePlayerMonFainted]
|
|
and a ; was this called by HandlePlayerMonFainted?
|
|
jr nz, .playermonnotfaint ; if so, don't call RemoveFaintedPlayerMon twice
|
|
call RemoveFaintedPlayerMon
|
|
.playermonnotfaint
|
|
call AnyPartyAlive
|
|
ld a, d
|
|
and a
|
|
ret z
|
|
ld hl, EnemyMonFaintedText
|
|
call PrintText
|
|
call PrintEmptyString
|
|
call SaveScreenTilesToBuffer1
|
|
xor a
|
|
ld [wBattleResult], a
|
|
ld b, EXP_ALL
|
|
call IsItemInBag
|
|
push af
|
|
jr z, .giveExpToMonsThatFought ; if no exp all, then jump
|
|
|
|
; the player has exp all
|
|
; first, we halve the values that determine exp gain
|
|
; the enemy mon base stats are added to stat exp, so they are halved
|
|
; the base exp (which determines normal exp) is also halved
|
|
ld hl, wEnemyMonBaseStats
|
|
ld b, $7
|
|
.halveExpDataLoop
|
|
srl [hl]
|
|
inc hl
|
|
dec b
|
|
jr nz, .halveExpDataLoop
|
|
|
|
; give exp (divided evenly) to the mons that actually fought in battle against the enemy mon that has fainted
|
|
; if exp all is in the bag, this will be only be half of the stat exp and normal exp, due to the above loop
|
|
.giveExpToMonsThatFought
|
|
xor a
|
|
ld [wBoostExpByExpAll], a
|
|
callfar GainExperience
|
|
pop af
|
|
ret z ; return if no exp all
|
|
|
|
; the player has exp all
|
|
; now, set the gain exp flag for every party member
|
|
; half of the total stat exp and normal exp will divided evenly amongst every party member
|
|
ld a, $1
|
|
ld [wBoostExpByExpAll], a
|
|
ld a, [wPartyCount]
|
|
ld b, 0
|
|
.gainExpFlagsLoop
|
|
scf
|
|
rl b
|
|
dec a
|
|
jr nz, .gainExpFlagsLoop
|
|
ld a, b
|
|
ld [wPartyGainExpFlags], a
|
|
jpfar GainExperience
|
|
|
|
EnemyMonFaintedText:
|
|
text_far _EnemyMonFaintedText
|
|
text_end
|
|
|
|
EndLowHealthAlarm:
|
|
; This function is called when the player has the won the battle. It turns off
|
|
; the low health alarm and prevents it from reactivating until the next battle.
|
|
xor a
|
|
ld [wLowHealthAlarm], a ; turn off low health alarm
|
|
ld [wChannelSoundIDs + CHAN5], a
|
|
inc a
|
|
ld [wLowHealthAlarmDisabled], a ; prevent it from reactivating
|
|
ret
|
|
|
|
AnyEnemyPokemonAliveCheck:
|
|
ld a, [wEnemyPartyCount]
|
|
ld b, a
|
|
xor a
|
|
ld hl, wEnemyMon1HP
|
|
ld de, wEnemyMon2 - wEnemyMon1
|
|
.nextPokemon
|
|
or [hl]
|
|
inc hl
|
|
or [hl]
|
|
dec hl
|
|
add hl, de
|
|
dec b
|
|
jr nz, .nextPokemon
|
|
and a
|
|
ret
|
|
|
|
; stores whether enemy ran in Z flag
|
|
ReplaceFaintedEnemyMon:
|
|
ld hl, wEnemyHPBarColor
|
|
ld e, $30
|
|
call GetBattleHealthBarColor
|
|
callfar DrawEnemyPokeballs
|
|
ld a, [wLinkState]
|
|
cp LINK_STATE_BATTLING
|
|
jr nz, .notLinkBattle
|
|
; link battle
|
|
call LinkBattleExchangeData
|
|
ld a, [wSerialExchangeNybbleReceiveData]
|
|
cp LINKBATTLE_RUN
|
|
ret z
|
|
call LoadScreenTilesFromBuffer1
|
|
.notLinkBattle
|
|
call EnemySendOut
|
|
xor a
|
|
ld [wEnemyMoveNum], a
|
|
ld [wActionResultOrTookBattleTurn], a
|
|
ld [wAILayer2Encouragement], a
|
|
inc a ; reset Z flag
|
|
ret
|
|
|
|
TrainerBattleVictory:
|
|
call EndLowHealthAlarm
|
|
ld b, MUSIC_DEFEATED_GYM_LEADER
|
|
ld a, [wGymLeaderNo]
|
|
and a
|
|
jr nz, .gymleader
|
|
ld b, MUSIC_DEFEATED_TRAINER
|
|
.gymleader
|
|
ld a, [wTrainerClass]
|
|
cp RIVAL3 ; final battle against rival
|
|
jr nz, .notrival
|
|
ld b, MUSIC_DEFEATED_GYM_LEADER
|
|
ld hl, wFlags_D733
|
|
set 1, [hl]
|
|
.notrival
|
|
ld a, [wLinkState]
|
|
cp LINK_STATE_BATTLING
|
|
ld a, b
|
|
call nz, PlayBattleVictoryMusic
|
|
ld hl, TrainerDefeatedText
|
|
call PrintText
|
|
ld a, [wLinkState]
|
|
cp LINK_STATE_BATTLING
|
|
ret z
|
|
call ScrollTrainerPicAfterBattle
|
|
ld c, 40
|
|
call DelayFrames
|
|
call PrintEndBattleText
|
|
; win money
|
|
ld hl, MoneyForWinningText
|
|
call PrintText
|
|
ld de, wPlayerMoney + 2
|
|
ld hl, wAmountMoneyWon + 2
|
|
ld c, $3
|
|
predef_jump AddBCDPredef
|
|
|
|
MoneyForWinningText:
|
|
text_far _MoneyForWinningText
|
|
text_end
|
|
|
|
TrainerDefeatedText:
|
|
text_far _TrainerDefeatedText
|
|
text_end
|
|
|
|
PlayBattleVictoryMusic:
|
|
push af
|
|
ld a, SFX_STOP_ALL_MUSIC
|
|
ld [wNewSoundID], a
|
|
call PlaySoundWaitForCurrent
|
|
ld c, BANK(Music_DefeatedTrainer)
|
|
pop af
|
|
call PlayMusic
|
|
jp Delay3
|
|
|
|
HandlePlayerMonFainted:
|
|
ld a, 1
|
|
ld [wInHandlePlayerMonFainted], a
|
|
call RemoveFaintedPlayerMon
|
|
call AnyPartyAlive ; test if any more mons are alive
|
|
ld a, d
|
|
and a
|
|
jp z, HandlePlayerBlackOut
|
|
ld hl, wEnemyMonHP
|
|
ld a, [hli]
|
|
or [hl] ; is enemy mon's HP 0?
|
|
jr nz, .doUseNextMonDialogue ; if not, jump
|
|
; the enemy mon has 0 HP
|
|
call FaintEnemyPokemon
|
|
ld a, [wIsInBattle]
|
|
dec a
|
|
ret z ; if wild encounter, battle is over
|
|
call AnyEnemyPokemonAliveCheck
|
|
jp z, TrainerBattleVictory
|
|
.doUseNextMonDialogue
|
|
call DoUseNextMonDialogue
|
|
ret c ; return if the player ran from battle
|
|
call ChooseNextMon
|
|
jp nz, MainInBattleLoop ; if the enemy mon has more than 0 HP, go back to battle loop
|
|
; the enemy mon has 0 HP
|
|
ld a, $1
|
|
ld [wActionResultOrTookBattleTurn], a
|
|
call ReplaceFaintedEnemyMon
|
|
jp z, EnemyRan ; if enemy ran from battle rather than sending out another mon, jump
|
|
xor a
|
|
ld [wActionResultOrTookBattleTurn], a
|
|
jp MainInBattleLoop
|
|
|
|
; resets flags, slides mon's pic down, plays cry, and prints fainted message
|
|
RemoveFaintedPlayerMon:
|
|
ld a, [wPlayerMonNumber]
|
|
ld c, a
|
|
ld hl, wPartyGainExpFlags
|
|
ld b, FLAG_RESET
|
|
predef FlagActionPredef ; clear gain exp flag for fainted mon
|
|
ld hl, wEnemyBattleStatus1
|
|
res 2, [hl] ; reset "attacking multiple times" flag
|
|
ld a, [wLowHealthAlarm]
|
|
bit 7, a ; skip sound flag (red bar (?))
|
|
jr z, .skipWaitForSound
|
|
ld a, $ff
|
|
ld [wLowHealthAlarm], a ;disable low health alarm
|
|
call WaitForSoundToFinish
|
|
.skipWaitForSound
|
|
; a is 0, so this zeroes the enemy's accumulated damage.
|
|
ld hl, wEnemyBideAccumulatedDamage
|
|
ld [hli], a
|
|
ld [hl], a
|
|
ld [wBattleMonStatus], a
|
|
call ReadPlayerMonCurHPAndStatus
|
|
hlcoord 9, 7
|
|
lb bc, 5, 11
|
|
call ClearScreenArea
|
|
hlcoord 1, 10
|
|
decoord 1, 11
|
|
call SlideDownFaintedMonPic
|
|
ld a, $1
|
|
ld [wBattleResult], a
|
|
|
|
; When the player mon and enemy mon faint at the same time and the fact that the
|
|
; enemy mon has fainted is detected first (e.g. when the player mon knocks out
|
|
; the enemy mon using a move with recoil and faints due to the recoil), don't
|
|
; play the player mon's cry or show the "[player mon] fainted!" message.
|
|
ld a, [wInHandlePlayerMonFainted]
|
|
and a ; was this called by HandleEnemyMonFainted?
|
|
ret z ; if so, return
|
|
|
|
ld a, [wBattleMonSpecies]
|
|
call PlayCry
|
|
ld hl, PlayerMonFaintedText
|
|
jp PrintText
|
|
|
|
PlayerMonFaintedText:
|
|
text_far _PlayerMonFaintedText
|
|
text_end
|
|
|
|
; asks if you want to use next mon
|
|
; stores whether you ran in C flag
|
|
DoUseNextMonDialogue:
|
|
call PrintEmptyString
|
|
call SaveScreenTilesToBuffer1
|
|
ld a, [wIsInBattle]
|
|
and a
|
|
dec a
|
|
ret nz ; return if it's a trainer battle
|
|
ld hl, UseNextMonText
|
|
call PrintText
|
|
.displayYesNoBox
|
|
hlcoord 13, 9
|
|
lb bc, 10, 14
|
|
ld a, TWO_OPTION_MENU
|
|
ld [wTextBoxID], a
|
|
call DisplayTextBoxID
|
|
ld a, [wMenuExitMethod]
|
|
cp CHOSE_SECOND_ITEM ; did the player choose NO?
|
|
jr z, .tryRunning ; if the player chose NO, try running
|
|
and a ; reset carry
|
|
ret
|
|
.tryRunning
|
|
ld a, [wCurrentMenuItem]
|
|
and a
|
|
jr z, .displayYesNoBox ; xxx when does this happen?
|
|
ld hl, wPartyMon1Speed
|
|
ld de, wEnemyMonSpeed
|
|
jp TryRunningFromBattle
|
|
|
|
UseNextMonText:
|
|
text_far _UseNextMonText
|
|
text_end
|
|
|
|
; choose next player mon to send out
|
|
; stores whether enemy mon has no HP left in Z flag
|
|
ChooseNextMon:
|
|
ld a, BATTLE_PARTY_MENU
|
|
ld [wPartyMenuTypeOrMessageID], a
|
|
call DisplayPartyMenu
|
|
.checkIfMonChosen
|
|
jr nc, .monChosen
|
|
.goBackToPartyMenu
|
|
call GoBackToPartyMenu
|
|
jr .checkIfMonChosen
|
|
.monChosen
|
|
call HasMonFainted
|
|
jr z, .goBackToPartyMenu ; if mon fainted, you have to choose another
|
|
ld a, [wLinkState]
|
|
cp LINK_STATE_BATTLING
|
|
jr nz, .notLinkBattle
|
|
inc a ; 1
|
|
ld [wActionResultOrTookBattleTurn], a
|
|
call LinkBattleExchangeData
|
|
.notLinkBattle
|
|
xor a
|
|
ld [wActionResultOrTookBattleTurn], a
|
|
call ClearSprites
|
|
ld a, [wWhichPokemon]
|
|
ld [wPlayerMonNumber], a
|
|
ld c, a
|
|
ld hl, wPartyGainExpFlags
|
|
ld b, FLAG_SET
|
|
push bc
|
|
predef FlagActionPredef
|
|
pop bc
|
|
ld hl, wPartyFoughtCurrentEnemyFlags
|
|
predef FlagActionPredef
|
|
call LoadBattleMonFromParty
|
|
call GBPalWhiteOut
|
|
call LoadHudTilePatterns
|
|
call LoadScreenTilesFromBuffer1
|
|
call RunDefaultPaletteCommand
|
|
call GBPalNormal
|
|
call SendOutMon
|
|
ld hl, wEnemyMonHP
|
|
ld a, [hli]
|
|
or [hl]
|
|
ret
|
|
|
|
; called when player is out of usable mons.
|
|
; prints appropriate lose message, sets carry flag if player blacked out (special case for initial rival fight)
|
|
HandlePlayerBlackOut:
|
|
ld a, [wLinkState]
|
|
cp LINK_STATE_BATTLING
|
|
jr z, .notRival1Battle
|
|
ld a, [wCurOpponent]
|
|
cp OPP_RIVAL1
|
|
jr nz, .notRival1Battle
|
|
hlcoord 0, 0 ; rival 1 battle
|
|
lb bc, 8, 21
|
|
call ClearScreenArea
|
|
call ScrollTrainerPicAfterBattle
|
|
ld c, 40
|
|
call DelayFrames
|
|
ld hl, Rival1WinText
|
|
call PrintText
|
|
ld a, [wCurMap]
|
|
cp OAKS_LAB
|
|
ret z ; starter battle in oak's lab: don't black out
|
|
.notRival1Battle
|
|
ld b, SET_PAL_BATTLE_BLACK
|
|
call RunPaletteCommand
|
|
ld hl, PlayerBlackedOutText2
|
|
ld a, [wLinkState]
|
|
cp LINK_STATE_BATTLING
|
|
jr nz, .noLinkBattle
|
|
ld hl, LinkBattleLostText
|
|
.noLinkBattle
|
|
call PrintText
|
|
ld a, [wd732]
|
|
res 5, a
|
|
ld [wd732], a
|
|
call ClearScreen
|
|
scf
|
|
ret
|
|
|
|
Rival1WinText:
|
|
text_far _Rival1WinText
|
|
text_end
|
|
|
|
PlayerBlackedOutText2:
|
|
text_far _PlayerBlackedOutText2
|
|
text_end
|
|
|
|
LinkBattleLostText:
|
|
text_far _LinkBattleLostText
|
|
text_end
|
|
|
|
; slides pic of fainted mon downwards until it disappears
|
|
; bug: when this is called, [hAutoBGTransferEnabled] is non-zero, so there is screen tearing
|
|
SlideDownFaintedMonPic:
|
|
ld a, [wd730]
|
|
push af
|
|
set 6, a
|
|
ld [wd730], a
|
|
ld b, 7 ; number of times to slide
|
|
.slideStepLoop ; each iteration, the mon is slid down one row
|
|
push bc
|
|
push de
|
|
push hl
|
|
ld b, 6 ; number of rows
|
|
.rowLoop
|
|
push bc
|
|
push hl
|
|
push de
|
|
ld bc, $7
|
|
call CopyData
|
|
pop de
|
|
pop hl
|
|
ld bc, -SCREEN_WIDTH
|
|
add hl, bc
|
|
push hl
|
|
ld h, d
|
|
ld l, e
|
|
add hl, bc
|
|
ld d, h
|
|
ld e, l
|
|
pop hl
|
|
pop bc
|
|
dec b
|
|
jr nz, .rowLoop
|
|
ld bc, SCREEN_WIDTH
|
|
add hl, bc
|
|
ld de, SevenSpacesText
|
|
call PlaceString
|
|
ld c, 2
|
|
call DelayFrames
|
|
pop hl
|
|
pop de
|
|
pop bc
|
|
dec b
|
|
jr nz, .slideStepLoop
|
|
pop af
|
|
ld [wd730], a
|
|
ret
|
|
|
|
SevenSpacesText:
|
|
db " @"
|
|
|
|
; slides the player or enemy trainer off screen
|
|
; a is the number of tiles to slide it horizontally (always 9 for the player trainer or 8 for the enemy trainer)
|
|
; if a is 8, the slide is to the right, else it is to the left
|
|
; bug: when this is called, [hAutoBGTransferEnabled] is non-zero, so there is screen tearing
|
|
SlideTrainerPicOffScreen:
|
|
ldh [hSlideAmount], a
|
|
ld c, a
|
|
.slideStepLoop ; each iteration, the trainer pic is slid one tile left/right
|
|
push bc
|
|
push hl
|
|
ld b, 7 ; number of rows
|
|
.rowLoop
|
|
push hl
|
|
ldh a, [hSlideAmount]
|
|
ld c, a
|
|
.columnLoop
|
|
ldh a, [hSlideAmount]
|
|
cp 8
|
|
jr z, .slideRight
|
|
.slideLeft ; slide player sprite off screen
|
|
ld a, [hld]
|
|
ld [hli], a
|
|
inc hl
|
|
jr .nextColumn
|
|
.slideRight ; slide enemy trainer sprite off screen
|
|
ld a, [hli]
|
|
ld [hld], a
|
|
dec hl
|
|
.nextColumn
|
|
dec c
|
|
jr nz, .columnLoop
|
|
pop hl
|
|
ld de, 20
|
|
add hl, de
|
|
dec b
|
|
jr nz, .rowLoop
|
|
ld c, 2
|
|
call DelayFrames
|
|
pop hl
|
|
pop bc
|
|
dec c
|
|
jr nz, .slideStepLoop
|
|
ret
|
|
|
|
; send out a trainer's mon
|
|
EnemySendOut:
|
|
ld hl, wPartyGainExpFlags
|
|
xor a
|
|
ld [hl], a
|
|
ld a, [wPlayerMonNumber]
|
|
ld c, a
|
|
ld b, FLAG_SET
|
|
push bc
|
|
predef FlagActionPredef
|
|
ld hl, wPartyFoughtCurrentEnemyFlags
|
|
xor a
|
|
ld [hl], a
|
|
pop bc
|
|
predef FlagActionPredef
|
|
|
|
; don't change wPartyGainExpFlags or wPartyFoughtCurrentEnemyFlags
|
|
EnemySendOutFirstMon:
|
|
xor a
|
|
ld hl, wEnemyStatsToDouble ; clear enemy statuses
|
|
ld [hli], a
|
|
ld [hli], a
|
|
ld [hli], a
|
|
ld [hli], a
|
|
ld [hl], a
|
|
ld [wEnemyDisabledMove], a
|
|
ld [wEnemyDisabledMoveNumber], a
|
|
ld [wEnemyMonMinimized], a
|
|
ld hl, wPlayerUsedMove
|
|
ld [hli], a
|
|
ld [hl], a
|
|
dec a
|
|
ld [wAICount], a
|
|
ld hl, wPlayerBattleStatus1
|
|
res 5, [hl]
|
|
hlcoord 18, 0
|
|
ld a, 8
|
|
call SlideTrainerPicOffScreen
|
|
call PrintEmptyString
|
|
call SaveScreenTilesToBuffer1
|
|
ld a, [wLinkState]
|
|
cp LINK_STATE_BATTLING
|
|
jr nz, .next
|
|
ld a, [wSerialExchangeNybbleReceiveData]
|
|
sub 4
|
|
ld [wWhichPokemon], a
|
|
jr .next3
|
|
.next
|
|
ld b, $ff
|
|
.next2
|
|
inc b
|
|
ld a, [wEnemyMonPartyPos]
|
|
cp b
|
|
jr z, .next2
|
|
ld hl, wEnemyMon1
|
|
ld a, b
|
|
ld [wWhichPokemon], a
|
|
push bc
|
|
ld bc, wEnemyMon2 - wEnemyMon1
|
|
call AddNTimes
|
|
pop bc
|
|
inc hl
|
|
ld a, [hli]
|
|
ld c, a
|
|
ld a, [hl]
|
|
or c
|
|
jr z, .next2
|
|
.next3
|
|
ld a, [wWhichPokemon]
|
|
ld hl, wEnemyMon1Level
|
|
ld bc, wEnemyMon2 - wEnemyMon1
|
|
call AddNTimes
|
|
ld a, [hl]
|
|
ld [wCurEnemyLVL], a
|
|
ld a, [wWhichPokemon]
|
|
inc a
|
|
ld hl, wEnemyPartyCount
|
|
ld c, a
|
|
ld b, 0
|
|
add hl, bc
|
|
ld a, [hl]
|
|
ld [wEnemyMonSpecies2], a
|
|
ld [wcf91], a
|
|
call LoadEnemyMonData
|
|
ld hl, wEnemyMonHP
|
|
ld a, [hli]
|
|
ld [wLastSwitchInEnemyMonHP], a
|
|
ld a, [hl]
|
|
ld [wLastSwitchInEnemyMonHP + 1], a
|
|
ld a, 1
|
|
ld [wCurrentMenuItem], a
|
|
ld a, [wFirstMonsNotOutYet]
|
|
dec a
|
|
jr z, .next4
|
|
ld a, [wPartyCount]
|
|
dec a
|
|
jr z, .next4
|
|
ld a, [wLinkState]
|
|
cp LINK_STATE_BATTLING
|
|
jr z, .next4
|
|
ld a, [wOptions]
|
|
bit BIT_BATTLE_SHIFT, a
|
|
jr nz, .next4
|
|
ld hl, TrainerAboutToUseText
|
|
call PrintText
|
|
hlcoord 0, 7
|
|
lb bc, 8, 1
|
|
ld a, TWO_OPTION_MENU
|
|
ld [wTextBoxID], a
|
|
call DisplayTextBoxID
|
|
ld a, [wCurrentMenuItem]
|
|
and a
|
|
jr nz, .next4
|
|
ld a, BATTLE_PARTY_MENU
|
|
ld [wPartyMenuTypeOrMessageID], a
|
|
call DisplayPartyMenu
|
|
.next9
|
|
ld a, 1
|
|
ld [wCurrentMenuItem], a
|
|
jr c, .next7
|
|
ld hl, wPlayerMonNumber
|
|
ld a, [wWhichPokemon]
|
|
cp [hl]
|
|
jr nz, .next6
|
|
ld hl, AlreadyOutText
|
|
call PrintText
|
|
.next8
|
|
call GoBackToPartyMenu
|
|
jr .next9
|
|
.next6
|
|
call HasMonFainted
|
|
jr z, .next8
|
|
xor a
|
|
ld [wCurrentMenuItem], a
|
|
.next7
|
|
call GBPalWhiteOut
|
|
call LoadHudTilePatterns
|
|
call LoadScreenTilesFromBuffer1
|
|
.next4
|
|
call ClearSprites
|
|
hlcoord 0, 0
|
|
lb bc, 4, 11
|
|
call ClearScreenArea
|
|
ld b, SET_PAL_BATTLE
|
|
call RunPaletteCommand
|
|
call GBPalNormal
|
|
ld hl, TrainerSentOutText
|
|
call PrintText
|
|
ld a, [wEnemyMonSpecies2]
|
|
ld [wcf91], a
|
|
ld [wd0b5], a
|
|
call GetMonHeader
|
|
ld de, vFrontPic
|
|
call LoadMonFrontSprite
|
|
ld a, -$31
|
|
ldh [hStartTileID], a
|
|
hlcoord 15, 6
|
|
predef AnimateSendingOutMon
|
|
ld a, [wEnemyMonSpecies2]
|
|
call PlayCry
|
|
call DrawEnemyHUDAndHPBar
|
|
ld a, [wCurrentMenuItem]
|
|
and a
|
|
ret nz
|
|
xor a
|
|
ld [wPartyGainExpFlags], a
|
|
ld [wPartyFoughtCurrentEnemyFlags], a
|
|
call SaveScreenTilesToBuffer1
|
|
jp SwitchPlayerMon
|
|
|
|
TrainerAboutToUseText:
|
|
text_far _TrainerAboutToUseText
|
|
text_end
|
|
|
|
TrainerSentOutText:
|
|
text_far _TrainerSentOutText
|
|
text_end
|
|
|
|
; tests if the player has any pokemon that are not fainted
|
|
; sets d = 0 if all fainted, d != 0 if some mons are still alive
|
|
AnyPartyAlive::
|
|
ld a, [wPartyCount]
|
|
ld e, a
|
|
xor a
|
|
ld hl, wPartyMon1HP
|
|
ld bc, wPartyMon2 - wPartyMon1 - 1
|
|
.partyMonsLoop
|
|
or [hl]
|
|
inc hl
|
|
or [hl]
|
|
add hl, bc
|
|
dec e
|
|
jr nz, .partyMonsLoop
|
|
ld d, a
|
|
ret
|
|
|
|
; tests if player mon has fainted
|
|
; stores whether mon has fainted in Z flag
|
|
HasMonFainted:
|
|
ld a, [wWhichPokemon]
|
|
ld hl, wPartyMon1HP
|
|
ld bc, wPartyMon2 - wPartyMon1
|
|
call AddNTimes
|
|
ld a, [hli]
|
|
or [hl]
|
|
ret nz
|
|
ld a, [wFirstMonsNotOutYet]
|
|
and a
|
|
jr nz, .done
|
|
ld hl, NoWillText
|
|
call PrintText
|
|
.done
|
|
xor a
|
|
ret
|
|
|
|
NoWillText:
|
|
text_far _NoWillText
|
|
text_end
|
|
|
|
; try to run from battle (hl = player speed, de = enemy speed)
|
|
; stores whether the attempt was successful in carry flag
|
|
TryRunningFromBattle:
|
|
call IsGhostBattle
|
|
jp z, .canEscape ; jump if it's a ghost battle
|
|
ld a, [wBattleType]
|
|
cp BATTLE_TYPE_SAFARI
|
|
jp z, .canEscape ; jump if it's a safari battle
|
|
ld a, [wLinkState]
|
|
cp LINK_STATE_BATTLING
|
|
jp z, .canEscape
|
|
ld a, [wIsInBattle]
|
|
dec a
|
|
jr nz, .trainerBattle ; jump if it's a trainer battle
|
|
ld a, [wNumRunAttempts]
|
|
inc a
|
|
ld [wNumRunAttempts], a
|
|
ld a, [hli]
|
|
ldh [hMultiplicand + 1], a
|
|
ld a, [hl]
|
|
ldh [hMultiplicand + 2], a
|
|
ld a, [de]
|
|
ldh [hEnemySpeed], a
|
|
inc de
|
|
ld a, [de]
|
|
ldh [hEnemySpeed + 1], a
|
|
call LoadScreenTilesFromBuffer1
|
|
ld de, hMultiplicand + 1
|
|
ld hl, hEnemySpeed
|
|
ld c, 2
|
|
call StringCmp
|
|
jr nc, .canEscape ; jump if player speed greater than enemy speed
|
|
xor a
|
|
ldh [hMultiplicand], a
|
|
ld a, 32
|
|
ldh [hMultiplier], a
|
|
call Multiply ; multiply player speed by 32
|
|
ldh a, [hProduct + 2]
|
|
ldh [hDividend], a
|
|
ldh a, [hProduct + 3]
|
|
ldh [hDividend + 1], a
|
|
ldh a, [hEnemySpeed]
|
|
ld b, a
|
|
ldh a, [hEnemySpeed + 1]
|
|
; divide enemy speed by 4
|
|
srl b
|
|
rr a
|
|
srl b
|
|
rr a
|
|
and a
|
|
jr z, .canEscape ; jump if enemy speed divided by 4, mod 256 is 0
|
|
ldh [hDivisor], a ; ((enemy speed / 4) % 256)
|
|
ld b, $2
|
|
call Divide ; divide (player speed * 32) by ((enemy speed / 4) % 256)
|
|
ldh a, [hQuotient + 2]
|
|
and a ; is the quotient greater than 256?
|
|
jr nz, .canEscape ; if so, the player can escape
|
|
ld a, [wNumRunAttempts]
|
|
ld c, a
|
|
; add 30 to the quotient for each run attempt
|
|
.loop
|
|
dec c
|
|
jr z, .compareWithRandomValue
|
|
ld b, 30
|
|
ldh a, [hQuotient + 3]
|
|
add b
|
|
ldh [hQuotient + 3], a
|
|
jr c, .canEscape
|
|
jr .loop
|
|
.compareWithRandomValue
|
|
call BattleRandom
|
|
ld b, a
|
|
ldh a, [hQuotient + 3]
|
|
cp b
|
|
jr nc, .canEscape ; if the random value was less than or equal to the quotient
|
|
; plus 30 times the number of attempts, the player can escape
|
|
; can't escape
|
|
ld a, $1
|
|
ld [wActionResultOrTookBattleTurn], a ; you lose your turn when you can't escape
|
|
ld hl, CantEscapeText
|
|
jr .printCantEscapeOrNoRunningText
|
|
.trainerBattle
|
|
ld hl, NoRunningText
|
|
.printCantEscapeOrNoRunningText
|
|
call PrintText
|
|
ld a, 1
|
|
ld [wForcePlayerToChooseMon], a
|
|
call SaveScreenTilesToBuffer1
|
|
and a ; reset carry
|
|
ret
|
|
.canEscape
|
|
ld a, [wLinkState]
|
|
cp LINK_STATE_BATTLING
|
|
ld a, $2
|
|
jr nz, .playSound
|
|
; link battle
|
|
call SaveScreenTilesToBuffer1
|
|
xor a
|
|
ld [wActionResultOrTookBattleTurn], a
|
|
ld a, LINKBATTLE_RUN
|
|
ld [wPlayerMoveListIndex], a
|
|
call LinkBattleExchangeData
|
|
call LoadScreenTilesFromBuffer1
|
|
ld a, [wSerialExchangeNybbleReceiveData]
|
|
cp LINKBATTLE_RUN
|
|
ld a, $2
|
|
jr z, .playSound
|
|
dec a
|
|
.playSound
|
|
ld [wBattleResult], a
|
|
ld a, SFX_RUN
|
|
call PlaySoundWaitForCurrent
|
|
ld hl, GotAwayText
|
|
call PrintText
|
|
call WaitForSoundToFinish
|
|
call SaveScreenTilesToBuffer1
|
|
scf ; set carry
|
|
ret
|
|
|
|
CantEscapeText:
|
|
text_far _CantEscapeText
|
|
text_end
|
|
|
|
NoRunningText:
|
|
text_far _NoRunningText
|
|
text_end
|
|
|
|
GotAwayText:
|
|
text_far _GotAwayText
|
|
text_end
|
|
|
|
; copies from party data to battle mon data when sending out a new player mon
|
|
LoadBattleMonFromParty:
|
|
ld a, [wWhichPokemon]
|
|
ld bc, wPartyMon2 - wPartyMon1
|
|
ld hl, wPartyMon1Species
|
|
call AddNTimes
|
|
ld de, wBattleMonSpecies
|
|
ld bc, wBattleMonDVs - wBattleMonSpecies
|
|
call CopyData
|
|
ld bc, wPartyMon1DVs - wPartyMon1OTID
|
|
add hl, bc
|
|
ld de, wBattleMonDVs
|
|
ld bc, wPartyMon1PP - wPartyMon1DVs
|
|
call CopyData
|
|
ld de, wBattleMonPP
|
|
ld bc, NUM_MOVES
|
|
call CopyData
|
|
ld de, wBattleMonLevel
|
|
ld bc, wBattleMonPP - wBattleMonLevel
|
|
call CopyData
|
|
ld a, [wBattleMonSpecies2]
|
|
ld [wd0b5], a
|
|
call GetMonHeader
|
|
ld hl, wPartyMonNicks
|
|
ld a, [wPlayerMonNumber]
|
|
call SkipFixedLengthTextEntries
|
|
ld de, wBattleMonNick
|
|
ld bc, NAME_LENGTH
|
|
call CopyData
|
|
ld hl, wBattleMonLevel
|
|
ld de, wPlayerMonUnmodifiedLevel ; block of memory used for unmodified stats
|
|
ld bc, 1 + NUM_STATS * 2
|
|
call CopyData
|
|
call ApplyBurnAndParalysisPenaltiesToPlayer
|
|
call ApplyBadgeStatBoosts
|
|
ld a, $7 ; default stat modifier
|
|
ld b, NUM_STAT_MODS
|
|
ld hl, wPlayerMonAttackMod
|
|
.statModLoop
|
|
ld [hli], a
|
|
dec b
|
|
jr nz, .statModLoop
|
|
ret
|
|
|
|
; copies from enemy party data to current enemy mon data when sending out a new enemy mon
|
|
LoadEnemyMonFromParty:
|
|
ld a, [wWhichPokemon]
|
|
ld bc, wEnemyMon2 - wEnemyMon1
|
|
ld hl, wEnemyMons
|
|
call AddNTimes
|
|
ld de, wEnemyMonSpecies
|
|
ld bc, wEnemyMonDVs - wEnemyMonSpecies
|
|
call CopyData
|
|
ld bc, wEnemyMon1DVs - wEnemyMon1OTID
|
|
add hl, bc
|
|
ld de, wEnemyMonDVs
|
|
ld bc, wEnemyMon1PP - wEnemyMon1DVs
|
|
call CopyData
|
|
ld de, wEnemyMonPP
|
|
ld bc, NUM_MOVES
|
|
call CopyData
|
|
ld de, wEnemyMonLevel
|
|
ld bc, wEnemyMonPP - wEnemyMonLevel
|
|
call CopyData
|
|
ld a, [wEnemyMonSpecies]
|
|
ld [wd0b5], a
|
|
call GetMonHeader
|
|
ld hl, wEnemyMonNicks
|
|
ld a, [wWhichPokemon]
|
|
call SkipFixedLengthTextEntries
|
|
ld de, wEnemyMonNick
|
|
ld bc, NAME_LENGTH
|
|
call CopyData
|
|
ld hl, wEnemyMonLevel
|
|
ld de, wEnemyMonUnmodifiedLevel ; block of memory used for unmodified stats
|
|
ld bc, 1 + NUM_STATS * 2
|
|
call CopyData
|
|
call ApplyBurnAndParalysisPenaltiesToEnemy
|
|
ld hl, wMonHBaseStats
|
|
ld de, wEnemyMonBaseStats
|
|
ld b, NUM_STATS
|
|
.copyBaseStatsLoop
|
|
ld a, [hli]
|
|
ld [de], a
|
|
inc de
|
|
dec b
|
|
jr nz, .copyBaseStatsLoop
|
|
ld a, $7 ; default stat modifier
|
|
ld b, NUM_STAT_MODS
|
|
ld hl, wEnemyMonStatMods
|
|
.statModLoop
|
|
ld [hli], a
|
|
dec b
|
|
jr nz, .statModLoop
|
|
ld a, [wWhichPokemon]
|
|
ld [wEnemyMonPartyPos], a
|
|
ret
|
|
|
|
SendOutMon:
|
|
callfar PrintSendOutMonMessage
|
|
ld hl, wEnemyMonHP
|
|
ld a, [hli]
|
|
or [hl] ; is enemy mon HP zero?
|
|
jp z, .skipDrawingEnemyHUDAndHPBar ; if HP is zero, skip drawing the HUD and HP bar
|
|
call DrawEnemyHUDAndHPBar
|
|
.skipDrawingEnemyHUDAndHPBar
|
|
call DrawPlayerHUDAndHPBar
|
|
predef LoadMonBackPic
|
|
xor a
|
|
ldh [hStartTileID], a
|
|
ld hl, wBattleAndStartSavedMenuItem
|
|
ld [hli], a
|
|
ld [hl], a
|
|
ld [wBoostExpByExpAll], a
|
|
ld [wDamageMultipliers], a
|
|
ld [wPlayerMoveNum], a
|
|
ld hl, wPlayerUsedMove
|
|
ld [hli], a
|
|
ld [hl], a
|
|
ld hl, wPlayerStatsToDouble
|
|
ld [hli], a
|
|
ld [hli], a
|
|
ld [hli], a
|
|
ld [hli], a
|
|
ld [hl], a
|
|
ld [wPlayerDisabledMove], a
|
|
ld [wPlayerDisabledMoveNumber], a
|
|
ld [wPlayerMonMinimized], a
|
|
ld b, SET_PAL_BATTLE
|
|
call RunPaletteCommand
|
|
ld hl, wEnemyBattleStatus1
|
|
res USING_TRAPPING_MOVE, [hl]
|
|
ld a, $1
|
|
ldh [hWhoseTurn], a
|
|
ld a, POOF_ANIM
|
|
call PlayMoveAnimation
|
|
hlcoord 4, 11
|
|
predef AnimateSendingOutMon
|
|
ld a, [wcf91]
|
|
call PlayCry
|
|
call PrintEmptyString
|
|
jp SaveScreenTilesToBuffer1
|
|
|
|
; show 2 stages of the player mon getting smaller before disappearing
|
|
AnimateRetreatingPlayerMon:
|
|
hlcoord 1, 5
|
|
lb bc, 7, 7
|
|
call ClearScreenArea
|
|
hlcoord 3, 7
|
|
lb bc, 5, 5
|
|
xor a
|
|
ld [wDownscaledMonSize], a
|
|
ldh [hBaseTileID], a
|
|
predef CopyDownscaledMonTiles
|
|
ld c, 4
|
|
call DelayFrames
|
|
call .clearScreenArea
|
|
hlcoord 4, 9
|
|
lb bc, 3, 3
|
|
ld a, 1
|
|
ld [wDownscaledMonSize], a
|
|
xor a
|
|
ldh [hBaseTileID], a
|
|
predef CopyDownscaledMonTiles
|
|
call Delay3
|
|
call .clearScreenArea
|
|
ld a, $4c
|
|
ldcoord_a 5, 11
|
|
.clearScreenArea
|
|
hlcoord 1, 5
|
|
lb bc, 7, 7
|
|
jp ClearScreenArea
|
|
|
|
; reads player's current mon's HP into wBattleMonHP
|
|
ReadPlayerMonCurHPAndStatus:
|
|
ld a, [wPlayerMonNumber]
|
|
ld hl, wPartyMon1HP
|
|
ld bc, wPartyMon2 - wPartyMon1
|
|
call AddNTimes
|
|
ld d, h
|
|
ld e, l
|
|
ld hl, wBattleMonHP
|
|
ld bc, $4 ; 2 bytes HP, 1 byte unknown (unused?), 1 byte status
|
|
jp CopyData
|
|
|
|
DrawHUDsAndHPBars:
|
|
call DrawPlayerHUDAndHPBar
|
|
jp DrawEnemyHUDAndHPBar
|
|
|
|
DrawPlayerHUDAndHPBar:
|
|
xor a
|
|
ldh [hAutoBGTransferEnabled], a
|
|
hlcoord 9, 7
|
|
lb bc, 5, 11
|
|
call ClearScreenArea
|
|
callfar PlacePlayerHUDTiles
|
|
hlcoord 18, 9
|
|
ld [hl], $73
|
|
ld de, wBattleMonNick
|
|
hlcoord 10, 7
|
|
call CenterMonName
|
|
call PlaceString
|
|
ld hl, wBattleMonSpecies
|
|
ld de, wLoadedMon
|
|
ld bc, wBattleMonDVs - wBattleMonSpecies
|
|
call CopyData
|
|
ld hl, wBattleMonLevel
|
|
ld de, wLoadedMonLevel
|
|
ld bc, wBattleMonPP - wBattleMonLevel
|
|
call CopyData
|
|
hlcoord 14, 8
|
|
push hl
|
|
inc hl
|
|
ld de, wLoadedMonStatus
|
|
call PrintStatusConditionNotFainted
|
|
pop hl
|
|
jr nz, .doNotPrintLevel
|
|
call PrintLevel
|
|
.doNotPrintLevel
|
|
ld a, [wLoadedMonSpecies]
|
|
ld [wcf91], a
|
|
hlcoord 10, 9
|
|
predef DrawHP
|
|
ld a, $1
|
|
ldh [hAutoBGTransferEnabled], a
|
|
ld hl, wPlayerHPBarColor
|
|
call GetBattleHealthBarColor
|
|
ld hl, wBattleMonHP
|
|
ld a, [hli]
|
|
or [hl]
|
|
jr z, .fainted
|
|
ld a, [wLowHealthAlarmDisabled]
|
|
and a ; has the alarm been disabled because the player has already won?
|
|
ret nz ; if so, return
|
|
ld a, [wPlayerHPBarColor]
|
|
cp HP_BAR_RED
|
|
jr z, .setLowHealthAlarm
|
|
.fainted
|
|
ld hl, wLowHealthAlarm
|
|
bit 7, [hl] ;low health alarm enabled?
|
|
ld [hl], $0
|
|
ret z
|
|
xor a
|
|
ld [wChannelSoundIDs + CHAN5], a
|
|
ret
|
|
.setLowHealthAlarm
|
|
ld hl, wLowHealthAlarm
|
|
set 7, [hl] ;enable low health alarm
|
|
ret
|
|
|
|
DrawEnemyHUDAndHPBar:
|
|
xor a
|
|
ldh [hAutoBGTransferEnabled], a
|
|
hlcoord 0, 0
|
|
lb bc, 4, 12
|
|
call ClearScreenArea
|
|
callfar PlaceEnemyHUDTiles
|
|
ld de, wEnemyMonNick
|
|
hlcoord 1, 0
|
|
call CenterMonName
|
|
call PlaceString
|
|
hlcoord 4, 1
|
|
push hl
|
|
inc hl
|
|
ld de, wEnemyMonStatus
|
|
call PrintStatusConditionNotFainted
|
|
pop hl
|
|
jr nz, .skipPrintLevel ; if the mon has a status condition, skip printing the level
|
|
ld a, [wEnemyMonLevel]
|
|
ld [wLoadedMonLevel], a
|
|
call PrintLevel
|
|
.skipPrintLevel
|
|
ld hl, wEnemyMonHP
|
|
ld a, [hli]
|
|
ldh [hMultiplicand + 1], a
|
|
ld a, [hld]
|
|
ldh [hMultiplicand + 2], a
|
|
or [hl] ; is current HP zero?
|
|
jr nz, .hpNonzero
|
|
; current HP is 0
|
|
; set variables for DrawHPBar
|
|
ld c, a
|
|
ld e, a
|
|
ld d, $6
|
|
jp .drawHPBar
|
|
.hpNonzero
|
|
xor a
|
|
ldh [hMultiplicand], a
|
|
ld a, 48
|
|
ldh [hMultiplier], a
|
|
call Multiply ; multiply current HP by 48
|
|
ld hl, wEnemyMonMaxHP
|
|
ld a, [hli]
|
|
ld b, a
|
|
ld a, [hl]
|
|
ldh [hDivisor], a
|
|
ld a, b
|
|
and a ; is max HP > 255?
|
|
jr z, .doDivide
|
|
; if max HP > 255, scale both (current HP * 48) and max HP by dividing by 4 so that max HP fits in one byte
|
|
; (it needs to be one byte so it can be used as the divisor for the Divide function)
|
|
ldh a, [hDivisor]
|
|
srl b
|
|
rr a
|
|
srl b
|
|
rr a
|
|
ldh [hDivisor], a
|
|
ldh a, [hProduct + 2]
|
|
ld b, a
|
|
srl b
|
|
ldh a, [hProduct + 3]
|
|
rr a
|
|
srl b
|
|
rr a
|
|
ldh [hProduct + 3], a
|
|
ld a, b
|
|
ldh [hProduct + 2], a
|
|
.doDivide
|
|
ldh a, [hProduct + 2]
|
|
ldh [hDividend], a
|
|
ldh a, [hProduct + 3]
|
|
ldh [hDividend + 1], a
|
|
ld a, $2
|
|
ld b, a
|
|
call Divide ; divide (current HP * 48) by max HP
|
|
ldh a, [hQuotient + 3]
|
|
; set variables for DrawHPBar
|
|
ld e, a
|
|
ld a, $6
|
|
ld d, a
|
|
ld c, a
|
|
.drawHPBar
|
|
xor a
|
|
ld [wHPBarType], a
|
|
hlcoord 2, 2
|
|
call DrawHPBar
|
|
ld a, $1
|
|
ldh [hAutoBGTransferEnabled], a
|
|
ld hl, wEnemyHPBarColor
|
|
|
|
GetBattleHealthBarColor:
|
|
ld b, [hl]
|
|
call GetHealthBarColor
|
|
ld a, [hl]
|
|
cp b
|
|
ret z
|
|
ld b, SET_PAL_BATTLE
|
|
jp RunPaletteCommand
|
|
|
|
; center's mon's name on the battle screen
|
|
; if the name is 1 or 2 letters long, it is printed 2 spaces more to the right than usual
|
|
; (i.e. for names longer than 4 letters)
|
|
; if the name is 3 or 4 letters long, it is printed 1 space more to the right than usual
|
|
; (i.e. for names longer than 4 letters)
|
|
CenterMonName:
|
|
push de
|
|
inc hl
|
|
inc hl
|
|
ld b, $2
|
|
.loop
|
|
inc de
|
|
ld a, [de]
|
|
cp "@"
|
|
jr z, .done
|
|
inc de
|
|
ld a, [de]
|
|
cp "@"
|
|
jr z, .done
|
|
dec hl
|
|
dec b
|
|
jr nz, .loop
|
|
.done
|
|
pop de
|
|
ret
|
|
|
|
DisplayBattleMenu::
|
|
call LoadScreenTilesFromBuffer1 ; restore saved screen
|
|
ld a, [wBattleType]
|
|
and a
|
|
jr nz, .nonstandardbattle
|
|
call DrawHUDsAndHPBars
|
|
call PrintEmptyString
|
|
call SaveScreenTilesToBuffer1
|
|
.nonstandardbattle
|
|
ld a, [wBattleType]
|
|
cp BATTLE_TYPE_SAFARI
|
|
ld a, BATTLE_MENU_TEMPLATE
|
|
jr nz, .menuselected
|
|
ld a, SAFARI_BATTLE_MENU_TEMPLATE
|
|
.menuselected
|
|
ld [wTextBoxID], a
|
|
call DisplayTextBoxID
|
|
; handle menu input if it's not the old man tutorial
|
|
ld a, [wBattleType]
|
|
dec a
|
|
jp nz, .handleBattleMenuInput
|
|
; the following happens for the old man tutorial
|
|
; Temporarily save the player name in wLinkEnemyTrainerName.
|
|
; Since wLinkEnemyTrainerName == wGrassRate, this affects wild encounters.
|
|
; The wGrassRate byte and following wGrassMons buffer are supposed
|
|
; to get overwritten when entering a map with wild Pokémon,
|
|
; but an oversight prevents this in Cinnabar and Route 21,
|
|
; so the infamous MissingNo. glitch can show up.
|
|
ld hl, wPlayerName
|
|
ld de, wLinkEnemyTrainerName
|
|
ld bc, NAME_LENGTH
|
|
call CopyData
|
|
ld hl, .oldManName
|
|
ld de, wPlayerName
|
|
ld bc, NAME_LENGTH
|
|
call CopyData
|
|
; the following simulates the keystrokes by drawing menus on screen
|
|
hlcoord 9, 14
|
|
ld [hl], "▶"
|
|
ld c, 80
|
|
call DelayFrames
|
|
ld [hl], " "
|
|
hlcoord 9, 16
|
|
ld [hl], "▶"
|
|
ld c, 50
|
|
call DelayFrames
|
|
ld [hl], "▷"
|
|
ld a, $2 ; select the "ITEM" menu
|
|
jp .upperLeftMenuItemWasNotSelected
|
|
.oldManName
|
|
db "OLD MAN@"
|
|
.handleBattleMenuInput
|
|
ld a, [wBattleAndStartSavedMenuItem]
|
|
ld [wCurrentMenuItem], a
|
|
ld [wLastMenuItem], a
|
|
sub 2 ; check if the cursor is in the left column
|
|
jr c, .leftColumn
|
|
; cursor is in the right column
|
|
ld [wCurrentMenuItem], a
|
|
ld [wLastMenuItem], a
|
|
jr .rightColumn
|
|
.leftColumn ; put cursor in left column of menu
|
|
ld a, [wBattleType]
|
|
cp BATTLE_TYPE_SAFARI
|
|
ld a, " "
|
|
jr z, .safariLeftColumn
|
|
; put cursor in left column for normal battle menu (i.e. when it's not a Safari battle)
|
|
ldcoord_a 15, 14 ; clear upper cursor position in right column
|
|
ldcoord_a 15, 16 ; clear lower cursor position in right column
|
|
ld b, $9 ; top menu item X
|
|
jr .leftColumn_WaitForInput
|
|
.safariLeftColumn
|
|
ldcoord_a 13, 14
|
|
ldcoord_a 13, 16
|
|
hlcoord 7, 14
|
|
ld de, wNumSafariBalls
|
|
lb bc, 1, 2
|
|
call PrintNumber
|
|
ld b, $1 ; top menu item X
|
|
.leftColumn_WaitForInput
|
|
ld hl, wTopMenuItemY
|
|
ld a, $e
|
|
ld [hli], a ; wTopMenuItemY
|
|
ld a, b
|
|
ld [hli], a ; wTopMenuItemX
|
|
inc hl
|
|
inc hl
|
|
ld a, $1
|
|
ld [hli], a ; wMaxMenuItem
|
|
ld [hl], D_RIGHT | A_BUTTON ; wMenuWatchedKeys
|
|
call HandleMenuInput
|
|
bit BIT_D_RIGHT, a
|
|
jr nz, .rightColumn
|
|
jr .AButtonPressed ; the A button was pressed
|
|
.rightColumn ; put cursor in right column of menu
|
|
ld a, [wBattleType]
|
|
cp BATTLE_TYPE_SAFARI
|
|
ld a, " "
|
|
jr z, .safariRightColumn
|
|
; put cursor in right column for normal battle menu (i.e. when it's not a Safari battle)
|
|
ldcoord_a 9, 14 ; clear upper cursor position in left column
|
|
ldcoord_a 9, 16 ; clear lower cursor position in left column
|
|
ld b, $f ; top menu item X
|
|
jr .rightColumn_WaitForInput
|
|
.safariRightColumn
|
|
ldcoord_a 1, 14 ; clear upper cursor position in left column
|
|
ldcoord_a 1, 16 ; clear lower cursor position in left column
|
|
hlcoord 7, 14
|
|
ld de, wNumSafariBalls
|
|
lb bc, 1, 2
|
|
call PrintNumber
|
|
ld b, $d ; top menu item X
|
|
.rightColumn_WaitForInput
|
|
ld hl, wTopMenuItemY
|
|
ld a, $e
|
|
ld [hli], a ; wTopMenuItemY
|
|
ld a, b
|
|
ld [hli], a ; wTopMenuItemX
|
|
inc hl
|
|
inc hl
|
|
ld a, $1
|
|
ld [hli], a ; wMaxMenuItem
|
|
ld a, D_LEFT | A_BUTTON
|
|
ld [hli], a ; wMenuWatchedKeys
|
|
call HandleMenuInput
|
|
bit 5, a ; check if left was pressed
|
|
jr nz, .leftColumn ; if left was pressed, jump
|
|
ld a, [wCurrentMenuItem]
|
|
add $2 ; if we're in the right column, the actual id is +2
|
|
ld [wCurrentMenuItem], a
|
|
.AButtonPressed
|
|
call PlaceUnfilledArrowMenuCursor
|
|
ld a, [wBattleType]
|
|
cp BATTLE_TYPE_SAFARI
|
|
ld a, [wCurrentMenuItem]
|
|
ld [wBattleAndStartSavedMenuItem], a
|
|
jr z, .handleMenuSelection
|
|
; not Safari battle
|
|
; swap the IDs of the item menu and party menu (this is probably because they swapped the positions
|
|
; of these menu items in first generation English versions)
|
|
cp $1 ; was the item menu selected?
|
|
jr nz, .notItemMenu
|
|
; item menu was selected
|
|
inc a ; increment a to 2
|
|
jr .handleMenuSelection
|
|
.notItemMenu
|
|
cp $2 ; was the party menu selected?
|
|
jr nz, .handleMenuSelection
|
|
; party menu selected
|
|
dec a ; decrement a to 1
|
|
.handleMenuSelection
|
|
and a
|
|
jr nz, .upperLeftMenuItemWasNotSelected
|
|
; the upper left menu item was selected
|
|
ld a, [wBattleType]
|
|
cp BATTLE_TYPE_SAFARI
|
|
jr z, .throwSafariBallWasSelected
|
|
; the "FIGHT" menu was selected
|
|
xor a
|
|
ld [wNumRunAttempts], a
|
|
jp LoadScreenTilesFromBuffer1 ; restore saved screen and return
|
|
.throwSafariBallWasSelected
|
|
ld a, SAFARI_BALL
|
|
ld [wcf91], a
|
|
jr UseBagItem
|
|
|
|
.upperLeftMenuItemWasNotSelected ; a menu item other than the upper left item was selected
|
|
cp $2
|
|
jp nz, PartyMenuOrRockOrRun
|
|
|
|
; either the bag (normal battle) or bait (safari battle) was selected
|
|
ld a, [wLinkState]
|
|
cp LINK_STATE_BATTLING
|
|
jr nz, .notLinkBattle
|
|
|
|
; can't use items in link battles
|
|
ld hl, ItemsCantBeUsedHereText
|
|
call PrintText
|
|
jp DisplayBattleMenu
|
|
|
|
.notLinkBattle
|
|
call SaveScreenTilesToBuffer2
|
|
ld a, [wBattleType]
|
|
cp BATTLE_TYPE_SAFARI
|
|
jr nz, BagWasSelected
|
|
|
|
; bait was selected
|
|
ld a, SAFARI_BAIT
|
|
ld [wcf91], a
|
|
jr UseBagItem
|
|
|
|
BagWasSelected:
|
|
call LoadScreenTilesFromBuffer1
|
|
ld a, [wBattleType]
|
|
and a ; is it a normal battle?
|
|
jr nz, .next
|
|
|
|
; normal battle
|
|
call DrawHUDsAndHPBars
|
|
.next
|
|
ld a, [wBattleType]
|
|
dec a ; is it the old man tutorial?
|
|
jr nz, DisplayPlayerBag ; no, it is a normal battle
|
|
ld hl, OldManItemList
|
|
ld a, l
|
|
ld [wListPointer], a
|
|
ld a, h
|
|
ld [wListPointer + 1], a
|
|
jr DisplayBagMenu
|
|
|
|
OldManItemList:
|
|
db 1 ; # items
|
|
db POKE_BALL, 50
|
|
db -1 ; end
|
|
|
|
DisplayPlayerBag:
|
|
; get the pointer to player's bag when in a normal battle
|
|
ld hl, wNumBagItems
|
|
ld a, l
|
|
ld [wListPointer], a
|
|
ld a, h
|
|
ld [wListPointer + 1], a
|
|
|
|
DisplayBagMenu:
|
|
xor a
|
|
ld [wPrintItemPrices], a
|
|
ld a, ITEMLISTMENU
|
|
ld [wListMenuID], a
|
|
ld a, [wBagSavedMenuItem]
|
|
ld [wCurrentMenuItem], a
|
|
call DisplayListMenuID
|
|
ld a, [wCurrentMenuItem]
|
|
ld [wBagSavedMenuItem], a
|
|
ld a, $0
|
|
ld [wMenuWatchMovingOutOfBounds], a
|
|
ld [wMenuItemToSwap], a
|
|
jp c, DisplayBattleMenu ; go back to battle menu if an item was not selected
|
|
|
|
UseBagItem:
|
|
; either use an item from the bag or use a safari zone item
|
|
ld a, [wcf91]
|
|
ld [wd11e], a
|
|
call GetItemName
|
|
call CopyToStringBuffer
|
|
xor a
|
|
ld [wPseudoItemID], a
|
|
call UseItem
|
|
call LoadHudTilePatterns
|
|
call ClearSprites
|
|
xor a
|
|
ld [wCurrentMenuItem], a
|
|
ld a, [wBattleType]
|
|
cp BATTLE_TYPE_SAFARI
|
|
jr z, .checkIfMonCaptured
|
|
|
|
ld a, [wActionResultOrTookBattleTurn]
|
|
and a ; was the item used successfully?
|
|
jp z, BagWasSelected ; if not, go back to the bag menu
|
|
|
|
ld a, [wPlayerBattleStatus1]
|
|
bit USING_TRAPPING_MOVE, a ; is the player using a multi-turn move like wrap?
|
|
jr z, .checkIfMonCaptured
|
|
ld hl, wPlayerNumAttacksLeft
|
|
dec [hl]
|
|
jr nz, .checkIfMonCaptured
|
|
ld hl, wPlayerBattleStatus1
|
|
res USING_TRAPPING_MOVE, [hl] ; not using multi-turn move any more
|
|
|
|
.checkIfMonCaptured
|
|
ld a, [wCapturedMonSpecies]
|
|
and a ; was the enemy mon captured with a ball?
|
|
jr nz, .returnAfterCapturingMon
|
|
|
|
ld a, [wBattleType]
|
|
cp BATTLE_TYPE_SAFARI
|
|
jr z, .returnAfterUsingItem_NoCapture
|
|
; not a safari battle
|
|
call LoadScreenTilesFromBuffer1
|
|
call DrawHUDsAndHPBars
|
|
call Delay3
|
|
.returnAfterUsingItem_NoCapture
|
|
|
|
call GBPalNormal
|
|
and a ; reset carry
|
|
ret
|
|
|
|
.returnAfterCapturingMon
|
|
call GBPalNormal
|
|
xor a
|
|
ld [wCapturedMonSpecies], a
|
|
ld a, $2
|
|
ld [wBattleResult], a
|
|
scf ; set carry
|
|
ret
|
|
|
|
ItemsCantBeUsedHereText:
|
|
text_far _ItemsCantBeUsedHereText
|
|
text_end
|
|
|
|
PartyMenuOrRockOrRun:
|
|
dec a ; was Run selected?
|
|
jp nz, BattleMenu_RunWasSelected
|
|
; party menu or rock was selected
|
|
call SaveScreenTilesToBuffer2
|
|
ld a, [wBattleType]
|
|
cp BATTLE_TYPE_SAFARI
|
|
jr nz, .partyMenuWasSelected
|
|
; safari battle
|
|
ld a, SAFARI_ROCK
|
|
ld [wcf91], a
|
|
jp UseBagItem
|
|
.partyMenuWasSelected
|
|
call LoadScreenTilesFromBuffer1
|
|
xor a ; NORMAL_PARTY_MENU
|
|
ld [wPartyMenuTypeOrMessageID], a
|
|
ld [wMenuItemToSwap], a
|
|
call DisplayPartyMenu
|
|
.checkIfPartyMonWasSelected
|
|
jp nc, .partyMonWasSelected ; if a party mon was selected, jump, else we quit the party menu
|
|
.quitPartyMenu
|
|
call ClearSprites
|
|
call GBPalWhiteOut
|
|
call LoadHudTilePatterns
|
|
call LoadScreenTilesFromBuffer2
|
|
call RunDefaultPaletteCommand
|
|
call GBPalNormal
|
|
jp DisplayBattleMenu
|
|
.partyMonDeselected
|
|
hlcoord 11, 11
|
|
ld bc, 6 * SCREEN_WIDTH + 9
|
|
ld a, " "
|
|
call FillMemory
|
|
xor a ; NORMAL_PARTY_MENU
|
|
ld [wPartyMenuTypeOrMessageID], a
|
|
call GoBackToPartyMenu
|
|
jr .checkIfPartyMonWasSelected
|
|
.partyMonWasSelected
|
|
ld a, SWITCH_STATS_CANCEL_MENU_TEMPLATE
|
|
ld [wTextBoxID], a
|
|
call DisplayTextBoxID
|
|
ld hl, wTopMenuItemY
|
|
ld a, $c
|
|
ld [hli], a ; wTopMenuItemY
|
|
ld [hli], a ; wTopMenuItemX
|
|
xor a
|
|
ld [hli], a ; wCurrentMenuItem
|
|
inc hl
|
|
ld a, $2
|
|
ld [hli], a ; wMaxMenuItem
|
|
ld a, B_BUTTON | A_BUTTON
|
|
ld [hli], a ; wMenuWatchedKeys
|
|
xor a
|
|
ld [hl], a ; wLastMenuItem
|
|
call HandleMenuInput
|
|
bit BIT_B_BUTTON, a
|
|
jr nz, .partyMonDeselected ; if B was pressed, jump
|
|
; A was pressed
|
|
call PlaceUnfilledArrowMenuCursor
|
|
ld a, [wCurrentMenuItem]
|
|
cp $2 ; was Cancel selected?
|
|
jr z, .quitPartyMenu ; if so, quit the party menu entirely
|
|
and a ; was Switch selected?
|
|
jr z, .switchMon ; if so, jump
|
|
; Stats was selected
|
|
xor a ; PLAYER_PARTY_DATA
|
|
ld [wMonDataLocation], a
|
|
ld hl, wPartyMon1
|
|
call ClearSprites
|
|
; display the two status screens
|
|
predef StatusScreen
|
|
predef StatusScreen2
|
|
; now we need to reload the enemy mon pic
|
|
ld a, [wEnemyBattleStatus2]
|
|
bit HAS_SUBSTITUTE_UP, a ; does the enemy mon have a substitute?
|
|
ld hl, AnimationSubstitute
|
|
jr nz, .doEnemyMonAnimation
|
|
; enemy mon doesn't have substitute
|
|
ld a, [wEnemyMonMinimized]
|
|
and a ; has the enemy mon used Minimise?
|
|
ld hl, AnimationMinimizeMon
|
|
jr nz, .doEnemyMonAnimation
|
|
; enemy mon is not minimised
|
|
ld a, [wEnemyMonSpecies]
|
|
ld [wcf91], a
|
|
ld [wd0b5], a
|
|
call GetMonHeader
|
|
ld de, vFrontPic
|
|
call LoadMonFrontSprite
|
|
jr .enemyMonPicReloaded
|
|
.doEnemyMonAnimation
|
|
ld b, BANK(AnimationSubstitute) ; BANK(AnimationMinimizeMon)
|
|
call Bankswitch
|
|
.enemyMonPicReloaded ; enemy mon pic has been reloaded, so return to the party menu
|
|
jp .partyMenuWasSelected
|
|
.switchMon
|
|
ld a, [wPlayerMonNumber]
|
|
ld d, a
|
|
ld a, [wWhichPokemon]
|
|
cp d ; check if the mon to switch to is already out
|
|
jr nz, .notAlreadyOut
|
|
; mon is already out
|
|
ld hl, AlreadyOutText
|
|
call PrintText
|
|
jp .partyMonDeselected
|
|
.notAlreadyOut
|
|
call HasMonFainted
|
|
jp z, .partyMonDeselected ; can't switch to fainted mon
|
|
ld a, $1
|
|
ld [wActionResultOrTookBattleTurn], a
|
|
call GBPalWhiteOut
|
|
call ClearSprites
|
|
call LoadHudTilePatterns
|
|
call LoadScreenTilesFromBuffer1
|
|
call RunDefaultPaletteCommand
|
|
call GBPalNormal
|
|
; fall through to SwitchPlayerMon
|
|
|
|
SwitchPlayerMon:
|
|
callfar RetreatMon
|
|
ld c, 50
|
|
call DelayFrames
|
|
call AnimateRetreatingPlayerMon
|
|
ld a, [wWhichPokemon]
|
|
ld [wPlayerMonNumber], a
|
|
ld c, a
|
|
ld b, FLAG_SET
|
|
push bc
|
|
ld hl, wPartyGainExpFlags
|
|
predef FlagActionPredef
|
|
pop bc
|
|
ld hl, wPartyFoughtCurrentEnemyFlags
|
|
predef FlagActionPredef
|
|
call LoadBattleMonFromParty
|
|
call SendOutMon
|
|
call SaveScreenTilesToBuffer1
|
|
ld a, $2
|
|
ld [wCurrentMenuItem], a
|
|
and a
|
|
ret
|
|
|
|
AlreadyOutText:
|
|
text_far _AlreadyOutText
|
|
text_end
|
|
|
|
BattleMenu_RunWasSelected:
|
|
call LoadScreenTilesFromBuffer1
|
|
ld a, $3
|
|
ld [wCurrentMenuItem], a
|
|
ld hl, wBattleMonSpeed
|
|
ld de, wEnemyMonSpeed
|
|
call TryRunningFromBattle
|
|
ld a, 0
|
|
ld [wForcePlayerToChooseMon], a
|
|
ret c
|
|
ld a, [wActionResultOrTookBattleTurn]
|
|
and a
|
|
ret nz ; return if the player couldn't escape
|
|
jp DisplayBattleMenu
|
|
|
|
MoveSelectionMenu:
|
|
ld a, [wMoveMenuType]
|
|
dec a
|
|
jr z, .mimicmenu
|
|
dec a
|
|
jr z, .relearnmenu
|
|
jr .regularmenu
|
|
|
|
.loadmoves
|
|
ld de, wMoves
|
|
ld bc, NUM_MOVES
|
|
call CopyData
|
|
callfar FormatMovesString
|
|
ret
|
|
|
|
.writemoves
|
|
ld de, wMovesString
|
|
ldh a, [hUILayoutFlags]
|
|
set 2, a
|
|
ldh [hUILayoutFlags], a
|
|
call PlaceString
|
|
ldh a, [hUILayoutFlags]
|
|
res 2, a
|
|
ldh [hUILayoutFlags], a
|
|
ret
|
|
|
|
.regularmenu
|
|
call AnyMoveToSelect
|
|
ret z
|
|
ld hl, wBattleMonMoves
|
|
call .loadmoves
|
|
hlcoord 4, 12
|
|
ld b, 4
|
|
ld c, 14
|
|
di ; out of pure coincidence, it is possible for vblank to occur between the di and ei
|
|
; so it is necessary to put the di ei block to not cause tearing
|
|
call TextBoxBorder
|
|
hlcoord 4, 12
|
|
ld [hl], "─"
|
|
hlcoord 10, 12
|
|
ld [hl], "┘"
|
|
ei
|
|
hlcoord 6, 13
|
|
call .writemoves
|
|
ld b, $5
|
|
ld a, $c
|
|
jr .menuset
|
|
.mimicmenu
|
|
ld hl, wEnemyMonMoves
|
|
call .loadmoves
|
|
hlcoord 0, 7
|
|
ld b, 4
|
|
ld c, 14
|
|
call TextBoxBorder
|
|
hlcoord 2, 8
|
|
call .writemoves
|
|
ld b, $1
|
|
ld a, $7
|
|
jr .menuset
|
|
.relearnmenu
|
|
ld a, [wWhichPokemon]
|
|
ld hl, wPartyMon1Moves
|
|
ld bc, wPartyMon2 - wPartyMon1
|
|
call AddNTimes
|
|
call .loadmoves
|
|
hlcoord 4, 7
|
|
ld b, 4
|
|
ld c, 14
|
|
call TextBoxBorder
|
|
hlcoord 6, 8
|
|
call .writemoves
|
|
ld b, $5
|
|
ld a, $7
|
|
.menuset
|
|
ld hl, wTopMenuItemY
|
|
ld [hli], a ; wTopMenuItemY
|
|
ld a, b
|
|
ld [hli], a ; wTopMenuItemX
|
|
ld a, [wMoveMenuType]
|
|
cp $1
|
|
jr z, .selectedmoveknown
|
|
ld a, $1
|
|
jr nc, .selectedmoveknown
|
|
ld a, [wPlayerMoveListIndex]
|
|
inc a
|
|
.selectedmoveknown
|
|
ld [hli], a ; wCurrentMenuItem
|
|
inc hl ; wTileBehindCursor untouched
|
|
ld a, [wNumMovesMinusOne]
|
|
inc a
|
|
inc a
|
|
ld [hli], a ; wMaxMenuItem
|
|
ld a, [wMoveMenuType]
|
|
dec a
|
|
ld b, D_UP | D_DOWN | A_BUTTON
|
|
jr z, .matchedkeyspicked
|
|
dec a
|
|
ld b, D_UP | D_DOWN | A_BUTTON | B_BUTTON
|
|
jr z, .matchedkeyspicked
|
|
ld a, [wLinkState]
|
|
cp LINK_STATE_BATTLING
|
|
jr z, .matchedkeyspicked
|
|
; Disable left, right, and START buttons in regular battles.
|
|
ld a, [wFlags_D733]
|
|
bit BIT_TEST_BATTLE, a
|
|
ld b, D_UP | D_DOWN | A_BUTTON | B_BUTTON | SELECT
|
|
jr z, .matchedkeyspicked
|
|
ld b, D_UP | D_DOWN | D_LEFT | D_RIGHT | A_BUTTON | B_BUTTON | SELECT | START
|
|
.matchedkeyspicked
|
|
ld a, b
|
|
ld [hli], a ; wMenuWatchedKeys
|
|
ld a, [wMoveMenuType]
|
|
cp $1
|
|
jr z, .movelistindex1
|
|
ld a, [wPlayerMoveListIndex]
|
|
inc a
|
|
.movelistindex1
|
|
ld [hl], a
|
|
; fallthrough
|
|
|
|
SelectMenuItem:
|
|
ld a, [wMoveMenuType]
|
|
and a
|
|
jr z, .battleselect
|
|
dec a
|
|
jr nz, .select
|
|
hlcoord 1, 14
|
|
ld de, WhichTechniqueString
|
|
call PlaceString
|
|
jr .select
|
|
.battleselect
|
|
; Hide move swap cursor in TestBattle.
|
|
ld a, [wFlags_D733]
|
|
bit BIT_TEST_BATTLE, a
|
|
; This causes PrintMenuItem to not run in TestBattle.
|
|
; MoveSelectionMenu still draws part of its window, an issue
|
|
; which did not seem to exist in the Japanese versions.
|
|
jr nz, .select
|
|
call PrintMenuItem
|
|
ld a, [wMenuItemToSwap]
|
|
and a
|
|
jr z, .select
|
|
hlcoord 5, 13
|
|
dec a
|
|
ld bc, SCREEN_WIDTH
|
|
call AddNTimes
|
|
ld [hl], "▷"
|
|
.select
|
|
ld hl, hUILayoutFlags
|
|
set 1, [hl]
|
|
call HandleMenuInput
|
|
ld hl, hUILayoutFlags
|
|
res 1, [hl]
|
|
bit BIT_D_UP, a
|
|
jp nz, SelectMenuItem_CursorUp
|
|
bit BIT_D_DOWN, a
|
|
jp nz, SelectMenuItem_CursorDown
|
|
bit BIT_SELECT, a
|
|
jp nz, SwapMovesInMenu
|
|
bit BIT_B_BUTTON, a
|
|
push af
|
|
xor a
|
|
ld [wMenuItemToSwap], a
|
|
ld a, [wCurrentMenuItem]
|
|
dec a
|
|
ld [wCurrentMenuItem], a
|
|
ld b, a
|
|
ld a, [wMoveMenuType]
|
|
dec a ; if not mimic
|
|
jr nz, .notB
|
|
pop af
|
|
ret
|
|
.notB
|
|
dec a
|
|
ld a, b
|
|
ld [wPlayerMoveListIndex], a
|
|
jr nz, .moveselected
|
|
pop af
|
|
ret
|
|
.moveselected
|
|
pop af
|
|
ret nz
|
|
ld hl, wBattleMonPP
|
|
ld a, [wCurrentMenuItem]
|
|
ld c, a
|
|
ld b, $0
|
|
add hl, bc
|
|
ld a, [hl]
|
|
and $3f
|
|
jr z, .noPP
|
|
ld a, [wPlayerDisabledMove]
|
|
swap a
|
|
and $f
|
|
dec a
|
|
cp c
|
|
jr z, .disabled
|
|
ld a, [wPlayerBattleStatus3]
|
|
bit 3, a ; transformed
|
|
jr nz, .transformedMoveSelected
|
|
.transformedMoveSelected ; pointless
|
|
; Allow moves copied by Transform to be used.
|
|
ld a, [wCurrentMenuItem]
|
|
ld hl, wBattleMonMoves
|
|
ld c, a
|
|
ld b, $0
|
|
add hl, bc
|
|
ld a, [hl]
|
|
ld [wPlayerSelectedMove], a
|
|
xor a
|
|
ret
|
|
.disabled
|
|
ld hl, MoveDisabledText
|
|
jr .print
|
|
.noPP
|
|
ld hl, MoveNoPPText
|
|
.print
|
|
call PrintText
|
|
call LoadScreenTilesFromBuffer1
|
|
jp MoveSelectionMenu
|
|
|
|
MoveNoPPText:
|
|
text_far _MoveNoPPText
|
|
text_end
|
|
|
|
MoveDisabledText:
|
|
text_far _MoveDisabledText
|
|
text_end
|
|
|
|
WhichTechniqueString:
|
|
db "WHICH TECHNIQUE?@"
|
|
|
|
SelectMenuItem_CursorUp:
|
|
ld a, [wCurrentMenuItem]
|
|
and a
|
|
jp nz, SelectMenuItem
|
|
call EraseMenuCursor
|
|
ld a, [wNumMovesMinusOne]
|
|
inc a
|
|
ld [wCurrentMenuItem], a
|
|
jp SelectMenuItem
|
|
|
|
SelectMenuItem_CursorDown:
|
|
ld a, [wCurrentMenuItem]
|
|
ld b, a
|
|
ld a, [wNumMovesMinusOne]
|
|
inc a
|
|
inc a
|
|
cp b
|
|
jp nz, SelectMenuItem
|
|
call EraseMenuCursor
|
|
ld a, $1
|
|
ld [wCurrentMenuItem], a
|
|
jp SelectMenuItem
|
|
|
|
AnyMoveToSelect:
|
|
; return z and Struggle as the selected move if all moves have 0 PP and/or are disabled
|
|
ld a, STRUGGLE
|
|
ld [wPlayerSelectedMove], a
|
|
ld a, [wPlayerDisabledMove]
|
|
and a
|
|
ld hl, wBattleMonPP
|
|
jr nz, .handleDisabledMove
|
|
ld a, [hli]
|
|
or [hl]
|
|
inc hl
|
|
or [hl]
|
|
inc hl
|
|
or [hl]
|
|
and $3f
|
|
ret nz
|
|
jr .noMovesLeft
|
|
.handleDisabledMove
|
|
swap a
|
|
and $f ; get disabled move
|
|
ld b, a
|
|
ld d, NUM_MOVES + 1
|
|
xor a
|
|
.handleDisabledMovePPLoop
|
|
dec d
|
|
jr z, .allMovesChecked
|
|
ld c, [hl] ; get move PP
|
|
inc hl
|
|
dec b ; is this the disabled move?
|
|
jr z, .handleDisabledMovePPLoop ; if so, ignore its PP value
|
|
or c
|
|
jr .handleDisabledMovePPLoop
|
|
.allMovesChecked
|
|
and a ; any PP left?
|
|
ret nz ; return if a move has PP left
|
|
.noMovesLeft
|
|
ld hl, NoMovesLeftText
|
|
call PrintText
|
|
ld c, 60
|
|
call DelayFrames
|
|
xor a
|
|
ret
|
|
|
|
NoMovesLeftText:
|
|
text_far _NoMovesLeftText
|
|
text_end
|
|
|
|
SwapMovesInMenu:
|
|
ld a, [wMenuItemToSwap]
|
|
and a
|
|
jr z, .noMenuItemSelected
|
|
ld hl, wBattleMonMoves
|
|
call .swapBytes ; swap moves
|
|
ld hl, wBattleMonPP
|
|
call .swapBytes ; swap move PP
|
|
; update the index of the disabled move if necessary
|
|
ld hl, wPlayerDisabledMove
|
|
ld a, [hl]
|
|
swap a
|
|
and $f
|
|
ld b, a
|
|
ld a, [wCurrentMenuItem]
|
|
cp b
|
|
jr nz, .next
|
|
ld a, [hl]
|
|
and $f
|
|
ld b, a
|
|
ld a, [wMenuItemToSwap]
|
|
swap a
|
|
add b
|
|
ld [hl], a
|
|
jr .swapMovesInPartyMon
|
|
.next
|
|
ld a, [wMenuItemToSwap]
|
|
cp b
|
|
jr nz, .swapMovesInPartyMon
|
|
ld a, [hl]
|
|
and $f
|
|
ld b, a
|
|
ld a, [wCurrentMenuItem]
|
|
swap a
|
|
add b
|
|
ld [hl], a
|
|
.swapMovesInPartyMon
|
|
ld hl, wPartyMon1Moves
|
|
ld a, [wPlayerMonNumber]
|
|
ld bc, wPartyMon2 - wPartyMon1
|
|
call AddNTimes
|
|
push hl
|
|
call .swapBytes ; swap moves
|
|
pop hl
|
|
ld bc, wPartyMon1PP - wPartyMon1Moves
|
|
add hl, bc
|
|
call .swapBytes ; swap move PP
|
|
xor a
|
|
ld [wMenuItemToSwap], a ; deselect the item
|
|
jp MoveSelectionMenu
|
|
.swapBytes
|
|
push hl
|
|
ld a, [wMenuItemToSwap]
|
|
dec a
|
|
ld c, a
|
|
ld b, 0
|
|
add hl, bc
|
|
ld d, h
|
|
ld e, l
|
|
pop hl
|
|
ld a, [wCurrentMenuItem]
|
|
dec a
|
|
ld c, a
|
|
ld b, 0
|
|
add hl, bc
|
|
ld a, [de]
|
|
ld b, [hl]
|
|
ld [hl], a
|
|
ld a, b
|
|
ld [de], a
|
|
ret
|
|
.noMenuItemSelected
|
|
ld a, [wCurrentMenuItem]
|
|
ld [wMenuItemToSwap], a ; select the current menu item for swapping
|
|
jp MoveSelectionMenu
|
|
|
|
PrintMenuItem:
|
|
xor a
|
|
ldh [hAutoBGTransferEnabled], a
|
|
hlcoord 0, 8
|
|
ld b, 3
|
|
ld c, 9
|
|
call TextBoxBorder
|
|
ld a, [wPlayerDisabledMove]
|
|
and a
|
|
jr z, .notDisabled
|
|
swap a
|
|
and $f
|
|
ld b, a
|
|
ld a, [wCurrentMenuItem]
|
|
cp b
|
|
jr nz, .notDisabled
|
|
hlcoord 1, 10
|
|
ld de, DisabledText
|
|
call PlaceString
|
|
jr .moveDisabled
|
|
.notDisabled
|
|
ld hl, wCurrentMenuItem
|
|
dec [hl]
|
|
xor a
|
|
ldh [hWhoseTurn], a
|
|
ld hl, wBattleMonMoves
|
|
ld a, [wCurrentMenuItem]
|
|
ld c, a
|
|
ld b, $0 ; which item in the menu is the cursor pointing to? (0-3)
|
|
add hl, bc ; point to the item (move) in memory
|
|
ld a, [hl]
|
|
ld [wPlayerSelectedMove], a ; update wPlayerSelectedMove even if the move
|
|
; isn't actually selected (just pointed to by the cursor)
|
|
ld a, [wPlayerMonNumber]
|
|
ld [wWhichPokemon], a
|
|
ld a, BATTLE_MON_DATA
|
|
ld [wMonDataLocation], a
|
|
callfar GetMaxPP
|
|
ld hl, wCurrentMenuItem
|
|
ld c, [hl]
|
|
inc [hl]
|
|
ld b, $0
|
|
ld hl, wBattleMonPP
|
|
add hl, bc
|
|
ld a, [hl]
|
|
and $3f
|
|
ld [wcd6d], a
|
|
; print TYPE/<type> and <curPP>/<maxPP>
|
|
hlcoord 1, 9
|
|
ld de, TypeText
|
|
call PlaceString
|
|
hlcoord 7, 11
|
|
ld [hl], "/"
|
|
hlcoord 5, 9
|
|
ld [hl], "/"
|
|
hlcoord 5, 11
|
|
ld de, wcd6d
|
|
lb bc, 1, 2
|
|
call PrintNumber
|
|
hlcoord 8, 11
|
|
ld de, wMaxPP
|
|
lb bc, 1, 2
|
|
call PrintNumber
|
|
call GetCurrentMove
|
|
hlcoord 2, 10
|
|
predef PrintMoveType
|
|
.moveDisabled
|
|
ld a, $1
|
|
ldh [hAutoBGTransferEnabled], a
|
|
jp Delay3
|
|
|
|
DisabledText:
|
|
db "disabled!@"
|
|
|
|
TypeText:
|
|
db "TYPE@"
|
|
|
|
SelectEnemyMove:
|
|
ld a, [wLinkState]
|
|
sub LINK_STATE_BATTLING
|
|
jr nz, .noLinkBattle
|
|
; link battle
|
|
call SaveScreenTilesToBuffer1
|
|
call LinkBattleExchangeData
|
|
call LoadScreenTilesFromBuffer1
|
|
ld a, [wSerialExchangeNybbleReceiveData]
|
|
cp LINKBATTLE_STRUGGLE
|
|
jp z, .linkedOpponentUsedStruggle
|
|
cp LINKBATTLE_NO_ACTION
|
|
jr z, .unableToSelectMove
|
|
cp 4
|
|
ret nc
|
|
ld [wEnemyMoveListIndex], a
|
|
ld c, a
|
|
ld hl, wEnemyMonMoves
|
|
ld b, 0
|
|
add hl, bc
|
|
ld a, [hl]
|
|
jr .done
|
|
.noLinkBattle
|
|
ld a, [wEnemyBattleStatus2]
|
|
and (1 << NEEDS_TO_RECHARGE) | (1 << USING_RAGE) ; need to recharge or using rage
|
|
ret nz
|
|
ld hl, wEnemyBattleStatus1
|
|
ld a, [hl]
|
|
and (1 << CHARGING_UP) | (1 << THRASHING_ABOUT) ; using a charging move or thrash/petal dance
|
|
ret nz
|
|
ld a, [wEnemyMonStatus]
|
|
and (1 << FRZ) | SLP_MASK
|
|
ret nz
|
|
ld a, [wEnemyBattleStatus1]
|
|
and (1 << USING_TRAPPING_MOVE) | (1 << STORING_ENERGY) ; using a trapping move like wrap or bide
|
|
ret nz
|
|
ld a, [wPlayerBattleStatus1]
|
|
bit USING_TRAPPING_MOVE, a ; caught in player's trapping move (e.g. wrap)
|
|
jr z, .canSelectMove
|
|
.unableToSelectMove
|
|
ld a, $ff
|
|
jr .done
|
|
.canSelectMove
|
|
ld hl, wEnemyMonMoves+1 ; 2nd enemy move
|
|
ld a, [hld]
|
|
and a
|
|
jr nz, .atLeastTwoMovesAvailable
|
|
ld a, [wEnemyDisabledMove]
|
|
and a
|
|
ld a, STRUGGLE ; struggle if the only move is disabled
|
|
jr nz, .done
|
|
.atLeastTwoMovesAvailable
|
|
ld a, [wIsInBattle]
|
|
dec a
|
|
jr z, .chooseRandomMove ; wild encounter
|
|
callfar AIEnemyTrainerChooseMoves
|
|
.chooseRandomMove
|
|
push hl
|
|
call BattleRandom
|
|
ld b, 1 ; 25% chance to select move 1
|
|
cp 25 percent
|
|
jr c, .moveChosen
|
|
inc hl
|
|
inc b ; 25% chance to select move 2
|
|
cp 50 percent
|
|
jr c, .moveChosen
|
|
inc hl
|
|
inc b ; 25% chance to select move 3
|
|
cp 75 percent - 1
|
|
jr c, .moveChosen
|
|
inc hl
|
|
inc b ; 25% chance to select move 4
|
|
.moveChosen
|
|
ld a, b
|
|
dec a
|
|
ld [wEnemyMoveListIndex], a
|
|
ld a, [wEnemyDisabledMove]
|
|
swap a
|
|
and $f
|
|
cp b
|
|
ld a, [hl]
|
|
pop hl
|
|
jr z, .chooseRandomMove ; move disabled, try again
|
|
and a
|
|
jr z, .chooseRandomMove ; move non-existant, try again
|
|
.done
|
|
ld [wEnemySelectedMove], a
|
|
ret
|
|
.linkedOpponentUsedStruggle
|
|
ld a, STRUGGLE
|
|
jr .done
|
|
|
|
; this appears to exchange data with the other gameboy during link battles
|
|
LinkBattleExchangeData:
|
|
ld a, $ff
|
|
ld [wSerialExchangeNybbleReceiveData], a
|
|
ld a, [wPlayerMoveListIndex]
|
|
cp LINKBATTLE_RUN ; is the player running from battle?
|
|
jr z, .doExchange
|
|
ld a, [wActionResultOrTookBattleTurn]
|
|
and a ; is the player switching in another mon?
|
|
jr nz, .switching
|
|
; the player used a move
|
|
ld a, [wPlayerSelectedMove]
|
|
cp STRUGGLE
|
|
ld b, LINKBATTLE_STRUGGLE
|
|
jr z, .next
|
|
dec b ; LINKBATTLE_NO_ACTION
|
|
inc a ; does move equal -1 (i.e. no action)?
|
|
jr z, .next
|
|
ld a, [wPlayerMoveListIndex]
|
|
jr .doExchange
|
|
.switching
|
|
ld a, [wWhichPokemon]
|
|
add 4
|
|
ld b, a
|
|
.next
|
|
ld a, b
|
|
.doExchange
|
|
ld [wSerialExchangeNybbleSendData], a
|
|
vc_hook Wireless_start_exchange
|
|
callfar PrintWaitingText
|
|
.syncLoop1
|
|
call Serial_ExchangeNybble
|
|
call DelayFrame
|
|
ld a, [wSerialExchangeNybbleReceiveData]
|
|
inc a
|
|
jr z, .syncLoop1
|
|
vc_hook Wireless_end_exchange
|
|
vc_patch Wireless_net_delay_1
|
|
IF DEF(_RED_VC) || DEF(_BLUE_VC)
|
|
ld b, 26
|
|
ELSE
|
|
ld b, 10
|
|
ENDC
|
|
vc_patch_end
|
|
.syncLoop2
|
|
call DelayFrame
|
|
call Serial_ExchangeNybble
|
|
dec b
|
|
jr nz, .syncLoop2
|
|
vc_hook Wireless_start_send_zero_bytes
|
|
vc_patch Wireless_net_delay_2
|
|
IF DEF(_RED_VC) || DEF(_BLUE_VC)
|
|
ld b, 26
|
|
ELSE
|
|
ld b, 10
|
|
ENDC
|
|
vc_patch_end
|
|
.syncLoop3
|
|
call DelayFrame
|
|
call Serial_SendZeroByte
|
|
dec b
|
|
jr nz, .syncLoop3
|
|
vc_hook Wireless_end_send_zero_bytes
|
|
ret
|
|
|
|
ExecutePlayerMove:
|
|
xor a
|
|
ldh [hWhoseTurn], a ; set player's turn
|
|
ld a, [wPlayerSelectedMove]
|
|
inc a
|
|
jp z, ExecutePlayerMoveDone ; for selected move = FF, skip most of player's turn
|
|
xor a
|
|
ld [wMoveMissed], a
|
|
ld [wMonIsDisobedient], a
|
|
ld [wMoveDidntMiss], a
|
|
ld a, $a
|
|
ld [wDamageMultipliers], a
|
|
ld a, [wActionResultOrTookBattleTurn]
|
|
and a ; has the player already used the turn (e.g. by using an item, trying to run or switching pokemon)
|
|
jp nz, ExecutePlayerMoveDone
|
|
call PrintGhostText
|
|
jp z, ExecutePlayerMoveDone
|
|
call CheckPlayerStatusConditions
|
|
jr nz, .playerHasNoSpecialCondition
|
|
jp hl
|
|
.playerHasNoSpecialCondition
|
|
call GetCurrentMove
|
|
ld hl, wPlayerBattleStatus1
|
|
bit CHARGING_UP, [hl] ; charging up for attack
|
|
jr nz, PlayerCanExecuteChargingMove
|
|
call CheckForDisobedience
|
|
jp z, ExecutePlayerMoveDone
|
|
|
|
CheckIfPlayerNeedsToChargeUp:
|
|
ld a, [wPlayerMoveEffect]
|
|
cp CHARGE_EFFECT
|
|
jp z, JumpMoveEffect
|
|
cp FLY_EFFECT
|
|
jp z, JumpMoveEffect
|
|
jr PlayerCanExecuteMove
|
|
|
|
; in-battle stuff
|
|
PlayerCanExecuteChargingMove:
|
|
ld hl, wPlayerBattleStatus1
|
|
res CHARGING_UP, [hl] ; reset charging up and invulnerability statuses if mon was charging up for an attack
|
|
; being fully paralyzed or hurting oneself in confusion removes charging up status
|
|
; resulting in the Pokemon being invulnerable for the whole battle
|
|
res INVULNERABLE, [hl]
|
|
PlayerCanExecuteMove:
|
|
call PrintMonName1Text
|
|
ld hl, DecrementPP
|
|
ld de, wPlayerSelectedMove ; pointer to the move just used
|
|
ld b, BANK(DecrementPP)
|
|
call Bankswitch
|
|
ld a, [wPlayerMoveEffect] ; effect of the move just used
|
|
ld hl, ResidualEffects1
|
|
ld de, 1
|
|
call IsInArray
|
|
jp c, JumpMoveEffect ; ResidualEffects1 moves skip damage calculation and accuracy tests
|
|
; unless executed as part of their exclusive effect functions
|
|
ld a, [wPlayerMoveEffect]
|
|
ld hl, SpecialEffectsCont
|
|
ld de, 1
|
|
call IsInArray
|
|
call c, JumpMoveEffect ; execute the effects of SpecialEffectsCont moves (e.g. Wrap, Thrash) but don't skip anything
|
|
PlayerCalcMoveDamage:
|
|
ld a, [wPlayerMoveEffect]
|
|
ld hl, SetDamageEffects
|
|
ld de, 1
|
|
call IsInArray
|
|
jp c, .moveHitTest ; SetDamageEffects moves (e.g. Seismic Toss and Super Fang) skip damage calculation
|
|
call CriticalHitTest
|
|
call HandleCounterMove
|
|
jr z, handleIfPlayerMoveMissed
|
|
call GetDamageVarsForPlayerAttack
|
|
call CalculateDamage
|
|
jp z, playerCheckIfFlyOrChargeEffect ; for moves with 0 BP, skip any further damage calculation and, for now, skip MoveHitTest
|
|
; for these moves, accuracy tests will only occur if they are called as part of the effect itself
|
|
call AdjustDamageForMoveType
|
|
call RandomizeDamage
|
|
.moveHitTest
|
|
call MoveHitTest
|
|
handleIfPlayerMoveMissed:
|
|
ld a, [wMoveMissed]
|
|
and a
|
|
jr z, getPlayerAnimationType
|
|
ld a, [wPlayerMoveEffect]
|
|
sub EXPLODE_EFFECT
|
|
jr z, playPlayerMoveAnimation ; don't play any animation if the move missed, unless it was EXPLODE_EFFECT
|
|
jr playerCheckIfFlyOrChargeEffect
|
|
getPlayerAnimationType:
|
|
ld a, [wPlayerMoveEffect]
|
|
and a
|
|
ld a, 4 ; move has no effect other than dealing damage
|
|
jr z, playPlayerMoveAnimation
|
|
ld a, 5 ; move has effect
|
|
playPlayerMoveAnimation:
|
|
push af
|
|
ld a, [wPlayerBattleStatus2]
|
|
bit HAS_SUBSTITUTE_UP, a
|
|
ld hl, HideSubstituteShowMonAnim
|
|
ld b, BANK(HideSubstituteShowMonAnim)
|
|
call nz, Bankswitch
|
|
pop af
|
|
ld [wAnimationType], a
|
|
ld a, [wPlayerMoveNum]
|
|
call PlayMoveAnimation
|
|
call HandleExplodingAnimation
|
|
call DrawPlayerHUDAndHPBar
|
|
ld a, [wPlayerBattleStatus2]
|
|
bit HAS_SUBSTITUTE_UP, a
|
|
ld hl, ReshowSubstituteAnim
|
|
ld b, BANK(ReshowSubstituteAnim)
|
|
call nz, Bankswitch
|
|
jr MirrorMoveCheck
|
|
playerCheckIfFlyOrChargeEffect:
|
|
ld c, 30
|
|
call DelayFrames
|
|
ld a, [wPlayerMoveEffect]
|
|
cp FLY_EFFECT
|
|
jr z, .playAnim
|
|
cp CHARGE_EFFECT
|
|
jr z, .playAnim
|
|
jr MirrorMoveCheck
|
|
.playAnim
|
|
xor a
|
|
ld [wAnimationType], a
|
|
ld a, STATUS_AFFECTED_ANIM
|
|
call PlayMoveAnimation
|
|
MirrorMoveCheck:
|
|
ld a, [wPlayerMoveEffect]
|
|
cp MIRROR_MOVE_EFFECT
|
|
jr nz, .metronomeCheck
|
|
call MirrorMoveCopyMove
|
|
jp z, ExecutePlayerMoveDone
|
|
xor a
|
|
ld [wMonIsDisobedient], a
|
|
jp CheckIfPlayerNeedsToChargeUp ; if Mirror Move was successful go back to damage calculation for copied move
|
|
.metronomeCheck
|
|
cp METRONOME_EFFECT
|
|
jr nz, .next
|
|
call MetronomePickMove
|
|
jp CheckIfPlayerNeedsToChargeUp ; Go back to damage calculation for the move picked by Metronome
|
|
.next
|
|
ld a, [wPlayerMoveEffect]
|
|
ld hl, ResidualEffects2
|
|
ld de, 1
|
|
call IsInArray
|
|
jp c, JumpMoveEffect ; done here after executing effects of ResidualEffects2
|
|
ld a, [wMoveMissed]
|
|
and a
|
|
jr z, .moveDidNotMiss
|
|
call PrintMoveFailureText
|
|
ld a, [wPlayerMoveEffect]
|
|
cp EXPLODE_EFFECT ; even if Explosion or Selfdestruct missed, its effect still needs to be activated
|
|
jr z, .notDone
|
|
jp ExecutePlayerMoveDone ; otherwise, we're done if the move missed
|
|
.moveDidNotMiss
|
|
call ApplyAttackToEnemyPokemon
|
|
call PrintCriticalOHKOText
|
|
callfar DisplayEffectiveness
|
|
ld a, 1
|
|
ld [wMoveDidntMiss], a
|
|
.notDone
|
|
ld a, [wPlayerMoveEffect]
|
|
ld hl, AlwaysHappenSideEffects
|
|
ld de, 1
|
|
call IsInArray
|
|
call c, JumpMoveEffect ; not done after executing effects of AlwaysHappenSideEffects
|
|
ld hl, wEnemyMonHP
|
|
ld a, [hli]
|
|
ld b, [hl]
|
|
or b
|
|
ret z ; don't do anything else if the enemy fainted
|
|
call HandleBuildingRage
|
|
|
|
ld hl, wPlayerBattleStatus1
|
|
bit ATTACKING_MULTIPLE_TIMES, [hl]
|
|
jr z, .executeOtherEffects
|
|
ld a, [wPlayerNumAttacksLeft]
|
|
dec a
|
|
ld [wPlayerNumAttacksLeft], a
|
|
jp nz, getPlayerAnimationType ; for multi-hit moves, apply attack until PlayerNumAttacksLeft hits 0 or the enemy faints.
|
|
; damage calculation and accuracy tests only happen for the first hit
|
|
res ATTACKING_MULTIPLE_TIMES, [hl] ; clear attacking multiple times status when all attacks are over
|
|
ld hl, MultiHitText
|
|
call PrintText
|
|
xor a
|
|
ld [wPlayerNumHits], a
|
|
.executeOtherEffects
|
|
ld a, [wPlayerMoveEffect]
|
|
and a
|
|
jp z, ExecutePlayerMoveDone
|
|
ld hl, SpecialEffects
|
|
ld de, 1
|
|
call IsInArray
|
|
call nc, JumpMoveEffect ; move effects not included in SpecialEffects or in either of the ResidualEffect arrays,
|
|
; which are the effects not covered yet. Rage effect will be executed for a second time (though it's irrelevant).
|
|
; Includes side effects that only need to be called if the target didn't faint.
|
|
; Responsible for executing Twineedle's second side effect (poison).
|
|
jp ExecutePlayerMoveDone
|
|
|
|
MultiHitText:
|
|
text_far _MultiHitText
|
|
text_end
|
|
|
|
ExecutePlayerMoveDone:
|
|
xor a
|
|
ld [wActionResultOrTookBattleTurn], a
|
|
ld b, 1
|
|
ret
|
|
|
|
PrintGhostText:
|
|
; print the ghost battle messages
|
|
call IsGhostBattle
|
|
ret nz
|
|
ldh a, [hWhoseTurn]
|
|
and a
|
|
jr nz, .Ghost
|
|
ld a, [wBattleMonStatus] ; player's turn
|
|
and (1 << FRZ) | SLP_MASK
|
|
ret nz
|
|
ld hl, ScaredText
|
|
call PrintText
|
|
xor a
|
|
ret
|
|
.Ghost ; ghost's turn
|
|
ld hl, GetOutText
|
|
call PrintText
|
|
xor a
|
|
ret
|
|
|
|
ScaredText:
|
|
text_far _ScaredText
|
|
text_end
|
|
|
|
GetOutText:
|
|
text_far _GetOutText
|
|
text_end
|
|
|
|
IsGhostBattle:
|
|
ld a, [wIsInBattle]
|
|
dec a
|
|
ret nz
|
|
ld a, [wCurMap]
|
|
cp POKEMON_TOWER_1F
|
|
jr c, .next
|
|
cp POKEMON_TOWER_7F + 1
|
|
jr nc, .next
|
|
ld b, SILPH_SCOPE
|
|
call IsItemInBag
|
|
ret z
|
|
.next
|
|
ld a, 1
|
|
and a
|
|
ret
|
|
|
|
; checks for various status conditions affecting the player mon
|
|
; stores whether the mon cannot use a move this turn in Z flag
|
|
CheckPlayerStatusConditions:
|
|
ld hl, wBattleMonStatus
|
|
ld a, [hl]
|
|
and SLP_MASK
|
|
jr z, .FrozenCheck
|
|
; sleeping
|
|
dec a
|
|
ld [wBattleMonStatus], a ; decrement number of turns left
|
|
and a
|
|
jr z, .WakeUp ; if the number of turns hit 0, wake up
|
|
; fast asleep
|
|
xor a
|
|
ld [wAnimationType], a
|
|
ld a, SLP_PLAYER_ANIM
|
|
call PlayMoveAnimation
|
|
ld hl, FastAsleepText
|
|
call PrintText
|
|
jr .sleepDone
|
|
.WakeUp
|
|
ld hl, WokeUpText
|
|
call PrintText
|
|
.sleepDone
|
|
xor a
|
|
ld [wPlayerUsedMove], a
|
|
ld hl, ExecutePlayerMoveDone ; player can't move this turn
|
|
jp .returnToHL
|
|
|
|
.FrozenCheck
|
|
bit FRZ, [hl] ; frozen?
|
|
jr z, .HeldInPlaceCheck
|
|
ld hl, IsFrozenText
|
|
call PrintText
|
|
xor a
|
|
ld [wPlayerUsedMove], a
|
|
ld hl, ExecutePlayerMoveDone ; player can't move this turn
|
|
jp .returnToHL
|
|
|
|
.HeldInPlaceCheck
|
|
ld a, [wEnemyBattleStatus1]
|
|
bit USING_TRAPPING_MOVE, a ; is enemy using a multi-turn move like wrap?
|
|
jp z, .FlinchedCheck
|
|
ld hl, CantMoveText
|
|
call PrintText
|
|
ld hl, ExecutePlayerMoveDone ; player can't move this turn
|
|
jp .returnToHL
|
|
|
|
.FlinchedCheck
|
|
ld hl, wPlayerBattleStatus1
|
|
bit FLINCHED, [hl]
|
|
jp z, .HyperBeamCheck
|
|
res FLINCHED, [hl] ; reset player's flinch status
|
|
ld hl, FlinchedText
|
|
call PrintText
|
|
ld hl, ExecutePlayerMoveDone ; player can't move this turn
|
|
jp .returnToHL
|
|
|
|
.HyperBeamCheck
|
|
ld hl, wPlayerBattleStatus2
|
|
bit NEEDS_TO_RECHARGE, [hl]
|
|
jr z, .AnyMoveDisabledCheck
|
|
res NEEDS_TO_RECHARGE, [hl] ; reset player's recharge status
|
|
ld hl, MustRechargeText
|
|
call PrintText
|
|
ld hl, ExecutePlayerMoveDone ; player can't move this turn
|
|
jp .returnToHL
|
|
|
|
.AnyMoveDisabledCheck
|
|
ld hl, wPlayerDisabledMove
|
|
ld a, [hl]
|
|
and a
|
|
jr z, .ConfusedCheck
|
|
dec a
|
|
ld [hl], a
|
|
and $f ; did Disable counter hit 0?
|
|
jr nz, .ConfusedCheck
|
|
ld [hl], a
|
|
ld [wPlayerDisabledMoveNumber], a
|
|
ld hl, DisabledNoMoreText
|
|
call PrintText
|
|
|
|
.ConfusedCheck
|
|
ld a, [wPlayerBattleStatus1]
|
|
add a ; is player confused?
|
|
jr nc, .TriedToUseDisabledMoveCheck
|
|
ld hl, wPlayerConfusedCounter
|
|
dec [hl]
|
|
jr nz, .IsConfused
|
|
ld hl, wPlayerBattleStatus1
|
|
res CONFUSED, [hl] ; if confused counter hit 0, reset confusion status
|
|
ld hl, ConfusedNoMoreText
|
|
call PrintText
|
|
jr .TriedToUseDisabledMoveCheck
|
|
.IsConfused
|
|
ld hl, IsConfusedText
|
|
call PrintText
|
|
xor a
|
|
ld [wAnimationType], a
|
|
ld a, CONF_PLAYER_ANIM
|
|
call PlayMoveAnimation
|
|
call BattleRandom
|
|
cp 50 percent + 1 ; chance to hurt itself
|
|
jr c, .TriedToUseDisabledMoveCheck
|
|
ld hl, wPlayerBattleStatus1
|
|
ld a, [hl]
|
|
and 1 << CONFUSED ; if mon hurts itself, clear every other status from wPlayerBattleStatus1
|
|
ld [hl], a
|
|
call HandleSelfConfusionDamage
|
|
jr .MonHurtItselfOrFullyParalysed
|
|
|
|
.TriedToUseDisabledMoveCheck
|
|
; prevents a disabled move that was selected before being disabled from being used
|
|
ld a, [wPlayerDisabledMoveNumber]
|
|
and a
|
|
jr z, .ParalysisCheck
|
|
ld hl, wPlayerSelectedMove
|
|
cp [hl]
|
|
jr nz, .ParalysisCheck
|
|
call PrintMoveIsDisabledText
|
|
ld hl, ExecutePlayerMoveDone ; if a disabled move was somehow selected, player can't move this turn
|
|
jp .returnToHL
|
|
|
|
.ParalysisCheck
|
|
ld hl, wBattleMonStatus
|
|
bit PAR, [hl]
|
|
jr z, .BideCheck
|
|
call BattleRandom
|
|
cp $3F ; 25% to be fully paralyzed
|
|
jr nc, .BideCheck
|
|
ld hl, FullyParalyzedText
|
|
call PrintText
|
|
|
|
.MonHurtItselfOrFullyParalysed
|
|
ld hl, wPlayerBattleStatus1
|
|
ld a, [hl]
|
|
; clear bide, thrashing, charging up, and trapping moves such as warp (already cleared for confusion damage)
|
|
and ~((1 << STORING_ENERGY) | (1 << THRASHING_ABOUT) | (1 << CHARGING_UP) | (1 << USING_TRAPPING_MOVE))
|
|
ld [hl], a
|
|
ld a, [wPlayerMoveEffect]
|
|
cp FLY_EFFECT
|
|
jr z, .FlyOrChargeEffect
|
|
cp CHARGE_EFFECT
|
|
jr z, .FlyOrChargeEffect
|
|
jr .NotFlyOrChargeEffect
|
|
|
|
.FlyOrChargeEffect
|
|
xor a
|
|
ld [wAnimationType], a
|
|
ld a, STATUS_AFFECTED_ANIM
|
|
call PlayMoveAnimation
|
|
.NotFlyOrChargeEffect
|
|
ld hl, ExecutePlayerMoveDone
|
|
jp .returnToHL ; if using a two-turn move, we need to recharge the first turn
|
|
|
|
.BideCheck
|
|
ld hl, wPlayerBattleStatus1
|
|
bit STORING_ENERGY, [hl] ; is mon using bide?
|
|
jr z, .ThrashingAboutCheck
|
|
xor a
|
|
ld [wPlayerMoveNum], a
|
|
ld hl, wDamage
|
|
ld a, [hli]
|
|
ld b, a
|
|
ld c, [hl]
|
|
ld hl, wPlayerBideAccumulatedDamage + 1
|
|
ld a, [hl]
|
|
add c ; accumulate damage taken
|
|
ld [hld], a
|
|
ld a, [hl]
|
|
adc b
|
|
ld [hl], a
|
|
ld hl, wPlayerNumAttacksLeft
|
|
dec [hl] ; did Bide counter hit 0?
|
|
jr z, .UnleashEnergy
|
|
ld hl, ExecutePlayerMoveDone
|
|
jp .returnToHL ; unless mon unleashes energy, can't move this turn
|
|
.UnleashEnergy
|
|
ld hl, wPlayerBattleStatus1
|
|
res STORING_ENERGY, [hl] ; not using bide any more
|
|
ld hl, UnleashedEnergyText
|
|
call PrintText
|
|
ld a, 1
|
|
ld [wPlayerMovePower], a
|
|
ld hl, wPlayerBideAccumulatedDamage + 1
|
|
ld a, [hld]
|
|
add a
|
|
ld b, a
|
|
ld [wDamage + 1], a
|
|
ld a, [hl]
|
|
rl a ; double the damage
|
|
ld [wDamage], a
|
|
or b
|
|
jr nz, .next
|
|
ld a, 1
|
|
ld [wMoveMissed], a
|
|
.next
|
|
xor a
|
|
ld [hli], a
|
|
ld [hl], a
|
|
ld a, BIDE
|
|
ld [wPlayerMoveNum], a
|
|
ld hl, handleIfPlayerMoveMissed ; skip damage calculation, DecrementPP and MoveHitTest
|
|
jp .returnToHL
|
|
|
|
.ThrashingAboutCheck
|
|
bit THRASHING_ABOUT, [hl] ; is mon using thrash or petal dance?
|
|
jr z, .MultiturnMoveCheck
|
|
ld a, THRASH
|
|
ld [wPlayerMoveNum], a
|
|
ld hl, ThrashingAboutText
|
|
call PrintText
|
|
ld hl, wPlayerNumAttacksLeft
|
|
dec [hl] ; did Thrashing About counter hit 0?
|
|
ld hl, PlayerCalcMoveDamage ; skip DecrementPP
|
|
jp nz, .returnToHL
|
|
push hl
|
|
ld hl, wPlayerBattleStatus1
|
|
res THRASHING_ABOUT, [hl] ; no longer thrashing about
|
|
set CONFUSED, [hl] ; confused
|
|
call BattleRandom
|
|
and 3
|
|
inc a
|
|
inc a ; confused for 2-5 turns
|
|
ld [wPlayerConfusedCounter], a
|
|
pop hl ; skip DecrementPP
|
|
jp .returnToHL
|
|
|
|
.MultiturnMoveCheck
|
|
bit USING_TRAPPING_MOVE, [hl] ; is mon using multi-turn move?
|
|
jp z, .RageCheck
|
|
ld hl, AttackContinuesText
|
|
call PrintText
|
|
ld a, [wPlayerNumAttacksLeft]
|
|
dec a ; did multi-turn move end?
|
|
ld [wPlayerNumAttacksLeft], a
|
|
ld hl, getPlayerAnimationType ; if it didn't, skip damage calculation (deal damage equal to last hit),
|
|
; DecrementPP and MoveHitTest
|
|
jp nz, .returnToHL
|
|
jp .returnToHL
|
|
|
|
.RageCheck
|
|
ld a, [wPlayerBattleStatus2]
|
|
bit USING_RAGE, a ; is mon using rage?
|
|
jp z, .checkPlayerStatusConditionsDone ; if we made it this far, mon can move normally this turn
|
|
ld a, RAGE
|
|
ld [wd11e], a
|
|
call GetMoveName
|
|
call CopyToStringBuffer
|
|
xor a
|
|
ld [wPlayerMoveEffect], a
|
|
ld hl, PlayerCanExecuteMove
|
|
jp .returnToHL
|
|
|
|
.returnToHL
|
|
xor a
|
|
ret
|
|
|
|
.checkPlayerStatusConditionsDone
|
|
ld a, $1
|
|
and a
|
|
ret
|
|
|
|
FastAsleepText:
|
|
text_far _FastAsleepText
|
|
text_end
|
|
|
|
WokeUpText:
|
|
text_far _WokeUpText
|
|
text_end
|
|
|
|
IsFrozenText:
|
|
text_far _IsFrozenText
|
|
text_end
|
|
|
|
FullyParalyzedText:
|
|
text_far _FullyParalyzedText
|
|
text_end
|
|
|
|
FlinchedText:
|
|
text_far _FlinchedText
|
|
text_end
|
|
|
|
MustRechargeText:
|
|
text_far _MustRechargeText
|
|
text_end
|
|
|
|
DisabledNoMoreText:
|
|
text_far _DisabledNoMoreText
|
|
text_end
|
|
|
|
IsConfusedText:
|
|
text_far _IsConfusedText
|
|
text_end
|
|
|
|
HurtItselfText:
|
|
text_far _HurtItselfText
|
|
text_end
|
|
|
|
ConfusedNoMoreText:
|
|
text_far _ConfusedNoMoreText
|
|
text_end
|
|
|
|
SavingEnergyText:
|
|
text_far _SavingEnergyText
|
|
text_end
|
|
|
|
UnleashedEnergyText:
|
|
text_far _UnleashedEnergyText
|
|
text_end
|
|
|
|
ThrashingAboutText:
|
|
text_far _ThrashingAboutText
|
|
text_end
|
|
|
|
AttackContinuesText:
|
|
text_far _AttackContinuesText
|
|
text_end
|
|
|
|
CantMoveText:
|
|
text_far _CantMoveText
|
|
text_end
|
|
|
|
PrintMoveIsDisabledText:
|
|
ld hl, wPlayerSelectedMove
|
|
ld de, wPlayerBattleStatus1
|
|
ldh a, [hWhoseTurn]
|
|
and a
|
|
jr z, .removeChargingUp
|
|
inc hl
|
|
ld de, wEnemyBattleStatus1
|
|
.removeChargingUp
|
|
ld a, [de]
|
|
res CHARGING_UP, a ; end the pokemon's
|
|
ld [de], a
|
|
ld a, [hl]
|
|
ld [wd11e], a
|
|
call GetMoveName
|
|
ld hl, MoveIsDisabledText
|
|
jp PrintText
|
|
|
|
MoveIsDisabledText:
|
|
text_far _MoveIsDisabledText
|
|
text_end
|
|
|
|
HandleSelfConfusionDamage:
|
|
ld hl, HurtItselfText
|
|
call PrintText
|
|
ld hl, wEnemyMonDefense
|
|
ld a, [hli]
|
|
push af
|
|
ld a, [hld]
|
|
push af
|
|
ld a, [wBattleMonDefense]
|
|
ld [hli], a
|
|
ld a, [wBattleMonDefense + 1]
|
|
ld [hl], a
|
|
ld hl, wPlayerMoveEffect
|
|
push hl
|
|
ld a, [hl]
|
|
push af
|
|
xor a
|
|
ld [hli], a
|
|
ld [wCriticalHitOrOHKO], a ; self-inflicted confusion damage can't be a Critical Hit
|
|
ld a, 40 ; 40 base power
|
|
ld [hli], a
|
|
xor a
|
|
ld [hl], a
|
|
call GetDamageVarsForPlayerAttack
|
|
call CalculateDamage ; ignores AdjustDamageForMoveType (type-less damage), RandomizeDamage,
|
|
; and MoveHitTest (always hits)
|
|
pop af
|
|
pop hl
|
|
ld [hl], a
|
|
ld hl, wEnemyMonDefense + 1
|
|
pop af
|
|
ld [hld], a
|
|
pop af
|
|
ld [hl], a
|
|
xor a
|
|
ld [wAnimationType], a
|
|
inc a
|
|
ldh [hWhoseTurn], a
|
|
call PlayMoveAnimation
|
|
call DrawPlayerHUDAndHPBar
|
|
xor a
|
|
ldh [hWhoseTurn], a
|
|
jp ApplyDamageToPlayerPokemon
|
|
|
|
PrintMonName1Text:
|
|
ld hl, MonName1Text
|
|
jp PrintText
|
|
|
|
; this function wastes time calling DetermineExclamationPointTextNum
|
|
; and choosing between Used1Text and Used2Text, even though
|
|
; those text strings are identical and both continue at PrintInsteadText
|
|
; this likely had to do with Japanese grammar that got translated,
|
|
; but the functionality didn't get removed
|
|
MonName1Text:
|
|
text_far _MonName1Text
|
|
text_asm
|
|
ldh a, [hWhoseTurn]
|
|
and a
|
|
ld a, [wPlayerMoveNum]
|
|
ld hl, wPlayerUsedMove
|
|
jr z, .playerTurn
|
|
ld a, [wEnemyMoveNum]
|
|
ld hl, wEnemyUsedMove
|
|
.playerTurn
|
|
ld [hl], a
|
|
ld [wd11e], a
|
|
call DetermineExclamationPointTextNum
|
|
ld a, [wMonIsDisobedient]
|
|
and a
|
|
ld hl, Used2Text
|
|
ret nz
|
|
ld a, [wd11e]
|
|
cp 3
|
|
ld hl, Used2Text
|
|
ret c
|
|
ld hl, Used1Text
|
|
ret
|
|
|
|
Used1Text:
|
|
text_far _Used1Text
|
|
text_asm
|
|
jr PrintInsteadText
|
|
|
|
Used2Text:
|
|
text_far _Used2Text
|
|
text_asm
|
|
; fall through
|
|
|
|
PrintInsteadText:
|
|
ld a, [wMonIsDisobedient]
|
|
and a
|
|
jr z, PrintMoveName
|
|
ld hl, InsteadText
|
|
ret
|
|
|
|
InsteadText:
|
|
text_far _InsteadText
|
|
text_asm
|
|
; fall through
|
|
|
|
PrintMoveName:
|
|
ld hl, _PrintMoveName
|
|
ret
|
|
|
|
_PrintMoveName:
|
|
text_far _MoveNameText
|
|
text_asm
|
|
ld hl, ExclamationPointPointerTable
|
|
ld a, [wd11e] ; exclamation point num
|
|
add a
|
|
push bc
|
|
ld b, $0
|
|
ld c, a
|
|
add hl, bc
|
|
pop bc
|
|
ld a, [hli]
|
|
ld h, [hl]
|
|
ld l, a
|
|
ret
|
|
|
|
ExclamationPointPointerTable:
|
|
dw ExclamationPoint1Text
|
|
dw ExclamationPoint2Text
|
|
dw ExclamationPoint3Text
|
|
dw ExclamationPoint4Text
|
|
dw ExclamationPoint5Text
|
|
|
|
ExclamationPoint1Text:
|
|
text_far _ExclamationPoint1Text
|
|
text_end
|
|
|
|
ExclamationPoint2Text:
|
|
text_far _ExclamationPoint2Text
|
|
text_end
|
|
|
|
ExclamationPoint3Text:
|
|
text_far _ExclamationPoint3Text
|
|
text_end
|
|
|
|
ExclamationPoint4Text:
|
|
text_far _ExclamationPoint4Text
|
|
text_end
|
|
|
|
ExclamationPoint5Text:
|
|
text_far _ExclamationPoint5Text
|
|
text_end
|
|
|
|
; this function does nothing useful
|
|
; if the move being used is in set [1-4] from ExclamationPointMoveSets,
|
|
; use ExclamationPoint[1-4]Text
|
|
; otherwise, use ExclamationPoint5Text
|
|
; but all five text strings are identical
|
|
; this likely had to do with Japanese grammar that got translated,
|
|
; but the functionality didn't get removed
|
|
DetermineExclamationPointTextNum:
|
|
push bc
|
|
ld a, [wd11e] ; move ID
|
|
ld c, a
|
|
ld b, $0
|
|
ld hl, ExclamationPointMoveSets
|
|
.loop
|
|
ld a, [hli]
|
|
cp $ff
|
|
jr z, .done
|
|
cp c
|
|
jr z, .done
|
|
and a
|
|
jr nz, .loop
|
|
inc b
|
|
jr .loop
|
|
.done
|
|
ld a, b
|
|
ld [wd11e], a ; exclamation point num
|
|
pop bc
|
|
ret
|
|
|
|
INCLUDE "data/moves/grammar.asm"
|
|
|
|
PrintMoveFailureText:
|
|
ld de, wPlayerMoveEffect
|
|
ldh a, [hWhoseTurn]
|
|
and a
|
|
jr z, .playersTurn
|
|
ld de, wEnemyMoveEffect
|
|
.playersTurn
|
|
ld hl, DoesntAffectMonText
|
|
ld a, [wDamageMultipliers]
|
|
and $7f
|
|
jr z, .gotTextToPrint
|
|
ld hl, AttackMissedText
|
|
ld a, [wCriticalHitOrOHKO]
|
|
cp $ff
|
|
jr nz, .gotTextToPrint
|
|
ld hl, UnaffectedText
|
|
.gotTextToPrint
|
|
push de
|
|
call PrintText
|
|
xor a
|
|
ld [wCriticalHitOrOHKO], a
|
|
pop de
|
|
ld a, [de]
|
|
cp JUMP_KICK_EFFECT
|
|
ret nz
|
|
|
|
; if you get here, the mon used jump kick or hi jump kick and missed
|
|
ld hl, wDamage ; since the move missed, wDamage will always contain 0 at this point.
|
|
; Thus, recoil damage will always be equal to 1
|
|
; even if it was intended to be potential damage/8.
|
|
ld a, [hli]
|
|
ld b, [hl]
|
|
srl a
|
|
rr b
|
|
srl a
|
|
rr b
|
|
srl a
|
|
rr b
|
|
ld [hl], b
|
|
dec hl
|
|
ld [hli], a
|
|
or b
|
|
jr nz, .applyRecoil
|
|
inc a
|
|
ld [hl], a
|
|
.applyRecoil
|
|
ld hl, KeptGoingAndCrashedText
|
|
call PrintText
|
|
ld b, $4
|
|
predef PredefShakeScreenHorizontally
|
|
ldh a, [hWhoseTurn]
|
|
and a
|
|
jr nz, .enemyTurn
|
|
jp ApplyDamageToPlayerPokemon
|
|
.enemyTurn
|
|
jp ApplyDamageToEnemyPokemon
|
|
|
|
AttackMissedText:
|
|
text_far _AttackMissedText
|
|
text_end
|
|
|
|
KeptGoingAndCrashedText:
|
|
text_far _KeptGoingAndCrashedText
|
|
text_end
|
|
|
|
UnaffectedText:
|
|
text_far _UnaffectedText
|
|
text_end
|
|
|
|
PrintDoesntAffectText:
|
|
ld hl, DoesntAffectMonText
|
|
jp PrintText
|
|
|
|
DoesntAffectMonText:
|
|
text_far _DoesntAffectMonText
|
|
text_end
|
|
|
|
; if there was a critical hit or an OHKO was successful, print the corresponding text
|
|
PrintCriticalOHKOText:
|
|
ld a, [wCriticalHitOrOHKO]
|
|
and a
|
|
jr z, .done ; do nothing if there was no critical hit or successful OHKO
|
|
dec a
|
|
add a
|
|
ld hl, CriticalOHKOTextPointers
|
|
ld b, $0
|
|
ld c, a
|
|
add hl, bc
|
|
ld a, [hli]
|
|
ld h, [hl]
|
|
ld l, a
|
|
call PrintText
|
|
xor a
|
|
ld [wCriticalHitOrOHKO], a
|
|
.done
|
|
ld c, 20
|
|
jp DelayFrames
|
|
|
|
CriticalOHKOTextPointers:
|
|
dw CriticalHitText
|
|
dw OHKOText
|
|
|
|
CriticalHitText:
|
|
text_far _CriticalHitText
|
|
text_end
|
|
|
|
OHKOText:
|
|
text_far _OHKOText
|
|
text_end
|
|
|
|
; checks if a traded mon will disobey due to lack of badges
|
|
; stores whether the mon will use a move in Z flag
|
|
CheckForDisobedience:
|
|
xor a
|
|
ld [wMonIsDisobedient], a
|
|
ld a, [wLinkState]
|
|
cp LINK_STATE_BATTLING
|
|
jr nz, .checkIfMonIsTraded
|
|
ld a, $1
|
|
and a
|
|
ret
|
|
; compare the mon's original trainer ID with the player's ID to see if it was traded
|
|
.checkIfMonIsTraded
|
|
ld hl, wPartyMon1OTID
|
|
ld bc, wPartyMon2 - wPartyMon1
|
|
ld a, [wPlayerMonNumber]
|
|
call AddNTimes
|
|
ld a, [wPlayerID]
|
|
cp [hl]
|
|
jr nz, .monIsTraded
|
|
inc hl
|
|
ld a, [wPlayerID + 1]
|
|
cp [hl]
|
|
jp z, .canUseMove
|
|
; it was traded
|
|
.monIsTraded
|
|
; what level might disobey?
|
|
ld hl, wObtainedBadges
|
|
bit BIT_EARTHBADGE, [hl]
|
|
ld a, 101
|
|
jr nz, .next
|
|
bit BIT_MARSHBADGE, [hl]
|
|
ld a, 70
|
|
jr nz, .next
|
|
bit BIT_RAINBOWBADGE, [hl]
|
|
ld a, 50
|
|
jr nz, .next
|
|
bit BIT_CASCADEBADGE, [hl]
|
|
ld a, 30
|
|
jr nz, .next
|
|
ld a, 10
|
|
.next
|
|
ld b, a
|
|
ld c, a
|
|
ld a, [wBattleMonLevel]
|
|
ld d, a
|
|
add b
|
|
ld b, a
|
|
jr nc, .noCarry
|
|
ld b, $ff ; cap b at $ff
|
|
.noCarry
|
|
ld a, c
|
|
cp d
|
|
jp nc, .canUseMove
|
|
.loop1
|
|
call BattleRandom
|
|
swap a
|
|
cp b
|
|
jr nc, .loop1
|
|
cp c
|
|
jp c, .canUseMove
|
|
.loop2
|
|
call BattleRandom
|
|
cp b
|
|
jr nc, .loop2
|
|
cp c
|
|
jr c, .useRandomMove
|
|
ld a, d
|
|
sub c
|
|
ld b, a
|
|
call BattleRandom
|
|
swap a
|
|
sub b
|
|
jr c, .monNaps
|
|
cp b
|
|
jr nc, .monDoesNothing
|
|
ld hl, WontObeyText
|
|
call PrintText
|
|
call HandleSelfConfusionDamage
|
|
jp .cannotUseMove
|
|
.monNaps
|
|
call BattleRandom
|
|
add a
|
|
swap a
|
|
and SLP_MASK
|
|
jr z, .monNaps ; keep trying until we get at least 1 turn of sleep
|
|
ld [wBattleMonStatus], a
|
|
ld hl, BeganToNapText
|
|
jr .printText
|
|
.monDoesNothing
|
|
call BattleRandom
|
|
and $3
|
|
ld hl, LoafingAroundText
|
|
and a
|
|
jr z, .printText
|
|
ld hl, WontObeyText
|
|
dec a
|
|
jr z, .printText
|
|
ld hl, TurnedAwayText
|
|
dec a
|
|
jr z, .printText
|
|
ld hl, IgnoredOrdersText
|
|
.printText
|
|
call PrintText
|
|
jr .cannotUseMove
|
|
.useRandomMove
|
|
ld a, [wBattleMonMoves + 1]
|
|
and a ; is the second move slot empty?
|
|
jr z, .monDoesNothing ; mon will not use move if it only knows one move
|
|
ld a, [wPlayerDisabledMoveNumber]
|
|
and a
|
|
jr nz, .monDoesNothing
|
|
ld a, [wPlayerSelectedMove]
|
|
cp STRUGGLE
|
|
jr z, .monDoesNothing ; mon will not use move if struggling
|
|
; check if only one move has remaining PP
|
|
ld hl, wBattleMonPP
|
|
push hl
|
|
ld a, [hli]
|
|
and $3f
|
|
ld b, a
|
|
ld a, [hli]
|
|
and $3f
|
|
add b
|
|
ld b, a
|
|
ld a, [hli]
|
|
and $3f
|
|
add b
|
|
ld b, a
|
|
ld a, [hl]
|
|
and $3f
|
|
add b
|
|
pop hl
|
|
push af
|
|
ld a, [wCurrentMenuItem]
|
|
ld c, a
|
|
ld b, $0
|
|
add hl, bc
|
|
ld a, [hl]
|
|
and $3f
|
|
ld b, a
|
|
pop af
|
|
cp b
|
|
jr z, .monDoesNothing ; mon will not use move if only one move has remaining PP
|
|
ld a, $1
|
|
ld [wMonIsDisobedient], a
|
|
ld a, [wMaxMenuItem]
|
|
ld b, a
|
|
ld a, [wCurrentMenuItem]
|
|
ld c, a
|
|
.chooseMove
|
|
call BattleRandom
|
|
and $3
|
|
cp b
|
|
jr nc, .chooseMove ; if the random number is greater than the move count, choose another
|
|
cp c
|
|
jr z, .chooseMove ; if the random number matches the move the player selected, choose another
|
|
ld [wCurrentMenuItem], a
|
|
ld hl, wBattleMonPP
|
|
ld e, a
|
|
ld d, $0
|
|
add hl, de
|
|
ld a, [hl]
|
|
and a ; does the move have any PP left?
|
|
jr z, .chooseMove ; if the move has no PP left, choose another
|
|
ld a, [wCurrentMenuItem]
|
|
ld c, a
|
|
ld b, $0
|
|
ld hl, wBattleMonMoves
|
|
add hl, bc
|
|
ld a, [hl]
|
|
ld [wPlayerSelectedMove], a
|
|
call GetCurrentMove
|
|
.canUseMove
|
|
ld a, $1
|
|
and a ; clear Z flag
|
|
ret
|
|
.cannotUseMove
|
|
xor a ; set Z flag
|
|
ret
|
|
|
|
LoafingAroundText:
|
|
text_far _LoafingAroundText
|
|
text_end
|
|
|
|
BeganToNapText:
|
|
text_far _BeganToNapText
|
|
text_end
|
|
|
|
WontObeyText:
|
|
text_far _WontObeyText
|
|
text_end
|
|
|
|
TurnedAwayText:
|
|
text_far _TurnedAwayText
|
|
text_end
|
|
|
|
IgnoredOrdersText:
|
|
text_far _IgnoredOrdersText
|
|
text_end
|
|
|
|
; sets b, c, d, and e for the CalculateDamage routine in the case of an attack by the player mon
|
|
GetDamageVarsForPlayerAttack:
|
|
xor a
|
|
ld hl, wDamage ; damage to eventually inflict, initialise to zero
|
|
ldi [hl], a
|
|
ld [hl], a
|
|
ld hl, wPlayerMovePower
|
|
ld a, [hli]
|
|
and a
|
|
ld d, a ; d = move power
|
|
ret z ; return if move power is zero
|
|
ld a, [hl] ; a = [wPlayerMoveType]
|
|
cp SPECIAL ; types >= SPECIAL are all special
|
|
jr nc, .specialAttack
|
|
.physicalAttack
|
|
ld hl, wEnemyMonDefense
|
|
ld a, [hli]
|
|
ld b, a
|
|
ld c, [hl] ; bc = enemy defense
|
|
ld a, [wEnemyBattleStatus3]
|
|
bit HAS_REFLECT_UP, a ; check for Reflect
|
|
jr z, .physicalAttackCritCheck
|
|
; if the enemy has used Reflect, double the enemy's defense
|
|
sla c
|
|
rl b
|
|
.physicalAttackCritCheck
|
|
ld hl, wBattleMonAttack
|
|
ld a, [wCriticalHitOrOHKO]
|
|
and a ; check for critical hit
|
|
jr z, .scaleStats
|
|
; in the case of a critical hit, reset the player's attack and the enemy's defense to their base values
|
|
ld c, 3 ; defense stat
|
|
call GetEnemyMonStat
|
|
ldh a, [hProduct + 2]
|
|
ld b, a
|
|
ldh a, [hProduct + 3]
|
|
ld c, a
|
|
push bc
|
|
ld hl, wPartyMon1Attack
|
|
ld a, [wPlayerMonNumber]
|
|
ld bc, wPartyMon2 - wPartyMon1
|
|
call AddNTimes
|
|
pop bc
|
|
jr .scaleStats
|
|
.specialAttack
|
|
ld hl, wEnemyMonSpecial
|
|
ld a, [hli]
|
|
ld b, a
|
|
ld c, [hl] ; bc = enemy special
|
|
ld a, [wEnemyBattleStatus3]
|
|
bit HAS_LIGHT_SCREEN_UP, a ; check for Light Screen
|
|
jr z, .specialAttackCritCheck
|
|
; if the enemy has used Light Screen, double the enemy's special
|
|
sla c
|
|
rl b
|
|
; reflect and light screen boosts do not cap the stat at MAX_STAT_VALUE, so weird things will happen during stats scaling
|
|
; if a Pokemon with 512 or more Defense has used Reflect, or if a Pokemon with 512 or more Special has used Light Screen
|
|
.specialAttackCritCheck
|
|
ld hl, wBattleMonSpecial
|
|
ld a, [wCriticalHitOrOHKO]
|
|
and a ; check for critical hit
|
|
jr z, .scaleStats
|
|
; in the case of a critical hit, reset the player's and enemy's specials to their base values
|
|
ld c, 5 ; special stat
|
|
call GetEnemyMonStat
|
|
ldh a, [hProduct + 2]
|
|
ld b, a
|
|
ldh a, [hProduct + 3]
|
|
ld c, a
|
|
push bc
|
|
ld hl, wPartyMon1Special
|
|
ld a, [wPlayerMonNumber]
|
|
ld bc, wPartyMon2 - wPartyMon1
|
|
call AddNTimes
|
|
pop bc
|
|
; if either the offensive or defensive stat is too large to store in a byte, scale both stats by dividing them by 4
|
|
; this allows values with up to 10 bits (values up to 1023) to be handled
|
|
; anything larger will wrap around
|
|
.scaleStats
|
|
ld a, [hli]
|
|
ld l, [hl]
|
|
ld h, a ; hl = player's offensive stat
|
|
or b ; is either high byte nonzero?
|
|
jr z, .next ; if not, we don't need to scale
|
|
; bc /= 4 (scale enemy's defensive stat)
|
|
srl b
|
|
rr c
|
|
srl b
|
|
rr c
|
|
; defensive stat can actually end up as 0, leading to a division by 0 freeze during damage calculation
|
|
; hl /= 4 (scale player's offensive stat)
|
|
srl h
|
|
rr l
|
|
srl h
|
|
rr l
|
|
ld a, l
|
|
or h ; is the player's offensive stat 0?
|
|
jr nz, .next
|
|
inc l ; if the player's offensive stat is 0, bump it up to 1
|
|
.next
|
|
ld b, l ; b = player's offensive stat (possibly scaled)
|
|
; (c already contains enemy's defensive stat (possibly scaled))
|
|
ld a, [wBattleMonLevel]
|
|
ld e, a ; e = level
|
|
ld a, [wCriticalHitOrOHKO]
|
|
and a ; check for critical hit
|
|
jr z, .done
|
|
sla e ; double level if it was a critical hit
|
|
.done
|
|
ld a, 1
|
|
and a
|
|
ret
|
|
|
|
; sets b, c, d, and e for the CalculateDamage routine in the case of an attack by the enemy mon
|
|
GetDamageVarsForEnemyAttack:
|
|
ld hl, wDamage ; damage to eventually inflict, initialise to zero
|
|
xor a
|
|
ld [hli], a
|
|
ld [hl], a
|
|
ld hl, wEnemyMovePower
|
|
ld a, [hli]
|
|
ld d, a ; d = move power
|
|
and a
|
|
ret z ; return if move power is zero
|
|
ld a, [hl] ; a = [wEnemyMoveType]
|
|
cp SPECIAL ; types >= SPECIAL are all special
|
|
jr nc, .specialAttack
|
|
.physicalAttack
|
|
ld hl, wBattleMonDefense
|
|
ld a, [hli]
|
|
ld b, a
|
|
ld c, [hl] ; bc = player defense
|
|
ld a, [wPlayerBattleStatus3]
|
|
bit HAS_REFLECT_UP, a ; check for Reflect
|
|
jr z, .physicalAttackCritCheck
|
|
; if the player has used Reflect, double the player's defense
|
|
sla c
|
|
rl b
|
|
.physicalAttackCritCheck
|
|
ld hl, wEnemyMonAttack
|
|
ld a, [wCriticalHitOrOHKO]
|
|
and a ; check for critical hit
|
|
jr z, .scaleStats
|
|
; in the case of a critical hit, reset the player's defense and the enemy's attack to their base values
|
|
ld hl, wPartyMon1Defense
|
|
ld a, [wPlayerMonNumber]
|
|
ld bc, wPartyMon2 - wPartyMon1
|
|
call AddNTimes
|
|
ld a, [hli]
|
|
ld b, a
|
|
ld c, [hl]
|
|
push bc
|
|
ld c, 2 ; attack stat
|
|
call GetEnemyMonStat
|
|
ld hl, hProduct + 2
|
|
pop bc
|
|
jr .scaleStats
|
|
.specialAttack
|
|
ld hl, wBattleMonSpecial
|
|
ld a, [hli]
|
|
ld b, a
|
|
ld c, [hl]
|
|
ld a, [wPlayerBattleStatus3]
|
|
bit HAS_LIGHT_SCREEN_UP, a ; check for Light Screen
|
|
jr z, .specialAttackCritCheck
|
|
; if the player has used Light Screen, double the player's special
|
|
sla c
|
|
rl b
|
|
; reflect and light screen boosts do not cap the stat at MAX_STAT_VALUE, so weird things will happen during stats scaling
|
|
; if a Pokemon with 512 or more Defense has used Reflect, or if a Pokemon with 512 or more Special has used Light Screen
|
|
.specialAttackCritCheck
|
|
ld hl, wEnemyMonSpecial
|
|
ld a, [wCriticalHitOrOHKO]
|
|
and a ; check for critical hit
|
|
jr z, .scaleStats
|
|
; in the case of a critical hit, reset the player's and enemy's specials to their base values
|
|
ld hl, wPartyMon1Special
|
|
ld a, [wPlayerMonNumber]
|
|
ld bc, wPartyMon2 - wPartyMon1
|
|
call AddNTimes
|
|
ld a, [hli]
|
|
ld b, a
|
|
ld c, [hl]
|
|
push bc
|
|
ld c, 5 ; special stat
|
|
call GetEnemyMonStat
|
|
ld hl, hProduct + 2
|
|
pop bc
|
|
; if either the offensive or defensive stat is too large to store in a byte, scale both stats by dividing them by 4
|
|
; this allows values with up to 10 bits (values up to 1023) to be handled
|
|
; anything larger will wrap around
|
|
.scaleStats
|
|
ld a, [hli]
|
|
ld l, [hl]
|
|
ld h, a ; hl = enemy's offensive stat
|
|
or b ; is either high byte nonzero?
|
|
jr z, .next ; if not, we don't need to scale
|
|
; bc /= 4 (scale player's defensive stat)
|
|
srl b
|
|
rr c
|
|
srl b
|
|
rr c
|
|
; defensive stat can actually end up as 0, leading to a division by 0 freeze during damage calculation
|
|
; hl /= 4 (scale enemy's offensive stat)
|
|
srl h
|
|
rr l
|
|
srl h
|
|
rr l
|
|
ld a, l
|
|
or h ; is the enemy's offensive stat 0?
|
|
jr nz, .next
|
|
inc l ; if the enemy's offensive stat is 0, bump it up to 1
|
|
.next
|
|
ld b, l ; b = enemy's offensive stat (possibly scaled)
|
|
; (c already contains player's defensive stat (possibly scaled))
|
|
ld a, [wEnemyMonLevel]
|
|
ld e, a
|
|
ld a, [wCriticalHitOrOHKO]
|
|
and a ; check for critical hit
|
|
jr z, .done
|
|
sla e ; double level if it was a critical hit
|
|
.done
|
|
ld a, $1
|
|
and a
|
|
and a
|
|
ret
|
|
|
|
; get stat c of enemy mon
|
|
; c: stat to get (HP=1,Attack=2,Defense=3,Speed=4,Special=5)
|
|
GetEnemyMonStat:
|
|
push de
|
|
push bc
|
|
ld a, [wLinkState]
|
|
cp LINK_STATE_BATTLING
|
|
jr nz, .notLinkBattle
|
|
ld hl, wEnemyMon1Stats
|
|
dec c
|
|
sla c
|
|
ld b, $0
|
|
add hl, bc
|
|
ld a, [wEnemyMonPartyPos]
|
|
ld bc, wEnemyMon2 - wEnemyMon1
|
|
call AddNTimes
|
|
ld a, [hli]
|
|
ldh [hMultiplicand + 1], a
|
|
ld a, [hl]
|
|
ldh [hMultiplicand + 2], a
|
|
pop bc
|
|
pop de
|
|
ret
|
|
.notLinkBattle
|
|
ld a, [wEnemyMonLevel]
|
|
ld [wCurEnemyLVL], a
|
|
ld a, [wEnemyMonSpecies]
|
|
ld [wd0b5], a
|
|
call GetMonHeader
|
|
ld hl, wEnemyMonDVs
|
|
ld de, wLoadedMonSpeedExp
|
|
ld a, [hli]
|
|
ld [de], a
|
|
inc de
|
|
ld a, [hl]
|
|
ld [de], a
|
|
pop bc
|
|
ld b, $0
|
|
ld hl, wLoadedMonSpeedExp - $b ; this base address makes CalcStat look in [wLoadedMonSpeedExp] for DVs
|
|
call CalcStat
|
|
pop de
|
|
ret
|
|
|
|
CalculateDamage:
|
|
; input:
|
|
; b: attack
|
|
; c: opponent defense
|
|
; d: base power
|
|
; e: level
|
|
|
|
ldh a, [hWhoseTurn] ; whose turn?
|
|
and a
|
|
ld a, [wPlayerMoveEffect]
|
|
jr z, .effect
|
|
ld a, [wEnemyMoveEffect]
|
|
.effect
|
|
|
|
; EXPLODE_EFFECT halves defense.
|
|
cp EXPLODE_EFFECT
|
|
jr nz, .ok
|
|
srl c
|
|
jr nz, .ok
|
|
inc c ; ...with a minimum value of 1 (used as a divisor later on)
|
|
.ok
|
|
|
|
; Multi-hit attacks may or may not have 0 bp.
|
|
cp TWO_TO_FIVE_ATTACKS_EFFECT
|
|
jr z, .skipbp
|
|
cp $1e
|
|
jr z, .skipbp
|
|
|
|
; Calculate OHKO damage based on remaining HP.
|
|
cp OHKO_EFFECT
|
|
jp z, JumpToOHKOMoveEffect
|
|
|
|
; Don't calculate damage for moves that don't do any.
|
|
ld a, d ; base power
|
|
and a
|
|
ret z
|
|
.skipbp
|
|
|
|
xor a
|
|
ld hl, hDividend
|
|
ldi [hl], a
|
|
ldi [hl], a
|
|
ld [hl], a
|
|
|
|
; Multiply level by 2
|
|
ld a, e ; level
|
|
add a
|
|
jr nc, .nc
|
|
push af
|
|
ld a, 1
|
|
ld [hl], a
|
|
pop af
|
|
.nc
|
|
inc hl
|
|
ldi [hl], a
|
|
|
|
; Divide by 5
|
|
ld a, 5
|
|
ldd [hl], a
|
|
push bc
|
|
ld b, 4
|
|
call Divide
|
|
pop bc
|
|
|
|
; Add 2
|
|
inc [hl]
|
|
inc [hl]
|
|
|
|
inc hl ; multiplier
|
|
|
|
; Multiply by attack base power
|
|
ld [hl], d
|
|
call Multiply
|
|
|
|
; Multiply by attack stat
|
|
ld [hl], b
|
|
call Multiply
|
|
|
|
; Divide by defender's defense stat
|
|
ld [hl], c
|
|
ld b, 4
|
|
call Divide
|
|
|
|
; Divide by 50
|
|
ld [hl], 50
|
|
ld b, 4
|
|
call Divide
|
|
|
|
; Update wCurDamage.
|
|
; Capped at MAX_NEUTRAL_DAMAGE - MIN_NEUTRAL_DAMAGE: 999 - 2 = 997.
|
|
ld hl, wDamage
|
|
ld b, [hl]
|
|
ldh a, [hQuotient + 3]
|
|
add b
|
|
ldh [hQuotient + 3], a
|
|
jr nc, .dont_cap_1
|
|
|
|
ldh a, [hQuotient + 2]
|
|
inc a
|
|
ldh [hQuotient + 2], a
|
|
and a
|
|
jr z, .cap
|
|
|
|
.dont_cap_1
|
|
ldh a, [hQuotient]
|
|
ld b, a
|
|
ldh a, [hQuotient + 1]
|
|
or a
|
|
jr nz, .cap
|
|
|
|
ldh a, [hQuotient + 2]
|
|
cp HIGH(MAX_NEUTRAL_DAMAGE - MIN_NEUTRAL_DAMAGE + 1)
|
|
jr c, .dont_cap_2
|
|
|
|
cp HIGH(MAX_NEUTRAL_DAMAGE - MIN_NEUTRAL_DAMAGE + 1) + 1
|
|
jr nc, .cap
|
|
|
|
ldh a, [hQuotient + 3]
|
|
cp LOW(MAX_NEUTRAL_DAMAGE - MIN_NEUTRAL_DAMAGE + 1)
|
|
jr nc, .cap
|
|
|
|
.dont_cap_2
|
|
inc hl
|
|
|
|
ldh a, [hQuotient + 3]
|
|
ld b, [hl]
|
|
add b
|
|
ld [hld], a
|
|
|
|
ldh a, [hQuotient + 2]
|
|
ld b, [hl]
|
|
adc b
|
|
ld [hl], a
|
|
jr c, .cap
|
|
|
|
ld a, [hl]
|
|
cp HIGH(MAX_NEUTRAL_DAMAGE - MIN_NEUTRAL_DAMAGE + 1)
|
|
jr c, .dont_cap_3
|
|
|
|
cp HIGH(MAX_NEUTRAL_DAMAGE - MIN_NEUTRAL_DAMAGE + 1) + 1
|
|
jr nc, .cap
|
|
|
|
inc hl
|
|
ld a, [hld]
|
|
cp LOW(MAX_NEUTRAL_DAMAGE - MIN_NEUTRAL_DAMAGE + 1)
|
|
jr c, .dont_cap_3
|
|
|
|
.cap
|
|
ld a, HIGH(MAX_NEUTRAL_DAMAGE - MIN_NEUTRAL_DAMAGE)
|
|
ld [hli], a
|
|
ld a, LOW(MAX_NEUTRAL_DAMAGE - MIN_NEUTRAL_DAMAGE)
|
|
ld [hld], a
|
|
|
|
.dont_cap_3
|
|
; Add back MIN_NEUTRAL_DAMAGE (capping at 999).
|
|
inc hl
|
|
ld a, [hl]
|
|
add MIN_NEUTRAL_DAMAGE
|
|
ld [hld], a
|
|
jr nc, .dont_floor
|
|
inc [hl]
|
|
.dont_floor
|
|
|
|
; Returns nz and nc.
|
|
ld a, 1
|
|
and a
|
|
ret
|
|
|
|
JumpToOHKOMoveEffect:
|
|
call JumpMoveEffect
|
|
ld a, [wMoveMissed]
|
|
dec a
|
|
ret
|
|
|
|
INCLUDE "data/battle/unused_critical_hit_moves.asm"
|
|
|
|
; determines if attack is a critical hit
|
|
; Azure Heights claims "the fastest pokémon (who are, not coincidentally,
|
|
; among the most popular) tend to CH about 20 to 25% of the time."
|
|
CriticalHitTest:
|
|
xor a
|
|
ld [wCriticalHitOrOHKO], a
|
|
ldh a, [hWhoseTurn]
|
|
and a
|
|
ld a, [wEnemyMonSpecies]
|
|
jr nz, .handleEnemy
|
|
ld a, [wBattleMonSpecies]
|
|
.handleEnemy
|
|
ld [wd0b5], a
|
|
call GetMonHeader
|
|
ld a, [wMonHBaseSpeed]
|
|
ld b, a
|
|
srl b ; (effective (base speed/2))
|
|
ldh a, [hWhoseTurn]
|
|
and a
|
|
ld hl, wPlayerMovePower
|
|
ld de, wPlayerBattleStatus2
|
|
jr z, .calcCriticalHitProbability
|
|
ld hl, wEnemyMovePower
|
|
ld de, wEnemyBattleStatus2
|
|
.calcCriticalHitProbability
|
|
ld a, [hld] ; read base power from RAM
|
|
and a
|
|
ret z ; do nothing if zero
|
|
dec hl
|
|
ld c, [hl] ; read move id
|
|
ld a, [de]
|
|
bit GETTING_PUMPED, a ; test for focus energy
|
|
jr nz, .focusEnergyUsed ; bug: using focus energy causes a shift to the right instead of left,
|
|
; resulting in 1/4 the usual crit chance
|
|
sla b ; (effective (base speed/2)*2)
|
|
jr nc, .noFocusEnergyUsed
|
|
ld b, $ff ; cap at 255/256
|
|
jr .noFocusEnergyUsed
|
|
.focusEnergyUsed
|
|
srl b
|
|
.noFocusEnergyUsed
|
|
ld hl, HighCriticalMoves ; table of high critical hit moves
|
|
.Loop
|
|
ld a, [hli] ; read move from move table
|
|
cp c ; does it match the move about to be used?
|
|
jr z, .HighCritical ; if so, the move about to be used is a high critical hit ratio move
|
|
inc a ; move on to the next move, FF terminates loop
|
|
jr nz, .Loop ; check the next move in HighCriticalMoves
|
|
srl b ; /2 for regular move (effective (base speed / 2))
|
|
jr .SkipHighCritical ; continue as a normal move
|
|
.HighCritical
|
|
sla b ; *2 for high critical hit moves
|
|
jr nc, .noCarry
|
|
ld b, $ff ; cap at 255/256
|
|
.noCarry
|
|
sla b ; *4 for high critical move (effective (base speed/2)*8))
|
|
jr nc, .SkipHighCritical
|
|
ld b, $ff
|
|
.SkipHighCritical
|
|
call BattleRandom ; generates a random value, in "a"
|
|
rlc a
|
|
rlc a
|
|
rlc a
|
|
cp b ; check a against calculated crit rate
|
|
ret nc ; no critical hit if no borrow
|
|
ld a, $1
|
|
ld [wCriticalHitOrOHKO], a ; set critical hit flag
|
|
ret
|
|
|
|
INCLUDE "data/battle/critical_hit_moves.asm"
|
|
|
|
; function to determine if Counter hits and if so, how much damage it does
|
|
HandleCounterMove:
|
|
; The variables checked by Counter are updated whenever the cursor points to a new move in the battle selection menu.
|
|
; This is irrelevant for the opponent's side outside of link battles, since the move selection is controlled by the AI.
|
|
; However, in the scenario where the player switches out and the opponent uses Counter,
|
|
; the outcome may be affected by the player's actions in the move selection menu prior to switching the Pokemon.
|
|
; This might also lead to desync glitches in link battles.
|
|
|
|
ldh a, [hWhoseTurn] ; whose turn
|
|
and a
|
|
; player's turn
|
|
ld hl, wEnemySelectedMove
|
|
ld de, wEnemyMovePower
|
|
ld a, [wPlayerSelectedMove]
|
|
jr z, .next
|
|
; enemy's turn
|
|
ld hl, wPlayerSelectedMove
|
|
ld de, wPlayerMovePower
|
|
ld a, [wEnemySelectedMove]
|
|
.next
|
|
cp COUNTER
|
|
ret nz ; return if not using Counter
|
|
ld a, $01
|
|
ld [wMoveMissed], a ; initialize the move missed variable to true (it is set to false below if the move hits)
|
|
ld a, [hl]
|
|
cp COUNTER
|
|
ret z ; miss if the opponent's last selected move is Counter.
|
|
ld a, [de]
|
|
and a
|
|
ret z ; miss if the opponent's last selected move's Base Power is 0.
|
|
; check if the move the target last selected was Normal or Fighting type
|
|
inc de
|
|
ld a, [de]
|
|
and a ; normal type
|
|
jr z, .counterableType
|
|
cp FIGHTING
|
|
jr z, .counterableType
|
|
; if the move wasn't Normal or Fighting type, miss
|
|
xor a
|
|
ret
|
|
.counterableType
|
|
ld hl, wDamage
|
|
ld a, [hli]
|
|
or [hl]
|
|
ret z ; If we made it here, Counter still misses if the last move used in battle did no damage to its target.
|
|
; wDamage is shared by both players, so Counter may strike back damage dealt by the Counter user itself
|
|
; if the conditions meet, even though 99% of the times damage will come from the target.
|
|
; if it did damage, double it
|
|
ld a, [hl]
|
|
add a
|
|
ldd [hl], a
|
|
ld a, [hl]
|
|
adc a
|
|
ld [hl], a
|
|
jr nc, .noCarry
|
|
; damage is capped at 0xFFFF
|
|
ld a, $ff
|
|
ld [hli], a
|
|
ld [hl], a
|
|
.noCarry
|
|
xor a
|
|
ld [wMoveMissed], a
|
|
call MoveHitTest ; do the normal move hit test in addition to Counter's special rules
|
|
xor a
|
|
ret
|
|
|
|
ApplyAttackToEnemyPokemon:
|
|
ld a, [wPlayerMoveEffect]
|
|
cp OHKO_EFFECT
|
|
jr z, ApplyDamageToEnemyPokemon
|
|
cp SUPER_FANG_EFFECT
|
|
jr z, .superFangEffect
|
|
cp SPECIAL_DAMAGE_EFFECT
|
|
jr z, .specialDamage
|
|
ld a, [wPlayerMovePower]
|
|
and a
|
|
jp z, ApplyAttackToEnemyPokemonDone ; no attack to apply if base power is 0
|
|
jr ApplyDamageToEnemyPokemon
|
|
.superFangEffect
|
|
; set the damage to half the target's HP
|
|
ld hl, wEnemyMonHP
|
|
ld de, wDamage
|
|
ld a, [hli]
|
|
srl a
|
|
ld [de], a
|
|
inc de
|
|
ld b, a
|
|
ld a, [hl]
|
|
rr a
|
|
ld [de], a
|
|
or b
|
|
jr nz, ApplyDamageToEnemyPokemon
|
|
; make sure Super Fang's damage is always at least 1
|
|
ld a, $01
|
|
ld [de], a
|
|
jr ApplyDamageToEnemyPokemon
|
|
.specialDamage
|
|
ld hl, wBattleMonLevel
|
|
ld a, [hl]
|
|
ld b, a ; Seismic Toss deals damage equal to the user's level
|
|
ld a, [wPlayerMoveNum]
|
|
cp SEISMIC_TOSS
|
|
jr z, .storeDamage
|
|
cp NIGHT_SHADE
|
|
jr z, .storeDamage
|
|
ld b, SONICBOOM_DAMAGE ; 20
|
|
cp SONICBOOM
|
|
jr z, .storeDamage
|
|
ld b, DRAGON_RAGE_DAMAGE ; 40
|
|
cp DRAGON_RAGE
|
|
jr z, .storeDamage
|
|
; Psywave
|
|
ld a, [hl]
|
|
ld b, a
|
|
srl a
|
|
add b
|
|
ld b, a ; b = level * 1.5
|
|
; loop until a random number in the range [1, b) is found
|
|
.loop
|
|
call BattleRandom
|
|
and a
|
|
jr z, .loop
|
|
cp b
|
|
jr nc, .loop
|
|
ld b, a
|
|
.storeDamage ; store damage value at b
|
|
ld hl, wDamage
|
|
xor a
|
|
ld [hli], a
|
|
ld a, b
|
|
ld [hl], a
|
|
|
|
ApplyDamageToEnemyPokemon:
|
|
ld hl, wDamage
|
|
ld a, [hli]
|
|
ld b, a
|
|
ld a, [hl]
|
|
or b
|
|
jr z, ApplyAttackToEnemyPokemonDone ; we're done if damage is 0
|
|
ld a, [wEnemyBattleStatus2]
|
|
bit HAS_SUBSTITUTE_UP, a ; does the enemy have a substitute?
|
|
jp nz, AttackSubstitute
|
|
; subtract the damage from the pokemon's current HP
|
|
; also, save the current HP at wHPBarOldHP
|
|
ld a, [hld]
|
|
ld b, a
|
|
ld a, [wEnemyMonHP + 1]
|
|
ld [wHPBarOldHP], a
|
|
sub b
|
|
ld [wEnemyMonHP + 1], a
|
|
ld a, [hl]
|
|
ld b, a
|
|
ld a, [wEnemyMonHP]
|
|
ld [wHPBarOldHP+1], a
|
|
sbc b
|
|
ld [wEnemyMonHP], a
|
|
jr nc, .animateHpBar
|
|
; if more damage was done than the current HP, zero the HP and set the damage (wDamage)
|
|
; equal to how much HP the pokemon had before the attack
|
|
ld a, [wHPBarOldHP+1]
|
|
ld [hli], a
|
|
ld a, [wHPBarOldHP]
|
|
ld [hl], a
|
|
xor a
|
|
ld hl, wEnemyMonHP
|
|
ld [hli], a
|
|
ld [hl], a
|
|
.animateHpBar
|
|
ld hl, wEnemyMonMaxHP
|
|
ld a, [hli]
|
|
ld [wHPBarMaxHP+1], a
|
|
ld a, [hl]
|
|
ld [wHPBarMaxHP], a
|
|
ld hl, wEnemyMonHP
|
|
ld a, [hli]
|
|
ld [wHPBarNewHP+1], a
|
|
ld a, [hl]
|
|
ld [wHPBarNewHP], a
|
|
hlcoord 2, 2
|
|
xor a
|
|
ld [wHPBarType], a
|
|
predef UpdateHPBar2 ; animate the HP bar shortening
|
|
ApplyAttackToEnemyPokemonDone:
|
|
jp DrawHUDsAndHPBars
|
|
|
|
ApplyAttackToPlayerPokemon:
|
|
ld a, [wEnemyMoveEffect]
|
|
cp OHKO_EFFECT
|
|
jr z, ApplyDamageToPlayerPokemon
|
|
cp SUPER_FANG_EFFECT
|
|
jr z, .superFangEffect
|
|
cp SPECIAL_DAMAGE_EFFECT
|
|
jr z, .specialDamage
|
|
ld a, [wEnemyMovePower]
|
|
and a
|
|
jp z, ApplyAttackToPlayerPokemonDone
|
|
jr ApplyDamageToPlayerPokemon
|
|
.superFangEffect
|
|
; set the damage to half the target's HP
|
|
ld hl, wBattleMonHP
|
|
ld de, wDamage
|
|
ld a, [hli]
|
|
srl a
|
|
ld [de], a
|
|
inc de
|
|
ld b, a
|
|
ld a, [hl]
|
|
rr a
|
|
ld [de], a
|
|
or b
|
|
jr nz, ApplyDamageToPlayerPokemon
|
|
; make sure Super Fang's damage is always at least 1
|
|
ld a, $01
|
|
ld [de], a
|
|
jr ApplyDamageToPlayerPokemon
|
|
.specialDamage
|
|
ld hl, wEnemyMonLevel
|
|
ld a, [hl]
|
|
ld b, a
|
|
ld a, [wEnemyMoveNum]
|
|
cp SEISMIC_TOSS
|
|
jr z, .storeDamage
|
|
cp NIGHT_SHADE
|
|
jr z, .storeDamage
|
|
ld b, SONICBOOM_DAMAGE
|
|
cp SONICBOOM
|
|
jr z, .storeDamage
|
|
ld b, DRAGON_RAGE_DAMAGE
|
|
cp DRAGON_RAGE
|
|
jr z, .storeDamage
|
|
; Psywave
|
|
ld a, [hl]
|
|
ld b, a
|
|
srl a
|
|
add b
|
|
ld b, a ; b = attacker's level * 1.5
|
|
; loop until a random number in the range [0, b) is found
|
|
; this differs from the range when the player attacks, which is [1, b)
|
|
; it's possible for the enemy to do 0 damage with Psywave, but the player always does at least 1 damage
|
|
.loop
|
|
call BattleRandom
|
|
cp b
|
|
jr nc, .loop
|
|
ld b, a
|
|
.storeDamage
|
|
ld hl, wDamage
|
|
xor a
|
|
ld [hli], a
|
|
ld a, b
|
|
ld [hl], a
|
|
|
|
ApplyDamageToPlayerPokemon:
|
|
ld hl, wDamage
|
|
ld a, [hli]
|
|
ld b, a
|
|
ld a, [hl]
|
|
or b
|
|
jr z, ApplyAttackToPlayerPokemonDone ; we're done if damage is 0
|
|
ld a, [wPlayerBattleStatus2]
|
|
bit HAS_SUBSTITUTE_UP, a ; does the player have a substitute?
|
|
jp nz, AttackSubstitute
|
|
; subtract the damage from the pokemon's current HP
|
|
; also, save the current HP at wHPBarOldHP and the new HP at wHPBarNewHP
|
|
ld a, [hld]
|
|
ld b, a
|
|
ld a, [wBattleMonHP + 1]
|
|
ld [wHPBarOldHP], a
|
|
sub b
|
|
ld [wBattleMonHP + 1], a
|
|
ld [wHPBarNewHP], a
|
|
ld b, [hl]
|
|
ld a, [wBattleMonHP]
|
|
ld [wHPBarOldHP+1], a
|
|
sbc b
|
|
ld [wBattleMonHP], a
|
|
ld [wHPBarNewHP+1], a
|
|
jr nc, .animateHpBar
|
|
; if more damage was done than the current HP, zero the HP and set the damage (wDamage)
|
|
; equal to how much HP the pokemon had before the attack
|
|
ld a, [wHPBarOldHP+1]
|
|
ld [hli], a
|
|
ld a, [wHPBarOldHP]
|
|
ld [hl], a
|
|
xor a
|
|
ld hl, wBattleMonHP
|
|
ld [hli], a
|
|
ld [hl], a
|
|
ld hl, wHPBarNewHP
|
|
ld [hli], a
|
|
ld [hl], a
|
|
.animateHpBar
|
|
ld hl, wBattleMonMaxHP
|
|
ld a, [hli]
|
|
ld [wHPBarMaxHP+1], a
|
|
ld a, [hl]
|
|
ld [wHPBarMaxHP], a
|
|
hlcoord 10, 9
|
|
ld a, $01
|
|
ld [wHPBarType], a
|
|
predef UpdateHPBar2 ; animate the HP bar shortening
|
|
ApplyAttackToPlayerPokemonDone:
|
|
jp DrawHUDsAndHPBars
|
|
|
|
AttackSubstitute:
|
|
; Unlike the two ApplyAttackToPokemon functions, Attack Substitute is shared by player and enemy.
|
|
; Self-confusion damage as well as Hi-Jump Kick and Jump Kick recoil cause a momentary turn swap before being applied.
|
|
; If the user has a Substitute up and would take damage because of that,
|
|
; damage will be applied to the other player's Substitute.
|
|
; Normal recoil such as from Double-Edge isn't affected by this glitch,
|
|
; because this function is never called in that case.
|
|
|
|
ld hl, SubstituteTookDamageText
|
|
call PrintText
|
|
; values for player turn
|
|
ld de, wEnemySubstituteHP
|
|
ld bc, wEnemyBattleStatus2
|
|
ldh a, [hWhoseTurn]
|
|
and a
|
|
jr z, .applyDamageToSubstitute
|
|
; values for enemy turn
|
|
ld de, wPlayerSubstituteHP
|
|
ld bc, wPlayerBattleStatus2
|
|
.applyDamageToSubstitute
|
|
ld hl, wDamage
|
|
ld a, [hli]
|
|
and a
|
|
jr nz, .substituteBroke ; damage > 0xFF always breaks substitutes
|
|
; subtract damage from HP of substitute
|
|
ld a, [de]
|
|
sub [hl]
|
|
ld [de], a
|
|
ret nc
|
|
.substituteBroke
|
|
; If the target's Substitute breaks, wDamage isn't updated with the amount of HP
|
|
; the Substitute had before being attacked.
|
|
ld h, b
|
|
ld l, c
|
|
res HAS_SUBSTITUTE_UP, [hl] ; unset the substitute bit
|
|
ld hl, SubstituteBrokeText
|
|
call PrintText
|
|
; flip whose turn it is for the next function call
|
|
ldh a, [hWhoseTurn]
|
|
xor $01
|
|
ldh [hWhoseTurn], a
|
|
callfar HideSubstituteShowMonAnim ; animate the substitute breaking
|
|
; flip the turn back to the way it was
|
|
ldh a, [hWhoseTurn]
|
|
xor $01
|
|
ldh [hWhoseTurn], a
|
|
ld hl, wPlayerMoveEffect ; value for player's turn
|
|
and a
|
|
jr z, .nullifyEffect
|
|
ld hl, wEnemyMoveEffect ; value for enemy's turn
|
|
.nullifyEffect
|
|
xor a
|
|
ld [hl], a ; zero the effect of the attacker's move
|
|
jp DrawHUDsAndHPBars
|
|
|
|
SubstituteTookDamageText:
|
|
text_far _SubstituteTookDamageText
|
|
text_end
|
|
|
|
SubstituteBrokeText:
|
|
text_far _SubstituteBrokeText
|
|
text_end
|
|
|
|
; this function raises the attack modifier of a pokemon using Rage when that pokemon is attacked
|
|
HandleBuildingRage:
|
|
; values for the player turn
|
|
ld hl, wEnemyBattleStatus2
|
|
ld de, wEnemyMonStatMods
|
|
ld bc, wEnemyMoveNum
|
|
ldh a, [hWhoseTurn]
|
|
and a
|
|
jr z, .next
|
|
; values for the enemy turn
|
|
ld hl, wPlayerBattleStatus2
|
|
ld de, wPlayerMonStatMods
|
|
ld bc, wPlayerMoveNum
|
|
.next
|
|
bit USING_RAGE, [hl] ; is the pokemon being attacked under the effect of Rage?
|
|
ret z ; return if not
|
|
ld a, [de]
|
|
cp $0d ; maximum stat modifier value
|
|
ret z ; return if attack modifier is already maxed
|
|
ldh a, [hWhoseTurn]
|
|
xor $01 ; flip turn for the stat modifier raising function
|
|
ldh [hWhoseTurn], a
|
|
; temporarily change the target pokemon's move to $00 and the effect to the one
|
|
; that causes the attack modifier to go up one stage
|
|
ld h, b
|
|
ld l, c
|
|
ld [hl], $00 ; null move number
|
|
inc hl
|
|
ld [hl], ATTACK_UP1_EFFECT
|
|
push hl
|
|
ld hl, BuildingRageText
|
|
call PrintText
|
|
call StatModifierUpEffect ; stat modifier raising function
|
|
pop hl
|
|
xor a
|
|
ldd [hl], a ; null move effect
|
|
ld a, RAGE
|
|
ld [hl], a ; restore the target pokemon's move number to Rage
|
|
ldh a, [hWhoseTurn]
|
|
xor $01 ; flip turn back to the way it was
|
|
ldh [hWhoseTurn], a
|
|
ret
|
|
|
|
BuildingRageText:
|
|
text_far _BuildingRageText
|
|
text_end
|
|
|
|
; copy last move for Mirror Move
|
|
; sets zero flag on failure and unsets zero flag on success
|
|
MirrorMoveCopyMove:
|
|
; Mirror Move makes use of wPlayerUsedMove and wEnemyUsedMove,
|
|
; which are mainly used to print the "[Pokemon] used [Move]" text.
|
|
; Both are set to 0 whenever a new Pokemon is sent out
|
|
; wPlayerUsedMove is also set to 0 whenever the player is fast asleep or frozen solid.
|
|
; wEnemyUsedMove is also set to 0 whenever the enemy is fast asleep or frozen solid.
|
|
|
|
ldh a, [hWhoseTurn]
|
|
and a
|
|
; values for player turn
|
|
ld a, [wEnemyUsedMove]
|
|
ld hl, wPlayerSelectedMove
|
|
ld de, wPlayerMoveNum
|
|
jr z, .next
|
|
; values for enemy turn
|
|
ld a, [wPlayerUsedMove]
|
|
ld de, wEnemyMoveNum
|
|
ld hl, wEnemySelectedMove
|
|
.next
|
|
ld [hl], a
|
|
cp MIRROR_MOVE ; did the target Pokemon last use Mirror Move, and miss?
|
|
jr z, .mirrorMoveFailed
|
|
and a ; has the target selected any move yet?
|
|
jr nz, ReloadMoveData
|
|
.mirrorMoveFailed
|
|
ld hl, MirrorMoveFailedText
|
|
call PrintText
|
|
xor a
|
|
ret
|
|
|
|
MirrorMoveFailedText:
|
|
text_far _MirrorMoveFailedText
|
|
text_end
|
|
|
|
; function used to reload move data for moves like Mirror Move and Metronome
|
|
ReloadMoveData:
|
|
ld [wd11e], a
|
|
dec a
|
|
ld hl, Moves
|
|
ld bc, MOVE_LENGTH
|
|
call AddNTimes
|
|
ld a, BANK(Moves)
|
|
call FarCopyData ; copy the move's stats
|
|
call IncrementMovePP
|
|
; the follow two function calls are used to reload the move name
|
|
call GetMoveName
|
|
call CopyToStringBuffer
|
|
ld a, $01
|
|
and a
|
|
ret
|
|
|
|
; function that picks a random move for metronome
|
|
MetronomePickMove:
|
|
xor a
|
|
ld [wAnimationType], a
|
|
ld a, METRONOME
|
|
call PlayMoveAnimation ; play Metronome's animation
|
|
; values for player turn
|
|
ld de, wPlayerMoveNum
|
|
ld hl, wPlayerSelectedMove
|
|
ldh a, [hWhoseTurn]
|
|
and a
|
|
jr z, .pickMoveLoop
|
|
; values for enemy turn
|
|
ld de, wEnemyMoveNum
|
|
ld hl, wEnemySelectedMove
|
|
; loop to pick a random number in the range of valid moves used by Metronome
|
|
.pickMoveLoop
|
|
call BattleRandom
|
|
and a
|
|
jr z, .pickMoveLoop
|
|
cp STRUGGLE
|
|
assert NUM_ATTACKS == STRUGGLE ; random numbers greater than STRUGGLE are not moves
|
|
jr nc, .pickMoveLoop
|
|
cp METRONOME
|
|
jr z, .pickMoveLoop
|
|
ld [hl], a
|
|
jr ReloadMoveData
|
|
|
|
; this function increments the current move's PP
|
|
; it's used to prevent moves that run another move within the same turn
|
|
; (like Mirror Move and Metronome) from losing 2 PP
|
|
IncrementMovePP:
|
|
ldh a, [hWhoseTurn]
|
|
and a
|
|
; values for player turn
|
|
ld hl, wBattleMonPP
|
|
ld de, wPartyMon1PP
|
|
ld a, [wPlayerMoveListIndex]
|
|
jr z, .next
|
|
; values for enemy turn
|
|
ld hl, wEnemyMonPP
|
|
ld de, wEnemyMon1PP
|
|
ld a, [wEnemyMoveListIndex]
|
|
.next
|
|
ld b, $00
|
|
ld c, a
|
|
add hl, bc
|
|
inc [hl] ; increment PP in the currently battling pokemon memory location
|
|
ld h, d
|
|
ld l, e
|
|
add hl, bc
|
|
ldh a, [hWhoseTurn]
|
|
and a
|
|
ld a, [wPlayerMonNumber] ; value for player turn
|
|
jr z, .updatePP
|
|
ld a, [wEnemyMonPartyPos] ; value for enemy turn
|
|
.updatePP
|
|
ld bc, wEnemyMon2 - wEnemyMon1
|
|
call AddNTimes
|
|
inc [hl] ; increment PP in the party memory location
|
|
ret
|
|
|
|
; function to adjust the base damage of an attack to account for type effectiveness
|
|
AdjustDamageForMoveType:
|
|
; values for player turn
|
|
ld hl, wBattleMonType
|
|
ld a, [hli]
|
|
ld b, a ; b = type 1 of attacker
|
|
ld c, [hl] ; c = type 2 of attacker
|
|
ld hl, wEnemyMonType
|
|
ld a, [hli]
|
|
ld d, a ; d = type 1 of defender
|
|
ld e, [hl] ; e = type 2 of defender
|
|
ld a, [wPlayerMoveType]
|
|
ld [wMoveType], a
|
|
ldh a, [hWhoseTurn]
|
|
and a
|
|
jr z, .next
|
|
; values for enemy turn
|
|
ld hl, wEnemyMonType
|
|
ld a, [hli]
|
|
ld b, a ; b = type 1 of attacker
|
|
ld c, [hl] ; c = type 2 of attacker
|
|
ld hl, wBattleMonType
|
|
ld a, [hli]
|
|
ld d, a ; d = type 1 of defender
|
|
ld e, [hl] ; e = type 2 of defender
|
|
ld a, [wEnemyMoveType]
|
|
ld [wMoveType], a
|
|
.next
|
|
ld a, [wMoveType]
|
|
cp b ; does the move type match type 1 of the attacker?
|
|
jr z, .sameTypeAttackBonus
|
|
cp c ; does the move type match type 2 of the attacker?
|
|
jr z, .sameTypeAttackBonus
|
|
jr .skipSameTypeAttackBonus
|
|
.sameTypeAttackBonus
|
|
; if the move type matches one of the attacker's types
|
|
ld hl, wDamage + 1
|
|
ld a, [hld]
|
|
ld h, [hl]
|
|
ld l, a ; hl = damage
|
|
ld b, h
|
|
ld c, l ; bc = damage
|
|
srl b
|
|
rr c ; bc = floor(0.5 * damage)
|
|
add hl, bc ; hl = floor(1.5 * damage)
|
|
; store damage
|
|
ld a, h
|
|
ld [wDamage], a
|
|
ld a, l
|
|
ld [wDamage + 1], a
|
|
ld hl, wDamageMultipliers
|
|
set 7, [hl]
|
|
.skipSameTypeAttackBonus
|
|
ld a, [wMoveType]
|
|
ld b, a
|
|
ld hl, TypeEffects
|
|
.loop
|
|
ld a, [hli] ; a = "attacking type" of the current type pair
|
|
cp $ff
|
|
jr z, .done
|
|
cp b ; does move type match "attacking type"?
|
|
jr nz, .nextTypePair
|
|
ld a, [hl] ; a = "defending type" of the current type pair
|
|
cp d ; does type 1 of defender match "defending type"?
|
|
jr z, .matchingPairFound
|
|
cp e ; does type 2 of defender match "defending type"?
|
|
jr z, .matchingPairFound
|
|
jr .nextTypePair
|
|
.matchingPairFound
|
|
; if the move type matches the "attacking type" and one of the defender's types matches the "defending type"
|
|
push hl
|
|
push bc
|
|
inc hl
|
|
ld a, [wDamageMultipliers]
|
|
and $80
|
|
ld b, a
|
|
ld a, [hl] ; a = damage multiplier
|
|
ldh [hMultiplier], a
|
|
add b
|
|
ld [wDamageMultipliers], a
|
|
xor a
|
|
ldh [hMultiplicand], a
|
|
ld hl, wDamage
|
|
ld a, [hli]
|
|
ldh [hMultiplicand + 1], a
|
|
ld a, [hld]
|
|
ldh [hMultiplicand + 2], a
|
|
call Multiply
|
|
ld a, 10
|
|
ldh [hDivisor], a
|
|
ld b, $04
|
|
call Divide
|
|
ldh a, [hQuotient + 2]
|
|
ld [hli], a
|
|
ld b, a
|
|
ldh a, [hQuotient + 3]
|
|
ld [hl], a
|
|
or b ; is damage 0?
|
|
jr nz, .skipTypeImmunity
|
|
.typeImmunity
|
|
; if damage is 0, make the move miss
|
|
; this only occurs if a move that would do 2 or 3 damage is 0.25x effective against the target
|
|
inc a
|
|
ld [wMoveMissed], a
|
|
.skipTypeImmunity
|
|
pop bc
|
|
pop hl
|
|
.nextTypePair
|
|
inc hl
|
|
inc hl
|
|
jp .loop
|
|
.done
|
|
ret
|
|
|
|
; function to tell how effective the type of an enemy attack is on the player's current pokemon
|
|
; this doesn't take into account the effects that dual types can have
|
|
; (e.g. 4x weakness / resistance, weaknesses and resistances canceling)
|
|
; the result is stored in [wTypeEffectiveness]
|
|
; ($05 is not very effective, $10 is neutral, $14 is super effective)
|
|
; as far is can tell, this is only used once in some AI code to help decide which move to use
|
|
AIGetTypeEffectiveness:
|
|
ld a, [wEnemyMoveType]
|
|
ld d, a ; d = type of enemy move
|
|
ld hl, wBattleMonType
|
|
ld b, [hl] ; b = type 1 of player's pokemon
|
|
inc hl
|
|
ld c, [hl] ; c = type 2 of player's pokemon
|
|
ld a, $10
|
|
ld [wTypeEffectiveness], a ; initialize to neutral effectiveness
|
|
ld hl, TypeEffects
|
|
.loop
|
|
ld a, [hli]
|
|
cp $ff
|
|
ret z
|
|
cp d ; match the type of the move
|
|
jr nz, .nextTypePair1
|
|
ld a, [hli]
|
|
cp b ; match with type 1 of pokemon
|
|
jr z, .done
|
|
cp c ; or match with type 2 of pokemon
|
|
jr z, .done
|
|
jr .nextTypePair2
|
|
.nextTypePair1
|
|
inc hl
|
|
.nextTypePair2
|
|
inc hl
|
|
jr .loop
|
|
.done
|
|
ld a, [hl]
|
|
ld [wTypeEffectiveness], a ; store damage multiplier
|
|
ret
|
|
|
|
INCLUDE "data/types/type_matchups.asm"
|
|
|
|
; some tests that need to pass for a move to hit
|
|
MoveHitTest:
|
|
; player's turn
|
|
ld hl, wEnemyBattleStatus1
|
|
ld de, wPlayerMoveEffect
|
|
ld bc, wEnemyMonStatus
|
|
ldh a, [hWhoseTurn]
|
|
and a
|
|
jr z, .dreamEaterCheck
|
|
; enemy's turn
|
|
ld hl, wPlayerBattleStatus1
|
|
ld de, wEnemyMoveEffect
|
|
ld bc, wBattleMonStatus
|
|
.dreamEaterCheck
|
|
ld a, [de]
|
|
cp DREAM_EATER_EFFECT
|
|
jr nz, .swiftCheck
|
|
ld a, [bc]
|
|
and SLP_MASK
|
|
jp z, .moveMissed
|
|
.swiftCheck
|
|
ld a, [de]
|
|
cp SWIFT_EFFECT
|
|
ret z ; Swift never misses (this was fixed from the Japanese versions)
|
|
call CheckTargetSubstitute ; substitute check (note that this overwrites a)
|
|
jr z, .checkForDigOrFlyStatus
|
|
; The fix for Swift broke this code. It's supposed to prevent HP draining moves from working on Substitutes.
|
|
; Since CheckTargetSubstitute overwrites a with either $00 or $01, it never works.
|
|
cp DRAIN_HP_EFFECT
|
|
jp z, .moveMissed
|
|
cp DREAM_EATER_EFFECT
|
|
jp z, .moveMissed
|
|
.checkForDigOrFlyStatus
|
|
bit INVULNERABLE, [hl]
|
|
jp nz, .moveMissed
|
|
ldh a, [hWhoseTurn]
|
|
and a
|
|
jr nz, .enemyTurn
|
|
.playerTurn
|
|
; this checks if the move effect is disallowed by mist
|
|
ld a, [wPlayerMoveEffect]
|
|
cp ATTACK_DOWN1_EFFECT
|
|
jr c, .skipEnemyMistCheck
|
|
cp HAZE_EFFECT + 1
|
|
jr c, .enemyMistCheck
|
|
cp ATTACK_DOWN2_EFFECT
|
|
jr c, .skipEnemyMistCheck
|
|
cp REFLECT_EFFECT + 1
|
|
jr c, .enemyMistCheck
|
|
jr .skipEnemyMistCheck
|
|
.enemyMistCheck
|
|
; if move effect is from $12 to $19 inclusive or $3a to $41 inclusive
|
|
; i.e. the following moves
|
|
; GROWL, TAIL WHIP, LEER, STRING SHOT, SAND-ATTACK, SMOKESCREEN, KINESIS,
|
|
; FLASH, CONVERSION*, HAZE*, SCREECH, LIGHT SCREEN*, REFLECT*
|
|
; the moves that are marked with an asterisk are not affected since this
|
|
; function is not called when those moves are used
|
|
ld a, [wEnemyBattleStatus2]
|
|
bit PROTECTED_BY_MIST, a ; is mon protected by mist?
|
|
jp nz, .moveMissed
|
|
.skipEnemyMistCheck
|
|
ld a, [wPlayerBattleStatus2]
|
|
bit USING_X_ACCURACY, a ; is the player using X Accuracy?
|
|
ret nz ; if so, always hit regardless of accuracy/evasion
|
|
jr .calcHitChance
|
|
.enemyTurn
|
|
ld a, [wEnemyMoveEffect]
|
|
cp ATTACK_DOWN1_EFFECT
|
|
jr c, .skipPlayerMistCheck
|
|
cp HAZE_EFFECT + 1
|
|
jr c, .playerMistCheck
|
|
cp ATTACK_DOWN2_EFFECT
|
|
jr c, .skipPlayerMistCheck
|
|
cp REFLECT_EFFECT + 1
|
|
jr c, .playerMistCheck
|
|
jr .skipPlayerMistCheck
|
|
.playerMistCheck
|
|
; similar to enemy mist check
|
|
ld a, [wPlayerBattleStatus2]
|
|
bit PROTECTED_BY_MIST, a ; is mon protected by mist?
|
|
jp nz, .moveMissed
|
|
.skipPlayerMistCheck
|
|
ld a, [wEnemyBattleStatus2]
|
|
bit USING_X_ACCURACY, a ; is the enemy using X Accuracy?
|
|
ret nz ; if so, always hit regardless of accuracy/evasion
|
|
.calcHitChance
|
|
call CalcHitChance ; scale the move accuracy according to attacker's accuracy and target's evasion
|
|
ld a, [wPlayerMoveAccuracy]
|
|
ld b, a
|
|
ldh a, [hWhoseTurn]
|
|
and a
|
|
jr z, .doAccuracyCheck
|
|
ld a, [wEnemyMoveAccuracy]
|
|
ld b, a
|
|
.doAccuracyCheck
|
|
; if the random number generated is greater than or equal to the scaled accuracy, the move misses
|
|
; note that this means that even the highest accuracy is still just a 255/256 chance, not 100%
|
|
call BattleRandom
|
|
cp b
|
|
jr nc, .moveMissed
|
|
ret
|
|
.moveMissed
|
|
xor a
|
|
ld hl, wDamage ; zero the damage
|
|
ld [hli], a
|
|
ld [hl], a
|
|
inc a
|
|
ld [wMoveMissed], a
|
|
ldh a, [hWhoseTurn]
|
|
and a
|
|
jr z, .playerTurn2
|
|
.enemyTurn2
|
|
ld hl, wEnemyBattleStatus1
|
|
res USING_TRAPPING_MOVE, [hl] ; end multi-turn attack e.g. wrap
|
|
ret
|
|
.playerTurn2
|
|
ld hl, wPlayerBattleStatus1
|
|
res USING_TRAPPING_MOVE, [hl] ; end multi-turn attack e.g. wrap
|
|
ret
|
|
|
|
; values for player turn
|
|
CalcHitChance:
|
|
ld hl, wPlayerMoveAccuracy
|
|
ldh a, [hWhoseTurn]
|
|
and a
|
|
ld a, [wPlayerMonAccuracyMod]
|
|
ld b, a
|
|
ld a, [wEnemyMonEvasionMod]
|
|
ld c, a
|
|
jr z, .next
|
|
; values for enemy turn
|
|
ld hl, wEnemyMoveAccuracy
|
|
ld a, [wEnemyMonAccuracyMod]
|
|
ld b, a
|
|
ld a, [wPlayerMonEvasionMod]
|
|
ld c, a
|
|
.next
|
|
ld a, $0e
|
|
sub c
|
|
ld c, a ; c = 14 - EVASIONMOD (this "reflects" the value over 7, so that an increase in the target's evasion
|
|
; decreases the hit chance instead of increasing the hit chance)
|
|
; zero the high bytes of the multiplicand
|
|
xor a
|
|
ldh [hMultiplicand], a
|
|
ldh [hMultiplicand + 1], a
|
|
ld a, [hl]
|
|
ldh [hMultiplicand + 2], a ; set multiplicand to move accuracy
|
|
push hl
|
|
ld d, $02 ; loop has two iterations
|
|
; loop to do the calculations, the first iteration multiplies by the accuracy ratio and
|
|
; the second iteration multiplies by the evasion ratio
|
|
.loop
|
|
push bc
|
|
ld hl, StatModifierRatios ; stat modifier ratios
|
|
dec b
|
|
sla b
|
|
ld c, b
|
|
ld b, $00
|
|
add hl, bc ; hl = address of stat modifier ratio
|
|
pop bc
|
|
ld a, [hli]
|
|
ldh [hMultiplier], a ; set multiplier to the numerator of the ratio
|
|
call Multiply
|
|
ld a, [hl]
|
|
ldh [hDivisor], a ; set divisor to the the denominator of the ratio
|
|
; (the dividend is the product of the previous multiplication)
|
|
ld b, $04 ; number of bytes in the dividend
|
|
call Divide
|
|
ldh a, [hQuotient + 3]
|
|
ld b, a
|
|
ldh a, [hQuotient + 2]
|
|
or b
|
|
jp nz, .nextCalculation
|
|
; make sure the result is always at least one
|
|
ldh [hQuotient + 2], a
|
|
ld a, $01
|
|
ldh [hQuotient + 3], a
|
|
.nextCalculation
|
|
ld b, c
|
|
dec d
|
|
jr nz, .loop
|
|
ldh a, [hQuotient + 2]
|
|
and a ; is the calculated hit chance over 0xFF?
|
|
ldh a, [hQuotient + 3]
|
|
jr z, .storeAccuracy
|
|
; if calculated hit chance over 0xFF
|
|
ld a, $ff ; set the hit chance to 0xFF
|
|
.storeAccuracy
|
|
pop hl
|
|
ld [hl], a ; store the hit chance in the move accuracy variable
|
|
ret
|
|
|
|
; multiplies damage by a random percentage from ~85% to 100%
|
|
RandomizeDamage:
|
|
ld hl, wDamage
|
|
ld a, [hli]
|
|
and a
|
|
jr nz, .DamageGreaterThanOne
|
|
ld a, [hl]
|
|
cp 2
|
|
ret c ; return if damage is equal to 0 or 1
|
|
.DamageGreaterThanOne
|
|
xor a
|
|
ldh [hMultiplicand], a
|
|
dec hl
|
|
ld a, [hli]
|
|
ldh [hMultiplicand + 1], a
|
|
ld a, [hl]
|
|
ldh [hMultiplicand + 2], a
|
|
; loop until a random number greater than or equal to 217 is generated
|
|
.loop
|
|
call BattleRandom
|
|
rrca
|
|
cp 217
|
|
jr c, .loop
|
|
ldh [hMultiplier], a
|
|
call Multiply ; multiply damage by the random number, which is in the range [217, 255]
|
|
ld a, 255
|
|
ldh [hDivisor], a
|
|
ld b, $4
|
|
call Divide ; divide the result by 255
|
|
; store the modified damage
|
|
ldh a, [hQuotient + 2]
|
|
ld hl, wDamage
|
|
ld [hli], a
|
|
ldh a, [hQuotient + 3]
|
|
ld [hl], a
|
|
ret
|
|
|
|
; for more detailed commentary, see equivalent function for player side (ExecutePlayerMove)
|
|
ExecuteEnemyMove:
|
|
ld a, [wEnemySelectedMove]
|
|
inc a
|
|
jp z, ExecuteEnemyMoveDone
|
|
call PrintGhostText
|
|
jp z, ExecuteEnemyMoveDone
|
|
ld a, [wLinkState]
|
|
cp LINK_STATE_BATTLING
|
|
jr nz, .executeEnemyMove
|
|
ld b, $1
|
|
ld a, [wSerialExchangeNybbleReceiveData]
|
|
cp LINKBATTLE_STRUGGLE
|
|
jr z, .executeEnemyMove
|
|
cp 4
|
|
ret nc
|
|
.executeEnemyMove
|
|
ld hl, wAILayer2Encouragement
|
|
inc [hl]
|
|
xor a
|
|
ld [wMoveMissed], a
|
|
ld [wMoveDidntMiss], a
|
|
ld a, $a
|
|
ld [wDamageMultipliers], a
|
|
call CheckEnemyStatusConditions
|
|
jr nz, .enemyHasNoSpecialConditions
|
|
jp hl
|
|
.enemyHasNoSpecialConditions
|
|
ld hl, wEnemyBattleStatus1
|
|
bit CHARGING_UP, [hl] ; is the enemy charging up for attack?
|
|
jr nz, EnemyCanExecuteChargingMove ; if so, jump
|
|
call GetCurrentMove
|
|
|
|
CheckIfEnemyNeedsToChargeUp:
|
|
ld a, [wEnemyMoveEffect]
|
|
cp CHARGE_EFFECT
|
|
jp z, JumpMoveEffect
|
|
cp FLY_EFFECT
|
|
jp z, JumpMoveEffect
|
|
jr EnemyCanExecuteMove
|
|
EnemyCanExecuteChargingMove:
|
|
ld hl, wEnemyBattleStatus1
|
|
res CHARGING_UP, [hl] ; no longer charging up for attack
|
|
res INVULNERABLE, [hl] ; no longer invulnerable to typical attacks
|
|
ld a, [wEnemyMoveNum]
|
|
ld [wd0b5], a
|
|
ld a, BANK(MoveNames)
|
|
ld [wPredefBank], a
|
|
ld a, MOVE_NAME
|
|
ld [wNameListType], a
|
|
call GetName
|
|
ld de, wcd6d
|
|
call CopyToStringBuffer
|
|
EnemyCanExecuteMove:
|
|
xor a
|
|
ld [wMonIsDisobedient], a
|
|
call PrintMonName1Text
|
|
ld a, [wEnemyMoveEffect]
|
|
ld hl, ResidualEffects1
|
|
ld de, $1
|
|
call IsInArray
|
|
jp c, JumpMoveEffect
|
|
ld a, [wEnemyMoveEffect]
|
|
ld hl, SpecialEffectsCont
|
|
ld de, $1
|
|
call IsInArray
|
|
call c, JumpMoveEffect
|
|
EnemyCalcMoveDamage:
|
|
call SwapPlayerAndEnemyLevels
|
|
ld a, [wEnemyMoveEffect]
|
|
ld hl, SetDamageEffects
|
|
ld de, $1
|
|
call IsInArray
|
|
jp c, EnemyMoveHitTest
|
|
call CriticalHitTest
|
|
call HandleCounterMove
|
|
jr z, handleIfEnemyMoveMissed
|
|
call SwapPlayerAndEnemyLevels
|
|
call GetDamageVarsForEnemyAttack
|
|
call SwapPlayerAndEnemyLevels
|
|
call CalculateDamage
|
|
jp z, EnemyCheckIfFlyOrChargeEffect
|
|
call AdjustDamageForMoveType
|
|
call RandomizeDamage
|
|
|
|
EnemyMoveHitTest:
|
|
call MoveHitTest
|
|
handleIfEnemyMoveMissed:
|
|
ld a, [wMoveMissed]
|
|
and a
|
|
jr z, .moveDidNotMiss
|
|
ld a, [wEnemyMoveEffect]
|
|
cp EXPLODE_EFFECT
|
|
jr z, handleExplosionMiss
|
|
jr EnemyCheckIfFlyOrChargeEffect
|
|
.moveDidNotMiss
|
|
call SwapPlayerAndEnemyLevels
|
|
|
|
GetEnemyAnimationType:
|
|
ld a, [wEnemyMoveEffect]
|
|
and a
|
|
ld a, $1
|
|
jr z, playEnemyMoveAnimation
|
|
ld a, $2
|
|
jr playEnemyMoveAnimation
|
|
handleExplosionMiss:
|
|
call SwapPlayerAndEnemyLevels
|
|
xor a
|
|
playEnemyMoveAnimation:
|
|
push af
|
|
ld a, [wEnemyBattleStatus2]
|
|
bit HAS_SUBSTITUTE_UP, a ; does mon have a substitute?
|
|
ld hl, HideSubstituteShowMonAnim
|
|
ld b, BANK(HideSubstituteShowMonAnim)
|
|
call nz, Bankswitch
|
|
pop af
|
|
ld [wAnimationType], a
|
|
ld a, [wEnemyMoveNum]
|
|
call PlayMoveAnimation
|
|
call HandleExplodingAnimation
|
|
call DrawEnemyHUDAndHPBar
|
|
ld a, [wEnemyBattleStatus2]
|
|
bit HAS_SUBSTITUTE_UP, a ; does mon have a substitute?
|
|
ld hl, ReshowSubstituteAnim
|
|
ld b, BANK(ReshowSubstituteAnim)
|
|
call nz, Bankswitch ; slide the substitute's sprite out
|
|
jr EnemyCheckIfMirrorMoveEffect
|
|
|
|
EnemyCheckIfFlyOrChargeEffect:
|
|
call SwapPlayerAndEnemyLevels
|
|
ld c, 30
|
|
call DelayFrames
|
|
ld a, [wEnemyMoveEffect]
|
|
cp FLY_EFFECT
|
|
jr z, .playAnim
|
|
cp CHARGE_EFFECT
|
|
jr z, .playAnim
|
|
jr EnemyCheckIfMirrorMoveEffect
|
|
.playAnim
|
|
xor a
|
|
ld [wAnimationType], a
|
|
ld a, STATUS_AFFECTED_ANIM
|
|
call PlayMoveAnimation
|
|
EnemyCheckIfMirrorMoveEffect:
|
|
ld a, [wEnemyMoveEffect]
|
|
cp MIRROR_MOVE_EFFECT
|
|
jr nz, .notMirrorMoveEffect
|
|
call MirrorMoveCopyMove
|
|
jp z, ExecuteEnemyMoveDone
|
|
jp CheckIfEnemyNeedsToChargeUp
|
|
.notMirrorMoveEffect
|
|
cp METRONOME_EFFECT
|
|
jr nz, .notMetronomeEffect
|
|
call MetronomePickMove
|
|
jp CheckIfEnemyNeedsToChargeUp
|
|
.notMetronomeEffect
|
|
ld a, [wEnemyMoveEffect]
|
|
ld hl, ResidualEffects2
|
|
ld de, $1
|
|
call IsInArray
|
|
jp c, JumpMoveEffect
|
|
ld a, [wMoveMissed]
|
|
and a
|
|
jr z, .moveDidNotMiss
|
|
call PrintMoveFailureText
|
|
ld a, [wEnemyMoveEffect]
|
|
cp EXPLODE_EFFECT
|
|
jr z, .handleExplosionMiss
|
|
jp ExecuteEnemyMoveDone
|
|
.moveDidNotMiss
|
|
call ApplyAttackToPlayerPokemon
|
|
call PrintCriticalOHKOText
|
|
callfar DisplayEffectiveness
|
|
ld a, 1
|
|
ld [wMoveDidntMiss], a
|
|
.handleExplosionMiss
|
|
ld a, [wEnemyMoveEffect]
|
|
ld hl, AlwaysHappenSideEffects
|
|
ld de, $1
|
|
call IsInArray
|
|
call c, JumpMoveEffect
|
|
ld hl, wBattleMonHP
|
|
ld a, [hli]
|
|
ld b, [hl]
|
|
or b
|
|
ret z
|
|
call HandleBuildingRage
|
|
ld hl, wEnemyBattleStatus1
|
|
bit ATTACKING_MULTIPLE_TIMES, [hl] ; is mon hitting multiple times? (example: double kick)
|
|
jr z, .notMultiHitMove
|
|
push hl
|
|
ld hl, wEnemyNumAttacksLeft
|
|
dec [hl]
|
|
pop hl
|
|
jp nz, GetEnemyAnimationType
|
|
res ATTACKING_MULTIPLE_TIMES, [hl] ; mon is no longer hitting multiple times
|
|
ld hl, HitXTimesText
|
|
call PrintText
|
|
xor a
|
|
ld [wEnemyNumHits], a
|
|
.notMultiHitMove
|
|
ld a, [wEnemyMoveEffect]
|
|
and a
|
|
jr z, ExecuteEnemyMoveDone
|
|
ld hl, SpecialEffects
|
|
ld de, $1
|
|
call IsInArray
|
|
call nc, JumpMoveEffect
|
|
jr ExecuteEnemyMoveDone
|
|
|
|
HitXTimesText:
|
|
text_far _HitXTimesText
|
|
text_end
|
|
|
|
ExecuteEnemyMoveDone:
|
|
ld b, $1
|
|
ret
|
|
|
|
; checks for various status conditions affecting the enemy mon
|
|
; stores whether the mon cannot use a move this turn in Z flag
|
|
CheckEnemyStatusConditions:
|
|
ld hl, wEnemyMonStatus
|
|
ld a, [hl]
|
|
and SLP_MASK
|
|
jr z, .checkIfFrozen
|
|
dec a ; decrement number of turns left
|
|
ld [wEnemyMonStatus], a
|
|
and a
|
|
jr z, .wokeUp ; if the number of turns hit 0, wake up
|
|
ld hl, FastAsleepText
|
|
call PrintText
|
|
xor a
|
|
ld [wAnimationType], a
|
|
ld a, SLP_ANIM
|
|
call PlayMoveAnimation
|
|
jr .sleepDone
|
|
.wokeUp
|
|
ld hl, WokeUpText
|
|
call PrintText
|
|
.sleepDone
|
|
xor a
|
|
ld [wEnemyUsedMove], a
|
|
ld hl, ExecuteEnemyMoveDone ; enemy can't move this turn
|
|
jp .enemyReturnToHL
|
|
.checkIfFrozen
|
|
bit FRZ, [hl]
|
|
jr z, .checkIfTrapped
|
|
ld hl, IsFrozenText
|
|
call PrintText
|
|
xor a
|
|
ld [wEnemyUsedMove], a
|
|
ld hl, ExecuteEnemyMoveDone ; enemy can't move this turn
|
|
jp .enemyReturnToHL
|
|
.checkIfTrapped
|
|
ld a, [wPlayerBattleStatus1]
|
|
bit USING_TRAPPING_MOVE, a ; is the player using a multi-turn attack like warp
|
|
jp z, .checkIfFlinched
|
|
ld hl, CantMoveText
|
|
call PrintText
|
|
ld hl, ExecuteEnemyMoveDone ; enemy can't move this turn
|
|
jp .enemyReturnToHL
|
|
.checkIfFlinched
|
|
ld hl, wEnemyBattleStatus1
|
|
bit FLINCHED, [hl] ; check if enemy mon flinched
|
|
jp z, .checkIfMustRecharge
|
|
res FLINCHED, [hl]
|
|
ld hl, FlinchedText
|
|
call PrintText
|
|
ld hl, ExecuteEnemyMoveDone ; enemy can't move this turn
|
|
jp .enemyReturnToHL
|
|
.checkIfMustRecharge
|
|
ld hl, wEnemyBattleStatus2
|
|
bit NEEDS_TO_RECHARGE, [hl] ; check if enemy mon has to recharge after using a move
|
|
jr z, .checkIfAnyMoveDisabled
|
|
res NEEDS_TO_RECHARGE, [hl]
|
|
ld hl, MustRechargeText
|
|
call PrintText
|
|
ld hl, ExecuteEnemyMoveDone ; enemy can't move this turn
|
|
jp .enemyReturnToHL
|
|
.checkIfAnyMoveDisabled
|
|
ld hl, wEnemyDisabledMove
|
|
ld a, [hl]
|
|
and a
|
|
jr z, .checkIfConfused
|
|
dec a ; decrement disable counter
|
|
ld [hl], a
|
|
and $f ; did disable counter hit 0?
|
|
jr nz, .checkIfConfused
|
|
ld [hl], a
|
|
ld [wEnemyDisabledMoveNumber], a
|
|
ld hl, DisabledNoMoreText
|
|
call PrintText
|
|
.checkIfConfused
|
|
ld a, [wEnemyBattleStatus1]
|
|
add a ; check if enemy mon is confused
|
|
jp nc, .checkIfTriedToUseDisabledMove
|
|
ld hl, wEnemyConfusedCounter
|
|
dec [hl]
|
|
jr nz, .isConfused
|
|
ld hl, wEnemyBattleStatus1
|
|
res CONFUSED, [hl] ; if confused counter hit 0, reset confusion status
|
|
ld hl, ConfusedNoMoreText
|
|
call PrintText
|
|
jp .checkIfTriedToUseDisabledMove
|
|
.isConfused
|
|
ld hl, IsConfusedText
|
|
call PrintText
|
|
xor a
|
|
ld [wAnimationType], a
|
|
ld a, CONF_ANIM
|
|
call PlayMoveAnimation
|
|
call BattleRandom
|
|
cp $80
|
|
jr c, .checkIfTriedToUseDisabledMove
|
|
ld hl, wEnemyBattleStatus1
|
|
ld a, [hl]
|
|
and 1 << CONFUSED ; if mon hurts itself, clear every other status from wEnemyBattleStatus1
|
|
ld [hl], a
|
|
ld hl, HurtItselfText
|
|
call PrintText
|
|
ld hl, wBattleMonDefense
|
|
ld a, [hli]
|
|
push af
|
|
ld a, [hld]
|
|
push af
|
|
ld a, [wEnemyMonDefense]
|
|
ld [hli], a
|
|
ld a, [wEnemyMonDefense + 1]
|
|
ld [hl], a
|
|
ld hl, wEnemyMoveEffect
|
|
push hl
|
|
ld a, [hl]
|
|
push af
|
|
xor a
|
|
ld [hli], a
|
|
ld [wCriticalHitOrOHKO], a
|
|
ld a, 40
|
|
ld [hli], a
|
|
xor a
|
|
ld [hl], a
|
|
call GetDamageVarsForEnemyAttack
|
|
call CalculateDamage
|
|
pop af
|
|
pop hl
|
|
ld [hl], a
|
|
ld hl, wBattleMonDefense + 1
|
|
pop af
|
|
ld [hld], a
|
|
pop af
|
|
ld [hl], a
|
|
xor a
|
|
ld [wAnimationType], a
|
|
ldh [hWhoseTurn], a
|
|
ld a, POUND
|
|
call PlayMoveAnimation
|
|
ld a, $1
|
|
ldh [hWhoseTurn], a
|
|
call ApplyDamageToEnemyPokemon
|
|
jr .monHurtItselfOrFullyParalysed
|
|
.checkIfTriedToUseDisabledMove
|
|
; prevents a disabled move that was selected before being disabled from being used
|
|
ld a, [wEnemyDisabledMoveNumber]
|
|
and a
|
|
jr z, .checkIfParalysed
|
|
ld hl, wEnemySelectedMove
|
|
cp [hl]
|
|
jr nz, .checkIfParalysed
|
|
call PrintMoveIsDisabledText
|
|
ld hl, ExecuteEnemyMoveDone ; if a disabled move was somehow selected, player can't move this turn
|
|
jp .enemyReturnToHL
|
|
.checkIfParalysed
|
|
ld hl, wEnemyMonStatus
|
|
bit PAR, [hl]
|
|
jr z, .checkIfUsingBide
|
|
call BattleRandom
|
|
cp 25 percent ; chance to be fully paralysed
|
|
jr nc, .checkIfUsingBide
|
|
ld hl, FullyParalyzedText
|
|
call PrintText
|
|
.monHurtItselfOrFullyParalysed
|
|
ld hl, wEnemyBattleStatus1
|
|
ld a, [hl]
|
|
; clear bide, thrashing about, charging up, and multi-turn moves such as warp
|
|
and ~((1 << STORING_ENERGY) | (1 << THRASHING_ABOUT) | (1 << CHARGING_UP) | (1 << USING_TRAPPING_MOVE))
|
|
ld [hl], a
|
|
ld a, [wEnemyMoveEffect]
|
|
cp FLY_EFFECT
|
|
jr z, .flyOrChargeEffect
|
|
cp CHARGE_EFFECT
|
|
jr z, .flyOrChargeEffect
|
|
jr .notFlyOrChargeEffect
|
|
.flyOrChargeEffect
|
|
xor a
|
|
ld [wAnimationType], a
|
|
ld a, STATUS_AFFECTED_ANIM
|
|
call PlayMoveAnimation
|
|
.notFlyOrChargeEffect
|
|
ld hl, ExecuteEnemyMoveDone
|
|
jp .enemyReturnToHL ; if using a two-turn move, enemy needs to recharge the first turn
|
|
.checkIfUsingBide
|
|
ld hl, wEnemyBattleStatus1
|
|
bit STORING_ENERGY, [hl] ; is mon using bide?
|
|
jr z, .checkIfThrashingAbout
|
|
xor a
|
|
ld [wEnemyMoveNum], a
|
|
ld hl, wDamage
|
|
ld a, [hli]
|
|
ld b, a
|
|
ld c, [hl]
|
|
ld hl, wEnemyBideAccumulatedDamage + 1
|
|
ld a, [hl]
|
|
add c ; accumulate damage taken
|
|
ld [hld], a
|
|
ld a, [hl]
|
|
adc b
|
|
ld [hl], a
|
|
ld hl, wEnemyNumAttacksLeft
|
|
dec [hl] ; did Bide counter hit 0?
|
|
jr z, .unleashEnergy
|
|
ld hl, ExecuteEnemyMoveDone
|
|
jp .enemyReturnToHL ; unless mon unleashes energy, can't move this turn
|
|
.unleashEnergy
|
|
ld hl, wEnemyBattleStatus1
|
|
res STORING_ENERGY, [hl] ; not using bide any more
|
|
ld hl, UnleashedEnergyText
|
|
call PrintText
|
|
ld a, $1
|
|
ld [wEnemyMovePower], a
|
|
ld hl, wEnemyBideAccumulatedDamage + 1
|
|
ld a, [hld]
|
|
add a
|
|
ld b, a
|
|
ld [wDamage + 1], a
|
|
ld a, [hl]
|
|
rl a ; double the damage
|
|
ld [wDamage], a
|
|
or b
|
|
jr nz, .next
|
|
ld a, $1
|
|
ld [wMoveMissed], a
|
|
.next
|
|
xor a
|
|
ld [hli], a
|
|
ld [hl], a
|
|
ld a, BIDE
|
|
ld [wEnemyMoveNum], a
|
|
call SwapPlayerAndEnemyLevels
|
|
ld hl, handleIfEnemyMoveMissed ; skip damage calculation, DecrementPP and MoveHitTest
|
|
jp .enemyReturnToHL
|
|
.checkIfThrashingAbout
|
|
bit THRASHING_ABOUT, [hl] ; is mon using thrash or petal dance?
|
|
jr z, .checkIfUsingMultiturnMove
|
|
ld a, THRASH
|
|
ld [wEnemyMoveNum], a
|
|
ld hl, ThrashingAboutText
|
|
call PrintText
|
|
ld hl, wEnemyNumAttacksLeft
|
|
dec [hl] ; did Thrashing About counter hit 0?
|
|
ld hl, EnemyCalcMoveDamage ; skip DecrementPP
|
|
jp nz, .enemyReturnToHL
|
|
push hl
|
|
ld hl, wEnemyBattleStatus1
|
|
res THRASHING_ABOUT, [hl] ; mon is no longer using thrash or petal dance
|
|
set CONFUSED, [hl] ; mon is now confused
|
|
call BattleRandom
|
|
and $3
|
|
inc a
|
|
inc a ; confused for 2-5 turns
|
|
ld [wEnemyConfusedCounter], a
|
|
pop hl ; skip DecrementPP
|
|
jp .enemyReturnToHL
|
|
.checkIfUsingMultiturnMove
|
|
bit USING_TRAPPING_MOVE, [hl] ; is mon using multi-turn move?
|
|
jp z, .checkIfUsingRage
|
|
ld hl, AttackContinuesText
|
|
call PrintText
|
|
ld hl, wEnemyNumAttacksLeft
|
|
dec [hl] ; did multi-turn move end?
|
|
ld hl, GetEnemyAnimationType ; if it didn't, skip damage calculation (deal damage equal to last hit),
|
|
; DecrementPP and MoveHitTest
|
|
jp nz, .enemyReturnToHL
|
|
jp .enemyReturnToHL
|
|
.checkIfUsingRage
|
|
ld a, [wEnemyBattleStatus2]
|
|
bit USING_RAGE, a ; is mon using rage?
|
|
jp z, .checkEnemyStatusConditionsDone ; if we made it this far, mon can move normally this turn
|
|
ld a, RAGE
|
|
ld [wd11e], a
|
|
call GetMoveName
|
|
call CopyToStringBuffer
|
|
xor a
|
|
ld [wEnemyMoveEffect], a
|
|
ld hl, EnemyCanExecuteMove
|
|
jp .enemyReturnToHL
|
|
.enemyReturnToHL
|
|
xor a ; set Z flag
|
|
ret
|
|
.checkEnemyStatusConditionsDone
|
|
ld a, $1
|
|
and a ; clear Z flag
|
|
ret
|
|
|
|
GetCurrentMove:
|
|
ldh a, [hWhoseTurn]
|
|
and a
|
|
jp z, .player
|
|
ld de, wEnemyMoveNum
|
|
ld a, [wEnemySelectedMove]
|
|
jr .selected
|
|
.player
|
|
ld de, wPlayerMoveNum
|
|
; Apply InitBattleVariables to TestBattle.
|
|
ld a, [wFlags_D733]
|
|
bit BIT_TEST_BATTLE, a
|
|
ld a, [wTestBattlePlayerSelectedMove]
|
|
jr nz, .selected
|
|
ld a, [wPlayerSelectedMove]
|
|
.selected
|
|
ld [wd0b5], a
|
|
dec a
|
|
ld hl, Moves
|
|
ld bc, MOVE_LENGTH
|
|
call AddNTimes
|
|
ld a, BANK(Moves)
|
|
call FarCopyData
|
|
|
|
ld a, BANK(MoveNames)
|
|
ld [wPredefBank], a
|
|
ld a, MOVE_NAME
|
|
ld [wNameListType], a
|
|
call GetName
|
|
ld de, wcd6d
|
|
jp CopyToStringBuffer
|
|
|
|
LoadEnemyMonData:
|
|
ld a, [wLinkState]
|
|
cp LINK_STATE_BATTLING
|
|
jp z, LoadEnemyMonFromParty
|
|
ld a, [wEnemyMonSpecies2]
|
|
ld [wEnemyMonSpecies], a
|
|
ld [wd0b5], a
|
|
call GetMonHeader
|
|
ld a, [wEnemyBattleStatus3]
|
|
bit TRANSFORMED, a ; is enemy mon transformed?
|
|
ld hl, wTransformedEnemyMonOriginalDVs ; original DVs before transforming
|
|
ld a, [hli]
|
|
ld b, [hl]
|
|
jr nz, .storeDVs
|
|
ld a, [wIsInBattle]
|
|
cp $2 ; is it a trainer battle?
|
|
; fixed DVs for trainer mon
|
|
ld a, ATKDEFDV_TRAINER
|
|
ld b, SPDSPCDV_TRAINER
|
|
jr z, .storeDVs
|
|
; random DVs for wild mon
|
|
call BattleRandom
|
|
ld b, a
|
|
call BattleRandom
|
|
.storeDVs
|
|
ld hl, wEnemyMonDVs
|
|
ld [hli], a
|
|
ld [hl], b
|
|
ld de, wEnemyMonLevel
|
|
ld a, [wCurEnemyLVL]
|
|
ld [de], a
|
|
inc de
|
|
ld b, $0
|
|
ld hl, wEnemyMonHP
|
|
push hl
|
|
call CalcStats
|
|
pop hl
|
|
ld a, [wIsInBattle]
|
|
cp $2 ; is it a trainer battle?
|
|
jr z, .copyHPAndStatusFromPartyData
|
|
ld a, [wEnemyBattleStatus3]
|
|
bit TRANSFORMED, a ; is enemy mon transformed?
|
|
jr nz, .copyTypes ; if transformed, jump
|
|
; if it's a wild mon and not transformed, init the current HP to max HP and the status to 0
|
|
ld a, [wEnemyMonMaxHP]
|
|
ld [hli], a
|
|
ld a, [wEnemyMonMaxHP+1]
|
|
ld [hli], a
|
|
xor a
|
|
inc hl
|
|
ld [hl], a ; init status to 0
|
|
jr .copyTypes
|
|
; if it's a trainer mon, copy the HP and status from the enemy party data
|
|
.copyHPAndStatusFromPartyData
|
|
ld hl, wEnemyMon1HP
|
|
ld a, [wWhichPokemon]
|
|
ld bc, wEnemyMon2 - wEnemyMon1
|
|
call AddNTimes
|
|
ld a, [hli]
|
|
ld [wEnemyMonHP], a
|
|
ld a, [hli]
|
|
ld [wEnemyMonHP + 1], a
|
|
ld a, [wWhichPokemon]
|
|
ld [wEnemyMonPartyPos], a
|
|
inc hl
|
|
ld a, [hl]
|
|
ld [wEnemyMonStatus], a
|
|
jr .copyTypes
|
|
.copyTypes
|
|
ld hl, wMonHTypes
|
|
ld de, wEnemyMonType
|
|
ld a, [hli] ; copy type 1
|
|
ld [de], a
|
|
inc de
|
|
ld a, [hli] ; copy type 2
|
|
ld [de], a
|
|
inc de
|
|
ld a, [hli] ; copy catch rate
|
|
ld [de], a
|
|
inc de
|
|
ld a, [wIsInBattle]
|
|
cp $2 ; is it a trainer battle?
|
|
jr nz, .copyStandardMoves
|
|
; if it's a trainer battle, copy moves from enemy party data
|
|
ld hl, wEnemyMon1Moves
|
|
ld a, [wWhichPokemon]
|
|
ld bc, wEnemyMon2 - wEnemyMon1
|
|
call AddNTimes
|
|
ld bc, NUM_MOVES
|
|
call CopyData
|
|
jr .loadMovePPs
|
|
.copyStandardMoves
|
|
; for a wild mon, first copy default moves from the mon header
|
|
ld hl, wMonHMoves
|
|
ld a, [hli]
|
|
ld [de], a
|
|
inc de
|
|
ld a, [hli]
|
|
ld [de], a
|
|
inc de
|
|
ld a, [hli]
|
|
ld [de], a
|
|
inc de
|
|
ld a, [hl]
|
|
ld [de], a
|
|
dec de
|
|
dec de
|
|
dec de
|
|
xor a
|
|
ld [wLearningMovesFromDayCare], a
|
|
predef WriteMonMoves ; get moves based on current level
|
|
.loadMovePPs
|
|
ld hl, wEnemyMonMoves
|
|
ld de, wEnemyMonPP - 1
|
|
predef LoadMovePPs
|
|
ld hl, wMonHBaseStats
|
|
ld de, wEnemyMonBaseStats
|
|
ld b, NUM_STATS
|
|
.copyBaseStatsLoop
|
|
ld a, [hli]
|
|
ld [de], a
|
|
inc de
|
|
dec b
|
|
jr nz, .copyBaseStatsLoop
|
|
ld hl, wMonHCatchRate
|
|
ld a, [hli]
|
|
ld [de], a
|
|
inc de
|
|
ld a, [hl] ; base exp
|
|
ld [de], a
|
|
ld a, [wEnemyMonSpecies2]
|
|
ld [wd11e], a
|
|
call GetMonName
|
|
ld hl, wcd6d
|
|
ld de, wEnemyMonNick
|
|
ld bc, NAME_LENGTH
|
|
call CopyData
|
|
ld a, [wEnemyMonSpecies2]
|
|
ld [wd11e], a
|
|
predef IndexToPokedex
|
|
ld a, [wd11e]
|
|
dec a
|
|
ld c, a
|
|
ld b, FLAG_SET
|
|
ld hl, wPokedexSeen
|
|
predef FlagActionPredef ; mark this mon as seen in the pokedex
|
|
ld hl, wEnemyMonLevel
|
|
ld de, wEnemyMonUnmodifiedLevel
|
|
ld bc, 1 + NUM_STATS * 2
|
|
call CopyData
|
|
ld a, $7 ; default stat mod
|
|
ld b, NUM_STAT_MODS ; number of stat mods
|
|
ld hl, wEnemyMonStatMods
|
|
.statModLoop
|
|
ld [hli], a
|
|
dec b
|
|
jr nz, .statModLoop
|
|
ret
|
|
|
|
; calls BattleTransition to show the battle transition animation and initializes some battle variables
|
|
DoBattleTransitionAndInitBattleVariables:
|
|
ld a, [wLinkState]
|
|
cp LINK_STATE_BATTLING
|
|
jr nz, .next
|
|
; link battle
|
|
xor a
|
|
ld [wMenuJoypadPollCount], a
|
|
callfar DisplayLinkBattleVersusTextBox
|
|
ld a, $1
|
|
ld [wUpdateSpritesEnabled], a
|
|
call ClearScreen
|
|
.next
|
|
call DelayFrame
|
|
predef BattleTransition
|
|
callfar LoadHudAndHpBarAndStatusTilePatterns
|
|
ld a, $1
|
|
ldh [hAutoBGTransferEnabled], a
|
|
ld a, $ff
|
|
ld [wUpdateSpritesEnabled], a
|
|
call ClearSprites
|
|
call ClearScreen
|
|
xor a
|
|
ldh [hAutoBGTransferEnabled], a
|
|
ldh [hWY], a
|
|
ldh [rWY], a
|
|
ldh [hTileAnimations], a
|
|
ld hl, wPlayerStatsToDouble
|
|
ld [hli], a
|
|
ld [hli], a
|
|
ld [hli], a
|
|
ld [hli], a
|
|
ld [hl], a
|
|
ld [wPlayerDisabledMove], a
|
|
ret
|
|
|
|
; swaps the level values of the BattleMon and EnemyMon structs
|
|
SwapPlayerAndEnemyLevels:
|
|
push bc
|
|
ld a, [wBattleMonLevel]
|
|
ld b, a
|
|
ld a, [wEnemyMonLevel]
|
|
ld [wBattleMonLevel], a
|
|
ld a, b
|
|
ld [wEnemyMonLevel], a
|
|
pop bc
|
|
ret
|
|
|
|
; loads either red back pic or old man back pic
|
|
; also writes OAM data and loads tile patterns for the Red or Old Man back sprite's head
|
|
; (for use when scrolling the player sprite and enemy's silhouettes on screen)
|
|
LoadPlayerBackPic:
|
|
ld a, [wBattleType]
|
|
dec a ; is it the old man tutorial?
|
|
ld de, RedPicBack
|
|
jr nz, .next
|
|
ld de, OldManPicBack
|
|
.next
|
|
ld a, BANK(RedPicBack)
|
|
ASSERT BANK(RedPicBack) == BANK(OldManPicBack)
|
|
call UncompressSpriteFromDE
|
|
predef ScaleSpriteByTwo
|
|
ld hl, wShadowOAM
|
|
xor a
|
|
ldh [hOAMTile], a ; initial tile number
|
|
ld b, $7 ; 7 columns
|
|
ld e, $a0 ; X for the left-most column
|
|
.loop ; each loop iteration writes 3 OAM entries in a vertical column
|
|
ld c, $3 ; 3 tiles per column
|
|
ld d, $38 ; Y for the top of each column
|
|
.innerLoop ; each loop iteration writes 1 OAM entry in the column
|
|
ld [hl], d ; OAM Y
|
|
inc hl
|
|
ld [hl], e ; OAM X
|
|
ld a, $8 ; height of tile
|
|
add d ; increase Y by height of tile
|
|
ld d, a
|
|
inc hl
|
|
ldh a, [hOAMTile]
|
|
ld [hli], a ; OAM tile number
|
|
inc a ; increment tile number
|
|
ldh [hOAMTile], a
|
|
inc hl
|
|
dec c
|
|
jr nz, .innerLoop
|
|
ldh a, [hOAMTile]
|
|
add $4 ; increase tile number by 4
|
|
ldh [hOAMTile], a
|
|
ld a, $8 ; width of tile
|
|
add e ; increase X by width of tile
|
|
ld e, a
|
|
dec b
|
|
jr nz, .loop
|
|
ld de, vBackPic
|
|
call InterlaceMergeSpriteBuffers
|
|
ld a, $a
|
|
ld [MBC1SRamEnable], a
|
|
xor a
|
|
ld [MBC1SRamBank], a
|
|
ld hl, vSprites
|
|
ld de, sSpriteBuffer1
|
|
ldh a, [hLoadedROMBank]
|
|
ld b, a
|
|
ld c, 7 * 7
|
|
call CopyVideoData
|
|
xor a
|
|
ld [MBC1SRamEnable], a
|
|
ld a, $31
|
|
ldh [hStartTileID], a
|
|
hlcoord 1, 5
|
|
predef_jump CopyUncompressedPicToTilemap
|
|
|
|
; does nothing since no stats are ever selected (barring glitches)
|
|
DoubleOrHalveSelectedStats:
|
|
callfar DoubleSelectedStats
|
|
jpfar HalveSelectedStats
|
|
|
|
ScrollTrainerPicAfterBattle:
|
|
jpfar _ScrollTrainerPicAfterBattle
|
|
|
|
ApplyBurnAndParalysisPenaltiesToPlayer:
|
|
ld a, $1
|
|
jr ApplyBurnAndParalysisPenalties
|
|
|
|
ApplyBurnAndParalysisPenaltiesToEnemy:
|
|
xor a
|
|
|
|
ApplyBurnAndParalysisPenalties:
|
|
ldh [hWhoseTurn], a
|
|
call QuarterSpeedDueToParalysis
|
|
jp HalveAttackDueToBurn
|
|
|
|
QuarterSpeedDueToParalysis:
|
|
ldh a, [hWhoseTurn]
|
|
and a
|
|
jr z, .playerTurn
|
|
.enemyTurn ; quarter the player's speed
|
|
ld a, [wBattleMonStatus]
|
|
and 1 << PAR
|
|
ret z ; return if player not paralysed
|
|
ld hl, wBattleMonSpeed + 1
|
|
ld a, [hld]
|
|
ld b, a
|
|
ld a, [hl]
|
|
srl a
|
|
rr b
|
|
srl a
|
|
rr b
|
|
ld [hli], a
|
|
or b
|
|
jr nz, .storePlayerSpeed
|
|
ld b, 1 ; give the player a minimum of at least one speed point
|
|
.storePlayerSpeed
|
|
ld [hl], b
|
|
ret
|
|
.playerTurn ; quarter the enemy's speed
|
|
ld a, [wEnemyMonStatus]
|
|
and 1 << PAR
|
|
ret z ; return if enemy not paralysed
|
|
ld hl, wEnemyMonSpeed + 1
|
|
ld a, [hld]
|
|
ld b, a
|
|
ld a, [hl]
|
|
srl a
|
|
rr b
|
|
srl a
|
|
rr b
|
|
ld [hli], a
|
|
or b
|
|
jr nz, .storeEnemySpeed
|
|
ld b, 1 ; give the enemy a minimum of at least one speed point
|
|
.storeEnemySpeed
|
|
ld [hl], b
|
|
ret
|
|
|
|
HalveAttackDueToBurn:
|
|
ldh a, [hWhoseTurn]
|
|
and a
|
|
jr z, .playerTurn
|
|
.enemyTurn ; halve the player's attack
|
|
ld a, [wBattleMonStatus]
|
|
and 1 << BRN
|
|
ret z ; return if player not burnt
|
|
ld hl, wBattleMonAttack + 1
|
|
ld a, [hld]
|
|
ld b, a
|
|
ld a, [hl]
|
|
srl a
|
|
rr b
|
|
ld [hli], a
|
|
or b
|
|
jr nz, .storePlayerAttack
|
|
ld b, 1 ; give the player a minimum of at least one attack point
|
|
.storePlayerAttack
|
|
ld [hl], b
|
|
ret
|
|
.playerTurn ; halve the enemy's attack
|
|
ld a, [wEnemyMonStatus]
|
|
and 1 << BRN
|
|
ret z ; return if enemy not burnt
|
|
ld hl, wEnemyMonAttack + 1
|
|
ld a, [hld]
|
|
ld b, a
|
|
ld a, [hl]
|
|
srl a
|
|
rr b
|
|
ld [hli], a
|
|
or b
|
|
jr nz, .storeEnemyAttack
|
|
ld b, 1 ; give the enemy a minimum of at least one attack point
|
|
.storeEnemyAttack
|
|
ld [hl], b
|
|
ret
|
|
|
|
CalculateModifiedStats:
|
|
ld c, 0
|
|
.loop
|
|
call CalculateModifiedStat
|
|
inc c
|
|
ld a, c
|
|
cp NUM_STATS - 1
|
|
jr nz, .loop
|
|
ret
|
|
|
|
; calculate modified stat for stat c (0 = attack, 1 = defense, 2 = speed, 3 = special)
|
|
CalculateModifiedStat:
|
|
push bc
|
|
push bc
|
|
ld a, [wCalculateWhoseStats]
|
|
and a
|
|
ld a, c
|
|
ld hl, wBattleMonAttack
|
|
ld de, wPlayerMonUnmodifiedAttack
|
|
ld bc, wPlayerMonStatMods
|
|
jr z, .next
|
|
ld hl, wEnemyMonAttack
|
|
ld de, wEnemyMonUnmodifiedAttack
|
|
ld bc, wEnemyMonStatMods
|
|
.next
|
|
add c
|
|
ld c, a
|
|
jr nc, .noCarry1
|
|
inc b
|
|
.noCarry1
|
|
ld a, [bc]
|
|
pop bc
|
|
ld b, a
|
|
push bc
|
|
sla c
|
|
ld b, 0
|
|
add hl, bc
|
|
ld a, c
|
|
add e
|
|
ld e, a
|
|
jr nc, .noCarry2
|
|
inc d
|
|
.noCarry2
|
|
pop bc
|
|
push hl
|
|
ld hl, StatModifierRatios
|
|
dec b
|
|
sla b
|
|
ld c, b
|
|
ld b, 0
|
|
add hl, bc
|
|
xor a
|
|
ldh [hMultiplicand], a
|
|
ld a, [de]
|
|
ldh [hMultiplicand + 1], a
|
|
inc de
|
|
ld a, [de]
|
|
ldh [hMultiplicand + 2], a
|
|
ld a, [hli]
|
|
ldh [hMultiplier], a
|
|
call Multiply
|
|
ld a, [hl]
|
|
ldh [hDivisor], a
|
|
ld b, $4
|
|
call Divide
|
|
pop hl
|
|
ldh a, [hDividend + 3]
|
|
sub LOW(MAX_STAT_VALUE)
|
|
ldh a, [hDividend + 2]
|
|
sbc HIGH(MAX_STAT_VALUE)
|
|
jp c, .storeNewStatValue
|
|
; cap the stat at MAX_STAT_VALUE (999)
|
|
ld a, HIGH(MAX_STAT_VALUE)
|
|
ldh [hDividend + 2], a
|
|
ld a, LOW(MAX_STAT_VALUE)
|
|
ldh [hDividend + 3], a
|
|
.storeNewStatValue
|
|
ldh a, [hDividend + 2]
|
|
ld [hli], a
|
|
ld b, a
|
|
ldh a, [hDividend + 3]
|
|
ld [hl], a
|
|
or b
|
|
jr nz, .done
|
|
inc [hl] ; if the stat is 0, bump it up to 1
|
|
.done
|
|
pop bc
|
|
ret
|
|
|
|
ApplyBadgeStatBoosts:
|
|
ld a, [wLinkState]
|
|
cp LINK_STATE_BATTLING
|
|
ret z ; return if link battle
|
|
ld a, [wObtainedBadges]
|
|
ld b, a
|
|
ld hl, wBattleMonAttack
|
|
ld c, $4
|
|
; the boost is applied for badges whose bit position is even
|
|
; the order of boosts matches the order they are laid out in RAM
|
|
; Boulder (bit 0) - attack
|
|
; Thunder (bit 2) - defense
|
|
; Soul (bit 4) - speed
|
|
; Volcano (bit 6) - special
|
|
.loop
|
|
srl b
|
|
call c, .applyBoostToStat
|
|
inc hl
|
|
inc hl
|
|
srl b
|
|
dec c
|
|
jr nz, .loop
|
|
ret
|
|
|
|
; multiply stat at hl by 1.125
|
|
; cap stat at MAX_STAT_VALUE
|
|
.applyBoostToStat
|
|
ld a, [hli]
|
|
ld d, a
|
|
ld e, [hl]
|
|
srl d
|
|
rr e
|
|
srl d
|
|
rr e
|
|
srl d
|
|
rr e
|
|
ld a, [hl]
|
|
add e
|
|
ld [hld], a
|
|
ld a, [hl]
|
|
adc d
|
|
ld [hli], a
|
|
ld a, [hld]
|
|
sub LOW(MAX_STAT_VALUE)
|
|
ld a, [hl]
|
|
sbc HIGH(MAX_STAT_VALUE)
|
|
ret c
|
|
ld a, HIGH(MAX_STAT_VALUE)
|
|
ld [hli], a
|
|
ld a, LOW(MAX_STAT_VALUE)
|
|
ld [hld], a
|
|
ret
|
|
|
|
LoadHudAndHpBarAndStatusTilePatterns:
|
|
call LoadHpBarAndStatusTilePatterns
|
|
|
|
LoadHudTilePatterns:
|
|
ldh a, [rLCDC]
|
|
add a ; is LCD disabled?
|
|
jr c, .lcdEnabled
|
|
.lcdDisabled
|
|
ld hl, BattleHudTiles1
|
|
ld de, vChars2 tile $6d
|
|
ld bc, BattleHudTiles1End - BattleHudTiles1
|
|
ld a, BANK(BattleHudTiles1)
|
|
call FarCopyDataDouble
|
|
ld hl, BattleHudTiles2
|
|
ld de, vChars2 tile $73
|
|
ld bc, BattleHudTiles3End - BattleHudTiles2
|
|
ld a, BANK(BattleHudTiles2)
|
|
jp FarCopyDataDouble
|
|
.lcdEnabled
|
|
ld de, BattleHudTiles1
|
|
ld hl, vChars2 tile $6d
|
|
lb bc, BANK(BattleHudTiles1), (BattleHudTiles1End - BattleHudTiles1) / $8
|
|
call CopyVideoDataDouble
|
|
ld de, BattleHudTiles2
|
|
ld hl, vChars2 tile $73
|
|
lb bc, BANK(BattleHudTiles2), (BattleHudTiles3End - BattleHudTiles2) / $8
|
|
jp CopyVideoDataDouble
|
|
|
|
PrintEmptyString:
|
|
ld hl, .emptyString
|
|
jp PrintText
|
|
|
|
.emptyString
|
|
db "@"
|
|
|
|
|
|
BattleRandom:
|
|
; Link battles use a shared PRNG.
|
|
|
|
ld a, [wLinkState]
|
|
cp LINK_STATE_BATTLING
|
|
jp nz, Random
|
|
|
|
push hl
|
|
push bc
|
|
ld a, [wLinkBattleRandomNumberListIndex]
|
|
ld c, a
|
|
ld b, 0
|
|
ld hl, wLinkBattleRandomNumberList
|
|
add hl, bc
|
|
inc a
|
|
ld [wLinkBattleRandomNumberListIndex], a
|
|
cp 9
|
|
ld a, [hl]
|
|
pop bc
|
|
pop hl
|
|
vc_hook Unknown_BattleRandom_ret_c
|
|
vc_patch BattleRandom_ret
|
|
IF DEF(_RED_VC) || DEF(_BLUE_VC)
|
|
ret
|
|
ELSE
|
|
ret c
|
|
ENDC
|
|
vc_patch_end
|
|
|
|
; if we picked the last seed, we need to recalculate the nine seeds
|
|
push hl
|
|
push bc
|
|
push af
|
|
|
|
; point to seed 0 so we pick the first number the next time
|
|
xor a
|
|
ld [wLinkBattleRandomNumberListIndex], a
|
|
|
|
ld hl, wLinkBattleRandomNumberList
|
|
ld b, 9
|
|
.loop
|
|
ld a, [hl]
|
|
ld c, a
|
|
; multiply by 5
|
|
add a
|
|
add a
|
|
add c
|
|
; add 1
|
|
inc a
|
|
ld [hli], a
|
|
dec b
|
|
jr nz, .loop
|
|
|
|
pop af
|
|
pop bc
|
|
pop hl
|
|
ret
|
|
|
|
|
|
HandleExplodingAnimation:
|
|
ldh a, [hWhoseTurn]
|
|
and a
|
|
ld hl, wEnemyMonType1
|
|
ld de, wEnemyBattleStatus1
|
|
ld a, [wPlayerMoveNum]
|
|
jr z, .player
|
|
ld hl, wBattleMonType1
|
|
ld de, wEnemyBattleStatus1
|
|
ld a, [wEnemyMoveNum]
|
|
.player
|
|
cp SELFDESTRUCT
|
|
jr z, .isExplodingMove
|
|
cp EXPLOSION
|
|
ret nz
|
|
.isExplodingMove
|
|
ld a, [de]
|
|
bit INVULNERABLE, a ; fly/dig
|
|
ret nz
|
|
ld a, [hli]
|
|
cp GHOST
|
|
ret z
|
|
ld a, [hl]
|
|
cp GHOST
|
|
ret z
|
|
ld a, [wMoveMissed]
|
|
and a
|
|
ret nz
|
|
ld a, 5
|
|
ld [wAnimationType], a
|
|
|
|
PlayMoveAnimation:
|
|
ld [wAnimationID], a
|
|
vc_hook_red Reduce_move_anim_flashing_Confusion
|
|
call Delay3
|
|
vc_hook_red Reduce_move_anim_flashing_Psychic
|
|
predef_jump MoveAnimation
|
|
|
|
InitBattle::
|
|
ld a, [wCurOpponent]
|
|
and a
|
|
jr z, DetermineWildOpponent
|
|
|
|
InitOpponent:
|
|
ld a, [wCurOpponent]
|
|
ld [wcf91], a
|
|
ld [wEnemyMonSpecies2], a
|
|
jr InitBattleCommon
|
|
|
|
DetermineWildOpponent:
|
|
ld a, [wd732]
|
|
bit BIT_DEBUG_MODE, a
|
|
jr z, .notDebugMode
|
|
ldh a, [hJoyHeld]
|
|
bit BIT_B_BUTTON, a ; disable wild encounters
|
|
ret nz
|
|
.notDebugMode
|
|
ld a, [wNumberOfNoRandomBattleStepsLeft]
|
|
and a
|
|
ret nz
|
|
callfar TryDoWildEncounter
|
|
ret nz
|
|
InitBattleCommon:
|
|
ld a, [wMapPalOffset]
|
|
push af
|
|
ld hl, wLetterPrintingDelayFlags
|
|
ld a, [hl]
|
|
push af
|
|
res 1, [hl]
|
|
callfar InitBattleVariables
|
|
ld a, [wEnemyMonSpecies2]
|
|
sub OPP_ID_OFFSET
|
|
jp c, InitWildBattle
|
|
ld [wTrainerClass], a
|
|
call GetTrainerInformation
|
|
callfar ReadTrainer
|
|
call DoBattleTransitionAndInitBattleVariables
|
|
call _LoadTrainerPic
|
|
xor a
|
|
ld [wEnemyMonSpecies2], a
|
|
ldh [hStartTileID], a
|
|
dec a
|
|
ld [wAICount], a
|
|
hlcoord 12, 0
|
|
predef CopyUncompressedPicToTilemap
|
|
ld a, $ff
|
|
ld [wEnemyMonPartyPos], a
|
|
ld a, $2
|
|
ld [wIsInBattle], a
|
|
jp _InitBattleCommon
|
|
|
|
InitWildBattle:
|
|
ld a, $1
|
|
ld [wIsInBattle], a
|
|
call LoadEnemyMonData
|
|
call DoBattleTransitionAndInitBattleVariables
|
|
ld a, [wCurOpponent]
|
|
cp RESTLESS_SOUL
|
|
jr z, .isGhost
|
|
call IsGhostBattle
|
|
jr nz, .isNoGhost
|
|
.isGhost
|
|
ld hl, wMonHSpriteDim
|
|
ld a, $66
|
|
ld [hli], a ; write sprite dimensions
|
|
ld bc, GhostPic
|
|
ld a, c
|
|
ld [hli], a ; write front sprite pointer
|
|
ld [hl], b
|
|
ld hl, wEnemyMonNick ; set name to "GHOST"
|
|
ld a, "G"
|
|
ld [hli], a
|
|
ld a, "H"
|
|
ld [hli], a
|
|
ld a, "O"
|
|
ld [hli], a
|
|
ld a, "S"
|
|
ld [hli], a
|
|
ld a, "T"
|
|
ld [hli], a
|
|
ld [hl], "@"
|
|
ld a, [wcf91]
|
|
push af
|
|
ld a, MON_GHOST
|
|
ld [wcf91], a
|
|
ld de, vFrontPic
|
|
call LoadMonFrontSprite ; load ghost sprite
|
|
pop af
|
|
ld [wcf91], a
|
|
jr .spriteLoaded
|
|
.isNoGhost
|
|
ld de, vFrontPic
|
|
call LoadMonFrontSprite ; load mon sprite
|
|
.spriteLoaded
|
|
xor a
|
|
ld [wTrainerClass], a
|
|
ldh [hStartTileID], a
|
|
hlcoord 12, 0
|
|
predef CopyUncompressedPicToTilemap
|
|
|
|
; common code that executes after init battle code specific to trainer or wild battles
|
|
_InitBattleCommon:
|
|
ld b, SET_PAL_BATTLE_BLACK
|
|
call RunPaletteCommand
|
|
call SlidePlayerAndEnemySilhouettesOnScreen
|
|
xor a
|
|
ldh [hAutoBGTransferEnabled], a
|
|
ld hl, .emptyString
|
|
call PrintText
|
|
call SaveScreenTilesToBuffer1
|
|
call ClearScreen
|
|
ld a, $98
|
|
ldh [hAutoBGTransferDest + 1], a
|
|
ld a, $1
|
|
ldh [hAutoBGTransferEnabled], a
|
|
call Delay3
|
|
ld a, $9c
|
|
ldh [hAutoBGTransferDest + 1], a
|
|
call LoadScreenTilesFromBuffer1
|
|
hlcoord 9, 7
|
|
lb bc, 5, 10
|
|
call ClearScreenArea
|
|
hlcoord 1, 0
|
|
lb bc, 4, 10
|
|
call ClearScreenArea
|
|
call ClearSprites
|
|
ld a, [wIsInBattle]
|
|
dec a ; is it a wild battle?
|
|
call z, DrawEnemyHUDAndHPBar ; draw enemy HUD and HP bar if it's a wild battle
|
|
call StartBattle
|
|
callfar EndOfBattle
|
|
pop af
|
|
ld [wLetterPrintingDelayFlags], a
|
|
pop af
|
|
ld [wMapPalOffset], a
|
|
ld a, [wSavedTileAnimations]
|
|
ldh [hTileAnimations], a
|
|
scf
|
|
ret
|
|
.emptyString
|
|
db "@"
|
|
|
|
_LoadTrainerPic:
|
|
; wd033-wd034 contain pointer to pic
|
|
ld a, [wTrainerPicPointer]
|
|
ld e, a
|
|
ld a, [wTrainerPicPointer + 1]
|
|
ld d, a ; de contains pointer to trainer pic
|
|
ld a, [wLinkState]
|
|
and a
|
|
ld a, BANK("Pics 6") ; this is where all the trainer pics are (not counting Red's)
|
|
jr z, .loadSprite
|
|
ld a, BANK(RedPicFront)
|
|
.loadSprite
|
|
call UncompressSpriteFromDE
|
|
ld de, vFrontPic
|
|
ld a, $77
|
|
ld c, a
|
|
jp LoadUncompressedSpriteData
|
|
|
|
; unreferenced
|
|
ResetCryModifiers:
|
|
xor a
|
|
ld [wFrequencyModifier], a
|
|
ld [wTempoModifier], a
|
|
jp PlaySound
|
|
|
|
; animates the mon "growing" out of the pokeball
|
|
AnimateSendingOutMon:
|
|
ld a, [wPredefHL]
|
|
ld h, a
|
|
ld a, [wPredefHL + 1]
|
|
ld l, a
|
|
ldh a, [hStartTileID]
|
|
ldh [hBaseTileID], a
|
|
ld b, $4c
|
|
ld a, [wIsInBattle]
|
|
and a
|
|
jr z, .notInBattle
|
|
add b
|
|
ld [hl], a
|
|
call Delay3
|
|
ld bc, -(SCREEN_WIDTH * 2 + 1)
|
|
add hl, bc
|
|
ld a, 1
|
|
ld [wDownscaledMonSize], a
|
|
lb bc, 3, 3
|
|
predef CopyDownscaledMonTiles
|
|
ld c, 4
|
|
call DelayFrames
|
|
ld bc, -(SCREEN_WIDTH * 2 + 1)
|
|
add hl, bc
|
|
xor a
|
|
ld [wDownscaledMonSize], a
|
|
lb bc, 5, 5
|
|
predef CopyDownscaledMonTiles
|
|
ld c, 5
|
|
call DelayFrames
|
|
ld bc, -(SCREEN_WIDTH * 2 + 1)
|
|
jr .next
|
|
.notInBattle
|
|
ld bc, -(SCREEN_WIDTH * 6 + 3)
|
|
.next
|
|
add hl, bc
|
|
ldh a, [hBaseTileID]
|
|
add $31
|
|
jr CopyUncompressedPicToHL
|
|
|
|
CopyUncompressedPicToTilemap:
|
|
ld a, [wPredefHL]
|
|
ld h, a
|
|
ld a, [wPredefHL + 1]
|
|
ld l, a
|
|
ldh a, [hStartTileID]
|
|
CopyUncompressedPicToHL::
|
|
lb bc, 7, 7
|
|
ld de, SCREEN_WIDTH
|
|
push af
|
|
ld a, [wSpriteFlipped]
|
|
and a
|
|
jr nz, .flipped
|
|
pop af
|
|
.loop
|
|
push bc
|
|
push hl
|
|
.innerLoop
|
|
ld [hl], a
|
|
add hl, de
|
|
inc a
|
|
dec c
|
|
jr nz, .innerLoop
|
|
pop hl
|
|
inc hl
|
|
pop bc
|
|
dec b
|
|
jr nz, .loop
|
|
ret
|
|
|
|
.flipped
|
|
push bc
|
|
ld b, 0
|
|
dec c
|
|
add hl, bc
|
|
pop bc
|
|
pop af
|
|
.flippedLoop
|
|
push bc
|
|
push hl
|
|
.flippedInnerLoop
|
|
ld [hl], a
|
|
add hl, de
|
|
inc a
|
|
dec c
|
|
jr nz, .flippedInnerLoop
|
|
pop hl
|
|
dec hl
|
|
pop bc
|
|
dec b
|
|
jr nz, .flippedLoop
|
|
ret
|
|
|
|
LoadMonBackPic:
|
|
; Assumes the monster's attributes have
|
|
; been loaded with GetMonHeader.
|
|
ld a, [wBattleMonSpecies2]
|
|
ld [wcf91], a
|
|
hlcoord 1, 5
|
|
ld b, 7
|
|
ld c, 8
|
|
call ClearScreenArea
|
|
ld hl, wMonHBackSprite - wMonHeader
|
|
call UncompressMonSprite
|
|
predef ScaleSpriteByTwo
|
|
ld de, vBackPic
|
|
call InterlaceMergeSpriteBuffers ; combine the two buffers to a single 2bpp sprite
|
|
ld hl, vSprites
|
|
ld de, vBackPic
|
|
ld c, (2*SPRITEBUFFERSIZE)/16 ; count of 16-byte chunks to be copied
|
|
ldh a, [hLoadedROMBank]
|
|
ld b, a
|
|
jp CopyVideoData
|