pokered-rematch/engine/battle/core.asm

8721 lines
190 KiB
NASM
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

BattleCore:
; These are move effects (second value from the Moves table in bank $E).
ResidualEffects1:
; most non-side effects
db CONVERSION_EFFECT
db HAZE_EFFECT
db SWITCH_AND_TELEPORT_EFFECT
db MIST_EFFECT
db FOCUS_ENERGY_EFFECT
db CONFUSION_EFFECT
db HEAL_EFFECT
db TRANSFORM_EFFECT
db LIGHT_SCREEN_EFFECT
db REFLECT_EFFECT
db POISON_EFFECT
db PARALYZE_EFFECT
db SUBSTITUTE_EFFECT
db MIMIC_EFFECT
db LEECH_SEED_EFFECT
db SPLASH_EFFECT
db -1
SetDamageEffects:
; moves that do damage but not through normal calculations
; e.g., Super Fang, Psywave
db SUPER_FANG_EFFECT
db SPECIAL_DAMAGE_EFFECT
db -1
ResidualEffects2:
; non-side effects not included in ResidualEffects1
; stat-affecting moves, sleep-inflicting moves, and Bide
; e.g., Meditate, Bide, Hypnosis
db $01
db ATTACK_UP1_EFFECT
db DEFENSE_UP1_EFFECT
db SPEED_UP1_EFFECT
db SPECIAL_UP1_EFFECT
db ACCURACY_UP1_EFFECT
db EVASION_UP1_EFFECT
db ATTACK_DOWN1_EFFECT
db DEFENSE_DOWN1_EFFECT
db SPEED_DOWN1_EFFECT
db SPECIAL_DOWN1_EFFECT
db ACCURACY_DOWN1_EFFECT
db EVASION_DOWN1_EFFECT
db BIDE_EFFECT
db SLEEP_EFFECT
db ATTACK_UP2_EFFECT
db DEFENSE_UP2_EFFECT
db SPEED_UP2_EFFECT
db SPECIAL_UP2_EFFECT
db ACCURACY_UP2_EFFECT
db EVASION_UP2_EFFECT
db ATTACK_DOWN2_EFFECT
db DEFENSE_DOWN2_EFFECT
db SPEED_DOWN2_EFFECT
db SPECIAL_DOWN2_EFFECT
db ACCURACY_DOWN2_EFFECT
db EVASION_DOWN2_EFFECT
db -1
AlwaysHappenSideEffects:
; Attacks that aren't finished after they faint the opponent.
db DRAIN_HP_EFFECT
db EXPLODE_EFFECT
db DREAM_EATER_EFFECT
db PAY_DAY_EFFECT
db TWO_TO_FIVE_ATTACKS_EFFECT
db $1E
db ATTACK_TWICE_EFFECT
db RECOIL_EFFECT
db TWINEEDLE_EFFECT
db RAGE_EFFECT
db -1
SpecialEffects:
; Effects from arrays 2, 4, and 5B, minus Twineedle and Rage.
; Includes all effects that do not need to be called at the end of
; ExecutePlayerMove (or ExecuteEnemyMove), because they have already been handled
db DRAIN_HP_EFFECT
db EXPLODE_EFFECT
db DREAM_EATER_EFFECT
db PAY_DAY_EFFECT
db SWIFT_EFFECT
db TWO_TO_FIVE_ATTACKS_EFFECT
db $1E
db CHARGE_EFFECT
db SUPER_FANG_EFFECT
db SPECIAL_DAMAGE_EFFECT
db FLY_EFFECT
db ATTACK_TWICE_EFFECT
db JUMP_KICK_EFFECT
db RECOIL_EFFECT
; fallthrough to Next EffectsArray
SpecialEffectsCont:
; damaging moves whose effect is executed prior to damage calculation
db THRASH_PETAL_DANCE_EFFECT
db TRAPPING_EFFECT
db -1
SlidePlayerAndEnemySilhouettesOnScreen:
call LoadPlayerBackPic
ld a, MESSAGE_BOX ; the usual text box at the bottom of the screen
ld [wTextBoxID], a
call DisplayTextBoxID
coord hl, 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
coord hl, 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
ld [hWY], a
ld [rWY], a
xor a
ld [hTilesetType], a
ld [hSCY], a
dec a
ld [wUpdateSpritesEnabled], a
call Delay3
xor a
ld [H_AUTOBGTRANSFERENABLED], a
ld b, $70
ld c, $90
ld a, c
ld [hSCX], a
call DelayFrame
ld a, %11100100 ; inverted palette for silhouette effect
ld [rBGP], a
ld [rOBP0], a
ld [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
ld [hSCX], a
dec c
dec c
jr nz, .slideSilhouettesLoop
ld a, $1
ld [H_AUTOBGTRANSFERENABLED], a
ld a, $31
ld [hStartTileID], a
coord hl, 1, 5
predef CopyUncompressedPicToTilemap
xor a
ld [hWY], a
ld [rWY], a
inc a
ld [H_AUTOBGTRANSFERENABLED], a
call Delay3
ld b, SET_PAL_BATTLE
call RunPaletteCommand
call HideSprites
jpab 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, wOAMBuffer + $01
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:
ld a, [rLY]
cp l
jr nz, SetScrollXForSlidingPlayerBodyLeft
ld a, h
ld [rSCX], a
.loop
ld 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
callab 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
TX_FAR _OutOfSafariBallsText
db "@"
.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
coord hl, 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
ld [H_WHOSETURN], a
jpab AnimationSlideEnemyMonOff
WildRanText:
TX_FAR _WildRanText
db "@"
EnemyRanText:
TX_FAR _EnemyRanText
db "@"
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 << NeedsToRecharge) | (1 << UsingRage) ; 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 << ThrashingAbout) | (1 << ChargingUp) ; 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 ; is mon frozen or asleep?
jr nz, .selectEnemyMove ; if so, jump
ld a, [wPlayerBattleStatus1]
and (1 << StoringEnergy) | (1 << UsingTrappingMove) ; check player is using Bide or using a multi-turn attack like wrap
jr nz, .selectEnemyMove ; if so, jump
ld a, [wEnemyBattleStatus1]
bit UsingTrappingMove, a ; check if enemy is using a multi-turn attack like wrap
jr z, .selectPlayerMove ; if not, jump
; enemy is using a mult-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 $f
jp z, EnemyRan
cp $e
jr z, .noLinkBattle
cp $d
jr z, .noLinkBattle
sub $4
jr c, .noLinkBattle
; the link battle enemy has switched mons
ld a, [wPlayerBattleStatus1]
bit UsingTrappingMove, 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
callab 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
ld a, [$ffaa]
cp $2
jr z, .invertOutcome
call BattleRandom
cp $80
jr c, .playerMovesFirst
jr .enemyMovesFirst
.invertOutcome
call BattleRandom
cp $80
jr c, .enemyMovesFirst
jr .playerMovesFirst
.enemyMovesFirst
ld a, $1
ld [H_WHOSETURN], a
callab 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
ld [H_WHOSETURN], a
callab 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
ld a, [H_WHOSETURN]
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
ld a, [H_WHOSETURN]
and a
jr z, .playersTurn2
ld de, wEnemyBattleStatus2
.playersTurn2
ld a, [de]
add a
jr nc, .notLeechSeeded
push hl
ld a, [H_WHOSETURN]
push af
xor $1
ld [H_WHOSETURN], a
xor a
ld [wAnimationType], a
ld a,ABSORB
call PlayMoveAnimation ; play leech seed animation (from opposing mon)
pop af
ld [H_WHOSETURN], 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:
TX_FAR _HurtByPoisonText
db "@"
HurtByBurnText:
TX_FAR _HurtByBurnText
db "@"
HurtByLeechSeedText:
TX_FAR _HurtByLeechSeedText
db "@"
; 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
ld a, [H_WHOSETURN]
and a
jr z, .playersTurn
ld hl, wEnemyBattleStatus3
ld de, wEnemyToxicCounter
.playersTurn
bit BadlyPoisoned, [hl]
jr z, .noToxic
ld a, [de] ; increment toxic counter
inc a
ld [de], a
ld hl, $0000
.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 substracted was capped to prevent overkill
HandlePoisonBurnLeechSeed_IncreaseEnemyHP:
push hl
ld hl, wEnemyMonMaxHP
ld a, [H_WHOSETURN]
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
ld a, [H_WHOSETURN]
xor $1
ld [H_WHOSETURN], a
call UpdateCurMonHPBar
ld a, [H_WHOSETURN]
xor $1
ld [H_WHOSETURN], a
pop hl
ret
UpdateCurMonHPBar:
coord hl, 10, 9 ; tile pointer to player HP bar
ld a, [H_WHOSETURN]
and a
ld a, $1
jr z, .playersTurn
coord hl, 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 UsingTrappingMove, [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 UsingTrappingMove, [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 AttackingMultipleTimes, [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
coord hl, 12, 5
coord de, 12, 6
call SlideDownFaintedMonPic
coord hl, 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 + CH4]
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
callab 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
jpab GainExperience
EnemyMonFaintedText:
TX_FAR _EnemyMonFaintedText
db "@"
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 + CH4], 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
callab DrawEnemyPokeballs
ld a, [wLinkState]
cp LINK_STATE_BATTLING
jr nz, .notLinkBattle
; link battle
call LinkBattleExchangeData
ld a, [wSerialExchangeNybbleReceiveData]
cp $f
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 SONY3 ; 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:
TX_FAR _MoneyForWinningText
db "@"
TrainerDefeatedText:
TX_FAR _TrainerDefeatedText
db "@"
PlayBattleVictoryMusic:
push af
ld a, $ff
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
coord hl, 9, 7
lb bc, 5, 11
call ClearScreenArea
coord hl, 1, 10
coord de, 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:
TX_FAR _PlayerMonFaintedText
db "@"
; 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
coord hl, 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:
TX_FAR _UseNextMonText
db "@"
; 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
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 approriate 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, .notSony1Battle
ld a, [wCurOpponent]
cp OPP_SONY1
jr nz, .notSony1Battle
coord hl, 0, 0 ; sony 1 battle
lb bc, 8, 21
call ClearScreenArea
call ScrollTrainerPicAfterBattle
ld c, 40
call DelayFrames
ld hl, Sony1WinText
call PrintText
ld a, [wCurMap]
cp OAKS_LAB
ret z ; starter battle in oak's lab: don't black out
.notSony1Battle
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
Sony1WinText:
TX_FAR _Sony1WinText
db "@"
PlayerBlackedOutText2:
TX_FAR _PlayerBlackedOutText2
db "@"
LinkBattleLostText:
TX_FAR _LinkBattleLostText
db "@"
; slides pic of fainted mon downwards until it disappears
; bug: when this is called, [H_AUTOBGTRANSFERENABLED] 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, [H_AUTOBGTRANSFERENABLED] is non-zero, so there is screen tearing
SlideTrainerPicOffScreen:
ld [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
ld a, [hSlideAmount]
ld c, a
.columnLoop
ld 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]
coord hl, 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 6,a
jr nz,.next4
ld hl, TrainerAboutToUseText
call PrintText
coord hl, 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
coord hl, 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
ld [hStartTileID],a
coord hl, 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:
TX_FAR _TrainerAboutToUseText
db "@"
TrainerSentOutText:
TX_FAR _TrainerSentOutText
db "@"
; 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:
TX_FAR _NoWillText
db "@"
; 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]
ld [H_MULTIPLICAND + 1], a
ld a, [hl]
ld [H_MULTIPLICAND + 2], a
ld a, [de]
ld [hEnemySpeed], a
inc de
ld a, [de]
ld [hEnemySpeed + 1], a
call LoadScreenTilesFromBuffer1
ld de, H_MULTIPLICAND + 1
ld hl, hEnemySpeed
ld c, 2
call StringCmp
jr nc, .canEscape ; jump if player speed greater than enemy speed
xor a
ld [H_MULTIPLICAND], a
ld a, 32
ld [H_MULTIPLIER], a
call Multiply ; multiply player speed by 32
ld a, [H_PRODUCT + 2]
ld [H_DIVIDEND], a
ld a, [H_PRODUCT + 3]
ld [H_DIVIDEND + 1], a
ld a, [hEnemySpeed]
ld b, a
ld 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
ld [H_DIVISOR], a ; ((enemy speed / 4) % 256)
ld b, $2
call Divide ; divide (player speed * 32) by ((enemy speed / 4) % 256)
ld a, [H_QUOTIENT + 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
ld a, [H_QUOTIENT + 3]
add b
ld [H_QUOTIENT + 3], a
jr c, .canEscape
jr .loop
.compareWithRandomValue
call BattleRandom
ld b, a
ld a, [H_QUOTIENT + 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, $f
ld [wPlayerMoveListIndex], a
call LinkBattleExchangeData
call LoadScreenTilesFromBuffer1
ld a, [wSerialExchangeNybbleReceiveData]
cp $f
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:
TX_FAR _CantEscapeText
db "@"
NoRunningText:
TX_FAR _NoRunningText
db "@"
GotAwayText:
TX_FAR _GotAwayText
db "@"
; 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, NUM_DVS
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, NUM_DVS
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:
callab 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
ld [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 UsingTrappingMove, [hl]
ld a, $1
ld [H_WHOSETURN], a
ld a, POOF_ANIM
call PlayMoveAnimation
coord hl, 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:
coord hl, 1, 5
lb bc, 7, 7
call ClearScreenArea
coord hl, 3, 7
lb bc, 5, 5
xor a
ld [wDownscaledMonSize], a
ld [hBaseTileID], a
predef CopyDownscaledMonTiles
ld c, 4
call DelayFrames
call .clearScreenArea
coord hl, 4, 9
lb bc, 3, 3
ld a, 1
ld [wDownscaledMonSize], a
xor a
ld [hBaseTileID], a
predef CopyDownscaledMonTiles
call Delay3
call .clearScreenArea
ld a, $4c
Coorda 5, 11
.clearScreenArea
coord hl, 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
ld [H_AUTOBGTRANSFERENABLED], a
coord hl, 9, 7
lb bc, 5, 11
call ClearScreenArea
callab PlacePlayerHUDTiles
coord hl, 18, 9
ld [hl], $73
ld de, wBattleMonNick
coord hl, 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
coord hl, 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
coord hl, 10, 9
predef DrawHP
ld a, $1
ld [H_AUTOBGTRANSFERENABLED], 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 + CH4], a
ret
.setLowHealthAlarm
ld hl, wLowHealthAlarm
set 7, [hl] ;enable low health alarm
ret
DrawEnemyHUDAndHPBar:
xor a
ld [H_AUTOBGTRANSFERENABLED], a
coord hl, 0, 0
lb bc, 4, 12
call ClearScreenArea
callab PlaceEnemyHUDTiles
ld de, wEnemyMonNick
coord hl, 1, 0
call CenterMonName
call PlaceString
coord hl, 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]
ld [H_MULTIPLICAND + 1], a
ld a, [hld]
ld [H_MULTIPLICAND + 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
ld [H_MULTIPLICAND], a
ld a, 48
ld [H_MULTIPLIER], a
call Multiply ; multiply current HP by 48
ld hl, wEnemyMonMaxHP
ld a, [hli]
ld b, a
ld a, [hl]
ld [H_DIVISOR], 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)
ld a, [H_DIVISOR]
srl b
rr a
srl b
rr a
ld [H_DIVISOR], a
ld a, [H_PRODUCT + 2]
ld b, a
srl b
ld a, [H_PRODUCT + 3]
rr a
srl b
rr a
ld [H_PRODUCT + 3], a
ld a, b
ld [H_PRODUCT + 2], a
.doDivide
ld a, [H_PRODUCT + 2]
ld [H_DIVIDEND], a
ld a, [H_PRODUCT + 3]
ld [H_DIVIDEND + 1], a
ld a, $2
ld b, a
call Divide ; divide (current HP * 48) by max HP
ld a, [H_QUOTIENT + 3]
; set variables for DrawHPBar
ld e, a
ld a, $6
ld d, a
ld c, a
.drawHPBar
xor a
ld [wHPBarType], a
coord hl, 2, 2
call DrawHPBar
ld a, $1
ld [H_AUTOBGTRANSFERENABLED], 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
ld a, [wBattleType]
dec a
jp nz, .handleBattleMenuInput ; handle menu input if it's not the old man tutorial
; the following happens for the old man tutorial
ld hl, wPlayerName
ld de, wGrassRate
ld bc, NAME_LENGTH
call CopyData ; temporarily save the player name in unused space,
; which is supposed to get overwritten when entering a
; map with wild Pokémon. Due to an oversight, the data
; may not get overwritten (cinnabar) and the infamous
; Missingno. glitch can show up.
ld hl, .oldManName
ld de, wPlayerName
ld bc, NAME_LENGTH
call CopyData
; the following simulates the keystrokes by drawing menus on screen
coord hl, 9, 14
ld [hl], "▶"
ld c, 80
call DelayFrames
ld [hl], " "
coord hl, 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)
Coorda 15, 14 ; clear upper cursor position in right column
Coorda 15, 16 ; clear lower cursor position in right column
ld b, $9 ; top menu item X
jr .leftColumn_WaitForInput
.safariLeftColumn
Coorda 13, 14
Coorda 13, 16
coord hl, 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 4, a ; check if right was pressed
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)
Coorda 9, 14 ; clear upper cursor position in left column
Coorda 9, 16 ; clear lower cursor position in left column
ld b, $f ; top menu item X
jr .rightColumn_WaitForInput
.safariRightColumn
Coorda 1, 14 ; clear upper cursor position in left column
Coorda 1, 16 ; clear lower cursor position in left column
coord hl, 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
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 CopyStringToCF4B ; copy name
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 UsingTrappingMove, 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 UsingTrappingMove, [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:
TX_FAR _ItemsCantBeUsedHereText
db "@"
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
coord hl, 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 1, a ; was A pressed?
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 HasSubstituteUp, 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:
callab 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:
TX_FAR _AlreadyOutText
db "@"
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
callab FormatMovesString
ret
.writemoves
ld de, wMovesString
ld a, [hFlags_0xFFF6]
set 2, a
ld [hFlags_0xFFF6], a
call PlaceString
ld a, [hFlags_0xFFF6]
res 2, a
ld [hFlags_0xFFF6], a
ret
.regularmenu
call AnyMoveToSelect
ret z
ld hl, wBattleMonMoves
call .loadmoves
coord hl, 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
coord hl, 4, 12
ld [hl], $7a
coord hl, 10, 12
ld [hl], $7e
ei
coord hl, 6, 13
call .writemoves
ld b, $5
ld a, $c
jr .menuset
.mimicmenu
ld hl, wEnemyMonMoves
call .loadmoves
coord hl, 0, 7
ld b, 4
ld c, 14
call TextBoxBorder
coord hl, 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
coord hl, 4, 7
ld b, 4
ld c, 14
call TextBoxBorder
coord hl, 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
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, $ff
.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
coord hl, 1, 14
ld de, WhichTechniqueString
call PlaceString
jr .select
.battleselect
ld a, [wFlags_D733]
bit BIT_TEST_BATTLE, a
jr nz, .select
call PrintMenuItem
ld a, [wMenuItemToSwap]
and a
jr z, .select
coord hl, 5, 13
dec a
ld bc, SCREEN_WIDTH
call AddNTimes
ld [hl], "▷"
.select
ld hl, hFlags_0xFFF6
set 1, [hl]
call HandleMenuInput
ld hl, hFlags_0xFFF6
res 1, [hl]
bit 6, a
jp nz, SelectMenuItem_CursorUp ; up
bit 7, a
jp nz, SelectMenuItem_CursorDown ; down
bit 2, a
jp nz, SwapMovesInMenu ; select
bit 1, a ; B, but was it reset above?
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, .dummy ; game freak derp
.dummy
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:
TX_FAR _MoveNoPPText
db "@"
MoveDisabledText:
TX_FAR _MoveDisabledText
db "@"
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:
TX_FAR _NoMovesLeftText
db "@"
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
ld [H_AUTOBGTRANSFERENABLED], a
coord hl, 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
coord hl, 1, 10
ld de, DisabledText
call PlaceString
jr .moveDisabled
.notDisabled
ld hl, wCurrentMenuItem
dec [hl]
xor a
ld [H_WHOSETURN], 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
callab 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>
coord hl, 1, 9
ld de, TypeText
call PlaceString
coord hl, 7, 11
ld [hl], "/"
coord hl, 5, 9
ld [hl], "/"
coord hl, 5, 11
ld de, wcd6d
lb bc, 1, 2
call PrintNumber
coord hl, 8, 11
ld de, wMaxPP
lb bc, 1, 2
call PrintNumber
call GetCurrentMove
coord hl, 2, 10
predef PrintMoveType
.moveDisabled
ld a, $1
ld [H_AUTOBGTRANSFERENABLED], 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 $e
jp z, .linkedOpponentUsedStruggle
cp $d
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 << NeedsToRecharge) | (1 << UsingRage) ; need to recharge or using rage
ret nz
ld hl, wEnemyBattleStatus1
ld a, [hl]
and (1 << ChargingUp) | (1 << ThrashingAbout) ; using a charging move or thrash/petal dance
ret nz
ld a, [wEnemyMonStatus]
and SLP | 1 << FRZ ; sleeping or frozen
ret nz
ld a, [wEnemyBattleStatus1]
and (1 << UsingTrappingMove) | (1 << StoringEnergy) ; using a trapping move like wrap or bide
ret nz
ld a, [wPlayerBattleStatus1]
bit UsingTrappingMove, 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
callab AIEnemyTrainerChooseMoves
.chooseRandomMove
push hl
call BattleRandom
ld b, $1
cp $3f ; select move 1, [0,3e] (63/256 chance)
jr c, .moveChosen
inc hl
inc b
cp $7f ; select move 2, [3f,7e] (64/256 chance)
jr c, .moveChosen
inc hl
inc b
cp $be ; select move 3, [7f,bd] (63/256 chance)
jr c, .moveChosen
inc hl
inc b ; select move 4, [be,ff] (66/256 chance)
.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 $f ; 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, $e
jr z, .next
dec b
inc a
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
callab PrintWaitingText
.syncLoop1
call Serial_ExchangeNybble
call DelayFrame
ld a, [wSerialExchangeNybbleReceiveData]
inc a
jr z, .syncLoop1
ld b, 10
.syncLoop2
call DelayFrame
call Serial_ExchangeNybble
dec b
jr nz, .syncLoop2
ld b, 10
.syncLoop3
call DelayFrame
call Serial_SendZeroByte
dec b
jr nz, .syncLoop3
ret
ExecutePlayerMove:
xor a
ld [H_WHOSETURN], 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 ChargingUp, [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 ChargingUp,[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 a,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 HasSubstituteUp,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 HasSubstituteUp,a
ld hl,ReshowSubstituteAnim
ld b,BANK(ReshowSubstituteAnim)
call nz,Bankswitch
jr MirrorMoveCheck
playerCheckIfFlyOrChargeEffect:
ld c,30
call DelayFrames
ld a,[wPlayerMoveEffect]
cp a,FLY_EFFECT
jr z,.playAnim
cp a,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 a,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 a,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 a,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
callab 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 AttackingMultipleTimes,[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 AttackingMultipleTimes,[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:
TX_FAR _MultiHitText
db "@"
ExecutePlayerMoveDone:
xor a
ld [wActionResultOrTookBattleTurn],a
ld b,1
ret
PrintGhostText:
; print the ghost battle messages
call IsGhostBattle
ret nz
ld a,[H_WHOSETURN]
and a
jr nz,.Ghost
ld a,[wBattleMonStatus] ; players turn
and a,SLP | (1 << FRZ)
ret nz
ld hl,ScaredText
call PrintText
xor a
ret
.Ghost ; ghosts turn
ld hl,GetOutText
call PrintText
xor a
ret
ScaredText:
TX_FAR _ScaredText
db "@"
GetOutText:
TX_FAR _GetOutText
db "@"
IsGhostBattle:
ld a,[wIsInBattle]
dec a
ret nz
ld a,[wCurMap]
cp a,POKEMONTOWER_1
jr c,.next
cp a,LAVENDER_HOUSE_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 a,SLP ; sleep 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_ANIM - 1
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 UsingTrappingMove,a ; is enemy using a mult-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 NeedsToRecharge,[hl]
jr z,.AnyMoveDisabledCheck
res NeedsToRecharge,[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_ANIM - 1
call PlayMoveAnimation
call BattleRandom
cp a,$80 ; 50% chance to hurt itself
jr c,.TriedToUseDisabledMoveCheck
ld hl,wPlayerBattleStatus1
ld a,[hl]
and a, 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 a,$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 $ff ^ ((1 << StoringEnergy) | (1 << ThrashingAbout) | (1 << ChargingUp) | (1 << UsingTrappingMove))
ld [hl],a
ld a,[wPlayerMoveEffect]
cp a,FLY_EFFECT
jr z,.FlyOrChargeEffect
cp a,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 StoringEnergy,[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 ; acumulate 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 StoringEnergy,[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 ThrashingAbout,[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 ThrashingAbout,[hl] ; no longer thrashing about
set Confused,[hl] ; confused
call BattleRandom
and a,3
inc a
inc a ; confused for 2-5 turns
ld [wPlayerConfusedCounter],a
pop hl ; skip DecrementPP
jp .returnToHL
.MultiturnMoveCheck
bit UsingTrappingMove,[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 UsingRage, 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 CopyStringToCF4B
xor a
ld [wPlayerMoveEffect], a
ld hl, PlayerCanExecuteMove
jp .returnToHL
.returnToHL
xor a
ret
.checkPlayerStatusConditionsDone
ld a, $1
and a
ret
FastAsleepText:
TX_FAR _FastAsleepText
db "@"
WokeUpText:
TX_FAR _WokeUpText
db "@"
IsFrozenText:
TX_FAR _IsFrozenText
db "@"
FullyParalyzedText:
TX_FAR _FullyParalyzedText
db "@"
FlinchedText:
TX_FAR _FlinchedText
db "@"
MustRechargeText:
TX_FAR _MustRechargeText
db "@"
DisabledNoMoreText:
TX_FAR _DisabledNoMoreText
db "@"
IsConfusedText:
TX_FAR _IsConfusedText
db "@"
HurtItselfText:
TX_FAR _HurtItselfText
db "@"
ConfusedNoMoreText:
TX_FAR _ConfusedNoMoreText
db "@"
SavingEnergyText:
TX_FAR _SavingEnergyText
db "@"
UnleashedEnergyText:
TX_FAR _UnleashedEnergyText
db "@"
ThrashingAboutText:
TX_FAR _ThrashingAboutText
db "@"
AttackContinuesText:
TX_FAR _AttackContinuesText
db "@"
CantMoveText:
TX_FAR _CantMoveText
db "@"
PrintMoveIsDisabledText:
ld hl, wPlayerSelectedMove
ld de, wPlayerBattleStatus1
ld a, [H_WHOSETURN]
and a
jr z, .removeChargingUp
inc hl
ld de, wEnemyBattleStatus1
.removeChargingUp
ld a, [de]
res ChargingUp, a ; end the pokemon's
ld [de], a
ld a, [hl]
ld [wd11e], a
call GetMoveName
ld hl, MoveIsDisabledText
jp PrintText
MoveIsDisabledText:
TX_FAR _MoveIsDisabledText
db "@"
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
ld [H_WHOSETURN], a
call PlayMoveAnimation
call DrawPlayerHUDAndHPBar
xor a
ld [H_WHOSETURN], 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:
TX_FAR _MonName1Text
TX_ASM
ld a, [H_WHOSETURN]
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:
TX_FAR _Used1Text
TX_ASM
jr PrintInsteadText
Used2Text:
TX_FAR _Used2Text
TX_ASM
; fall through
PrintInsteadText:
ld a, [wMonIsDisobedient]
and a
jr z, PrintMoveName
ld hl, InsteadText
ret
InsteadText:
TX_FAR _InsteadText
TX_ASM
; fall through
PrintMoveName:
ld hl, _PrintMoveName
ret
_PrintMoveName:
TX_FAR _CF4BText
TX_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:
TX_FAR _ExclamationPoint1Text
db "@"
ExclamationPoint2Text:
TX_FAR _ExclamationPoint2Text
db "@"
ExclamationPoint3Text:
TX_FAR _ExclamationPoint3Text
db "@"
ExclamationPoint4Text:
TX_FAR _ExclamationPoint4Text
db "@"
ExclamationPoint5Text:
TX_FAR _ExclamationPoint5Text
db "@"
; 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
ExclamationPointMoveSets:
db SWORDS_DANCE, GROWTH
db $00
db RECOVER, BIDE, SELFDESTRUCT, AMNESIA
db $00
db MEDITATE, AGILITY, TELEPORT, MIMIC, DOUBLE_TEAM, BARRAGE
db $00
db POUND, SCRATCH, VICEGRIP, WING_ATTACK, FLY, BIND, SLAM, HORN_ATTACK, BODY_SLAM
db WRAP, THRASH, TAIL_WHIP, LEER, BITE, GROWL, ROAR, SING, PECK, COUNTER
db STRENGTH, ABSORB, STRING_SHOT, EARTHQUAKE, FISSURE, DIG, TOXIC, SCREECH, HARDEN
db MINIMIZE, WITHDRAW, DEFENSE_CURL, METRONOME, LICK, CLAMP, CONSTRICT, POISON_GAS
db LEECH_LIFE, BUBBLE, FLASH, SPLASH, ACID_ARMOR, FURY_SWIPES, REST, SHARPEN, SLASH, SUBSTITUTE
db $00
db $FF ; terminator
PrintMoveFailureText:
ld de, wPlayerMoveEffect
ld a, [H_WHOSETURN]
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
ld a, [H_WHOSETURN]
and a
jr nz, .enemyTurn
jp ApplyDamageToPlayerPokemon
.enemyTurn
jp ApplyDamageToEnemyPokemon
AttackMissedText:
TX_FAR _AttackMissedText
db "@"
KeptGoingAndCrashedText:
TX_FAR _KeptGoingAndCrashedText
db "@"
UnaffectedText:
TX_FAR _UnaffectedText
db "@"
PrintDoesntAffectText:
ld hl, DoesntAffectMonText
jp PrintText
DoesntAffectMonText:
TX_FAR _DoesntAffectMonText
db "@"
; 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:
TX_FAR _CriticalHitText
db "@"
OHKOText:
TX_FAR _OHKOText
db "@"
; 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 7, [hl]
ld a, 101
jr nz, .next
bit 5, [hl]
ld a, 70
jr nz, .next
bit 3, [hl]
ld a, 50
jr nz, .next
bit 1, [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 ; sleep 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:
TX_FAR _LoafingAroundText
db "@"
BeganToNapText:
TX_FAR _BeganToNapText
db "@"
WontObeyText:
TX_FAR _WontObeyText
db "@"
TurnedAwayText:
TX_FAR _TurnedAwayText
db "@"
IgnoredOrdersText:
TX_FAR _IgnoredOrdersText
db "@"
; 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 FIRE ; types >= FIRE 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 HasReflectUp, 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
ld a, [H_PRODUCT + 2]
ld b, a
ld a, [H_PRODUCT + 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 HasLightScreenUp, 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 999, so weird things will happen during stats scaling if
; a Pokemon with 512 or more Defense has ued 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
ld a, [H_PRODUCT + 2]
ld b, a
ld a, [H_PRODUCT + 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 FIRE ; types >= FIRE 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 HasReflectUp, 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, H_PRODUCT + 2
pop bc
jr .scaleStats
.specialAttack
ld hl, wBattleMonSpecial
ld a, [hli]
ld b, a
ld c, [hl]
ld a, [wPlayerBattleStatus3]
bit HasLightScreenUp, 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 999, so weird things will happen during stats scaling if
; a Pokemon with 512 or more Defense has ued 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, H_PRODUCT + 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]
ld [H_MULTIPLICAND + 1], a
ld a, [hl]
ld [H_MULTIPLICAND + 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
ld a, [H_WHOSETURN] ; whose turn?
and a
ld a, [wPlayerMoveEffect]
jr z, .effect
ld a, [wEnemyMoveEffect]
.effect
; EXPLODE_EFFECT halves defense.
cp a, 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 a, TWO_TO_FIVE_ATTACKS_EFFECT
jr z, .skipbp
cp a, $1e
jr z, .skipbp
; Calculate OHKO damage based on remaining HP.
cp a, 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, H_DIVIDEND
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
ld hl, wDamage
ld b, [hl]
ld a, [H_QUOTIENT + 3]
add b
ld [H_QUOTIENT + 3], a
jr nc, .asm_3dfd0
ld a, [H_QUOTIENT + 2]
inc a
ld [H_QUOTIENT + 2], a
and a
jr z, .asm_3e004
.asm_3dfd0
ld a, [H_QUOTIENT]
ld b, a
ld a, [H_QUOTIENT + 1]
or a
jr nz, .asm_3e004
ld a, [H_QUOTIENT + 2]
cp 998 / $100
jr c, .asm_3dfe8
cp 998 / $100 + 1
jr nc, .asm_3e004
ld a, [H_QUOTIENT + 3]
cp 998 % $100
jr nc, .asm_3e004
.asm_3dfe8
inc hl
ld a, [H_QUOTIENT + 3]
ld b, [hl]
add b
ld [hld], a
ld a, [H_QUOTIENT + 2]
ld b, [hl]
adc b
ld [hl], a
jr c, .asm_3e004
ld a, [hl]
cp 998 / $100
jr c, .asm_3e00a
cp 998 / $100 + 1
jr nc, .asm_3e004
inc hl
ld a, [hld]
cp 998 % $100
jr c, .asm_3e00a
.asm_3e004
; cap at 997
ld a, 997 / $100
ld [hli], a
ld a, 997 % $100
ld [hld], a
.asm_3e00a
; add 2
inc hl
ld a, [hl]
add 2
ld [hld], a
jr nc, .done
inc [hl]
.done
; minimum damage is 1
ld a, 1
and a
ret
JumpToOHKOMoveEffect:
call JumpMoveEffect
ld a, [wMoveMissed]
dec a
ret
UnusedHighCriticalMoves:
db KARATE_CHOP
db RAZOR_LEAF
db CRABHAMMER
db SLASH
db $FF
; 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
ld a, [H_WHOSETURN]
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))
ld a, [H_WHOSETURN]
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 GettingPumped, 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
; high critical hit moves
HighCriticalMoves:
db KARATE_CHOP
db RAZOR_LEAF
db CRABHAMMER
db SLASH
db $FF
; 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.
ld a,[H_WHOSETURN] ; 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 a,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 a,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 a,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 a,OHKO_EFFECT
jr z,ApplyDamageToEnemyPokemon
cp a,SUPER_FANG_EFFECT
jr z,.superFangEffect
cp a,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 a,SEISMIC_TOSS
jr z,.storeDamage
cp a,NIGHT_SHADE
jr z,.storeDamage
ld b,SONICBOOM_DAMAGE ; 20
cp a,SONICBOOM
jr z,.storeDamage
ld b,DRAGON_RAGE_DAMAGE ; 40
cp a,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 HasSubstituteUp,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
coord hl, 2, 2
xor a
ld [wHPBarType],a
predef UpdateHPBar2 ; animate the HP bar shortening
ApplyAttackToEnemyPokemonDone:
jp DrawHUDsAndHPBars
ApplyAttackToPlayerPokemon:
ld a,[wEnemyMoveEffect]
cp a,OHKO_EFFECT
jr z,ApplyDamageToPlayerPokemon
cp a,SUPER_FANG_EFFECT
jr z,.superFangEffect
cp a,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 a,SEISMIC_TOSS
jr z,.storeDamage
cp a,NIGHT_SHADE
jr z,.storeDamage
ld b,SONICBOOM_DAMAGE
cp a,SONICBOOM
jr z,.storeDamage
ld b,DRAGON_RAGE_DAMAGE
cp a,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 HasSubstituteUp,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
coord hl, 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
ld a,[H_WHOSETURN]
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 HasSubstituteUp,[hl] ; unset the substitute bit
ld hl,SubstituteBrokeText
call PrintText
; flip whose turn it is for the next function call
ld a,[H_WHOSETURN]
xor a,$01
ld [H_WHOSETURN],a
callab HideSubstituteShowMonAnim ; animate the substitute breaking
; flip the turn back to the way it was
ld a,[H_WHOSETURN]
xor a,$01
ld [H_WHOSETURN],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:
TX_FAR _SubstituteTookDamageText
db "@"
SubstituteBrokeText:
TX_FAR _SubstituteBrokeText
db "@"
; 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
ld a,[H_WHOSETURN]
and a
jr z,.next
; values for the enemy turn
ld hl,wPlayerBattleStatus2
ld de,wPlayerMonStatMods
ld bc,wPlayerMoveNum
.next
bit UsingRage,[hl] ; is the pokemon being attacked under the effect of Rage?
ret z ; return if not
ld a,[de]
cp a,$0d ; maximum stat modifier value
ret z ; return if attack modifier is already maxed
ld a,[H_WHOSETURN]
xor a,$01 ; flip turn for the stat modifier raising function
ld [H_WHOSETURN],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
ld a,[H_WHOSETURN]
xor a,$01 ; flip turn back to the way it was
ld [H_WHOSETURN],a
ret
BuildingRageText:
TX_FAR _BuildingRageText
db "@"
; copy last move for Mirror Move
; sets zero flag on failure and unsets zero flag on success
MirrorMoveCopyMove:
; Mirror Move makes use of ccf1 (wPlayerUsedMove) and ccf2 (wEnemyUsedMove) addresses,
; which are mainly used to print the "[Pokemon] used [Move]" text.
; Both are set to 0 whenever a new Pokemon is sent out
; ccf1 is also set to 0 whenever the player is fast asleep or frozen solid.
; ccf2 is also set to 0 whenever the enemy is fast asleep or frozen solid.
ld a,[H_WHOSETURN]
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 a,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:
TX_FAR _MirrorMoveFailedText
db "@"
; function used to reload move data for moves like Mirror Move and Metronome
ReloadMoveData:
ld [wd11e],a
dec a
ld hl,Moves
ld bc,MoveEnd - Moves
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 CopyStringToCF4B
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
ld a,[H_WHOSETURN]
and a
jr z,.pickMoveLoop
; values for enemy turn
ld de,wEnemyMoveNum
ld hl,wEnemySelectedMove
; loop to pick a random number in the range [1, $a5) to be the move used by Metronome
.pickMoveLoop
call BattleRandom
and a
jr z,.pickMoveLoop
cp a,NUM_ATTACKS + 1 ; max normal move number + 1 (this is Struggle's move number)
jr nc,.pickMoveLoop
cp a,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:
ld a,[H_WHOSETURN]
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
ld a,[H_WHOSETURN]
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
ld a,[H_WHOSETURN]
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 a,$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 a,$80
ld b,a
ld a,[hl] ; a = damage multiplier
ld [H_MULTIPLIER],a
add b
ld [wDamageMultipliers],a
xor a
ld [H_MULTIPLICAND],a
ld hl,wDamage
ld a,[hli]
ld [H_MULTIPLICAND + 1],a
ld a,[hld]
ld [H_MULTIPLICAND + 2],a
call Multiply
ld a,10
ld [H_DIVISOR],a
ld b,$04
call Divide
ld a,[H_QUOTIENT + 2]
ld [hli],a
ld b,a
ld a,[H_QUOTIENT + 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 a,$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/type_effects.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
ld a,[H_WHOSETURN]
and a
jr z,.dreamEaterCheck
; enemy's turn
ld hl,wPlayerBattleStatus1
ld de,wEnemyMoveEffect
ld bc,wBattleMonStatus
.dreamEaterCheck
ld a,[de]
cp a,DREAM_EATER_EFFECT
jr nz,.swiftCheck
ld a,[bc]
and a,SLP ; is the target pokemon sleeping?
jp z,.moveMissed
.swiftCheck
ld a,[de]
cp a,SWIFT_EFFECT
ret z ; Swift never misses (interestingly, Azure Heights lists this is a myth, but it appears to be true)
call CheckTargetSubstitute ; substitute check (note that this overwrites a)
jr z,.checkForDigOrFlyStatus
; this code is buggy. it's supposed to prevent HP draining moves from working on substitutes.
; since $7b79 overwrites a with either $00 or $01, it never works.
cp a,DRAIN_HP_EFFECT
jp z,.moveMissed
cp a,DREAM_EATER_EFFECT
jp z,.moveMissed
.checkForDigOrFlyStatus
bit Invulnerable,[hl]
jp nz,.moveMissed
ld a,[H_WHOSETURN]
and a
jr nz,.enemyTurn
.playerTurn
; this checks if the move effect is disallowed by mist
ld a,[wPlayerMoveEffect]
cp a,ATTACK_DOWN1_EFFECT
jr c,.skipEnemyMistCheck
cp a,HAZE_EFFECT + 1
jr c,.enemyMistCheck
cp a,ATTACK_DOWN2_EFFECT
jr c,.skipEnemyMistCheck
cp a,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 ProtectedByMist,a ; is mon protected by mist?
jp nz,.moveMissed
.skipEnemyMistCheck
ld a,[wPlayerBattleStatus2]
bit UsingXAccuracy,a ; is the player using X Accuracy?
ret nz ; if so, always hit regardless of accuracy/evasion
jr .calcHitChance
.enemyTurn
ld a,[wEnemyMoveEffect]
cp a,ATTACK_DOWN1_EFFECT
jr c,.skipPlayerMistCheck
cp a,HAZE_EFFECT + 1
jr c,.playerMistCheck
cp a,ATTACK_DOWN2_EFFECT
jr c,.skipPlayerMistCheck
cp a,REFLECT_EFFECT + 1
jr c,.playerMistCheck
jr .skipPlayerMistCheck
.playerMistCheck
; similar to enemy mist check
ld a,[wPlayerBattleStatus2]
bit ProtectedByMist,a ; is mon protected by mist?
jp nz,.moveMissed
.skipPlayerMistCheck
ld a,[wEnemyBattleStatus2]
bit UsingXAccuracy,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
ld a,[H_WHOSETURN]
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
ld a,[H_WHOSETURN]
and a
jr z,.playerTurn2
.enemyTurn2
ld hl,wEnemyBattleStatus1
res UsingTrappingMove,[hl] ; end multi-turn attack e.g. wrap
ret
.playerTurn2
ld hl,wPlayerBattleStatus1
res UsingTrappingMove,[hl] ; end multi-turn attack e.g. wrap
ret
; values for player turn
CalcHitChance:
ld hl,wPlayerMoveAccuracy
ld a,[H_WHOSETURN]
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
ld [H_MULTIPLICAND],a
ld [H_MULTIPLICAND + 1],a
ld a,[hl]
ld [H_MULTIPLICAND + 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]
ld [H_MULTIPLIER],a ; set multiplier to the numerator of the ratio
call Multiply
ld a,[hl]
ld [H_DIVISOR],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
ld a,[H_QUOTIENT + 3]
ld b,a
ld a,[H_QUOTIENT + 2]
or b
jp nz,.nextCalculation
; make sure the result is always at least one
ld [H_QUOTIENT + 2],a
ld a,$01
ld [H_QUOTIENT + 3],a
.nextCalculation
ld b,c
dec d
jr nz,.loop
ld a,[H_QUOTIENT + 2]
and a ; is the calculated hit chance over 0xFF?
ld a,[H_QUOTIENT + 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
ld [H_MULTIPLICAND], a
dec hl
ld a, [hli]
ld [H_MULTIPLICAND + 1], a
ld a, [hl]
ld [H_MULTIPLICAND + 2], a
; loop until a random number greater than or equal to 217 is generated
.loop
call BattleRandom
rrca
cp 217
jr c, .loop
ld [H_MULTIPLIER], a
call Multiply ; multiply damage by the random number, which is in the range [217, 255]
ld a, 255
ld [H_DIVISOR], a
ld b, $4
call Divide ; divide the result by 255
; store the modified damage
ld a, [H_QUOTIENT + 2]
ld hl, wDamage
ld [hli], a
ld a, [H_QUOTIENT + 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 $e
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 ChargingUp, [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 ChargingUp, [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 CopyStringToCF4B
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 HasSubstituteUp, 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 HasSubstituteUp, 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
callab 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 AttackingMultipleTimes, [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 AttackingMultipleTimes, [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:
TX_FAR _HitXTimesText
db "@"
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 ; sleep 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 UsingTrappingMove, 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 NeedsToRecharge, [hl] ; check if enemy mon has to recharge after using a move
jr z, .checkIfAnyMoveDisabled
res NeedsToRecharge, [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
ld [H_WHOSETURN], a
ld a, POUND
call PlayMoveAnimation
ld a, $1
ld [H_WHOSETURN], 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 $3f ; 25% 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 $ff ^ ((1 << StoringEnergy) | (1 << ThrashingAbout) | (1 << ChargingUp) | (1 << UsingTrappingMove))
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 StoringEnergy, [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 StoringEnergy, [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 ThrashingAbout, [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 ThrashingAbout, [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 UsingTrappingMove, [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 UsingRage, 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 CopyStringToCF4B
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:
ld a, [H_WHOSETURN]
and a
jp z, .player
ld de, wEnemyMoveNum
ld a, [wEnemySelectedMove]
jr .selected
.player
ld de, wPlayerMoveNum
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, MoveEnd - Moves
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 CopyStringToCF4B
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, $98
ld b, $88
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
callab DisplayLinkBattleVersusTextBox
ld a, $1
ld [wUpdateSpritesEnabled], a
call ClearScreen
.next
call DelayFrame
predef BattleTransition
callab LoadHudAndHpBarAndStatusTilePatterns
ld a, $1
ld [H_AUTOBGTRANSFERENABLED], a
ld a, $ff
ld [wUpdateSpritesEnabled], a
call ClearSprites
call ClearScreen
xor a
ld [H_AUTOBGTRANSFERENABLED], a
ld [hWY], a
ld [rWY], a
ld [hTilesetType], 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, OldManPic
.next
ld a, BANK(RedPicBack)
call UncompressSpriteFromDE
predef ScaleSpriteByTwo
ld hl, wOAMBuffer
xor a
ld [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
ld a, [hOAMTile]
ld [hli], a ; OAM tile number
inc a ; increment tile number
ld [hOAMTile], a
inc hl
dec c
jr nz, .innerLoop
ld a, [hOAMTile]
add $4 ; increase tile number by 4
ld [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 [$0], a
xor a
ld [$4000], a
ld hl, vSprites
ld de, sSpriteBuffer1
ld a, [H_LOADEDROMBANK]
ld b, a
ld c, 7 * 7
call CopyVideoData
xor a
ld [$0], a
ld a, $31
ld [hStartTileID], a
coord hl, 1, 5
predef_jump CopyUncompressedPicToTilemap
; does nothing since no stats are ever selected (barring glitches)
DoubleOrHalveSelectedStats:
callab DoubleSelectedStats
jpab HalveSelectedStats
ScrollTrainerPicAfterBattle:
jpab _ScrollTrainerPicAfterBattle
ApplyBurnAndParalysisPenaltiesToPlayer:
ld a, $1
jr ApplyBurnAndParalysisPenalties
ApplyBurnAndParalysisPenaltiesToEnemy:
xor a
ApplyBurnAndParalysisPenalties:
ld [H_WHOSETURN], a
call QuarterSpeedDueToParalysis
jp HalveAttackDueToBurn
QuarterSpeedDueToParalysis:
ld a, [H_WHOSETURN]
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:
ld a, [H_WHOSETURN]
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
ld [H_MULTIPLICAND], a
ld a, [de]
ld [H_MULTIPLICAND + 1], a
inc de
ld a, [de]
ld [H_MULTIPLICAND + 2], a
ld a, [hli]
ld [H_MULTIPLIER], a
call Multiply
ld a, [hl]
ld [H_DIVISOR], a
ld b, $4
call Divide
pop hl
ld a, [H_DIVIDEND + 3]
sub 999 % $100
ld a, [H_DIVIDEND + 2]
sbc 999 / $100
jp c, .storeNewStatValue
; cap the stat at 999
ld a, 999 / $100
ld [H_DIVIDEND + 2], a
ld a, 999 % $100
ld [H_DIVIDEND + 3], a
.storeNewStatValue
ld a, [H_DIVIDEND + 2]
ld [hli], a
ld b, a
ld a, [H_DIVIDEND + 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 999
.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 999 % $100
ld a, [hl]
sbc 999 / $100
ret c
ld a, 999 / $100
ld [hli], a
ld a, 999 % $100
ld [hld], a
ret
LoadHudAndHpBarAndStatusTilePatterns:
call LoadHpBarAndStatusTilePatterns
LoadHudTilePatterns:
ld a, [rLCDC]
add a ; is LCD disabled?
jr c, .lcdEnabled
.lcdDisabled
ld hl, BattleHudTiles1
ld de, vChars2 + $6d0
ld bc, BattleHudTiles1End - BattleHudTiles1
ld a, BANK(BattleHudTiles1)
call FarCopyDataDouble
ld hl, BattleHudTiles2
ld de, vChars2 + $730
ld bc, BattleHudTiles3End - BattleHudTiles2
ld a, BANK(BattleHudTiles2)
jp FarCopyDataDouble
.lcdEnabled
ld de, BattleHudTiles1
ld hl, vChars2 + $6d0
lb bc, BANK(BattleHudTiles1), (BattleHudTiles1End - BattleHudTiles1) / $8
call CopyVideoDataDouble
ld de, BattleHudTiles2
ld hl, vChars2 + $730
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
ret c
; 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:
ld a, [H_WHOSETURN]
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
call Delay3
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 1, a
jr z, .asm_3ef2f
ld a, [hJoyHeld]
bit 1, a ; B button pressed?
ret nz
.asm_3ef2f
ld a, [wNumberOfNoRandomBattleStepsLeft]
and a
ret nz
callab TryDoWildEncounter
ret nz
InitBattleCommon:
ld a, [wMapPalOffset]
push af
ld hl, wLetterPrintingDelayFlags
ld a, [hl]
push af
res 1, [hl]
callab InitBattleVariables
ld a, [wEnemyMonSpecies2]
sub 200
jp c, InitWildBattle
ld [wTrainerClass], a
call GetTrainerInformation
callab ReadTrainer
call DoBattleTransitionAndInitBattleVariables
call _LoadTrainerPic
xor a
ld [wEnemyMonSpecies2], a
ld [hStartTileID], a
dec a
ld [wAICount], a
coord hl, 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 MAROWAK
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
ld [hStartTileID], a
coord hl, 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
ld [H_AUTOBGTRANSFERENABLED], a
ld hl, .emptyString
call PrintText
call SaveScreenTilesToBuffer1
call ClearScreen
ld a, $98
ld [H_AUTOBGTRANSFERDEST + 1], a
ld a, $1
ld [H_AUTOBGTRANSFERENABLED], a
call Delay3
ld a, $9c
ld [H_AUTOBGTRANSFERDEST + 1], a
call LoadScreenTilesFromBuffer1
coord hl, 9, 7
lb bc, 5, 10
call ClearScreenArea
coord hl, 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
callab EndOfBattle
pop af
ld [wLetterPrintingDelayFlags], a
pop af
ld [wMapPalOffset], a
ld a, [wSavedTilesetType]
ld [hTilesetType], 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(TrainerPics) ; 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, [wPredefRegisters]
ld h, a
ld a, [wPredefRegisters + 1]
ld l, a
ld a, [hStartTileID]
ld [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
ld a, [hBaseTileID]
add $31
jr CopyUncompressedPicToHL
CopyUncompressedPicToTilemap:
ld a, [wPredefRegisters]
ld h, a
ld a, [wPredefRegisters + 1]
ld l, a
ld 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
coord hl, 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
ld a, [H_LOADEDROMBANK]
ld b, a
jp CopyVideoData
JumpMoveEffect:
call _JumpMoveEffect
ld b, $1
ret
_JumpMoveEffect:
ld a, [H_WHOSETURN]
and a
ld a, [wPlayerMoveEffect]
jr z, .next1
ld a, [wEnemyMoveEffect]
.next1
dec a ; subtract 1, there is no special effect for 00
add a ; x2, 16bit pointers
ld hl, MoveEffectPointerTable
ld b, 0
ld c, a
add hl, bc
ld a, [hli]
ld h, [hl]
ld l, a
jp [hl] ; jump to special effect handler
MoveEffectPointerTable:
dw SleepEffect ; unused effect
dw PoisonEffect ; POISON_SIDE_EFFECT1
dw DrainHPEffect ; DRAIN_HP_EFFECT
dw FreezeBurnParalyzeEffect ; BURN_SIDE_EFFECT1
dw FreezeBurnParalyzeEffect ; FREEZE_SIDE_EFFECT
dw FreezeBurnParalyzeEffect ; PARALYZE_SIDE_EFFECT1
dw ExplodeEffect ; EXPLODE_EFFECT
dw DrainHPEffect ; DREAM_EATER_EFFECT
dw $0000 ; MIRROR_MOVE_EFFECT
dw StatModifierUpEffect ; ATTACK_UP1_EFFECT
dw StatModifierUpEffect ; DEFENSE_UP1_EFFECT
dw StatModifierUpEffect ; SPEED_UP1_EFFECT
dw StatModifierUpEffect ; SPECIAL_UP1_EFFECT
dw StatModifierUpEffect ; ACCURACY_UP1_EFFECT
dw StatModifierUpEffect ; EVASION_UP1_EFFECT
dw PayDayEffect ; PAY_DAY_EFFECT
dw $0000 ; SWIFT_EFFECT
dw StatModifierDownEffect ; ATTACK_DOWN1_EFFECT
dw StatModifierDownEffect ; DEFENSE_DOWN1_EFFECT
dw StatModifierDownEffect ; SPEED_DOWN1_EFFECT
dw StatModifierDownEffect ; SPECIAL_DOWN1_EFFECT
dw StatModifierDownEffect ; ACCURACY_DOWN1_EFFECT
dw StatModifierDownEffect ; EVASION_DOWN1_EFFECT
dw ConversionEffect ; CONVERSION_EFFECT
dw HazeEffect ; HAZE_EFFECT
dw BideEffect ; BIDE_EFFECT
dw ThrashPetalDanceEffect ; THRASH_PETAL_DANCE_EFFECT
dw SwitchAndTeleportEffect ; SWITCH_AND_TELEPORT_EFFECT
dw TwoToFiveAttacksEffect ; TWO_TO_FIVE_ATTACKS_EFFECT
dw TwoToFiveAttacksEffect ; unused effect
dw FlinchSideEffect ; FLINCH_SIDE_EFFECT1
dw SleepEffect ; SLEEP_EFFECT
dw PoisonEffect ; POISON_SIDE_EFFECT2
dw FreezeBurnParalyzeEffect ; BURN_SIDE_EFFECT2
dw FreezeBurnParalyzeEffect ; unused effect
dw FreezeBurnParalyzeEffect ; PARALYZE_SIDE_EFFECT2
dw FlinchSideEffect ; FLINCH_SIDE_EFFECT2
dw OneHitKOEffect ; OHKO_EFFECT
dw ChargeEffect ; CHARGE_EFFECT
dw $0000 ; SUPER_FANG_EFFECT
dw $0000 ; SPECIAL_DAMAGE_EFFECT
dw TrappingEffect ; TRAPPING_EFFECT
dw ChargeEffect ; FLY_EFFECT
dw TwoToFiveAttacksEffect ; ATTACK_TWICE_EFFECT
dw $0000 ; JUMP_KICK_EFFECT
dw MistEffect ; MIST_EFFECT
dw FocusEnergyEffect ; FOCUS_ENERGY_EFFECT
dw RecoilEffect ; RECOIL_EFFECT
dw ConfusionEffect ; CONFUSION_EFFECT
dw StatModifierUpEffect ; ATTACK_UP2_EFFECT
dw StatModifierUpEffect ; DEFENSE_UP2_EFFECT
dw StatModifierUpEffect ; SPEED_UP2_EFFECT
dw StatModifierUpEffect ; SPECIAL_UP2_EFFECT
dw StatModifierUpEffect ; ACCURACY_UP2_EFFECT
dw StatModifierUpEffect ; EVASION_UP2_EFFECT
dw HealEffect ; HEAL_EFFECT
dw TransformEffect ; TRANSFORM_EFFECT
dw StatModifierDownEffect ; ATTACK_DOWN2_EFFECT
dw StatModifierDownEffect ; DEFENSE_DOWN2_EFFECT
dw StatModifierDownEffect ; SPEED_DOWN2_EFFECT
dw StatModifierDownEffect ; SPECIAL_DOWN2_EFFECT
dw StatModifierDownEffect ; ACCURACY_DOWN2_EFFECT
dw StatModifierDownEffect ; EVASION_DOWN2_EFFECT
dw ReflectLightScreenEffect ; LIGHT_SCREEN_EFFECT
dw ReflectLightScreenEffect ; REFLECT_EFFECT
dw PoisonEffect ; POISON_EFFECT
dw ParalyzeEffect ; PARALYZE_EFFECT
dw StatModifierDownEffect ; ATTACK_DOWN_SIDE_EFFECT
dw StatModifierDownEffect ; DEFENSE_DOWN_SIDE_EFFECT
dw StatModifierDownEffect ; SPEED_DOWN_SIDE_EFFECT
dw StatModifierDownEffect ; SPECIAL_DOWN_SIDE_EFFECT
dw StatModifierDownEffect ; unused effect
dw StatModifierDownEffect ; unused effect
dw StatModifierDownEffect ; unused effect
dw StatModifierDownEffect ; unused effect
dw ConfusionSideEffect ; CONFUSION_SIDE_EFFECT
dw TwoToFiveAttacksEffect ; TWINEEDLE_EFFECT
dw $0000 ; unused effect
dw SubstituteEffect ; SUBSTITUTE_EFFECT
dw HyperBeamEffect ; HYPER_BEAM_EFFECT
dw RageEffect ; RAGE_EFFECT
dw MimicEffect ; MIMIC_EFFECT
dw $0000 ; METRONOME_EFFECT
dw LeechSeedEffect ; LEECH_SEED_EFFECT
dw SplashEffect ; SPLASH_EFFECT
dw DisableEffect ; DISABLE_EFFECT
SleepEffect:
ld de, wEnemyMonStatus
ld bc, wEnemyBattleStatus2
ld a, [H_WHOSETURN]
and a
jp z, .sleepEffect
ld de, wBattleMonStatus
ld bc, wPlayerBattleStatus2
.sleepEffect
ld a, [bc]
bit NeedsToRecharge, a ; does the target need to recharge? (hyper beam)
res NeedsToRecharge, a ; target no longer needs to recharge
ld [bc], a
jr nz, .setSleepCounter ; if the target had to recharge, all hit tests will be skipped
; including the event where the target already has another status
ld a, [de]
ld b, a
and $7
jr z, .notAlreadySleeping ; can't affect a mon that is already asleep
ld hl, AlreadyAsleepText
jp PrintText
.notAlreadySleeping
ld a, b
and a
jr nz, .didntAffect ; can't affect a mon that is already statused
push de
call MoveHitTest ; apply accuracy tests
pop de
ld a, [wMoveMissed]
and a
jr nz, .didntAffect
.setSleepCounter
; set target's sleep counter to a random number between 1 and 7
call BattleRandom
and $7
jr z, .setSleepCounter
ld [de], a
call PlayCurrentMoveAnimation2
ld hl, FellAsleepText
jp PrintText
.didntAffect
jp PrintDidntAffectText
FellAsleepText:
TX_FAR _FellAsleepText
db "@"
AlreadyAsleepText:
TX_FAR _AlreadyAsleepText
db "@"
PoisonEffect:
ld hl, wEnemyMonStatus
ld de, wPlayerMoveEffect
ld a, [H_WHOSETURN]
and a
jr z, .poisonEffect
ld hl, wBattleMonStatus
ld de, wEnemyMoveEffect
.poisonEffect
call CheckTargetSubstitute
jr nz, .noEffect ; can't posion a substitute target
ld a, [hli]
ld b, a
and a
jr nz, .noEffect ; miss if target is already statused
ld a, [hli]
cp POISON ; can't posion a poison-type target
jr z, .noEffect
ld a, [hld]
cp POISON ; can't posion a poison-type target
jr z, .noEffect
ld a, [de]
cp POISON_SIDE_EFFECT1
ld b, $34 ; ~20% chance of poisoning
jr z, .sideEffectTest
cp POISON_SIDE_EFFECT2
ld b, $67 ; ~40% chance of poisoning
jr z, .sideEffectTest
push hl
push de
call MoveHitTest ; apply accuracy tests
pop de
pop hl
ld a, [wMoveMissed]
and a
jr nz, .didntAffect
jr .inflictPoison
.sideEffectTest
call BattleRandom
cp b ; was side effect successful?
ret nc
.inflictPoison
dec hl
set 3, [hl] ; mon is now poisoned
push de
dec de
ld a, [H_WHOSETURN]
and a
ld b, ANIM_C7
ld hl, wPlayerBattleStatus3
ld a, [de]
ld de, wPlayerToxicCounter
jr nz, .ok
ld b, ANIM_A9
ld hl, wEnemyBattleStatus3
ld de, wEnemyToxicCounter
.ok
cp TOXIC
jr nz, .normalPoison ; done if move is not Toxic
set BadlyPoisoned, [hl] ; else set Toxic battstatus
xor a
ld [de], a
ld hl, BadlyPoisonedText
jr .continue
.normalPoison
ld hl, PoisonedText
.continue
pop de
ld a, [de]
cp POISON_EFFECT
jr z, .regularPoisonEffect
ld a, b
call PlayBattleAnimation2
jp PrintText
.regularPoisonEffect
call PlayCurrentMoveAnimation2
jp PrintText
.noEffect
ld a, [de]
cp POISON_EFFECT
ret nz
.didntAffect
ld c, 50
call DelayFrames
jp PrintDidntAffectText
PoisonedText:
TX_FAR _PoisonedText
db "@"
BadlyPoisonedText:
TX_FAR _BadlyPoisonedText
db "@"
DrainHPEffect:
jpab DrainHPEffect_
ExplodeEffect:
ld hl, wBattleMonHP
ld de, wPlayerBattleStatus2
ld a, [H_WHOSETURN]
and a
jr z, .faintUser
ld hl, wEnemyMonHP
ld de, wEnemyBattleStatus2
.faintUser
xor a
ld [hli], a ; set the mon's HP to 0
ld [hli], a
inc hl
ld [hl], a ; set mon's status to 0
ld a, [de]
res Seeded, a ; clear mon's leech seed status
ld [de], a
ret
FreezeBurnParalyzeEffect:
xor a
ld [wAnimationType], a
call CheckTargetSubstitute ; test bit 4 of d063/d068 flags [target has substitute flag]
ret nz ; return if they have a substitute, can't effect them
ld a, [H_WHOSETURN]
and a
jp nz, opponentAttacker
ld a, [wEnemyMonStatus]
and a
jp nz, CheckDefrost ; can't inflict status if opponent is already statused
ld a, [wPlayerMoveType]
ld b, a
ld a, [wEnemyMonType1]
cp b ; do target type 1 and move type match?
ret z ; return if they match (an ice move can't freeze an ice-type, body slam can't paralyze a normal-type, etc.)
ld a, [wEnemyMonType2]
cp b ; do target type 2 and move type match?
ret z ; return if they match
ld a, [wPlayerMoveEffect]
cp a, PARALYZE_SIDE_EFFECT1 + 1 ; 10% status effects are 04, 05, 06 so 07 will set carry for those
ld b, $1a ; 0x1A/0x100 or 26/256 = 10.2%~ chance
jr c, .next1 ; branch ahead if this is a 10% chance effect..
ld b, $4d ; else use 0x4D/0x100 or 77/256 = 30.1%~ chance
sub a, $1e ; subtract $1E to map to equivalent 10% chance effects
.next1
push af
call BattleRandom ; get random 8bit value for probability test
cp b
pop bc
ret nc ; do nothing if random value is >= 1A or 4D [no status applied]
ld a, b ; what type of effect is this?
cp a, BURN_SIDE_EFFECT1
jr z, .burn
cp a, FREEZE_SIDE_EFFECT
jr z, .freeze
; .paralyze
ld a, 1 << PAR
ld [wEnemyMonStatus], a
call QuarterSpeedDueToParalysis ; quarter speed of affected mon
ld a, ANIM_A9
call PlayBattleAnimation
jp PrintMayNotAttackText ; print paralysis text
.burn
ld a, 1 << BRN
ld [wEnemyMonStatus], a
call HalveAttackDueToBurn ; halve attack of affected mon
ld a, ANIM_A9
call PlayBattleAnimation
ld hl, BurnedText
jp PrintText
.freeze
call ClearHyperBeam ; resets hyper beam (recharge) condition from target
ld a, 1 << FRZ
ld [wEnemyMonStatus], a
ld a, ANIM_A9
call PlayBattleAnimation
ld hl, FrozenText
jp PrintText
opponentAttacker:
ld a, [wBattleMonStatus] ; mostly same as above with addresses swapped for opponent
and a
jp nz, CheckDefrost
ld a, [wEnemyMoveType]
ld b, a
ld a, [wBattleMonType1]
cp b
ret z
ld a, [wBattleMonType2]
cp b
ret z
ld a, [wEnemyMoveEffect]
cp a, PARALYZE_SIDE_EFFECT1 + 1
ld b, $1a
jr c, .next1
ld b, $4d
sub a, $1e
.next1
push af
call BattleRandom
cp b
pop bc
ret nc
ld a, b
cp a, BURN_SIDE_EFFECT1
jr z, .burn
cp a, FREEZE_SIDE_EFFECT
jr z, .freeze
ld a, 1 << PAR
ld [wBattleMonStatus], a
call QuarterSpeedDueToParalysis
jp PrintMayNotAttackText
.burn
ld a, 1 << BRN
ld [wBattleMonStatus], a
call HalveAttackDueToBurn
ld hl, BurnedText
jp PrintText
.freeze
; hyper beam bits aren't reseted for opponent's side
ld a, 1 << FRZ
ld [wBattleMonStatus], a
ld hl, FrozenText
jp PrintText
BurnedText:
TX_FAR _BurnedText
db "@"
FrozenText:
TX_FAR _FrozenText
db "@"
CheckDefrost:
; any fire-type move that has a chance inflict burn (all but Fire Spin) will defrost a frozen target
and a, 1 << FRZ ; are they frozen?
ret z ; return if so
ld a, [H_WHOSETURN]
and a
jr nz, .opponent
;player [attacker]
ld a, [wPlayerMoveType]
sub a, FIRE
ret nz ; return if type of move used isn't fire
ld [wEnemyMonStatus], a ; set opponent status to 00 ["defrost" a frozen monster]
ld hl, wEnemyMon1Status
ld a, [wEnemyMonPartyPos]
ld bc, wEnemyMon2 - wEnemyMon1
call AddNTimes
xor a
ld [hl], a ; clear status in roster
ld hl, FireDefrostedText
jr .common
.opponent
ld a, [wEnemyMoveType] ; same as above with addresses swapped
sub a, FIRE
ret nz
ld [wBattleMonStatus], a
ld hl, wPartyMon1Status
ld a, [wPlayerMonNumber]
ld bc, wPartyMon2 - wPartyMon1
call AddNTimes
xor a
ld [hl], a
ld hl, FireDefrostedText
.common
jp PrintText
FireDefrostedText:
TX_FAR _FireDefrostedText
db "@"
StatModifierUpEffect:
ld hl, wPlayerMonStatMods
ld de, wPlayerMoveEffect
ld a, [H_WHOSETURN]
and a
jr z, .statModifierUpEffect
ld hl, wEnemyMonStatMods
ld de, wEnemyMoveEffect
.statModifierUpEffect
ld a, [de]
sub ATTACK_UP1_EFFECT
cp EVASION_UP1_EFFECT + $3 - ATTACK_UP1_EFFECT ; covers all +1 effects
jr c, .incrementStatMod
sub ATTACK_UP2_EFFECT - ATTACK_UP1_EFFECT ; map +2 effects to equivalent +1 effect
.incrementStatMod
ld c, a
ld b, $0
add hl, bc
ld b, [hl]
inc b ; increment corresponding stat mod
ld a, $d
cp b ; can't raise stat past +6 ($d or 13)
jp c, PrintNothingHappenedText
ld a, [de]
cp ATTACK_UP1_EFFECT + $8 ; is it a +2 effect?
jr c, .ok
inc b ; if so, increment stat mod again
ld a, $d
cp b ; unless it's already +6
jr nc, .ok
ld b, a
.ok
ld [hl], b
ld a, c
cp $4
jr nc, UpdateStatDone ; jump if mod affected is evasion/accuracy
push hl
ld hl, wBattleMonAttack + 1
ld de, wPlayerMonUnmodifiedAttack
ld a, [H_WHOSETURN]
and a
jr z, .pointToStats
ld hl, wEnemyMonAttack + 1
ld de, wEnemyMonUnmodifiedAttack
.pointToStats
push bc
sla c
ld b, $0
add hl, bc ; hl = modified stat
ld a, c
add e
ld e, a
jr nc, .checkIf999
inc d ; de = unmodified (original) stat
.checkIf999
pop bc
ld a, [hld]
sub 999 % $100 ; check if stat is already 999
jr nz, .recalculateStat
ld a, [hl]
sbc 999 / $100
jp z, RestoreOriginalStatModifier
.recalculateStat ; recalculate affected stat
; paralysis and burn penalties, as well as badge boosts are ignored
push hl
push bc
ld hl, StatModifierRatios
dec b
sla b
ld c, b
ld b, $0
add hl, bc
pop bc
xor a
ld [H_MULTIPLICAND], a
ld a, [de]
ld [H_MULTIPLICAND + 1], a
inc de
ld a, [de]
ld [H_MULTIPLICAND + 2], a
ld a, [hli]
ld [H_MULTIPLIER], a
call Multiply
ld a, [hl]
ld [H_DIVISOR], a
ld b, $4
call Divide
pop hl
; cap at 999
ld a, [H_PRODUCT + 3]
sub 999 % $100
ld a, [H_PRODUCT + 2]
sbc 999 / $100
jp c, UpdateStat
ld a, 999 / $100
ld [H_MULTIPLICAND + 1], a
ld a, 999 % $100
ld [H_MULTIPLICAND + 2], a
UpdateStat:
ld a, [H_PRODUCT + 2]
ld [hli], a
ld a, [H_PRODUCT + 3]
ld [hl], a
pop hl
UpdateStatDone:
ld b, c
inc b
call PrintStatText
ld hl, wPlayerBattleStatus2
ld de, wPlayerMoveNum
ld bc, wPlayerMonMinimized
ld a, [H_WHOSETURN]
and a
jr z, .asm_3f4e6
ld hl, wEnemyBattleStatus2
ld de, wEnemyMoveNum
ld bc, wEnemyMonMinimized
.asm_3f4e6
ld a, [de]
cp MINIMIZE
jr nz, .asm_3f4f9
; if a substitute is up, slide off the substitute and show the mon pic before
; playing the minimize animation
bit HasSubstituteUp, [hl]
push af
push bc
ld hl, HideSubstituteShowMonAnim
ld b, BANK(HideSubstituteShowMonAnim)
push de
call nz, Bankswitch
pop de
.asm_3f4f9
call PlayCurrentMoveAnimation
ld a, [de]
cp MINIMIZE
jr nz, .applyBadgeBoostsAndStatusPenalties
pop bc
ld a, $1
ld [bc], a
ld hl, ReshowSubstituteAnim
ld b, BANK(ReshowSubstituteAnim)
pop af
call nz, Bankswitch
.applyBadgeBoostsAndStatusPenalties
ld a, [H_WHOSETURN]
and a
call z, ApplyBadgeStatBoosts ; whenever the player uses a stat-up move, badge boosts get reapplied again to every stat,
; even to those not affected by the stat-up move (will be boosted further)
ld hl, MonsStatsRoseText
call PrintText
; these shouldn't be here
call QuarterSpeedDueToParalysis ; apply speed penalty to the player whose turn is not, if it's paralyzed
jp HalveAttackDueToBurn ; apply attack penalty to the player whose turn is not, if it's burned
RestoreOriginalStatModifier:
pop hl
dec [hl]
PrintNothingHappenedText:
ld hl, NothingHappenedText
jp PrintText
MonsStatsRoseText:
TX_FAR _MonsStatsRoseText
TX_ASM
ld hl, GreatlyRoseText
ld a, [H_WHOSETURN]
and a
ld a, [wPlayerMoveEffect]
jr z, .playerTurn
ld a, [wEnemyMoveEffect]
.playerTurn
cp ATTACK_DOWN1_EFFECT
ret nc
ld hl, RoseText
ret
GreatlyRoseText:
TX_DELAY
TX_FAR _GreatlyRoseText
; fallthrough
RoseText:
TX_FAR _RoseText
db "@"
StatModifierDownEffect:
ld hl, wEnemyMonStatMods
ld de, wPlayerMoveEffect
ld bc, wEnemyBattleStatus1
ld a, [H_WHOSETURN]
and a
jr z, .statModifierDownEffect
ld hl, wPlayerMonStatMods
ld de, wEnemyMoveEffect
ld bc, wPlayerBattleStatus1
ld a, [wLinkState]
cp LINK_STATE_BATTLING
jr z, .statModifierDownEffect
call BattleRandom
cp $40 ; 1/4 chance to miss by in regular battle
jp c, MoveMissed
.statModifierDownEffect
call CheckTargetSubstitute ; can't hit through substitute
jp nz, MoveMissed
ld a, [de]
cp ATTACK_DOWN_SIDE_EFFECT
jr c, .nonSideEffect
call BattleRandom
cp $55 ; 85/256 chance for side effects
jp nc, CantLowerAnymore
ld a, [de]
sub ATTACK_DOWN_SIDE_EFFECT ; map each stat to 0-3
jr .decrementStatMod
.nonSideEffect ; non-side effects only
push hl
push de
push bc
call MoveHitTest ; apply accuracy tests
pop bc
pop de
pop hl
ld a, [wMoveMissed]
and a
jp nz, MoveMissed
ld a, [bc]
bit Invulnerable, a ; fly/dig
jp nz, MoveMissed
ld a, [de]
sub ATTACK_DOWN1_EFFECT
cp EVASION_DOWN1_EFFECT + $3 - ATTACK_DOWN1_EFFECT ; covers al -1 effects
jr c, .decrementStatMod
sub ATTACK_DOWN2_EFFECT - ATTACK_DOWN1_EFFECT ; map -2 effects to corresponding -1 effect
.decrementStatMod
ld c, a
ld b, $0
add hl, bc
ld b, [hl]
dec b ; dec corresponding stat mod
jp z, CantLowerAnymore ; if stat mod is 1 (-6), can't lower anymore
ld a, [de]
cp ATTACK_DOWN2_EFFECT - $16 ; $24
jr c, .ok
cp EVASION_DOWN2_EFFECT + $5 ; $44
jr nc, .ok
dec b ; stat down 2 effects only (dec mod again)
jr nz, .ok
inc b ; increment mod to 1 (-6) if it would become 0 (-7)
.ok
ld [hl], b ; save modified mod
ld a, c
cp $4
jr nc, UpdateLoweredStatDone ; jump for evasion/accuracy
push hl
push de
ld hl, wEnemyMonAttack + 1
ld de, wEnemyMonUnmodifiedAttack
ld a, [H_WHOSETURN]
and a
jr z, .pointToStat
ld hl, wBattleMonAttack + 1
ld de, wPlayerMonUnmodifiedAttack
.pointToStat
push bc
sla c
ld b, $0
add hl, bc ; hl = modified stat
ld a, c
add e
ld e, a
jr nc, .noCarry
inc d ; de = unmodified stat
.noCarry
pop bc
ld a, [hld]
sub $1 ; can't lower stat below 1 (-6)
jr nz, .recalculateStat
ld a, [hl]
and a
jp z, CantLowerAnymore_Pop
.recalculateStat
; recalculate affected stat
; paralysis and burn penalties, as well as badge boosts are ignored
push hl
push bc
ld hl, StatModifierRatios
dec b
sla b
ld c, b
ld b, $0
add hl, bc
pop bc
xor a
ld [H_MULTIPLICAND], a
ld a, [de]
ld [H_MULTIPLICAND + 1], a
inc de
ld a, [de]
ld [H_MULTIPLICAND + 2], a
ld a, [hli]
ld [H_MULTIPLIER], a
call Multiply
ld a, [hl]
ld [H_DIVISOR], a
ld b, $4
call Divide
pop hl
ld a, [H_PRODUCT + 3]
ld b, a
ld a, [H_PRODUCT + 2]
or b
jp nz, UpdateLoweredStat
ld [H_MULTIPLICAND + 1], a
ld a, $1
ld [H_MULTIPLICAND + 2], a
UpdateLoweredStat:
ld a, [H_PRODUCT + 2]
ld [hli], a
ld a, [H_PRODUCT + 3]
ld [hl], a
pop de
pop hl
UpdateLoweredStatDone:
ld b, c
inc b
push de
call PrintStatText
pop de
ld a, [de]
cp $44
jr nc, .ApplyBadgeBoostsAndStatusPenalties
call PlayCurrentMoveAnimation2
.ApplyBadgeBoostsAndStatusPenalties
ld a, [H_WHOSETURN]
and a
call nz, ApplyBadgeStatBoosts ; whenever the player uses a stat-down move, badge boosts get reapplied again to every stat,
; even to those not affected by the stat-up move (will be boosted further)
ld hl, MonsStatsFellText
call PrintText
; These where probably added given that a stat-down move affecting speed or attack will override
; the stat penalties from paralysis and burn respectively.
; But they are always called regardless of the stat affected by the stat-down move.
call QuarterSpeedDueToParalysis
jp HalveAttackDueToBurn
CantLowerAnymore_Pop:
pop de
pop hl
inc [hl]
CantLowerAnymore:
ld a, [de]
cp ATTACK_DOWN_SIDE_EFFECT
ret nc
ld hl, NothingHappenedText
jp PrintText
MoveMissed:
ld a, [de]
cp $44
ret nc
jp ConditionalPrintButItFailed
MonsStatsFellText:
TX_FAR _MonsStatsFellText
TX_ASM
ld hl, FellText
ld a, [H_WHOSETURN]
and a
ld a, [wPlayerMoveEffect]
jr z, .playerTurn
ld a, [wEnemyMoveEffect]
.playerTurn
; check if the move's effect decreases a stat by 2
cp BIDE_EFFECT
ret c
cp ATTACK_DOWN_SIDE_EFFECT
ret nc
ld hl, GreatlyFellText
ret
GreatlyFellText:
TX_DELAY
TX_FAR _GreatlyFellText
; fallthrough
FellText:
TX_FAR _FellText
db "@"
PrintStatText:
ld hl, StatsTextStrings
ld c, "@"
.findStatName_outer
dec b
jr z, .foundStatName
.findStatName_inner
ld a, [hli]
cp c
jr z, .findStatName_outer
jr .findStatName_inner
.foundStatName
ld de, wcf4b
ld bc, $a
jp CopyData
StatsTextStrings:
db "ATTACK@"
db "DEFENSE@"
db "SPEED@"
db "SPECIAL@"
db "ACCURACY@"
db "EVADE@"
StatModifierRatios:
; first byte is numerator, second byte is denominator
db 25, 100 ; 0.25
db 28, 100 ; 0.28
db 33, 100 ; 0.33
db 40, 100 ; 0.40
db 50, 100 ; 0.50
db 66, 100 ; 0.66
db 1, 1 ; 1.00
db 15, 10 ; 1.50
db 2, 1 ; 2.00
db 25, 10 ; 2.50
db 3, 1 ; 3.00
db 35, 10 ; 3.50
db 4, 1 ; 4.00
BideEffect:
ld hl, wPlayerBattleStatus1
ld de, wPlayerBideAccumulatedDamage
ld bc, wPlayerNumAttacksLeft
ld a, [H_WHOSETURN]
and a
jr z, .bideEffect
ld hl, wEnemyBattleStatus1
ld de, wEnemyBideAccumulatedDamage
ld bc, wEnemyNumAttacksLeft
.bideEffect
set StoringEnergy, [hl] ; mon is now using bide
xor a
ld [de], a
inc de
ld [de], a
ld [wPlayerMoveEffect], a
ld [wEnemyMoveEffect], a
call BattleRandom
and $1
inc a
inc a
ld [bc], a ; set Bide counter to 2 or 3 at random
ld a, [H_WHOSETURN]
add XSTATITEM_ANIM
jp PlayBattleAnimation2
ThrashPetalDanceEffect:
ld hl, wPlayerBattleStatus1
ld de, wPlayerNumAttacksLeft
ld a, [H_WHOSETURN]
and a
jr z, .thrashPetalDanceEffect
ld hl, wEnemyBattleStatus1
ld de, wEnemyNumAttacksLeft
.thrashPetalDanceEffect
set ThrashingAbout, [hl] ; mon is now using thrash/petal dance
call BattleRandom
and $1
inc a
inc a
ld [de], a ; set thrash/petal dance counter to 2 or 3 at random
ld a, [H_WHOSETURN]
add ANIM_B0
jp PlayBattleAnimation2
SwitchAndTeleportEffect:
ld a, [H_WHOSETURN]
and a
jr nz, .handleEnemy
ld a, [wIsInBattle]
dec a
jr nz, .notWildBattle1
ld a, [wCurEnemyLVL]
ld b, a
ld a, [wBattleMonLevel]
cp b ; is the player's level greater than the enemy's level?
jr nc, .playerMoveWasSuccessful ; if so, teleport will always succeed
add b
ld c, a
inc c ; c = sum of player level and enemy level
.rejectionSampleLoop1
call BattleRandom
cp c ; get a random number between 0 and c
jr nc, .rejectionSampleLoop1
srl b
srl b ; b = enemy level * 4
; bug: does not account for overflow, so levels above 63 can lead to erroneousness results
cp b ; is rand[0, playerLevel + enemyLevel] > enemyLevel?
jr nc, .playerMoveWasSuccessful ; if so, allow teleporting
ld c, 50
call DelayFrames
ld a, [wPlayerMoveNum]
cp TELEPORT
jp nz, PrintDidntAffectText
jp PrintButItFailedText_
.playerMoveWasSuccessful
call ReadPlayerMonCurHPAndStatus
xor a
ld [wAnimationType], a
inc a
ld [wEscapedFromBattle], a
ld a, [wPlayerMoveNum]
jr .playAnimAndPrintText
.notWildBattle1
ld c, 50
call DelayFrames
ld hl, IsUnaffectedText
ld a, [wPlayerMoveNum]
cp TELEPORT
jp nz, PrintText
jp PrintButItFailedText_
.handleEnemy
ld a, [wIsInBattle]
dec a
jr nz, .notWildBattle2
ld a, [wBattleMonLevel]
ld b, a
ld a, [wCurEnemyLVL]
cp b
jr nc, .enemyMoveWasSuccessful
add b
ld c, a
inc c
.rejectionSampleLoop2
call BattleRandom
cp c
jr nc, .rejectionSampleLoop2
srl b
srl b
cp b
jr nc, .enemyMoveWasSuccessful
ld c, 50
call DelayFrames
ld a, [wEnemyMoveNum]
cp TELEPORT
jp nz, PrintDidntAffectText
jp PrintButItFailedText_
.enemyMoveWasSuccessful
call ReadPlayerMonCurHPAndStatus
xor a
ld [wAnimationType], a
inc a
ld [wEscapedFromBattle], a
ld a, [wEnemyMoveNum]
jr .playAnimAndPrintText
.notWildBattle2
ld c, 50
call DelayFrames
ld hl, IsUnaffectedText
ld a, [wEnemyMoveNum]
cp TELEPORT
jp nz, PrintText
jp ConditionalPrintButItFailed
.playAnimAndPrintText
push af
call PlayBattleAnimation
ld c, 20
call DelayFrames
pop af
ld hl, RanFromBattleText
cp TELEPORT
jr z, .printText
ld hl, RanAwayScaredText
cp ROAR
jr z, .printText
ld hl, WasBlownAwayText
.printText
jp PrintText
RanFromBattleText:
TX_FAR _RanFromBattleText
db "@"
RanAwayScaredText:
TX_FAR _RanAwayScaredText
db "@"
WasBlownAwayText:
TX_FAR _WasBlownAwayText
db "@"
TwoToFiveAttacksEffect:
ld hl, wPlayerBattleStatus1
ld de, wPlayerNumAttacksLeft
ld bc, wPlayerNumHits
ld a, [H_WHOSETURN]
and a
jr z, .twoToFiveAttacksEffect
ld hl, wEnemyBattleStatus1
ld de, wEnemyNumAttacksLeft
ld bc, wEnemyNumHits
.twoToFiveAttacksEffect
bit AttackingMultipleTimes, [hl] ; is mon attacking multiple times?
ret nz
set AttackingMultipleTimes, [hl] ; mon is now attacking multiple times
ld hl, wPlayerMoveEffect
ld a, [H_WHOSETURN]
and a
jr z, .setNumberOfHits
ld hl, wEnemyMoveEffect
.setNumberOfHits
ld a, [hl]
cp TWINEEDLE_EFFECT
jr z, .twineedle
cp ATTACK_TWICE_EFFECT
ld a, $2 ; number of hits it's always 2 for ATTACK_TWICE_EFFECT
jr z, .saveNumberOfHits
; for TWO_TO_FIVE_ATTACKS_EFFECT 3/8 chance for 2 and 3 hits, and 1/8 chance for 4 and 5 hits
call BattleRandom
and $3
cp $2
jr c, .gotNumHits
; if the number of hits was greater than 2, re-roll again for a lower chance
call BattleRandom
and $3
.gotNumHits
inc a
inc a
.saveNumberOfHits
ld [de], a
ld [bc], a
ret
.twineedle
ld a, POISON_SIDE_EFFECT1
ld [hl], a ; set Twineedle's effect to poison effect
jr .saveNumberOfHits
FlinchSideEffect:
call CheckTargetSubstitute
ret nz
ld hl, wEnemyBattleStatus1
ld de, wPlayerMoveEffect
ld a, [H_WHOSETURN]
and a
jr z, .flinchSideEffect
ld hl, wPlayerBattleStatus1
ld de, wEnemyMoveEffect
.flinchSideEffect
ld a, [de]
cp FLINCH_SIDE_EFFECT1
ld b, $1a ; ~10% chance of flinch
jr z, .gotEffectChance
ld b, $4d ; ~30% chance of flinch
.gotEffectChance
call BattleRandom
cp b
ret nc
set Flinched, [hl] ; set mon's status to flinching
call ClearHyperBeam
ret
OneHitKOEffect:
jpab OneHitKOEffect_
ChargeEffect:
ld hl, wPlayerBattleStatus1
ld de, wPlayerMoveEffect
ld a, [H_WHOSETURN]
and a
ld b, XSTATITEM_ANIM
jr z, .chargeEffect
ld hl, wEnemyBattleStatus1
ld de, wEnemyMoveEffect
ld b, ANIM_AF
.chargeEffect
set ChargingUp, [hl]
ld a, [de]
dec de ; de contains enemy or player MOVENUM
cp FLY_EFFECT
jr nz, .notFly
set Invulnerable, [hl] ; mon is now invulnerable to typical attacks (fly/dig)
ld b, TELEPORT ; load Teleport's animation
.notFly
ld a, [de]
cp DIG
jr nz, .notDigOrFly
set Invulnerable, [hl] ; mon is now invulnerable to typical attacks (fly/dig)
ld b, ANIM_C0
.notDigOrFly
xor a
ld [wAnimationType], a
ld a, b
call PlayBattleAnimation
ld a, [de]
ld [wChargeMoveNum], a
ld hl, ChargeMoveEffectText
jp PrintText
ChargeMoveEffectText:
TX_FAR _ChargeMoveEffectText
TX_ASM
ld a, [wChargeMoveNum]
cp RAZOR_WIND
ld hl, MadeWhirlwindText
jr z, .gotText
cp SOLARBEAM
ld hl, TookInSunlightText
jr z, .gotText
cp SKULL_BASH
ld hl, LoweredItsHeadText
jr z, .gotText
cp SKY_ATTACK
ld hl, SkyAttackGlowingText
jr z, .gotText
cp FLY
ld hl, FlewUpHighText
jr z, .gotText
cp DIG
ld hl, DugAHoleText
.gotText
ret
MadeWhirlwindText:
TX_FAR _MadeWhirlwindText
db "@"
TookInSunlightText:
TX_FAR _TookInSunlightText
db "@"
LoweredItsHeadText:
TX_FAR _LoweredItsHeadText
db "@"
SkyAttackGlowingText:
TX_FAR _SkyAttackGlowingText
db "@"
FlewUpHighText:
TX_FAR _FlewUpHighText
db "@"
DugAHoleText:
TX_FAR _DugAHoleText
db "@"
TrappingEffect:
ld hl, wPlayerBattleStatus1
ld de, wPlayerNumAttacksLeft
ld a, [H_WHOSETURN]
and a
jr z, .trappingEffect
ld hl, wEnemyBattleStatus1
ld de, wEnemyNumAttacksLeft
.trappingEffect
bit UsingTrappingMove, [hl]
ret nz
call ClearHyperBeam ; since this effect is called before testing whether the move will hit,
; the target won't need to recharge even if the trapping move missed
set UsingTrappingMove, [hl] ; mon is now using a trapping move
call BattleRandom ; 3/8 chance for 2 and 3 attacks, and 1/8 chance for 4 and 5 attacks
and $3
cp $2
jr c, .setTrappingCounter
call BattleRandom
and $3
.setTrappingCounter
inc a
ld [de], a
ret
MistEffect:
jpab MistEffect_
FocusEnergyEffect:
jpab FocusEnergyEffect_
RecoilEffect:
jpab RecoilEffect_
ConfusionSideEffect:
call BattleRandom
cp $19 ; ~10% chance
ret nc
jr ConfusionSideEffectSuccess
ConfusionEffect:
call CheckTargetSubstitute
jr nz, ConfusionEffectFailed
call MoveHitTest
ld a, [wMoveMissed]
and a
jr nz, ConfusionEffectFailed
ConfusionSideEffectSuccess:
ld a, [H_WHOSETURN]
and a
ld hl, wEnemyBattleStatus1
ld bc, wEnemyConfusedCounter
ld a, [wPlayerMoveEffect]
jr z, .confuseTarget
ld hl, wPlayerBattleStatus1
ld bc, wPlayerConfusedCounter
ld a, [wEnemyMoveEffect]
.confuseTarget
bit Confused, [hl] ; is mon confused?
jr nz, ConfusionEffectFailed
set Confused, [hl] ; mon is now confused
push af
call BattleRandom
and $3
inc a
inc a
ld [bc], a ; confusion status will last 2-5 turns
pop af
cp CONFUSION_SIDE_EFFECT
call nz, PlayCurrentMoveAnimation2
ld hl, BecameConfusedText
jp PrintText
BecameConfusedText:
TX_FAR _BecameConfusedText
db "@"
ConfusionEffectFailed:
cp CONFUSION_SIDE_EFFECT
ret z
ld c, 50
call DelayFrames
jp ConditionalPrintButItFailed
ParalyzeEffect:
jpab ParalyzeEffect_
SubstituteEffect:
jpab SubstituteEffect_
HyperBeamEffect:
ld hl, wPlayerBattleStatus2
ld a, [H_WHOSETURN]
and a
jr z, .hyperBeamEffect
ld hl, wEnemyBattleStatus2
.hyperBeamEffect
set NeedsToRecharge, [hl] ; mon now needs to recharge
ret
ClearHyperBeam:
push hl
ld hl, wEnemyBattleStatus2
ld a, [H_WHOSETURN]
and a
jr z, .playerTurn
ld hl, wPlayerBattleStatus2
.playerTurn
res NeedsToRecharge, [hl] ; mon no longer needs to recharge
pop hl
ret
RageEffect:
ld hl, wPlayerBattleStatus2
ld a, [H_WHOSETURN]
and a
jr z, .player
ld hl, wEnemyBattleStatus2
.player
set UsingRage, [hl] ; mon is now in "rage" mode
ret
MimicEffect:
ld c, 50
call DelayFrames
call MoveHitTest
ld a, [wMoveMissed]
and a
jr nz, .mimicMissed
ld a, [H_WHOSETURN]
and a
ld hl, wBattleMonMoves
ld a, [wPlayerBattleStatus1]
jr nz, .enemyTurn
ld a, [wLinkState]
cp LINK_STATE_BATTLING
jr nz, .letPlayerChooseMove
ld hl, wEnemyMonMoves
ld a, [wEnemyBattleStatus1]
.enemyTurn
bit Invulnerable, a
jr nz, .mimicMissed
.getRandomMove
push hl
call BattleRandom
and $3
ld c, a
ld b, $0
add hl, bc
ld a, [hl]
pop hl
and a
jr z, .getRandomMove
ld d, a
ld a, [H_WHOSETURN]
and a
ld hl, wBattleMonMoves
ld a, [wPlayerMoveListIndex]
jr z, .playerTurn
ld hl, wEnemyMonMoves
ld a, [wEnemyMoveListIndex]
jr .playerTurn
.letPlayerChooseMove
ld a, [wEnemyBattleStatus1]
bit Invulnerable, a
jr nz, .mimicMissed
ld a, [wCurrentMenuItem]
push af
ld a, $1
ld [wMoveMenuType], a
call MoveSelectionMenu
call LoadScreenTilesFromBuffer1
ld hl, wEnemyMonMoves
ld a, [wCurrentMenuItem]
ld c, a
ld b, $0
add hl, bc
ld d, [hl]
pop af
ld hl, wBattleMonMoves
.playerTurn
ld c, a
ld b, $0
add hl, bc
ld a, d
ld [hl], a
ld [wd11e], a
call GetMoveName
call PlayCurrentMoveAnimation
ld hl, MimicLearnedMoveText
jp PrintText
.mimicMissed
jp PrintButItFailedText_
MimicLearnedMoveText:
TX_FAR _MimicLearnedMoveText
db "@"
LeechSeedEffect:
jpab LeechSeedEffect_
SplashEffect:
call PlayCurrentMoveAnimation
jp PrintNoEffectText
DisableEffect:
call MoveHitTest
ld a, [wMoveMissed]
and a
jr nz, .moveMissed
ld de, wEnemyDisabledMove
ld hl, wEnemyMonMoves
ld a, [H_WHOSETURN]
and a
jr z, .disableEffect
ld de, wPlayerDisabledMove
ld hl, wBattleMonMoves
.disableEffect
; no effect if target already has a move disabled
ld a, [de]
and a
jr nz, .moveMissed
.pickMoveToDisable
push hl
call BattleRandom
and $3
ld c, a
ld b, $0
add hl, bc
ld a, [hl]
pop hl
and a
jr z, .pickMoveToDisable ; loop until a non-00 move slot is found
ld [wd11e], a ; store move number
push hl
ld a, [H_WHOSETURN]
and a
ld hl, wBattleMonPP
jr nz, .enemyTurn
ld a, [wLinkState]
cp LINK_STATE_BATTLING
pop hl ; wEnemyMonMoves
jr nz, .playerTurnNotLinkBattle
; .playerTurnLinkBattle
push hl
ld hl, wEnemyMonPP
.enemyTurn
push hl
ld a, [hli]
or [hl]
inc hl
or [hl]
inc hl
or [hl]
and $3f
pop hl ; wBattleMonPP or wEnemyMonPP
jr z, .moveMissedPopHL ; nothing to do if all moves have no PP left
add hl, bc
ld a, [hl]
pop hl
and a
jr z, .pickMoveToDisable ; pick another move if this one had 0 PP
.playerTurnNotLinkBattle
; non-link battle enemies have unlimited PP so the previous checks aren't needed
call BattleRandom
and $7
inc a ; 1-8 turns disabled
inc c ; move 1-4 will be disabled
swap c
add c ; map disabled move to high nibble of wEnemyDisabledMove / wPlayerDisabledMove
ld [de], a
call PlayCurrentMoveAnimation2
ld hl, wPlayerDisabledMoveNumber
ld a, [H_WHOSETURN]
and a
jr nz, .printDisableText
inc hl ; wEnemyDisabledMoveNumber
.printDisableText
ld a, [wd11e] ; move number
ld [hl], a
call GetMoveName
ld hl, MoveWasDisabledText
jp PrintText
.moveMissedPopHL
pop hl
.moveMissed
jp PrintButItFailedText_
MoveWasDisabledText:
TX_FAR _MoveWasDisabledText
db "@"
PayDayEffect:
jpab PayDayEffect_
ConversionEffect:
jpab ConversionEffect_
HazeEffect:
jpab HazeEffect_
HealEffect:
jpab HealEffect_
TransformEffect:
jpab TransformEffect_
ReflectLightScreenEffect:
jpab ReflectLightScreenEffect_
NothingHappenedText:
TX_FAR _NothingHappenedText
db "@"
PrintNoEffectText:
ld hl, NoEffectText
jp PrintText
NoEffectText:
TX_FAR _NoEffectText
db "@"
ConditionalPrintButItFailed:
ld a, [wMoveDidntMiss]
and a
ret nz ; return if the side effect failed, yet the attack was successful
PrintButItFailedText_:
ld hl, ButItFailedText
jp PrintText
ButItFailedText:
TX_FAR _ButItFailedText
db "@"
PrintDidntAffectText:
ld hl, DidntAffectText
jp PrintText
DidntAffectText:
TX_FAR _DidntAffectText
db "@"
IsUnaffectedText:
TX_FAR _IsUnaffectedText
db "@"
PrintMayNotAttackText:
ld hl, ParalyzedMayNotAttackText
jp PrintText
ParalyzedMayNotAttackText:
TX_FAR _ParalyzedMayNotAttackText
db "@"
CheckTargetSubstitute:
push hl
ld hl, wEnemyBattleStatus2
ld a, [H_WHOSETURN]
and a
jr z, .next1
ld hl, wPlayerBattleStatus2
.next1
bit HasSubstituteUp, [hl]
pop hl
ret
PlayCurrentMoveAnimation2:
; animation at MOVENUM will be played unless MOVENUM is 0
; plays wAnimationType 3 or 6
ld a, [H_WHOSETURN]
and a
ld a, [wPlayerMoveNum]
jr z, .notEnemyTurn
ld a, [wEnemyMoveNum]
.notEnemyTurn
and a
ret z
PlayBattleAnimation2:
; play animation ID at a and animation type 6 or 3
ld [wAnimationID], a
ld a, [H_WHOSETURN]
and a
ld a, $6
jr z, .storeAnimationType
ld a, $3
.storeAnimationType
ld [wAnimationType], a
jp PlayBattleAnimationGotID
PlayCurrentMoveAnimation:
; animation at MOVENUM will be played unless MOVENUM is 0
; resets wAnimationType
xor a
ld [wAnimationType], a
ld a, [H_WHOSETURN]
and a
ld a, [wPlayerMoveNum]
jr z, .notEnemyTurn
ld a, [wEnemyMoveNum]
.notEnemyTurn
and a
ret z
PlayBattleAnimation:
; play animation ID at a and predefined animation type
ld [wAnimationID], a
PlayBattleAnimationGotID:
; play animation at wAnimationID
push hl
push de
push bc
predef MoveAnimation
pop bc
pop de
pop hl
ret