diff options
Diffstat (limited to 'de/engine/battle')
-rwxr-xr-x | de/engine/battle/core.asm | 8720 | ||||
-rwxr-xr-x | de/engine/battle/end_of_battle.asm | 91 | ||||
-rw-r--r-- | de/engine/battle/link_battle_versus_text.asm | 23 | ||||
-rw-r--r-- | de/engine/battle/save_trainer_name.asm | 112 |
4 files changed, 8946 insertions, 0 deletions
diff --git a/de/engine/battle/core.asm b/de/engine/battle/core.asm new file mode 100755 index 00000000..6f0ad8b6 --- /dev/null +++ b/de/engine/battle/core.asm @@ -0,0 +1,8720 @@ +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 multi-turn attack like wrap, so player is trapped and cannot execute a move + ld a, $ff + ld [wPlayerSelectedMove], a + jr .selectEnemyMove +.selectPlayerMove + ld a, [wActionResultOrTookBattleTurn] + and a ; has the player already used the turn (e.g. by using an item, trying to run or switching pokemon) + jr nz, .selectEnemyMove + ld [wMoveMenuType], a + inc a + ld [wAnimationID], a + xor a + ld [wMenuItemToSwap], a + call MoveSelectionMenu + push af + call LoadScreenTilesFromBuffer1 + call DrawHUDsAndHPBars + pop af + jr nz, MainInBattleLoop ; if the player didn't select a move, jump +.selectEnemyMove + call SelectEnemyMove + ld a, [wLinkState] + cp LINK_STATE_BATTLING + jr nz, .noLinkBattle +; link battle + ld a, [wSerialExchangeNybbleReceiveData] + cp LINKBATTLE_RUN + jp z, EnemyRan + cp LINKBATTLE_STRUGGLE + jr z, .noLinkBattle + cp LINKBATTLE_NO_ACTION + jr z, .noLinkBattle + sub 4 + jr c, .noLinkBattle +; the link battle enemy has switched mons + ld a, [wPlayerBattleStatus1] + bit 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, [hSerialConnectionStatus] + cp USING_INTERNAL_CLOCK + 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 subtracted 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 LINKBATTLE_RUN + ret z + call LoadScreenTilesFromBuffer1 +.notLinkBattle + call EnemySendOut + xor a + ld [wEnemyMoveNum], a + ld [wActionResultOrTookBattleTurn], a + ld [wAILayer2Encouragement], a + inc a ; reset Z flag + ret + +TrainerBattleVictory: + call EndLowHealthAlarm + ld b, MUSIC_DEFEATED_GYM_LEADER + ld a, [wGymLeaderNo] + and a + jr nz, .gymleader + ld b, MUSIC_DEFEATED_TRAINER +.gymleader + ld a, [wTrainerClass] + cp 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 appropriate lose message, sets carry flag if player blacked out (special case for initial rival fight) +HandlePlayerBlackOut: + ld a, [wLinkState] + cp LINK_STATE_BATTLING + jr z, .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, LINKBATTLE_RUN + ld [wPlayerMoveListIndex], a + call LinkBattleExchangeData + call LoadScreenTilesFromBuffer1 + ld a, [wSerialExchangeNybbleReceiveData] + cp LINKBATTLE_RUN + ld a, $2 + jr z, .playSound + dec a +.playSound + ld [wBattleResult], a + ld a, SFX_RUN + call PlaySoundWaitForCurrent + ld hl, GotAwayText + call PrintText + call WaitForSoundToFinish + call SaveScreenTilesToBuffer1 + scf ; set carry + ret + +CantEscapeText: + 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, 7, 14 + ld [hl], "▶" + ld c, 80 + call DelayFrames + ld [hl], " " + coord hl, 7, 16 + ld [hl], "▶" + ld c, 50 + call DelayFrames + ld [hl], "▷" + ld a, $2 ; select the "ITEM" menu + jp .upperLeftMenuItemWasNotSelected +.oldManName + db "GREIS@" +.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 12, 14 ; clear upper cursor position in right column + Coorda 12, 16 ; clear lower cursor position in right column + ld b, $7 ; top menu item X + jr .leftColumn_WaitForInput +.safariLeftColumn + Coorda 12, 14 + Coorda 12, 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 7, 14 ; clear upper cursor position in left column + Coorda 7, 16 ; clear lower cursor position in left column + ld b, $c ; 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, $c ; 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 CopyStringToCF50 ; 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 "Welche attacke?" + next " @" + +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, 4, 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 "BLOCKIERT@" + +TypeText: + db "TYP@" + +SelectEnemyMove: + ld a, [wLinkState] + sub LINK_STATE_BATTLING + jr nz, .noLinkBattle +; link battle + call SaveScreenTilesToBuffer1 + call LinkBattleExchangeData + call LoadScreenTilesFromBuffer1 + ld a, [wSerialExchangeNybbleReceiveData] + cp LINKBATTLE_STRUGGLE + jp z, .linkedOpponentUsedStruggle + cp LINKBATTLE_NO_ACTION + jr z, .unableToSelectMove + cp 4 + ret nc + ld [wEnemyMoveListIndex], a + ld c, a + ld hl, wEnemyMonMoves + ld b, 0 + add hl, bc + ld a, [hl] + jr .done +.noLinkBattle + ld a, [wEnemyBattleStatus2] + and (1 << 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 LINKBATTLE_RUN ; is the player running from battle? + jr z, .doExchange + ld a, [wActionResultOrTookBattleTurn] + and a ; is the player switching in another mon? + jr nz, .switching +; the player used a move + ld a, [wPlayerSelectedMove] + cp STRUGGLE + ld b, LINKBATTLE_STRUGGLE + jr z, .next + dec b ; LINKBATTLE_NO_ACTION + inc a ; does move equal -1 (i.e. no action)? + jr z, .next + ld a, [wPlayerMoveListIndex] + jr .doExchange +.switching + ld a, [wWhichPokemon] + add 4 + ld b, a +.next + ld a, b +.doExchange + ld [wSerialExchangeNybbleSendData], a + 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] ; player’s turn + and a,SLP | (1 << FRZ) + ret nz + ld hl,ScaredText + call PrintText + xor a + ret +.Ghost ; ghost’s 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 ; accumulate damage taken + ld [hld],a + ld a,[hl] + adc b + ld [hl],a + ld hl,wPlayerNumAttacksLeft + dec [hl] ; did Bide counter hit 0? + jr z,.UnleashEnergy + ld hl,ExecutePlayerMoveDone + jp .returnToHL ; unless mon unleashes energy, can't move this turn +.UnleashEnergy + ld hl,wPlayerBattleStatus1 + res 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 CopyStringToCF50 + 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 _CF50Text + 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 used Reflect, or if a Pokemon with 512 or more Special has used Light Screen +.specialAttackCritCheck + ld hl, wBattleMonSpecial + ld a, [wCriticalHitOrOHKO] + and a ; check for critical hit + jr z, .scaleStats +; in the case of a critical hit, reset the player's and enemy's specials to their base values + ld c, 5 ; special stat + call GetEnemyMonStat + 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 used Reflect, or if a Pokemon with 512 or more Special has used Light Screen +.specialAttackCritCheck + ld hl, wEnemyMonSpecial + ld a, [wCriticalHitOrOHKO] + and a ; check for critical hit + jr z, .scaleStats +; in the case of a critical hit, reset the player's and enemy's specials to their base values + ld hl, wPartyMon1Special + ld a, [wPlayerMonNumber] + ld bc, wPartyMon2 - wPartyMon1 + call AddNTimes + ld a, [hli] + ld b, a + ld c, [hl] + push bc + ld c, 5 ; special stat + call GetEnemyMonStat + ld hl, 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 CopyStringToCF50 + 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 LINKBATTLE_STRUGGLE + jr z, .executeEnemyMove + cp 4 + ret nc +.executeEnemyMove + ld hl, wAILayer2Encouragement + inc [hl] + xor a + ld [wMoveMissed], a + ld [wMoveDidntMiss], a + ld a, $a + ld [wDamageMultipliers], a + call CheckEnemyStatusConditions + jr nz, .enemyHasNoSpecialConditions + jp hl +.enemyHasNoSpecialConditions + ld hl, wEnemyBattleStatus1 + bit 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 CopyStringToCF50 +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 CopyStringToCF50 + 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 CopyStringToCF50 + +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, "E" + ld [hli], a + ld a, "I" + 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 poison 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 poison a poison-type target + jr z, .noEffect + ld a, [hld] + cp POISON ; can't poison 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 all -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, wcf50 + ld bc, $a + jp CopyData + +StatsTextStrings: + db "ANGR@" + db "VERT@" + db "INIT@" + db "SPEZ@" + db "GENA@" + db "FLU@" + +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 = enemyLevel / 4 + cp b ; is rand[0, playerLevel + enemyLevel) >= (enemyLevel / 4)? + 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 diff --git a/de/engine/battle/end_of_battle.asm b/de/engine/battle/end_of_battle.asm new file mode 100755 index 00000000..190992dc --- /dev/null +++ b/de/engine/battle/end_of_battle.asm @@ -0,0 +1,91 @@ +EndOfBattle: + ld a, [wLinkState] + cp LINK_STATE_BATTLING + jr nz, .notLinkBattle +; link battle + ld a, [wEnemyMonPartyPos] + ld hl, wEnemyMon1Status + ld bc, wEnemyMon2 - wEnemyMon1 + call AddNTimes + ld a, [wEnemyMonStatus] + ld [hl], a + call ClearScreen + callab DisplayLinkBattleVersusTextBox + ld a, [wBattleResult] + cp 1 + ld de, YouWinText + jr c, .unk + ld de, YouLoseText + jr z, .unk + ld de, DrawText + coord hl, 4, 8 + jr .placeWinOrLoseString +.unk + coord hl, 6, 8 +.placeWinOrLoseString + call PlaceString + ld c, 200 + call DelayFrames + jr .evolution +.notLinkBattle + ld a, [wBattleResult] + and a + jr nz, .resetVariables + ld hl, wTotalPayDayMoney + ld a, [hli] + or [hl] + inc hl + or [hl] + jr z, .evolution ; if pay day money is 0, jump + ld de, wPlayerMoney + 2 + ld c, $3 + predef AddBCDPredef + ld hl, PickUpPayDayMoneyText + call PrintText +.evolution + xor a + ld [wForceEvolution], a + predef EvolutionAfterBattle +.resetVariables + xor a + ld [wLowHealthAlarm], a ;disable low health alarm + ld [wChannelSoundIDs + Ch4], a + ld [wIsInBattle], a + ld [wBattleType], a + ld [wMoveMissed], a + ld [wCurOpponent], a + ld [wForcePlayerToChooseMon], a + ld [wNumRunAttempts], a + ld [wEscapedFromBattle], a + ld hl, wPartyAndBillsPCSavedMenuItem + ld [hli], a + ld [hli], a + ld [hli], a + ld [hl], a + ld [wListScrollOffset], a + ld hl, wPlayerStatsToDouble + ld b, $18 +.loop + ld [hli], a + dec b + jr nz, .loop + ld hl, wd72c + set 0, [hl] + call WaitForSoundToFinish + call GBPalWhiteOut + ld a, $ff + ld [wDestinationWarpID], a + ret + +YouWinText: + db "GEWONNEN@" + +YouLoseText: + db "VERLOREN@" + +DrawText: + db "UNENTSCHIEDEN@" + +PickUpPayDayMoneyText: + TX_FAR _PickUpPayDayMoneyText + db "@" diff --git a/de/engine/battle/link_battle_versus_text.asm b/de/engine/battle/link_battle_versus_text.asm new file mode 100644 index 00000000..9e5f89cb --- /dev/null +++ b/de/engine/battle/link_battle_versus_text.asm @@ -0,0 +1,23 @@ +; display "[player] VS [enemy]" text box with pokeballs representing their parties next to the names +DisplayLinkBattleVersusTextBox: + call LoadTextBoxTilePatterns + coord hl, 3, 4 + ld b, 7 + ld c, 13 + call TextBoxBorder + coord hl, 4, 5 + ld de, wPlayerName + call PlaceString + coord hl, 4, 10 + ld de, wLinkEnemyTrainerName + call PlaceString +; place bold "VS" tiles between the names + coord hl, 9, 8 + ld a, "V" + ldi [hl], a + ld [hl], "S" + xor a + ld [wUpdateSpritesEnabled], a + callab SetupPlayerAndEnemyPokeballs + ld c, 150 + jp DelayFrames diff --git a/de/engine/battle/save_trainer_name.asm b/de/engine/battle/save_trainer_name.asm new file mode 100644 index 00000000..50f8f052 --- /dev/null +++ b/de/engine/battle/save_trainer_name.asm @@ -0,0 +1,112 @@ +SaveTrainerName: + ld hl,TrainerNamePointers + ld a,[wTrainerClass] + dec a + ld c,a + ld b,0 + add hl,bc + add hl,bc + ld a,[hli] + ld h,[hl] + ld l,a + ld de,wcd6d +.CopyCharacter + ld a,[hli] + ld [de],a + inc de + cp "@" + jr nz,.CopyCharacter + ret + +TrainerNamePointers: +; what is the point of these? + dw YoungsterName + dw BugCatcherName + dw LassName + dw wTrainerName + dw JrTrainerMName + dw JrTrainerFName + dw PokemaniacName + dw SuperNerdName + dw wTrainerName + dw wTrainerName + dw BurglarName + dw EngineerName + dw JugglerXName + dw wTrainerName + dw SwimmerName + dw wTrainerName + dw wTrainerName + dw BeautyName + dw wTrainerName + dw RockerName + dw JugglerName + dw wTrainerName + dw wTrainerName + dw BlackbeltName + dw wTrainerName + dw ProfOakName + dw ChiefName + dw ScientistName + dw wTrainerName + dw RocketName + dw CooltrainerMName + dw CooltrainerFName + dw wTrainerName + dw wTrainerName + dw wTrainerName + dw wTrainerName + dw wTrainerName + dw wTrainerName + dw wTrainerName + dw wTrainerName + dw wTrainerName + dw wTrainerName + dw wTrainerName + dw wTrainerName + dw wTrainerName + dw wTrainerName + dw wTrainerName + +YoungsterName: + db "TEENAGER@" +BugCatcherName: + db "KÄFERSAMMLER@" +LassName: + db "GÖRE@" +JrTrainerMName: + db "PFADFINDER@" +JrTrainerFName: + db "PFADFINDERIN@" +PokemaniacName: + db "#MANIAC@" +SuperNerdName: + db "STREBER@" +BurglarName: + db "DIEB@" +EngineerName: + db "MECHANIKER@" +JugglerXName: + db "JONGLEUR@" +SwimmerName: + db "SCHWIMMER@" +BeautyName: + db "SCHÖNHEIT@" +RockerName: + db "ROCKER@" +JugglerName: + db "JONGLEUR@" +BlackbeltName: + db "SCHWARZGURT@" +ProfOakName: + db "PROF.EICH@" +ChiefName: + db "CHIEF@" +ScientistName: + db "FORSCHER@" +RocketName: + db "ROCKET@" +CooltrainerMName: + db "TRAINER@" +CooltrainerFName: + db "TRAINERIN@" |