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